Mergulhos profundos ou investigando sistemas
Tags: programming, debugging, infrastructure, aws
Published: 2023-01-10 17:29:28

Eu sempre gostei de investigar coisas, qualquer coisa. Quando criança queria saber como os brinquedos funcionavam, desmontava e montava os carrinhos, bicicletas, aparelhos de som, computadores... Vivia tentando entender as partes mecânicas, depois os circuitos e eventualmente os softwares. Cresci com isso, algo muito pessoal e sempre uma grande diversão para mim. Nem todas as pessoas curtem, inclusive talvez a exceção sejam as pessoas que gostam de entender tudo, então não se sinta culpado ou mal por não gostar ou não querer fazer esse tipo de investigação; chame a pessoa que você conhece e sabe que gosta disso para te ajudar, vai ser um prazer para ela. Pra mim, fazer esse tipo de trabalho é tão natural e divertido que às vezes esqueço que também é trabalho!

![Mergulhos profundos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p6ip14pyozd0dbe5bsvk.png)

Toda essa introdução para chegarmos no assunto que quero contar: uma investigação para otimizar custos na AWS. Mas não é só sobre isso, é uma tentativa minha de mostrar que investigar coisas não é linear e não tem fórmula mágica. Bom, vamos ao causo!

Temos uma conta na AWS que rodam alguns sistemas e o custo de NAT Gateway estava um tanto elevado. NAT Gateway é a peça mágica que traduz as conexões que vem de uma rede privada para uma rede pública (ie. a internet). Toda a rede privada tem isso, na sua casa é o roteador que faz esse papel, na AWS temos o NAT Gateway como serviço gerenciado, pagamos e a AWS resolve os problemas de escala, mantém o sistema atualizado, etc — também dá pra fazer direto numa EC2 mas não é o objetivo aqui. Outra peça legal que a AWS fornece são os VPC Endpoints, que permite conectar serviços de múltiplas contas sem integrar a rede ou conectar diretamente com serviços da AWS (que não deixam de ser outras contas, só que gerenciadas pela própria AWS).

A rede em questão só tinha NAT Gateway e nenhum VPC Endpoint, então todos os serviços da AWS que estavam em uso passavam pelo gateway para conseguir chegar na internet e no serviço. Funciona, mas tem um custo elevado perto do custo de um VPC Endpoint (0.045 usd / GB[^1] contra 0.01 usd / GB[^2] do VPC Endpoint). Investiguei rapidamente quais os serviços estavam em uso na conta: SQS, S3, SNS, EC2... talvez os serviços mais comuns da AWS. Criei o VPC Endpoint para o SQS e magicamente o tráfego da NAT Gateway despencou — bateu aquele momento de pânico, será que fiz algo tão errado assim? Mas não, foi só olhar as métricas do endpoint que o tráfego estava todo lá, ufa!

Só isso já deu uma ótima economia, mas não tava feliz e tinha sido fácil demais, zero aprendizados. Resolvi investigar um pouco mais, pra isso fui atrás de outro serviço da AWS: VPC Flow Logs. Habilitando isso dentro de um VPC temos acesso a todas as conexões que existem na rede, de onde elas surgem, para onde vão, quando começaram, quantos bytes trafegaram em cada conexão. Uma ferramenta ótima para investigar redes, porém pode ter um custo elevado dependendo da rede. Para evitar surpresas liguei o serviço, coletei dados por alguns minutos e desliguei. Cada arquivo gerado tinha cerca de 10Mb, compactados, com algo em torno de 1 milhão de entradas. Hora de trazer a ferramenta de análise de dados (que sempre esqueço como usar, obrigado Google por me salvar): pandas.

Primeiro passo era carregar um tanto dos dados e olhar o formato deles, temos várias colunas: IP de origem, IP de destino, porta de origem, instância, serviço da AWS, IP de origem do pacote. Todas as informações necessárias para identificar os fluxos de dados. A documentação da AWS tem alguns exemplos de fluxos e seus significados: https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs-records-examples.html, com isso já sabia qual seria o primeiro passo: como não tinha interesse em saber de onde tinha vindo o pacote, só que ele tinha saído de um NAT Gateway e estava indo para a internet, então filtrei os dados com o IP interno do gateway. Agora fiquei com bem menos linhas para trabalhar, mas ainda assim um volume grande para olhar manualmente.

Agrupei os IPs de destino, filtrei alguns que eu já conhecia e não tinha interesse. Mas ainda estava com um monte de IPs que não diziam muita coisa para mim. Duas ferramentas vieram a mente: `curl` e `host`. A primeira ferramenta faz chamadas HTTP e a segunda resolve DNS e DNS Reverso. Tentei a segunda ferramente primeiro e não tive muito sucesso, só indicava que era uma máquina da AWS (isso eu já sabia, AWS é dona do bloco `3.128.0.0/9`).

```
host 3.239.232.234
234.232.239.3.in-addr.arpa domain name pointer ec2-3-239-232-234.compute-1.amazonaws.com.
```

Isso não me ajuda a descobrir qual o sistema, então vamos para o `curl`. Mas como HTTP vai me ajudar a identificar um IP? Bom, estamos em 2023, maioria dos sistemas tem um certificado e trabalham com HTTPS. No certicado sempre temos o nome comum que aquele sistema responde.

```
curl -v https://3.239.232.234
...
* Server certificate:
*  subject: CN=queue.amazonaws.com
...
```

Ótimo, agora eu sei que o IP `3.239.232.234` é o SQS. Opa! Como assim? Eu criei um VPC Endpoint para ele, não deveria passar mais pelo NAT Gateway. Fiz algo errado? o que está acontecendo? Muitas perguntas... mas tinha mais serviços para identificar e usar `host` e `curl` manualmente não escala. Hora de escrever um código[^3] que imite o funcionamento das ferramentas em python para passar a coluna do DataFrame e deixar trabalhando. Algumas tentativas depois tenho o que eu preciso, rodo em cima dos IPs de destino de tabela, agrupo pelo nome encontrado e tá lá em primeiro lugar o SQS, mas também tem outros serviços da AWS. Vejo quais fazem sentido adicionar VPC Endpoints, crio eles e vou ser feliz na próxima tarefa? Não consigo, preciso saber por que o SQS continua passando no Gateway. Antes disso dou uma olhada nos gráficos e os VPC Endpoints novos fizeram sentido e vão realmente diminuir os custos.

Posso voltar a tentar entender o que aconteceu com o SQS. Olho o nome `queue.amazonaws.com` e vejo que é diferente do `sqs.<region>.amazonaws.com` que estou acostumado a ver, olho a documentação da AWS: https://docs.aws.amazon.com/general/latest/gr/sqs-service.html e entendo o problema, temos aplicações usando o endereço legado do serviço. Uma chuva de perguntas passa pela minha cabeça: Quais aplicações? Onde elas estão? Quais linguagens? Será que são sistemas legados que queremos desligar?

Primeiro passo é investigar se temos algum repositório com código chamando direto esse endereço. Rápida pesquisa no sistema de versionamento e nada relevante, então hora de fazer uma análise mais profunda. Enquanto levantava a relação de serviços acessados já tinha gerado uma lista de nome e IPs, então só filtrei a lista e obtive todos os IPs que atendem `queue.amazonaws.com`; sim, são vários, é um serviço da AWS com altíssima disponibilidade. Agora posso filtrar os flows procurando os endereços de origem que chegam em algum dos IPs dessa lista, o que resultou numa pequena lista de IPs internos — algo em torno de 25 endereços.

Filtro as instâncias EC2 que temos rodando com essa lista de IPs internos, e surpresa, são todos nós de um cluster kubernetes. "É, não vai ser tão fácil encontrar a aplicação" penso eu enquanto procuro como listar os pods de um dado nó do cluster. Mais uma linha de comando gigante e obtenho a lista de aplicações e são muitas! Tento alguns filtros na linha de comando mesmo `cat | cut | sort | unique -c`, vejo alguns sistemas que rodam em todos os nós: coisas padrão do kube, umas duas aplicações grandes e uma lista enorme de aplicações pequenas. Vou atrás do código fonte das maiores aplicações e nada fora do comum e nem usam SQS! Vou seguindo a lista, mas sem muita esperança de encontrar uma aplicação só. Um padrão começa a surgir: muitas aplicações são escritas em python, mas isso não me diz muito ainda. Nesse momento, quase desisto da investigação, versão do `boto3` (biblioteca python para acesso aos serviços da AWS) era relativamente nova em todos os projetos; nada com cara de sistemas legados e sem manutenção; parecia um beco sem saída, então voltei atrás mas retive algumas informações: "python", "endereços legados SQS".

Peguei as poucas informações que tinha e voltei pro Google, mesmo sem esperanças de achar algo. Eis que encontro uma issue no Github do `botocore` (biblioteca que faz o grosso das coisas para a `boto3`): https://github.com/boto/botocore/issues/1418 e tá ali o problema, reportado em 2018! A biblioteca gera e usa os endereços antigos por causa de alguma incompatibilidade do python 2.6, que está há muitos anos descontinuado. Cheguei na solução, não tem solução! Mas não fiquei feliz com isso e fui olhar issues relacionadas, algumas duplicadas, outras com mais informações e encontrei outra: https://github.com/boto/botocore/issues/2705 essa tinha um pull request de 1º de novembro e falava que o problema estava resolvido na `botocore >= 1.29.0`. Vou direto pro terminal, instalo a biblioteca e testo. Problema resolvido! Provavelmente eu não precisava ter olhado para todas as aplicações que estavam rodando — mesmo que por amostragem como eu fiz, uma pesquisa mais certeira poderia ter resolvido meu problema de forma mais rápida. Mas como eu poderia ser mais certeiro se não sabia o que estava procurando?

Depois de tantas idas e vindas, tudo o que me restava fazer era comunicar os times que as bibliotecas `botocore` e `boto3` deveriam ser atualizadas para a última versão e que isso ajudaria na redução de custos. Foi uma grande montanha russa, cheia de voltas, mas no final deu tudo certo. Em outros casos simplesmente não conseguimos achar a causa, seja por falta de tempo (ie. a causa raiz não vale o esforço da investigação) ou por falta de conhecimento do problema que estamos lidando. Sempre peça ajuda! Um outro par de olhos (ou orelhas) ajudam muito. Às vezes só de explicar em qual parede chegamos e como chegamos lá já nos ajuda a pensarmos em outras soluções para o problema.

[^1]: https://aws.amazon.com/vpc/pricing/
[^2]: https://aws.amazon.com/privatelink/pricing/
[^3]: Parte do código python utilizado na análise https://gist.github.com/pedrokiefer/3e8f4103f1094de6018256e0088cf8d8