Kubernetes
Roadmap de estudo de Kubernetes (K8s), estruturado em 11 pontos, do zero ao avançado. Ambiente de prática local via kind, com contexto de produção em VPS (Hetzner/Oracle) via k3s.
1. Fundamentos
Por que o Kubernetes existe
Docker Compose roda containers em uma máquina só. Funciona até você precisar de:
- Múltiplas máquinas (mais capacidade, mais disponibilidade)
- Reinício automático quando um container cai
- Escalonamento automático de réplicas
- Deploy sem downtime
O Kubernetes é o orquestrador que gerencia containers rodando em um cluster (conjunto de máquinas), garantindo que o estado real bata com o estado desejado.
O que é um cluster
Conjunto de máquinas (nodes) que trabalham juntas como uma unidade, gerenciadas pelo Control Plane. Pode ter 1 node (estudo) ou centenas (produção). Você não interage com uma máquina específica — fala com o cluster através do API Server, e o Kubernetes decide onde as coisas rodam.
Arquitetura
Control Plane (o "cérebro")
| Componente | Função |
|---|---|
| API Server | Porta de entrada — todo comando (kubectl) passa por ele |
| etcd | Banco de dados que guarda o estado do cluster |
| Scheduler | Decide em qual node um Pod vai rodar |
| Controller Manager | Garante que o estado real bate com o desejado (recria Pods que morrem, etc) |
Node (worker, onde os containers rodam)
| Componente | Função |
|---|---|
| kubelet | Agente que conversa com o Control Plane e sobe os containers |
| kube-proxy | Gerencia rede/roteamento entre Pods |
| Container runtime | containerd — o que de fato roda o container |
Fluxo básico: você manda um YAML pro API Server → ele salva no etcd → Scheduler escolhe um node → kubelet daquele node sobe o container.
Escalonamento — o que escala, de fato
- Cluster: nunca escala. É a unidade fixa criada.
- Node: só escala com Cluster Autoscaler configurado (comum em cloud), que adiciona máquinas quando os nodes existentes não têm mais capacidade.
- Pod: é o que escala na prática. Ao pedir mais réplicas (Deployment) ou via HorizontalPodAutoscaler, o Kubernetes cria novos Pods e distribui entre os nodes existentes.
Instalação local
- kind (Kubernetes IN Docker) — roda o cluster inteiro dentro de containers Docker, leve, recomendado para começar
- minikube — cluster em VM/container, consome mais recursos
- k3d — k3s (versão leve) dentro de Docker
# instalar kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.23.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
# instalar kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
# criar cluster
kind create cluster --name meu-primeiro-cluster
kubectl cluster-info
kubectl get nodes2. Objetos básicos
Pod
Menor unidade do Kubernetes — um ou mais containers rodando juntos, compartilhando rede e storage. Na prática, quase sempre 1 container por Pod. Sozinho, não tem auto-cura: se morre, ninguém recria.
apiVersion: v1
kind: Pod
metadata:
name: meu-app
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80ReplicaSet
Garante N réplicas de um Pod sempre rodando. Raramente criado diretamente — é gerenciado pelo Deployment.
Deployment
Camada acima do ReplicaSet. Gerencia rolling updates e rollback. Objeto usado no dia a dia (equivalente a docker-compose up com restart automático + deploy sem downtime).
apiVersion: apps/v1
kind: Deployment
metadata:
name: meu-app
spec:
replicas: 3
selector:
matchLabels:
app: meu-app
template:
metadata:
labels:
app: meu-app
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80kubectl set image deployment/meu-app nginx=nginx:1.25
kubectl rollout status deployment/meu-app
kubectl rollout undo deployment/meu-app
# parar temporariamente
kubectl scale deployment meu-app --replicas=0
# deletar de vez
kubectl delete deployment meu-appService
Pods são efêmeros (IP muda ao recriar). Service dá um endereço estável para acessar um grupo de Pods.
ClusterIP(padrão) — acesso só dentro do clusterNodePort— expõe porta fixa em cada nodeLoadBalancer— pede um LB externo (cloud)
apiVersion: v1
kind: Service
metadata:
name: meu-app-svc
spec:
selector:
app: meu-app
ports:
- port: 80
targetPort: 80
type: ClusterIPNamespace
Divisão lógica dentro do cluster — organiza recursos (dev, staging, prod) e isola nomes.
kubectl create namespace dev
kubectl apply -f deployment.yaml -n dev3. Configuração e dados
ConfigMap
Configurações não sensíveis (variáveis de ambiente, arquivos de config) fora da imagem.
apiVersion: v1
kind: ConfigMap
metadata:
name: meu-app-config
data:
APP_ENV: "production"
LOG_LEVEL: "info"Secret
Igual ConfigMap, para dados sensíveis (senhas, tokens). Valores em base64 por padrão — não é criptografia forte; em produção real, combinar com Sealed Secrets ou Vault.
apiVersion: v1
kind: Secret
metadata:
name: meu-app-secret
type: Opaque
stringData:
DB_PASSWORD: "minhasenha123"
JWT_SECRET: "supersecreto"Uso em Deployment (ambos):
spec:
containers:
- name: nginx
image: nginx:latest
envFrom:
- configMapRef:
name: meu-app-config
- secretRef:
name: meu-app-secretVolumes
emptyDir— storage temporário compartilhado entre containers do mesmo Pod. Some quando o Pod morre.hostPath— monta um diretório do node dentro do Pod. Só uso local (não usar em produção multi-node).
PersistentVolume (PV) / PersistentVolumeClaim (PVC) / StorageClass
Storage que sobrevive mesmo se o Pod for recriado em outro node.
- PersistentVolume — o storage físico real (disco, NFS, cloud disk)
- PersistentVolumeClaim — o pedido de storage feito pela aplicação
- StorageClass — define como o PV é provisionado automaticamente (ex: Hetzner CSI)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: meu-app-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1GiNo kind, o PVC provisiona automaticamente local. Em produção (Hetzner), usar CSI driver da Hetzner ou Longhorn.
4. Rede
DNS interno
Todo Service ganha um nome DNS automático: <service>.<namespace>.svc.cluster.local. Dentro do mesmo namespace, basta usar o nome curto do Service.
Ingress e Ingress Controller
Service LoadBalancer/NodePort expõe portas, mas não roteia por domínio/path nem faz HTTPS. O Ingress faz esse papel — equivalente ao Traefik usado em VPS. O objeto Ingress sozinho não faz nada: precisa de um Ingress Controller rodando no cluster para interpretar as regras.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: meu-app-ingress
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: meuapp.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: meu-app-svc
port:
number: 80Configurando Traefik no kind:
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
- containerPort: 443
hostPort: 443kind create cluster --config kind-config.yaml
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik \
--namespace traefik --create-namespace \
--set ports.web.hostPort=80 \
--set ports.websecure.hostPort=443 \
--set service.type=NodePortNetworkPolicy
Por padrão, todo Pod pode falar com todo Pod no cluster. NetworkPolicy é o "firewall" que restringe isso.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: so-backend-acessa-db
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432O
kindusakindnetpor padrão, que não suporta NetworkPolicy. Para testar de verdade, trocar o CNI para Calico.
5. Escalonamento e recursos
Resource requests/limits
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"requests— usado pelo Scheduler pra decidir em qual node o Pod cabelimits— ultrapassar memória mata o Pod (OOMKilled); ultrapassar CPU apenas limita (throttle)
HorizontalPodAutoscaler (HPA)
Escala réplicas automaticamente com base em CPU/memória (ou métricas customizadas). Depende de resources.requests definido e do metrics-server rodando.
kubectl autoscale deployment meu-app --cpu-percent=70 --min=2 --max=10apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: meu-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: meu-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Probes
- livenessProbe — "está vivo?" Se falha, o Kubernetes reinicia o container.
- readinessProbe — "está pronto pra tráfego?" Se falha, sai da lista do Service, sem reiniciar.
- startupProbe — dá tempo extra pra apps lentas pra subir, antes das outras probes agirem.
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 56. Workloads avançados
StatefulSet
Para apps com estado: identidade fixa (app-0, app-1...), storage próprio por Pod, ordem de criação/destruição. Uso típico: bancos de dados, Redis em cluster, Kafka.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5GiO StatefulSet garante identidade e storage estáveis, mas não sincroniza dados. Replicação (quem é primário/réplica) é responsabilidade do próprio banco (streaming replication nativo) ou de um operator especializado (CloudNativePG, Zalando Postgres Operator, Patroni).
Casos que justificam múltiplas réplicas de Postgres: alta disponibilidade/failover, escalar leitura (read replicas), distribuição geográfica, réplica dedicada a backups/relatórios pesados. Para a maioria dos projetos pequenos, uma instância única com PVC e backup regular é suficiente.
DaemonSet
Garante um Pod rodando em cada node do cluster. Usado para agentes de infraestrutura (coletores de log, monitoramento por node), não para aplicações de negócio.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-logger
spec:
selector:
matchLabels:
app: node-logger
template:
metadata:
labels:
app: node-logger
spec:
containers:
- name: logger
image: fluent/fluent-bitJob
Roda uma tarefa até completar e para. Bom para migrations, processamento em batch.
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
spec:
template:
spec:
containers:
- name: migrate
image: minha-app:latest
command: ["npx", "prisma", "migrate", "deploy"]
restartPolicy: Never
backoffLimit: 3CronJob
Job com agendamento cron. Equivalente ao setup de cron + msmtp em VPS, nativo do cluster.
apiVersion: batch/v1
kind: CronJob
metadata:
name: relatorio-diario
spec:
schedule: "0 8,20 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: relatorio
image: minha-app:latest
command: ["node", "gerar-relatorio.js"]
restartPolicy: OnFailure7. Gerenciamento de aplicações
Helm
Gerenciador de pacotes do Kubernetes.
- Chart — pacote de templates YAML + metadados
- Values — arquivo
values.yamlcom variáveis que preenchem os templates - Release — instância instalada de um chart no cluster
helm create meu-app
helm install meu-app ./meu-app
helm upgrade meu-app ./meu-app --set replicaCount=5
helm uninstall meu-app
helm install meu-app ./meu-app -f values-prod.yamlKustomize
Sem templates — YAML base "puro" com patches por ambiente. Já embutido no kubectl.
base/
deployment.yaml
kustomization.yaml
overlays/
prod/
kustomization.yaml
patch-replicas.yamlkubectl apply -k overlays/prodQuando usar cada um: Helm para instalar dependências de terceiros (Traefik, Prometheus) ou lógica complexa de template; Kustomize para as próprias aplicações, com variações simples entre ambientes.
8. Segurança
RBAC (Role-Based Access Control)
Controla quem pode fazer o quê. Role = permissões dentro de um namespace. ClusterRole = permissões no cluster inteiro.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: leitor-pods
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-le-pods
namespace: default
subjects:
- kind: ServiceAccount
name: minha-app-sa
namespace: default
roleRef:
kind: Role
name: leitor-pods
apiGroup: rbac.authorization.k8s.ioServiceAccount
Identidade que um Pod usa para se autenticar contra a API do Kubernetes. Por padrão, todo Pod usa a ServiceAccount default do namespace, com permissões mínimas. Criar uma dedicada quando a aplicação precisa falar com a API (operators, apps que listam/criam recursos).
PodSecurityStandards
Restringe como um Pod pode rodar. Três níveis, aplicados por namespace: privileged, baseline, restricted.
kubectl label namespace default pod-security.kubernetes.io/enforce=restrictedsecurityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]9. Observabilidade
kubectl na prática
kubectl describe pod <nome> # eventos, motivo de erro — comando #1 pra debug
kubectl logs <nome> -f
kubectl logs <nome> --previous # logs da execução anterior, após crash
kubectl exec -it <nome> -- sh
kubectl top pods # precisa do metrics-serverErros comuns:
CrashLoopBackOff— container inicia e morre repetidamenteImagePullBackOff— falha ao baixar a imagemPending— Scheduler não conseguiu encaixar o Pod em nenhum node
Metrics Server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yamlNo kind, geralmente precisa desabilitar verificação TLS:
kubectl patch deployment metrics-server -n kube-system --type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'Prometheus + Grafana
Prometheus guarda histórico de métricas (série temporal); Grafana visualiza em dashboards.
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespace
kubectl port-forward -n monitoring svc/monitoring-grafana 3000:8010. CI/CD e GitOps
CI/CD tradicional vs GitOps
CI/CD tradicional: o pipeline aplica direto no cluster com credenciais guardadas no CI. GitOps: o Git é a única fonte de verdade — um agente dentro do cluster observa o repositório e aplica mudanças automaticamente. Mais seguro (sem credenciais de cluster expostas fora) e auditável.
ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yamlapiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: meu-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/usuario/meu-app-k8s
targetRevision: main
path: overlays/prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: trueselfHeal: true desfaz mudanças manuais feitas fora do Git, forçando tudo a passar pelo repositório.
Flux
Alternativa ao ArgoCD, mesmo conceito, sem UI própria. ArgoCD é mais indicado para quem está começando, pela interface visual.
Pipeline completo
- Push no código → CI builda imagem Docker → push pro registry (GHCR)
- CI atualiza a tag da imagem no repositório de manifests (separado do repo de código)
- ArgoCD detecta a mudança e aplica no cluster
11. Produção
Multi-node real (k3s em VPS)
Para VPS (Hetzner/Oracle), recomenda-se k3s em vez do K8s completo (kubeadm): distribuição leve, binário único, sem etcd pesado por padrão.
# node control-plane
curl -sfL https://get.k3s.io | sh -
cat /var/lib/rancher/k3s/server/node-token
# nodes worker
curl -sfL https://get.k3s.io | K3S_URL=https://<ip-do-server>:6443 \
K3S_TOKEN=<token> sh -Storage em produção
- Hetzner CSI — integra com Volumes da Hetzner Cloud
- Longhorn — storage distribuído entre nodes do próprio cluster, independente de provedor
helm repo add longhorn https://charts.longhorn.io
helm install longhorn longhorn/longhorn --namespace longhorn-system --create-namespaceBackup
- etcd — snapshot do estado do cluster (k3s já embute isso por padrão:
k3s etcd-snapshot save) - Aplicações e volumes — Velero, backup de recursos + volumes para storage externo (S3, B2)
velero backup create backup-diario --include-namespaces default
velero schedule create backup-diario --schedule="0 3 * * *" --include-namespaces defaultEstratégias de deploy
| Estratégia | Como funciona |
|---|---|
| Rolling update | Padrão do Deployment — troca Pods aos poucos, zero downtime |
| Blue-green | Sobe a versão nova em paralelo, testa, troca o Service, deleta a antiga. Rollback instantâneo |
| Canary | Libera a versão nova para uma fração do tráfego, observa métricas, aumenta gradualmente |
Canary/blue-green raramente são feitos manualmente — Argo Rollouts automatiza esse processo, substituindo o Deployment padrão por um controlador que gerencia métricas, promoção e rollback automático.