Pular para o conteúdo
iauaiiauai — portal de tecnologia, IA e Cloud
CloudIniciante

Kubernetes para quem vem do Docker Compose

Traduza Compose em Kubernetes manifest por manifest. Pod, Deployment, Service, ConfigMap e por que depends_on não existe.

Por Equipe iauai · 22 de julho de 2026 · 12 min de leitura

Nesta página

O problema real

Você tem um docker-compose.yml que roda perfeitamente no laptop: API em Django, banco Postgres, Redis pra cache. 5 serviços, docker-compose up e pronto. Aí chega a hora de colocar em produção com Kubernetes. Você abre a documentação oficial, vê "Pod", "ReplicaSet", "Deployment", "Service", "Ingress", "ConfigMap", "Secret"... e pensa: por que tudo tem nome de ficção científica?

A realidade: cada conceito do Compose mapeia exatamente 1:1 para Kubernetes. Service do Compose = Deployment + Service do K8s. Volumes = PVC. Environment = ConfigMap. O problema é que ninguém faz essa tradução lado a lado. Você fica procurando doc, stackoverflows, e 3 horas depois descobre que depends_on não existe em Kubernetes porque... bem, porque o design é diferente.

Conceitos que você já tem, em nomes novos

Vamos começar com um docker-compose.yml real e converter campo por campo:

# docker-compose.yml que você já entende
version: '3.8'

services:
  api:
    image: myapp/api:1.2.0
    container_name: api
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/app
      - REDIS_URL=redis://cache:6379
    depends_on:
      - db
      - cache
    volumes:
      - ./logs:/app/logs

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    volumes:
      - dbdata:/var/lib/postgresql/data

  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  dbdata:

Aqui está a tradução:

ports:Service + Ingress

Em Compose, ports: - "8000:8000" mapeia porta do host. Em K8s, um Pod nunca é alcançado direto. Você precisa:

  1. Service (load balancer interno): expõe pods dentro do cluster
  2. Ingress (roteador HTTP): expõe pra internet
# Kubernetes: Deployment roda os pods
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3  # 3 cópias do container
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: myapp/api:1.2.0
        ports:
        - containerPort: 8000
---
# Kubernetes: Service redireciona tráfego pros Pods
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  type: ClusterIP  # interno ao cluster
  selector:
    app: api
  ports:
  - port: 80        # porta que outros pods usam
    targetPort: 8000  # porta que o container escuta
---
# Kubernetes: Ingress expõe pra internet (HTTPS, DNS, etc)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api
spec:
  ingressClassName: nginx
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api
            port:
              number: 80

Diferença chave: em Compose você vê localhost:8000 do host. Em K8s, você coloca api.example.com no DNS e o Ingress rota. Nada de localhost.

environment:ConfigMap

Em Compose, variáveis diretamente no yml. Em K8s, nunca coloque valores direto. Use ConfigMap (pra não-sensível) ou Secret (pra sensível):

# ConfigMap: valores públicos
apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
data:
  LOG_LEVEL: "INFO"
  CACHE_TTL: "3600"
---
# Secret: valores sensíveis (encoded, não é criptografia real)
apiVersion: v1
kind: Secret
metadata:
  name: api-secret
type: Opaque
stringData:  # Kubernetes vai base64 isso automaticamente
  DATABASE_URL: "postgres://user:pass@db:5432/app"
  REDIS_URL: "redis://cache:6379"
---
# Deployment que usa ConfigMap e Secret
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: myapp/api:1.2.0
        ports:
        - containerPort: 8000
        env:
        # Vem de ConfigMap
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: api-config
              key: LOG_LEVEL
        # Vem de Secret
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: api-secret
              key: DATABASE_URL

volumes:PVC (PersistentVolumeClaim)

Em Compose, volumes: dbdata: cria um diretório anônimo gerenciado. Em K8s, é explícito:

# PersistentVolumeClaim: pede armazenamento
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dbdata
spec:
  accessModes:
    - ReadWriteOnce  # só 1 pod lê e escreve
  storageClassName: standard
  resources:
    requests:
      storage: 10Gi  # 10 gigabytes
---
# StatefulSet: como Deployment, mas com identidade estável (pra DB)
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: db
spec:
  serviceName: db  # importante pra pods se descobrirem
  replicas: 1
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
      - name: postgres
        image: postgres:15
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_DB
          value: "app"
        - name: POSTGRES_USER
          value: "user"
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:  # cada replica pega seu volume
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: standard
      resources:
        requests:
          storage: 10Gi

depends_on: → Health checks explícitos

Aqui é a armadilha: Compose tem depends_on, K8s não. Por quê? Porque em Kubernetes, serviços escalam dinamicamente. API pode ter 1 ou 3 réplicas, db pode estar em outro nó. Você não pode dizer "espera db subir", porque db nunca morre (almejado).

Solução real: probes de saúde. O próprio container avisa quando tá pronto:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: myapp/api:1.2.0
        ports:
        - containerPort: 8000
        
        # Readiness: "tá pronto pra receber tráfego?"
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 10
          timeoutSeconds: 2
        
        # Liveness: "tá vivo ou morreu?"
        livenessProbe:
          httpGet:
            path: /health/live
            port: 8000
          initialDelaySeconds: 15
          periodSeconds: 20
          timeoutSeconds: 2
          failureThreshold: 3  # se falhar 3x, reinicia
        
        # Startup: "ainda tá iniciando?"
        startupProbe:
          httpGet:
            path: /health/startup
            port: 8000
          failureThreshold: 30
          periodSeconds: 10  # tenta por 5 minutos

O código da API precisa responder essas rotas:

# Flask example
@app.route('/health/startup')
def startup():
    # Verificar dependências pesadas (conexão com DB, etc)
    try:
        db.session.execute('SELECT 1')
        return {'status': 'ok'}, 200
    except:
        return {'status': 'not ready'}, 503

@app.route('/health/ready')
def ready():
    # Verificar se pode aceitar requests
    return {'status': 'ready'}, 200

@app.route('/health/live')
def live():
    # Ping simples
    return {'status': 'alive'}, 200

Pod, ReplicaSet, Deployment: por que tantos?

  • Pod: unidade mínima, 1+ containers. Raro você criar direto.
  • ReplicaSet: "quero 3 cópias do Pod". Raro você criar direto.
  • Deployment: "quero 3 cópias do Pod + rolling updates + rollback". Você usa isso 99% das vezes.
# NÃO FAÇA: Pod isolado
apiVersion: v1
kind: Pod
metadata:
  name: api
spec:
  containers:
  - name: api
    image: myapp/api:1.2.0

# FAÇA: Deployment (que gerencia ReplicaSet, que gerencia Pods)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: myapp/api:1.2.0

Se você matarei um Pod, o Deployment percebe ("espera, falha!") e cria outro automaticamente. Em Compose, o container morreu, pronto.

Requests vs. Limits: o que acontece se omitir

Em Compose, nada. Em Kubernetes, é obrigatório (em produção) especificar quanto cada container precisa:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: myapp/api:1.2.0
        
        # Requests: quanto você GARANTE que precisa
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"  # 0.25 CPU
          
          # Limits: nunca passar disso
          limits:
            memory: "512Mi"
            cpu: "500m"

Sem requests, o scheduler pode colocar 100 pods num nó com 2 GB de RAM total. Aí tudo fica lento. Com requests, o scheduler sabe "preciso de no mínimo 25 GB pra esses 100 pods" e falha se não houver.

Armadilhas comuns

Imagem sem tag: image: postgres é postgres:latest. Amanhã sai versão nova, sua aplicação quebra. Sempre coloque versão: postgres:15.2.

ConfigMap no lugar de Secret: você coloca senha em ConfigMap (não é criptografado, base64 é legível). Qualquer um com acesso ao cluster lê. Use Secret sempre pra sensível.

Número de replicas fixo: replicas: 3 tá hardcoded. Use HPA (HorizontalPodAutoscaler) pra escalar automaticamente:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-autoscale
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70  # escala se CPU passar de 70%

Quando NÃO usar Kubernetes

Se sua aplicação roda em 1 container, use Docker simples ou ECS (AWS). K8s é overhead.

Se o time não tem DevOps/SRE dedicado, considera managed K8s (EKS, GKE). Não gerencie cluster você mesmo.

Para prototipagem de 2 semanas, use Compose. K8s só quando chegar em produção.

Próximos passos

Configure alertas e observabilidade com Observabilidade em aplicações com LLM pra monitorar seus pods.

Deploy isso com infra como código: Terraform na prática: sua primeira infra versionada.

Instale kubectl localmente, execute kubectl apply -f api.yaml num cluster gratuito (minikube ou kind), e veja os pods ligarem. A prática é o melhor professor.

Aprenda jogando

Salto de Container

Pule entre containers que morrem e reiniciam sem ser reagendado pelo orquestrador.

Jogar Salto de Container

Teste seu conhecimento

Teste o que você aprendeu sobre Kubernetes

Pergunta 1 de 5

Seu retriever devolve um trecho que corta no meio de uma frase importante, perdendo contexto. Qual é a causa mais provável?

Continue lendo