Edição 2026 · Engenharia de Plataforma

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.

10Capítulos
30+Exemplos reais
20Questões CKA
2026Stack atualizado
Docker Engine 27 Kubernetes 1.31 containerd 2.0 Helm 3.17 Prometheus Grafana Loki Istio Falco ArgoCD

Osvaldo J. Filho

Perito Digital · Engenheiro de Plataforma · perito.digital  |  linkedin.com/in/ojaneri

— Capítulo 01 · Fundamentos & Evolução

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

EraTecnologiaIsolamentoTamanhoBootDensidade
1990sServidores FísicosNenhum——1 app/máquina
2000sVMs (VMware, KVM)HardwareGBMinutos3–5 VMs/host
2008LXC (Linux Containers)SOMBSegundosDezenas
2013DockerSO + UXMBSegundosCentenas
2014+KubernetesSO + ClusterMBMilisseg.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.

🔲 Namespaces — "Bolha de Visão"
Cada processo dentro de um namespace acredita ser o único no sistema. O kernel mantém 7 tipos:

pid — lista de processos própria (PID 1 é o processo principal)
net — interface de rede e tabela de roteamento próprias
mnt — sistema de arquivos com raiz própria (chroot moderno)
uts — hostname e domainname próprios
ipc — filas de mensagens POSIX isoladas
user — mapeamento UID/GID (root do container ≠ root do host)
cgroup — visão isolada da hierarquia de cgroups
📊 Cgroups — "Cotas de Recursos"
Control Groups (Linux 2.6.24+) agrupam processos e impõem limites de recursos físicos:

cpu.shares — peso relativo de CPU
cpu.cfs_quota_us — limite absoluto de CPU
memory.limit_in_bytes — RAM máxima (OOM killer mata se ultrapassar)
memory.soft_limit_in_bytes — limite "suave" sem OOM
blkio.weight — prioridade de I/O de bloco
net_cls — classificação de pacotes de rede

Sem 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:

Camada de Cliente
docker CLI ─── REST/Unix socket ───→ Docker Daemon (dockerd) ←── eventos/responses ─── Docker Desktop / Engine
↓ dockerd delega para o container runtime (OCI padrão)
Container Runtime (OCI / CRI)
containerd (high-level runtime) → runc (low-level: cria namespaces + cgroups) → Kernel Linux
↓ sistema de arquivos em camadas
OverlayFS — Sistema de Arquivos em Camadas
Camada base (alpine/ubuntu — read-only) + Camada do app (sua instrução RUN — read-only) + Container Layer (read-write — descartada ao deletar)

Container vs VM: Comparação Técnica Detalhada

CaracterísticaMáquina VirtualContainer Docker
IsolamentoHypervisor virtualiza hardwareKernel compartilhado (namespaces)
Sistema OperacionalSO convidado completoApenas binários e libs da app
Tamanho de imagem1–20 GB típico10–200 MB típico
Tempo de boot30s – 5 min< 1 segundo
Overhead de memóriaGigabytes (SO duplicado)Megabytes
Densidade por host5–20 VMs500–3000 containers
PortabilidadeOVF/VMDK (pesado)Docker image (layers)
Segurança de isolamentoMuito alta (hypervisor)Alta (runc + seccomp)
Persistência de dadosDisco virtual da VMVolumes 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.

— Capítulo 02 · Dockerfile & Imagens

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.

Dockerfile — API Node.js · Multi-Stage · Produção
# ─────────────────────────────────────────────────────────────────────────────
# 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çãoDescriçãoCamada?Dica de boas práticas
FROMDefine a imagem baseSimUse tags imutáveis como node:22.3.0-alpine3.19, não :latest
RUNExecuta comando durante o buildSimEncadeie com && e limpe caches: apt-get clean && rm -rf /var/lib/apt/lists/*
COPYCopia arquivos do contexto de buildSimPrefira COPY a ADD — ADD tem comportamentos implícitos (untar, URL)
WORKDIRDefine o diretório de trabalhoSimUse caminhos absolutos; cria o diretório automaticamente
ENVVariável de ambiente persistenteSimVisível em runtime; use ARG para valores apenas no build
ARGArgumento de buildNãoSó existe durante docker build --build-arg; não aparece na imagem final
EXPOSEDocumenta a porta de escutaNãoPuramente documental — use -p host:container para publicar
HEALTHCHECKSonda de saúde do containerNãoEssential em produção; define --start-period para apps lentos para inicializar
ENTRYPOINTPonto de entrada fixo do containerNãoUse forma exec (["cmd"]), não shell (cmd); recebe sinais diretamente
CMDComando/argumento padrãoNãoSubstituível com docker run image outra-coisa
USERDefine o usuário para execuçãoNãoSempre defina antes de CMD/ENTRYPOINT; nunca rode como root
VOLUMECria ponto de montagemSimDeclara onde dados externos devem ser montados
LABELMetadados da imagemSimUse para rastreabilidade: LABEL git-commit="abc123" build-date="2026-01-15"

Exemplos Adicionais: Python FastAPI e Go Binário

Dockerfile — Python FastAPI · Produção
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"]
Dockerfile — Go · Imagem Final < 10 MB com scratch
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

bash — Docker Build & Image Management
# 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!
— Capítulo 03 · Rede & Persistência

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

🌉 Bridge — Padrão de Rede Local
Cria uma rede virtual privada em um único host Docker. O daemon cria uma interface virtual 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
🕸️ Overlay — Redes Multi-Host
Cria uma rede virtualizada sobre múltiplos hosts Docker. Usa VXLAN para encapsular pacotes UDP e tunelá-los entre nós. Essencial para Docker Swarm e é a base dos CNIs Kubernetes (Flannel, Weave).

O tráfego é cifrado por padrão no Swarm com IPSEC.

Clusters multi-host Swarm / K8s
⚡ Host — Performance Máxima
Remove o isolamento de rede: o container compartilha a interface de rede do host diretamente. Melhor performance (sem overhead de NAT), mas zero isolamento de rede.

-p não funciona — o container já usa as portas do host diretamente.

Monitoring agents Network tools
📡 Macvlan — IP Físico Real
Atribui um endereço MAC e IP físicos ao container, tornando-o visível na rede local como um dispositivo real. Ideal para migrar aplicações legadas que precisam de IP fixo na LAN ou integrar com VLANs existentes.

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.

bash — Criando e usando redes customizadas
# 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

TipoLocalização no HostGerenciado porCaso de uso idealPerformance
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
bash — Volumes na prática: criação, uso e backup
# ── 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.

— Capítulo 04 · Docker Compose

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.yml — Stack de Produção Comentado
# 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

bash — Docker Compose Workflow Completo
# 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.

— Capítulo 05 · Kubernetes: Arquitetura

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

CONTROL PLANE — "O Cérebro do Cluster"
kube-apiserver
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
↕ kubectl / API calls (TLS mútuo)
WORKER NODE 1
kubelet kube-proxy containerd
Pod: api-1 (8080) Pod: api-2 (8080)
WORKER NODE 2
kubelet kube-proxy containerd
Pod: api-3 (8080) Pod: postgres-0
WORKER NODE 3
kubelet kube-proxy containerd
Pod: redis-0 Pod: nginx-1

Componentes do Control Plane em Detalhe

🔑 kube-apiserver
O único ponto de entrada para o cluster. Todo o estado passa por ele: kubectl, kubelet, controllers — todos falam com o API Server via HTTPS REST. Ele autentica via certificados X.509, autoriza via RBAC, valida via Admission Controllers e persiste no etcd. Alta disponibilidade exige pelo menos 3 réplicas do API Server com load balancer à frente.
💾 etcd
Banco de dados distribuído chave-valor usando o algoritmo Raft para consenso entre membros. Armazena todo o estado do cluster: pods, services, config maps, secrets, RBAC. Exige número ímpar de membros (3, 5 ou 7) para quórum. Backup regular via etcdctl snapshot save é mandatório — perder o etcd sem backup significa perder o cluster inteiro.
📅 kube-scheduler
Monitora pods com status "Pending" (sem node atribuído) e decide onde colocá-los com base em: recursos disponíveis no node (CPU/RAM), restrições do pod (nodeSelector, nodeAffinity), Taints e Tolerations, políticas de Anti-Affinity para HA (espalhar réplicas em nodes diferentes), e topologia de zona/região.
🔄 controller-manager
Executa múltiplos controllers em loops de reconciliação:
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

🤖 kubelet
O agente que roda em cada worker node. Recebe PodSpecs do API Server (via watch) e garante que os containers descritos estejam rodando e saudáveis. Chama o container runtime (containerd) via CRI (Container Runtime Interface) para criar/destruir containers. Reporta o status do pod e do node de volta ao API Server a cada 10 segundos.
🔀 kube-proxy
Implementa o conceito de Service em cada node. Mantém regras de iptables (ou ipvs) que roteiam tráfego para pods saudáveis. Quando você acessa 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".

— Capítulo 06 · Workloads Kubernetes

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

deployment.yaml — API com Auto-Scaling e Segurança
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

TipoAcessível porCaso de usoExemplo
ClusterIPApenas dentro do clusterComunicação entre microserviços internosAPI → Banco de dados
NodePortIP do node + porta (30000–32767)Dev/staging sem load balancerTestes de integração externos
LoadBalancerIP externo via cloud providerExpor serviços públicos em produçãoAPI pública, frontend
ExternalNameCNAME para serviço externoIntegrar serviços externos ao clusterRDS AWS, Redis ElastiCache
HeadlessDNS retorna IPs dos pods diretamenteStatefulSets que precisam de DNS por podCassandra, Kafka, etcd
— Capítulo 07 · Segurança em Contêineres

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.

rbac.yaml — ServiceAccount mínimo para API de produção
# 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

networkpolicy.yaml — Zero-trust: bloquear tudo, permitir explicitamente
# 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)

1. Privileged Pods
Rodar um container com 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.
2. etcd sem Criptografia
Por padrão, dados no etcd são armazenados em texto claro, incluindo Secrets. Ative EncryptionConfiguration com AES-GCM ou use external KMS (AWS KMS, Vault). Um atacante com acesso ao etcd compromete todo o cluster.
3. RBAC Excessivo
ServiceAccounts com 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.
4. Imagens sem Assinatura
Deployer uma imagem comprometida sem verificação. Use Cosign + Policy Controller (Sigstore) para garantir que apenas imagens assinadas por pipelines CI/CD verificadas possam ser deployadas no cluster.
5. Dashboard Kubernetes sem Autenticação
O Kubernetes Dashboard é frequentemente configurado sem autenticação adequada ou com acesso público. Em 2018, um ataque ao cluster da Tesla explorou exatamente isso para minerar criptomoedas. Nunca exponha o Dashboard publicamente. Use RBAC estrito, acesso via kubectl port-forward e tokens com tempo de expiração curto.
— Capítulo 08 · Observabilidade

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

📊 Métricas (Prometheus + Grafana)
Dados numéricos agregados ao longo do tempo. O Prometheus faz scraping periódico de endpoints /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.
📝 Logs (Loki + Grafana)
Registros de eventos textuais. Promtail ou Fluent Bit como DaemonSet coletam logs de /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.
🔍 Traces (Tempo + OpenTelemetry)
Rastrea o caminho de uma requisição através de múltiplos serviços. O OpenTelemetry SDK instrumenta as aplicações para emitir spans. O Grafana Tempo armazena e consulta os traces via TraceQL. Correlação automática entre logs, métricas e traces no Grafana 10+ permite ir de um spike de latência diretamente para o trace causador.
🔔 Alertas (AlertManager)
O AlertManager recebe alertas do Prometheus, realiza deduplicação, agrupamento e roteamento para canais como Slack, PagerDuty, email ou webhook. Regras são escritas em PromQL. Em 2026, o Grafana On-Call substitui o AlertManager em muitas organizações com melhor UX para on-call rotations.

PromQL: Queries Essenciais para Kubernetes

PromQL — Queries de Monitoramento K8s
# 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

bash — Troubleshooting Kubernetes Completo
# 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)
— Capítulo 09 · Certificações & Carreira

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

Fundação

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.

Fundação

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.

Intermediário · MAIS VALORIZADO

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.

Intermediário

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.

Avançado · Exige CKA ativo

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

CargoCertificações TípicasSalário BR (CLT)Remoto Global (USD/ano)
DevOps Engineer Jr.KCNA, Docker DCAR$ 6.000–9.000$55k–75k
DevOps Engineer Sr.CKA + CKADR$ 12.000–18.000$90k–120k
Platform EngineerCKA + Helm + ArgoCDR$ 15.000–22.000$100k–135k
SRE (Site Reliability)CKA + observabilidadeR$ 14.000–22.000$95k–130k
Cloud Security EngineerCKS + KCSAR$ 18.000–32.000$130k–180k

Ferramentas Complementares Essenciais

⚙️ Helm 3
Gerenciador de pacotes K8s. Empacota manifests em Charts reutilizáveis com templating Go. Essencial para GitOps e CI/CD.
🔄 ArgoCD
GitOps para Kubernetes. O estado do cluster é sincronizado continuamente com o repositório Git. Deploy declarativo e auditável.
🌐 Istio / Linkerd
Service Mesh: mTLS automático entre pods, traffic shaping, canary deploys, observabilidade L7 sem modificar código.
🔒 Falco
Runtime security via eBPF. Detecta comportamentos anômalos: shell em container de produção, acesso a /etc/shadow, exfiltração de dados.
📦 Kustomize
Overlay de manifests K8s sem templates. Nativo no kubectl. Alternativa ao Helm para customizações simples de ambiente.
🛡️ Kyverno
Policy Engine nativo K8s. Define políticas como recursos K8s (sem Rego). Valida, muta e gera recursos automaticamente.
— Capítulo 10 · Quiz Final

Teste seu Conhecimento

20 questões no estilo CKA/CKAD. Cada resposta incluí explicação técnica detalhada.

Questão 1 de 20 Acertos: 0
Carregando...