DevOps, CI/CD e contêineres
Fundamentos de Kubernetes: arquitetura, Pods, Deployments e Services
Intermediário10 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
O Kubernetes substitui uma pilha de scripts de deploy por um loop de controle: você declara o estado que quer e o cluster trabalha continuamente para alcançá-lo. Este guia explica os componentes que fazem esse loop funcionar, os objetos básicos que você vai usar todos os dias — Pods, ReplicaSets, Deployments, Services, ConfigMaps e Secrets — e termina com um manifesto completo para uma aplicação real, além dos comandos kubectl para operá-la.
O que você vai aprender
- Descrever os componentes do control plane e dos nós e a responsabilidade de cada um
- Explicar o loop de reconciliação e por que a configuração do Kubernetes é declarativa
- Criar Deployments e entender as camadas de Pod e ReplicaSet que ficam por baixo deles
- Expor workloads com Services e resolvê-los pelo DNS do cluster
- Fornecer configuração e segredos aos containers sem refazer o build das imagens
- Usar labels, selectors e namespaces para organizar e selecionar objetos
Depois que uma aplicação é empacotada como imagem de container, surge uma nova pergunta: o que a executa? Em uma única máquina, uma política de reinício e um script bastam. Em uma frota inteira, você precisa de algo que decida em qual máquina cada container roda, reinicie o container quando ele morrer, mova-o quando um nó falhar, substitua a versão antiga pela nova sem perder tráfego e dê aos outros serviços uma forma estável de encontrá-lo. Esse trabalho se chama orquestração, e o Kubernetes virou a resposta padrão.
O que torna o Kubernetes incomum não é a lista de funcionalidades, e sim o modelo. Você não diz a ele para iniciar um container; você registra o estado que deseja no banco de dados do cluster, e um conjunto de controladores trabalha continuamente para que a realidade corresponda a ele. Entender essa ideia torna o resto do sistema — inclusive as mensagens de erro — muito mais fácil de ler. Este guia parte da arquitetura e chega a um manifesto completo e funcional, partindo do princípio de que você já sabe o que é uma imagem pelo guia de fundamentos de Docker.
Como um cluster é montado
Um cluster é um control plane mais um conjunto de nós worker.
O control plane roda quatro componentes. O API server é a porta de entrada: tudo — o kubectl, os controladores, os kubelets de cada nó — conversa com ele por meio de uma API REST autenticada, e nada conversa com mais nada. O etcd é o armazenamento chave-valor que guarda todo o estado do cluster; se você for fazer backup de uma única coisa, que seja o etcd. O scheduler observa os Pods sem nó atribuído e escolhe um para cada, com base nas requisições de recursos, nas labels dos nós, nos taints e nas regras de afinidade. O controller manager roda os loops de reconciliação: o controlador de Deployments, o de nós, o de jobs e dezenas de outros, cada um observando os objetos de um tipo e agindo sobre as diferenças. Em um cluster gerenciado você nunca vê esses processos; em um autogerenciado, eles costumam rodar como Pods estáticos em máquinas dedicadas, em número ímpar por causa do quórum do etcd.
Cada nó worker roda três coisas. O kubelet é o agente que conversa com o API server, recebe os Pods atribuídos ao seu nó, pede ao container runtime que os inicie e reporta o status. O container runtime — containerd ou CRI-O nos clusters atuais, já que o shim do Docker foi removido na 1.24 — é quem de fato baixa as imagens e executa os containers. O kube-proxy programa as regras de rede do nó para que o tráfego destinado ao IP virtual de um Service chegue a um dos Pods por trás dele.
Adicione um plugin CNI (Calico, Cilium, Flannel e outros) que dá a cada Pod um IP roteável, e o CoreDNS rodando como workload para resolver os nomes dos Services, e você tem um cluster.
O loop de reconciliação
Todo controlador roda o mesmo loop: observar o estado atual pelo API server, compará-lo com o estado desejado registrado na spec do objeto e dar um passo para fechar a diferença. Nada é um comando pontual.
É por isso que os objetos do Kubernetes têm uma spec (o que você quer) e um status (o que é verdade agora), e por isso a resposta para "por que meu Pod não está rodando?" quase sempre está no status e nos eventos associados a ele. Apague um Pod que pertence a um Deployment e um substituto aparece em segundos — não porque a exclusão disparou uma criação, mas porque um controlador percebeu que há um Pod a menos do que a spec pede.
É também por isso que a configuração é declarativa. Você escreve YAML descrevendo o estado final e o aplica:
kubectl apply -f k8s/ # create or update everything in the directory
kubectl diff -f k8s/ # what would change, before it changes
kubectl delete -f k8s/
O kubectl apply registra a configuração que você forneceu, então aplicar um arquivo modificado remove os campos que você tirou e não mexe nos campos gerenciados por outros atores. Comandos imperativos (kubectl create deployment, kubectl scale, kubectl edit) existem e são úteis para explorar, mas a configuração de produção deve ficar em arquivos sob controle de versão — o mesmo argumento que vale para infraestrutura como código em geral.
Pods
Um Pod é a menor coisa que o Kubernetes agenda. Ele contém um ou mais containers que compartilham um namespace de rede — um endereço IP, um espaço de portas, de modo que se alcançam em localhost — e podem compartilhar volumes. Os containers de um Pod são sempre colocados no mesmo nó e vivem e morrem juntos.
A maioria dos Pods tem um único container de aplicação. O segundo container, quando existe, é um auxiliar: um encaminhador de logs lendo um volume compartilhado, um proxy de service mesh interceptando o tráfego, um agente renovando um arquivo de credenciais.
apiVersion: v1
kind: Pod
metadata:
name: shop-api
labels:
app: shop-api
spec:
containers:
- name: api
image: registry.example.com/acme/shop-api:1.4.2
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
Você quase nunca deveria criar um Pod assim. Um Pod avulso não é recriado se o nó dele morrer: ninguém é dono dele. Pods são gado, criados e destruídos por controladores, e seus nomes, endereços IP e tempos de vida são todos efêmeros. Tudo o que precisa persistir vai para um volume; tudo o que precisa ser acessível fica atrás de um Service.
Deployments e ReplicaSets
Um ReplicaSet mantém vivo um número especificado de Pods que correspondem a um template. Um Deployment é dono de ReplicaSets e gerencia as transições entre eles, e é isso que dá a você rollouts versionados e rollback.
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-api
labels: { app: shop-api }
spec:
replicas: 3
selector:
matchLabels:
app: shop-api
template:
metadata:
labels:
app: shop-api
version: "1.4.2"
spec:
containers:
- name: api
image: registry.example.com/acme/shop-api:1.4.2
ports:
- containerPort: 8080
O selector diz ao Deployment quais Pods são dele; o template é o Pod que ele produz em série. Os dois precisam estar de acordo — as labels do template têm de satisfazer o selector — e o selector é imutável depois da criação, então escolha uma label estável como app em vez de algo que muda a cada release.
Mude a tag da imagem, aplique de novo, e o Deployment cria um segundo ReplicaSet, aumenta sua escala conforme os novos Pods ficam prontos e reduz o antigo. kubectl rollout status deployment/shop-api acompanha o progresso; kubectl rollout undo volta ao ReplicaSet anterior. Os detalhes de quão gradual isso é — surge, indisponibilidade, espera pela readiness — estão no guia de padrões de Kubernetes.
Existem outros dois controladores de workload para formatos diferentes: o DaemonSet roda exatamente um Pod por nó (agentes de log, node exporters), e o StatefulSet dá aos Pods identidades estáveis e armazenamento próprio (bancos de dados, brokers).
Labels, selectors e namespaces
O Kubernetes não tem hierarquia de objetos. Em vez disso, tudo é marcado com labels — pares chave/valor arbitrários — e os outros objetos encontram o que precisam com selectors. Um Service encontra seus Pods por selector, um Deployment é dono dos seus Pods por selector, e você também consulta por selector:
kubectl get pods -l app=shop-api
kubectl get pods -l 'environment in (staging,prod),tier!=batch'
kubectl label pod shop-api-7c9f-2k4x debug=true # add a label on the fly
Vale a pena lembrar esse último truque: altere as labels de um Pod para que o selector do Service deixe de corresponder, e o Pod sai do balanceamento de carga enquanto continua rodando. Assim, você pode conectar um debugger a uma instância com problema sem que os usuários cheguem até ela, e o Deployment sobe um substituto para manter o número de réplicas.
Use annotations para metadados que não identificam o objeto — uma URL de build, um checksum, a configuração de um ingress controller. Os selectors as ignoram.
Os namespaces particionam um cluster: os nomes precisam ser únicos dentro de um namespace, e cotas, regras de RBAC e network policies se aplicam por namespace. As divisões típicas são por time, ou por ambiente quando os ambientes compartilham um cluster. Objetos de escopo de cluster — nós, PersistentVolumes, StorageClasses — ficam fora dos namespaces.
kubectl create namespace staging
kubectl config set-context --current --namespace=staging # stop typing -n
kubectl get all -n staging
Services e como o tráfego encontra um Pod
Os IPs dos Pods mudam o tempo todo. Um Service fornece um IP virtual e um nome DNS estáveis na frente de um conjunto de Pods selecionados por label, e balanceia a carga entre os que estão prontos.
apiVersion: v1
kind: Service
metadata:
name: shop-api
spec:
type: ClusterIP
selector:
app: shop-api
ports:
- name: http
port: 80 # the Service port
targetPort: 8080 # the container port
Quatro tipos importam:
| Tipo | O que ele oferece | Uso típico |
|---|---|---|
ClusterIP | Um IP virtual acessível apenas de dentro do cluster | Chamadas entre serviços. O padrão. |
NodePort | O mesmo, mais uma porta alta aberta em todos os nós | Clusters bare metal, demos rápidas |
LoadBalancer | Um NodePort mais um load balancer de nuvem com IP externo | Pontos de entrada públicos em clusters gerenciados |
Headless (clusterIP: None) | Sem IP virtual; o DNS retorna diretamente os IPs dos Pods | StatefulSets, balanceamento de carga no cliente |
Dentro do cluster, o CoreDNS resolve shop-api no mesmo namespace e shop-api.staging.svc.cluster.local de qualquer lugar. Assim, a string de conexão na configuração de outro serviço é simplesmente http://shop-api/, e continua funcionando a cada reinício, reagendamento e rollout.
Expor muitos serviços HTTP por um único endereço é trabalho para um Ingress ou para a mais recente Gateway API, e não para um LoadBalancer por serviço; isso é tratado no guia de padrões.
Configuração e segredos
As imagens devem ser idênticas em todos os ambientes; só a configuração muda. ConfigMaps guardam configuração não sensível, Secrets guardam credenciais, e ambos podem ser expostos como variáveis de ambiente ou como arquivos.
apiVersion: v1
kind: ConfigMap
metadata:
name: shop-api-config
data:
SPRING_PROFILES_ACTIVE: "prod"
application.yaml: |
shop:
currency: EUR
catalogue:
page-size: 50
---
apiVersion: v1
kind: Secret
metadata:
name: shop-api-db
type: Opaque
stringData:
username: "shop"
password: "correct-horse-battery-staple"
O stringData permite escrever valores em texto puro que o API server codifica em base64 na entrada — muito mais fácil de revisar do que blobs já codificados. Consuma-os na spec do Pod:
envFrom:
- configMapRef:
name: shop-api-config
env:
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: shop-api-db
key: password
volumeMounts:
- name: config
mountPath: /config
readOnly: true
Montar como arquivo tem uma propriedade útil: as atualizações do ConfigMap se propagam para o container em execução em um ou dois minutos, então uma aplicação que observa seu arquivo de configuração consegue aplicar as mudanças sem reiniciar. Já as variáveis de ambiente são fixadas na inicialização do container — alterar um ConfigMap não reinicia nada —, e é por isso que os times adicionam uma annotation de checksum ao template do Pod, para que uma atualização dispare o rollout do Deployment.
Não faça commit no Git de manifestos de Secrets com valores reais. Use Sealed Secrets, o External Secrets Operator ou o gerenciador de segredos do seu provedor de nuvem, e habilite a criptografia em repouso do etcd.
Um manifesto completo
Tudo o que vimos até aqui, para um único serviço:
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-api
namespace: staging
labels: { app: shop-api }
spec:
replicas: 3
selector:
matchLabels: { app: shop-api }
template:
metadata:
labels: { app: shop-api }
annotations:
config-checksum: "8f4c1a9e"
spec:
serviceAccountName: shop-api
securityContext:
runAsNonRoot: true
runAsUser: 10001
containers:
- name: api
image: registry.example.com/acme/shop-api:1.4.2
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
envFrom:
- configMapRef: { name: shop-api-config }
env:
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef: { name: shop-api-db, key: password }
- name: POD_NAME
valueFrom:
fieldRef: { fieldPath: metadata.name }
volumeMounts:
- { name: config, mountPath: /config, readOnly: true }
- { name: tmp, mountPath: /tmp }
volumes:
- name: config
configMap: { name: shop-api-config }
- name: tmp
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: shop-api
namespace: staging
spec:
selector: { app: shop-api }
ports:
- { name: http, port: 80, targetPort: http }
Repare em targetPort: http, que se refere à porta nomeada do container: se você renomear a porta depois, o Service acompanha. O fieldRef injeta metadados do cluster no container, e é assim que as aplicações descobrem o nome do próprio Pod para os logs. E o emptyDir em /tmp é o que permite, mais tarde, montar o sistema de arquivos do container como somente leitura sem quebrar bibliotecas que precisam de espaço temporário.
Trabalhando com kubectl
Uma dúzia de comandos cobre a operação do dia a dia.
kubectl get pods -o wide # with node and IP
kubectl get deploy,svc,cm -n staging
kubectl describe pod shop-api-7c9f-2k4x # spec, status and events
kubectl logs -f deploy/shop-api # follow one Pod of the Deployment
kubectl logs shop-api-7c9f-2k4x --previous # the crashed container's logs
kubectl exec -it shop-api-7c9f-2k4x -- sh
kubectl port-forward svc/shop-api 8080:80 # reach a ClusterIP from your laptop
kubectl rollout status deploy/shop-api
kubectl rollout undo deploy/shop-api
kubectl scale deploy/shop-api --replicas=5
kubectl get events --sort-by=.lastTimestamp # what the cluster has been doing
kubectl top pods # live CPU and memory, needs metrics-server
Quando algo dá errado, a ordem que resolve mais rápido é: get pods para ver a fase, describe pod para ler os eventos no final da saída e logs --previous se o Pod estiver em crash loop. A fase geralmente nomeia o problema diretamente — ImagePullBackOff (tag errada ou credencial do registry ausente), CrashLoopBackOff (o processo encerra; leia os logs), Pending (nada consegue agendá-lo; os eventos dizem se é falta de recursos, um taint ou um volume não vinculado), CreateContainerConfigError (uma chave de ConfigMap ou Secret referenciada não existe).
Principais conclusões
- O control plane — API server, etcd, scheduler, controller manager — registra o estado desejado e roda os loops que reconciliam a realidade com ele; o kubelet, o container runtime e o kube-proxy fazem o trabalho em cada nó.
- Tudo é declarativo: escreva a spec, aplique-a e leia o
statuse os eventos quando o resultado for diferente do esperado. - Um Pod é a unidade de agendamento e é descartável; Deployments são donos de ReplicaSets, que são donos de Pods, o que dá a você rollouts, rollback e autorrecuperação.
- Labels e selectors são a forma como os objetos se encontram, e namespaces particionam nomes, cotas e acesso.
- Services fornecem um IP virtual e um nome DNS estáveis na frente dos Pods prontos:
ClusterIPpara tráfego interno,LoadBalancerpara pontos de entrada na nuvem, headless para endereçamento estável por Pod. - ConfigMaps e Secrets mantêm a configuração fora das imagens; arquivos montados podem ser atualizados em tempo real, variáveis de ambiente não, e Secrets precisam de criptografia em repouso e de um RBAC rigoroso.
get,describe,logs --previouseeventsdiagnosticam quase tudo, e a fase do Pod geralmente já nomeia a falha de cara.
Fontes e leituras complementares
Perguntas frequentes
- Preciso de Kubernetes para a minha aplicação?
- Não necessariamente. O Kubernetes se paga quando você roda muitos serviços, precisa de autorrecuperação diante de falhas de máquina, faz deploy com frequência e sem downtime ou quer uma plataforma uniforme compartilhada por vários times. Para um ou dois serviços em um único servidor, ele adiciona muita engrenagem e uma curva de aprendizado realmente íngreme. Um cluster gerenciado reduz o custo operacional, mas não o conceitual.
- Por que um Pod em vez de simplesmente um container?
- Um Pod é um grupo de containers que compartilham um namespace de rede, um endereço IP e, opcionalmente, volumes, e que são sempre agendados no mesmo nó. Esse agrupamento permite que um container auxiliar — um coletor de logs, um proxy, um agente que renova credenciais — rode ao lado da aplicação e a acesse em localhost. A maioria dos Pods contém exatamente um container de aplicação e, ocasionalmente, um auxiliar.
- Qual é a diferença entre um Deployment e um ReplicaSet?
- Um ReplicaSet mantém em execução um número fixo de Pods idênticos. Um Deployment gerencia ReplicaSets ao longo do tempo: quando você altera o template do Pod, ele cria um novo ReplicaSet, aumenta sua escala enquanto reduz o antigo e mantém os anteriores para rollback. Você quase sempre cria Deployments e deixa que eles sejam donos dos ReplicaSets, e é por isso que raramente se escreve um ReplicaSet à mão.
- Os Secrets do Kubernetes são realmente secretos?
- Por padrão, eles são codificados em base64, não criptografados, e qualquer pessoa que consiga ler os Secrets de um namespace consegue ler seus valores. São melhores que uma variável de ambiente em um manifesto porque o acesso é controlado por RBAC e eles não ficam embutidos nas imagens, mas uma configuração séria adiciona criptografia em repouso no etcd, um RBAC rigoroso e, muitas vezes, um gerenciador de segredos externo sincronizado com o cluster.