Back-End

1 abr, 2026

Kubernetes em produção: o que ninguém te conta e você aprende tarde demais

Publicidade

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.