Kubernetes em produção: boas práticas e armadilhas comuns
Luriel Santana · 03 de julho de 2026
Kubernetes virou o padrão de fato para orquestração de containers, mas o caminho entre "subiu no cluster" e "roda bem em produção" é cheio de detalhes que só aparecem quando algo quebra às 3 da manhã. O projeto é um dos mais maduros do ecossistema cloud-native: foi o primeiro a se graduar na CNCF (Cloud Native Computing Foundation), sinalizando estabilidade e ampla adoção. Maturidade do projeto, porém, não significa que sua aplicação esteja pronta para produção. Este post reúne boas práticas concretas e as armadilhas mais comuns que vejo derrubarem clusters.
Requests e limits: o alicerce do scheduling
Definir requests e limits de CPU e memória não é opcional em produção. O request informa ao scheduler quanto de recurso o Pod precisa para ser alocado num nó; o limit define o teto que ele pode consumir.
resources:
requests:
cpu: '250m'
memory: '256Mi'
limits:
cpu: '500m'
memory: '512Mi'
A armadilha número um é não definir limits. Sem teto de memória, um Pod com vazamento pode consumir toda a RAM do nó, disparando o OOM killer e derrubando processos vizinhos — o clássico problema do noisy neighbor. Sem requests corretos, o scheduler toma decisões ruins de alocação e o cluster fica desbalanceado.
Atenção a uma sutileza: CPU é um recurso compressível (o container é apenas throttled ao bater o limit), mas memória não é. Estourar o limit de memória resulta em OOMKilled, não em lentidão. Por isso, evite definir limits de memória muito baixos sem medir o consumo real da aplicação sob carga.
Liveness e readiness probes: saiba o que cada uma faz
Esse é um ponto onde muita gente erra por confundir as responsabilidades:
- readiness probe: decide se o Pod recebe tráfego. Se falha, o endereço do Pod é removido dos endpoints do Service — mas o container não é reiniciado.
- liveness probe: decide se o container deve ser reiniciado. Se falha repetidamente, o kubelet mata e recria o container.
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
A armadilha clássica é uma liveness probe agressiva demais apontando para um endpoint que depende de banco de dados ou de serviços externos. Quando a dependência fica lenta, a probe falha, o container é reiniciado, e o reinício piora a situação — você cria um loop de restarts no pior momento possível. Liveness deve checar apenas se o processo está vivo e não travado; dependências externas pertencem à readiness. Use startupProbe para aplicações com inicialização lenta, evitando que a liveness mate o container antes dele terminar de subir.
PodDisruptionBudget: proteja a disponibilidade durante manutenções
Quando um nó é drenado (upgrade do cluster, autoscaling, manutenção), o Kubernetes despeja os Pods. Sem um PodDisruptionBudget (PDB), todas as réplicas de um serviço podem sair ao mesmo tempo.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: api
O PDB garante um mínimo de Pods disponíveis durante disruptions voluntárias. Cuidado: ele não protege contra falhas involuntárias (queda física de um nó) e, se mal configurado, pode bloquear a drenagem de nós indefinidamente. Combine PDB com múltiplas réplicas e anti-affinity para espalhar os Pods entre nós.
HPA: escale com base em métricas reais
O HorizontalPodAutoscaler ajusta o número de réplicas conforme a carga. Para funcionar com métricas de recurso, ele depende do metrics-server instalado e — ponto crítico — de requests definidos, já que o cálculo de utilização é relativo ao request.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Sem requests de CPU, o HPA não tem base para calcular o percentual de utilização e não escala por essa métrica. Para cargas que não são CPU-bound (filas, latência), considere métricas customizadas ou externas.
Namespaces e RBAC: isole e restrinja
Use namespaces para separar ambientes, times ou domínios — isso habilita quotas, network policies e limites de acesso por fronteira lógica. Sobre acesso, aplique RBAC com o princípio do menor privilégio: conceda a cada ServiceAccount apenas as permissões que ele realmente precisa.
A armadilha aqui é o comodismo de usar cluster-admin ou bindings amplos "para não ter dor de cabeça". Isso transforma qualquer credencial vazada num comprometimento total do cluster. Prefira Role + RoleBinding por namespace e revise periodicamente quem tem o quê.
Segurança de container: não rode como root
Containers rodando como root ampliam drasticamente o impacto de uma falha de aplicação. Configure o securityContext para rodar como usuário sem privilégios e com sistema de arquivos somente leitura quando possível. Atenção: nem todos os campos vivem no mesmo nível — runAsNonRoot e runAsUser podem ficar no securityContext do Pod ou do container, enquanto allowPrivilegeEscalation, readOnlyRootFilesystem e capabilities são do securityContext do container:
# securityContext do container
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ['ALL']
Evite a tag :latest
Usar image: minha-app:latest é uma das armadilhas mais traiçoeiras. Você perde a noção de qual versão está realmente rodando, rollbacks viram adivinhação, e o comportamento do imagePullPolicy pode trazer uma imagem diferente em cada nó. Sempre fixe uma tag imutável de versão e, idealmente, referencie pelo digest (@sha256:...) para garantir reprodutibilidade.
GitOps: o cluster reflete o Git
Aplicar mudanças com kubectl apply manual a partir da máquina de alguém não escala e não deixa rastro. O padrão GitOps — com ferramentas como Argo CD ou Flux, ambos projetos da CNCF — coloca o estado desejado do cluster num repositório Git, que vira a fonte única da verdade. Um controlador reconcilia continuamente o cluster com o que está versionado.
Os ganhos são concretos: todo deploy é auditável via histórico de commits, rollback é um git revert, e o drift entre o que está no Git e o que roda no cluster é detectado e corrigido automaticamente. Pull requests passam a ser o ponto de revisão e aprovação de mudanças de infraestrutura.
Fechando
Nenhuma dessas práticas é exótica — são higiene básica de produção. O padrão que se repete nas armadilhas é a omissão: faltou limit, faltou request, a probe foi configurada no automático, o RBAC ficou aberto. Comece definindo requests/limits e probes corretas em todos os workloads, proteja a disponibilidade com PDB e réplicas, restrinja acesso com RBAC e securityContext, abandone o :latest e mova seus deploys para GitOps. O resultado é um cluster previsível, auditável e que não te acorda de madrugada.
Quer praticar isso de verdade?
Terminal real, ProMind e jornadas guiadas. Conta Essencial grátis, sem cartão.
Criar conta grátis e começar