Docker &
Kubernetes
Masterclass
Arquitetura de contêineres, orquestração de microserviços, segurança e governança de infraestrutura para ambientes de alta performance — do fundamento ao cluster produtivo.
Por que Contêineres?
Da crise do "funciona na minha máquina" à abstração no nível do kernel Linux. Entenda o problema que Docker resolveu e como ele funciona por dentro.
Antes de 2013, empacotar e distribuir software era um pesadelo operacional. Desenvolvedores entregavam binários que dependiam de versões específicas de bibliotecas do sistema operacional, variáveis de ambiente configuradas manualmente e permissões de arquivo que variavam entre máquinas. O resultado era previsível: falhas em produção que nunca aconteciam em desenvolvimento.
A solução da época eram as Máquinas Virtuais — isolar completamente cada aplicação em um SO convidado completo. Funcionava, mas a um custo enorme: cada VM carregava gigabytes de SO, levava minutos para inicializar e desperdiçava memória duplicando o kernel em cada instância. Servidores físicos rodavam 3–5 VMs; hoje, um único host roda centenas de contêineres.
Linha do Tempo: Isolamento de Software
| Era | Tecnologia | Isolamento | Tamanho | Boot | Densidade |
|---|---|---|---|---|---|
| 1990s | Servidores Físicos | Nenhum | — | — | 1 app/máquina |
| 2000s | VMs (VMware, KVM) | Hardware | GB | Minutos | 3–5 VMs/host |
| 2008 | LXC (Linux Containers) | SO | MB | Segundos | Dezenas |
| 2013 | Docker | SO + UX | MB | Segundos | Centenas |
| 2014+ | Kubernetes | SO + Cluster | MB | Milisseg. | Milhares |
As Primitivas Linux que Tornam Tudo Possível
Docker não inventou isolamento. Ele democratizou o acesso a duas funcionalidades do kernel Linux que existiam há anos, mas eram complexas de usar diretamente. Compreender esses mecanismos é fundamental para qualquer certificação CKA ou CKS.
pid — lista de processos própria (PID 1 é o processo principal)net — interface de rede e tabela de roteamento própriasmnt — sistema de arquivos com raiz própria (chroot moderno)uts — hostname e domainname própriosipc — filas de mensagens POSIX isoladasuser — mapeamento UID/GID (root do container ≠ root do host)cgroup — visão isolada da hierarquia de cgroups
cpu.shares — peso relativo de CPUcpu.cfs_quota_us — limite absoluto de CPUmemory.limit_in_bytes — RAM máxima (OOM killer mata se ultrapassar)memory.soft_limit_in_bytes — limite "suave" sem OOMblkio.weight — prioridade de I/O de bloconet_cls — classificação de pacotes de redeSem cgroups, um container poderia consumir 100% da CPU e derrubar todos os vizinhos.
Analogia: O Navio Cargueiro
VMs são como contratar um navio inteiro para transportar cada caixa de mercadoria — enorme, caro e lento. Contêineres são caixas ISO padronizadas compartilhando o mesmo navio (kernel). Cada caixa está isolada, com seu conteúdo e temperatura controlados, mas todas usam a propulsão e a tripulação do navio em comum. O Docker é a grua que carrega e descarrega essas caixas com eficiência.
Como o Docker Engine Funciona por Dentro
Muitos desenvolvedores usam Docker como "caixa preta". Entender a arquitetura interna é essencial para depurar problemas e para as certificações. O Docker não é uma única aplicação — é um ecossistema de componentes:
Container vs VM: Comparação Técnica Detalhada
| Característica | Máquina Virtual | Container Docker |
|---|---|---|
| Isolamento | Hypervisor virtualiza hardware | Kernel compartilhado (namespaces) |
| Sistema Operacional | SO convidado completo | Apenas binários e libs da app |
| Tamanho de imagem | 1–20 GB típico | 10–200 MB típico |
| Tempo de boot | 30s – 5 min | < 1 segundo |
| Overhead de memória | Gigabytes (SO duplicado) | Megabytes |
| Densidade por host | 5–20 VMs | 500–3000 containers |
| Portabilidade | OVF/VMDK (pesado) | Docker image (layers) |
| Segurança de isolamento | Muito alta (hypervisor) | Alta (runc + seccomp) |
| Persistência de dados | Disco virtual da VM | Volumes nomeados ou bind mounts |
VMs e Contêineres não são excludentes
Na prática, a maioria dos clusters Kubernetes em produção roda contêineres dentro de VMs. As VMs fornecem isolamento forte de hardware entre tenants; os contêineres fornecem densidade e portabilidade dentro de cada VM. É camadas de segurança em defesa.
Construindo Imagens de Produção
Como escrever Dockerfiles eficientes, otimizar cache de camadas, usar Multi-Stage Builds e produzir imagens enxutas e seguras para ambientes reais.
Um Dockerfile é uma receita declarativa: cada instrução cria uma camada imutável na imagem final. A ordem das instruções define a eficiência do cache — errar a sequência pode transformar um build de 10 segundos em 5 minutos a cada mudança de código.
O princípio fundamental é: coloque no topo o que muda raramente, e no final o que muda com frequência. O Docker reutiliza camadas cacheadas até encontrar a primeira diferença — tudo abaixo é reconstruído.
Dockerfile Completo: API Node.js de Produção
O exemplo abaixo incorpora as melhores práticas de 2026: Multi-Stage Build para imagem enxuta, usuário não-root, dumb-init para propagação correta de sinais, e HEALTHCHECK para integração com orquestradores.
# ───────────────────────────────────────────────────────────────────────────── # ESTÁGIO 1: Build # Instala todas as dependências (incluindo devDependencies) e transpila o código # ───────────────────────────────────────────────────────────────────────────── FROM node:22-alpine AS builder # Criar usuário não-root desde o estágio de build RUN addgroup -S appgroup && adduser -S appuser -G appgroup WORKDIR /usr/src/app # COPIAR manifests ANTES do código-fonte # Se package.json NÃO mudou, o Docker usa cache e pula o 'yarn install' # Se você copiasse tudo com 'COPY . .' aqui, qualquer mudança de código # invalidaria o cache do install — builds seriam 3x mais lentos. COPY package.json yarn.lock ./ RUN yarn install --frozen-lockfile --production=false # Agora copia o código-fonte (invalida cache apenas quando código muda) COPY . . RUN yarn build # transpila TypeScript → dist/ # ───────────────────────────────────────────────────────────────────────────── # ESTÁGIO 2: Runtime # Imagem final ENXUTA: copia apenas o necessário do estágio anterior # Compiladores, devDependencies e código-fonte ficam fora da imagem final # ───────────────────────────────────────────────────────────────────────────── FROM node:22-alpine AS runtime # dumb-init: PID 1 leve que propaga sinais POSIX corretamente # Sem ele, Node.js roda como PID 1 e ignora SIGTERM → container não fecha gracefully RUN apk add --no-cache dumb-init # Princípio do Menor Privilégio: nunca rodar como root em produção RUN addgroup -S appgroup && adduser -S appuser -G appgroup USER appuser WORKDIR /usr/src/app # Copiar APENAS artefatos do estágio 'builder' — não o código-fonte TypeScript COPY --from=builder --chown=appuser:appgroup /usr/src/app/dist ./dist COPY --from=builder --chown=appuser:appgroup /usr/src/app/node_modules ./node_modules # Variáveis de ambiente default — override via --env-file em produção ENV NODE_ENV=production \ PORT=8080 # EXPOSE é apenas documentação — não abre porta automaticamente # A porta real é mapeada com 'docker run -p 8080:8080' EXPOSE 8080 # HEALTHCHECK: permite que Docker/K8s saibam se o app está saudável # Sem isso, o orquestrador não sabe distinguir "container rodando" de "app respondendo" HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD wget -qO- http://localhost:8080/health || exit 1 # ENTRYPOINT fixo com dumb-init; CMD são os argumentos (substituíveis) ENTRYPOINT ["dumb-init", "--"] CMD ["node", "dist/server.js"]
Anti-padrão: Imagem sem Multi-Stage
Sem Multi-Stage, a imagem final carrega o compilador TypeScript, todos os devDependencies e o código-fonte original. Uma API simples pode chegar a 1,2 GB. Com Multi-Stage, a mesma API fica em 90–150 MB. Em Kubernetes com 50 réplicas, isso é 50 GB a menos de bandwidth por deploy.
Referência Completa de Instruções
| Instrução | Descrição | Camada? | Dica de boas práticas |
|---|---|---|---|
FROM | Define a imagem base | Sim | Use tags imutáveis como node:22.3.0-alpine3.19, não :latest |
RUN | Executa comando durante o build | Sim | Encadeie com && e limpe caches: apt-get clean && rm -rf /var/lib/apt/lists/* |
COPY | Copia arquivos do contexto de build | Sim | Prefira COPY a ADD — ADD tem comportamentos implícitos (untar, URL) |
WORKDIR | Define o diretório de trabalho | Sim | Use caminhos absolutos; cria o diretório automaticamente |
ENV | Variável de ambiente persistente | Sim | Visível em runtime; use ARG para valores apenas no build |
ARG | Argumento de build | Não | Só existe durante docker build --build-arg; não aparece na imagem final |
EXPOSE | Documenta a porta de escuta | Não | Puramente documental — use -p host:container para publicar |
HEALTHCHECK | Sonda de saúde do container | Não | Essential em produção; define --start-period para apps lentos para inicializar |
ENTRYPOINT | Ponto de entrada fixo do container | Não | Use forma exec (["cmd"]), não shell (cmd); recebe sinais diretamente |
CMD | Comando/argumento padrão | Não | Substituível com docker run image outra-coisa |
USER | Define o usuário para execução | Não | Sempre defina antes de CMD/ENTRYPOINT; nunca rode como root |
VOLUME | Cria ponto de montagem | Sim | Declara onde dados externos devem ser montados |
LABEL | Metadados da imagem | Sim | Use para rastreabilidade: LABEL git-commit="abc123" build-date="2026-01-15" |
Exemplos Adicionais: Python FastAPI e Go Binário
FROM python:3.12-slim AS base ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 RUN pip install --no-cache-dir uv # uv: pip ultra-rápido em Rust FROM base AS builder WORKDIR /app COPY requirements.txt . RUN uv pip install --system -r requirements.txt FROM base AS runtime RUN useradd --create-home appuser USER appuser WORKDIR /app COPY --from=builder /usr/local/lib/python3.12 /usr/local/lib/python3.12 COPY --chown=appuser . . EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
FROM golang:1.23-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . # CGO_ENABLED=0: binário estático sem dependência de libc RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server # scratch: imagem absolutamente vazia — apenas o binário Go FROM scratch COPY --from=builder /app/server /server COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ EXPOSE 8080 ENTRYPOINT ["/server"] # Imagem final: ~6 MB. Zero vulnerabilidades (nada para atacar)
Comandos Essenciais: Build, Inspeção e Scan
# Build completo com tag semântica $ docker build \ --target runtime \ --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \ --build-arg GIT_SHA=$(git rev-parse --short HEAD) \ -t minha-api:1.2.0 \ -t registry.empresa.com/api/minha-api:1.2.0 . # Inspecionar camadas e tamanho de cada uma $ docker image history minha-api:1.2.0 --no-trunc IMAGE CREATED CREATED BY SIZE a3f7d9c2b1e0 5 sec ago CMD ["node" "dist/server.js"] 0B b2c4e8a1d5f3 5 sec ago COPY --from=builder ./dist 2.1MB # Scan de vulnerabilidades com Docker Scout (built-in desde Docker 4.17) $ docker scout quickview minha-api:1.2.0 ✓ 0 critical vulnerabilities ✓ 0 high vulnerabilities ‼ 3 medium (base image node:22-alpine) # Listar imagens com tamanho $ docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" REPOSITORY TAG SIZE minha-api 1.2.0 127MB # Push para registry $ docker push registry.empresa.com/api/minha-api:1.2.0 1.2.0: digest: sha256:a3f7d9c2... size: 1579 Push completo!
Como Contêineres se Comunicam
Drivers de rede Docker, estratégias de persistência e as armadilhas de segurança mais comuns em ambientes de produção.
Contêineres são efêmeros por design — ao deletar um container, sua camada de escrita desaparece. Para dados que precisam sobreviver (banco de dados, uploads, logs), Docker oferece três mecanismos de montagem. Para comunicação, quatro drivers de rede atendem cenários distintos.
Drivers de Rede
docker0 e atribui sub-rede (ex: 172.17.0.0/16) a cada container. Containers na mesma bridge se comunicam pelo nome do container (DNS interno do Docker).Port mapping (
-p 8080:80) é necessário para expor ao host.Dev local Apps single-host
O tráfego é cifrado por padrão no Swarm com IPSEC.
Clusters multi-host Swarm / K8s
-p não funciona — o container já usa as portas do host diretamente.Monitoring agents Network tools
Migração bare metal Redes corporativas
Redes User-Defined: Isolamento e DNS
A rede bridge padrão do Docker tem uma limitação importante: containers se comunicam por IP, não por nome. Criar redes bridge customizadas habilita resolução DNS automática por nome de container — prática obrigatória em produção.
# Criar rede interna isolada para o backend $ docker network create \ --driver bridge \ --subnet 10.10.0.0/24 \ --opt "com.docker.network.bridge.name"="br-backend" \ backend-net # Subir PostgreSQL na rede backend $ docker run -d \ --name postgres \ --network backend-net \ -e POSTGRES_PASSWORD=secret \ postgres:16-alpine # A API alcança o banco pelo NOME 'postgres' — não precisa de IP! $ docker run -d \ --name api \ --network backend-net \ -e DATABASE_URL="postgresql://postgres:secret@postgres:5432/app" \ -p 8080:8080 \ minha-api:1.2.0 # Verificar conectividade DNS interno $ docker exec api nslookup postgres Server: 127.0.0.11 Address: 127.0.0.11#53 Name: postgres Address: 10.10.0.2 # Listar todas as redes $ docker network ls NETWORK ID NAME DRIVER SCOPE a3f7d9c2b1e0 backend-net bridge local b2c4e8a1d5f3 bridge bridge local c5d6f0e1a2b3 host host local
Estratégias de Persistência de Dados
| Tipo | Localização no Host | Gerenciado por | Caso de uso ideal | Performance |
|---|---|---|---|---|
| Named Volume | /var/lib/docker/volumes/nome/_data |
Docker Daemon | Banco de dados, dados persistentes em produção | Alta |
| Bind Mount | Qualquer path absoluto do host | Você (sistema de arquivos do host) | Desenvolvimento local com hot-reload de código | Média |
| tmpfs Mount | Memória RAM do host (nunca em disco) | Kernel Linux | Dados sensíveis temporários (tokens, segredos) | Muito alta |
| Volume CSI (K8s) | Storage provider externo (EBS, NFS, Ceph) | Container Storage Interface | Volumes persistentes em clusters Kubernetes | Variável |
# ── Named Volume: dados do PostgreSQL sobrevivem ao container ── $ docker volume create pg-data $ docker run -d \ --name postgres \ -e POSTGRES_PASSWORD=secret \ -v pg-data:/var/lib/postgresql/data \ -p 5432:5432 \ postgres:16-alpine # Inspecionar volume: ver localização real no host $ docker volume inspect pg-data [{ "Mountpoint": "/var/lib/docker/volumes/pg-data/_data", "Driver": "local", "Labels": {} }] # ── Bind Mount: código local sincronizado (desenvolvimento) ── $ docker run -d \ --name api-dev \ -v $(pwd)/src:/usr/src/app/src:ro \ -v $(pwd)/tests:/usr/src/app/tests:ro \ -p 3000:3000 \ minha-api:dev # ── tmpfs: token JWT em memória (nunca persiste em disco) ── $ docker run -d \ --name secure-api \ --tmpfs /run/secrets:rw,noexec,nosuid,size=10m \ minha-api:1.2.0 # ── Backup de volume para arquivo comprimido ── $ docker run --rm \ -v pg-data:/data:ro \ -v $(pwd)/backups:/backup \ alpine tar czf /backup/pg-backup-$(date +%Y%m%d-%H%M%S).tar.gz /data backup: pg-backup-20260115-143022.tar.gz (45MB)
⚠️ NUNCA monte o socket Docker em produção
Montar -v /var/run/docker.sock:/var/run/docker.sock concede controle irrestrito do host Docker a esse container. Um container comprometido pode criar containers privilegiados com acesso root ao host, ler dados de todos os outros containers e exfiltrar segredos. Use alternativas como Podman rootless, Kaniko (builds sem docker daemon) ou BuildKit remoto para pipelines CI/CD.
Orquestrando Stacks Multi-Serviço
Do dev ao staging: defina um stack inteiro em um único YAML e suba tudo com um comando.
O Docker Compose é a ferramenta de orquestração local do Docker. Em vez de lembrar dez comandos docker run com dezenas de flags, você descreve todo o stack em um arquivo YAML e usa docker compose up -d para subir tudo com as dependências corretas.
O Compose v2 (integrado ao Docker CLI desde 4.x) trouxe melhorias significativas: depends_on com condition: service_healthy garante que serviços dependentes só iniciem após o healthcheck passar — resolvendo o famoso problema da API subir antes do banco estar pronto.
Stack Completo: Nginx + API + PostgreSQL + Redis + Adminer
# Docker Compose v2 — integrado ao Docker CLI (sem versão obsoleta) services: # ─── Nginx: Reverse Proxy + TLS Termination ────────────────────────────── nginx: image: nginx:1.27-alpine ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro # :ro = read-only no container - ssl-certs:/etc/ssl/certs - ./nginx/logs:/var/log/nginx depends_on: api: condition: service_healthy # Só sobe após API passar no healthcheck networks: - frontend-net restart: unless-stopped # ─── API Node.js: 3 réplicas com limites de recursos ───────────────────── api: build: context: . target: runtime # Usa o estágio 'runtime' do Multi-Stage Dockerfile args: BUILD_DATE: ${BUILD_DATE} env_file: .env.production environment: DATABASE_URL: postgresql://apiuser:${DB_PASS}@postgres:5432/appdb REDIS_URL: redis://:${REDIS_PASS}@redis:6379/0 NODE_ENV: production deploy: replicas: 3 resources: limits: cpus: "0.5" # máximo 50% de 1 CPU por réplica memory: 512M # OOM killer mata se ultrapassar reservations: cpus: "0.1" memory: 256M healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"] interval: 30s timeout: 5s retries: 3 start_period: 15s # Grace period para o app inicializar depends_on: postgres: condition: service_healthy redis: condition: service_started networks: - frontend-net - backend-net restart: on-failure:3 # Reinicia até 3 vezes em caso de falha # ─── PostgreSQL 16 com Secrets ──────────────────────────────────────────── postgres: image: postgres:16.3-alpine environment: POSTGRES_USER: apiuser POSTGRES_DB: appdb POSTGRES_PASSWORD_FILE: /run/secrets/db_password secrets: - db_password volumes: - pg-data:/var/lib/postgresql/data - ./postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U apiuser -d appdb"] interval: 10s retries: 5 networks: - backend-net restart: unless-stopped # ─── Redis 7: Cache + Session Store com eviction policy ────────────────── redis: image: redis:7.4-alpine command: > redis-server --requirepass ${REDIS_PASS} --maxmemory 256mb --maxmemory-policy allkeys-lru --save 60 1 --appendonly yes volumes: - redis-data:/data networks: - backend-net restart: unless-stopped # ─── Adminer: GUI de banco de dados (apenas em dev/staging) ────────────── adminer: image: adminer:4.8 profiles: - dev # Só sobe com: docker compose --profile dev up ports: - "8888:8080" networks: - backend-net networks: frontend-net: # Nginx ↔ API driver: bridge backend-net: # API ↔ Banco ↔ Cache (sem acesso externo) driver: bridge internal: true # Nenhum container nessa rede acessa a internet volumes: pg-data: redis-data: ssl-certs: secrets: db_password: file: ./secrets/db_password.txt # Arquivo fora do repositório Git
Comandos Compose Essenciais
# Subir stack em background $ docker compose up -d [+] Running 6/6 ✔ Network backend-net Created ✔ Container postgres Healthy ✔ Container redis Started ✔ Container api Healthy ✔ Container nginx Started # Subir com profile de dev (inclui Adminer) $ docker compose --profile dev up -d # Ver status dos serviços $ docker compose ps NAME IMAGE STATUS PORTS nginx nginx:1.27-alpine Up (healthy) 0.0.0.0:443->443/tcp api-1 minha-api:latest Up (healthy) - postgres postgres:16.3 Up (healthy) - # Logs de um serviço específico (follow) $ docker compose logs api -f --tail=50 # Escalar réplicas da API $ docker compose up -d --scale api=5 [+] Scaling api from 3 to 5 # Rebuild somente do serviço api sem parar outros $ docker compose up -d --build api # Parar e remover containers + redes (volumes preservados) $ docker compose down # Parar + remover volumes também (CUIDADO: apaga dados) $ docker compose down -v
Profiles: ambientes diferentes com o mesmo arquivo
Use profiles para marcar serviços como "apenas dev" (Adminer, MailHog, Jaeger) ou "apenas produção". O comando docker compose --profile monitoring up sobe apenas os serviços com esse perfil, mantendo um único docker-compose.yml para todos os ambientes.
Anatomia de um Cluster K8s
Entenda cada componente do Control Plane e dos Worker Nodes, e como eles colaboram para implementar o modelo de reconciliação de estado do Kubernetes.
Kubernetes é fundamentalmente um sistema de reconciliação de estado. Você declara o que deseja ("quero 3 réplicas da API rodando com pelo menos 256 MB de RAM cada"), e os controladores do K8s trabalham continuamente para garantir que a realidade corresponda à sua declaração — criando, deletando ou movendo pods conforme necessário.
Esse modelo declarativo é o que diferencia Kubernetes de scripts de deploy imperativos. Em vez de dizer "execute este container neste servidor", você diz "garanta que este workload esteja rodando" e deixa o K8s decidir como.
Visão Geral do Cluster
Porta de entrada REST
Valida e persiste estado etcd
Banco chave-valor
Algoritmo Raft kube-scheduler
Decide onde pods
serão executados controller-manager
Reconcilia estado
(10+ controllers) cloud-controller
Integra AWS/GCP/Azure
LB, discos, IPs
Componentes do Control Plane em Detalhe
etcdctl snapshot save é mandatório — perder o etcd sem backup significa perder o cluster inteiro.ReplicaSet Controller: garante N réplicas de um pod
Deployment Controller: gerencia RollingUpdate
Node Controller: detecta e reage a nós mortos
Job Controller: garante conclusão de tarefas
ServiceAccount Controller: cria SAs padrão
Componentes do Worker Node
ClusterIP:porta, o kube-proxy é quem faz o load balancing para os pods reais. Em clusters modernos com Cilium, o kube-proxy pode ser substituído por eBPF para melhor performance.O Modelo de Reconciliação: Desired State vs Current State
Quando você aplica um Deployment com replicas: 3 e um pod morre, o ReplicaSet Controller detecta que o Current State (2 pods) difere do Desired State (3 pods) e solicita ao Scheduler que crie um novo pod. Esse loop contínuo de observar → comparar → agir é o coração do Kubernetes. É por isso que o K8s é "self-healing".
Deployando Aplicações Reais
Pods, Deployments, Services, ConfigMaps, Secrets, HPA e as melhores práticas para aplicações de produção no Kubernetes.
No Kubernetes, você raramente cria Pods diretamente. Em vez disso, usa objetos de nível superior como Deployment (apps stateless), StatefulSet (apps com estado, como bancos de dados), DaemonSet (um pod por node, como agentes de monitoring), e Job/CronJob (tarefas pontuais ou agendadas).
Deployment Completo com HPA e Security Context
apiVersion: apps/v1 kind: Deployment metadata: name: minha-api namespace: producao labels: app: minha-api version: "1.2.0" spec: replicas: 3 selector: matchLabels: app: minha-api strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 # Nunca mais que 1 pod indisponível durante update maxSurge: 1 # Máximo 1 pod extra criado durante update template: metadata: labels: app: minha-api spec: serviceAccountName: minha-api-sa # ServiceAccount dedicado (não o default) # Pod-level security: aplica a todos os containers do pod securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 3000 fsGroup: 2000 # Garantir HA: pods em nodes e zonas diferentes affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: topologyKey: "kubernetes.io/hostname" labelSelector: matchLabels: app: minha-api containers: - name: api image: registry.empresa.com/api/minha-api:1.2.0 imagePullPolicy: Always # Sempre verifica novidades no registry ports: - name: http containerPort: 8080 protocol: TCP # Config via ConfigMap (não-sensível) e Secret (sensível) envFrom: - configMapRef: name: api-config env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: api-secrets key: db-password # Limites de recursos: crítico para scheduler e OOM killer resources: requests: # O scheduler usa isso para encontrar nodes com espaço cpu: "100m" # 100 millicores = 0.1 CPU memory: 128Mi limits: # O cgroup mata se ultrapassar esses limites cpu: "500m" # CPU é throttled (não mata), memória mata (OOM) memory: 512Mi # Container-level security securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] # Liveness: "o container está vivo?" — falha = reinicia livenessProbe: httpGet: path: /health/live port: http initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3 # Readiness: "pode receber tráfego?" — falha = remove do Service readinessProbe: httpGet: path: /health/ready port: http initialDelaySeconds: 10 periodSeconds: 5 volumeMounts: - name: tmp mountPath: /tmp # readOnlyRootFilesystem precisa de tmp explícito volumes: - name: tmp emptyDir: {} --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: minha-api-hpa namespace: producao spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: minha-api minReplicas: 3 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 # Escala quando CPU média > 70% - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80
Tipos de Service e Quando Usar
| Tipo | Acessível por | Caso de uso | Exemplo |
|---|---|---|---|
| ClusterIP | Apenas dentro do cluster | Comunicação entre microserviços internos | API → Banco de dados |
| NodePort | IP do node + porta (30000–32767) | Dev/staging sem load balancer | Testes de integração externos |
| LoadBalancer | IP externo via cloud provider | Expor serviços públicos em produção | API pública, frontend |
| ExternalName | CNAME para serviço externo | Integrar serviços externos ao cluster | RDS AWS, Redis ElastiCache |
| Headless | DNS retorna IPs dos pods diretamente | StatefulSets que precisam de DNS por pod | Cassandra, Kafka, etcd |
Hardening de Clusters K8s
RBAC, Network Policies, Pod Security Standards, gerenciamento de Secrets e as principais vulnerabilidades a evitar em 2026.
Um cluster Kubernetes padrão é inseguro por design — criado para flexibilidade, não para segurança. O endurecimento (hardening) requer uma abordagem em camadas: controle de acesso, segmentação de rede, restrições em pods, gerenciamento seguro de segredos e monitoramento de runtime.
Camada 1: RBAC — Quem Pode Fazer o Quê
O RBAC (Role-Based Access Control) é o sistema de autorização do Kubernetes. Todo acesso ao API Server — de kubectl, de aplicações dentro do cluster, de sistemas de CI/CD — deve ser explicitamente autorizado.
# 1. ServiceAccount: identidade da aplicação dentro do cluster apiVersion: v1 kind: ServiceAccount metadata: name: minha-api-sa namespace: producao automountServiceAccountToken: false # NÃO montar token automático (CVE comum) --- # 2. Role: permissões DENTRO do namespace producao apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: minha-api-role namespace: producao rules: # Princípio do Menor Privilégio: só o que a app REALMENTE precisa - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "list"] - apiGroups: [""] resources: ["secrets"] resourceNames: ["api-secrets"] # Apenas UM secret específico verbs: ["get"] --- # 3. RoleBinding: associa o Role ao ServiceAccount apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: minha-api-binding namespace: producao subjects: - kind: ServiceAccount name: minha-api-sa namespace: producao roleRef: kind: Role name: minha-api-role apiGroup: rbac.authorization.k8s.io
Camada 2: Network Policies — Microsegmentação
# Política 1: Bloquear TODO tráfego ingress e egress no namespace apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all namespace: producao spec: podSelector: {} # Aplica a TODOS os pods do namespace policyTypes: - Ingress - Egress --- # Política 2: Permitir Nginx → API (porta 8080 apenas) apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-nginx-to-api namespace: producao spec: podSelector: matchLabels: app: minha-api ingress: - from: - podSelector: matchLabels: app: nginx ports: - protocol: TCP port: 8080 --- # Política 3: Permitir API → PostgreSQL (porta 5432 apenas) apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-api-to-db namespace: producao spec: podSelector: matchLabels: app: postgres ingress: - from: - podSelector: matchLabels: app: minha-api ports: - protocol: TCP port: 5432
Top 5 Vulnerabilidades em Clusters K8s (2026)
privileged: true ou montar /proc do host permite container escape — obter acesso root ao node. Aplique Pod Security Standards (Restricted) e use Admission Controllers como Kyverno para bloquear automaticamente.EncryptionConfiguration com AES-GCM ou use external KMS (AWS KMS, Vault). Um atacante com acesso ao etcd compromete todo o cluster.verbs: ["*"] em ClusterRole ou acesso a secrets de todos os namespaces. Audite com kubectl auth can-i --as=system:serviceaccount:producao:minha-api-sa --list regularmente.kubectl port-forward e tokens com tempo de expiração curto.Métricas, Logs e Traces
A tríade que transforma um cluster cego em infraestrutura inteligente. Stack Prometheus + Loki + Tempo com Grafana.
Você não pode gerenciar o que não consegue medir. Em ambientes de contêineres, a observabilidade vai além de "o serviço está rodando?" — requer entender latência, throughput, erros, uso de recursos e a correlação entre eventos distribuídos.
Os Três Pilares
/metrics expostos pelas aplicações e pelo próprio K8s. kube-state-metrics expõe o estado dos objetos K8s; node-exporter expõe métricas do SO. O Grafana visualiza com dashboards e gerencia alertas via AlertManager./var/log/pods/ em cada node e enviam para o Loki. Ao contrário do Elasticsearch, o Loki não indexa o conteúdo dos logs — apenas os labels — tornando-o mais barato e simples. Queries com LogQL no Grafana.PromQL: Queries Essenciais para Kubernetes
# Taxa de requisições por segundo (golden signal: throughput) rate(http_requests_total{namespace="producao",job="minha-api"}[5m]) # Percentil 99 de latência (golden signal: latency) histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{namespace="producao"}[5m]) ) # Taxa de erros 5xx (golden signal: errors) sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) # Uso de CPU por pod (% do limit) 100 * ( rate(container_cpu_usage_seconds_total{namespace="producao",container!=""}[5m]) / container_spec_cpu_quota{namespace="producao",container!=""} * 100000 ) # Pods em CrashLoop nos últimos 15 minutos rate(kube_pod_container_status_restarts_total{namespace="producao"}[15m]) * 60 * 15 > 5 # Nodes com pressão de memória kube_node_status_condition{condition="MemoryPressure",status="true"} == 1 # Estimativa de quando o disco ficará cheio (próximas 4 horas) predict_linear( node_filesystem_avail_bytes{mountpoint="/"}[1h], 4 * 3600 ) < 0
Kubectl: Toolkit de Debug em Produção
# Ver logs de um deployment (follow, últimas 100 linhas) $ kubectl logs -n producao deploy/minha-api --tail=100 -f # Logs de um pod específico + container específico (multi-container pod) $ kubectl logs -n producao minha-api-7d9b4c-xk2p1 -c api --previous # Describe: ver eventos de erro e estado detalhado do pod $ kubectl describe pod -n producao minha-api-7d9b4c-xk2p1 Warning BackOff 2m kubelet Back-off restarting failed container Warning OOMKilled 5m kubelet Container exceeded memory limit (512Mi) # Top pods: uso atual de CPU e memória $ kubectl top pods -n producao --sort-by=memory NAME CPU(cores) MEMORY(bytes) minha-api-7d9b4c-xk2p1 45m 487Mi ← perto do limite de 512Mi! minha-api-7d9b4c-p1q2r 32m 187Mi # Exec: entrar no container para debug interativo $ kubectl exec -it -n producao minha-api-7d9b4c-xk2p1 -- sh # Debug com container efêmero (sem modificar o pod) $ kubectl debug -it minha-api-7d9b4c-xk2p1 \ --image=nicolaka/netshoot \ --target=api -n producao # Port-forward: acesso local sem expor serviço externamente $ kubectl port-forward svc/minha-api 8080:80 -n producao Forwarding from 127.0.0.1:8080 -> 80 # Ver eventos do cluster (últimas 1 hora) $ kubectl get events -n producao --sort-by='.lastTimestamp' # Rollback imediato para versão anterior $ kubectl rollout undo deployment/minha-api -n producao deployment.apps/minha-api rolled back # Ver histórico de rollouts $ kubectl rollout history deployment/minha-api -n producao REVISION CHANGE-CAUSE 1 deploy: versao 1.1.0 2 deploy: versao 1.2.0 (current)
Roadmap para 2026 e Além
Certificações CNCF, ferramentas complementares e o estado do mercado de Platform Engineering no Brasil.
O mercado de 2026 recompensa especialistas que combinam conhecimento profundo de Kubernetes com expertise em segurança e observabilidade. As certificações CNCF são hands-on (você opera um cluster real durante o exame), tornando-as muito mais valiosas que provas teóricas.
Roadmap CNCF: Do Zero ao CKS
KCNA — Kubernetes & Cloud Native Associate
Prova teórica de múltipla escolha (90 min, online). Cobre conceitos Cloud Native, ecossistema CNCF, arquitetura básica de K8s e fundamentos de containers. Pré-requisito recomendado antes das provas hands-on.
KCSA — Kubernetes & Cloud Native Security Associate
Complementa KCNA com foco em segurança Cloud Native: ameaças, supply chain security, políticas de compliance. Teórico, boa preparação para o CKS.
CKA — Certified Kubernetes Administrator
Prova hands-on (2h). Você opera um cluster real em ambiente Linux. Tópicos: instalação com kubeadm, troubleshooting, RBAC, networking, storage, upgrades de cluster, backup/restore do etcd. Válida por 2 anos. A certificação K8s de maior ROI no mercado.
CKAD — Certified Kubernetes Application Developer
Prova hands-on (2h). Foco no desenvolvedor: criar e gerenciar workloads, definir resources e probes, usar ConfigMaps e Secrets, Jobs, CronJobs, Services, Ingress. Complementar ao CKA.
CKS — Certified Kubernetes Security Specialist
O pico da especialização K8s. Hands-on (2h). Cobre cluster hardening, minimização de vulnerabilidades de sistema, supply chain security (Cosign, OPA/Gatekeeper), runtime security com Falco, análise de imagens, network policies avançadas e auditoria de logs.
Mercado de Trabalho: Salários 2026
| Cargo | Certificações Típicas | Salário BR (CLT) | Remoto Global (USD/ano) |
|---|---|---|---|
| DevOps Engineer Jr. | KCNA, Docker DCA | R$ 6.000–9.000 | $55k–75k |
| DevOps Engineer Sr. | CKA + CKAD | R$ 12.000–18.000 | $90k–120k |
| Platform Engineer | CKA + Helm + ArgoCD | R$ 15.000–22.000 | $100k–135k |
| SRE (Site Reliability) | CKA + observabilidade | R$ 14.000–22.000 | $95k–130k |
| Cloud Security Engineer | CKS + KCSA | R$ 18.000–32.000 | $130k–180k |
Ferramentas Complementares Essenciais
Teste seu Conhecimento
20 questões no estilo CKA/CKAD. Cada resposta incluí explicação técnica detalhada.