Kubernetes Completo: do Pod ao cluster de produção
Kubernetes é um orquestrador de containers. Tradução: ele pega centenas de containers, distribui em dezenas de servidores, garante que o número certo esteja rodando, reinicia os que quebram, escala por carga, faz deploy sem downtime, roteia tráfego e cuida de estado persistente. É gigante porque os problemas que ele resolve são gigantes. Este guia cobre arquitetura do control plane, todos os objetos que você realmente usa (Pod, Deployment, Service, Ingress, ConfigMap, Secret, PVC, Namespace), RBAC, autoscaling, Helm, observabilidade e os comandos de kubectl que resolvem 95% dos casos. Pré-requisito: entender Docker (se não entende, volte um módulo).
Por que K8s existe: o problema que ele resolve
Com Docker você sobe um container numa máquina. Mas produção real tem perguntas chatas:
K8s responde todas essas. O custo é complexidade e uma curva de aprendizado real. Para apps pequenos um único container num servidor basta; K8s começa a valer a pena quando você tem múltiplos serviços, SLA de uptime, ou precisa de escalabilidade elástica.
A arquitetura: Control Plane + Data Plane
Mental model: K8s é um loop de reconciliação. Você declara o desejado (YAML com “quero 3 réplicas da v2”), salva no etcd via apiserver, e controllers trabalham em loop infinito pra fazer a realidade bater com o desejo. Não é um scheduler de jobs — é um sistema de “convergência contínua”.
Pod — a unidade atômica
Pod é o menor objeto que o K8s agenda. Um Pod é um ou mais containers que compartilham rede (mesmo IP, localhost entre eles), volumes e ciclo de vida. Na prática, 95% dos Pods têm 1 container. Multi-container Pod é o padrão “sidecar” (ex.: app + proxy Envoy, app + log shipper).
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- containerPort: 80
resources:
requests: { cpu: 100m, memory: 64Mi }
limits: { cpu: 500m, memory: 256Mi }| Coisa | Entre containers DO mesmo Pod | Entre Pods |
|---|---|---|
| Rede | localhost, mesma IP | IP próprio, via Service |
| Volume | Podem montar o mesmo | Não compartilham (use PVC) |
| Ciclo de vida | Pod cai, todos caem juntos | Independente |
Não crie Pod direto em prod. Se o node morre, o Pod é perdido e ninguém o recria — Pod não tem “controller”. Use Deployment (stateless), StatefulSet (stateful), DaemonSet (um por node), Job/CronJob (batch). Pod nu é só pra debug: .
Deployment + ReplicaSet — como apps stateless rodam
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels: { app: api }
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 1 a mais pode existir durante o rollout
maxUnavailable: 0 # nenhum pode estar indisponível
template:
metadata:
labels: { app: api }
spec:
containers:
- name: api
image: ghcr.io/me/api:1.2.0
ports: [{ containerPort: 3000 }]
env:
- name: DATABASE_URL
valueFrom: { secretKeyRef: { text: api-secrets, key: db-url } }
readinessProbe:
httpGet: { path: /health, port: 3000 }
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
httpGet: { path: /health, port: 3000 }
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 500m, memory: 512Mi }Comandos de rollout essenciais: , , .
StatefulSet, DaemonSet, Job — quando usar cada um
| Controller | Propósito | Exemplos |
|---|---|---|
| Deployment | Apps stateless com N réplicas intercambiáveis | API HTTP, worker de fila, frontend |
| StatefulSet | Pods com identidade estável (pod-0, pod-1) e PVC por Pod | Postgres, Kafka, Elasticsearch, Redis cluster |
| DaemonSet | Um Pod por node (ou subset via nodeSelector) | Log collector (fluentd), node exporter, CNI |
| Job | Roda até completar N vezes com sucesso | Migração de schema, export de dados |
| CronJob | Job em schedule cron | Backup noturno, cleanup de lixo, relatório diário |
StatefulSet vs Deployment: StatefulSet garante nomes estáveis (, ) e volume persistente por Pod (). Se morre e volta, reengancha no mesmo PVC. Essencial pra bancos e sistemas distribuídos que precisam saber quem é o líder/follower.
Services — 4 tipos, 4 propósitos
Service é o objeto que dá endpoint estável pra um conjunto de Pods. Pods morrem e nascem com IPs diferentes; Service tem um IP virtual (ClusterIP) e um nome DNS (api.default.svc.cluster.local) que persistem.
| Tipo | Escopo | Quando usar |
|---|---|---|
| ClusterIP | Só dentro do cluster | Default. Comunicação entre services. |
| NodePort | Expõe em uma porta 30000-32767 de cada node | Dev, on-prem sem LoadBalancer, debug |
| LoadBalancer | Pede LB externo à cloud (ELB, GLB, Azure LB) | Produção cloud — expõe um service ao mundo |
| ExternalName | DNS CNAME pra fora do cluster | Apontar pra RDS externo, API parceira |
| Headless (clusterIP: None) | Sem IP virtual, só DNS de Pods | StatefulSet — cada Pod precisa ser endereçado |
apiVersion: v1
kind: Service
metadata:
name: api
spec:
type: ClusterIP
selector: { app: api }
ports:
- port: 80 # a porta do Service
targetPort: 3000 # a porta do container nos PodsLoadBalancer por service fica caro. Cada Service type=LoadBalancer na AWS cria um ELB = US$ ~16/mês + tráfego. Em vez disso, o padrão é: 1 LoadBalancer → Ingress Controller → muitos Services ClusterIP. Um LB só, roteamento L7.
Ingress — o roteamento HTTP do cluster
Service opera em L4 (TCP/UDP). Pra roteamento HTTP/S por host ou path com TLS, você quer Ingress. Mas Ingress por si só é só uma regra — precisa de um Ingress Controller (pod que efetivamente faz o roteamento). Os mais comuns: nginx-ingress, Traefik, HAProxy, AWS ALB Controller.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts: [api.meusite.com]
secretName: api-tls
rules:
- host: api.meusite.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service: { text: api-v1, port: { number: 80 } }
- path: /v2
pathType: Prefix
backend:
service: { text: api-v2, port: { number: 80 } }ConfigMap e Secret — separar código de config
| Objeto | Conteúdo | Como injetar |
|---|---|---|
| ConfigMap | Config não-sensível (feature flags, URLs, tunings) | env var, volume de arquivo, argv |
| Secret | Segredo (senha de db, API key, TLS cert) | env var ou volume (preferível volume) |
apiVersion: v1
kind: ConfigMap
metadata: { text: app-config }
data:
LOG_LEVEL: info
FEATURE_NEW_DASHBOARD: "true"
---
apiVersion: v1
kind: Secret
metadata: { text: app-secrets }
type: Opaque
stringData:
db-url: postgres://user:pass@db:5432/app
jwt-key: "super-secret-change-me"
---
# no Pod spec
spec:
containers:
- name: app
image: me/app:1.0
envFrom:
- configMapRef: { text: app-config }
env:
- name: DB_URL
valueFrom: { secretKeyRef: { text: app-secrets, key: db-url } }Secret NÃO é criptografado por default no etcd — só codificado em base64. Habilite encryption-at-rest no kube-apiserver (KMS provider) em produção. Para segredos críticos, use + Vault / AWS Secrets Manager / Azure Key Vault — o Secret no cluster fica sincronizado do cofre real, nunca em plaintext no git.
Mudança de ConfigMap não reinicia Pod automaticamente. Se você usa , o valor é congelado no start. Soluções: (1) mount como volume (atualiza sozinho) e o app faz re-load; (2) adicionar annotation com hash do ConfigMap no PodTemplate ( é um operator que faz isso automaticamente).
Storage — PV, PVC, StorageClass
Volumes em K8s são uma abstração em 3 camadas:
# PVC dinâmico: o StorageClass provisiona o PV sozinho
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { text: pg-data }
spec:
accessModes: [ReadWriteOnce]
storageClassName: gp3
resources:
requests: { storage: 20Gi }
---
apiVersion: v1
kind: Pod
metadata: { text: postgres }
spec:
containers:
- name: pg
image: postgres:16-alpine
volumeMounts:
- { text: data, mountPath: /var/lib/postgresql/data }
volumes:
- name: data
persistentVolumeClaim: { claimName: pg-data }| accessMode | Significado | Backends típicos |
|---|---|---|
| ReadWriteOnce (RWO) | Montado R/W em um node por vez | EBS, disk volumes |
| ReadOnlyMany (ROX) | Vários Pods, só leitura | Config/assets em NFS |
| ReadWriteMany (RWX) | Vários Pods, todos R/W | EFS, CephFS, GlusterFS |
| ReadWriteOncePod (RWOP) | Um único Pod R/W (mais forte que RWO) | PV para Pods únicos |
Namespaces — multi-tenancy dentro do cluster
Namespace é um agrupamento lógico. Recursos com o mesmo nome podem coexistir em namespaces diferentes. Default quando você não define: default. Do sistema: kube-system (control plane), kube-public.
kubectl create namespace staging
kubectl apply -f deployment.yaml -n staging
kubectl get pods -n staging
kubectl config set-context --current --namespace=staging # fica no nsNamespace não é barreira de segurança forte. Serve pra organizar, aplicar quota (), e anexar políticas (RBAC, NetworkPolicy). Pra isolamento forte (tenants hostis), use clusters separados.
RBAC — controle de acesso
4 objetos:
# Permite que o SA "deployer" faça qualquer coisa com Deployments em prod
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployer
namespace: prod
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: deployer-bind
namespace: prod
subjects:
- kind: ServiceAccount
name: ci-bot
namespace: ci
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: deployerAutoscaling — HPA, VPA, Cluster Autoscaler
| Autoscaler | O que escala | Baseado em |
|---|---|---|
| HorizontalPodAutoscaler (HPA) | Número de réplicas do Deployment | CPU, memória, custom metrics (Prometheus) |
| VerticalPodAutoscaler (VPA) | Requests/limits do Pod | Histórico de uso — recomenda ou ajusta |
| Cluster Autoscaler | Número de nodes | Pods pendentes que não cabem nos nodes atuais |
| KEDA | Réplicas, baseado em eventos (fila SQS, Kafka lag) | Event-driven (0 → N) |
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: { text: api-hpa }
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target: { type: Utilization, averageUtilization: 70 }
- type: Resource
resource:
name: memory
target: { type: Utilization, averageUtilization: 80 }HPA precisa de instalado no cluster — não vem por default em clusters DIY. Em EKS/GKE/AKS geralmente já vem. Sem metrics-server, HPA reporta nos targets e não escala nada.
Helm — package manager do K8s
Aplicar 15 YAMLs à mão, com valores diferentes por ambiente, vira pesadelo. Helm empacota isso em um Chart: templates Go com valores parametrizáveis + um values.yaml por ambiente.
# instalar um chart público (Postgres oficial do Bitnami)
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-pg bitnami/postgresql \
--namespace data \
--set auth.postgresPassword=secret \
--set primary.persistence.size=20Gi
# seu próprio chart
helm create minhaapp # scaffolds templates/
helm install meuapp ./minhaapp -f values.prod.yaml
helm upgrade meuapp ./minhaapp -f values.prod.yaml
helm rollback meuapp 3 # volta pra revisão 3
helm uninstall meuappAlternativas ao Helm: (embutido no kubectl — overlays em YAML puro, sem templating); (GitOps pull-based). Em produção profissional, o padrão hoje é Helm + Argo CD: Helm empacota, Argo CD reconcilia do git pro cluster.
Observabilidade — o que você precisa pra dormir à noite
kubectl — o CLI que resolve 95% do dia-a-dia
# Contexto e namespace
kubectl config get-contexts
kubectl config use-context prod
kubectl config set-context --current --namespace=default
# Inspeção
kubectl get pods # lista pods no ns atual
kubectl get pods -A # todos os namespaces
kubectl get pods -o wide # + node, IP
kubectl get pods -w # watch (live)
kubectl describe pod api-xyz # tudo sobre o pod (eventos!)
kubectl get events --sort-by=.lastTimestamp
# Logs e exec
kubectl logs -f deploy/api # segue logs do Deployment
kubectl logs -p pod/api-xyz # logs do container que morreu (--previous)
kubectl exec -it pod/api-xyz -- sh # shell no container
# Debug de rede
kubectl run -it --rm curl --image=curlimages/curl -- sh
kubectl port-forward svc/api 8080:80 # acessa service local em http://localhost:8080
# Mudanças rápidas
kubectl scale deploy/api --replicas=5
kubectl set image deploy/api api=me/api:1.3.0
kubectl rollout status deploy/api
kubectl rollout undo deploy/api
# Apply, diff, explain
kubectl apply -f manifests/
kubectl diff -f manifests/ # o que vai mudar
kubectl explain deployment.spec.strategy # docs do schema direto do cluster
# Copy e debug effêmero
kubectl cp api-xyz:/app/log.txt ./log.txt
kubectl debug pod/api-xyz --image=nicolaka/netshoot # sidecar de debugTroubleshooting — o checklist quando algo quebra
Regra número 1 do K8s: sempre rode antes de . A seção Events no final do describe mostra o que aconteceu, em ordem, com timestamp. 90% dos problemas aparecem ali.
Cenários de decisão
📋 Equipe de 6 devs, 3 microservices, SLA baixo. Precisa de K8s?
K8s introduz complexidade enorme (RBAC, ingress, helm, observabilidade). Pra 3 apps stateless com tráfego moderado, compose + systemd resolve. Mude pra K8s quando tiver 10+ services e time de ops.
Alt: K3s (K8s leve) —
Alt: ECS/Fargate —
📋 20 microservices, 3 ambientes, vários times. Expor cada um?
Um LB só cloud (caro) → Ingress → 20 services ClusterIP. cert-manager renova TLS Let's Encrypt automaticamente. Roteamento L7 por host/path. Custo previsível, governança centralizada.
Alt: LoadBalancer por service —
📋 Preciso rodar Postgres HA dentro do K8s — é boa ideia?
StatefulSet cru não faz HA de Postgres — backup, failover, PITR são não-triviais. Operators encapsulam esse know-how. RDS/Aurora terceirizam o problema inteiro. Só 'Postgres artesanal' se o time entende muito bem.
Alt: RDS/Cloud SQL —
Perguntas típicas
❓ Posso começar a aprender K8s sem ter um cluster pago?
❓ Qual a diferença entre EKS, GKE e AKS?
❓ Preciso escrever YAML todo dia?
❓ K8s faz CI/CD?
❓ O que é um Operator?
Take-aways. (1) K8s é loop de reconciliação: você declara desejado, controllers convergem. (2) Control plane (apiserver + etcd + scheduler + controller-manager) é o cérebro; data plane (kubelet + kube-proxy + runtime) roda as cargas. (3) Pod é átomo, mas você nunca cria Pod direto — use Deployment/StatefulSet/DaemonSet/Job. (4) Service é IP estável; Ingress é roteamento HTTP com TLS. (5) ConfigMap/Secret separam config de código — cuidado com mudança que não propaga. (6) PVC dá volume persistente; StatefulSet garante que cada Pod mantenha o dele. (7) RBAC é obrigatório em prod. (8) HPA escala Pods, Cluster Autoscaler escala nodes, Helm+Argo CD gerencia releases. (9) Sempre antes de . (10) K8s resolve problemas grandes e cobra complexidade — só adote quando o retorno compensar.
Próximos passos sugeridos
Discussão
Carregando…