Kubernetes (K8s)

Arquitetura do Kubernetes

Control Plane

O control plane é o cérebro do cluster. Todos os componentes são stateless exceto o etcd.

┌── Control Plane ──────────────────────────────────────────────────┐
│                                                                    │
│  ┌─────────────┐  ┌───────────┐  ┌──────────┐  ┌──────────────┐ │
│  │  API Server  │  │   etcd    │  │ Scheduler│  │  Controller  │ │
│  │  (kube-      │  │ (cluster  │  │ (decide  │  │  Manager     │ │
│  │  apiserver)  │←→│  state)   │  │  onde     │  │ (reconcilia │ │
│  │              │  │           │  │  rodar)   │  │  estado)     │ │
│  └──────┬───────┘  └───────────┘  └──────────┘  └──────────────┘ │
│         │                                                          │
│  ┌──────┴───────┐                                                  │
│  │ cloud-        │  ← Integração com AWS/GCP/Azure                │
│  │ controller-   │    (load balancers, discos, rotas)              │
│  │ manager       │                                                 │
│  └───────────────┘                                                 │
└────────────────────────────────────────────────────────────────────┘
          │ HTTPS (mTLS)

┌── Node ───────────────────────────────────────────────────────────┐
│  ┌──────────┐  ┌────────────┐  ┌────────────────────┐           │
│  │ kubelet   │  │ kube-proxy │  │ Container Runtime  │           │
│  │ (agente)  │  │ (rede/svc) │  │ (containerd/CRI-O)│           │
│  └──────────┘  └────────────┘  └────────────────────┘           │
│  ┌─── Pod ────┐  ┌─── Pod ────┐  ┌─── Pod ────┐                │
│  │ Container  │  │ Container  │  │ Container  │                 │
│  │ Container  │  │            │  │ Init Cont. │                 │
│  └────────────┘  └────────────┘  └────────────┘                 │
└────────────────────────────────────────────────────────────────────┘

API Server (kube-apiserver): ponto central de comunicação. Toda operação passa por ele — kubectl, kubelets, controllers. Valida requests, autentica via certificados/tokens, autoriza via RBAC e persiste no etcd. Expõe API RESTful versionada (/apis/apps/v1/deployments).

etcd: banco key-value distribuído (Raft consensus). Armazena todo o estado do cluster. Perder o etcd sem backup = perder o cluster. Em produção, sempre em cluster de 3 ou 5 membros.

# Backup do etcd (operação crítica)
etcdctl snapshot save backup.db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

Scheduler (kube-scheduler): observa Pods sem node atribuído e decide onde colocá-los. Considera: resource requests, affinity/anti-affinity, taints/tolerations, topology constraints, volume locality.

Controller Manager: executa controllers em loop de reconciliação. Cada controller observa o estado atual e age para convergir ao estado desejado. Exemplos: Deployment controller, ReplicaSet controller, Node controller, Job controller.

Kubelet: agente em cada node. Recebe PodSpecs do API server, garante que os containers definidos estão rodando. Reporta status, executa probes (liveness, readiness, startup). Comunica com o container runtime via CRI (Container Runtime Interface).

Kube-proxy: implementa Services. Mantém regras de rede (iptables ou IPVS) que redirecionam tráfego para os Pods corretos. Modo IPVS é mais eficiente para clusters grandes (O(1) vs O(n) de iptables).

API Objects em Profundidade

Pod: Unidade Fundamental

apiVersion: v1
kind: Pod
metadata:
  name: app-debug
  labels:
    app: api
    version: v1
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "9090"
spec:
  # Init containers executam em sequência ANTES dos containers principais
  initContainers:
    - name: wait-for-db
      image: busybox
      command: ['sh', '-c', 'until nc -z db-service 5432; do sleep 1; done']

    - name: run-migrations
      image: app:v1.2.3
      command: ['npx', 'prisma', 'migrate', 'deploy']
      env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: database-url

  containers:
    - name: api
      image: app:v1.2.3
      ports:
        - containerPort: 3000
          name: http
        - containerPort: 9090
          name: metrics

      resources:
        requests:
          cpu: "250m"          # 0.25 cores — garantido pelo scheduler
          memory: "256Mi"      # 256 MiB — garantido
        limits:
          cpu: "1000m"         # 1 core — máximo (throttling se exceder)
          memory: "512Mi"      # 512 MiB — OOM kill se exceder

      # Startup probe: período inicial de grace
      startupProbe:
        httpGet:
          path: /health
          port: http
        failureThreshold: 30
        periodSeconds: 2         # Até 60s para iniciar

      # Liveness: está vivo? Se falhar → reinicia
      livenessProbe:
        httpGet:
          path: /health
          port: http
        periodSeconds: 10
        failureThreshold: 3

      # Readiness: está pronto para receber tráfego?
      readinessProbe:
        httpGet:
          path: /ready
          port: http
        periodSeconds: 5
        failureThreshold: 2

      lifecycle:
        preStop:
          exec:
            command: ["/bin/sh", "-c", "sleep 10"]
            # Delay para permitir que kube-proxy remova o Pod
            # dos endpoints ANTES de enviar SIGTERM

      volumeMounts:
        - name: config
          mountPath: /app/config
          readOnly: true
        - name: tmp
          mountPath: /tmp

      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        readOnlyRootFilesystem: true
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]

    # Sidecar container (ex: log forwarder, proxy)
    - name: log-forwarder
      image: fluent-bit:latest
      volumeMounts:
        - name: shared-logs
          mountPath: /var/log/app

  volumes:
    - name: config
      configMap:
        name: app-config
    - name: tmp
      emptyDir:
        sizeLimit: 100Mi
    - name: shared-logs
      emptyDir: {}

  serviceAccountName: app-sa
  terminationGracePeriodSeconds: 30

  # Affinity/anti-affinity para distribuição
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          podAffinityTerm:
            labelSelector:
              matchLabels:
                app: api
            topologyKey: kubernetes.io/hostname
    # Prefere distribuir réplicas em nodes diferentes

Workload Resources

# Deployment — aplicações stateless com rolling updates
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  revisionHistoryLimit: 5        # Mantém 5 versões para rollback
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry/api:v1.2.3
---
# StatefulSet — aplicações stateful (banco, cache, message broker)
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres          # Headless service obrigatório
  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:          # PVC por Pod (postgres-0, postgres-1, postgres-2)
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: gp3
        resources:
          requests:
            storage: 100Gi
# StatefulSet garante:
# - Identidade estável: postgres-0, postgres-1, postgres-2
# - Storage persistente por Pod
# - Ordem de criação e deleção (0 → 1 → 2; 2 → 1 → 0)
# - DNS previsível: postgres-0.postgres.namespace.svc.cluster.local
---
# DaemonSet — um Pod em CADA node (logging, monitoring, CNI)
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentbit
spec:
  selector:
    matchLabels:
      app: fluentbit
  template:
    metadata:
      labels:
        app: fluentbit
    spec:
      containers:
        - name: fluentbit
          image: fluent/fluent-bit:latest
          volumeMounts:
            - name: varlog
              mountPath: /var/log
              readOnly: true
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
      tolerations:               # Rodar inclusive em nodes com taints
        - operator: Exists
---
# Job — execução única (migrations, batch processing)
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  backoffLimit: 3                # Tentativas em caso de falha
  activeDeadlineSeconds: 300     # Timeout total
  ttlSecondsAfterFinished: 3600  # Limpar depois de 1h
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: app:v1.2.3
          command: ["npx", "prisma", "migrate", "deploy"]
---
# CronJob — execução periódica
apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-backup
spec:
  schedule: "0 3 * * *"         # Todo dia às 3h
  concurrencyPolicy: Forbid      # Não executa se anterior ainda roda
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: backup-tool:latest
              command: ["./backup.sh"]

Services: Networking Interno

# ClusterIP — acessível apenas dentro do cluster (padrão)
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  type: ClusterIP
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 3000
      protocol: TCP
# DNS: api.namespace.svc.cluster.local
# Redireciona para Pods com label app=api via iptables/IPVS
---
# Headless Service — sem ClusterIP, retorna IPs dos Pods diretamente
apiVersion: v1
kind: Service
metadata:
  name: postgres
spec:
  clusterIP: None               # Headless
  selector:
    app: postgres
  ports:
    - port: 5432
# DNS: postgres-0.postgres.namespace.svc.cluster.local (registro A por Pod)
# Usado por StatefulSets para discovery direto
---
# NodePort — expõe em porta alta (30000-32767) de cada node
apiVersion: v1
kind: Service
metadata:
  name: api-nodeport
spec:
  type: NodePort
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 3000
      nodePort: 30080
---
# LoadBalancer — provisiona load balancer na cloud
apiVersion: v1
kind: Service
metadata:
  name: api-lb
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: nlb
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
  type: LoadBalancer
  selector:
    app: api
  ports:
    - port: 443
      targetPort: 3000

Ingress: Roteamento HTTP/HTTPS

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rate-limit-connections: "10"
    nginx.ingress.kubernetes.io/rate-limit-rps: "100"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.empresa.com
        - app.empresa.com
      secretName: app-tls
  rules:
    - host: api.empresa.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80
    - host: app.empresa.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 80

ConfigMaps e Secrets

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  FEATURE_FLAG_NEW_UI: "true"
  config.yaml: |
    server:
      port: 3000
      gracefulShutdown: 30s
    cache:
      ttl: 300
---
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
stringData:                      # stringData aceita plain text (base64 automático)
  database-url: "postgres://user:pass@postgres:5432/myapp"
  jwt-secret: "super-secret-key"
# IMPORTANTE: Secrets do K8s são apenas base64, NÃO criptografia
# Use Sealed Secrets, SOPS ou External Secrets Operator para secrets reais
# Sealed Secrets — criptografa secrets para armazenar no Git
kubeseal --format=yaml < secret.yaml > sealed-secret.yaml
# Apenas o controller no cluster pode descriptografar

# External Secrets Operator — sincroniza com AWS Secrets Manager/Vault
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: app-secrets
  data:
    - secretKey: database-url
      remoteRef:
        key: prod/myapp/database-url

PersistentVolumes e CSI

# StorageClass — define como provisionar volumes
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
reclaimPolicy: Retain            # Não deleta o volume ao apagar PVC
volumeBindingMode: WaitForFirstConsumer  # Provisiona na AZ do Pod
allowVolumeExpansion: true
---
# PersistentVolumeClaim — request de storage por um Pod
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
spec:
  accessModes:
    - ReadWriteOnce              # RWO: um node por vez
  storageClassName: gp3
  resources:
    requests:
      storage: 50Gi

RBAC: Controle de Acesso

# ServiceAccount para a aplicação
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-sa
  namespace: production
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456:role/app-role  # IRSA
---
# Role — permissões dentro de um namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-role
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["configmaps", "secrets"]
    verbs: ["get", "list"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
---
# RoleBinding — associa Role ao ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: app-role-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: app-sa
    namespace: production
roleRef:
  kind: Role
  name: app-role
  apiGroup: rbac.authorization.k8s.io
---
# ClusterRole — permissões no cluster inteiro (cross-namespace)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring-reader
rules:
  - apiGroups: [""]
    resources: ["nodes", "nodes/metrics", "pods"]
    verbs: ["get", "list", "watch"]
  - nonResourceURLs: ["/metrics"]
    verbs: ["get"]

Resource Management: QoS, LimitRanges, ResourceQuotas

# LimitRange — defaults e limites por Pod/Container no namespace
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: production
spec:
  limits:
    - type: Container
      default:                   # Valores padrão se não especificado
        cpu: "500m"
        memory: "256Mi"
      defaultRequest:
        cpu: "100m"
        memory: "128Mi"
      max:                       # Máximo permitido
        cpu: "4"
        memory: "4Gi"
      min:                       # Mínimo permitido
        cpu: "50m"
        memory: "64Mi"
---
# ResourceQuota — limite total do namespace
apiVersion: v1
kind: ResourceQuota
metadata:
  name: production-quota
  namespace: production
spec:
  hard:
    requests.cpu: "20"
    requests.memory: "40Gi"
    limits.cpu: "40"
    limits.memory: "80Gi"
    pods: "100"
    services: "20"
    persistentvolumeclaims: "30"
    secrets: "50"

QoS Classes

┌──────────────────────────────────────────────────────────────┐
│ Classe      │ Condição                   │ Prioridade OOM   │
├─────────────┼────────────────────────────┼──────────────────┤
│ Guaranteed  │ requests == limits para    │ Último a morrer  │
│             │ CPU e memória              │                  │
│ Burstable   │ requests < limits          │ Intermediário    │
│ BestEffort  │ Sem requests nem limits    │ Primeiro a morrer│
└──────────────────────────────────────────────────────────────┘

# Regra: SEMPRE defina requests. Em produção, defina limits também.
# BestEffort = primeiro a ser evicted quando o node fica sem recursos.

HPA e VPA

# HPA — escala horizontal baseado em métricas
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 20
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 4
          periodSeconds: 60        # Máximo +4 pods por minuto
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60        # Máximo -10% por minuto
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
    # Métricas customizadas via Prometheus adapter
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "1000"

Networking Model e CNI

Modelo de rede do Kubernetes:
1. Todo Pod tem IP único e roteável (sem NAT entre Pods)
2. Todo Pod pode falar com qualquer outro Pod diretamente
3. O IP que o Pod vê é o mesmo que outros Pods veem

CNI Plugins:
┌──────────┬─────────────────────────────────────────────────┐
│ Calico   │ BGP routing, Network Policies avançadas,        │
│          │ eBPF dataplane. Mais popular em bare-metal.     │
├──────────┼─────────────────────────────────────────────────┤
│ Cilium   │ eBPF nativo, observabilidade L7, mTLS,          │
│          │ Network Policies L3-L7. Recomendado para novos. │
├──────────┼─────────────────────────────────────────────────┤
│ AWS VPC  │ IPs diretamente do VPC. Integração nativa.      │
│ CNI      │ Limitado por ENI (IPs por instance type).       │
├──────────┼─────────────────────────────────────────────────┤
│ Flannel  │ Simples, VXLAN overlay. Sem Network Policies.   │
└──────────┴─────────────────────────────────────────────────┘
# NetworkPolicy — firewall no nível do Pod
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-netpol
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: nginx-ingress
      ports:
        - port: 3000
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - port: 5432
    - to:                          # Permitir DNS
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - port: 53
          protocol: UDP

Helm Charts

# Estrutura de um chart
mychart/
├── Chart.yaml              # Metadados (nome, versão, dependências)
├── values.yaml             # Valores padrão
├── values-production.yaml  # Override para produção
├── templates/
   ├── _helpers.tpl        # Template helpers
   ├── deployment.yaml
   ├── service.yaml
   ├── ingress.yaml
   ├── hpa.yaml
   ├── configmap.yaml
   ├── secret.yaml
   └── tests/
       └── test-connection.yaml
└── charts/                 # Dependências (subcharts)
# values.yaml
image:
  repository: registry.io/app
  tag: "v1.0.0"
  pullPolicy: IfNotPresent

replicaCount: 3

resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "1"
    memory: "512Mi"

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 20
  targetCPUUtilization: 70
# Comandos essenciais
helm install app ./mychart -f values-production.yaml -n production
helm upgrade app ./mychart --set image.tag=v1.2.3 -n production
helm rollback app 1 -n production     # Volta para revisão 1
helm diff upgrade app ./mychart --set image.tag=v1.2.3  # Preview (plugin)
helm template app ./mychart -f values-production.yaml    # Render local

# kubectl essenciais para debugging
kubectl get pods -n production -o wide
kubectl describe pod <pod-name> -n production
kubectl logs <pod-name> -n production -f --previous  # Logs do container anterior
kubectl exec -it <pod-name> -n production -- sh
kubectl top pods -n production                       # Uso de recursos
kubectl get events -n production --sort-by='.lastTimestamp'
kubectl rollout undo deployment/api -n production    # Rollback imediato

Kubernetes é complexo, mas cada componente resolve um problema real. A chave é entender o loop de reconciliação — você declara o estado desejado, os controllers convergem. Domine primeiro Deployments, Services e Ingress; depois avance para StatefulSets, RBAC e HPA conforme a necessidade.


Referencias e Fontes

  • Kubernetes Official Documentationhttps://kubernetes.io/docs/ — referência oficial
  • “Kubernetes in Action” — Marko Lukša — o livro mais completo sobre K8s
  • “Kubernetes Up & Running” — Kelsey Hightower, Brendan Burns, Joe Beda — dos criadores do K8s
  • “Production Kubernetes” — Josh Rosso et al. — operação em produção
  • Helm Documentationhttps://helm.sh/docs/ — package manager para K8s
  • CNCF Landscapehttps://landscape.cncf.io/ — ecossistema cloud native