DevOps

Kubernetes

Orquestração de containers — Pods, Deployments, Services, Ingress e muito mais.

1. Conceitos Fundamentais

O que é Kubernetes

Kubernetes (K8s) é uma plataforma open-source para orquestração de containers. Ele automatiza implantação, escalonamento e gerenciamento de aplicações containerizadas.

Arquitetura do Cluster

┌─────────────────────────────────────────────────────────┐
│                     CONTROL PLANE                       │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────┐ │
│  │  kube-   │  │  kube-   │  │  kube-   │  │  etcd  │ │
│  │ apiserver│  │controller│  │scheduler │  │        │ │
│  └──────────┘  └──────────┘  └──────────┘  └────────┘ │
└─────────────────────────────────────────────────────────┘
           │                │                │
┌──────────┴────┐  ┌────────┴──────┐  ┌─────┴─────────┐
│  WORKER NODE  │  │  WORKER NODE  │  │  WORKER NODE  │
│ ┌───────────┐ │  │ ┌───────────┐ │  │ ┌───────────┐ │
│ │  kubelet  │ │  │ │  kubelet  │ │  │ │  kubelet  │ │
│ │ kube-proxy│ │  │ │ kube-proxy│ │  │ │ kube-proxy│ │
│ │  runtime  │ │  │ │  runtime  │ │  │ │  runtime  │ │
│ └───────────┘ │  │ └───────────┘ │  │ └───────────┘ │
└───────────────┘  └───────────────┘  └───────────────┘

Componentes do Control Plane

ComponenteFunção
kube-apiserverPonto de entrada para todas as operações (REST API). Valida e processa requests
etcdBanco de dados chave-valor distribuído. Armazena todo o estado do cluster
kube-schedulerDecide em qual Node um Pod será executado com base em recursos e constraints
kube-controller-managerRoda controllers (Deployment, ReplicaSet, Node, etc.) em loop de reconciliação
cloud-controller-managerIntegra com APIs de cloud (AWS, GCP, Azure) para LoadBalancer, volumes, etc.

Componentes do Worker Node

ComponenteFunção
kubeletAgente que garante que os containers estão rodando conforme spec do Pod
kube-proxyMantém regras de rede (iptables/ipvs) para roteamento de Services
Container RuntimeExecuta containers (containerd, CRI-O, Docker)

Objetos Principais

Namespace          — Isolamento lógico de recursos dentro do cluster
Pod                — Menor unidade executável: um ou mais containers
ReplicaSet         — Garante N réplicas de um Pod rodando
Deployment         — Gerencia ReplicaSets com rollout/rollback
StatefulSet        — Deployment com identidade estável (banco de dados, etc.)
DaemonSet          — Um Pod por Node (monitoring agents, log collectors)
Job                — Execução até conclusão (batch processing)
CronJob            — Job agendado com expressão cron
Service            — Exposição estável de Pods via selector
Ingress            — Roteamento HTTP/HTTPS para Services
ConfigMap          — Configurações não sensíveis em chave-valor
Secret             — Dados sensíveis codificados em base64
PersistentVolume   — Storage provisionado no cluster
PVC                — Requisição de storage por um Pod
ServiceAccount     — Identidade para Pods interagirem com a API

Namespaces Built-in

kubectl get namespaces

# NAME              STATUS   AGE
# default           Active   30d    # recursos sem namespace explícito
# kube-system       Active   30d    # componentes internos do K8s
# kube-public       Active   30d    # recursos públicos (leitura por todos)
# kube-node-lease   Active   30d    # heartbeats dos nodes

2. kubectl Essencial

Configuração e Contextos

# Ver config atual
kubectl config view
kubectl config current-context

# Listar contextos disponíveis
kubectl config get-contexts

# Trocar de contexto (cluster)
kubectl config use-context prod-cluster
kubectl config use-context staging-cluster

# Definir namespace padrão para o contexto atual
kubectl config set-context --current --namespace=meu-namespace

# Instalar kubectx + kubens (facilita troca de contexto/namespace)
# brew install kubectx
kubectx prod-cluster
kubens meu-namespace

get — Listar Recursos

# Sintaxe: kubectl get <recurso> [nome] [flags]

# Listar todos os pods no namespace atual
kubectl get pods

# Listar com mais informações (IP, Node, status detalhado)
kubectl get pods -o wide

# Listar em todos os namespaces
kubectl get pods --all-namespaces
kubectl get pods -A

# Listar em namespace específico
kubectl get pods -n kube-system

# Output em YAML (ver spec completo)
kubectl get pod meu-pod -o yaml

# Output em JSON
kubectl get pod meu-pod -o json

# Output customizado com jsonpath
kubectl get pods -o jsonpath='{.items[*].metadata.name}'

# Listar com labels
kubectl get pods --show-labels

# Filtrar por label
kubectl get pods -l app=frontend
kubectl get pods -l app=frontend,env=prod

# Watch (atualização em tempo real)
kubectl get pods -w

# Listar múltiplos recursos de uma vez
kubectl get pods,services,deployments

# Listar todos os recursos de um namespace
kubectl get all -n meu-namespace

# Listar nodes do cluster
kubectl get nodes
kubectl get nodes -o wide

describe — Detalhes e Eventos

# Ver detalhes completos de um recurso (inclui Events — muito útil para debug)
kubectl describe pod meu-pod
kubectl describe node worker-1
kubectl describe service meu-service
kubectl describe deployment meu-deploy
kubectl describe ingress meu-ingress

# Descrever todos os pods com label específica
kubectl describe pods -l app=backend

apply / create / delete — Gerenciar Recursos

# Aplicar manifest (cria ou atualiza — idempotente, PREFERIDO)
kubectl apply -f deployment.yaml
kubectl apply -f ./k8s/                  # aplica todos os arquivos do diretório
kubectl apply -f ./k8s/ --recursive      # aplica recursivamente
kubectl apply -f https://raw.githubusercontent.com/.../manifest.yaml

# Criar recurso (falha se já existe)
kubectl create -f pod.yaml
kubectl create namespace producao
kubectl create configmap minha-config --from-literal=chave=valor

# Deletar recursos
kubectl delete -f deployment.yaml
kubectl delete pod meu-pod
kubectl delete pods -l app=frontend
kubectl delete namespace staging               # deleta tudo no namespace
kubectl delete pod meu-pod --force --grace-period=0  # forçar deleção imediata

exec — Executar Comandos em Containers

# Shell interativo no container
kubectl exec -it meu-pod -- /bin/bash
kubectl exec -it meu-pod -- /bin/sh       # quando bash não disponível

# Container específico em Pod multi-container
kubectl exec -it meu-pod -c sidecar-container -- /bin/bash

# Executar comando único (sem TTY)
kubectl exec meu-pod -- ls /app
kubectl exec meu-pod -- env
kubectl exec meu-pod -- cat /etc/config/app.yaml

# Namespace específico
kubectl exec -it meu-pod -n producao -- /bin/bash

logs — Ver Logs

# Logs do pod (container principal)
kubectl logs meu-pod

# Seguir logs em tempo real
kubectl logs -f meu-pod

# Container específico
kubectl logs meu-pod -c meu-container

# Últimas N linhas
kubectl logs meu-pod --tail=100

# Logs dos últimos 30 minutos
kubectl logs meu-pod --since=30m

# Logs de pod anterior (após crash/restart)
kubectl logs meu-pod --previous
kubectl logs meu-pod -p

# Logs de todos os pods com label
kubectl logs -l app=backend --all-containers=true

# Timestamps nos logs
kubectl logs meu-pod --timestamps

port-forward — Acesso Local

# Encaminhar porta local para porta do pod
kubectl port-forward pod/meu-pod 8080:80
# Agora: curl localhost:8080 → pod:80

# Via Service
kubectl port-forward service/meu-service 8080:80

# Via Deployment
kubectl port-forward deployment/meu-deploy 8080:80

# Múltiplas portas
kubectl port-forward pod/meu-pod 8080:80 9090:9090

# Bind em todas as interfaces (acessível na rede)
kubectl port-forward pod/meu-pod 8080:80 --address=0.0.0.0

cp — Copiar Arquivos

# Copiar do pod para local
kubectl cp meu-pod:/app/logs/app.log ./app.log
kubectl cp meu-pod:/etc/config/ ./config-backup/

# Copiar do local para pod
kubectl cp ./config.yaml meu-pod:/app/config/config.yaml

# Container específico
kubectl cp meu-pod:/app/data ./data -c meu-container

# Namespace específico
kubectl cp meu-pod:/app/log.txt ./log.txt -n producao

top — Monitorar Recursos

# Uso de CPU/memória dos nodes
kubectl top nodes

# Uso de CPU/memória dos pods
kubectl top pods
kubectl top pods -n kube-system
kubectl top pods --all-namespaces
kubectl top pods --sort-by=cpu
kubectl top pods --sort-by=memory

# Pod específico com containers
kubectl top pod meu-pod --containers

rollout — Gerenciar Rollouts

# Status do rollout
kubectl rollout status deployment/meu-deploy
kubectl rollout status deployment/meu-deploy -w   # aguardar conclusão

# Histórico de rollouts
kubectl rollout history deployment/meu-deploy

# Ver detalhes de uma revisão específica
kubectl rollout history deployment/meu-deploy --revision=3

# Desfazer último rollout (rollback)
kubectl rollout undo deployment/meu-deploy

# Rollback para revisão específica
kubectl rollout undo deployment/meu-deploy --to-revision=2

# Pausar rollout (zero-downtime maintenance)
kubectl rollout pause deployment/meu-deploy

# Retomar rollout pausado
kubectl rollout resume deployment/meu-deploy

# Forçar rollout (restart sem mudar imagem)
kubectl rollout restart deployment/meu-deploy

scale — Escalar Réplicas

# Escalar deployment para N réplicas
kubectl scale deployment meu-deploy --replicas=5
kubectl scale deployment meu-deploy --replicas=0   # "desligar" sem deletar

# Escalar apenas se réplicas atuais batem com expectativa
kubectl scale deployment meu-deploy --replicas=3 --current-replicas=2

# Escalar múltiplos deployments
kubectl scale deployment frontend backend --replicas=3

# Escalar ReplicaSet
kubectl scale replicaset meu-rs --replicas=4

edit / patch — Editar Recursos

# Abrir editor (usa $EDITOR ou vi por padrão)
kubectl edit deployment meu-deploy
kubectl edit configmap minha-config

# Patch estratégico (merge parcial)
kubectl patch deployment meu-deploy -p '{"spec":{"replicas":3}}'

# Patch com arquivo
kubectl patch deployment meu-deploy --patch-file=patch.yaml

# Patch de substituição total (replace)
kubectl patch deployment meu-deploy --type=merge -p '{"spec":{"replicas":5}}'

# Patch JSON (path específico)
kubectl patch pod meu-pod --type='json' \
  -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"nginx:1.25"}]'

Comandos Úteis Adicionais

# Ver API resources disponíveis
kubectl api-resources
kubectl api-resources --namespaced=true

# Verificar permissões do usuário atual
kubectl auth can-i create pods
kubectl auth can-i delete deployments -n producao
kubectl auth can-i '*' '*'   # admin check

# Dry-run (simular sem aplicar)
kubectl apply -f deployment.yaml --dry-run=client
kubectl apply -f deployment.yaml --dry-run=server

# Gerar YAML de um recurso imperativo
kubectl create deployment nginx --image=nginx --dry-run=client -o yaml > deploy.yaml

# Diff entre estado atual e novo manifest
kubectl diff -f deployment.yaml

# Explain — documentação de campos
kubectl explain pod.spec.containers
kubectl explain deployment.spec.strategy

# Eventos do namespace
kubectl get events --sort-by='.lastTimestamp'
kubectl get events -n meu-namespace

# Forçar deleção de namespace preso em Terminating
kubectl get namespace stuck-ns -o json | \
  jq '.spec.finalizers = []' | \
  kubectl replace --raw /api/v1/namespaces/stuck-ns/finalize -f -

3. Pods

Manifest Completo de Pod

apiVersion: v1
kind: Pod
metadata:
  name: meu-app
  namespace: producao
  labels:
    app: meu-app
    version: "1.2.0"
    env: prod
  annotations:
    description: "Pod de exemplo com configurações completas"
    prometheus.io/scrape: "true"
    prometheus.io/port: "8080"
spec:
  # Node onde o pod vai rodar (opcional)
  nodeName: worker-1

  # Afinidade de node por labels
  nodeSelector:
    kubernetes.io/os: linux
    node-type: high-memory

  # Restart policy (Always | OnFailure | Never)
  restartPolicy: Always

  # Tempo para terminar graciosamente (padrão: 30s)
  terminationGracePeriodSeconds: 60

  # Service Account para acesso à API do K8s
  serviceAccountName: meu-service-account

  # Não montar token automático do service account
  automountServiceAccountToken: false

  # Security context no nível do Pod
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault

  # Volumes declarados para uso pelos containers
  volumes:
    - name: config-volume
      configMap:
        name: minha-config
    - name: secret-volume
      secret:
        secretName: meu-secret
        defaultMode: 0400   # permissões dos arquivos
    - name: dados-temp
      emptyDir: {}
    - name: host-logs
      hostPath:
        path: /var/log/app
        type: DirectoryOrCreate

  # Init containers rodam antes dos containers principais (em ordem)
  initContainers:
    - name: wait-for-db
      image: busybox:1.36
      command: ['sh', '-c', 'until nc -z postgres-service 5432; do sleep 2; done']

    - name: run-migrations
      image: meu-app:1.2.0
      command: ['./migrate.sh']
      env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: url

  containers:
    - name: app
      image: meu-app:1.2.0
      imagePullPolicy: IfNotPresent  # Always | Never | IfNotPresent

      # Portas expostas (informativo, não cria regras de rede)
      ports:
        - name: http
          containerPort: 8080
          protocol: TCP
        - name: metrics
          containerPort: 9090
          protocol: TCP

      # Security context no nível do container (sobrescreve Pod-level)
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]

      # Variáveis de ambiente
      env:
        - name: APP_ENV
          value: "production"
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name    # metadata do próprio pod
        - name: POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: CPU_REQUEST
          valueFrom:
            resourceFieldRef:
              containerName: app
              resource: requests.cpu

      # Todas as chaves de um ConfigMap como env vars
      envFrom:
        - configMapRef:
            name: minha-config
        - secretRef:
            name: meu-secret

      # Montar volumes
      volumeMounts:
        - name: config-volume
          mountPath: /etc/config
          readOnly: true
        - name: secret-volume
          mountPath: /etc/secrets
          readOnly: true
        - name: dados-temp
          mountPath: /tmp

      # Recursos: requests (mínimo garantido) e limits (máximo permitido)
      resources:
        requests:
          cpu: "250m"       # 0.25 vCPU
          memory: "256Mi"
        limits:
          cpu: "500m"       # 0.5 vCPU
          memory: "512Mi"

      # Liveness probe: se falhar, container é reiniciado
      livenessProbe:
        httpGet:
          path: /health/live
          port: 8080
          httpHeaders:
            - name: Custom-Header
              value: liveness
        initialDelaySeconds: 30   # aguarda antes da primeira verificação
        periodSeconds: 10          # intervalo entre verificações
        timeoutSeconds: 5          # timeout de cada verificação
        failureThreshold: 3        # falhas consecutivas para reiniciar
        successThreshold: 1        # sucessos para considerar saudável

      # Readiness probe: se falhar, pod é removido do Service (sem tráfego)
      readinessProbe:
        httpGet:
          path: /health/ready
          port: 8080
        initialDelaySeconds: 10
        periodSeconds: 5
        timeoutSeconds: 3
        failureThreshold: 3
        successThreshold: 1

      # Startup probe: para apps lentos na inicialização
      # Desativa liveness/readiness até o app iniciar
      startupProbe:
        httpGet:
          path: /health/live
          port: 8080
        failureThreshold: 30    # 30 * 10s = 5 min para iniciar
        periodSeconds: 10

      # Lifecycle hooks
      lifecycle:
        postStart:
          exec:
            command: ["/bin/sh", "-c", "echo 'Container started' >> /tmp/log"]
        preStop:
          exec:
            command: ["/bin/sh", "-c", "sleep 10"]  # drenagem de conexões

Pod Multi-Container (Sidecar Pattern)

apiVersion: v1
kind: Pod
metadata:
  name: app-com-sidecar
spec:
  volumes:
    - name: logs-compartilhados
      emptyDir: {}

  containers:
    # Container principal: aplicação
    - name: app
      image: meu-app:latest
      volumeMounts:
        - name: logs-compartilhados
          mountPath: /var/log/app

    # Sidecar: agente de logs (coleta e envia logs da app)
    - name: log-shipper
      image: fluent/fluent-bit:latest
      volumeMounts:
        - name: logs-compartilhados
          mountPath: /var/log/app
          readOnly: true
      env:
        - name: FLUENTBIT_OUTPUT
          value: "elasticsearch"

    # Sidecar: proxy (Envoy, Istio, etc.)
    - name: envoy-proxy
      image: envoyproxy/envoy:v1.28
      ports:
        - containerPort: 9901   # admin
        - containerPort: 10000  # listener

Tipos de Probes

# HTTP GET probe
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
    scheme: HTTPS  # HTTP (padrão) ou HTTPS

# TCP Socket probe (conexão na porta)
readinessProbe:
  tcpSocket:
    port: 5432

# Exec probe (exit code 0 = saudável)
livenessProbe:
  exec:
    command:
      - /bin/sh
      - -c
      - "redis-cli ping | grep PONG"

# gRPC probe (requer servidor implementar Health protocol)
livenessProbe:
  grpc:
    port: 50051
    service: liveness

4. Deployments

Manifest Completo de Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-api
  namespace: producao
  labels:
    app: backend-api
    version: "2.0.0"
spec:
  # Número de réplicas desejadas
  replicas: 3

  # Quantas revisões manter no histórico (para rollback)
  revisionHistoryLimit: 10

  # Tempo mínimo que pod deve ficar Running antes de ser considerado Available
  minReadySeconds: 5

  # Seleciona os Pods gerenciados por este Deployment
  selector:
    matchLabels:
      app: backend-api

  # Estratégia de atualização
  strategy:
    type: RollingUpdate   # ou Recreate
    rollingUpdate:
      maxSurge: 1         # pods extras acima do desejado durante update
      maxUnavailable: 0   # pods que podem ficar indisponíveis (0 = zero-downtime)

  # Template dos pods
  template:
    metadata:
      labels:
        app: backend-api    # deve bater com selector.matchLabels
        version: "2.0.0"
      annotations:
        # Forçar rollout quando configmap muda (add checksum)
        checksum/config: "abc123def456"
    spec:
      # Anti-afinidade: evita dois pods no mesmo node
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app: backend-api
                topologyKey: kubernetes.io/hostname

      containers:
        - name: backend-api
          image: meu-registry/backend-api:2.0.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "1000m"
              memory: "1Gi"
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 20
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10

Estratégia Recreate

# Derruba TODOS os pods antigos antes de criar os novos
# Causa downtime — usar apenas quando versões não podem coexistir
strategy:
  type: Recreate

Estratégia Blue-Green (manual)

# Blue = versão atual (v1), Green = nova versão (v2)

# 1. Deploy da versão green com label separada
kubectl apply -f deployment-v2.yaml

# 2. Verificar se green está saudável
kubectl rollout status deployment/app-v2

# 3. Mudar o selector do Service para apontar para green
kubectl patch service meu-service -p '{"spec":{"selector":{"version":"v2"}}}'

# 4. Se der problema, reverter o selector
kubectl patch service meu-service -p '{"spec":{"selector":{"version":"v1"}}}'

# 5. Após validação, deletar blue
kubectl delete deployment app-v1

Horizontal Pod Autoscaler (HPA)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: backend-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: backend-api
  minReplicas: 2
  maxReplicas: 10
  metrics:
    # Escalar por CPU
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70   # manter CPU média em 70%
    # Escalar por memória
    - type: Resource
      resource:
        name: memory
        target:
          type: AverageValue
          averageValue: 400Mi
    # Escalar por métrica customizada (requer Prometheus Adapter)
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "100"
# Criar HPA de forma imperativa
kubectl autoscale deployment backend-api --cpu-percent=70 --min=2 --max=10

# Ver status do HPA
kubectl get hpa
kubectl describe hpa backend-hpa

5. Services

ClusterIP (padrão — acesso interno ao cluster)

apiVersion: v1
kind: Service
metadata:
  name: backend-service
  namespace: producao
spec:
  type: ClusterIP           # padrão, pode omitir
  selector:
    app: backend-api        # roteia para pods com esta label
  ports:
    - name: http
      port: 80              # porta do Service (usada pelos clientes)
      targetPort: 8080      # porta no container
      protocol: TCP
    - name: metrics
      port: 9090
      targetPort: 9090
# Acessar ClusterIP de dentro do cluster:
# <service-name>.<namespace>.svc.cluster.local
curl http://backend-service.producao.svc.cluster.local/api/health
# Dentro do mesmo namespace:
curl http://backend-service/api/health

NodePort (acesso externo via porta do Node)

apiVersion: v1
kind: Service
metadata:
  name: frontend-nodeport
spec:
  type: NodePort
  selector:
    app: frontend
  ports:
    - port: 80
      targetPort: 3000
      nodePort: 30080   # porta no node (range: 30000-32767)
      # se omitir nodePort, K8s escolhe automaticamente
# Acessar via IP de qualquer node + nodePort
curl http://<NODE_IP>:30080

LoadBalancer (provisionado pelo cloud provider)

apiVersion: v1
kind: Service
metadata:
  name: api-loadbalancer
  annotations:
    # AWS NLB ao invés de CLB
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    # GCP: IP estático
    cloud.google.com/load-balancer-type: "External"
spec:
  type: LoadBalancer
  selector:
    app: api
  ports:
    - port: 443
      targetPort: 8443
  # IP externo estático (se disponível)
  loadBalancerIP: "34.100.200.50"

ExternalName (alias DNS externo)

# Mapeia um Service para um DNS externo
# Útil para integrar serviços externos como se fossem internos
apiVersion: v1
kind: Service
metadata:
  name: banco-externo
  namespace: producao
spec:
  type: ExternalName
  externalName: banco.empresa.com.br  # CNAME

Headless Service (sem ClusterIP)

# Sem proxy — DNS retorna IPs dos Pods diretamente
# Necessário para StatefulSets e service discovery direto
apiVersion: v1
kind: Service
metadata:
  name: postgres-headless
spec:
  clusterIP: None    # headless
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432
# DNS resolve para lista de IPs dos pods:
# postgres-headless.namespace.svc.cluster.local → [10.0.0.1, 10.0.0.2, ...]
# Pods do StatefulSet recebem DNS individual:
# postgres-0.postgres-headless.namespace.svc.cluster.local

6. ConfigMaps e Secrets

ConfigMap — Criação

# Criação imperativa — literais
kubectl create configmap app-config \
  --from-literal=APP_ENV=production \
  --from-literal=LOG_LEVEL=info \
  --from-literal=MAX_CONNECTIONS=100

# A partir de arquivo de properties
kubectl create configmap app-config --from-file=app.properties

# A partir de arquivo env
kubectl create configmap app-config --from-env-file=.env

# A partir de diretório (cada arquivo vira uma chave)
kubectl create configmap configs --from-file=./configs/

# Ver o configmap criado
kubectl get configmap app-config -o yaml

ConfigMap — Manifest

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: producao
data:
  # Valores simples (chave-valor)
  APP_ENV: "production"
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"

  # Arquivo completo como valor (bloco literal YAML)
  app.properties: |
    server.port=8080
    spring.datasource.url=jdbc:postgresql://postgres:5432/mydb
    cache.ttl=3600

  nginx.conf: |
    server {
        listen 80;
        location / {
            proxy_pass http://backend:8080;
        }
    }

Secret — Criação

# Criação imperativa
kubectl create secret generic db-secret \
  --from-literal=username=admin \
  --from-literal=password='S3cr3t!P@ss'

# A partir de arquivo (ex: certificado TLS)
kubectl create secret tls meu-tls \
  --cert=tls.crt \
  --key=tls.key

# Docker registry credentials
kubectl create secret docker-registry registry-credentials \
  --docker-server=register.homelab-cloud.com \
  --docker-username=deploy \
  --docker-password=minhasenha \
  --docker-email=deploy@empresa.com

Secret — Manifest

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
  namespace: producao
type: Opaque   # genérico (outros: kubernetes.io/tls, kubernetes.io/dockerconfigjson)
# Valores DEVEM ser base64 encoded
data:
  username: YWRtaW4=      # echo -n 'admin' | base64
  password: UzNjcjN0IQ==  # echo -n 'S3cr3t!' | base64

# Alternativa: stringData aceita valores em texto plano (K8s faz o encoding)
stringData:
  api-key: "minha-api-key-secreta"
  config.yaml: |
    database:
      host: postgres
      password: senha123

Usando ConfigMaps e Secrets em Pods

spec:
  volumes:
    - name: config-vol
      configMap:
        name: app-config
        # Montar apenas chaves específicas
        items:
          - key: app.properties
            path: app.properties   # nome do arquivo no mount
          - key: nginx.conf
            path: nginx/nginx.conf
    - name: secret-vol
      secret:
        secretName: db-secret
        defaultMode: 0400

  containers:
    - name: app
      # Todas as chaves do ConfigMap como env vars
      envFrom:
        - configMapRef:
            name: app-config
        - secretRef:
            name: db-secret

      # Env vars individuais
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password
        - name: APP_ENV
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: APP_ENV

      # Montar como arquivos
      volumeMounts:
        - name: config-vol
          mountPath: /etc/app/config
          readOnly: true
        - name: secret-vol
          mountPath: /etc/secrets
          readOnly: true

Sealed Secrets (GitOps-friendly)

# Instalar sealed-secrets controller
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system

# Instalar kubeseal CLI
# brew install kubeseal

# Criar SealedSecret (pode commitar no Git!)
kubectl create secret generic db-secret \
  --from-literal=password=S3cr3t \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-db-secret.yaml

# Aplicar (controller decripta e cria o Secret real)
kubectl apply -f sealed-db-secret.yaml

7. Ingress

nginx-ingress Controller — Instalação

# Via Helm (recomendado)
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace \
  --set controller.replicaCount=2

Ingress Básico

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: meu-ingress
  namespace: producao
  annotations:
    # Especifica o IngressClass (nginx, traefik, etc.)
    kubernetes.io/ingress.class: "nginx"
    # Redirect HTTP → HTTPS
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    # Tamanho máximo do body
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    # Timeout
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
    # Rate limiting
    nginx.ingress.kubernetes.io/limit-rps: "10"
spec:
  ingressClassName: nginx

  # TLS
  tls:
    - hosts:
        - app.meusite.com.br
        - api.meusite.com.br
      secretName: tls-meusite-secret   # Secret tipo kubernetes.io/tls

  rules:
    # Host-based routing
    - host: app.meusite.com.br
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

    - host: api.meusite.com.br
      http:
        paths:
          # Path-based routing
          - path: /v1
            pathType: Prefix
            backend:
              service:
                name: api-v1-service
                port:
                  number: 8080
          - path: /v2
            pathType: Prefix
            backend:
              service:
                name: api-v2-service
                port:
                  number: 8080

TLS com cert-manager (Let’s Encrypt)

# Instalar cert-manager
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true
# ClusterIssuer para Let's Encrypt
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: ops@empresa.com.br
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            class: nginx

---
# Ingress com TLS automático via cert-manager
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
    - hosts:
        - app.meusite.com.br
      secretName: app-tls-cert   # cert-manager cria e renova automaticamente
  rules:
    - host: app.meusite.com.br
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 80

Anotações Nginx Comuns

annotations:
  # Rewrite de URL
  nginx.ingress.kubernetes.io/rewrite-target: /$2

  # CORS
  nginx.ingress.kubernetes.io/enable-cors: "true"
  nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.meusite.com.br"

  # Autenticação básica
  nginx.ingress.kubernetes.io/auth-type: basic
  nginx.ingress.kubernetes.io/auth-secret: basic-auth-secret
  nginx.ingress.kubernetes.io/auth-realm: "Área Restrita"

  # Whitelist de IPs
  nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,192.168.0.0/16"

  # Backend protocol
  nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"

  # Canary release (10% do tráfego para nova versão)
  nginx.ingress.kubernetes.io/canary: "true"
  nginx.ingress.kubernetes.io/canary-weight: "10"

  # Session affinity (sticky sessions)
  nginx.ingress.kubernetes.io/affinity: "cookie"
  nginx.ingress.kubernetes.io/session-cookie-name: "route"

8. Volumes e Storage

emptyDir e hostPath

spec:
  volumes:
    # emptyDir: criado quando pod inicia, destruído quando pod termina
    # Compartilhado entre containers do mesmo pod
    - name: temp-storage
      emptyDir: {}
    - name: temp-memory
      emptyDir:
        medium: Memory    # tmpfs (RAM) — mais rápido mas consome memória
        sizeLimit: 512Mi

    # hostPath: monta diretório do node no pod
    # EVITAR em produção — acopla ao node, problema de segurança
    - name: host-logs
      hostPath:
        path: /var/log/containers
        type: Directory   # Directory | File | DirectoryOrCreate | FileOrCreate | Socket

PersistentVolume (PV) — Provisionamento Manual

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-dados-postgres
spec:
  capacity:
    storage: 50Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce       # RWO: um node por vez (discos)
    # - ReadOnlyMany      # ROX: múltiplos nodes leitura
    # - ReadWriteMany     # RWX: múltiplos nodes leitura/escrita (NFS, EFS)
  persistentVolumeReclaimPolicy: Retain   # Retain | Recycle | Delete
  storageClassName: manual
  hostPath:                # para dev local (minikube)
    path: /data/postgres

PersistentVolumeClaim (PVC)

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-postgres-dados
  namespace: producao
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ssd-premium   # deve existir no cluster
  resources:
    requests:
      storage: 20Gi

---
# Usando o PVC em um Pod
spec:
  volumes:
    - name: postgres-storage
      persistentVolumeClaim:
        claimName: pvc-postgres-dados
  containers:
    - name: postgres
      image: postgres:16
      volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data

StorageClass — Provisionamento Dinâmico

# StorageClass para AWS EBS (gp3)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ssd-premium
  annotations:
    storageclass.kubernetes.io/is-default-class: "false"
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer   # aguarda pod ser agendado
reclaimPolicy: Delete
allowVolumeExpansion: true
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"

---
# StorageClass para GCP Persistent Disk
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gcp-ssd
provisioner: pd.csi.storage.gke.io
parameters:
  type: pd-ssd
  replication-type: regional-pd
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
# Verificar StorageClasses disponíveis
kubectl get storageclass
kubectl get sc

# Ver PVs e PVCs
kubectl get pv
kubectl get pvc -A

# Expandir PVC (requer allowVolumeExpansion: true na StorageClass)
kubectl patch pvc meu-pvc -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'

9. StatefulSets

Quando usar StatefulSet vs Deployment

CaracterísticaDeploymentStatefulSet
Identidade do PodAleatóriaEstável e previsível (pod-0, pod-1, …)
Ordem de criaçãoSimultâneaSequencial (0 → 1 → 2)
Ordem de deleçãoSimultâneaReversa (2 → 1 → 0)
StorageCompartilhado ou sem estadoStorage dedicado por pod
DNSService genéricoDNS individual por pod
Casos de usoAplicações statelessBancos, Kafka, ZooKeeper, Redis Cluster

Manifest de StatefulSet — PostgreSQL com Replicação

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: banco
spec:
  serviceName: postgres-headless   # OBRIGATÓRIO: headless service para DNS
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      # Init container para configurar réplicas
      initContainers:
        - name: init-postgres
          image: postgres:16
          command:
            - bash
            - "-c"
            - |
              set -ex
              # Determinar se é master ou replica pelo ordinal
              [[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
              ordinal=${BASH_REMATCH[1]}
              if [[ $ordinal -eq 0 ]]; then
                echo "Iniciando como PRIMARY"
                cp /mnt/config-map/primary.conf /etc/postgresql/postgresql.conf
              else
                echo "Iniciando como REPLICA"
                cp /mnt/config-map/replica.conf /etc/postgresql/postgresql.conf
              fi
          volumeMounts:
            - name: config-map
              mountPath: /mnt/config-map
            - name: conf
              mountPath: /etc/postgresql

      containers:
        - name: postgres
          image: postgres:16
          env:
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: password
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
            - name: conf
              mountPath: /etc/postgresql
          resources:
            requests:
              cpu: "500m"
              memory: "1Gi"
            limits:
              cpu: "2"
              memory: "4Gi"
          readinessProbe:
            exec:
              command: ["pg_isready", "-U", "postgres"]
            initialDelaySeconds: 10
            periodSeconds: 5

      volumes:
        - name: config-map
          configMap:
            name: postgres-config
        - name: conf
          emptyDir: {}

  # volumeClaimTemplates: cria um PVC dedicado por pod automaticamente
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: ssd-premium
        resources:
          requests:
            storage: 20Gi

---
# Headless Service obrigatório para StatefulSet
apiVersion: v1
kind: Service
metadata:
  name: postgres-headless
  namespace: banco
spec:
  clusterIP: None
  selector:
    app: postgres
  ports:
    - port: 5432

---
# Service regular para acesso ao primary (pod-0)
apiVersion: v1
kind: Service
metadata:
  name: postgres-primary
  namespace: banco
spec:
  selector:
    app: postgres
    statefulset.kubernetes.io/pod-name: postgres-0
  ports:
    - port: 5432
# DNS dos pods do StatefulSet:
# postgres-0.postgres-headless.banco.svc.cluster.local
# postgres-1.postgres-headless.banco.svc.cluster.local
# postgres-2.postgres-headless.banco.svc.cluster.local

# Verificar pods e PVCs criados
kubectl get pods -n banco
kubectl get pvc -n banco

# Escalar StatefulSet (cria/destrói em ordem)
kubectl scale statefulset postgres --replicas=5 -n banco

10. DaemonSet, Jobs e CronJobs

DaemonSet — Um Pod por Node

# Casos de uso: log collectors, monitoring agents, network plugins, security scanners
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: node-exporter
  template:
    metadata:
      labels:
        app: node-exporter
    spec:
      # Tolerar todos os taints (inclusive master nodes)
      tolerations:
        - operator: Exists
          effect: NoSchedule

      # Acesso ao host (necessário para métricas do sistema)
      hostNetwork: true
      hostPID: true

      containers:
        - name: node-exporter
          image: prom/node-exporter:v1.7.0
          ports:
            - containerPort: 9100
              hostPort: 9100
          securityContext:
            privileged: true
          volumeMounts:
            - name: proc
              mountPath: /host/proc
              readOnly: true
            - name: sys
              mountPath: /host/sys
              readOnly: true
      volumes:
        - name: proc
          hostPath:
            path: /proc
        - name: sys
          hostPath:
            path: /sys

Job — Execução Única até Conclusão

# Casos de uso: migrations, processamento batch, backups
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
  namespace: producao
spec:
  # Número de conclusões desejadas
  completions: 1
  # Pods paralelos
  parallelism: 1
  # Tentativas máximas antes de falhar
  backoffLimit: 4
  # Deletar job automaticamente após conclusão (TTL em segundos)
  ttlSecondsAfterFinished: 3600
  # Timeout total
  activeDeadlineSeconds: 600

  template:
    spec:
      restartPolicy: OnFailure   # Never | OnFailure (obrigatório para Jobs)
      containers:
        - name: migration
          image: meu-app:2.0.0
          command: ["./bin/run-migrations.sh"]
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: url

---
# Job com paralelismo (work queue)
apiVersion: batch/v1
kind: Job
metadata:
  name: batch-processor
spec:
  completions: 10       # processar 10 itens
  parallelism: 3        # 3 workers em paralelo
  completionMode: Indexed  # cada pod recebe um índice (JOB_COMPLETION_INDEX)
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: worker
          image: batch-worker:latest
          env:
            - name: WORKER_INDEX
              valueFrom:
                fieldRef:
                  fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']

CronJob — Jobs Agendados

apiVersion: batch/v1
kind: CronJob
metadata:
  name: backup-diario
  namespace: producao
spec:
  # Expressão cron: minuto hora dia-mês mês dia-semana
  schedule: "0 2 * * *"   # todo dia às 02:00 UTC
  timezone: "America/Sao_Paulo"   # K8s 1.27+

  # Política quando job anterior ainda está rodando
  concurrencyPolicy: Forbid   # Allow | Forbid | Replace

  # Histórico de jobs para manter
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1

  # Iniciar mesmo se perdeu a janela (em segundos)
  startingDeadlineSeconds: 300

  # Suspender temporariamente
  suspend: false

  jobTemplate:
    spec:
      backoffLimit: 2
      ttlSecondsAfterFinished: 86400  # 24h
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: backup-tool:latest
              command: ["/bin/sh", "-c", "pg_dump $DATABASE_URL | gzip | aws s3 cp - s3://backups/$(date +%Y%m%d).sql.gz"]
              env:
                - name: DATABASE_URL
                  valueFrom:
                    secretKeyRef:
                      name: db-secret
                      key: url
# Criar job manual a partir de um CronJob (útil para testar ou executar manualmente)
kubectl create job backup-manual --from=cronjob/backup-diario

# Ver jobs e cronjobs
kubectl get jobs
kubectl get cronjobs
kubectl describe cronjob backup-diario

# Ver logs do job
kubectl logs job/backup-manual

11. RBAC

Conceitos

ServiceAccount  — Identidade para Pods (como se autenticar na API do K8s)
Role            — Permissões dentro de um namespace
ClusterRole     — Permissões em todo o cluster (ou recursos não-namespacados)
RoleBinding     — Liga um Role a um usuário/grupo/ServiceAccount (namespace)
ClusterRoleBinding — Liga um ClusterRole a um usuário/grupo/SA (cluster todo)

ServiceAccount

apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-service-account
  namespace: producao
  annotations:
    # AWS IRSA: associar a uma IAM Role
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/minha-role

---
# Usar o ServiceAccount em um Pod
spec:
  serviceAccountName: app-service-account

Role e RoleBinding (namespace-scoped)

# Role: define permissões
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: producao
rules:
  # Recursos de API
  - apiGroups: [""]           # "" = core API group
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]

  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]

  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list"]
    # Restringir a resources específicos pelo nome
    resourceNames: ["app-config", "feature-flags"]

---
# RoleBinding: liga Role a um Subject
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-reader-binding
  namespace: producao
subjects:
  # ServiceAccount
  - kind: ServiceAccount
    name: app-service-account
    namespace: producao
  # Usuário
  - kind: User
    name: developer@empresa.com
  # Grupo
  - kind: Group
    name: developers
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

ClusterRole e ClusterRoleBinding (cluster-wide)

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-viewer
rules:
  # Recursos não-namespacados
  - apiGroups: [""]
    resources: ["nodes", "namespaces", "persistentvolumes"]
    verbs: ["get", "list", "watch"]

  # Permissão em tudo (admin)
  # - apiGroups: ["*"]
  #   resources: ["*"]
  #   verbs: ["*"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: node-viewer-binding
subjects:
  - kind: ServiceAccount
    name: monitoring-agent
    namespace: monitoring
roleRef:
  kind: ClusterRole
  name: node-viewer
  apiGroup: rbac.authorization.k8s.io
# Verificar permissões
kubectl auth can-i get pods --as=system:serviceaccount:producao:app-service-account
kubectl auth can-i '*' '*' --as=admin-user

# Listar todas as permissões de um ServiceAccount
kubectl auth can-i --list --as=system:serviceaccount:producao:app-service-account

# Ver roles e bindings
kubectl get roles -n producao
kubectl get rolebindings -n producao
kubectl get clusterroles
kubectl get clusterrolebindings

# Roles built-in úteis
# cluster-admin: acesso total
# admin: acesso total no namespace
# edit: criar/atualizar recursos (sem RBAC)
# view: somente leitura
kubectl describe clusterrole edit

12. Network Policies

Por padrão: sem isolamento

Sem NetworkPolicy, todos os pods podem se comunicar com qualquer outro pod, em qualquer namespace.

Negar todo tráfego (deny-all)

# Passo 1: negar TODO tráfego de entrada para pods com label app=backend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-ingress
  namespace: producao
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
    - Ingress
  # Sem regras ingress = nega tudo

Permitir tráfego específico

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-network-policy
  namespace: producao
spec:
  podSelector:
    matchLabels:
      app: backend

  policyTypes:
    - Ingress
    - Egress

  ingress:
    # Permitir do frontend (mesmo namespace)
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080

    # Permitir do namespace de monitoring
    - from:
        - namespaceSelector:
            matchLabels:
              name: monitoring
          podSelector:
            matchLabels:
              app: prometheus
      ports:
        - protocol: TCP
          port: 9090

    # Permitir de IP externo específico
    - from:
        - ipBlock:
            cidr: 10.0.0.0/8
            except:
              - 10.0.1.0/24  # exceto esta sub-rede

  egress:
    # Permitir acesso ao banco (namespace banco)
    - to:
        - namespaceSelector:
            matchLabels:
              name: banco
          podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432

    # Permitir DNS (obrigatório se restringir egress)
    - to:
        - namespaceSelector: {}
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Isolamento completo de namespace

# Isolar completamente um namespace (deny all ingress/egress)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: isolado
spec:
  podSelector: {}   # aplica a todos os pods do namespace
  policyTypes:
    - Ingress
    - Egress

13. Helm

Conceitos

Chart      — Pacote Helm (conjunto de templates K8s + values)
Release    — Instância de um chart instalada no cluster
Repository — Coleção de charts (índice HTTP)
Values     — Parâmetros de configuração do chart
Template   — Manifests K8s com Go templating

Estrutura de um Chart

meu-chart/
├── Chart.yaml          # metadados do chart (nome, versão, descrição)
├── values.yaml         # valores padrão (substituíveis no install/upgrade)
├── charts/             # charts dependentes (subcharts)
├── templates/          # templates K8s
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── _helpers.tpl    # funções/partials reutilizáveis
│   ├── NOTES.txt       # mensagem pós-install
│   └── tests/
│       └── test-connection.yaml
└── .helmignore         # arquivos a ignorar no package

Comandos Essenciais

# Repositórios
helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update                          # atualizar índice de repos
helm repo list                            # listar repos adicionados
helm repo remove bitnami                  # remover repo

# Buscar charts
helm search repo postgres
helm search repo nginx --versions         # todas as versões
helm search hub redis                     # buscar no Artifact Hub

# Inspecionar chart antes de instalar
helm show chart bitnami/postgresql
helm show values bitnami/postgresql       # ver todos os values
helm show readme bitnami/postgresql

# Instalar
helm install minha-release bitnami/postgresql
helm install minha-release bitnami/postgresql --namespace banco --create-namespace
helm install minha-release ./meu-chart   # chart local

# Instalar com values customizados
helm install minha-release bitnami/postgresql \
  --set auth.username=myuser \
  --set auth.password=mypass \
  --set primary.persistence.size=20Gi

# Instalar com arquivo de values
helm install minha-release bitnami/postgresql -f values-prod.yaml

# Combinar múltiplos arquivos de values (último sobrescreve)
helm install minha-release bitnami/postgresql \
  -f values-base.yaml \
  -f values-prod.yaml \
  --set auth.password=senhasecreta

# Upgrade (atualiza release existente)
helm upgrade minha-release bitnami/postgresql -f values-prod.yaml

# Instalar ou atualizar (idempotente)
helm upgrade --install minha-release bitnami/postgresql -f values-prod.yaml

# Rollback
helm rollback minha-release               # volta para revisão anterior
helm rollback minha-release 2             # volta para revisão específica
helm history minha-release                # ver histórico de revisões

# Ver releases instaladas
helm list
helm list -A                              # todos os namespaces
helm list -n banco

# Detalhes de uma release
helm status minha-release

# Deletar release
helm uninstall minha-release
helm uninstall minha-release --keep-history  # mantém histórico para rollback

# Renderizar templates sem instalar (debug)
helm template minha-release bitnami/postgresql -f values.yaml
helm template minha-release ./meu-chart --debug

# Verificar chart (lint)
helm lint ./meu-chart
helm lint ./meu-chart -f values-prod.yaml

# Empacotar chart
helm package ./meu-chart
helm package ./meu-chart --version 1.0.0

# Download do chart
helm pull bitnami/postgresql
helm pull bitnami/postgresql --untar     # extrair
helm pull bitnami/postgresql --version 13.2.1

Chart.yaml Exemplo

apiVersion: v2
name: meu-app
description: Aplicação backend com PostgreSQL
type: application   # application | library
version: 1.2.0      # versão do chart (semver)
appVersion: "2.0.0" # versão da aplicação

dependencies:
  - name: postgresql
    version: "13.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled

values.yaml Exemplo

# Número de réplicas
replicaCount: 3

image:
  repository: meu-registry/backend-api
  tag: "2.0.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: true
  className: nginx
  host: api.meusite.com.br
  tls: true
  tlsSecretName: api-tls

resources:
  requests:
    cpu: 250m
    memory: 256Mi
  limits:
    cpu: 500m
    memory: 512Mi

postgresql:
  enabled: true
  auth:
    username: appuser
    database: myapp
  primary:
    persistence:
      size: 10Gi

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

Template com Go Templating

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "meu-app.fullname" . }}
  labels:
    {{- include "meu-app.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "meu-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "meu-app.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - containerPort: 8080
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          {{- if .Values.postgresql.enabled }}
          env:
            - name: DATABASE_HOST
              value: {{ include "meu-app.fullname" . }}-postgresql
          {{- end }}

14. Namespaces e Quotas

Namespaces

# Criar namespace
kubectl create namespace staging
kubectl apply -f namespace.yaml

# Ver recursos em um namespace
kubectl get all -n staging

# Deletar namespace (e todos os recursos dentro)
kubectl delete namespace staging

# Definir namespace padrão no contexto
kubectl config set-context --current --namespace=producao
apiVersion: v1
kind: Namespace
metadata:
  name: producao
  labels:
    name: producao
    environment: production
    team: backend

ResourceQuota — Limitar Consumo por Namespace

apiVersion: v1
kind: ResourceQuota
metadata:
  name: quota-producao
  namespace: producao
spec:
  hard:
    # Limitar objetos
    pods: "20"
    services: "10"
    persistentvolumeclaims: "10"
    configmaps: "30"
    secrets: "30"

    # Limitar recursos de compute
    requests.cpu: "10"          # total de CPU requests
    requests.memory: "20Gi"     # total de memory requests
    limits.cpu: "20"            # total de CPU limits
    limits.memory: "40Gi"       # total de memory limits

    # Limitar por storage class
    ssd-premium.storageclass.storage.k8s.io/requests.storage: "100Gi"
    ssd-premium.storageclass.storage.k8s.io/persistentvolumeclaims: "5"

LimitRange — Defaults e Limites por Container

# Define limites default para containers que não especificam resources
apiVersion: v1
kind: LimitRange
metadata:
  name: limitrange-default
  namespace: staging
spec:
  limits:
    - type: Container
      # Limits padrão (se container não especificar)
      default:
        cpu: "500m"
        memory: "512Mi"
      # Requests padrão
      defaultRequest:
        cpu: "100m"
        memory: "128Mi"
      # Máximo permitido por container
      max:
        cpu: "2"
        memory: "2Gi"
      # Mínimo permitido por container
      min:
        cpu: "50m"
        memory: "64Mi"

    - type: PersistentVolumeClaim
      max:
        storage: "10Gi"
      min:
        storage: "1Gi"
# Ver quotas e uso atual
kubectl get resourcequota -n producao
kubectl describe resourcequota quota-producao -n producao

# Ver limitranges
kubectl get limitrange -n staging
kubectl describe limitrange limitrange-default -n staging

Multi-tenant Pattern

# Namespace por time/cliente com labels padronizadas
apiVersion: v1
kind: Namespace
metadata:
  name: team-backend
  labels:
    team: backend
    cost-center: "123"
    environment: production
---
# ResourceQuota para o time
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-quota
  namespace: team-backend
spec:
  hard:
    requests.cpu: "4"
    requests.memory: "8Gi"
    limits.cpu: "8"
    limits.memory: "16Gi"
    pods: "10"

15. Troubleshooting

CrashLoopBackOff

Sintoma: Pod fica reiniciando em loop, STATUS = CrashLoopBackOff
Causa: Container termina com erro imediatamente após iniciar
# 1. Ver por que o container está crashando
kubectl describe pod meu-pod      # ver Events e Last State
kubectl logs meu-pod              # logs da execução atual
kubectl logs meu-pod --previous   # logs da execução anterior (antes do crash)

# 2. Causas comuns e soluções:
# a) Erro na aplicação: verificar logs de stack trace
# b) ConfigMap/Secret ausente ou com chave errada
kubectl get configmap minha-config -o yaml
kubectl get secret meu-secret -o yaml | base64 -d

# c) Entrypoint incorreto ou permissão negada
kubectl exec -it meu-pod -- /bin/sh  # se conseguir entrar antes do crash

# d) Probe muito agressiva matando o container
# Aumentar initialDelaySeconds e timeoutSeconds nas probes

# e) OOM: ver se aparece "OOMKilled" no describe
kubectl describe pod meu-pod | grep -A5 "Last State"

# Temporariamente: substituir command para debug
kubectl patch deployment meu-deploy -p '{"spec":{"template":{"spec":{"containers":[{"name":"app","command":["sleep","infinity"]}]}}}}'
kubectl exec -it <novo-pod> -- /bin/bash
# Agora pode inspecionar o container sem crash

ImagePullBackOff / ErrImagePull

Sintoma: Pod não consegue baixar a imagem
STATUS = ImagePullBackOff ou ErrImagePull
# 1. Verificar qual imagem está tentando usar
kubectl describe pod meu-pod | grep "Image:"

# 2. Causas e soluções:
# a) Nome/tag da imagem errado
kubectl set image deployment/meu-deploy app=meu-registry/app:v1.0.0

# b) Registry privado sem credentials
# Criar imagePullSecret
kubectl create secret docker-registry registry-creds \
  --docker-server=register.homelab-cloud.com \
  --docker-username=usuario \
  --docker-password=senha

# Adicionar ao deployment ou ServiceAccount
kubectl patch deployment meu-deploy -p '{"spec":{"template":{"spec":{"imagePullSecrets":[{"name":"registry-creds"}]}}}}'

# Adicionar ao ServiceAccount (aplica a todos os pods que usam o SA)
kubectl patch serviceaccount default -p '{"imagePullSecrets":[{"name":"registry-creds"}]}'

# c) Problema de rede/DNS no node
# Testar conectividade do node
kubectl debug node/worker-1 -it --image=busybox

Pod em Pending (não é agendado)

Sintoma: Pod fica em STATUS = Pending indefinidamente
# 1. Ver eventos (sempre o primeiro passo)
kubectl describe pod meu-pod | tail -20

# 2. Causas e soluções:
# a) Recursos insuficientes no cluster
kubectl describe nodes | grep -A5 "Allocated resources"
kubectl top nodes

# Solução: adicionar nodes ou reduzir requests do pod

# b) PVC não consegue ser bound (pending storage)
kubectl get pvc
kubectl describe pvc meu-pvc
# Ver se StorageClass existe e provisioner está funcionando

# c) Node selector / affinity não satisfeita
kubectl get nodes --show-labels
# Verificar se os nodes têm os labels necessários

# d) Taints no node sem toleration no pod
kubectl describe nodes | grep Taints
# Adicionar toleration ou remover taint:
kubectl taint nodes worker-1 maintenance=true:NoSchedule-

# e) Cotas excedidas no namespace
kubectl describe resourcequota -n meu-namespace

OOMKilled

Sintoma: Container morto por falta de memória
kubectl describe pod: State: Terminated, Reason: OOMKilled
# 1. Confirmar
kubectl describe pod meu-pod | grep -A3 "Last State"
# OOMKilled = processo foi morto pelo kernel por exceder o memory limit

# 2. Ver uso atual de memória
kubectl top pod meu-pod --containers
kubectl top nodes

# 3. Soluções:
# a) Aumentar memory limit
kubectl patch deployment meu-deploy -p '{"spec":{"template":{"spec":{"containers":[{"name":"app","resources":{"limits":{"memory":"1Gi"}}}]}}}}'

# b) Investigar memory leak na aplicação
# Capturar heap dump, analisar com profiler

# c) Verificar se requests != limits (evita problema de over-commit)
# requests = reserva garantida, limits = teto máximo
# Recomendado: requests == limits para workloads críticos (Guaranteed QoS)

Pod Running mas não recebe tráfego

# 1. Verificar endpoints do Service (devem mostrar IPs dos pods)
kubectl get endpoints meu-service
kubectl describe endpoints meu-service

# Se endpoints vazio: problema no selector do Service
kubectl get service meu-service -o yaml | grep -A3 selector
kubectl get pods --show-labels  # labels dos pods devem bater

# 2. Testar conectividade direta ao pod
kubectl port-forward pod/meu-pod 8080:8080
curl localhost:8080/health

# 3. Verificar readiness probe (pod pode estar Running mas não Ready)
kubectl get pods  # READY deve ser 1/1
kubectl describe pod meu-pod | grep -A10 "Readiness"

# 4. Testar do inside do cluster
kubectl run debug --image=curlimages/curl --rm -it -- \
  curl http://meu-service.namespace.svc.cluster.local/health

Problemas de DNS

# Testar resolução DNS de dentro do cluster
kubectl run dns-test --image=busybox --rm -it -- nslookup kubernetes.default

# Verificar CoreDNS
kubectl get pods -n kube-system | grep coredns
kubectl logs -n kube-system -l k8s-app=kube-dns

# Formato do DNS interno:
# <service>.<namespace>.svc.cluster.local
# <pod-ip-com-hifens>.<namespace>.pod.cluster.local

# Testar conectividade com service
kubectl run debug --image=nicolaka/netshoot --rm -it -- \
  curl http://meu-service.producao.svc.cluster.local:8080/health

16. Boas Práticas

Labels e Annotations

# Labels recomendadas (Kubernetes standard labels)
metadata:
  labels:
    app.kubernetes.io/name: backend-api         # nome da aplicação
    app.kubernetes.io/instance: backend-api-prod # instância única
    app.kubernetes.io/version: "2.0.0"           # versão da app
    app.kubernetes.io/component: api             # componente (frontend, backend, database)
    app.kubernetes.io/part-of: sistema-financeiro # sistema ao qual pertence
    app.kubernetes.io/managed-by: helm           # ferramenta que gerencia

  # Annotations: metadados para ferramentas (não usados como seletores)
  annotations:
    deployment.kubernetes.io/revision: "5"
    description: "API REST do sistema financeiro"
    contact: "time-backend@empresa.com.br"
    docs: "https://wiki.empresa.com.br/backend-api"

Resource Limits Obrigatórios

# SEMPRE definir resources para todos os containers em produção
# Sem limits: um container pode consumir todos os recursos do node
resources:
  requests:
    cpu: "250m"       # valor que o scheduler usa para planejar alocação
    memory: "256Mi"   # garante que o pod terá esses recursos disponíveis
  limits:
    cpu: "500m"       # container nunca usa mais que isso
    memory: "512Mi"   # se ultrapassar: OOMKilled

# Quality of Service (QoS) classes:
# Guaranteed: requests == limits (maior prioridade, nunca evicted antes)
# Burstable:  requests < limits
# BestEffort: sem requests/limits (menor prioridade, primeiro a ser evicted)

Non-root Containers

spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault

  containers:
    - name: app
      securityContext:
        allowPrivilegeEscalation: false  # impede sudo/setuid
        readOnlyRootFilesystem: true     # filesystem imutável
        capabilities:
          drop: ["ALL"]                  # remover todas as capabilities Linux
          add: []                        # adicionar apenas o necessário

Evitar Tags Mutáveis

# MAU — tag 'latest' pode mudar silenciosamente
image: meu-app:latest

# BOM — tag imutável garante reprodutibilidade
image: meu-app:2.0.0
# MELHOR — digest garante exatamente o mesmo layer
image: meu-app@sha256:abc123def456...

Liveness vs Readiness vs Startup

# Startup probe: usar quando app demora para iniciar (>30s)
# Enquanto startup não passa, liveness/readiness são desativados
startupProbe:
  httpGet:
    path: /health/startup
    port: 8080
  failureThreshold: 30     # 30 tentativas
  periodSeconds: 10        # a cada 10s = até 5 minutos

# Readiness probe: controla se pod recebe tráfego (Service)
# Falha = remove do endpoint (zero downtime durante sobrecarga temporária)
readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  failureThreshold: 3

# Liveness probe: controla se pod é reiniciado
# Usar apenas para detectar deadlocks/estados irrecuperáveis
# NÃO deve falhar por dependências externas (BD lento, etc.)
livenessProbe:
  httpGet:
    path: /health/live
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10
  failureThreshold: 3

PodDisruptionBudget — Alta Disponibilidade

# Garante número mínimo de pods durante manutenção
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: backend-pdb
spec:
  # Mínimo de pods disponíveis (pode ser número ou percentagem)
  minAvailable: 2
  # OU máximo de pods indisponíveis
  # maxUnavailable: 1
  selector:
    matchLabels:
      app: backend-api

Checklist de Deploy em Produção

[ ] Imagem com tag versionada (não latest)
[ ] Resource requests e limits definidos
[ ] Liveness e readiness probes configuradas
[ ] Non-root user no securityContext
[ ] readOnlyRootFilesystem: true (ou justificativa)
[ ] Secrets em Secret objects (não ConfigMaps ou env plain)
[ ] Pelo menos 2 réplicas para HA
[ ] PodDisruptionBudget criado
[ ] HPA configurado para workloads variáveis
[ ] Pod anti-affinity para distribuir réplicas entre nodes
[ ] Labels padrão (app.kubernetes.io/*) em todos os recursos
[ ] NetworkPolicy restritiva (deny-all + allow específico)
[ ] ResourceQuota no namespace
[ ] Rollout strategy com maxUnavailable: 0 para zero-downtime
[ ] RBAC mínimo necessário (principle of least privilege)
[ ] Logging estruturado em JSON para integração com ELK/Loki

Comandos de Debug Rápido

# Pod temporário de debug no cluster
kubectl run debug \
  --image=nicolaka/netshoot \
  --rm -it \
  --restart=Never \
  -- bash

# Debug de um pod existente (K8s 1.23+)
kubectl debug meu-pod -it --image=busybox --share-processes --copy-to=debug-pod

# Copiar pod existente com novo comando para inspeção
kubectl debug meu-pod -it \
  --image=ubuntu \
  --copy-to=meu-pod-debug \
  --container=app \
  -- bash

# Ver todos os eventos ordenados por tempo
kubectl get events --sort-by='.lastTimestamp' -A

# Forçar rollout (reiniciar todos os pods de um deployment)
kubectl rollout restart deployment/meu-deploy

# Ver uso de recursos por namespace
kubectl top pods -A --sort-by=memory

# Listar todos os containers de todos os pods
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' -A

# Verificar se há pods em estado ruim
kubectl get pods -A | grep -v Running | grep -v Completed