Neste post, vou trazer uma experiência recente que tive em um cenário real de produção em que falhei ao tentar escalar meu cluster horizontalmente e também falaremos um pouco sobre escala vertical e horizontal.
Quando falamos de escala vertical, estamos falando do aumento de poder computacional de um recurso que estamos tentando escalar. Um exemplo: você está com um serviço com uma grande volumetria de acessos e a sua capacidade atual não é o suficiente, então você resolve aumentar o número de CPUs e memória da máquina na qual ele está sendo executado. Isso é escala vertical! Aumentar o poder de fogo do recurso, seja em CPU, memória ou disco, é escalar esse recurso verticalmente. Esse é o cenário mais comum de escala e me atrevo a dizer que é o mais simples. Embora hoje tenhamos acesso a máquinas gigantes com muitos recursos de hardware, esses recursos são finitos! E isso é um problema. Também pode não ser a maneira mais barata de aumentar a capacidade de um ambiente.
Abaixo, um exemplo visual de escala vertical:
Escala horizontal é quando aumentamos o número de recursos para dividir a carga de processamento. Se você tem um serviço que está no seu limite, em vez de aumentar o poder computacional do seu ambiente, você criaria um ambiente adicional para dividir a carga de trabalho. Abaixo, um exemplo visual de escala horizontal:
Perceba que agora tenho múltiplas máquinas que podem aliviar altas cargas em um único servidor, porém você terá o ônus e o aumento de complexidade de lidar com a distribuição de carga entre os servidores.
Vertical x Horizontal
Quando falamos em simplicidade de escala, julgo que a escala vertical é mais simples, pois você pode aumentar o tamanho da máquina ou do recurso de maneira mais simples. Porém, você pode ter limitações por causa dos tamanhos finitos dos recursos, além de que, em cenários nos quais a escala é sazonal, fazer o downgrade do recurso pode não ser tão eficiente operacionalmente.
Ao escalar horizontalmente, você lida com uma complexidade maior, pois precisa distribuir as cargas de trabalho e também começa a trabalhar com computação distribuída. Porém, é um cenário muito eficiente para trabalhar com cargas e períodos sazonais, pois você consegue redimensionar a quantidade de máquinas e nós de maneira mais eficiente.
Antes de entrar no problema em si, vale falar um pouco sobre Kubernetes. De forma simples, o Kubernetes é uma ferramenta que ajuda a organizar e escalar aplicações em contêineres. Em vez de você cuidar manualmente de cada servidor, ele gerencia onde cada aplicação vai rodar, reinicia automaticamente quando algo falha, distribui o tráfego, facilita atualizações sem derrubar o sistema e permite aumentar ou diminuir recursos conforme a demanda.
Em resumo, o Kubernetes ajuda nossas aplicações a funcionarem de forma mais estável, escalável e automatizada.
Cenário do problema
Eu tinha um cluster Kubernetes rodando em um EKS na AWS. Nesse cluster, eu usava um Node Group, que basicamente é um grupo de máquinas onde configuramos a quantidade mínima, máxima e desejada de nós.
No meu caso, esse Node Group estava configurado com no mínimo 3 máquinas, no máximo 10 e com 5 máquinas como quantidade desejada. O problema é que essas máquinas eram do tipo t3.small, ou seja, cada uma tinha apenas 2 GB de memória.
Ao realizar o deploy de uma nova aplicação, percebi que o pod não estava subindo corretamente. Quando fui analisar o erro, vi que o problema estava relacionado à falta de memória disponível para agendar aquela aplicação no cluster.
De início, eu ainda enxergava uma certa sobra de memória no ambiente, então tentei resolver aumentando a quantidade de nós do Node Group. Na prática, tentei escalar o cluster de forma horizontal, adicionando mais máquinas para distribuir melhor a carga.
Só que mesmo após esse ajuste, o deploy continuou sem funcionar.
Foi aí que, analisando melhor o cenário, percebi um ponto importante: sempre que um novo nó é criado no cluster, ele não vem “limpo” apenas para rodar minhas aplicações. O próprio Kubernetes e o EKS também precisam rodar vários componentes dentro desses nós, como CoreDNS, plugins de rede, EBS CSI, agentes de observabilidade, DaemonSets e outros add-ons necessários para o funcionamento do cluster.
Ou seja, mesmo adicionando mais máquinas, parte relevante dos recursos desses novos nós já era consumida pela própria infraestrutura do Kubernetes. Como as máquinas eram muito pequenas, esses componentes acabavam ocupando uma fatia grande da memória disponível.
No fim, o problema não era apenas a quantidade de máquinas no cluster. O problema principal era o tamanho das máquinas que eu estava usando.
Foi então que decidi mudar a estratégia e partir para uma escala vertical. Em vez de apenas adicionar mais nós pequenos, criei um novo Node Group com máquinas maiores, usando uma instância t4g.large, com mais capacidade de CPU e memória. Com isso, consegui ter nós mais adequados para suportar tanto os componentes internos do Kubernetes quanto as aplicações que eu precisava rodar.
Essa experiência me mostrou que escalar horizontalmente nem sempre resolve o problema. Às vezes, antes de sair adicionando mais máquinas, precisamos entender se cada nó tem capacidade suficiente para rodar a carga real do ambiente, incluindo não só nossas aplicações, mas também tudo aquilo que o próprio Kubernetes precisa para funcionar.
Em resumo, nem sempre escalar horizontalmente vai ser a melhor solução, assim como nem sempre escalar verticalmente vai resolver todos os problemas. Em qualquer cenário, sempre existirão trade-offs.
Não existe bala de prata. O mais importante é entender bem o problema, avaliar o contexto, os limites do ambiente, o custo, a complexidade operacional e, a partir disso, escolher a abordagem mais eficiente para escalar seus recursos.
Às vezes, a resposta vai ser aumentar o poder computacional da máquina. Em outros momentos, vai ser distribuir a carga entre múltiplos recursos. E em alguns casos, como foi o meu, tentar escalar horizontalmente pode revelar outros gargalos que estavam escondidos no ambiente.
No fim, escalar não é apenas adicionar mais máquinas ou mais CPU. Escalar é tomar boas decisões técnicas com base no cenário real que você tem em produção.