The Code Codex
Tema
← Guias

Kubernetes

Por que o Kubernetes existe, o modelo declarativo e a reconciliação, a arquitetura do cluster, o que acontece num kubectl apply, os objetos principais, rede, configuração, probes, recursos e escala, com um laboratório local.

31 jan 2025· 13 min· 17 seções

Neste guia · 17 seções

Por que Kubernetes

Rodar um container numa máquina é simples. Rodar centenas deles em dezenas de máquinas levanta perguntas que o Docker sozinho não responde:

  • Em qual máquina cada container deve rodar, considerando CPU e memória livres?
  • Quem reinicia um container que caiu, ou move os containers de uma máquina que morreu?
  • Como publicar uma versão nova sem derrubar a anterior, e voltar atrás se der errado?
  • Como os serviços se encontram, se os containers mudam de endereço o tempo todo?
  • Como escalar de 3 para 30 réplicas quando o tráfego sobe?

O Kubernetes (ou K8s) é um orquestrador: um sistema que responde a essas perguntas de forma automática, a partir de uma descrição do que você quer.

O modelo declarativo: estado desejado e reconciliação

Essa é a ideia mais importante do Kubernetes, e explica todo o resto. Você não dá comandos (“suba 3 containers”); você declara um estado (“quero 3 réplicas desta imagem”), e o Kubernetes trabalha continuamente para que a realidade fique igual a ele.

diferente

igual

Estado desejado

replicas: 3

Compara com

o estado atual

Observa o cluster

2 pods rodando

Age

cria 1 pod

Espera a

próxima mudança

diferente

igual

Estado desejado

replicas: 3

Compara com

o estado atual

Observa o cluster

2 pods rodando

Age

cria 1 pod

Espera a

próxima mudança

Esse ciclo se chama loop de reconciliação, e cada parte do Kubernetes é um controller rodando o seu. As consequências são o que torna o sistema resiliente:

  • Autocorreção: se um pod morre, o estado atual (2) fica diferente do desejado (3), e um novo pod é criado. Ninguém precisa “mandar” reiniciar.
  • Idempotência: aplicar o mesmo arquivo dez vezes dá o mesmo resultado que aplicar uma.
  • Infraestrutura como código: o estado desejado vive em arquivos YAML versionados no git, revisados em pull request como qualquer código.

Arquitetura do cluster

Node 2

Node 1

Control plane: o cérebro do cluster

kubectl / CI

kube-apiserver

etcd

kube-scheduler

controller-manager

kubelet

Pod

kube-proxy

kubelet

Pod

kube-proxy

Node 2

Node 1

Control plane: o cérebro do cluster

kubectl / CI

kube-apiserver

etcd

kube-scheduler

controller-manager

kubelet

Pod

kube-proxy

kubelet

Pod

kube-proxy

Control plane

Gerencia o cluster; em produção, roda em mais de uma máquina para não ser um ponto único de falha. Nos serviços gerenciados (EKS, GKE, AKS), é o provedor quem cuida dele.

  • kube-apiserver: a porta de entrada. Toda leitura e alteração do cluster passa por ele, que autentica, valida e grava o estado. Os outros componentes não conversam entre si: todos conversam com o apiserver.
  • etcd: armazenamento chave-valor consistente e altamente disponível com todo o estado do cluster. É a “memória” do cérebro, e o que precisa de backup.
  • kube-scheduler: decide em qual node cada pod novo vai rodar, conforme os recursos pedidos e as restrições.
  • controller-manager: roda os loops de reconciliação (Deployments, ReplicaSets, nodes, jobs…).
Nodes

As máquinas que executam as aplicações, comparáveis a VMs na nuvem. Cada uma expõe CPU, memória, rede e armazenamento.

  • kubelet: o agente em cada node. Recebe do apiserver a lista de pods daquele node e garante que os containers estão rodando como foi pedido.
  • kube-proxy: cuida da rede do node, encaminhando o tráfego dos Services para os pods certos.
  • Container runtime: quem de fato executa os containers (containerd, CRI-O). O Docker deixou de ser suportado diretamente como runtime na versão 1.24, mas imagens criadas com ele continuam funcionando.

O que acontece num kubectl apply

Juntando as peças: o caminho de um Deployment novo até um container rodando é uma cadeia de controllers, cada um reagindo a uma mudança no apiserver.

kubelet (node 2)SchedulerController de ReplicaSetController de Deploymentapiserver + etcdkubectlkubelet (node 2)SchedulerController de ReplicaSetController de Deploymentapiserver + etcdkubectlaplica Deployment (replicas: 3)Deployment novocria ReplicaSetReplicaSet novocria 3 Pods (sem node)Pods sem nodeatribui cada Pod a um nodePod atribuído a este nodebaixa a imagem e inicia o containerstatus: Running
kubelet (node 2)SchedulerController de ReplicaSetController de Deploymentapiserver + etcdkubectlkubelet (node 2)SchedulerController de ReplicaSetController de Deploymentapiserver + etcdkubectlaplica Deployment (replicas: 3)Deployment novocria ReplicaSetReplicaSet novocria 3 Pods (sem node)Pods sem nodeatribui cada Pod a um nodePod atribuído a este nodebaixa a imagem e inicia o containerstatus: Running

Ninguém orquestra a sequência: cada controller só observa o tipo de objeto que lhe interessa e reage. Por isso o sistema se recupera sozinho de falhas no meio do caminho: quando o componente volta, ele observa o estado e continua de onde parou.

Laboratório: um cluster local

Com Docker instalado, o kind (Kubernetes in Docker, veja as referências) cria um cluster de verdade dentro de containers:

Terminal window
brew install kind # ou o binário em kind.sigs.k8s.io
kind create cluster
# 3 réplicas do nginx e um Service na frente delas
kubectl create deployment web --image=nginx --replicas=3
kubectl expose deployment web --port=80
kubectl get pods -o wide
# acessar pelo navegador em http://localhost:8080
kubectl port-forward service/web 8080:80

Agora veja a reconciliação acontecendo. Em outro terminal, apague um pod e observe outro nascer no lugar:

Terminal window
kubectl get pods --watch
kubectl delete pod <nome-de-um-pod>
kubectl scale deployment web --replicas=5

Para limpar tudo: kind delete cluster.

Os objetos principais

ObjetoPara quê
PodA menor unidade: um ou mais containers que compartilham rede e volumes
DeploymentMantém N réplicas de um pod sem estado e faz atualizações graduais (via ReplicaSets)
StatefulSetComo o Deployment, mas com identidade e disco estáveis por réplica (bancos, filas)
DaemonSetUm pod em cada node (coletores de log, agentes de monitoramento)
Job / CronJobTarefas que terminam: uma vez, ou num agendamento (migrations, relatórios)
ServiceUm endereço estável na frente de um grupo de pods
Gateway / IngressEntrada HTTP de fora do cluster, com roteamento por domínio e caminho
ConfigMap / SecretConfiguração e dados sensíveis, separados da imagem
PersistentVolumeClaimUm pedido de disco que sobrevive ao pod
NamespaceSeparação lógica dentro do cluster (por time, ambiente ou aplicação)

Todos são declarados do mesmo jeito: um YAML com apiVersion, kind, metadata e spec (o estado desejado). O Kubernetes acrescenta o status (o estado atual).

Pods

Um pod agrupa containers que precisam estar sempre juntos no mesmo node: compartilham o mesmo IP (conversam por localhost) e podem compartilhar volumes. Na maioria das vezes, um container por pod.

  • Init containers rodam antes do principal e precisam terminar com sucesso (ex.: esperar o banco, rodar uma migração).
  • Sidecars rodam junto com o principal durante toda a vida do pod (ex.: proxy de service mesh, coletor de logs). Desde a versão 1.33 são um recurso estável, com ordem de início e de parada garantida.

Pods são descartáveis: não são “consertados”, são substituídos, e cada pod novo ganha outro IP. É por isso que ninguém fala com pods diretamente, e sim com Services. Também é por isso que a aplicação deve ser sem estado, como pedem os Doze Fatores.

Deployments e atualizações

Um Deployment completo, com os campos que importam em produção:

apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3 # estado desejado: 3 pods
selector:
matchLabels: { app: api } # quais pods pertencem a este Deployment
strategy:
rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }
template: # o molde de cada pod
metadata:
labels: { app: api }
spec:
containers:
- name: api
image: ghcr.io/acme/api:1.4.0
ports: [{ containerPort: 8080 }]
resources:
requests: { cpu: 250m, memory: 256Mi }
limits: { memory: 512Mi }
readinessProbe:
httpGet: { path: /ready, port: 8080 }
livenessProbe:
httpGet: { path: /health, port: 8080 }
envFrom:
- configMapRef: { name: api-config }

Ao trocar a imagem para 1.5.0, o Deployment faz um rolling update: cria um pod novo, espera ele ficar pronto (readiness), remove um antigo, e repete. Com maxUnavailable: 0, nunca há menos de 3 pods atendendo. Se algo der errado:

Terminal window
kubectl rollout status deployment/api # acompanha a atualização
kubectl rollout undo deployment/api # volta para a versão anterior

Rede: Services e entrada

Pods mudam de IP a cada recriação. Um Service dá um endereço e um nome DNS estáveis a um grupo de pods, escolhidos por labels:

ignora

Outro pod

http://api

Service api

selector: app=api

Pod

app=api

Pod

app=api

Pod

app=api

Pod

app=worker

ignora

Outro pod

http://api

Service api

selector: app=api

Pod

app=api

Pod

app=api

Pod

app=api

Pod

app=worker

Dentro do cluster, o Service é encontrado pelo nome: api no mesmo namespace, ou api.pagamentos.svc.cluster.local de qualquer lugar. Os pods entram e saem do Service conforme ficam prontos ou não (readiness).

Tipo de ServiceAcessível de
ClusterIP (padrão)Só de dentro do cluster
NodePortUma porta em cada node do cluster
LoadBalancerUm balanceador de carga do provedor de nuvem, com IP público
Entrada HTTP: Gateway API

Para expor vários serviços HTTP num mesmo endereço, com roteamento por domínio e caminho e TLS, o recurso clássico era o Ingress. O controller mais usado, o Ingress NGINX, foi aposentado em março de 2026 e não recebe mais correções, nem de segurança. A recomendação oficial agora é a Gateway API, que separa as responsabilidades:

  • GatewayClass: o tipo de gateway (qual controller implementa), definido pela infraestrutura.
  • Gateway: o ponto de entrada (endereço, portas, certificados), gerenciado pelo time de plataforma.
  • HTTPRoute: as regras de roteamento de cada aplicação, gerenciadas pelo time dono dela.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api
spec:
parentRefs: [{ name: public-gateway }]
hostnames: ["api.example.com"]
rules:
- matches: [{ path: { type: PathPrefix, value: /v1 } }]
backendRefs: [{ name: api, port: 80 }]

Configuração e segredos

  • ConfigMap: configuração não sensível (URLs, flags, arquivos de configuração), injetada como variáveis de ambiente ou como arquivos montados.
  • Secret: senhas, tokens e certificados. Atenção: por padrão, um Secret é só codificado em base64, não criptografado. Qualquer um com permissão de leitura no namespace o lê. Em produção, ative a criptografia do etcd, restrinja o acesso com RBAC e considere um cofre externo (Vault, gerenciadores de segredos da nuvem) sincronizado com o cluster.

Separar configuração da imagem permite usar a mesma imagem em todos os ambientes, mudando só o ConfigMap.

Saúde: probes

O Kubernetes não tem como saber se a sua aplicação está bem; ele pergunta, por meio de probes:

ProbePerguntaSe falhar
startupProbeJá terminou de iniciar?Continua esperando; as outras probes só começam depois
readinessProbePode receber tráfego agora?Sai do Service (sem reiniciar) até voltar a responder
livenessProbeEstá viva, ou travou?O container é reiniciado

O erro clássico: fazer a liveness checar o banco de dados. Se o banco cair, todos os pods falham a liveness e são reiniciados em loop, sem resolver nada. Dependências externas vão na readiness; a liveness só verifica se o próprio processo responde.

Recursos e escala

Cada container declara quanto precisa e até quanto pode usar:

  • requests: o que o scheduler reserva no node. Um pod só vai para um node que tenha esse espaço livre.
  • limits: o teto. Passar do limite de memória mata o container (OOMKilled). Passar do limite de CPU não mata: o container é estrangulado (throttling) e fica lento. Por isso muitos times definem limite de memória mas não de CPU.

A combinação define a prioridade do pod quando falta recurso no node (QoS): pods com requests iguais aos limits (Guaranteed) são os últimos a serem despejados.

Escalando
  • Horizontal (HPA): o HorizontalPodAutoscaler aumenta ou reduz o número de réplicas conforme uma métrica (CPU, memória ou métricas da aplicação, como requisições por segundo).
  • Vertical: ajustar requests e limits. Desde a versão 1.35, é possível redimensionar CPU e memória de um pod sem reiniciá-lo.
  • Cluster: quando os pods não cabem mais nos nodes, o cluster autoscaler (ou o Karpenter, na AWS) adiciona máquinas; quando sobram, remove.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api
spec:
scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: api }
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }

Armazenamento

O disco de um container some junto com ele. Para dados que precisam sobreviver, o pod pede um volume por meio de um PersistentVolumeClaim (“quero 10 GiB, de leitura e escrita”), e uma StorageClass provisiona o disco no provedor (um EBS na AWS, um Persistent Disk no GCP). Bancos de dados e filas usam StatefulSets, que dão a cada réplica um nome fixo (db-0, db-1) e o seu próprio disco.

Na prática, muitos times preferem bancos gerenciados fora do cluster e deixam o Kubernetes para as aplicações sem estado.

Sintomas e causas

Status ou sintomaO que geralmente é
PendingNenhum node tem os recursos pedidos (requests altos demais) ou o volume não pôde ser criado
ImagePullBackOffNome ou tag da imagem errados, ou falta credencial para o registry privado
CrashLoopBackOffO container inicia e morre em seguida; veja kubectl logs --previous
OOMKilledPassou do limite de memória; aumente o limite ou investigue o consumo
Service não respondeO selector não bate com os labels dos pods, ou nenhum pod passa na readiness (kubectl get endpoints)
Pods reiniciando sem pararLiveness rígida demais, ou dependente de algo externo
Aplicação lenta sem erroThrottling de CPU pelo limit

Os comandos que resolvem a maior parte das investigações:

Terminal window
kubectl get pods -o wide # status e em qual node está
kubectl describe pod <pod> # eventos: por que não agendou, por que reiniciou
kubectl logs <pod> --previous # logs da execução que morreu
kubectl get events --sort-by=.lastTimestamp

Quando usar

Use KubernetesPrefira outra coisa
Muitos serviços, vários times, deploys frequentesUma aplicação ou poucas: um PaaS ou containers gerenciados (Cloud Run, App Runner, Fly.io) resolvem com muito menos esforço
Precisa de portabilidade entre nuvens ou on-premiseTime pequeno sem ninguém para operar a plataforma
Escala automática e atualizações sem indisponibilidadeCargas que cabem em funções serverless

O custo do Kubernetes não é a instalação, é a operação: atualizações de versão a cada poucos meses (o projeto mantém só as três versões mais recentes), segurança, observabilidade e o conhecimento do time. Um cluster gerenciado reduz, mas não elimina, esse custo.

Veja também: Entendendo containers, Observabilidade e System design.

Vídeos

Referências

1/17 · Por que Kubernetes