Kubernetes ou runtime gerenciado de containers: onde o cluster próprio passa a compensar

Escrito por

em

Um time que roda seis serviços em ECS Fargate ou Cloud Run chega, em algum momento, na mesma pergunta: vale a pena assumir um cluster Kubernetes? A resposta honesta não é “depende do tamanho”. É: depende de quantas vezes por trimestre o runtime gerenciado obriga a contornar uma limitação com gambiarra, e de quanto tempo de engenharia essa gambiarra consome.

Este comparativo trata das duas opções como elas se apresentam hoje para times de 5 a 40 pessoas: de um lado, runtimes gerenciados de container (ECS/Fargate, Cloud Run, Azure Container Apps); do outro, Kubernetes — seja cluster gerenciado do provedor, seja auto-hospedado.

A diferença real não é orquestração, é superfície de extensão

Os dois modelos resolvem o básico: sobem containers, fazem rolling deploy, escalam por métrica, reiniciam o que morreu. Quem compara pelo básico conclui que Kubernetes é complexidade desnecessária — e está certo, enquanto o básico for suficiente.

A diferença aparece quando o requisito sai do básico. Kubernetes expõe um modelo de recursos extensível: Custom Resource Definitions, controladores, admission webhooks, operadores. Isso significa que padrões operacionais viram objeto declarativo versionado no repositório. Um operador de Postgres cuida de failover e backup; um controlador de certificados renova TLS; uma policy engine bloqueia deploy de container rodando como root — tudo pela mesma API.

Vale um exemplo concreto de como isso se materializa. Impedir que qualquer container suba com o filesystem raiz gravável é, em Kubernetes, um objeto declarativo aplicado ao namespace inteiro — quem tentar violar recebe erro no kubectl apply, não um comentário em code review. Fora do cluster, a mesma regra vira um passo de pipeline que alguém pode pular com skip ci num deploy de emergência às onze da noite, que é exatamente quando a regra importa.

Runtime gerenciado não tem esse ponto de extensão. Se o comportamento desejado não existe como flag do serviço, ele vira script de CI, função serverless auxiliar ou processo manual documentado no Notion. Cada contorno desses é barato isoladamente e caro no agregado.

O teste prático: liste os últimos dez incidentes ou pedidos de infraestrutura do time e marque quantos foram resolvidos com uma flag do serviço gerenciado e quantos exigiram código auxiliar. Se mais de três exigiram código auxiliar, a superfície de extensão já está fazendo falta.

O custo da fatura e o custo do calendário

A comparação de preço costuma ser feita errado porque compara apenas computação. Em runtime gerenciado, paga-se por vCPU e memória alocados por segundo, com sobretaxa embutida pela abstração — a ordem de grandeza usual é um adicional relevante sobre o preço de máquina equivalente, e a proporção varia bastante entre provedores e regiões. Em Kubernetes gerenciado, paga-se pelas instâncias do node pool, mais uma taxa fixa de control plane por cluster.

Três efeitos mudam a conta quando a carga cresce:

  • Bin packing. Kubernetes empacota vários pods pequenos numa mesma instância. Runtime gerenciado cobra por task, e task pequena com pico raro paga alocação ociosa o tempo todo.
  • Instâncias spot. Node pools spot com PodDisruptionBudget e tolerations bem configurados cortam boa parte do custo de computação de cargas tolerantes a interrupção. Runtimes gerenciados oferecem equivalentes mais limitados.
  • Escala a zero. Aqui o gerenciado ganha. Cloud Run e Container Apps zeram serviço sem tráfego; num cluster, manter o node pool de pé para um serviço ocioso custa mais do que o serviço vale.

Um exercício útil antes de decidir: pegue os serviços atuais, some o CPU e a memória efetivamente usados no percentil 95 e compare com o que está alocado. Em ambientes com muitos serviços pequenos, é comum que a alocação seja várias vezes o uso — a proporção varia demais entre workloads para citar um número, mas ela é mensurável em qualquer painel de métricas e não deveria ser estimada de cabeça. Essa diferença é exatamente o que o bin packing recupera, e é o único ganho de custo do cluster que aparece na fatura sem depender de renegociação com o provedor.

O custo de calendário é o que decide de verdade. Um cluster exige alguém responsável por upgrade de versão a cada poucos meses, por atualização de CRDs e controladores, por gestão de nodes e por dívida de configuração. Não é um projeto — é uma rotina permanente. Times que adotaram cluster sem designar essa responsabilidade acabam com versão desatualizada, controlador quebrado no upgrade seguinte e uma manhã perdida por incidente.

Onde cada modelo trava

Requisito Runtime gerenciado Kubernetes
Deploy simples com rollback Nativo, sem configuração Nativo, mas exige manifesto e pipeline
Escala a zero Nativo em Cloud Run/Container Apps Só com KEDA ou Knative por cima
Job batch com dependência entre etapas Depende de orquestrador externo Job, CronJob e operadores de workflow
Workload com estado (banco, fila, cache) Normalmente inviável — usa serviço gerenciado StatefulSet e operador, com custo operacional real
Rede: mTLS entre serviços, roteamento por header Limitado ao que o serviço expõe Service mesh ou Gateway API
Política de segurança obrigatória no deploy Verificação no CI, contornável Admission controller, bloqueia no cluster
Portabilidade entre provedores Reescrita da camada de deploy Manifests majoritariamente reaproveitáveis
Tempo até o primeiro serviço em produção Horas Dias a semanas, contando pipeline e observabilidade

A linha mais importante da tabela é a última. Runtime gerenciado entrega valor no primeiro dia; cluster cobra adiantado e devolve depois. Adotar Kubernetes antes de ter os requisitos das linhas do meio é pagar adiantado por algo que talvez nunca seja usado.

Há ainda um custo que a tabela não captura: contratação e rotatividade. Manifesto Kubernetes é conhecimento transferível — quem opera cluster num lugar opera em outro. Configuração de runtime gerenciado é conhecimento de provedor, e o time que trocar de nuvem reescreve a camada de deploy inteira. Para produto que roda em ambiente do cliente, ou para empresa com cláusula de portabilidade em contrato, isso deixa de ser preferência técnica e vira requisito comercial.

Os quatro sinais de que o cluster passou a compensar

Não existe número de serviços que funcione como gatilho. Existem sintomas:

  1. Workload com estado entrou no roadmap. Um ClickHouse, um Kafka, um Elasticsearch que o time quer operar em vez de contratar. Runtime gerenciado não tem resposta razoável para isso.
  2. Requisito de rede entre serviços. mTLS obrigatório, roteamento canário por header, política de saída restrita por serviço. São coisas que um mesh resolve declarativamente e que, fora do cluster, viram configuração de load balancer espalhada.
  3. Multi-ambiente com paridade exigida. Quando dev, staging e produção precisam ser o mesmo objeto com valores diferentes, e não três configurações mantidas à mão.
  4. Conta de computação em que o desperdício por alocação já é visível. Quando o time consegue apontar que boa parte do que se paga é CPU alocada e não usada, o bin packing começa a pagar o esforço.

Um sinal isolado não justifica a migração. Dois sinais simultâneos, com pessoa designada para operar o cluster, justificam. Nenhum sinal e a resposta é ficar onde está — o custo de manter um cluster subutilizado é pago em atenção de engenharia, que é o recurso mais escasso do time.

Migrar sem parar o produto

A migração que funciona não é big bang. É mover um serviço de baixo risco, estabilizar a plataforma em cima dele e só então trazer o resto. Na prática:

O primeiro serviço a migrar deve ser stateless, ter tráfego real (para exercitar escala e observabilidade) e não estar no caminho crítico de receita. Um worker de processamento assíncrono costuma servir bem: se ele atrasar cinco minutos durante o ajuste, ninguém perde venda.

Antes de migrar o segundo serviço, o cluster precisa ter três coisas prontas: coleta de métricas e logs equivalente à que já existia, política de recursos definida por namespace e um caminho de rollback testado — não descrito, testado.

Vale fixar limites desde o primeiro deploy, porque adicionar depois exige rediscutir capacidade com o time todo:

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    memory: 512Mi

Dois erros aparecem com frequência nessa fase. O primeiro é copiar os valores de CPU e memória da configuração antiga sem revisar: alocação herdada de runtime gerenciado costuma estar superdimensionada, e trazê-la para o cluster anula o ganho de empacotamento que motivou a migração. O segundo é rodar os dois ambientes em paralelo por tempo indefinido “só por garantia” — o custo dobra, o time mantém duas pipelines e a decisão de desligar o ambiente antigo vai sendo adiada. Defina a data de desligamento junto com a data de corte de tráfego.

A escolha de definir request de CPU sem limit de CPU é deliberada: limite de CPU gera throttling em picos curtos e degrada latência sem economizar nada relevante. Memória é diferente — sem limite, um vazamento derruba o node inteiro em vez de derrubar só o pod.

O passo seguinte, para quem está avaliando agora: pegue a fatura de computação dos últimos três meses, separe quanto é alocação e quanto é uso efetivo, e coloque ao lado a lista de contornos que o time escreveu para driblar limitação do runtime atual. Se o desperdício de alocação sozinho não paga um dia de engenharia por semana, o cluster ainda não compensa — e essa conta deve ser refeita a cada trimestre, não a cada reunião de arquitetura.

Comentários

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *