Skip to content

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")

ComponenteFunção
API ServerPorta de entrada — todo comando (kubectl) passa por ele
etcdBanco de dados que guarda o estado do cluster
SchedulerDecide em qual node um Pod vai rodar
Controller ManagerGarante que o estado real bate com o desejado (recria Pods que morrem, etc)

Node (worker, onde os containers rodam)

ComponenteFunção
kubeletAgente que conversa com o Control Plane e sobe os containers
kube-proxyGerencia rede/roteamento entre Pods
Container runtimecontainerd — 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
bash
# 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 nodes

2. 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.

yaml
apiVersion: v1
kind: Pod
metadata:
  name: meu-app
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80

ReplicaSet ​

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).

yaml
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: 80
bash
kubectl 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-app

Service ​

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 cluster
  • NodePort — expõe porta fixa em cada node
  • LoadBalancer — pede um LB externo (cloud)
yaml
apiVersion: v1
kind: Service
metadata:
  name: meu-app-svc
spec:
  selector:
    app: meu-app
  ports:
  - port: 80
    targetPort: 80
  type: ClusterIP

Namespace ​

Divisão lógica dentro do cluster — organiza recursos (dev, staging, prod) e isola nomes.

bash
kubectl create namespace dev
kubectl apply -f deployment.yaml -n dev

3. Configuração e dados ​

ConfigMap ​

Configurações não sensíveis (variáveis de ambiente, arquivos de config) fora da imagem.

yaml
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.

yaml
apiVersion: v1
kind: Secret
metadata:
  name: meu-app-secret
type: Opaque
stringData:
  DB_PASSWORD: "minhasenha123"
  JWT_SECRET: "supersecreto"

Uso em Deployment (ambos):

yaml
spec:
  containers:
  - name: nginx
    image: nginx:latest
    envFrom:
    - configMapRef:
        name: meu-app-config
    - secretRef:
        name: meu-app-secret

Volumes ​

  • 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)
yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: meu-app-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

No 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.

yaml
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: 80

Configurando Traefik no kind:

yaml
# 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: 443
bash
kind 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=NodePort

NetworkPolicy ​

Por padrão, todo Pod pode falar com todo Pod no cluster. NetworkPolicy é o "firewall" que restringe isso.

yaml
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: 5432

O kind usa kindnet por padrão, que não suporta NetworkPolicy. Para testar de verdade, trocar o CNI para Calico.

5. Escalonamento e recursos ​

Resource requests/limits ​

yaml
resources:
  requests:
    memory: "128Mi"
    cpu: "250m"
  limits:
    memory: "256Mi"
    cpu: "500m"
  • requests — usado pelo Scheduler pra decidir em qual node o Pod cabe
  • limits — 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.

bash
kubectl autoscale deployment meu-app --cpu-percent=70 --min=2 --max=10
yaml
apiVersion: 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: 70

Probes ​

  • 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.
yaml
livenessProbe:
  httpGet:
    path: /
    port: 80
  initialDelaySeconds: 5
  periodSeconds: 10
readinessProbe:
  httpGet:
    path: /
    port: 80
  initialDelaySeconds: 5
  periodSeconds: 5

6. 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.

yaml
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: 5Gi

O 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.

yaml
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-bit

Job ​

Roda uma tarefa até completar e para. Bom para migrations, processamento em batch.

yaml
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: 3

CronJob ​

Job com agendamento cron. Equivalente ao setup de cron + msmtp em VPS, nativo do cluster.

yaml
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: OnFailure

7. Gerenciamento de aplicações ​

Helm ​

Gerenciador de pacotes do Kubernetes.

  • Chart — pacote de templates YAML + metadados
  • Values — arquivo values.yaml com variáveis que preenchem os templates
  • Release — instância instalada de um chart no cluster
bash
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.yaml

Kustomize ​

Sem templates — YAML base "puro" com patches por ambiente. Já embutido no kubectl.

base/
  deployment.yaml
  kustomization.yaml
overlays/
  prod/
    kustomization.yaml
    patch-replicas.yaml
bash
kubectl apply -k overlays/prod

Quando 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.

yaml
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.io

ServiceAccount ​

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.

bash
kubectl label namespace default pod-security.kubernetes.io/enforce=restricted
yaml
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
containers:
- name: app
  securityContext:
    allowPrivilegeEscalation: false
    readOnlyRootFilesystem: true
    capabilities:
      drop: ["ALL"]

9. Observabilidade ​

kubectl na prática ​

bash
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-server

Erros comuns:

  • CrashLoopBackOff — container inicia e morre repetidamente
  • ImagePullBackOff — falha ao baixar a imagem
  • Pending — Scheduler não conseguiu encaixar o Pod em nenhum node

Metrics Server ​

bash
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

No kind, geralmente precisa desabilitar verificação TLS:

bash
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.

bash
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:80

10. 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 ​

bash
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
yaml
apiVersion: 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: true

selfHeal: 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 ​

  1. Push no código → CI builda imagem Docker → push pro registry (GHCR)
  2. CI atualiza a tag da imagem no repositório de manifests (separado do repo de código)
  3. 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.

bash
# 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
bash
helm repo add longhorn https://charts.longhorn.io
helm install longhorn longhorn/longhorn --namespace longhorn-system --create-namespace

Backup ​

  • 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)
bash
velero backup create backup-diario --include-namespaces default
velero schedule create backup-diario --schedule="0 3 * * *" --include-namespaces default

Estratégias de deploy ​

EstratégiaComo funciona
Rolling updatePadrão do Deployment — troca Pods aos poucos, zero downtime
Blue-greenSobe a versão nova em paralelo, testa, troca o Service, deleta a antiga. Rollback instantâneo
CanaryLibera 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.

Released under the License MIT. Versão 1.0.0