DevOps, Docker &
Kubernetes
Do primeiro docker run à operação de clusters em produção: cultura, pipelines, containers, orquestração, GitOps, observabilidade e segurança — com foco constante no que o mercado cobra em vagas e entrevistas.
Como usar esta apostila
- Digite os comandos, não copie. DevOps é habilidade motora: quem digitou
kubectl describe podcinquenta vezes debuga mais rápido em entrevista prática. - Monte seu laboratório no capítulo 2 e use-o em todos os outros. Tudo aqui roda em uma máquina local com Docker Desktop ou Linux +
kind/minikube. - Caixas 🎯 "cai em entrevista" marcam o que recrutadores técnicos realmente perguntam; caixas 💼 conectam o conteúdo a vagas e salários.
- Trilha sugerida: iniciante — caps. 1–7 + 21; pleno — tudo até o 16; sênior/SRE — caps. 8, 14, 15, 17–20 são o seu diferencial.
- Cada capítulo fecha com exercícios práticos e um checkpoint; só avance com todas as caixas marcadas de verdade.
O que é DevOps de verdade
Entender DevOps como cultura + práticas + ferramentas — e falar sobre isso do jeito que entrevistadores esperam ouvir.
Nível: básico1.1 O problema que o DevOps resolve
No modelo tradicional, desenvolvimento queria entregar mudanças rápido e operações queria estabilidade — e cada lado era medido por metas opostas. Resultado: deploys raros, gigantes e traumáticos, "funciona na minha máquina", e um muro de tíquetes entre os times. DevOps nasceu (por volta de 2009) para derrubar esse muro: o mesmo time constrói, entrega e opera o software, com automação de ponta a ponta.
"O que é DevOps para você?" — resposta forte: "É uma cultura de responsabilidade compartilhada pelo ciclo de vida completo do software, sustentada por automação (CI/CD, IaC), medição (métricas DORA, observabilidade) e feedback rápido. Ferramentas como Docker e Kubernetes são meios, não o fim." Fugir da resposta "DevOps é usar Jenkins/Docker" já diferencia você de metade dos candidatos.
1.2 CALMS: o modelo mental
| Pilar | Significado | Exemplo prático |
|---|---|---|
| Culture | Responsabilidade compartilhada, sem culpados | Postmortem sem apontar dedos (blameless) |
| Automation | Automatizar tudo que é repetitivo | Pipeline que testa e faz deploy a cada merge |
| Lean | Lotes pequenos, fluxo contínuo | PRs pequenos, feature flags, deploys diários |
| Measurement | Decidir com dados | Métricas DORA, SLOs, dashboards |
| Sharing | Conhecimento circula | Runbooks, documentação, pair ops |
1.3 Métricas DORA — o vocabulário de quem é sênior
A pesquisa State of DevOps (Google/DORA) consolidou 4 métricas que separam times de elite dos demais. Elas aparecem em entrevistas para vagas pleno/sênior:
- Deployment Frequency — com que frequência vai código para produção (elite: sob demanda, várias vezes ao dia).
- Lead Time for Changes — tempo do commit até produção (elite: < 1 dia).
- Change Failure Rate — % de deploys que causam incidente (elite: < 15%).
- MTTR / Failed Deployment Recovery Time — tempo para se recuperar de uma falha (elite: < 1 hora).
Repare no insight central: velocidade e estabilidade não são opostas — times de elite entregam mais rápido e quebram menos, porque lotes pequenos e automatizados são mais seguros que lotes grandes e manuais.
1.4 O ciclo de vida DevOps e onde cada ferramenta entra
| Fase | O que acontece | Ferramentas típicas |
|---|---|---|
| Plan | Backlog, histórias, épicos | Jira, Linear, GitHub Projects |
| Code | Versionamento e revisão | Git, GitHub/GitLab, PRs |
| Build | Compilar, empacotar, gerar imagem | Docker, GitHub Actions, GitLab CI |
| Test | Testes automatizados, análise estática | pytest/Jest, SonarQube, Trivy |
| Release/Deploy | Entregar em ambientes | Argo CD, Helm, Flux, Spinnaker |
| Operate | Rodar e escalar | Kubernetes, AWS/GCP/Azure, Terraform |
| Monitor | Métricas, logs, traces, alertas | Prometheus, Grafana, Loki, OpenTelemetry, Datadog |
1.5 DevOps, SRE e Platform Engineering — títulos que você verá nas vagas
- DevOps Engineer: constrói pipelines, automação e infraestrutura; o título mais comum no Brasil.
- SRE (Site Reliability Engineer): a implementação do Google para operar sistemas com engenharia de software — foco em confiabilidade, SLOs, error budgets e resposta a incidentes (cap. 19).
- Platform Engineer: tendência forte desde 2023 — constrói a plataforma interna (IDP) que os devs usam em autosserviço (templates, pipelines prontos, clusters gerenciados). Backstage é a ferramenta-símbolo.
- Cloud Engineer / Infra: mais peso em nuvem e IaC, menos em pipeline.
Na prática as vagas misturam tudo: uma vaga "DevOps Pleno" no Brasil quase sempre pede Docker + Kubernetes + Terraform + uma nuvem (AWS lidera) + CI/CD + observabilidade. Esta apostila cobre exatamente esse conjunto. "AI/MLOps" e "FinOps" (otimização de custos) são os complementos que mais cresceram em 2025–2026 — cite-os como interesse de evolução.
Exercícios do capítulo 1
- Escreva em 5 linhas, com suas palavras, a diferença entre DevOps e SRE. Guarde — é pergunta clássica de triagem.
- Pesquise 3 vagas reais de DevOps no LinkedIn e liste as ferramentas pedidas. Compare com o sumário desta apostila e marque o que você já cobre.
- Estime as 4 métricas DORA de um projeto seu (mesmo pessoal). Qual seria a mais fácil de melhorar?
Linux e terminal para DevOps
O mínimo de Linux que sustenta todo o resto — e a montagem do laboratório usado na apostila inteira.
Nível: básicoContainers são Linux (cap. 4). Toda entrevista prática de DevOps começa num shell. Se você já domina, pule para 2.4 e monte o laboratório.
2.1 Sobrevivência no shell
# onde estou / o que tem aqui / entrar e sair pwd && ls -lah && cd /var/log && cd - # ver conteúdo: cat (tudo), less (paginado), tail -f (acompanhar ao vivo — essencial p/ logs) tail -f /var/log/syslog # buscar texto e arquivos grep -rn "ERROR" ./logs/ find / -name "*.conf" -mtime -1 2>/dev/null # permissões: dono/grupo/outros — rwx = 421 chmod 640 app.env && chown app:app app.env
2.2 Processos, portas e recursos — o kit de diagnóstico
# processos e consumo ps aux --sort=-%mem | head top # ou htop; dentro: M ordena por memória, k mata processo # quem está ouvindo em quais portas (pergunta clássica de entrevista prática) ss -tulpn | grep 8080 # disco e memória df -h && du -sh /var/lib/docker && free -h # sinais: TERM (15) pede para encerrar; KILL (9) é o último recurso kill -15 1234 || kill -9 1234 # serviços (systemd) e seus logs systemctl status docker && journalctl -u docker --since "10 min ago"
"A aplicação não responde na porta 8080. Como você investiga?" — roteiro: ss -tulpn (algo está ouvindo?), ps aux | grep app (o processo vive?), journalctl -u app/logs do container (por que morreu?), curl -v localhost:8080 (responde localmente? então o problema é rede/firewall/proxy), df -h e free -h (disco cheio e OOM são vilões clássicos). Verbalizar um roteiro ordenado vale mais que acertar de primeira.
2.3 Pipes, variáveis e um script de verdade
# pipes encadeiam programas; awk/cut fatiam colunas ps aux | grep nginx | awk '{print $2}' # variáveis de ambiente — o mecanismo nº1 de configuração em containers export APP_ENV=production && echo $APP_ENV # script com o trio de segurança: para no erro, em var indefinida e em falha no pipe cat > healthcheck.sh <<'SH' #!/usr/bin/env bash set -euo pipefail URL="${1:?uso: healthcheck.sh <url>}" if curl -fsS --max-time 5 "$URL" > /dev/null; then echo "OK $URL" else echo "FAIL $URL" >&2; exit 1 fi SH chmod +x healthcheck.sh && ./healthcheck.sh https://example.com
set -euo pipefail em todo script é marca de profissional — scripts que continuam após erro silencioso causam incidentes reais.
2.4 Montando o laboratório da apostila
Você precisa de: Docker, kubectl e um cluster local (kind — Kubernetes in Docker — é leve e é o que uso nos exemplos; minikube e k3d são alternativas válidas).
# Docker Engine (Ubuntu/Debian) — no Windows/macOS use Docker Desktop ou Rancher Desktop curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER && newgrp docker docker run hello-world # kubectl curl -LO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" sudo install kubectl /usr/local/bin/ && kubectl version --client # kind + cluster de laboratório go install sigs.k8s.io/kind@latest 2>/dev/null || curl -Lo kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 && chmod +x kind && sudo mv kind /usr/local/bin/ kind create cluster --name lab Creating cluster "lab" ... ✓ Set kubectl context to "kind-lab" kubectl get nodes
Todo o mercado assume shell Linux. No Windows, instale o WSL2 (wsl --install) e trabalhe dentro do Ubuntu — Docker Desktop integra com WSL2 nativamente. Dominar isso também elimina o clássico sofrimento de caminhos e finais de linha.
Exercícios do capítulo 2
- Monte o laboratório completo (Docker + kubectl + kind) e guarde um
lab.mdcom os comandos que usou — será o embrião do seu repositório de estudos. - Escreva um script
diskwatch.shque alerte (exit 1 + mensagem) se qualquer partição passar de 80% de uso. Dica:df -h --output=pcent,target. - Sem olhar a apostila, execute o roteiro de diagnóstico da caixa de entrevista contra um container nginx que você mesmo parar de propósito.
CI/CD com GitHub Actions
Construir a esteira que testa, constrói e publica automaticamente — a habilidade DevOps mais pedida em vagas júnior/pleno.
Nível: intermediário3.1 Conceitos: CI, CD e CD
- Integração Contínua (CI): a cada push/PR, um robô roda build, testes e lint. Código quebrado não entra na
main. - Entrega Contínua (Continuous Delivery): todo commit aprovado gera um artefato pronto para produção; o deploy final pode ter aprovação manual.
- Deploy Contínuo (Continuous Deployment): sem aprovação manual — passou, foi para produção. Exige testes e observabilidade maduros.
"Qual a diferença entre Continuous Delivery e Continuous Deployment?" — a resposta está no parágrafo acima; a maioria dos candidatos não sabe. Emende com: "e a fronteira do CD moderno é o GitOps, onde o deploy é reconciliado a partir do Git" (cap. 17) para sinalizar profundidade.
3.2 Anatomia de um workflow
GitHub Actions lê arquivos YAML em .github/workflows/. Estrutura: workflow → disparado por eventos (on) → contém jobs (rodam em paralelo por padrão, cada um numa VM runner) → compostos de steps (comandos ou actions reutilizáveis).
name: CI on: push: { branches: [main] } pull_request: permissions: # princípio do menor privilégio no token do job contents: read jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 22, cache: npm } - run: npm ci - run: npm run lint - run: npm test -- --coverage
npm ci(e nãoinstall): instala exatamente o lockfile — builds reprodutíveis.cacheno setup-node corta minutos de pipeline; em pipelines grandes, cache é a otimização nº 1.permissionsmínimas evitam que um step comprometido faça push ou leia secrets além do necessário.
3.3 Build e push de imagem Docker com tag imutável
name: Build & Push on: push: { branches: [main] } permissions: contents: read packages: write # publicar no GHCR sem secret manual jobs: docker: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v6 with: push: true tags: | ghcr.io/${{ github.repository }}:${{ github.sha }} ghcr.io/${{ github.repository }}:latest cache-from: type=gha cache-to: type=gha,mode=max
:latestTag pelo SHA do commit (ou versão semântica). latest muda por baixo de você: impossibilita rollback confiável e auditoria. Em produção, tag imutável + (idealmente) digest pinning imagem@sha256:.... Isso é pergunta recorrente de entrevista pleno.
3.4 Secrets, ambientes e aprovações
- Secrets (Settings → Secrets and variables): nunca comite credencial; use
${{ secrets.NOME }}. O runner mascara o valor nos logs. - Environments (ex.:
staging,production): cada um com seus secrets, required reviewers (aprovação manual) e restrição de branch — assim você implementa Continuous Delivery com gate humano. - OIDC para nuvem (padrão sênior): em vez de guardar
AWS_SECRET_ACCESS_KEYcomo secret estático, o workflow troca um token OIDC de curta duração por credenciais na AWS/GCP/Azure (aws-actions/configure-aws-credentialscomrole-to-assume). Zero credencial de longa duração — mencione isso em entrevista e você pontua.
3.5 Estratégias de deploy que você precisa saber explicar
| Estratégia | Como funciona | Trade-off |
|---|---|---|
| Rolling | Substitui instâncias aos poucos (padrão do K8s) | Simples; versões antigas e novas convivem durante o rollout |
| Blue-Green | Dois ambientes completos; vira o tráfego de uma vez | Rollback instantâneo; custa 2× infra durante a troca |
| Canário | Nova versão recebe 1→5→25→100% do tráfego, guiada por métricas | Menor risco; exige observabilidade e automação (Argo Rollouts, Flagger) |
| Feature flags | Código vai para produção desligado; liga por configuração | Desacopla deploy de release; flags viram dívida se não forem limpas |
Exercícios do capítulo 3
- Crie um repositório com uma API mínima (qualquer linguagem) e o workflow de CI da seção 3.2 adaptado. Quebre um teste de propósito e veja o PR ser bloqueado.
- Adicione o workflow de build/push para o GHCR com tag por SHA. Rode
docker pullda sua própria imagem. - Crie o environment
productioncom aprovação manual e um job de "deploy" (pode ser umecho) que só roda após aprovação. - Desafio: adicione um job de matriz testando em Node 20 e 22 simultaneamente (
strategy.matrix).
Containers por dentro
Entender o que um container realmente é — o conhecimento que separa quem "usa Docker" de quem entende Docker.
Nível: intermediário4.1 Container não é uma VM leve
Uma VM virtualiza hardware e roda um kernel próprio dentro de um hipervisor. Um container é apenas um processo Linux comum, isolado por recursos do kernel do hospedeiro — sem kernel próprio, sem boot, por isso sobe em milissegundos.
| VM | Container | |
|---|---|---|
| Isolamento | Hardware virtual + kernel próprio (forte) | Kernel compartilhado + namespaces (mais fraco) |
| Tamanho | GBs | MBs |
| Boot | Minutos | Milissegundos |
| Densidade | Dezenas por host | Centenas por host |
4.2 As três tecnologias do kernel
- Namespaces — limitam o que o processo enxerga. Cada container tem seu próprio:
pid(vê só seus processos; seu app é o PID 1),net(interfaces e portas próprias),mnt(sistema de arquivos próprio),uts(hostname),ipceuser(mapeamento de UIDs — base do modo rootless). - cgroups (control groups v2) — limitam quanto o processo usa: CPU, memória, I/O, PIDs. É o cgroup que mata seu container com OOM quando ele estoura o limite de memória.
- Union filesystem (OverlayFS) — a imagem é uma pilha de camadas somente-leitura; ao rodar, o Docker acrescenta uma fina camada de escrita por cima (copy-on-write). Por isso 50 containers da mesma imagem quase não ocupam disco extra — e por isso o que se escreve no container morre com ele.
docker run -d --name web nginx:alpine # dentro do container, o nginx é PID 1 e só vê a si mesmo docker exec web ps aux PID USER COMMAND 1 root nginx: master process # no host, é um processo comum com PID normal ps aux | grep "nginx: master" root 41783 ... nginx: master process # limite de memória = cgroup; estourou, o kernel mata (OOMKilled) docker run --rm -m 64m progrium/stress --vm 1 --vm-bytes 128M
4.3 A pilha de execução: Docker → containerd → runc (OCI)
"Docker" hoje é um conjunto de peças padronizadas pela OCI (Open Container Initiative):
- docker CLI fala com o dockerd (daemon, API REST);
- dockerd delega ao containerd (runtime de alto nível: gerencia imagens, ciclo de vida) —
- que invoca o runc (runtime de baixo nível: cria namespaces/cgroups e executa o processo, seguindo a OCI Runtime Spec).
Consequências práticas que caem em entrevista: o Kubernetes usa containerd diretamente, sem Docker (o "dockershim" foi removido no K8s 1.24 — suas imagens Docker continuam funcionando porque seguem a OCI Image Spec); e alternativas como Podman (sem daemon, rootless por padrão) e runtimes isolados (gVisor, Kata Containers) plugam na mesma especificação.
"O Kubernetes abandonou o Docker?" — resposta precisa: o K8s removeu o dockershim e passou a falar com runtimes via CRI (containerd, CRI-O). As imagens construídas com Docker seguem o padrão OCI e rodam normalmente. Quem responde isso com clareza demonstra entender a arquitetura, não só decorar comandos.
Exercícios do capítulo 4
- Rode um container e compare
ps auxdentro e fora dele. Identifique o PID do mesmo processo nos dois mundos. - Use
docker run -m 32m --rm alpine sh -c 'cat /sys/fs/cgroup/memory.max'e confirme o limite imposto pelo cgroup. - Explique por escrito, em até 8 linhas, por que dados gravados dentro de um container somem quando ele é removido.
Docker essencial
O ciclo completo imagem → container → registry, e os comandos que você usará todos os dias da carreira.
Nível: básico5.1 Vocabulário mínimo
- Imagem: pacote imutável com o sistema de arquivos e metadados (comando padrão, portas, env). É o "molde".
- Container: uma instância em execução (ou parada) de uma imagem. É o "bolo".
- Registry: repositório de imagens — Docker Hub, GHCR, ECR (AWS), Harbor (self-hosted).
- Tag: apelido de versão (
nginx:1.27-alpine). Sem tag =latest, que é só um apelido móvel, não "a mais recente" garantida.
5.2 O ciclo de vida na prática
# rodar: -d (fundo), --name, -p host:container, -e env, --rm (autolimpeza) docker run -d --name api -p 8080:80 -e TZ=America/Sao_Paulo nginx:alpine # inspecionar docker ps -a # todos, inclusive parados docker logs -f --tail 100 api # logs ao vivo docker exec -it api sh # shell dentro do container docker inspect api | less # JSON completo: IP, mounts, config docker stats # CPU/mem em tempo real # ciclo docker stop api && docker start api && docker restart api docker rm -f api # imagens e limpeza (decore: prune salva o disco de todo time) docker images && docker pull redis:7-alpine && docker rmi redis:7-alpine docker system prune -af --volumes # CUIDADO: remove tudo que não está em uso
- Confundir a ordem de
-p: é semprehost:container.-p 8080:80= "porta 8080 da minha máquina → 80 do container". - Container que "morre sozinho": container vive enquanto o processo principal (PID 1) vive.
docker run ubuntusai na hora porque o bash sem TTY termina. Use-itpara interativo ou um processo de longa duração. - Guardar dados no container: a camada de escrita morre com ele. Dados persistentes → volumes (cap. 7).
5.3 Publicando no registry
docker build -t minha-api:1.0.0 . # o nome completo é registry/namespace/repo:tag docker tag minha-api:1.0.0 ghcr.io/seu-usuario/minha-api:1.0.0 docker login ghcr.io docker push ghcr.io/seu-usuario/minha-api:1.0.0 # em produção, o supremo em reprodutibilidade é referenciar o digest: docker pull ghcr.io/seu-usuario/minha-api@sha256:9f8e...
Exercícios do capítulo 5
- Suba um
postgres:16-alpinecom senha via-e, entre comdocker exec -it ... psql -U postgrese crie uma tabela. Remova o container, suba de novo e constate que a tabela sumiu (motivação para o cap. 7). - Suba dois nginx em portas diferentes do host e acesse ambos no navegador.
- Use
docker inspectpara descobrir o IP interno de um container e dêpingnele a partir de outro container na mesma rede.
Dockerfile a fundo
Escrever imagens pequenas, rápidas de construir e seguras — o artefato que mais denuncia (ou comprova) senioridade num teste técnico.
Nível: intermediário → avançado6.1 Instruções essenciais
| Instrução | Faz o quê | Nota de profissional |
|---|---|---|
FROM | Imagem base | Fixe versão: node:22-alpine, nunca node solto |
WORKDIR | Define diretório de trabalho | Use sempre; evita cd e caminhos relativos frágeis |
COPY | Copia arquivos do contexto | Prefira a ADD (que também baixa URLs e extrai tar — comportamento surpresa) |
RUN | Executa comando no build (cria camada) | Encadeie com && e limpe caches na mesma camada |
ENV / ARG | Variável em runtime / só no build | Segredo nunca em ENV/ARG — fica gravado na imagem |
EXPOSE | Documenta porta | É só metadado; quem publica é o -p |
USER | Usuário de execução | Rodar como não-root é requisito de segurança básico |
CMD / ENTRYPOINT | Comando padrão / executável fixo | Ver 6.4 — pergunta clássica de entrevista |
HEALTHCHECK | Teste periódico de saúde | Docker marca unhealthy; no K8s, quem faz isso são as probes |
6.2 Cache de camadas: a regra de ouro
Cada instrução gera uma camada; o Docker reaproveita camadas até a primeira que mudou — dali para baixo, tudo é reconstruído. Logo: o que muda pouco vai para cima; o que muda sempre (seu código) vai para baixo.
FROM node:22-alpine WORKDIR /app # 1º só os manifestos: camada de dependências só invalida se eles mudarem COPY package*.json ./ RUN npm ci --omit=dev # 2º o código — muda a cada commit, mas as deps acima ficam em cache COPY . . CMD ["node", "server.js"]
Complete com um .dockerignore (irmão do .gitignore): node_modules, .git, *.env, logs. Reduz o contexto enviado ao daemon, acelera o build e evita vazar segredos para dentro da imagem.
6.3 Multi-stage: a técnica que todo teste técnico espera
Compile num estágio gordo, copie só o artefato final para um estágio mínimo. A imagem final não carrega compilador, código-fonte nem dependências de build:
# ---- estágio de build ---- FROM golang:1.23-alpine AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /bin/app ./cmd/api # ---- estágio final: só o binário ---- FROM gcr.io/distroless/static-debian12:nonroot COPY --from=build /bin/app /app USER nonroot EXPOSE 8080 ENTRYPOINT ["/app"]
- Bases mínimas:
alpine(~5 MB, musl libc — atenção a binários que esperam glibc), distroless (Google: sem shell nem gerenciador de pacotes — superfície de ataque mínima),ubuntu/debian-slim(compatibilidade máxima). Para Java, useeclipse-temurin:21-jre-alpine(JRE, não JDK) oujlinkpara runtime customizado. - Menos pacotes = menos CVEs no scanner = imagem que passa na esteira de segurança da empresa.
6.4 ENTRYPOINT vs CMD — de uma vez por todas
ENTRYPOINT= o executável fixo.CMD= argumentos padrão (substituíveis na linha de comando).- Juntos:
ENTRYPOINT ["python", "app.py"]+CMD ["--port", "8000"]→docker run img --port 9000troca só os argumentos. - Use sempre a forma exec
["binário","arg"], não a forma shellbinário arg: na forma shell o processo vira filho de/bin/sh, que é o PID 1 e não repassa SIGTERM — seu app morre com SIGKILL sem graceful shutdown. Esse detalhe derruba candidatos em entrevistas de nível pleno.
6.5 Boas práticas que scanners e revisores cobram
- Não rode como root: crie usuário (
RUN adduser -D app && USER app) ou use basenonroot. - Uma responsabilidade por container — app e banco em containers separados, sempre.
- Segredos via BuildKit, nunca em camadas:
RUN --mount=type=secret,id=npmrc npm cimonta o segredo só durante o comando, sem gravá-lo na imagem. - Cache de pacotes com BuildKit:
RUN --mount=type=cache,target=/root/.npm npm cipersiste o cache entre builds. - Rotule a imagem:
LABEL org.opencontainers.image.source,.revision— rastreabilidade em produção. - Lint: rode
hadolint Dockerfileno CI.
Teste técnico padrão: "Otimize este Dockerfile". Checklist mental: fixar tag da base → ordenar camadas p/ cache (manifestos antes do código) → multi-stage → base mínima → usuário não-root → forma exec no ENTRYPOINT → .dockerignore. Aplicar os sete em voz alta, explicando o porquê de cada um, é resposta de aprovação.
Exercícios do capítulo 6
- Containerize uma API sua com multi-stage. Compare
docker imagesantes/depois e registre a redução no README (ótimo material de portfólio). - Meça o efeito do cache: mude 1 linha de código e cronometre o rebuild com a ordem errada (COPY . . antes das deps) e com a ordem certa.
- Rode
hadolintedocker run --rm aquasec/trivy image sua-imagem; corrija ao menos 3 apontamentos. - Prove o problema do PID 1: crie uma imagem com forma shell no CMD, rode,
docker stope cronometre os 10 s até o SIGKILL; converta para forma exec e compare.
Redes, volumes e Compose
Fazer containers conversarem, persistirem dados e subirem em conjunto com um único comando.
Nível: intermediário7.1 Redes
- bridge (padrão): rede NAT interna. Na bridge padrão containers só se falam por IP; em uma bridge criada por você, ganham DNS automático pelo nome — por isso a regra prática é: crie sempre sua rede.
- host: sem isolamento de rede; o container usa as portas do host diretamente (útil para performance/ferramentas, só em Linux).
- none: sem rede — para jobs totalmente isolados.
docker network create app-net docker run -d --name db --network app-net -e POSTGRES_PASSWORD=dev postgres:16-alpine docker run -d --name api --network app-net -e DATABASE_URL=postgres://postgres:dev@db:5432/postgres minha-api:1.0.0 # "db" resolve por DNS interno — nenhuma porta do banco exposta ao host docker exec api ping -c1 db
Repare no padrão de segurança: o banco não tem -p — só a API é alcançável de fora. Expor o mínimo é hábito que entrevistadores notam.
7.2 Volumes e bind mounts
| Tipo | Sintaxe | Quando usar |
|---|---|---|
| Volume nomeado | -v pgdata:/var/lib/postgresql/data | Dados de produção/persistentes; gerenciado pelo Docker, sobrevive ao container |
| Bind mount | -v $(pwd)/src:/app/src | Desenvolvimento: código do host refletido ao vivo no container |
| tmpfs | --tmpfs /tmp | Dados sensíveis/temporários só em memória |
docker volume create pgdata docker run -d --name db -v pgdata:/var/lib/postgresql/data -e POSTGRES_PASSWORD=dev postgres:16-alpine # destrua o container, suba outro apontando pro mesmo volume: os dados continuam lá docker rm -f db && docker run -d --name db2 -v pgdata:/var/lib/postgresql/data -e POSTGRES_PASSWORD=dev postgres:16-alpine
7.3 Docker Compose: o ambiente inteiro em um arquivo
Compose descreve um conjunto de serviços, redes e volumes em YAML — o padrão universal para desenvolvimento local e o primeiro "infra as code" da maioria das carreiras:
services: api: build: . ports: ["8080:8080"] environment: DATABASE_URL: postgres://postgres:${DB_PASS:-dev}@db:5432/app depends_on: db: { condition: service_healthy } # espera o banco ficar SAUDÁVEL, não só "iniciado" develop: watch: # hot reload moderno: docker compose watch - action: sync path: ./src target: /app/src db: image: postgres:16-alpine environment: { POSTGRES_PASSWORD: ${DB_PASS:-dev}, POSTGRES_DB: app } volumes: [pgdata:/var/lib/postgresql/data] healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s retries: 5 volumes: pgdata:
docker compose up -d --build docker compose ps && docker compose logs -f api docker compose exec db psql -U postgres app docker compose down # -v também apaga volumes (cuidado)
depends_on com condition: service_healthy (e não a forma simples, que só espera o container iniciar), variáveis com default ${VAR:-valor}, healthchecks em todos os serviços, e nenhum segredo real comitado (use .env no .gitignore). Um compose.yaml assim no seu portfólio comunica maturidade imediata.
Exercícios do capítulo 7
- Monte o stack API + Postgres + Redis com Compose, healthchecks em tudo e o banco sem porta exposta. Prove a persistência destruindo e recriando o stack.
- Configure
docker compose watche veja uma mudança de código refletir sem rebuild. - Desafio: adicione um serviço
adminerapenas em um arquivocompose.override.yamlde desenvolvimento, mantendo o arquivo base limpo para produção.
Docker avançado e segurança
Hardening, builds multi-arquitetura e o que separa "sei Docker" de "opero Docker em produção".
Nível: avançado8.1 Modelo de ameaça do container
O risco central: containers compartilham o kernel do host. Um escape de container (via kernel bug ou má configuração) compromete o host inteiro. As defesas são camadas:
- Não-root dentro do container (cap. 6) + rootless Docker/Podman: o próprio daemon roda sem root, então mesmo um escape aterrissa num usuário comum do host.
- Capabilities: root no Linux é um pacote de ~40 capacidades. Derrube todas e devolva só o necessário:
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE ... --security-opt no-new-privileges: impede escalada via binários setuid.- Filesystem somente-leitura:
--read-only --tmpfs /tmp— o app não consegue gravar malware em si mesmo. - seccomp/AppArmor: o perfil seccomp padrão do Docker já bloqueia ~44 syscalls perigosas; nunca use
--privileged(desliga TUDO isso) a menos que saiba exatamente por quê. - Runtimes isolados para cargas não confiáveis: gVisor (kernel em userspace) e Kata (microVM por container).
"O que --privileged faz e por que é perigoso?" — dá todas as capabilities, desabilita seccomp/AppArmor e expõe os devices do host: o container vira praticamente root na máquina. Alternativas: conceder capability específica, montar apenas o device necessário, ou usar runtime isolado. Citar que "montar /var/run/docker.sock dentro de um container equivale a dar root no host" também rende pontos.
8.2 Escaneando imagens: Trivy no fluxo
# vulnerabilidades (CVEs) na imagem — rode local e no CI trivy image --severity HIGH,CRITICAL minha-api:1.0.0 # segredos e misconfigs no repositório trivy fs --scanners secret,misconfig . # SBOM: inventário de tudo que existe na imagem (exigência crescente pós-ataques de supply chain) trivy image --format cyclonedx --output sbom.json minha-api:1.0.0
No pipeline, falhe o build em CRITICAL. Base mínima (distroless) + rebuild periódico (base atualizada) mantêm o relatório limpo.
8.3 BuildKit e buildx: builds modernos
- Multi-arquitetura — obrigatório na era dos servidores ARM (AWS Graviton) e Macs Apple Silicon:
docker buildx build --platform linux/amd64,linux/arm64 -t img:1.0 --push . - Cache remoto compartilhado entre runners de CI:
--cache-to type=registry,ref=img:cache,mode=max. - Attestations:
--provenance=true --sbom=trueanexa procedência e SBOM à imagem (supply chain, cap. 20).
8.4 Operação: logs, limites e restart
docker run -d --name api \
--restart unless-stopped \
--memory 512m --cpus 1.5 \
--read-only --tmpfs /tmp \
--cap-drop ALL --security-opt no-new-privileges \
--log-driver json-file --log-opt max-size=10m --log-opt max-file=3 \
ghcr.io/voce/minha-api@sha256:9f8e...
Sem max-size nos logs, o json-file cresce até derrubar o disco do host — um dos incidentes mais comuns do mundo real. Logs devem ir para stdout/stderr (nunca arquivo interno) para serem coletados pelo driver — princípio 12-factor que o K8s também assume.
Exercícios do capítulo 8
- Pegue sua API do cap. 6 e rode com o comando "produção standalone" acima. Documente cada flag no README.
- Rode Trivy na sua imagem, troque a base por distroless e compare o número de CVEs (screenshot para o portfólio).
- Faça um build multi-arch amd64+arm64 e verifique o manifesto com
docker buildx imagetools inspect. - Desafio: demonstre (em VM descartável!) por que montar o docker.sock num container é perigoso, criando um container que lista os containers do host.
Arquitetura do Kubernetes
Entender as peças do cluster e o modelo declarativo — a base para debugar qualquer coisa depois.
Nível: intermediário9.1 Por que orquestração
Docker resolve um container em uma máquina. Produção exige: distribuir centenas de containers por dezenas de máquinas, reiniciar o que morre, escalar sob carga, balancear tráfego, fazer rollout sem downtime e voltar atrás quando dá errado. O Kubernetes (K8s, do projeto interno Borg do Google, aberto em 2014) automatiza tudo isso a partir de um princípio único:
Você declara o estado desejado ("quero 3 réplicas desta imagem, com este limite de memória") em YAML. Controladores em loops de reconciliação comparam continuamente o estado atual com o desejado e agem para convergir. Pod morreu? O controlador vê 2 ≠ 3 e cria outro. Você nunca ordena "suba um container"; você descreve o mundo que quer e o cluster o persegue. Internalize isso e todo o resto da apostila vira consequência.
9.2 Control plane e worker nodes
| Componente | Onde | Função |
|---|---|---|
| kube-apiserver | control plane | Porta de entrada única: valida e serve a API REST. Tudo (kubectl, controladores, kubelet) fala com ele |
| etcd | control plane | Banco chave-valor distribuído (Raft) com TODO o estado do cluster. Perdeu o etcd sem backup = perdeu o cluster |
| kube-scheduler | control plane | Decide em qual node cada Pod novo roda (recursos, afinidades, taints) |
| controller-manager | control plane | Roda os loops de reconciliação (Deployment, ReplicaSet, Node, Job…) |
| kubelet | cada node | Agente que garante que os containers dos Pods designados estão rodando (via CRI → containerd) |
| kube-proxy | cada node | Implementa Services na rede (iptables/IPVS); em CNIs modernos como Cilium, é substituído por eBPF |
Interfaces plugáveis (vocabulário de entrevista): CRI (runtime: containerd, CRI-O), CNI (rede: Cilium, Calico, flannel), CSI (storage: EBS, Ceph…).
9.3 O caminho de um kubectl apply (pergunta clássica de entrevista)
- kubectl envia o YAML ao API server, que autentica, autoriza (RBAC), passa por admission controllers e grava no etcd;
- o controller-manager nota o Deployment novo → cria um ReplicaSet → que cria objetos Pod (ainda sem node);
- o scheduler vê Pods sem node e escolhe o melhor node para cada um;
- o kubelet do node escolhido vê o Pod atribuído a ele, puxa a imagem e inicia os containers via containerd;
- kubelet reporta status ao API server — e os loops seguem vigiando para sempre.
9.4 Distribuições e onde o mercado roda K8s
- Gerenciados (o padrão): EKS (AWS — mais vagas no Brasil), GKE (Google — mais maduro), AKS (Azure). O provedor opera o control plane; você opera nodes e workloads.
- Locais/edge: kind e minikube (estudo), k3s (leve, edge/IoT), Talos (SO imutável só-K8s).
- On-prem corporativo: OpenShift (Red Hat), Rancher, kubeadm puro.
Exercícios do capítulo 9
- No seu cluster kind:
kubectl get pods -n kube-systeme identifique cada componente da tabela 9.2. - Desenhe (papel mesmo) o fluxo do
kubectl applysem consultar. Refaça até sair fluido — é pergunta de entrevista. - Rode
kubectl get --raw /healthzekubectl api-resources | head -30para sentir a API por baixo do kubectl.
Workloads: de Pods a Deployments
Os objetos que rodam sua aplicação, os comandos kubectl do dia a dia e rollouts sem downtime.
Nível: intermediário10.1 Pod: a menor unidade
Um Pod é um grupo de 1+ containers que compartilham rede (mesmo IP, falam entre si por localhost) e volumes. Regra: 1 container principal por Pod; containers extras são sidecars (proxy, coletor de logs) ou init containers (preparação que roda antes do principal). Pods são descartáveis: têm IP efêmero e não se recriam sozinhos — por isso você quase nunca cria Pods diretamente.
10.2 Deployment: o objeto do dia a dia
O Deployment gerencia ReplicaSets (que mantêm N réplicas de um Pod) e orquestra atualizações entre versões:
apiVersion: apps/v1 kind: Deployment metadata: name: api labels: { app: api } spec: replicas: 3 selector: matchLabels: { app: api } # liga o Deployment aos Pods — deve casar com o template template: # o "molde" do Pod metadata: labels: { app: api } spec: containers: - name: api image: ghcr.io/voce/minha-api:1.0.0 ports: [{ containerPort: 8080 }]
Labels são o sistema circulatório do K8s: selectors de Deployments, Services e NetworkPolicies encontram Pods por labels, nunca por nome. Errar o matchLabels ≠ template.labels é o erro de YAML mais clássico de iniciante.
10.3 kubectl que você usa o dia inteiro
kubectl apply -f deployment.yaml kubectl get pods -o wide # -w acompanha ao vivo kubectl describe pod api-7d9c... # eventos no rodapé = ouro do debug kubectl logs -f deploy/api # --previous = logs do container que crashou kubectl exec -it deploy/api -- sh kubectl port-forward svc/api 8080:80 # acessar do seu notebook sem expor nada kubectl get events --sort-by=.lastTimestamp # gerar YAML sem decorar (técnica oficial de prova CKA/CKAD): kubectl create deployment api --image=nginx --replicas=3 --dry-run=client -o yaml > deploy.yaml kubectl explain deployment.spec.strategy # documentação embutida de qualquer campo
10.4 Rollout, rollback e a mecânica do zero-downtime
kubectl set image deploy/api api=ghcr.io/voce/minha-api:1.1.0 kubectl rollout status deploy/api Waiting for deployment "api" rollout to finish: 1 out of 3 new replicas... kubectl rollout history deploy/api kubectl rollout undo deploy/api # --to-revision=2 para versão específica
Estratégia padrão RollingUpdate com maxSurge: 25% (quantos Pods a mais durante a troca) e maxUnavailable: 25% (quantos podem faltar). O rollout só avança conforme os Pods novos ficam Ready — o que depende das probes do próximo capítulo; sem readiness probe, "zero downtime" é ilusão: o K8s manda tráfego para Pods que ainda estão subindo.
"Diferença entre Pod, ReplicaSet e Deployment?" — Pod roda containers; ReplicaSet mantém N cópias do Pod; Deployment gerencia ReplicaSets para permitir rollout/rollback declarativo (a cada versão nova ele cria um RS novo e escala o antigo a zero — por isso rollout undo é instantâneo). Emende: "na prática só crio Deployments; RS e Pods são gerenciados por ele".
Exercícios do capítulo 10
- Faça o deploy da sua API (imagem do cap. 6) com 3 réplicas no kind. Delete um Pod na mão e observe a reconciliação recriá-lo.
- Atualize a imagem para uma tag inexistente, observe o rollout travar (
ImagePullBackOff) e façarollout undo. Note que as réplicas antigas nunca saíram do ar. - Use
kubectl explainpara descobrir o que fazspec.revisionHistoryLimit, sem Google.
Configuração, probes e recursos
ConfigMaps, Secrets, health checks e requests/limits — o trio que define se sua aplicação é "cloud native" de verdade.
Nível: intermediário → avançado11.1 ConfigMaps e Secrets
Configuração fora da imagem (12-factor): a mesma imagem roda em dev/staging/prod mudando só a config.
kubectl create configmap api-config --from-literal=LOG_LEVEL=info --from-file=app.properties kubectl create secret generic api-secret --from-literal=DB_PASS='s3nh4' # consumindo no Pod: containers: - name: api envFrom: - configMapRef: { name: api-config } # tudo vira env var env: - name: DB_PASS valueFrom: secretKeyRef: { name: api-secret, key: DB_PASS } volumeMounts: # ou como arquivo (atualiza sem restart) - { name: cfg, mountPath: /etc/app } volumes: - name: cfg configMap: { name: api-config }
É apenas base64 (codificação, não criptografia) dentro do etcd. Em produção: habilite encryption at rest no etcd, restrinja leitura de Secrets via RBAC e — padrão de mercado — use um cofre externo (AWS Secrets Manager, Vault) integrado via External Secrets Operator ou CSI driver. Dizer "Secret é só base64" na entrevista é ponto garantido.
11.2 Probes: como o K8s sabe que seu app está vivo
| Probe | Pergunta que responde | Se falha… |
|---|---|---|
| startup | "Já terminou de subir?" | Segura as outras probes (apps lentos, ex. Java); estourou o prazo → restart |
| readiness | "Pode receber tráfego AGORA?" | Pod sai dos endpoints do Service (sem restart) — controla rollout e picos |
| liveness | "Travou sem esperança?" | kubelet reinicia o container |
containers: - name: api startupProbe: httpGet: { path: /healthz, port: 8080 } failureThreshold: 30 # 30 × 2s = até 60s para subir periodSeconds: 2 readinessProbe: httpGet: { path: /readyz, port: 8080 } # checa dependências (banco ok?) periodSeconds: 5 livenessProbe: httpGet: { path: /healthz, port: 8080 } # leve e SEM checar dependências periodSeconds: 10 failureThreshold: 3
Se a liveness testa a conexão com o banco e o banco cai, o K8s reinicia todos os seus Pods em loop — transformando um incidente de dependência em cascata total. Regra: liveness = só o processo ("estou deadlocked?"); readiness = dependências ("consigo servir?"). Essa distinção é pergunta recorrente em entrevistas pleno/sênior.
11.3 Requests, limits e QoS
requests= o que o Pod reserva (base do scheduler para escolher node e do HPA para calcular %).limits= teto. CPU acima do limite → throttling (lentidão); memória acima → OOMKilled (morte).
resources: requests: { cpu: 250m, memory: 256Mi } # 250m = 0,25 vCPU limits: { memory: 512Mi } # limite de CPU é frequentemente omitido…
Prática de mercado atual: defina memória com request = limit (previsível; OOM é melhor que swap invisível) e CPU só com request, sem limit — limites de CPU causam throttling mesmo com CPU sobrando no node, uma das causas nº 1 de latência misteriosa. QoS resultante: Guaranteed (requests=limits em tudo), Burstable (o comum), BestEffort (sem nada — primeiro a morrer sob pressão do node).
11.4 Namespaces e organização
Namespaces particionam o cluster (por time/ambiente/produto): kubectl create ns staging, kubectl -n staging get pods. Com eles vêm ResourceQuota (teto de CPU/mem por namespace) e LimitRange (defaults por container) — governança básica de plataforma. Contextos: kubectl config use-context troca de cluster; kubens/kubectx são os utilitários queridinhos do dia a dia.
Exercícios do capítulo 11
- Adicione ao seu Deployment: ConfigMap (env + arquivo), Secret, as 3 probes e resources. Este YAML "completo" será seu template de carreira.
- Provoque um OOMKilled: limite de 32Mi numa app que aloca mais; diagnostique com
describe(Last State: OOMKilled, Exit Code 137). - Simule o desastre da liveness com dependência: crie um endpoint /healthz que retorna 500 e observe o CrashLoop do Deployment inteiro.
- Crie os namespaces
deveprodcom ResourceQuota diferente em cada.
Rede: Services, Ingress e policies
Como o tráfego chega aos Pods — de dentro do cluster, da internet, e como restringir quem fala com quem.
Nível: avançado12.1 Services: IP estável para Pods efêmeros
Pods nascem e morrem com IPs novos; o Service dá um nome DNS e um IP virtual estáveis que balanceiam para os Pods selecionados por label:
| Tipo | O que faz | Uso típico |
|---|---|---|
| ClusterIP (padrão) | IP interno; DNS svc.namespace.svc.cluster.local | Comunicação interna serviço↔serviço |
| NodePort | Abre porta 30000–32767 em todo node | Testes/labs; raramente produção |
| LoadBalancer | Provisiona LB da nuvem (um por Service) | Expor 1 serviço L4; caro em escala |
Headless (clusterIP: None) | DNS retorna os IPs dos Pods diretamente | StatefulSets, clientes que descobrem peers (bancos) |
apiVersion: v1 kind: Service metadata: { name: api } spec: selector: { app: api } # casa com as labels dos PODS ports: [{ port: 80, targetPort: 8080 }] # --- debug: o Service tem endpoints? (a pergunta nº1 quando "não conecta") kubectl get endpointslices -l kubernetes.io/service-name=api # vazio? → selector errado OU pods não-Ready (readiness!). Teste de dentro: kubectl run tmp --rm -it --image=nicolaka/netshoot -- curl -s http://api.default/healthz
12.2 Ingress: HTTP(S) da internet para dentro
Um Ingress Controller (NGINX Ingress, Traefik, ALB Controller na AWS) é um proxy L7 que roda no cluster atrás de um LoadBalancer e roteia por host/caminho para os Services — TLS terminado ali, certificados automatizados pelo cert-manager + Let's Encrypt:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web annotations: cert-manager.io/cluster-issuer: letsencrypt-prod spec: ingressClassName: nginx tls: [{ hosts: [app.exemplo.com.br], secretName: web-tls }] rules: - host: app.exemplo.com.br http: paths: - { path: /api, pathType: Prefix, backend: { service: { name: api, port: { number: 80 }}}} - { path: /, pathType: Prefix, backend: { service: { name: front, port: { number: 80 }}}}
Gateway API é o sucessor oficial do Ingress (GA desde 2023): separa papéis (infra define Gateway, dev define HTTPRoute), suporta TCP/gRPC e split de tráfego nativo. Migração é gradual; cite-o em entrevista como "para onde o ecossistema está indo".
12.3 NetworkPolicies: firewall entre Pods
Por padrão, todo Pod fala com todo Pod — inaceitável em ambientes regulados. NetworkPolicies (aplicadas pelo CNI; Cilium/Calico suportam, flannel não) definem allow-lists:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: { name: db-allow-api, namespace: prod } spec: podSelector: { matchLabels: { app: db } } # a quem a regra se aplica policyTypes: [Ingress] ingress: - from: [{ podSelector: { matchLabels: { app: api } } }] ports: [{ port: 5432 }]
Padrão sênior: começar com default-deny por namespace e liberar fluxo a fluxo (zero trust). No dia a dia de debug de rede: nicolaka/netshoot é o canivete (curl, dig, tcpdump) e kubectl get networkpolicy é a primeira suspeita quando "parou de conectar do nada".
"Service ClusterIP não responde — como debuga?" Roteiro: 1) kubectl get endpointslices (tem endpoints? não → selector errado ou Pods não-Ready); 2) Pod responde direto? (kubectl exec + curl no IP do Pod); 3) DNS resolve? (nslookup api.default.svc.cluster.local de um netshoot); 4) NetworkPolicy bloqueando?; 5) targetPort confere com a porta real do container? Decorar essa escada resolve 95% dos casos reais.
Exercícios do capítulo 12
- Exponha sua API com ClusterIP + Ingress NGINX no kind (o kind tem guia oficial de ingress; use
extraPortMappings). Acesse viacurl -H "Host: app.local" localhost. - Quebre de propósito: mude o selector do Service e siga o roteiro de debug até o diagnóstico. Repita quebrando o targetPort.
- Instale o Cilium no kind e aplique default-deny + liberação seletiva. Prove com netshoot que o bloqueio funciona.
Storage e workloads com estado
PV/PVC, StatefulSets e os outros tipos de workload — Jobs, CronJobs e DaemonSets.
Nível: avançado13.1 A cadeia de storage: PV → PVC → Pod
- PersistentVolume (PV): o disco em si (EBS, NFS, Ceph…), recurso do cluster.
- PersistentVolumeClaim (PVC): o pedido do desenvolvedor ("quero 10Gi ReadWriteOnce"). O Pod monta o PVC, nunca o PV — desacoplamento entre app e infraestrutura.
- StorageClass: perfis de provisionamento dinâmico via CSI (ex.:
gp3na AWS) — o PVC nasce e o disco é criado sozinho. É assim que 99% do mercado opera.
apiVersion: v1 kind: PersistentVolumeClaim metadata: { name: data } spec: accessModes: [ReadWriteOnce] # RWO: 1 node; RWX (NFS/EFS): vários nodes storageClassName: standard resources: { requests: { storage: 10Gi } } # no Pod: volumes: [{ name: data, persistentVolumeClaim: { claimName: data } }]
Conceitos que caem em prova/entrevista: reclaimPolicy (Delete apaga o disco junto com o PVC; Retain preserva — use Retain para dados críticos) e volumeBindingMode: WaitForFirstConsumer (cria o disco na zona certa só quando o Pod é agendado — evita Pod numa AZ e disco em outra).
13.2 StatefulSet: quando identidade importa
Deployment trata Pods como gado intercambiável. Bancos e sistemas de consenso precisam de identidade estável — é o que o StatefulSet dá:
- Nomes previsíveis e ordenados:
postgres-0,postgres-1… (criação/remoção em ordem); - DNS estável por Pod via Service headless:
postgres-0.postgres.prod.svc— réplicas se encontram; volumeClaimTemplates: cada réplica ganha seu próprio PVC, que a segue em restarts e sobrevive à remoção do Pod.
Pergunta de entrevista com pegadinha: "você rodaria Postgres no Kubernetes?" Resposta equilibrada: "Se há serviço gerenciado disponível (RDS, Cloud SQL), ele costuma vencer em custo operacional. Rodar no K8s é viável e cada vez mais comum, mas com um operator maduro (CloudNativePG, Zalando) cuidando de failover, backup e réplicas — nunca um StatefulSet artesanal." Mostra critério, não dogma.
13.3 DaemonSet, Job e CronJob
| Workload | Garante | Casos reais |
|---|---|---|
| DaemonSet | 1 Pod por node (inclusive nodes novos) | Coletor de logs (Fluent Bit), agente de métricas (node-exporter), CNI |
| Job | Execução até completar N vezes com sucesso | Migração de banco, processamento em lote (backoffLimit, parallelism) |
| CronJob | Job em agenda cron | Backup noturno, relatórios (concurrencyPolicy: Forbid evita sobreposição) |
apiVersion: batch/v1 kind: CronJob metadata: { name: pg-backup } spec: schedule: "0 3 * * *" concurrencyPolicy: Forbid jobTemplate: spec: backoffLimit: 2 template: spec: restartPolicy: Never containers: - name: dump image: postgres:16-alpine command: ["sh","-c","pg_dump $DATABASE_URL | gzip > /backup/$(date +%F).sql.gz"]
Exercícios do capítulo 13
- Suba um Postgres via StatefulSet com volumeClaimTemplates no kind, grave dados, delete o Pod e comprove que
postgres-0volta com os mesmos dados. - Escreva um Job de migração que roda antes do deploy da API (padrão comum: Job + initContainer aguardando).
- Crie o CronJob de backup acima apontando para seu Postgres e verifique execuções com
kubectl get jobs. - Pesquisa dirigida: leia a página do CloudNativePG e liste 5 coisas que o operator automatiza que um StatefulSet puro não faz.
Escalonamento e agendamento
HPA, autoscaling de nodes e o controle fino de onde cada Pod roda — o coração de custo e resiliência.
Nível: avançado14.1 As três dimensões de autoscaling
| Camada | Ferramenta | Escala o quê |
|---|---|---|
| Horizontal (Pods) | HPA | Número de réplicas por métrica (CPU %, memória, métricas custom via Prometheus Adapter/KEDA) |
| Vertical (Pods) | VPA | Ajusta requests/limits; ótimo em modo "recomendação" para dimensionar |
| Nodes | Cluster Autoscaler / Karpenter | Cria/remove máquinas quando Pods ficam Pending ou nodes ociosos |
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: { name: api } spec: scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: api } minReplicas: 2 maxReplicas: 20 metrics: - type: Resource resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } } behavior: # evita flapping: desce devagar scaleDown: { stabilizationWindowSeconds: 300 } # requer metrics-server instalado; teste com gerador de carga: kubectl run load --rm -it --image=busybox -- sh -c 'while true; do wget -qO- http://api; done' kubectl get hpa -w
Pontos de senioridade: o HPA calcula sobre requests (requests errados = autoscaling errado); CPU é métrica ruim para apps I/O-bound — use KEDA para escalar por fila (SQS, Kafka lag) ou requisições, inclusive até zero. Karpenter (AWS, hoje CNCF) substituiu o Cluster Autoscaler como padrão: provisiona o node ideal em segundos direto da API, consolida nodes ociosos e casa com Spot — é assunto quente de FinOps em entrevistas.
14.2 Direcionando o scheduler
- nodeSelector / nodeAffinity: "este Pod PRECISA/prefere rodar em nodes com tal label" (ex.:
gpu=true,topology.kubernetes.io/zone). - Taints + tolerations: o inverso — o node repele Pods, salvo os que toleram. Uso clássico: reservar pool de GPU (
kubectl taint nodes gpu1 gpu=true:NoSchedule), nodes Spot, control plane. - podAntiAffinity / topologySpreadConstraints: espalhar réplicas por nodes/zonas — sem isso, "3 réplicas" podem cair juntas com um único node.
topologySpreadConstraintsé a forma moderna e mais barata para o scheduler. - PriorityClass + preemption: cargas críticas despejam as de baixa prioridade quando falta recurso.
14.3 PodDisruptionBudget: sobrevivendo à manutenção
apiVersion: policy/v1 kind: PodDisruptionBudget metadata: { name: api } spec: minAvailable: 2 selector: { matchLabels: { app: api } }
Durante kubectl drain (upgrade de node, consolidação do Karpenter), o PDB impede que despejos voluntários derrubem mais réplicas do que o permitido. Sem PDB, upgrades de cluster causam downtime — e "por que meu app caiu no upgrade do EKS?" é história real de mercado. Complete o combo de resiliência com terminationGracePeriodSeconds + tratamento de SIGTERM no app (graceful shutdown, fechando conexões antes de morrer) e preStop hook com sleep curto para o LB parar de mandar tráfego antes do encerramento.
"Como você garante alta disponibilidade de um Deployment?" Resposta em camadas: réplicas ≥ 2 + topologySpreadConstraints por zona + PDB + probes corretas + HPA com comportamento estável + graceful shutdown + requests bem dimensionados. Quem responde só "aumento as réplicas" fica no básico.
Exercícios do capítulo 14
- Instale o metrics-server no kind, aplique o HPA acima e provoque o scale-up com carga; observe o scale-down lento pelo behavior.
- Crie um kind com 3 nodes, aplique taint num deles e prove que só Pods com toleration entram lá.
- Adicione topologySpreadConstraints ao seu Deployment e confirme com
kubectl get pods -o widea distribuição. - Faça
kubectl drainde um node com e sem PDB e compare o comportamento.
Segurança no Kubernetes
RBAC, Pod Security e policy-as-code — os temas da certificação CKS e das vagas em bancos e fintechs.
Nível: avançado15.1 Os 4 Cs
Modelo mental oficial: segurança em camadas — Cloud (conta, rede, IAM) → Cluster (API server, RBAC, etcd cifrado) → Container (imagem mínima, scanning, não-root) → Code (dependências, segredos). Cada camada assume que a de fora pode falhar.
15.2 RBAC: quem pode fazer o quê
apiVersion: rbac.authorization.k8s.io/v1 kind: Role # Role = 1 namespace; ClusterRole = cluster todo metadata: { name: reader, namespace: prod } rules: - apiGroups: ["", "apps"] resources: [pods, deployments, pods/log] verbs: [get, list, watch] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: { name: devs-read, namespace: prod } subjects: [{ kind: Group, name: devs, apiGroup: rbac.authorization.k8s.io }] roleRef: { kind: Role, name: reader, apiGroup: rbac.authorization.k8s.io } # testar permissões (comando de prova CKA/CKS): kubectl auth can-i delete pods -n prod --as=maria kubectl auth can-i --list -n prod
- ServiceAccounts são as identidades dos Pods. Regra: cada workload com sua SA,
automountServiceAccountToken: falsequando a app não fala com a API — o token montado por padrão é vetor clássico de escalada. - Perigos a reconhecer de vista: ClusterRoleBinding a
cluster-adminpara SAs; permissão de criar Pods (permite montar o token de qualquer SA do namespace = escalada); verboescalate/impersonate. - Na nuvem, associe SA a papéis de IAM (IRSA/Pod Identity no EKS, Workload Identity no GKE) em vez de chaves estáticas.
15.3 Pod Security e policy-as-code
- Pod Security Standards (substituíram as PodSecurityPolicies, removidas na 1.25 — pegadinha de entrevista): três perfis —
privileged,baseline,restricted— aplicados por label no namespace:kubectl label ns prod pod-security.kubernetes.io/enforce=restricted - O perfil
restrictedexige o securityContext canônico que você deve saber escrever de cor:
securityContext: runAsNonRoot: true runAsUser: 10001 allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: { drop: ["ALL"] } seccompProfile: { type: RuntimeDefault }
- Admission policy engines: para regras além do Pod Security ("toda imagem vem do nosso registry", "todo Deployment tem limits"), o mercado usa Kyverno (políticas em YAML, curva suave — o mais adotado) ou OPA Gatekeeper (linguagem Rego, mais poderoso). Desde o K8s 1.30, ValidatingAdmissionPolicy nativa (expressões CEL) cobre casos simples sem instalar nada.
- Runtime security: Falco detecta comportamento anômalo ao vivo (shell aberto em container de produção, leitura de /etc/shadow) via eBPF.
- Auditoria e etcd: habilite audit logs do API server e encryption at rest; backup do etcd (
etcdctl snapshot save) é tópico garantido da CKA.
"Como você limitaria o raio de explosão de um Pod comprometido?" Resposta em camadas: imagem distroless não-root → securityContext restricted → SA dedicada sem token automontado e IAM mínimo → NetworkPolicy default-deny → namespace com quota → Falco alertando em runtime. Cada camada citada com o porquê = candidato de outro nível.
Exercícios do capítulo 15
- Crie um usuário fictício com Role somente-leitura e prove com
kubectl auth can-i --asque ele não deleta Pods. - Aplique
enforce=restrictednum namespace e tente subir um Pod sem securityContext — leia e entenda a rejeição. - Instale o Kyverno e escreva uma policy que bloqueia imagens com tag
latest. Teste. - Desafio CKS: num cluster kind, faça snapshot do etcd e restaure-o.
Helm e Kustomize
Empacotar e reutilizar YAML entre ambientes — ninguém mantém manifests duplicados na mão.
Nível: intermediário → avançado16.1 Helm: o gerenciador de pacotes do K8s
Charts são pacotes de manifests com templates Go + valores parametrizáveis. Dois usos: consumir charts prontos (Prometheus, ingress-nginx, cert-manager — é assim que se instala quase tudo) e criar o chart da sua aplicação.
# consumir: repositório → install com valores próprios helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm install monitor prometheus-community/kube-prometheus-stack -n monitoring --create-namespace -f my-values.yaml helm upgrade monitor ... && helm rollback monitor 1 && helm uninstall monitor # criar o seu helm create minha-api helm template minha-api ./minha-api | less # renderiza local: SEMPRE revise antes helm install api ./minha-api -f values-prod.yaml --atomic # --atomic: falhou, reverte sozinho
# values.yaml replicaCount: 3 image: { repository: ghcr.io/voce/minha-api, tag: "1.2.0" } # templates/deployment.yaml (trecho) replicas: {{ .Values.replicaCount }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
16.2 Kustomize: patches sem templates
Embutido no kubectl (kubectl apply -k): uma base de YAML puro + overlays que aplicam patches por ambiente. Sem linguagem de template — só YAML válido, fácil de revisar:
app/ ├── base/ (deployment.yaml, service.yaml, kustomization.yaml) └── overlays/ ├── dev/ └── prod/kustomization.yaml: resources: [../../base] namespace: prod replicas: [{ name: api, count: 5 }] images: [{ name: minha-api, newTag: "1.2.0" }] patches: - patch: |- - op: add path: /spec/template/spec/containers/0/resources/limits value: { memory: 1Gi } target: { kind: Deployment, name: api }
16.3 Qual usar? (pergunta certa de entrevista)
| Helm | Kustomize | |
|---|---|---|
| Modelo | Templates + valores; pacote versionado com lifecycle (install/upgrade/rollback) | Base + patches; sem estado, sem templates |
| Brilha em | Distribuir software (público/interno), muita parametrização, dependências | Variação simples entre ambientes dos SEUS apps; legibilidade |
| Dói em | Templates ilegíveis quando crescem; depurar {{ }} aninhado | Lógica condicional complexa; distribuição para terceiros |
Resposta madura: "não é ou/ou — consumo software de terceiros via Helm, gerencio meus apps com Kustomize (ou um chart interno único), e em GitOps o Argo CD renderiza ambos". Isso reflete o mercado real.
Exercícios do capítulo 16
- Crie um chart Helm para sua API com replicas, imagem, resources e ingress parametrizados; instale com values de dev e de prod.
- Refaça a mesma separação com Kustomize base + overlays e compare a legibilidade dos diffs no Git.
- Instale o kube-prometheus-stack via Helm no kind (você usará no cap. 19).
Muito avançado: Operators, GitOps e service mesh
Os três temas que definem o teto técnico das vagas sênior/staff em plataformas Kubernetes.
Nível: muito avançado17.1 CRDs e Operators: estendendo o Kubernetes
Uma CustomResourceDefinition ensina à API do K8s um tipo novo (ex.: kind: PostgresCluster). Um Operator = CRD + controlador que reconcilia esse tipo, codificando conhecimento operacional humano: o operator do Postgres sabe fazer failover, backup e upgrade; o do cert-manager sabe renovar certificados. Todo o ecossistema moderno (Argo CD, Prometheus Operator, Istio, cert-manager) é feito de operators — entender o padrão é entender o ecossistema.
kubectl get crds kubectl explain certificate.spec --recursive | head -30 # o loop de um controlador, em pseudocódigo: for each evento em MeuRecurso: desejado = spec do recurso atual = observar o mundo real if atual != desejado: agir() # criar/ajustar recursos filhos atualizar status
Para construir operators: Kubebuilder/controller-runtime (Go, padrão da indústria) ou Operator SDK. Saber escrever um operator simples é diferencial raro e muito bem pago; para a maioria das carreiras, saber operar e depurar operators (ler status, conditions e logs do controlador) já coloca você no topo.
17.2 GitOps com Argo CD
GitOps leva o modelo declarativo até o fim: o Git é a única fonte de verdade; um agente no cluster reconcilia continuamente o cluster com o repositório. Ninguém roda kubectl apply em produção — muda-se um PR.
- Pull, não push: o Argo CD (ou Flux) dentro do cluster puxa do Git — o CI não precisa de credenciais do cluster (ganho enorme de segurança).
- Drift detection: alguém mexeu na mão? O Argo marca OutOfSync e (se configurado) reverte sozinho —
selfHeal: true. - Auditoria e rollback grátis: o histórico de produção é
git log; rollback égit revert.
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: { name: api-prod, namespace: argocd } spec: project: default source: repoURL: https://github.com/voce/gitops-repo path: apps/api/overlays/prod targetRevision: main destination: { server: https://kubernetes.default.svc, namespace: prod } syncPolicy: automated: { prune: true, selfHeal: true }
Fluxo completo de mercado: push no repo da app → CI testa e publica imagem por SHA → CI (ou Argo CD Image Updater) abre PR no repo de GitOps trocando a tag no overlay → merge → Argo sincroniza. Para canário/blue-green automatizados por métricas, o complemento é Argo Rollouts. Flux é a alternativa equivalente (mais unix-like, sem UI própria); saiba comparar os dois.
17.3 Service mesh
Um mesh injeta um proxy ao lado de cada Pod (ou no kernel, via eBPF) e move para a plataforma o que antes era código em cada app:
- mTLS automático entre todos os serviços (identidade + criptografia — requisito em ambientes regulados);
- Gerência de tráfego: retries, timeouts, circuit breaking, espelhamento, splits % (base de canário fino);
- Observabilidade uniforme: métricas RED e traces de toda chamada serviço↔serviço sem tocar no código.
Opções: Istio (o mais completo e mais cobrado em vagas; modo ambient sem sidecar reduziu muito o custo), Linkerd (simplicidade e leveza) e Cilium Service Mesh (eBPF, sem sidecars). Resposta sênior sobre "devo usar mesh?": "só quando a escala de microsserviços justifica o custo operacional; para mTLS e retries em escala pequena, um bom ingress + bibliotecas resolvem" — critério vale mais que hype.
17.4 Troubleshooting profundo: a árvore de decisão
Pod Pending → describe: sem recursos? (requests × capacidade) | taint sem toleration?
| PVC não vinculado? | quota estourada?
ImagePullBackOff → nome/tag existem? registry privado sem imagePullSecret? rate limit do Hub?
CrashLoopBackOff → logs --previous! | exit 1: erro do app | 137: OOMKilled ou SIGKILL
| 143: SIGTERM | config/secret faltando? | comando errado?
Running não-Ready→ readiness falhando: endpoint certo? dependência fora? porta certa?
OOMKilled → subir memory limit? vazamento? (compare métricas ao longo do tempo)
Service sem resp.→ escada do cap. 12 (endpoints → pod direto → DNS → netpol → targetPort)
Node NotReady → describe node: disco? memória? kubelet? | drain + investigar
Ferramentas de gente grande: kubectl debug (containers efêmeros para inspecionar Pod distroless sem shell: kubectl debug -it pod/api --image=netshoot --target=api), k9s (TUI que multiplica sua velocidade), stern (logs agregados de múltiplos Pods), e kubectl get events -A --sort-by=.lastTimestamp como primeiro reflexo em qualquer incidente.
Exercícios do capítulo 17
- Projeto GitOps completo (o mais valioso da apostila): instale o Argo CD no kind, crie um repo
gitops-repocom overlays dev/prod da sua API, configure a Application com selfHeal e demonstre: (a) deploy via PR; (b) drift revertido automaticamente após umkubectl scalemanual. Grave um GIF para o README. - Explore um operator de verdade: instale o CloudNativePG, crie um
Clusterde 2 instâncias, mate a primária e observe o failover pelos eventos. - Provoque e diagnostique, sem olhar a árvore: um Pending por requests impossíveis, um CrashLoop por env faltando e um ImagePullBackOff. Cronometre-se.
- Desafio staff: siga o tutorial do Kubebuilder e crie uma CRD
Backupcom um controlador que cria CronJobs a partir dela.
Terraform e infraestrutura como código
Provisionar nuvem de forma versionada e reprodutível — o terceiro pilar (com Docker e K8s) do trio mais pedido em vagas.
Nível: avançado18.1 Por que IaC
Infra clicada no console é irreproduzível, não auditável e diverge entre ambientes. Com IaC, a infraestrutura vira código: revisada em PR, testada, aplicada de forma idempotente. Terraform domina o mercado (HCL, multi-nuvem); OpenTofu é o fork open-source 1:1 (após a mudança de licença da HashiCorp em 2023 — bom assunto para mostrar que você acompanha o ecossistema); alternativas: Pulumi (linguagens reais), CloudFormation/CDK (só AWS), Ansible (gerência de configuração — complementar, não concorrente).
18.2 O núcleo: recursos, estado e o ciclo plan/apply
terraform { required_version = ">= 1.9" backend "s3" { # estado remoto + lock: obrigatório em time bucket = "empresa-tfstate" key = "prod/eks.tfstate" region = "us-east-1" use_lockfile = true } } module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.0" name = "prod" cidr = "10.0.0.0/16" azs = ["us-east-1a", "us-east-1b", "us-east-1c"] private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] } module "eks" { source = "terraform-aws-modules/eks/aws" version = "~> 20.0" cluster_name = "prod" cluster_version = "1.31" vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnets }
terraform init # baixa providers/módulos, configura backend terraform plan -out plan.bin # diff: o que será criado/alterado/DESTRUÍDO — leia sempre terraform apply plan.bin terraform state list && terraform destroy
- O estado (tfstate) é sagrado: mapeia código ↔ recursos reais. Remoto (S3) com lock, cifrado, nunca no Git (contém segredos). Corromper/perder estado é o incidente clássico de Terraform — conheça
terraform importestate mvpara cirurgias. - Drift: mudou no console? O próximo
planmostra a divergência. Política madura: console somente-leitura em prod. - Módulos são as "funções" do Terraform: encapsulam padrões (a empresa inteira cria VPCs iguais). Fixe versões (
~>). - Workspaces vs diretórios por ambiente: o mercado prefere diretórios (
envs/dev,envs/prod) com backends separados — raio de explosão menor. - Pipeline de IaC: PR roda
fmt+validate+plancomentado no PR (Atlantis ou GitHub Actions) + scanners (tfsec/checkov); apply só após aprovação, via OIDC.
Trio garantido: "O que é o state e por que remoto com lock?" (mapeamento código↔real; lock evita dois applies simultâneos corrompendo tudo). "Diferença entre Terraform e Ansible?" (provisionar infra declarativa e imutável vs configurar máquinas existentes; hoje Ansible perde espaço para imagens imutáveis + user data). "Como você importa infra que já existe?" (terraform import + escrever o recurso correspondente; ou ferramentas de geração).
Exercícios do capítulo 18
- Sem nuvem paga: use o provider
kindoudockerdo Terraform para criar um cluster kind + namespaces + um Deployment inteiramente viaterraform apply. - Com free tier AWS: crie VPC + bucket S3 com estado remoto; provoque drift no console e observe o plan detectar.
- Rode
checkovno seu código e corrija 3 apontamentos. - Estruture
envs/deveenvs/prodconsumindo o mesmo módulo local com variáveis diferentes.
Observabilidade e SRE
Métricas, logs, traces, SLOs e resposta a incidentes — como se opera produção de verdade.
Nível: avançado19.1 Os três pilares (e o que cada um responde)
- Métricas (números agregados no tempo): "o quê está anormal?" — baratas, base de alertas. Stack padrão: Prometheus (coleta por pull de endpoints
/metrics, descobre alvos pela API do K8s) + Grafana (dashboards) + Alertmanager (roteia alertas p/ Slack/PagerDuty). Instala-se tudo com o chartkube-prometheus-stack. - Logs (eventos textuais): "o que exatamente aconteceu?" — app escreve JSON estruturado em stdout; um DaemonSet (Fluent Bit/Promtail/Alloy) coleta; Loki (barato, indexa só labels — casa com Grafana) ou ELK/OpenSearch armazenam.
- Traces (a jornada de UMA requisição por N serviços): "onde a latência/erro nasceu?" — instrumentação via OpenTelemetry (o padrão universal: SDK + Collector), backend Tempo/Jaeger. Correlação métrica→trace→log no Grafana é o fluxo de debug moderno.
# taxa de requisições por segundo (counter sempre com rate()) sum(rate(http_requests_total{job="api"}[5m])) # % de erro (a métrica de SLO por excelência) sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) # latência p95 a partir de histograma histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) # memória dos Pods vs limite (prevendo OOM) container_memory_working_set_bytes / on(pod) kube_pod_container_resource_limits{resource="memory"}
Frameworks de "o que medir": RED por serviço (Rate, Errors, Duration) e USE por recurso (Utilization, Saturation, Errors). Golden signals do Google = RED + saturação.
19.2 SRE: SLI, SLO e error budget
- SLI: a medida ("% de requisições com sucesso em <300 ms").
- SLO: a meta interna ("99,9% em 30 dias"). SLA é o contrato externo com multa — sempre mais frouxo que o SLO.
- Error budget: 100% − SLO = quanto pode falhar (99,9% ⇒ ~43 min/mês). É a ponte política entre velocidade e estabilidade: budget sobrando → acelere deploys; budget estourado → congela features e prioriza confiabilidade. Alertas maduros disparam por burn rate do budget (multiwindow), não por "CPU > 80%" — alerta bom exige ação humana e aponta impacto no usuário.
19.3 Incidentes e postmortems
- Papéis: Incident Commander coordena (não debuga), comunicador atualiza stakeholders, operadores investigam. Severidades (SEV1–3) definem quem acorda.
- Mitigar antes de entender: rollback/feature-flag primeiro; causa raiz depois. MTTR agradece.
- Postmortem blameless: linha do tempo, impacto, causas (técnica de "5 porquês"), ações com dono e prazo. Sem culpados: pessoas erram porque o sistema permitiu. Ter participado (ou simulado) de postmortems é história de ouro em entrevista comportamental de SRE.
"Latência p99 dobrou após um deploy, o que você faz?" Resposta estruturada: verificar impacto no SLO → rollback imediato (mitigação) → comparar janelas antes/depois nas métricas RED → traces das requisições lentas para achar o span vilão → logs correlacionados → hipótese: N+1? pool esgotado? CPU throttling por limit novo? GC? → corrigir, re-deploy canário observando burn rate → postmortem. A ordem (mitigar → diagnosticar) importa mais que a causa exata.
Exercícios do capítulo 19
- Com o kube-prometheus-stack instalado (cap. 16), exponha
/metricsna sua API (lib Prometheus da sua linguagem), crie um ServiceMonitor e monte um dashboard RED no Grafana. - Escreva as 4 queries PromQL acima para a SUA aplicação e crie um alerta de taxa de erro > 1% por 5 min chegando (Alertmanager → webhook de teste).
- Instale Loki + Alloy, converta os logs da sua API para JSON estruturado e faça uma busca por request_id.
- Defina SLI/SLO da sua API, calcule o error budget e escreva um mini-postmortem (1 página) do exercício de CrashLoop do cap. 17.
DevSecOps e supply chain
Segurança embutida na esteira — a camada que virou requisito após os grandes ataques de cadeia de suprimentos.
Nível: muito avançado20.1 Shift-left: segurança em cada estágio do pipeline
| Estágio | Controle | Ferramentas |
|---|---|---|
| Commit | Varredura de segredos (pre-commit + CI) | gitleaks, trufflehog |
| Código | SAST (análise estática) e SCA (dependências) | Semgrep, CodeQL; Dependabot/Renovate, Trivy |
| Build | Scan de imagem + IaC misconfig | Trivy, Grype; checkov, tfsec |
| Deploy | Admission: só imagens assinadas/aprovadas | Kyverno + cosign, Gatekeeper |
| Runtime | Detecção de anomalias, mTLS, netpol | Falco, mesh, Cilium |
Princípio de adoção realista: comece alertando, depois passe a bloquear — esteira que bloqueia tudo no dia 1 é esteira que os devs contornam.
20.2 Supply chain: assinar e provar a origem
Ataques como SolarWinds e pacotes maliciosos em registries deslocaram o foco para "como sei que este artefato é o que penso que é?":
- SBOM (inventário de componentes, formatos SPDX/CycloneDX) gerado no build e anexado à imagem — exigido em contratos com governo e grandes empresas.
- Assinatura com cosign (projeto Sigstore):
cosign sign ghcr.io/voce/api@sha256:...— no modo keyless, a assinatura usa a identidade OIDC do pipeline, sem chave para vazar. No cluster, uma policy KyvernoverifyImagesrejeita imagens não assinadas. - SLSA: níveis de garantia de procedência (provenance) do build — saber dizer "geramos provenance SLSA no CI" posiciona você na fronteira do tema.
- Higiene contínua: Renovate/Dependabot atualizando dependências e bases de imagem automaticamente via PR — o controle com melhor custo-benefício de todos.
cosign sign --yes ghcr.io/voce/minha-api@sha256:9f8e... cosign verify ghcr.io/voce/minha-api@sha256:9f8e... \ --certificate-identity-regexp "github.com/voce/.*" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com
Exercícios do capítulo 20
- Adicione ao seu pipeline: gitleaks + Trivy (falhando em CRITICAL) + geração de SBOM como artefato.
- Assine sua imagem com cosign keyless no GitHub Actions e verifique localmente.
- Escreva a policy Kyverno que só admite imagens assinadas pelo seu repositório e demonstre a rejeição de uma imagem qualquer.
- Ative o Renovate no seu repositório e faça o merge do primeiro PR automático.
Mercado, certificações e entrevistas
Converter tudo o que você estudou em emprego: roadmap, portfólio, certificações e o banco de perguntas.
Nível: todos21.1 O mapa das vagas no Brasil (2026)
| Nível | O que se espera | Faixa CLT/PJ típica (BR)* |
|---|---|---|
| Júnior / Analista | Linux, Git, Docker, CI básico, uma nuvem no básico, vontade de plantão | R$ 4–8 mil |
| Pleno | K8s no dia a dia, Terraform, pipelines completos, observabilidade, troubleshooting autônomo | R$ 9–16 mil |
| Sênior / SRE | Arquitetura de plataforma, GitOps, segurança, SLOs, incidentes, mentoria, custo (FinOps) | R$ 16–28 mil+ |
| Staff / Specialist | Operators, multi-cluster, decisões de longo prazo, influência entre times | R$ 25–40 mil+ · internacional US$ 90–160k |
*Faixas indicativas — variam por região, setor (fintechs pagam mais) e formato; vagas remotas internacionais (contractor) multiplicam os valores. Pesquise faixas atuais em levantamentos como Glassdoor e pesquisas salariais da comunidade antes de negociar.
A interseção pedida em praticamente toda vaga pleno no Brasil: AWS + Kubernetes (EKS) + Terraform + GitHub Actions/GitLab CI + Prometheus/Grafana + Python ou Go para automação. Diferenciais 2025–2026 que destacam currículo: GitOps (Argo CD), Karpenter/FinOps, plataforma interna (Backstage), segurança de supply chain e infra para IA (GPUs no K8s — MLOps).
21.2 Certificações: quais valem e em que ordem
| Certificação | Para quem | Comentário de mercado |
|---|---|---|
| CKA (Certified Kubernetes Administrator) | O alvo nº 1 desta apostila | Prática (terminal ao vivo, ~2h). A mais reconhecida em vagas DevOps/SRE. Caps. 9–17 cobrem o currículo; treine velocidade com killer.sh |
| CKAD | Perfil mais dev | Foco em workloads/config; mais fácil que a CKA. Boa primeira prova prática |
| CKS | Sênior/segurança | Exige CKA válida; rara e muito valorizada em bancos/fintechs (cap. 15 + 20) |
| KCNA / KCSA | Iniciantes | Teóricas e baratas; bom aquecimento, pouco peso sozinho |
| AWS SAA / DevOps Pro | Todos os níveis | SAA (Solutions Architect Associate) é o filtro de RH mais comum no Brasil; DevOps Professional para sênior |
| Terraform Associate | Complemento | Barata e objetiva; fecha o trio com CKA + AWS |
Ordem sugerida: AWS SAA → CKA → Terraform Associate (→ CKS se seguir para segurança). Certificação abre a porta do RH; o que sustenta a entrevista técnica é o laboratório que você construiu nesta apostila.
21.3 Portfólio: 3 projetos que provam senioridade
- Plataforma completa de referência (o carro-chefe): API sua containerizada (multi-stage, distroless, não-root) → CI no GitHub Actions (testes, Trivy, SBOM, cosign, push por SHA) → deploy em K8s com Helm/Kustomize → GitOps com Argo CD (dev/prod) → observabilidade (Prometheus/Grafana/Loki) → tudo provisionado por Terraform. README com diagrama de arquitetura e GIFs de rollout, drift-heal e rollback. Este único repositório responde 80% de qualquer entrevista.
- Incidente documentado: injete falhas no projeto 1 (OOM, liveness errada, netpol bloqueando) e escreva postmortems blameless com gráficos das métricas. Ninguém faz isso — você será lembrado.
- Automação em Python/Go: uma CLI útil de operação (ex.: relatório de Pods sem limits por namespace, limpeza de imagens antigas no ECR) — prova que você programa, não só configura.
21.4 Banco de perguntas de entrevista (com a resposta-esqueleto)
- "Explique o que acontece num
kubectl apply" → fluxo do cap. 9.3, terminando em reconciliação contínua. - "Container vs VM?" → processo isolado por namespaces/cgroups vs kernel próprio; trade-off isolamento × densidade (cap. 4).
- "Pod em CrashLoopBackOff, e agora?" →
logs --previous+describe+ exit codes (137=OOM/KILL, 143=TERM) + árvore do cap. 17.4. - "Readiness vs liveness?" → tráfego vs restart; antipadrão da liveness com dependências (cap. 11).
- "Como faz deploy sem downtime?" → RollingUpdate + readiness + PDB + graceful shutdown + preStop; canário com Argo Rollouts para risco menor (caps. 3, 10, 14).
- "Requests vs limits?" → reserva vs teto; throttling vs OOMKilled; prática memória req=limit, CPU sem limit (cap. 11).
- "O que é GitOps e por que pull?" → Git como fonte de verdade, reconciliação, credenciais fora do CI, drift/selfHeal (cap. 17).
- "Secret do K8s é seguro?" → base64 ≠ criptografia; encryption at rest + RBAC + cofre externo (cap. 11).
- "Por que o state do Terraform é crítico?" → mapeamento código↔real, remoto+lock, drift, import (cap. 18).
- "Defina SLO e error budget" → meta + orçamento de falha como mecanismo de decisão velocidade×estabilidade (cap. 19).
- Comportamentais: prepare 3 histórias no formato STAR — um incidente que você mitigou, uma automação que economizou tempo/dinheiro (quantifique!), um conflito técnico resolvido com dados.
21.5 Plano de estudo e próximos passos
| Semanas | Meta |
|---|---|
| 1–2 | Caps. 1–3: laboratório montado + primeiro pipeline no ar |
| 3–5 | Caps. 4–8: Docker completo; API própria com imagem otimizada e escaneada |
| 6–10 | Caps. 9–14: K8s essencial; app rodando no kind com probes, HPA e Ingress |
| 11–13 | Caps. 15–17: segurança, Helm, GitOps — projeto de portfólio nº 1 completo |
| 14–16 | Caps. 18–20 + simulados killer.sh → agende a CKA |
| contínuo | Cap. 19 no projeto, postmortems, aplicar para vagas desde a semana 10 (não espere "estar pronto") |
Fontes para seguir evoluindo: documentação oficial do Kubernetes (kubernetes.io — a melhor doc do ecossistema), Site Reliability Engineering e The SRE Workbook (Google, gratuitos online), The Phoenix Project e Accelerate (a base cultural/DORA), relatórios anuais da CNCF, e o simulador killer.sh para as provas Kubernetes.
Se três ideias ficaram, o trabalho está feito: (1) tudo é o loop de reconciliação — do Deployment ao Argo CD ao Terraform, você declara estado e sistemas convergem; (2) imutabilidade + automação — imagens fixas por digest, infra por código, deploy por PR: humanos decidem, robôs executam; (3) produção se opera com dados — probes, métricas, SLOs e postmortems transformam incidentes em aprendizado. Comandos mudam de versão; esses princípios sustentam a carreira inteira.