transmissão ao vivo · o gp do kubernetes avançado, etapa por etapa
Kubernetes avançado
direto do paddock.
De volta à pista, e agora é campeonato de gente grande! Nove etapas da Fase 2: saúde e recursos, agendamento fino, rollouts, Helm, Blue/Green, Canary, Karpenter, KEDA e segurança. Você já sabe pilotar o carro — aqui a gente afina o setup pra vencer o mundial. Farol apagado, o piloto assume.
Este é o guia da Fase 2: assume que você já domina o básico (Pods, Deployments, Services, kubectl) e sobe o nível pra operar clusters de verdade — o problema que cada peça resolve, a analogia de F1 honesta, o YAML comentado, os comandos com a saída esperada e um mão na massa por etapa.
O quadro de-para — cole isto no volante
A mesma analogia vale do início ao fim do guia. Não tente decorar esta tabela agora — ela é o mapa do circuito, não a prova: siga direto para a Etapa 01 e volte aqui sempre que uma analogia aparecer no texto.
| Kubernetes | Fórmula 1 |
|---|---|
| Container | Componente do carro (a PU híbrida, a ERS, a caixa de câmbio) — a peça que faz o trabalho |
| Pod | O carro completo — menor unidade que vai pra pista; agrupa componentes que dividem o mesmo chassi e a mesma telemetria |
| Node (nó) | O box/garagem na pit lane — hospeda vários carros e tem recurso finito (energia, ar comprimido, espaço) |
| Cluster | A equipe inteira — todos os boxes + muro + operação |
| kubelet | O chefe de mecânicos daquele box — vigia os carros ali, troca componente, chama pro reparo |
| Deployment | A ficha de operação que garante N carros idênticos prontos no grid |
| ReplicaSet | O conjunto de carros de uma mesma especificação/versão de setup |
| Réplica | Cada carro idêntico da equipe no grid |
| Service | O muro/rádio com endereço fixo que direciona o trabalho pro carro certo |
| Endpoints | A lista de carros no momento aptos a receber trabalho |
| Load balancing | O engenheiro de estratégia distribuindo stints/voltas entre os carros |
| Liveness probe | Telemetria "o carro travou?" → se sim, chama pro box e reinicia o componente |
| Readiness probe | Telemetria "o carro está em ritmo?" → tira/põe no pelotão, sem chamar pro box |
| Startup probe | "Terminou o out-lap de aquecimento?" (pneu e freio na janela de temperatura) |
| Requests / limits | Energia garantida por volta vs. teto que não pode furar (envelope de ERS / cota) |
| OOMKill | Componente estourou o envelope e queimou — falha catastrófica, carro fora |
| CPU throttling | Derate / engine mapping segurando potência pra ficar dentro da cota |
| Classes de QoS | A ordem de quem a equipe sacrifica sob safety car / contenção |
| Taint | Placa no box: "reservado, só carro autorizado" (box de pneu de chuva, box de GPU) |
| Toleration | A credencial do carro que aceita entrar naquele box marcado |
| Node affinity | Ordem de estratégia: "esse carro TEM que ir pra esse box/setup" |
| Pod anti-affinity | Nunca colocar os dois carros da equipe no mesmo box (risco duplo) |
| Topology Spread | Espalhar os carros por boxes/zonas com garantia numérica |
| Eviction | O chefe de mecânicos sacrificando um carro pra salvar o box quando falta recurso |
| PriorityClass | Carro do líder do campeonato vs. carro de teste — quem se salva primeiro |
| RollingUpdate | Trocar o setup carro a carro ao longo de pit stops, sem esvaziar a pista |
| maxUnavailable | O freio: quantos carros posso ter no box ao mesmo tempo |
| maxSurge | A potência: quantos carros extras (T-car) monto pra acelerar a troca |
| Recreate | Chamar todos ao box de uma vez — para tudo, mas nunca mistura versões |
| rollout undo | Voltar pro setup salvo da classificação (baseline de parc fermé) |
| Helm Chart | A fôrma de montagem do carro — mesma base, ajuste por circuito |
| values.yaml | A folha de setup por circuito (asa, pressão de pneu, mapa de motor) |
| helm template (função pura) | O simulador: mesma entrada → mesma saída, roda antes de ir pra pista |
| Blue/Green | O T-car já montado e aquecido nos bastidores; promover = trocar de carro |
| Canary | Mandar UM carro testar o upgrade numa fatia da corrida antes de aplicar nos dois |
| Peso de tráfego (service mesh) | O engenheiro dosando a fração exata, independente do nº de carros |
| SLO gate | O limite de delta/temperatura que aborta o teste na hora |
| Karpenter | O gerente de logística montando box sob medida just-in-time |
| Cluster Autoscaler | Boxes de tamanho fixo, pré-reservados por contrato |
| Spot instance | Recurso barato que pode ser requisitado de volta a qualquer momento |
| PodDisruptionBudget | Regra: nunca tirar réplicas demais juntas (nunca os dois carros no box) |
| KEDA / scale-to-zero | Abrir mecânicos de pit conforme a fila; zero quando não há carro chegando |
| ServiceAccount | O crachá de acesso de cada membro da equipe |
| RBAC (Role/RoleBinding) | O que aquele crachá abre — e só isso |
| IRSA / Workload Identity | Credencial temporária pra entrar no motorhome do fornecedor, sem chave-mestra |
| cert-manager / TLS | Lacres da FIA que expiram e se renovam sozinhos |
| Zero Trust | O regulamento FIA: ninguém passa sem credencial verificada |
Onde as analogias quebram (piloto que esconde limitação do carro quebra no muro):
- Blue/Green (Etapa 05): trocar de carro em pista é limpo, mas o software pode "reescrever o traçado da pista" — o schema do banco. Quando a versão nova muda o esquema de forma incompatível, voltar pro carro antigo não salva: a pista já mudou embaixo dele. Por isso a migração retrocompatível (compatibilidade dupla temporária) vem ANTES da virada de chave — o T-car não cobre esse caso sozinho.
- Réplicas e falha compartilhada (Etapas 01 e 02): dois carros no grid multiplicam disponibilidade SÓ se não dependerem da mesma peça de infra. Liveness amarrada ao mesmo banco, ou os dois pods no mesmo nó, quebram a independência que a analogia dos "dois carros" pressupõe.
Etapa 01 telemetria de bordo
Saúde da aplicação e gerenciamento de recursos
Senhoras e senhores, o pelotão sai da garagem pra primeira volta e a pergunta que abre TODA transmissão é a mais simples e a mais brutal: o carro está inteiro? Está em ritmo? Antes de qualquer estratégia mirabolante, é a telemetria que manda. Prende o cinto, porque a Etapa 01 é onde a gente aprende a LER o carro.
Imagine seu e-commerce numa Black Friday. Um dos pods da API entrou em deadlock: o processo continua de pé, o container não morreu, mas ele responde HTTP 500 para todo cliente que chega. O Kubernetes, sozinho, só sabe uma coisa: "o processo está vivo". Ele não reinicia nada, e você perde vendas por horas até alguém perceber no gráfico. No pod ao lado, um vazamento de memória vai comendo RAM até estourar o nó inteiro e derrubar os vizinhos saudáveis junto.
Como funciona
Foi exatamente o que rolou com o Antonelli em Silverstone: largou na pole (P1) e cruzou em P15. O carro estava vivo — motor rodando, dando voltas, terminou a corrida —, mas não estava em ritmo. Processo de pé, resposta 500. Você precisa de duas telemetrias diferentes: uma que grita "o carro travou" e manda pro box trocar componente (liveness), e outra que diz "o carro está de pé mas sem pace, tira ele da briga até resgatar" sem chamar pro reparo (readiness). E precisa definir de quanta energia de ERS e cota cada componente tem direito pra ninguém roubar o envelope do outro (requests/limits).
As três probes são a telemetria que o muro lê no fim de cada setor. A liveness reinicia o container quando falha N vezes (autocura); a readiness só tira o pod do balanceador, sem reiniciar; a startup protege apps de boot lento, suspendendo as outras duas enquanto o app não sobe. Confundir liveness com readiness causa reinícios em cascata — é a diferença entre chamar o carro pro box (perde 22s) e só mandar ele ceder posição e recuperar.
Requests e limits são o contrato de recursos. requests é o que o container reserva — o piso garantido que o scheduler usa pra decidir em qual nó o pod cabe (a energia de ERS garantida por volta). limits é o teto que ele não pode passar: estourar limite de memória = OOMKill (o kernel mata o container); estourar limite de CPU = throttling (o kernel atrasa o processo pra mantê-lo na cota). Furar o envelope térmico da PU queima o componente; bater no teto de energia é o derate que segura a potência.
Da combinação de requests e limits nasce a classe de QoS do pod — a ordem de sacrifício quando o box fica sem recurso. Guaranteed (requests == limits em todos os containers) são os últimos sacrificados (o carro do líder); Burstable (requests < limits) usam folga ociosa mas cedem sob contenção; BestEffort (sem requests nem limits) são os primeiros despejados (o carro de teste que ninguém protege).
Um detalhe contraintuitivo (caso real da Buffer): colocar limite de CPU pode piorar a latência. O algoritmo de quota do CFS pode throttlar um pod mesmo com uso médio baixo — pequenos picos consomem a cota de 100ms e o processo congela até o próximo período. A Buffer removeu os limits de CPU de serviços sensíveis a latência e viu o p99 cair de 5 a 10x. Em multi-tenant você quer limits pra isolamento; em serviço latency-sensitive, às vezes menos é mais. E réplicas em paralelo multiplicam disponibilidade (A = MTBF / (MTBF + MTTR)): 2 pods a 99% chegam a ~99,99%, porque o Service age como um "OU lógico" — basta um pod Ready. A equipe com dois carros: se um abandona, o outro pontua.
Os conceitos-chave desta etapa
Liveness probe
"Você travou?" Falhou N vezes seguidas → o kubelet reinicia o container. É a autocura; use pra deadlock, nunca pra lentidão passageira (senão vira ciclo de reinícios). Na pista: o DNF irrecuperável, tira da pista.
Readiness probe
"Está pronto pra tráfego?" Falhou → o pod sai do balanceador (dos endpoints), SEM reiniciar. Volta sozinho quando a dependência se recupera. Na pista: o carro cede posição e recupera, sem ir ao box.
Startup probe
"Já terminou de subir?" Enquanto não passa, suspende liveness e readiness — protege apps de boot lento de morrer durante a inicialização. Na pista: o out-lap de aquecimento; ninguém cobra pace com pneu frio.
Requests e limits
requests = piso reservado (o scheduler usa pra alocar); limits = teto que não pode passar. Estourar memória = OOMKill; estourar CPU = throttling. Energia de ERS garantida vs. cota que não pode furar.
Classes de QoS
Guaranteed (requests==limits) é o último sacrificado; Burstable (requests<limits) cede sob contenção; BestEffort (sem nada) é o primeiro despejado. A ordem de sacrifício sob safety car.
YAML e comandos na prática
apiVersion: apps/v1kind: Deploymentmetadata:name: apispec:replicas: 3selector: { matchLabels: { app: api } }template:metadata: { labels: { app: api } }spec:containers:- name: apiimage: myregistry/api:1.0.0ports: [ { containerPort: 8080 } ]startupProbe: # 1) protege o boot lentohttpGet: { path: /healthz, port: 8080 }failureThreshold: 30 # até 30 x 10s = 5min pra subirperiodSeconds: 10 # checa a cada 10slivenessProbe: # 2) travou? reinicia o containerhttpGet: { path: /healthz, port: 8080 }periodSeconds: 10failureThreshold: 3 # 3 falhas seguidas -> restartreadinessProbe: # 3) pronto? entra/sai do balanceadorhttpGet: { path: /ready, port: 8080 }periodSeconds: 5resources:requests: { cpu: 100m, memory: 128Mi } # piso garantido (scheduling)limits: { memory: 256Mi } # teto de RAM; SEM limit de CPU# (evita throttling do CFS)
💡 Este Deployment é Burstable (tem requests de CPU/memória, mas limit só de memória). Pra virar Guaranteed, requests teria que ser igual a limits em todos os recursos de todos os containers.
Comandos essenciais desta etapa
Mão na massa
Prove na prática que a liveness reinicia um container travado — a sua primeira volta rápida do fim de semana:
- Crie um pod cuja liveness aponta pra uma porta que NÃO existe (porta 9999) — ele vai falhar sempre. Use --restart=Always (com --restart=Never o kubelet não reinicia ao falhar a liveness).
- Rode kubectl get pod teste-liveness -w e observe a coluna RESTARTS subindo a cada ~15s.
- Confirme o motivo: kubectl describe pod teste-liveness | grep -i liveness — veja "Liveness probe failed".
- Isso é a autocura: o carro chamado pro box automaticamente porque a telemetria acusou travamento. Limpe com kubectl delete pod teste-liveness.
Dica: Não coloque checagem de banco de dados na liveness probe. Se o banco cair, todas as réplicas falham a liveness ao mesmo tempo, reiniciam juntas e você tem falha em cascata. Cheque dependências na startup (pra não subir sem elas) e degrade graciosamente. É como amarrar a liveness dos dois carros à mesma peça de infra — se aquilo falha, os dois entram no box juntos e você fica sem ninguém na pista.
Anota aí: Liveness reinicia, readiness só tira do tráfego, e todo pod merece pelo menos requests — pod sem request é BestEffort e o primeiro a ser despejado. Liveness é chamar pro box, readiness é ceder posição e recuperar; carro sem energia reservada é o primeiro sacrificado sob safety car.
SETOR 1 NO VERDE! Cruzamos o primeiro parcial e o carro está inteiro, telemetria limpa, recursos sob controle — que largada! Mas agora o traçado se fecha: vem o complexo técnico onde a gente decide EM QUAL box cada carro entra. A estratégia entra em cena. Segura firme que a Etapa 02 é pura curva de precisão.
Etapa 02 complexo técnico da estratégia
Agendamento avançado e comportamento de nós
A pista agora exige cabeça fria. Não basta ter carro rápido: é preciso pôr o carro CERTO no box CERTO e saber quem a equipe sacrifica quando o recurso aperta. É aqui que o engenheiro de estratégia ganha ou perde o domingo. Rádio ligado com o muro!
Você tem um cluster misto: alguns nós têm GPU (caros, para treinar modelos), a maioria é comum. Por padrão o scheduler joga o pod em qualquer nó onde ele caiba — então um pod qualquer pode ocupar o nó de GPU e travar o time de ML, enquanto um job de treino pode cair num nó comum sem GPU e nunca funcionar. Pior: quando um nó fica sem memória, quem o Kubernetes mata primeiro? Se for o pod errado, seu serviço crítico vai junto.
Como funciona
Pense na pit lane. Tem box comum e tem o box especializado — o que tem a manta térmica pros pneus de chuva, o rig de calibração de GPU. Taints são a placa "box reservado, só entra carro autorizado" (repele quem não tem credencial). Affinity é a ordem de estratégia no rádio: "leva esse carro pro box da GPU" (atrai). E eviction é o chefe de mecânicos que, quando falta energia no box, começa a sacrificar o carro de teste antes de mexer no carro do líder do campeonato.
Taints e Tolerations repelem. Um taint é uma marca no nó ("não coloque pods aqui a menos que tolerem X"); a toleration é a marca no pod ("eu sei lidar com X, posso ir"). Os efeitos: NoSchedule (não agenda não-tolerantes, os que já rodam ficam), PreferNoSchedule (evita, mas não é absoluto) e NoExecute (barra novos e expulsa os pods sem toleration que já estavam lá — útil pra esvaziar um nó pra manutenção). Ponto crucial: taint só repele, não atrai. Um pod que tolera o nó de GPU pode acabar num nó comum. Pra garantir que ele vá só pra GPU, combine com affinity.
Node Affinity é a contraparte positiva ("quero ir pra nós com o label X"): required (hard — sem o label, não agenda) vs. preferred (soft, com peso; se não achar, agenda em outro lugar). Já Pod Affinity/Anti-Affinity relaciona pods entre si: affinity aproxima ("coloque o cache perto do front-end"); anti-affinity separa ("não coloque duas réplicas do banco no mesmo nó/zona") — nunca os dois carros no mesmo box. Inter-pod affinity é caro no scheduler acima de centenas de nós; pra espalhamento com garantia numérica, prefira Topology Spread Constraints (maxSkew), que escala melhor.
Eviction sob pressão é a autoproteção do nó. Quando falta memória, disco ou PIDs, o kubelet primeiro tenta limpar (containers mortos, imagens não usadas); se não resolve, despeja pods nesta ordem: primeiro os que excedem suas requests (BestEffort e Burstable estourados), depois por PriorityClass, por último os Guaranteed. Detalhes que salvam: eviction por pressão de nó ignora o PodDisruptionBudget (é emergência); eviction ≠ OOMKill (OOMKill é o cgroup matando um container que passou do limit; eviction é o kubelet retirando pods pra salvar o nó inteiro). Alocar pods com múltiplas restrições é bin packing, NP-difícil — o scheduler usa heurística gulosa (filtra nós → pontua → escolhe): rápida, nunca ótima.
Os conceitos-chave desta etapa
Taints e Tolerations
Taint é a marca no nó ("não coloque pods aqui a menos que tolerem X"); toleration é a marca no pod ("eu lido com X, posso ir"). A placa no box e o crachá do carro. Só repele, não atrai.
Efeitos do taint
NoSchedule (não agenda não-tolerantes), PreferNoSchedule (evita, não é absoluto) e NoExecute (barra novos E expulsa os sem toleration que já estavam — esvazia o nó pra manutenção).
Node Affinity
A contraparte positiva ("quero ir pra nós com o label X"). required (hard: sem o label, não agenda) vs. preferred (soft, com peso). Só a affinity força o carro pro box certo.
Pod (anti-)affinity e Topology Spread
Anti-affinity separa réplicas ("nunca os dois carros no mesmo box"); é caro no scheduler acima de centenas de nós — pra espalhar com garantia numérica, prefira Topology Spread (maxSkew).
Eviction sob pressão
Sem memória/disco/PIDs, o kubelet despeja pods: primeiro os que excedem requests, depois por PriorityClass, por último os Guaranteed. Ignora o PodDisruptionBudget (emergência). ≠ OOMKill (cgroup mata um container).
YAML e comandos na prática
# 1) Marca o nó de GPU (uma vez, via kubectl):# kubectl taint nodes gpu-node-1 nvidia.com/gpu=true:NoScheduleapiVersion: v1kind: Podmetadata: { name: treino-ml }spec:tolerations: # PARTE 1: tolera o taint da GPU (permite entrar)- key: "nvidia.com/gpu"operator: "Equal"value: "true"effect: "NoSchedule"affinity:nodeAffinity: # PARTE 2: E exige ir pra um nó com GPU (força ir)requiredDuringSchedulingIgnoredDuringExecution: # hard: sem label, não agendanodeSelectorTerms:- matchExpressions:- key: "accelerator"operator: Invalues: ["nvidia-gpu"]containers:- name: treinoimage: myregistry/treino:1.0resources:limits: { nvidia.com/gpu: 1 } # pede 1 GPU (recurso estendido)
💡 Só a toleration não bastaria (o pod poderia cair num nó comum). É a combinação toleration + nodeAffinity que garante isolamento forte: credencial pra entrar no box de chuva MAIS a ordem de estratégia mandando ir pra lá.
Comandos essenciais desta etapa
Mão na massa
Veja o taint repelindo um pod em tempo real (funciona em minikube/kind de 1 nó):
- Descubra o nome do nó: kubectl get nodes.
- Aplique o taint: kubectl taint nodes <NOME_DO_NO> demo=sim:NoSchedule — nada sem toleration pode ser agendado.
- Suba um pod comum sem toleration: kubectl run repelido --image=nginx.
- Ele fica Pending; veja o motivo: kubectl describe pod repelido | grep -A2 Events — "had untolerated taint".
- Limpe: remova o taint (note o "-" no fim) com kubectl taint nodes <NOME_DO_NO> demo=sim:NoSchedule- e delete o pod.
Dica: Monitore uso de disco/memória dos nós e alerte em 85%, antes dos ~95% que disparam eviction. Junto com rotação de logs, isso evita o "eviction storm". Prefira reagir manualmente (drenar o nó com kubectl drain) a deixar o kubelet despejar em pânico. É a diferença entre chamar o carro pro box na sua janela planejada e ser forçado a parar sob safety car junto com todo mundo.
Anota aí: Taint repele, affinity atrai — pra reservar um nó de verdade, você precisa dos dois juntos. Credencial de box + ordem de estratégia: um sem o outro não garante que o carro certo vá pro box certo.
SETOR 2 COMPLETO, e que aula de estratégia! Você colocou o carro certo no box certo e já sabe quem a equipe sacrifica sob pressão. Mas agora vem a parte que faz o coração da equipe de pit bater forte: a corrida está em andamento e você PRECISA atualizar o carro sem tirar ninguém da briga. Bem-vindo à arte do pit stop. Etapa 03, LÁ VAMOS NÓS!
Etapa 03 a reta de boxes
Estratégias de atualização e rollouts
Sexta-feira, produção rodando, e você precisa subir a v2 sem que o público perceba. Este é o setor dos nervos de aço: um pit stop mal calculado joga a liderança fora. Aqui a gente aprende a trocar tudo com o carro em movimento. RESPIRA e vem comigo.
Sexta-feira, 16h. Você precisa subir a v2 da API. Se derrubar tudo de uma vez pra subir a nova, o serviço fica fora do ar no meio do expediente. Se subir errado e não tiver como voltar rápido, o fim de semana vira plantão. Pior ainda: se a v2 for incompatível com a v1 (por causa de uma migração de banco), deixar as duas rodando ao mesmo tempo corrompe dados.
Como funciona
É o dilema do pit stop numa corrida longa. RollingUpdate é trocar o setup dos carros um pit stop de cada vez, com o resto da equipe ainda pontuando na pista (a operação nunca para). Recreate é a bandeira vermelha: chama todos ao box de uma vez, para tudo, mas garante que nenhum carro roda com meia-versão de peça — nunca há mistura de spec velha e nova em pista ao mesmo tempo.
RollingUpdate (padrão) dá zero downtime: substitui os pods aos poucos, criando um novo ReplicaSet e reduzindo o antigo proporcionalmente. Dois parâmetros controlam o ritmo. maxUnavailable é o freio — quantos pods podem ficar indisponíveis durante a troca (padrão 25%), garantindo o piso de disponibilidade. maxSurge é a potência — quantos pods a mais que o desejado podem existir temporariamente (padrão 25%), os T-cars que aceleram a rodada de trocas. Com N réplicas e os padrões, durante o rollout você tem no mínimo 75% disponíveis e no máximo 125% rodando. Não é permitido maxUnavailable e maxSurge ambos em zero (senão a fila de troca trava).
Recreate garante consistência total, com downtime: derruba tudo da v1 antes de subir a v2. Garante que as duas versões nunca coexistem — necessário quando v1 e v2 são incompatíveis (migração de banco que quebra a versão antiga). O trade-off do tuning: maxUnavailable alto acelera mas comprime a capacidade (a latência explode); maxSurge alto acelera mas dobra pods → dobra conexões → pode saturar o banco. Duas receitas: disponibilidade-primeiro (maxUnavailable: 0, maxSurge generoso) ou tempo-mínimo sob orçamento (satura o pico e a degradação mínima aceitável).
Ferramentas de garantia: minReadySeconds (o pod precisa ficar Ready por X segundos antes de contar como disponível); progressDeadlineSeconds (padrão 600s — se o rollout empaca, marca ProgressDeadlineExceeded, mas o Deployment NÃO faz rollback sozinho: quem decide voltar é você); revisionHistoryLimit (padrão 10 — quantas revisões guardar pra rollback). O rollout só dispara com mudança em .spec.template — só escalar réplicas não conta. Estratégias mais sofisticadas (Blue/Green, Canary) não são nativas do Deployment; controladores como Argo Rollouts e Flagger as adicionam com gates de promoção automáticos.
Os conceitos-chave desta etapa
RollingUpdate (padrão)
Substitui pods aos poucos mantendo o serviço no ar: cria um novo ReplicaSet e reduz o antigo proporcionalmente. Zero downtime — troca o setup carro a carro enquanto os outros marcam tempo.
maxUnavailable (o freio)
Quantos pods podem ficar indisponíveis durante a troca. Garante o piso de disponibilidade. Padrão 25%. Quantos carros você aceita no box sem esvaziar a pista.
maxSurge (a potência)
Quantos pods a mais que o desejado podem existir temporariamente. Acelera criando extras (os T-cars). Padrão 25%. Proibido zerar os dois juntos (a fila de troca trava).
Recreate
Derruba TUDO da v1 antes de subir a v2. Causa downtime, mas garante que as duas versões nunca coexistem — necessário quando v1 e v2 são incompatíveis (migração de banco). A bandeira vermelha.
Garantias do rollout
minReadySeconds (só conta como pronto após X s estável), progressDeadlineSeconds (detecta o travamento, mas NÃO faz rollback sozinho), revisionHistoryLimit (revisões guardadas). Rollout só dispara com mudança em .spec.template.
YAML e comandos na prática
apiVersion: apps/v1kind: Deploymentmetadata:name: myappannotations:kubernetes.io/change-cause: "Rollout v2.0.0" # aparece no rollout historyspec:replicas: 10minReadySeconds: 30 # pod só "vale" após 30s estável ReadyprogressDeadlineSeconds: 600 # se travar por 10min, marca ProgressDeadlineExceededrevisionHistoryLimit: 10 # guarda 10 revisões pra rollbackstrategy:type: RollingUpdaterollingUpdate:maxUnavailable: 0 # FREIO: nunca reduz a capacidade mínimamaxSurge: "50%" # POTÊNCIA: cria até 5 pods extras pra acelerarselector: { matchLabels: { app: myapp } }template:metadata: { labels: { app: myapp } }spec:containers:- name: appimage: myregistry/myapp:2.0.0readinessProbe: # sem readiness, o rolling "mente" que está prontohttpGet: { path: /healthz, port: 8080 }
Comandos essenciais desta etapa
Mão na massa
Faça um rollout e um rollback do zero e observe os ReplicaSets:
- Crie e espere ficar pronto: kubectl create deployment web --image=nginx:1.25 --replicas=3 e kubectl rollout status deployment/web.
- Atualize a imagem (dispara o rollout porque muda o template): kubectl set image deployment/web nginx=nginx:1.27 e acompanhe com rollout status.
- Veja os DOIS ReplicaSets (o antigo zerado, o novo com 3): kubectl get rs -l app=web.
- Reverta e confirme que voltou pra 1.25: kubectl rollout undo deployment/web e kubectl describe deployment web | grep Image.
- Limpe: kubectl delete deployment web.
Dica: Sempre preencha kubernetes.io/change-cause (ou use --record). Sem ela, o rollout history fica cego e você não sabe pra qual revisão voltar quando o incidente chegar às 3h da manhã. É o seu log de setup: sem anotar "o que mudei e por quê", quando o carro ficar ruim você vai adivinhar no escuro no meio da corrida.
Anota aí: RollingUpdate = zero-downtime com freio (maxUnavailable) e potência (maxSurge); o undo é instantâneo porque o ReplicaSet antigo continua guardado, só zerado. Igual ao setup da classificação que fica salvo — voltar pra ele é recarregar, não remontar.
QUE PIT STOP, SENHORAS E SENHORES! Setor 3 no verde, o carro trocou de setup em movimento e ainda temos o botão de voltar atrás na mão. Mas agora a gente sobe de nível na organização da equipe: como montar TODOS os carros a partir da mesma fôrma, mudando só a folha de setup? O Helm assume o comando. Etapa 04!
Etapa 04 os bastidores da fábrica
Gerenciador de pacotes com Helm Charts
Longe das câmeras, é na fábrica que o campeonato é construído. Mesmo chassi o ano inteiro, folha de setup diferente a cada circuito. Chega de copiar e colar YAML e rezar pra não esquecer nada. O Helm é o apt do Kubernetes e vai organizar sua garagem. Vem pra oficina!
Você tem dev, staging e prod. Cada ambiente precisa dos mesmos ~15 manifestos YAML, mudando só réplicas, tag de imagem e recursos. Você copia e cola tudo três vezes. Aí um dia altera um label no Deployment de prod, esquece de replicar em staging, e passa a semana caçando por que "no staging funciona e em prod não". Isso viola DRY e é uma fábrica de erros.
Como funciona
O Helm é o apt/yum do Kubernetes. Pensa na fôrma de montagem do carro: o chassi, a filosofia de suspensão, a arquitetura aero são a mesma base o ano inteiro. O que muda de circuito pra circuito é a folha de setup — nível de asa em Monza (baixa) vs. Mônaco (alta), pressão de pneu, mapa de motor. Você não redesenha o carro em cada GP; troca só os ingredientes que variam (os values) na mesma fôrma (o template).
Anatomia de um Chart: Chart.yaml são os metadados — version é a versão do chart e appVersion é a versão da aplicação empacotada (ambos SemVer; separá-los ajuda a governança). values.yaml são os valores padrão configuráveis (réplicas, imagem, porta, recursos) — tudo que varia por ambiente mora aqui. templates/ são os manifestos como templates Go + funções Sprig, onde {{ .Values.replicaCount }} insere o valor e helpers em _helpers.tpl evitam repetir lógica.
A sacada teórica: um template Helm é uma função pura T(valores) → YAML — mesmos inputs, mesma saída, previsível e testável (helm template renderiza sem aplicar). É o simulador: você joga a folha de setup e ele te devolve exatamente como o carro vai ficar, sem tocar na pista. Isso encarna dois pilares de Infraestrutura como Código: idempotência (aplicar N vezes tem o mesmo efeito que aplicar uma) e imutabilidade (não muta o objeto existente, cria uma nova versão e mantém o histórico — o que torna o rollback confiável).
Ciclo de vida: helm install (cria e registra a release num Secret) → helm upgrade (calcula o diff e aplica só as mudanças) → helm rollback (volta pra uma revisão salva). O Helm 3 não tem servidor central (Tiller): o cliente usa direto as credenciais do kubectl, então o RBAC do usuário limita o que o Helm pode fazer. Em escala, charts seguem SemVer, são distribuídos por repositórios (Artifact Hub, privados), podem ser assinados digitalmente (GPG + .prov) pra proteger a supply chain, e devem embutir RBAC de menor privilégio. Combinado com GitOps (ArgoCD/Flux), cada release vira um commit auditável.
Os conceitos-chave desta etapa
Chart.yaml
Metadados. version é a versão do chart (a fôrma); appVersion é a versão da aplicação empacotada. Ambos SemVer — separá-los evolui o chart sem trocar o app.
values.yaml
Os valores padrão configuráveis (réplicas, imagem, porta, recursos). Tudo que varia por ambiente mora aqui. A folha de setup por circuito.
templates/
Os manifestos como templates Go + funções Sprig. {{ .Values.replicaCount }} insere o valor; helpers em _helpers.tpl evitam repetição. A fôrma que recebe os números da folha de setup.
Função pura
Um template é T(valores) → YAML: mesmos inputs, mesma saída. helm template renderiza sem aplicar (o simulador). Sustenta idempotência (aplicar N vezes = aplicar 1) e imutabilidade (nova versão, histórico preservado).
Helm 3 sem Tiller
Não há servidor central: o cliente usa direto as credenciais do kubectl, então o RBAC do usuário limita o que o Helm pode fazer. O crachá de quem opera define o que a fôrma monta.
YAML e comandos na prática
# Chart.yaml — a identidade do pacoteapiVersion: v2name: myappversion: 0.1.0 # versão do CHART (a fôrma)appVersion: "1.0.0" # versão da APLICAÇÃO empacotada (o bolo)type: application
Comandos essenciais desta etapa
Mão na massa
Veja a "função pura" com os próprios olhos, sem tocar em cluster nenhum:
- Crie um chart de exemplo: helm create demo.
- Renderize com o valor padrão: helm template demo | grep -i replicas (repare em replicas: 1).
- Renderize mudando SÓ o input: helm template demo --set replicaCount=4 | grep -i replicas — a saída muda de forma previsível.
- Valide o chart: helm lint demo. Você testou tudo antes de aplicar em cluster nenhum — rodou no simulador antes de mandar o carro pra pista.
Dica: Rode helm diff upgrade (plugin helm-diff) antes de aplicar em produção. Ver exatamente o que vai mudar no cluster evita a surpresa de aplicar template "às cegas" — o medo número um de quem começa com Helm. É comparar a folha de setup nova com a que está no carro antes de mexer no macaco.
Anota aí: Chart = fôrma (templates) + ingredientes (values); como o template é função pura, helm template te deixa conferir o resultado antes de mexer no cluster. Mesma fôrma de carro, folha de setup por circuito, e o simulador te mostra o resultado antes da pista.
SETOR 4 FECHADO com a garagem organizada! A fábrica está redonda, cada carro sai da mesma fôrma com a folha de setup certa. Agora a gente volta pra ADRENALINA do domingo: como lançar a v2 com um botão de desfazer INSTANTÂNEO? O T-car está aquecido nos bastidores. Etapa 05, Blue/Green — segura essa emoção!
Etapa 05 o T-car quente na garagem
Deploy Blue/Green
Imagina ter um carro reserva, mesma equipe, setup novo, motor na temperatura, PRONTO pra assumir num estalar de dedos. Era assim na era dos carros reserva, antes de 2008, quando um piloto trocava pro spare no grid e largava mesmo assim. É esse o espírito do Blue/Green: virar a chave e voltar em segundos. Rádio na garagem!
Você lançou a v2, ela passou em todos os testes, mas em produção — com tráfego e dados reais — apareceu um bug crítico 3 minutos depois. Com RollingUpdate, reverter significa fazer outro rollout completo, que leva minutos enquanto os clientes sofrem. Você queria um "botão de desfazer" instantâneo.
Como funciona
É o T-car (carro reserva) já montado e aquecido nos bastidores. O carro Azul é o que está em pista servindo o público (tráfego); o Verde é o reserva — mesma equipe, setup novo, motor já na temperatura, pronto pra assumir. Quando você valida o Verde, troca de carro na hora: o público passa a ver o Verde imediatamente. Deu ruim? Você volta pro Azul em segundos — ele nunca foi desmontado, ficou quente à espera.
A ideia central: mantenha dois ambientes de produção idênticos. O Blue é a versão atual servindo 100% do tráfego; o Green é a nova versão, pronta e "quente", mas sem tráfego. Quando o Green é validado, você vira a chave e ele passa a servir tudo. No Kubernetes, os dois conjuntos de pods têm labels diferentes (env: blue, env: green) e um único Service aponta pra um deles via selector. A promoção é só um patch no selector do Service — o kube-proxy reconfigura o roteamento em instantes. O rollback é o patch inverso. É uma transição atômica entre dois estados (B → G) com rollback determinístico.
As armadilhas: a consistência de dados/esquema é o calcanhar de Aquiles. Se a Green muda o banco de forma incompatível, reverter pra Blue quebra. A solução é compatibilidade dupla temporária (Martin Fowler): primeiro migrar o banco pra suportar as duas versões, validar, e só então trocar o código. Aqui a analogia do T-car para um pouco: se o carro novo "reescreve o traçado da pista" (o schema do banco), voltar pro carro antigo não adianta — a pista já mudou embaixo dele. Por isso a migração retrocompatível vem antes.
Custo: dois ambientes completos = ~100% de overhead durante a transição (a Etsy chegou a 200% de capacidade). Boa prática: escalar o ambiente inativo pra zero depois de estabilizar. Tempestade de conexões: trocar 100% de uma vez joga toda a carga na Green de repente — caches frios podem gerar um pico; mitigue aquecendo a Green com tráfego sombra antes. Antipadrão clássico: não tratar a Green como produção o tempo todo — se você acumula mudanças esperando o "momento certo", o salto Blue→Green vira gigante e arriscado. Ferramentas: Argo Rollouts suporta blueGreen nativo; Flagger integra com service mesh (também chamado Red/Black na Netflix/Spinnaker).
Os conceitos-chave desta etapa
Dois ambientes idênticos
Blue = versão atual servindo 100% do tráfego; Green = nova versão pronta e "quente", sem tráfego. Valida o Green, vira a chave, ele serve tudo. Deu problema? Vira de volta em segundos.
A virada de chave
Os dois conjuntos têm labels diferentes (env: blue/green); um Service aponta pra um via selector. A promoção é um patch no selector — o kube-proxy reconfigura em instantes. Rollback é o patch inverso.
Consistência de dados (o calcanhar de Aquiles)
Se a Green muda o banco de forma incompatível, reverter pra Blue quebra. Solução: compatibilidade dupla temporária — migrar o banco pra suportar as DUAS versões ANTES de trocar o código.
Custo e tempestade de conexões
Dois ambientes = ~100% de overhead (escale o inativo pra zero após estabilizar). Trocar 100% de uma vez com caches frios gera pico — aqueça a Green com tráfego sombra antes.
Antipadrão
Não tratar a Green como produção o tempo todo: acumular mudanças esperando o "momento certo" torna o salto gigante e arriscado. Blue/Green de verdade = deploy contínuo no ambiente passivo, só sem tráfego.
YAML e comandos na prática
# Service inicialmente apontando pro BLUEapiVersion: v1kind: Servicemetadata: { name: myapp-service }spec:selector: { app: myapp, env: blue } # <- a "chave": troca pra green na promoçãoports: [ { port: 80, targetPort: 8080 } ]
Comandos essenciais desta etapa
Mão na massa
Monte um Blue/Green de brinquedo e vire a chave observando os endpoints:
- Crie dois "ambientes": kubectl create deployment blue --image=nginx:1.25 e green --image=nginx:1.27.
- Marque os labels no TEMPLATE do pod (o Service seleciona pods): patch em blue com env=blue e green com env=green (app=bg).
- Exponha um Service apontando pro BLUE: kubectl expose deployment blue --name=bg-svc --port=80 --selector=app=bg,env=blue.
- Veja qual pod recebe tráfego: kubectl get endpoints bg-svc (o IP do pod blue).
- Vire a chave pro GREEN: kubectl patch service bg-svc -p ... env green — e olhe os endpoints mudarem, sem reiniciar nada.
- Limpe: kubectl delete deploy blue green; kubectl delete svc bg-svc.
Dica: Antes de virar a chave, garanta que o banco aceita as duas versões. A pergunta é sempre: "se eu precisar reverter pra Blue daqui a 5 minutos, os dados que a Green escreveu vão quebrar a Blue?". Se sim, você não está pronto pro cutover. Só troca pro T-car se você tem certeza de que consegue voltar pro titular sem que a pista tenha mudado embaixo dele.
Anota aí: Blue/Green é "tudo ou nada": a promoção e o rollback são um patch no selector do Service — instantâneos, desde que o banco seja retrocompatível. É trocar de carro na hora, com o antigo quente na garagem — só funciona se a pista (o schema) não mudou embaixo.
SETOR 5 NO VERDE, virada de chave perfeita! Você tem agora o botão de desfazer instantâneo no bolso. Mas o Blue/Green joga 100% do público na v2 de uma vez — e se o bug só aparecer com 1 usuário em 20? Aí a gente não arrisca a dupla toda: manda UM carro testar a peça nova em pista. O canário está no cockpit. Etapa 06!
Etapa 06 um carro testa a peça nova
Deploy Canário
A Ferrari não coloca a asa nova nos dois carros de uma vez. Ela bota no do Leclerc, lê a telemetria por algumas voltas, e só então libera pro Hamilton. Um sente o gás antes de todo mundo — o velho canário na mina. É a estratégia mais cirúrgica do grid, e é a próxima curva. Telemetria ligada!
Blue/Green vira 100% do tráfego de uma vez. Mas e se a v2 tiver um bug que só aparece sob carga real, com 1 usuário em cada 20? Virar tudo de uma vez expõe todo mundo ao mesmo tempo. Você queria testar a v2 com uma fatia pequena e real de usuários, medir, e só então expandir.
Como funciona
É mandar um único carro testar o upgrade em condição de corrida antes de aplicar na dupla. A Ferrari não coloca a asa nova nos dois carros de uma vez — bota no do Leclerc por algumas voltas, lê a telemetria (delta de tempo, temperatura de pneu, degradação), e só então libera pro Hamilton também. Se o delta piorar, tira a peça daquele carro e a base inteira nunca foi contaminada. O nome vem do "canário na mina": uma fração pequena sente o gás tóxico antes de todo mundo.
A ideia central: em vez de expor todo mundo à v2, você manda uma fração pequena de usuários reais pra ela, monitora métricas, e só aumenta se estiver tudo bem. Blue/Green é "tudo ou nada" (100% de uma vez); Canary é progressivo (1% → 5% → 25% → 50% → 100%), validando a cada passo com carga real. As duas versões rodam juntas e você migra o tráfego aos poucos.
Roteamento fino de tráfego: feito por um service mesh (Istio, Linkerd) ou ingress com peso — você define percentuais exatos (ex.: 90% v1 / 10% v2) independentes do número de réplicas, via proxies sidecar. No Istio, um VirtualService divide o tráfego entre subsets (v1/v2) e você ajusta os pesos dinamicamente. O que decide promover ou reverter são métricas em tempo real: taxa de erro (5xx), latência (p95, p99), uso de recursos, métricas de negócio. Isso fecha um laço de realimentação automatizável: se o erro do canário < threshold, aumenta o peso; se viola, rollback imediato pra v1.
O vocabulário que vale conhecer (aprofundamento teórico): o canário é um teste A/B contínuo, um experimento estatístico entre v1 (controle) e v2 — testado com significância (α = 0,05) via teste z, qui-quadrado ou Mann-Whitney (o Kayenta da Netflix usa Mann-Whitney). Testes sequenciais (SPRT de Wald) param cedo quando há evidência suficiente. Teoria de filas: mesmo 10% de tráfego pode ter latência ruim se aqueles 10% quase saturam os pods v2 (W = 1/(μ−λ) explode perto da saturação). Multi-armed bandits (Thompson Sampling) alocam tráfego adaptativamente. Casos reais: a Shopify manda ~5% por ~10min com análise automática; a Netflix automatizou tudo com Kayenta; o Nubank usa feature flags + canary pra centenas de deploys/dia.
Os conceitos-chave desta etapa
Rollout progressivo
Em vez de expor todo mundo, manda uma fração pequena de usuários reais pra v2, monitora e só aumenta se estiver tudo bem (1% → 5% → 25% → 50% → 100%). As duas versões rodam juntas.
Canary vs Blue/Green
Blue/Green é "tudo ou nada" (100% de uma vez); Canary é progressivo, validando a cada passo com carga real. Liberar a peça de carro em carro, medindo a cada stint.
Roteamento fino por peso
Feito por service mesh (Istio, Linkerd) ou ingress com peso: percentuais exatos (ex.: 90/10) independentes do número de réplicas, via proxies sidecar. O engenheiro dosa a fração exata.
Decisão orientada a dados
O que decide promover ou reverter são métricas em tempo real: taxa de erro (5xx), latência (p95/p99), recursos, negócio. Fecha um laço: erro < threshold sobe o peso; violou, rollback imediato.
SLO gate
Amarre o canário aos SLOs, não a métricas arbitrárias. Ferramentas como o Kayenta (Netflix) e o Argo Rollouts aplicam a análise estatística por você. Defina o delta-limite ANTES de mandar o carro pra pista.
YAML e comandos na prática
apiVersion: networking.istio.io/v1beta1kind: VirtualServicemetadata: { name: myapp }spec:hosts: [ myapp ]http:- route:- destination: { host: myapp, subset: v1 }weight: 90 # estável recebe 90%- destination: { host: myapp, subset: v2 }weight: 10 # canário recebe 10% — sobe gradualmente
Comandos essenciais desta etapa
Mão na massa
Sem Istio instalado, você consegue aproximar o efeito canário pela razão de réplicas atrás do mesmo Service (o balanceamento fica proporcional ao número de pods):
- Crie v1 com 9 réplicas e v2 com 1 (~10% do tráfego cai na v2): kubectl create deployment app-v1 --image=nginx:1.25 --replicas=9 e app-v2 --image=nginx:1.27 --replicas=1.
- Dê o mesmo label app=canary aos dois, inclusive no template do pod (via patch).
- Exponha um Service que cobre AMBAS as versões: kubectl expose deployment app-v1 --name=canary-svc --port=80 --selector=app=canary.
- Confirme 10 endpoints (9 v1 + 1 v2): kubectl get endpoints canary-svc -o wide.
- "Promova" ajustando réplicas: kubectl scale deployment app-v2 --replicas=3 e app-v1 --replicas=1.
- Limpe: kubectl delete deploy app-v1 app-v2; kubectl delete svc canary-svc.
Dica: Amarre o canário aos seus SLOs, não a métricas arbitrárias. Se o SLO de erro é 0,1%, o canário deve frear automaticamente quando a v2 violar isso. Assim o monitoramento de produção e o de deploy viram a mesma coisa — e promover deixa de ser "achismo". Defina o delta-limite antes: "se passar de +0,3s por volta ou 110°C de pneu, aborta" — número claro, decisão automática.
Anota aí: Canary é rollout progressivo com gate de métricas a cada passo; o peso ideal vem de service mesh (independe de réplicas), e a decisão de promover deve nascer do seu SLO. Um carro testa a peça nova por umas voltas, a telemetria decide, e só então a equipe toda recebe.
SETOR 6 CONCLUÍDO, canário aprovado! Você já sabe lançar sem medo, do jeito mais cirúrgico do grid. Chegamos à metade final da prova, e agora a pergunta muda de escala: e quando não tem BOX suficiente pros carros que chegam? Quem monta a garagem sob demanda? A logística assume o controle. Etapa 07, acelera na reta longa!
Etapa 07 potência de infraestrutura sob demanda
Escalabilidade de nós com Karpenter
Numa promoção relâmpago, o HPA pede 20 pods e... não tem box pra ninguém. Os carros ficam na fila da pit lane. É como um fim de semana back-to-back onde a equipe precisa montar a estrutura na hora, no lugar certo, com o material mais barato disponível. O Karpenter é o gerente de logística mais rápido do paddock. Pé no fundo!
Até agora você escalou pods. Mas numa promoção relâmpago o HPA pede 20 novos pods e... não há nó onde colocá-los. Os pods ficam Pending e o serviço não aguenta a demanda. O Cluster Autoscaler tradicional resolveria, mas é lento e depende de grupos de nós fixos (ASGs) pré-configurados por tipo/zona — uma dor de manter, e ele costuma acionar um nó gigante pra um pod pequeno, desperdiçando dinheiro.
Como funciona
É a diferença entre dois jeitos de montar box na pit lane. O Cluster Autoscaler é ter contratos fixos de box de um tamanho só, reservados com meses de antecedência — chegou um carro pequeno, você é obrigado a abrir um box gigante, pagando pelo espaço vazio. O Karpenter é o gerente de logística que olha exatamente quantos carros e de que tamanho chegaram e monta na hora o box do tamanho certo, escolhendo até o material mais barato disponível, e desmonta assim que esvazia.
Karpenter = autoscaling de nós just-in-time. Criado pela AWS (open source, hoje multicloud), provisiona nós sob medida pros pods pendentes, sem grupos fixos: mais rápido (segundos a <1min), escolhe a instância ótima (inclusive Spot) e remove nós ociosos sozinho. Empresas relatam até 30% de economia. O fluxo: pods ficam unschedulable (o scheduler não achou nó) → o Karpenter observa → calcula qual instância acomoda melhor esses pods → provisiona → os pods são agendados. No sentido inverso, ele identifica nós subutilizados e consolida: move os pods pra outros nós e remove o ocioso, ou troca um On-Demand caro por um Spot mais barato.
Configuração por NodePools (antes chamados Provisioners): definem as regras dos nós que o Karpenter pode lançar — tipos de instância, zonas, Spot vs On-Demand, limites. Recomendação: restrinja o mínimo possível, pra dar liberdade de otimização ao Karpenter. Por que ele é esperto: o problema é bin packing multidimensional (NP-difícil); ele usa heurística gulosa (filtra tipos que não servem, ordena por custo/adequação, faz algo próximo de First-Fit Decreasing). Diferença-chave: o Cluster Autoscaler preserva homogeneidade dentro do grupo (pod pequeno pode acionar nó grande); o Karpenter faz right-sizing — nó pequeno pra pod pequeno.
Spot e custo: instâncias Spot custam ~70-90% menos, mas podem ser retiradas com aviso curto (2min na AWS). São o material emprestado que o fornecedor pode pedir de volta a qualquer hora — barato, mas você precisa de plano B. O Karpenter trata isso com interrupção proativa: detecta o aviso, marca o nó como terminating, aplica taint e drena os pods pra outro lugar antes do corte. E respeita PodDisruptionBudgets — nunca consolida/termina nós se isso violar o budget. Boas práticas: PDB pra controlar quantos pods caem juntos, Topology Spread pra distribuir réplicas, e NodePools separados pra cargas críticas (On-Demand) vs. tolerantes (Spot).
Os conceitos-chave desta etapa
Autoscaling de nós just-in-time
Provisiona nós sob medida pros pods pendentes, SEM grupos fixos: rápido (segundos a <1min), escolhe a instância ótima (inclusive Spot) e remove nós ociosos sozinho. Até ~30% de economia.
O fluxo
Pods ficam unschedulable → o Karpenter observa → calcula a instância que acomoda melhor → provisiona → os pods são agendados. No inverso, consolida nós subutilizados: move pods e remove o ocioso.
NodePools
Definem as regras dos nós que o Karpenter pode lançar (tipos, zonas, Spot vs On-Demand, limites). Recomendação: restrinja o mínimo possível, pra dar liberdade de otimização.
Right-sizing vs Cluster Autoscaler
O CA preserva homogeneidade do grupo (pod pequeno pode acionar nó grande); o Karpenter faz right-sizing — nó pequeno pra pod pequeno. É bin packing NP-difícil resolvido por heurística gulosa (First-Fit Decreasing).
Spot e PodDisruptionBudget
Spot custa ~70-90% menos mas pode ser retirado com aviso curto (2min). O Karpenter faz interrupção proativa (drena antes do corte) e respeita o PDB — nunca tira réplicas demais juntas.
YAML e comandos na prática
apiVersion: karpenter.sh/v1kind: NodePoolmetadata: { name: default }spec:template:spec:requirements:- key: karpenter.sh/capacity-typeoperator: Invalues: ["spot", "on-demand"] # pode usar spot; cai pra on-demand se faltar- key: kubernetes.io/archoperator: Invalues: ["amd64"] # restringe arquiteturanodeClassRef: { name: default } # aponta pra config de infra (AMI, subnets...)limits: { cpu: "1000", memory: 1000Gi } # teto de custo: nunca passa distodisruption:consolidationPolicy: WhenEmptyOrUnderutilized # consolida nós vazios OU subutilizadosconsolidateAfter: 30s # histerese: espera 30s antes de agir
Comandos essenciais desta etapa
Mão na massa
Karpenter roda em nuvem, mas você pode ver o gatilho que ele observa (o pod Pending por falta de nó) em qualquer cluster:
- Peça mais CPU do que qualquer nó tem: kubectl create deployment faminto --image=nginx e kubectl set resources deployment faminto --requests=cpu=100.
- O pod fica Pending (não há nó que comporte a request): kubectl get pods -l app=faminto.
- Veja o motivo — o sinal que o Karpenter escuta: kubectl describe pod -l app=faminto | grep -A3 Events → "Insufficient cpu" (FailedScheduling).
- Limpe: kubectl delete deployment faminto.
Dica: Rode cargas stateless e tolerantes a interrupção em Spot, e reserve On-Demand só pro que é crítico/stateful — em NodePools separados. Combine com PodDisruptionBudget e Topology Spread pra que uma interrupção de Spot nunca derrube réplicas demais de um serviço ao mesmo tempo. Material emprestado (Spot) só nos componentes que você aguenta perder; no carro do líder, só material próprio garantido.
Anota aí: Karpenter provisiona o nó certo just-in-time reagindo a pods Pending, faz right-sizing e economiza com Spot — sempre respeitando o PodDisruptionBudget. É o gerente de logística montando box na medida do carro, com material barato onde dá, sem nunca esvaziar a equipe de uma vez.
SETOR 7 NO VERDE, que ultrapassagem na reta! Você resolveu a escala dos BOXES. Agora falta o outro lado da moeda: dimensionar a EQUIPE DE PIT pela fila de carros chegando — e recolher todo mundo a zero na calmaria da madrugada. Escala por evento, não por esforço. O KEDA entra em cena. Etapa 08!
Etapa 08 escala orientada a evento
Escalabilidade de aplicações com KEDA
Sem carro entrando no box, zero mecânico parado no macaco recebendo à toa. Chegou uma fila de 40 pedidos? Abre 4 estações na hora. Esvaziou? Recolhe todo mundo. O que dispara não é o suor de um mecânico — é quantos carros estão na fila. Escala orientada a evento. Vamos ao KEDA!
Você tem workers que consomem pedidos de uma fila. O HPA padrão só sabe escalar por CPU/memória — mas o seu gargalo não é CPU, é o tamanho da fila de pedidos. Pior: de madrugada a fila fica vazia, e mesmo assim você paga por 3 pods parados sem nada pra fazer. Você queria escalar pelo tamanho da fila e cair até zero quando não há trabalho.
Como funciona
É a equipe de pit stop dimensionada pela fila de carros chegando. Sem carro entrando no box? Zero mecânicos parados no macaco (a equipe não paga gente à toa). Chegou um carro? Um conjunto de mecânicos assume na hora. Fila de 40 pedidos e cada estação atende de 10 em 10? Abre 4 estações. Esvaziou e ficou vazia um tempo? Recolhe todos. O que dispara não é o esforço de um mecânico (CPU) — é quantos carros estão na fila (o evento).
KEDA = autoscaling orientado a eventos. Escala com base em eventos externos (filas RabbitMQ/SQS/Kafka, métricas Prometheus, cron, etc.) e permite scale-to-zero — nenhum pod consumindo recursos quando a fila está vazia. Ele complementa o HPA, não substitui. Como funciona: você cria um ScaledObject (o CRD central) que aponta pra um Deployment alvo e define um trigger (ex.: fila RabbitMQ, 1 pod a cada 10 mensagens). O KEDA cria um HPA por trás. Fila vazia → mantém o Deployment em 0 pods; chega mensagem → ativa 1 pod imediatamente; fila cresce → o HPA calcula os pods (40 mensagens = 4 pods); fila esvazia pelo cooldownPeriod → volta a zero. Só o KEDA tem autoridade pra ir a zero; o HPA sozinho vai de 1 a N (minReplicas ≥ 1).
A teoria (teoria de filas): modele eventos chegando a taxa λ e cada pod processando a μ; com c pods, a capacidade é c·μ. Condição de estabilidade: λ < c·μ (senão a fila cresce sem limite — nem autoscaling salva). A Lei de Little (L = λ·W) relaciona backlog médio L, taxa λ e tempo de resposta W: se o backlog cresce, ou as chegadas estão rápidas demais, ou faltam pods. O autoscaler ideal controla L pra manter W dentro do SLO.
O desafio: oscilação/jitter (subir e descer pods repetidamente), causado por atraso de realimentação e thresholds estáticos. Mitigações do KEDA/HPA: histerese (~10% — só escala se passar de 110% ou cair abaixo de 90% do alvo); janelas de estabilização e cooldownPeriod; banda morta (limiares distintos de subida e descida — escala up em 100 msgs mas só down abaixo de 20). O KEDA é agnóstico de nuvem (AKS, EKS, GKE, on-prem) e extensível via scalers externos gRPC. Caso típico: e-commerce escalando workers de pedidos por fila durante promoções relâmpago, voltando a zero na madrugada.
Os conceitos-chave desta etapa
Autoscaling orientado a eventos
Escala por eventos externos (filas RabbitMQ/SQS/Kafka, Prometheus, cron) e permite scale-to-zero — nenhum pod quando a fila está vazia. Complementa o HPA, não substitui.
ScaledObject e trigger
O CRD central aponta pra um Deployment e define um trigger (ex.: 1 pod a cada 10 mensagens). O KEDA cria um HPA por trás. 40 mensagens = 4 pods; fila vazia pelo cooldownPeriod → volta a zero.
Só o KEDA vai a zero
O HPA sozinho vai de 1 a N (minReplicas ≥ 1); só o KEDA tem autoridade pra ir a zero. O HPA sempre deixa um mecânico de plantão; só o KEDA fecha o box quando não vem carro.
Condição de estabilidade
Modele chegadas a λ e processamento a μ com c pods: capacidade c·μ. Estável só se λ < c·μ — senão a fila cresce sem limite e nenhum autoscaling salva. Lei de Little: L = λ·W.
Oscilação (jitter)
Subir e descer pods repetidamente. Mitigações: histerese (~10%: sobe acima de 110%, desce abaixo de 90%), janelas de estabilização/cooldownPeriod e banda morta (limiares distintos de subida e descida).
YAML e comandos na prática
apiVersion: keda.sh/v1alpha1kind: ScaledObjectmetadata: { name: worker-scaler }spec:scaleTargetRef: { name: meu-deployment } # o Deployment que será escaladominReplicaCount: 0 # scale-to-zero: zero pod quando não há trabalhomaxReplicaCount: 5 # teto de segurançacooldownPeriod: 60 # espera 60s de fila vazia antes de zerarpollingInterval: 15 # checa a fila a cada 15striggers:- type: rabbitmqmetadata:queueName: minha-filaprotocol: amqpmode: QueueLengthvalue: "10" # alvo: 1 pod a cada 10 mensagens na fila
Comandos essenciais desta etapa
Mão na massa
Você pode testar o KEDA sem fila externa, usando o trigger cron (que não depende de nenhum sistema) pra ver scale-to-zero e reativação:
- Pré-requisito: KEDA instalado (helm install keda kedacore/keda -n keda --create-namespace).
- Crie um Deployment qualquer: kubectl create deployment cron-demo --image=nginx.
- Aplique um ScaledObject com trigger cron: minReplicaCount 0, maxReplicaCount 3, sobe no minuto 0 e desce no minuto 30 de cada hora (timezone America/Sao_Paulo).
- Observe o Deployment: fora da janela fica em 0; na janela vai pra 3 — kubectl get deployment cron-demo -w.
- Limpe: kubectl delete scaledobject cron-demo; kubectl delete deployment cron-demo.
Dica: Ajuste pollingInterval e cooldownPeriod pensando na dinâmica da carga. Cooldown curto demais mata pods bons no meio de um burst intermitente; longo demais desperdiça recursos. E lembre: se λ ≥ c·μ de forma sustentada, nenhum autoscaling resolve — o gargalo é capacidade ou código, não número de pods. Contratar mecânico não conserta um box que atende mais devagar do que os carros chegam.
Anota aí: KEDA escala por eventos (fila, cron, métrica) e é o único que leva a zero; mas se λ ≥ c·μ de forma sustentada, o problema é capacidade, não autoscaler. Equipe de pit dimensionada pela fila de carros, recolhida a zero na calmaria — mas nenhum reforço salva um box que atende mais devagar do que os carros chegam.
SETOR 8 CONCLUÍDO, escala sob domínio total! Boxes e equipe agora respiram junto com a demanda. Falta o último setor — e é o mais implacável. Aqui não se perde corrida na pista: se perde FORA dela, na inspeção técnica. É a chicane final da segurança. Concentração máxima, porque um crachá errado custa o campeonato. Etapa 09, a volta decisiva!
Etapa 09 a chicane final
Segurança no cluster
A FIA não perdoa. Já vimos carros vencerem na pista e serem desclassificados na inspeção por um detalhe de regulamento. No cluster é igual: um container furado com a chave-mestra no bolso entrega o reino inteiro. Zero Trust, crachá nominal, lacre que expira sozinho. É a última curva, e é aqui que os campeões provam que merecem o troféu. FOCO!
Seu cluster tem vários times e serviços. Um deles roda uma dependência com vulnerabilidade e é comprometido. Se aquele pod usava a ServiceAccount default com permissões amplas — ou pior, tinha uma chave AWS estática embutida no container — o invasor acabou de virar dono do reino: lê segredos de todos os namespaces, cria pods de mineração, acessa buckets. Um único container furado não pode custar o cluster inteiro.
Como funciona
É o controle de acesso do paddock da FIA. Cada membro da equipe recebe um crachá que abre só a zona dele — o mecânico de pneu não entra na sala de estratégia, o engenheiro de dados não mexe na PU (ServiceAccount + RBAC de menor privilégio). Ninguém anda com a chave-mestra do autódromo. Os crachás expiram no fim do fim de semana e as áreas se trancam sozinhas (TLS e tokens de curta duração). A filosofia é a mesma do regulamento: Zero Trust — ninguém passa por uma cancela sem credencial verificada, nem que seja o chefe de equipe. Segurança em Kubernetes se apoia em três pilares — identidade, autorização e criptografia.
Identidade — ServiceAccounts: cada aplicação deve ter a sua própria ServiceAccount, nunca a default. Ela dá ao pod um token pra falar com o API Server. Isolar identidades por workload é o que permite delimitar o estrago quando um pod é comprometido — cada função da equipe com o seu crachá nominal, e se um cai em mãos erradas, você sabe exatamente qual zona foi exposta e revoga só ele.
Autorização — RBAC: concede permissões via Roles (namespaced) ou ClusterRoles (cluster-wide), ligadas a sujeitos por RoleBindings. O modelo é um grafo: Subject → RoleBinding → Role → Permissão. Menor privilégio na prática: prefira Role namespaced a ClusterRole; conceda só os verbos necessários (get, list — nunca *) e só os recursos necessários. O RBAC do Kubernetes é estático e aditivo (só soma permissões, não há negações), o que o torna auditável. Pra políticas condicionais ("negar pod sem limits", "só imagem stable"), usa-se OPA/Gatekeeper com a linguagem Rego (Policy as Code).
Identidade federada com a nuvem: aplicações precisam acessar S3, buckets, Key Vault — sem embutir chaves estáticas no container. A solução é federar a ServiceAccount do K8s com uma identidade cloud via OIDC, recebendo tokens de curta duração: IRSA (IAM Roles for Service Accounts) no EKS, Workload Identity no GKE e no AKS. Os incidentes provam o valor: no ataque SCARLETEEL (2023), o dano foi limitado porque a Role do pod tinha escopo restrito; no caso da Tesla (2018), um dashboard sem senha + credenciais AWS amplas num pod levou a cryptojacking. Criptografia — cert-manager: automatiza o ciclo de vida do TLS via CRDs (Issuer/ClusterIssuer emite; Certificate define CN, SANs, duração e renewBefore) — gera o CSR, assina, preenche o Secret e renova sozinho. E a rotação de segredos usa período de convivência: nova e antiga válidas ao mesmo tempo até todos migrarem, depois invalida a antiga.
Os conceitos-chave desta etapa
Identidade — ServiceAccounts
Cada app com a SUA ServiceAccount, nunca a default. Ela dá ao pod um token pra falar com o API Server. Isolar identidades por workload delimita o estrago quando um pod é comprometido. Crachá nominal.
Autorização — RBAC
Permissões via Roles (namespaced) ou ClusterRoles, ligadas por RoleBindings (Subject → RoleBinding → Role → Permissão). Menor privilégio: prefira Role namespaced, só os verbos/recursos necessários, nunca curinga *.
RBAC aditivo e auditável
Estático e aditivo (só soma, não nega), o que o torna auditável: dá pra listar quem pode o quê deterministicamente. Pra políticas condicionais, use OPA/Gatekeeper com Rego (Policy as Code).
Identidade federada (IRSA / Workload Identity)
Acessar S3/buckets/Key Vault sem chaves estáticas: federe a SA com uma identidade cloud via OIDC, recebendo tokens de curta duração. IRSA no EKS, Workload Identity no GKE/AKS. O crachá temporário do fornecedor.
Criptografia — cert-manager
Automatiza o ciclo de vida do TLS via CRDs: Issuer/ClusterIssuer (quem emite) e Certificate (o que emitir, com renewBefore). Gera CSR, assina, preenche o Secret e renova sozinho. Os lacres da FIA que expiram e se renovam.
YAML e comandos na prática
apiVersion: v1kind: ServiceAccountmetadata:name: analytics-sanamespace: analyticsannotations:eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/S3Reader # IRSA:# federa esta SA com uma IAM Role# (token curto, zero chave estática)---apiVersion: rbac.authorization.k8s.io/v1kind: Role # namespaced (limita ao namespace analytics), NÃO ClusterRolemetadata: { name: analytics-read, namespace: analytics }rules:- apiGroups: [""]resources: ["pods"]verbs: ["get", "list"] # só leitura de pods, sem "*"- apiGroups: [""]resources: ["configmaps"]verbs: ["get"] # só get de configmaps---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBinding # amarra a Role ao sujeito (a SA)metadata: { name: bind-analytics-read, namespace: analytics }roleRef: { apiGroup: rbac.authorization.k8s.io, kind: Role, name: analytics-read }subjects:- kind: ServiceAccountname: analytics-sanamespace: analytics
Comandos essenciais desta etapa
Mão na massa
Prove o menor privilégio com kubectl auth can-i, sem precisar de nuvem:
- Crie namespace, SA e Role de só-leitura de pods: kubectl create namespace lab; kubectl create serviceaccount leitor -n lab; kubectl create role ler-pods --verb=get,list --resource=pods -n lab; kubectl create rolebinding bind-leitor --role=ler-pods --serviceaccount=lab:leitor -n lab.
- A SA PODE listar pods? kubectl auth can-i list pods -n lab --as=system:serviceaccount:lab:leitor → yes.
- A SA PODE deletar pods? (não concedemos "delete") → no.
- A SA PODE ler Secrets? (não concedemos esse recurso) → no.
- Limpe: kubectl delete namespace lab. Crachá que só abre uma porta: quem o rouba fica preso naquela sala.
Dica: Use kubectl auth can-i --as=... no CI pra validar que suas ServiceAccounts têm exatamente as permissões esperadas e nada além. Combine com IRSA/Workload Identity de escopo mínimo — assim, mesmo que um container seja comprometido, o invasor fica preso ao que aquela identidade específica pode fazer. Faça a vistoria de credenciais antes do fim de semana começar, não depois do vazamento.
Anota aí: Uma SA por app + Role namespaced de verbos específicos + tokens de curta duração (IRSA/Workload Identity): é isso que transforma "container comprometido" em incidente contido, não em cluster perdido. Crachá nominal, que abre só a zona certa e expira sozinho — o Zero Trust do paddock aplicado ao cluster.
BANDEIRA QUADRICULADA À VISTA! O carro passou na chicane final sem tocar em nada, a vistoria da FIA aprovou, e o troféu é SEU. Nove setores completados de ponta a ponta. Você não é mais passageiro nesse cluster — você é o piloto no comando do muro. Foi uma honra narrar essa corrida!