Artigos

Um baseline de produção para workloads no Kubernetes

Requests, probes, disruption budgets, desligamento gracioso e segurança de pods: as configurações que um Deployment de produção deve ter antes do primeiro incidente.

Os exemplos são genéricos e ilustrativos. Não vêm do ambiente de nenhum cliente: ajuste limites e configurações ao seu próprio sistema.

Por que um baseline

O Kubernetes roda quase qualquer coisa com quase nenhuma configuração, e esse é o problema: os padrões foram escolhidos para começar, não para continuar no ar. As configurações abaixo custam pouco no primeiro dia e caro quando descobertas durante uma queda. Elas valem para Deployments sem estado; workloads com estado precisam de mais.

Requests e limits

O scheduler posiciona os pods pelos requests, e o Horizontal Pod Autoscaler mede a utilização em relação a eles. Um pod sem requests é posicionado às cegas e não pode escalar por CPU.

Memória não é compressível: um contêiner que passa do limite de memória é encerrado. Defina o limite de memória igual ou próximo ao request, para que um pod nunca seja agendado com memória que não pode manter. CPU é diferente — um contêiner acima do limite de CPU sofre throttling, que aparece como latência. Muitas equipes definem requests de CPU e deixam o limite de CPU de fora em serviços sensíveis à latência; qualquer que seja a escolha, meça o throttling.

Três probes, três funções

  • Readiness decide se o pod recebe tráfego. Cuidado com dependências compartilhadas: se todos os pods falharem na readiness ao mesmo tempo, o serviço fica sem nenhum endpoint.
  • Liveness decide se o contêiner é reiniciado. Limite-a ao próprio processo: se ela verifica o banco de dados, uma queda do banco reinicia todos os pods de uma vez.
  • Startup segura as outras probes enquanto uma aplicação lenta inicializa, em vez de inflar o atraso da liveness.

Sobreviver a drenagens de nós e a falhas de zona

Atualizações de nós e o autoscaling removem pods o tempo todo. Um PodDisruptionBudget limita quantas réplicas uma interrupção voluntária pode derrubar de uma vez, e as topology spread constraints mantêm as réplicas em zonas diferentes, para que a falha de uma zona não leve o serviço inteiro junto.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  maxUnavailable: 1
  selector:
    matchLabels: { app: api }

Desligamento gracioso

Quando um pod é excluído, a sua remoção dos endpoints do serviço e o desligamento dos contêineres (o hook preStop e depois o SIGTERM) começam ao mesmo tempo. Uma aplicação que encerra imediatamente perde requisições que ainda estão sendo roteadas para ela. Adicione uma pausa curta no preStop para os balanceadores se atualizarem, trate o SIGTERM concluindo o trabalho em andamento e mantenha o terminationGracePeriodSeconds maior que a pausa somada à drenagem.

Contexto de segurança e imagens

Execute como usuário não root, remova todas as capabilities do Linux, bloqueie a escalada de privilégios e monte o sistema de arquivos raiz como somente leitura; o Pod Security Standard restricted impõe a maior parte disso por namespace. Referencie imagens por digest em vez de uma tag mutável, para que o que foi testado seja exatamente o que roda.

Juntando tudo

Um Deployment com o baseline aplicado. Os valores são ilustrativos: dimensione os requests a partir do uso medido, não deste exemplo.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels: { app: api }
  template:
    metadata:
      labels: { app: api }
    spec:
      terminationGracePeriodSeconds: 45
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels: { app: api }
      securityContext:
        runAsNonRoot: true
        seccompProfile: { type: RuntimeDefault }
      containers:
        - name: api
          image: registry.example.com/api@sha256:<digest>
          resources:
            requests: { cpu: 250m, memory: 256Mi }
            limits: { memory: 256Mi }
          readinessProbe:
            httpGet: { path: /ready, port: 8080 }
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /live, port: 8080 }
            periodSeconds: 10
            failureThreshold: 3
          startupProbe:
            httpGet: { path: /live, port: 8080 }
            periodSeconds: 5
            failureThreshold: 30
          lifecycle:
            preStop:
              sleep: { seconds: 5 }   # native sleep action, Kubernetes 1.30+
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities: { drop: ["ALL"] }
Contato

Quer uma segunda opinião sobre o seu ambiente?

Envie uma descrição curta do seu sistema. Um engenheiro sênior responde por escrito em até um dia útil.

Fale sobre a sua infraestrutura