GitHub EN PT

K8s em Exemplos: Checklist de Produção

O que funciona em dev costuma quebrar em produção. Este checklist cobre os pontos essenciais: limites de recursos, health checks, desligamento gracioso e alta disponibilidade.

1-recursos.yaml

Defina requests e limits de recursos. Sem requests, o scheduler não tem informação suficiente para tomar boas decisões e seu Pod pode acabar em um node já sobrecarregado. Sem limits, um vazamento de memória pode derrubar o node inteiro.

resources:
  requests:
    cpu: 100m
    memory: 256Mi
  limits:
    cpu: 500m
    memory: 256Mi

Iguale o limit de memória ao request. Isso coloca o Pod na classe QoS Guaranteed, tornando o scheduling mais previsível. Para CPU, tudo bem ter um limit maior que o request (permitindo burst), mas memória deve ser igual para evitar OOMKills inesperados.

resources:
  requests:
    cpu: 100m
    memory: 512Mi
  limits:
    cpu: 1
    memory: 512Mi
2-probes.yaml

Configure o readiness probe. Sem ele, o tráfego começa a chegar nos Pods antes que eles estejam prontos. Durante rolling updates, os usuários acabam vendo erros. O probe deve verificar se a aplicação realmente consegue atender requisições, não apenas se o processo está rodando.

readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  failureThreshold: 3

Configure o liveness probe. Ele reinicia processos que travaram. Mas cuidado: se o check for muito agressivo, você vai reiniciar Pods saudáveis que estão apenas sob carga alta. Mantenha-o mais simples que o readiness — basta verificar se o processo responde.

livenessProbe:
  httpGet:
    path: /health/live
    port: 8080
  initialDelaySeconds: 15
  periodSeconds: 10
  failureThreshold: 3
3-shutdown.yaml

Trate o sinal SIGTERM. O Kubernetes envia SIGTERM, aguarda o tempo definido em terminationGracePeriodSeconds e depois envia SIGKILL. Sua aplicação precisa capturar o SIGTERM e finalizar as requisições em andamento. O padrão de 30 segundos muitas vezes não é suficiente para operações mais longas.

spec:
  terminationGracePeriodSeconds: 60
  containers:
    - name: api
      lifecycle:
        preStop:
          exec:
            command: ["/bin/sh", "-c", "sleep 5"]

O truque do sleep no preStop: Load balancers levam alguns segundos para remover os endpoints da lista. Sem esse delay, requisições ainda chegam em Pods que já estão terminando. O sleep dá tempo para o LB se atualizar, e só então sua aplicação processa o SIGTERM de forma limpa.

process.on('SIGTERM', async () => {
  console.log('SIGTERM recebido, finalizando requisições...');
  server.close(async () => {
    await db.disconnect();
    process.exit(0);
  });
});
4-replicas.yaml

Rode múltiplas réplicas. Uma única réplica é um ponto único de falha. Durante manutenção de nodes, deploys ou crashes, seu serviço fica indisponível. Use no mínimo 2 réplicas para qualquer serviço importante, e 3 ou mais para os fluxos críticos.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
5-anti-affinity.yaml

Distribua os Pods entre nodes diferentes. Ter 3 réplicas no mesmo node ainda é um ponto único de falha. O anti-affinity garante que os Pods fiquem em nodes separados. Use preferredDuringScheduling para que o agendamento ainda funcione quando houver poucos nodes disponíveis.

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app: api
                topologyKey: kubernetes.io/hostname
6-pdb.yaml

Defina um Pod Disruption Budget. Sem PDB, um kubectl drain pode remover todos os seus Pods de uma vez durante uma manutenção. O PDB garante uma disponibilidade mínima. Com maxUnavailable: 1, apenas 1 Pod pode ficar indisponível por vez.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: api
7-seguranca.yaml

Remova privilégios de root. Rodar como root dentro do container é desnecessário para a maioria das aplicações — e perigoso caso o container seja comprometido. Configure runAsNonRoot: true e especifique um user ID.

spec:
  template:
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000

Use filesystem read-only. Isso impede que atacantes instalem malware no container. Para diretórios que precisam de escrita (como tmp ou cache), use volumes emptyDir. Aproveite e remova todas as capabilities que sua aplicação não precisa.

      containers:
        - name: api
          securityContext:
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false
            capabilities:
              drop: ["ALL"]
          volumeMounts:
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir: {}
8-rolling-update.yaml

Configure a estratégia de rolling update. Os valores padrão são bem conservadores. O maxSurge define quantos Pods extras podem existir durante o update (controla a velocidade). O maxUnavailable define quantos podem ficar fora (controla a capacidade). Para zero downtime, use maxUnavailable: 0.

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
9-labels.yaml

Adicione labels operacionais. Você vai precisar delas quando tudo estiver pegando fogo às 3 da manhã. Use app para selecionar recursos, version para decidir rollbacks, e team para identificar quem é responsável.

metadata:
  name: api
  labels:
    app: api
    app.kubernetes.io/name: api
    app.kubernetes.io/version: "2.1.0"
    app.kubernetes.io/component: backend
    team: platform
    environment: production
10-completo.yaml

Deployment completo pronto para produção. Aqui está tudo junto. Este Deployment sobrevive a falhas de node, faz deploy de forma segura, desliga graciosamente e não roda como root.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app: api
    team: platform
spec:
  replicas: 3
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      terminationGracePeriodSeconds: 60
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app: api
                topologyKey: kubernetes.io/hostname
      containers:
        - name: api
          image: api:2.1.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 100m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 256Mi
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10
          securityContext:
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 5"]

Índice | Use as setas do teclado para navegar