Kubernetes virou padrão de mercado. Está em quase toda arquitetura moderna, aparece em praticamente toda vaga de DevOps e já virou pré-requisito em times que lidam com sistemas distribuídos. No papel, tudo parece resolvido: orquestração automatizada, escalabilidade elástica, alta disponibilidade e abstração de infraestrutura.
Mas existe um detalhe que raramente entra nas apresentações bonitas: rodar Kubernetes em produção de verdade é outra história. E é exatamente aqui que começa o nível sênior.
O problema: usar não é dominar
A maioria dos times “usa Kubernetes”, mas poucos realmente entendem o que está acontecendo por baixo dos manifests.
Na prática, isso significa:
- Pods reiniciando sem explicação clara
- Consumo de CPU e memória totalmente desbalanceado
- Deploys que funcionam em staging e quebram em produção
- Latência inesperada entre serviços
- Cluster caro e ineficiente
O ponto central é simples:
Kubernetes não resolve problemas de arquitetura. Ele amplifica.
Se sua base não está sólida, o cluster vira um caos distribuído.
Scheduling: o jogo invisível que define tudo
Um dos conceitos mais negligenciados — e mais críticos — é o scheduler.
É ele quem decide onde cada pod vai rodar, considerando recursos, afinidade, restrições e estado do cluster. Quando isso é ignorado, você começa a ter problemas clássicos: nós sobrecarregados enquanto outros ficam ociosos, pods críticos competindo com workloads irrelevantes e latência causada por má distribuição.
Vamos olhar um exemplo mais completo:
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
spec:
replicas: 4
selector:
matchLabels:
app: payments-api
template:
metadata:
labels:
app: payments-api
spec:
containers:
- name: app
image: company/payments-api:1.2.0
resources:
requests:
cpu: "300m"
memory: "256Mi"
limits:
cpu: "800m"
memory: "512Mi"
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- payments-api
topologyKey: "kubernetes.io/hostname"
tolerations:
- key: "dedicated"
operator: "Equal"
value: "backend"
effect: "NoSchedule"
Aqui já entramos em decisões de nível mais avançado.
Você define requests e limits corretamente, evitando tanto desperdício quanto throttling. Usa podAntiAffinity para garantir que réplicas não fiquem no mesmo nó, reduzindo risco de falha simultânea. E ainda trabalha com tolerations para separar workloads críticos de cargas menos importantes.
Isso não é detalhe.
Isso é o que separa cluster saudável de cluster caótico.
Otimização de recursos: onde o dinheiro vai embora
Se tem um ponto que dói em produção, é custo.
Clusters Kubernetes mal configurados queimam dinheiro silenciosamente, principalmente quando:
- Requests são altos demais (reservando recurso sem usar)
- Limits são baixos demais (causando throttling)
- Autoscaling está mal ajustado
- Não existe observabilidade de uso real
Agora, olha um exemplo com autoscaling configurado de forma mais madura:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: payments-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: payments-api
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 100
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 120
policies:
- type: Percent
value: 50
periodSeconds: 60
Esse tipo de configuração evita o clássico efeito “sanfona”, onde o sistema escala agressivamente e depois reduz rápido demais, gerando instabilidade.
Aqui já estamos falando de otimização fina.
Operators e CRDs: quando Kubernetes vira plataforma
Se você ainda está só criando Deployments e Services, você está usando Kubernetes no nível básico.
O salto de maturidade vem com CRDs (Custom Resource Definitions) e Operators, que permitem estender o Kubernetes como se fosse uma API própria da sua empresa.
Na prática, você cria novos “tipos de recurso”.
Exemplo:
apiVersion: platform.company.com/v1
kind: Application
metadata:
name: payments-api
spec:
image: company/payments-api:1.2.0
replicas: 3
resources:
cpu: "500m"
memory: "512Mi"
autoscaling:
enabled: true
min: 2
max: 8
Agora o desenvolvedor não precisa entender Deployment, HPA, Service, ConfigMap…
O Operator faz isso por trás.
E aí vem a parte interessante: o Operator em si é código.
Exemplo simplificado de Operator em Go
func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var app platformv1.Application
if err := r.Get(ctx, req.NamespacedName, &app); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: app.Name,
},
Spec: appsv1.DeploymentSpec{
Replicas: pointer.Int32Ptr(app.Spec.Replicas),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": app.Name},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"app": app.Name},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: app.Name,
Image: app.Spec.Image,
},
},
},
},
},
}
err := r.ApplyDeployment(ctx, deployment)
return ctrl.Result{}, err
}
Aqui você começa a entender:
Kubernetes não é só YAML.
É uma plataforma programável.
Service Mesh: quando a rede vira código
Outro ponto que só aparece quando o sistema cresce é comunicação entre serviços.
Sem controle, você enfrenta:
- Latência imprevisível
- Falhas em cascata
- Falta de visibilidade
- Segurança inconsistente
Service Mesh resolve isso — mas traz complexidade.
Exemplo com Istio:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: payments-api
spec:
hosts:
- payments-api
http:
- route:
- destination:
host: payments-api
subset: v1
weight: 80
- destination:
host: payments-api
subset: v2
weight: 20
Aqui você controla tráfego de forma declarativa.
Isso permite canary releases, testes controlados e rollout progressivo — tudo sem mudar código da aplicação.
Mas também adiciona uma nova camada de complexidade operacional.
Problemas reais de produção (os que ninguém posta no LinkedIn)
Agora a parte que realmente importa.
Esses são problemas que aparecem quando você está rodando Kubernetes em produção de verdade:
- Pods em CrashLoopBackOff sem log útil
- Node ficando “NotReady” por saturação de disco
- DNS interno falhando intermitentemente
- Deploy travando por readiness mal configurado
- Memory leak causando restart silencioso
- Cluster autoscaler criando mais nós do que deveria
E o mais perigoso:
falsos positivos de saúde.
Seu pod está “Running”, mas a aplicação está quebrada.
O papel do DevOps sênior aqui
Nesse nível, você não está mais só aplicando YAML.
Você está:
- Entendendo comportamento do scheduler
- Otimizando custo e performance
- Criando abstrações com Operators
- Controlando tráfego com Service Mesh
- Resolvendo problemas que não aparecem em tutorial
Kubernetes deixa de ser ferramenta…
E vira sistema complexo distribuído sob sua responsabilidade.
Conclusão: Kubernetes não simplifica — ele desloca a complexidade
A promessa do Kubernetes sempre foi abstrair infraestrutura. E ele cumpre isso. Mas em troca, ele introduz uma nova camada de complexidade que exige maturidade técnica para ser operada corretamente.
Se você só aplica manifests… Você está usando Kubernetes. Se você entende como ele se comporta em produção… Você começa a dominar.
E essa diferença é exatamente o que separa um DevOps operacional de um engenheiro de plataforma de verdade.




