Guias / kubernetes
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.
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
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.
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:
brew install kind # ou o binário em kind.sigs.k8s.iokind create cluster
# 3 réplicas do nginx e um Service na frente delaskubectl create deployment web --image=nginx --replicas=3kubectl expose deployment web --port=80kubectl get pods -o wide
# acessar pelo navegador em http://localhost:8080kubectl port-forward service/web 8080:80Agora veja a reconciliação acontecendo. Em outro terminal, apague um pod e observe outro nascer no lugar:
kubectl get pods --watchkubectl delete pod <nome-de-um-pod>kubectl scale deployment web --replicas=5Para limpar tudo: kind delete cluster.
Os objetos principais
| Objeto | Para quê |
|---|---|
| Pod | A menor unidade: um ou mais containers que compartilham rede e volumes |
| Deployment | Mantém N réplicas de um pod sem estado e faz atualizações graduais (via ReplicaSets) |
| StatefulSet | Como o Deployment, mas com identidade e disco estáveis por réplica (bancos, filas) |
| DaemonSet | Um pod em cada node (coletores de log, agentes de monitoramento) |
| Job / CronJob | Tarefas que terminam: uma vez, ou num agendamento (migrations, relatórios) |
| Service | Um endereço estável na frente de um grupo de pods |
| Gateway / Ingress | Entrada HTTP de fora do cluster, com roteamento por domínio e caminho |
| ConfigMap / Secret | Configuração e dados sensíveis, separados da imagem |
| PersistentVolumeClaim | Um pedido de disco que sobrevive ao pod |
| Namespace | Separaçã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/v1kind: Deploymentmetadata: name: apispec: 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:
kubectl rollout status deployment/api # acompanha a atualizaçãokubectl rollout undo deployment/api # volta para a versão anteriorRede: 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:
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 Service | Acessível de |
|---|---|
ClusterIP (padrão) | Só de dentro do cluster |
NodePort | Uma porta em cada node do cluster |
LoadBalancer | Um 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/v1kind: HTTPRoutemetadata: name: apispec: 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:
| Probe | Pergunta | Se falhar |
|---|---|---|
startupProbe | Já terminou de iniciar? | Continua esperando; as outras probes só começam depois |
readinessProbe | Pode receber tráfego agora? | Sai do Service (sem reiniciar) até voltar a responder |
livenessProbe | Está 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/v2kind: HorizontalPodAutoscalermetadata: name: apispec: 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 sintoma | O que geralmente é |
|---|---|
Pending | Nenhum node tem os recursos pedidos (requests altos demais) ou o volume não pôde ser criado |
ImagePullBackOff | Nome ou tag da imagem errados, ou falta credencial para o registry privado |
CrashLoopBackOff | O container inicia e morre em seguida; veja kubectl logs --previous |
OOMKilled | Passou do limite de memória; aumente o limite ou investigue o consumo |
| Service não responde | O selector não bate com os labels dos pods, ou nenhum pod passa na readiness (kubectl get endpoints) |
| Pods reiniciando sem parar | Liveness rígida demais, ou dependente de algo externo |
| Aplicação lenta sem erro | Throttling de CPU pelo limit |
Os comandos que resolvem a maior parte das investigações:
kubectl get pods -o wide # status e em qual node estákubectl describe pod <pod> # eventos: por que não agendou, por que reinicioukubectl logs <pod> --previous # logs da execução que morreukubectl get events --sort-by=.lastTimestampQuando usar
| Use Kubernetes | Prefira outra coisa |
|---|---|
| Muitos serviços, vários times, deploys frequentes | Uma 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-premise | Time pequeno sem ninguém para operar a plataforma |
| Escala automática e atualizações sem indisponibilidade | Cargas 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
- Kubernetes Explained in 6 Minutes (ByteByteGo): a arquitetura do cluster de forma visual e rápida (é o vídeo embutido no topo).
Referências
- Componentes do Kubernetes: a documentação oficial, em português.
- kind: quick start: instalar e criar clusters locais.
- Gateway API: os recursos e o modelo por papéis.
- Ingress NGINX Retirement: o anúncio da aposentadoria e as alternativas.
- In-place pod resize GA: redimensionar pods sem reiniciar (1.35).
- Versões suportadas: a versão atual e o ciclo de suporte.