Controlando onde seus pods rodam juntos com affinity e anti-affinity

Tabela de Conteúdo

Neste post, vamos configurar regras para controlar exatamente onde nossos pods devem rodar, garantindo alta disponibilidade e performance.

Vamos entender os conceitos

A ideia é simples:

  • node affinity = controlamos em qual nó o pod roda (hardware, zona)
  • pod affinity = colocamos pods próximos de outros pods
  • pod anti-affinity = colocamos pods longe de outros pods

required vs preferred

  • required: pod NÃO escala se regra não for atendida (use com cautela)
  • preferred: scheduler tenta atender mas escala mesmo se não conseguir

Dica: sempre comece com preferred e mude para required apenas se necessário.

Vamos configurar: node affinity para hardware específico

Vamos criar um deployment que só rode em nós com GPU:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ml-training
spec:
  replicas: 2
  selector:
    matchLabels:
      app: ml
  template:
    metadata:
      labels:
        app: ml
    spec:
      affinity:
        nodeAffinity:
          # Preferência: tenta usar instâncias específicas
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: node.kubernetes.io/instance-type
                    operator: In
                    values:
                      - g5.xlarge  # instância com GPU
          # Requisito obrigatório: só roda em nós com GPU
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: accelerator
                    operator: In
                    values:
                      - nvidia-gpu
      containers:
        - name: ml
          image: tensorflow/tensorflow:latest-gpu
          resources:
            requests:
              memory: "4Gi"
              cpu: "2000m"
            limits:
              memory: "8Gi"
              cpu: "4000m"

O que configuramos aqui?

  • preferred: tentamos usar instâncias g5.xlarge (peso 100)
  • required: obrigatório ter nvidia-gpu
  • resources: reservamos recursos adequados para ML

Vamos configurar: pod affinity para performance

Agora vamos colocar nossa API perto do banco de dados:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-com-banco
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - database
              topologyKey: "kubernetes.io/hostname"
      containers:
        - name: api
          image: nginx:1.27
          resources:
            requests:
              memory: "256Mi"
              cpu: "200m"
            limits:
              memory: "512Mi"
              cpu: "500m"

O que isso faz?

  • podAffinity: API só roda na mesma máquina que o banco
  • topologyKey: kubernetes.io/hostname = mesma máquina física
  • required: obrigatório estar perto do banco

Vamos configurar: pod anti-affinity para alta disponibilidade

Vamos espalhar nossos web servers para evitar single point of failure:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-servers
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - web
                topologyKey: "kubernetes.io/hostname"
      containers:
        - name: web
          image: nginx:1.27
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "200m"

O que isso garante?

  • podAntiAffinity: prefere não colocar web servers no mesmo nó
  • preferred: se não tiver nós suficientes, escala mesmo assim
  • weight: 100: força máxima da preferência

Vamos testar nossas configurações

Agora que você entendeu o conceito, vamos ao código para testar:

# Aplicar nossos deployments
kubectl apply -f ml-training.yaml
kubectl apply -f api-com-banco.yaml
kubectl apply -f web-servers.yaml

# Verificar onde os pods estão rodando
kubectl get pods -o wide

# Verificar distribuição por nó
kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName

# Ver eventos de scheduling
kubectl get events --sort-by=.lastTimestamp | grep "FailedScheduling"

# Descrever um pod para ver as regras de affinity
kubectl describe pod <pod-name> | grep -A 10 "Affinity"

Boas práticas que aprendemos

Para alta disponibilidade

  • Use pod anti-affinity para aplicações críticas
  • Combine com PodDisruptionBudgets para manutenções
  • Monitore a distribuição dos pós regularmente

Para performance

  • Use pod affinity para comunicação rápida
  • Considere node affinity para hardware específico
  • Monitore latência de rede entre nós/zonas

Para segurança e compliance

  • Use node affinity para isolar workloads
  • Combine com taints e tolerations
  • Implemente network policies adicionalmente

Para custos

  • Use node affinity para workloads em nós spot
  • Combine com cluster autoscaler
  • Monitore utilização dos recursos

Troubleshooting rápido

Se os pods ficam pendentes:

# Ver detalhes do pod
kubectl describe pod <pod-name>

# Ver eventos recentes
kubectl get events --sort-by=.lastTimestamp

# Ver se há nós disponíveis
kubectl get nodes -o wide

# Ver recursos disponíveis
kubectl top nodes

Problemas comuns que podemos encontrar:

  • Affinity muito restritiva: use preferred em vez de required
  • Labels incorretas: verifique se os pods alvo existem
  • Recursos insuficientes: monitore uso de CPU/memória

Se você quiser entender melhor como recursos afetam scheduling, dá uma olhada neste post: Kubernetes requests vs limits: como isso afeta scheduling e throttling.

Simples assim! :)