Node Affinity, Taints e Tolerations em Kubernetes: Do Básico ao Avançado Já leu

Node Affinity: Controlando a Colocação de Pods Node Affinity é um mecanismo que permite especificar regras para influenciar em qual nó (node) um pod será escalonado. Diferentemente de NodeSelector, que é mais simples e limitado, Node Affinity oferece operadores mais sofisticados como , , , , e , permitindo uma granularidade muito maior no controle de alocação de recursos. O conceito funciona em dois níveis: (obrigatório no momento do escalonamento) e (preferência, sem garantia). A primeira garante que o pod só será alocado se as regras forem atendidas; a segunda tenta atender as regras, mas permite alocação em nós que não as atendem se necessário. Exemplo Prático: Afinidade Obrigatória Você possui nós com labels específicos e precisa que certos pods rodem apenas em nós GPU. Primeiro, você rotula os nós: Em seguida, cria um pod com afinidade obrigatória: Este manifesto garante que o pod será escalonado apenas em nós com o label . Se nenhum nó atender ao critério, o

Node Affinity: Controlando a Colocação de Pods

Node Affinity é um mecanismo que permite especificar regras para influenciar em qual nó (node) um pod será escalonado. Diferentemente de NodeSelector, que é mais simples e limitado, Node Affinity oferece operadores mais sofisticados como In, NotIn, Exists, DoesNotExist, Gt e Lt, permitindo uma granularidade muito maior no controle de alocação de recursos.

O conceito funciona em dois níveis: requiredDuringSchedulingIgnoredDuringExecution (obrigatório no momento do escalonamento) e preferredDuringSchedulingIgnoredDuringExecution (preferência, sem garantia). A primeira garante que o pod só será alocado se as regras forem atendidas; a segunda tenta atender as regras, mas permite alocação em nós que não as atendem se necessário.

Exemplo Prático: Afinidade Obrigatória

Você possui nós com labels específicos e precisa que certos pods rodem apenas em nós GPU. Primeiro, você rotula os nós:

kubectl label nodes worker-1 gpu=true
kubectl label nodes worker-2 disktype=ssd

Em seguida, cria um pod com afinidade obrigatória:

apiVersion: v1
kind: Pod
metadata:
  name: gpu-intensive-app
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: gpu
            operator: In
            values:
            - "true"
  containers:
  - name: app
    image: nvidia/cuda:11.0-base
    resources:
      limits:
        nvidia.com/gpu: 1

Este manifesto garante que o pod será escalonado apenas em nós com o label gpu=true. Se nenhum nó atender ao critério, o pod permanece em estado Pending.

Exemplo Prático: Afinidade com Preferência

Quando você quer que o pod prefira nós SSD, mas aceita nós sem essa característica se necessário:

apiVersion: v1
kind: Pod
metadata:
  name: database-pod
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
      - weight: 50
        preference:
          matchExpressions:
          - key: zone
            operator: In
            values:
            - us-east-1a
  containers:
  - name: db
    image: postgres:14

O peso (weight) determina a prioridade: nós com disktype=ssd recebem 100 pontos, enquanto nós na zona us-east-1a recebem 50. O escalonador soma os pesos e escolhe o nó com maior pontuação.


Taints: Repelindo Pods de Nós

Taints (manchas) são propriedades aplicadas a nós que repelem pods, a menos que esses pods tenham tolerações correspondentes. Enquanto Node Affinity atrai pods para nós, Taints fazem o oposto: afastam pods. Um caso de uso comum é reservar nós para workloads específicas (como GPU, storage de alta performance) ou para tarefas de administração do cluster.

Um taint é composto por três componentes: key, value e effect. O effect pode ser NoSchedule (não agenda), NoExecute (não executa e remove pods existentes) ou PreferNoSchedule (evita, mas permite se necessário).

Aplicando Taints em Nós

Suponha que você tem um nó dedicado exclusivamente para máquinas virtuais críticas. Você aplica um taint:

kubectl taint nodes worker-gpu gpu=true:NoSchedule

Este comando adiciona um taint gpu=true com efeito NoSchedule. A partir deste momento, nenhum pod será escalonado neste nó a menos que tenha uma toleração correspondente.

Para listar taints de um nó:

kubectl describe node worker-gpu | grep Taints

Para remover um taint, adicione um hífen no final:

kubectl taint nodes worker-gpu gpu=true:NoSchedule-

Taints com Efeito NoExecute

O efeito NoExecute é mais agressivo: remove pods já em execução que não toleram o taint. Ideal para situações de manutenção ou quando você precisa liberar recursos imediatamente:

kubectl taint nodes worker-old os=deprecated:NoExecute

Qualquer pod sem toleração para este taint será removido do nó. Pods com toleração podem permanecer, respeitando tolerationSeconds se definido.


Tolerations: Permitindo Pods em Nós com Taints

Tolerations são definidas nos specs de pods e permitem que eles sejam escalonados em nós com taints específicos. Uma toleração não atrai o pod para um nó (isso é tarefa do Node Affinity), mas remove a restrição imposta pelo taint, permitindo alocação.

Uma toleração é composta por key, operator, value e effect, e opcionalmente tolerationSeconds. O operator pode ser Equal (valor exato) ou Exists (chave existe, independente do valor).

Exemplo: Pod com Toleração para GPU

Você deseja que um pod de treinamento de IA rode em um nó com taint de GPU:

apiVersion: v1
kind: Pod
metadata:
  name: ml-training
spec:
  tolerations:
  - key: gpu
    operator: Equal
    value: "true"
    effect: NoSchedule
  containers:
  - name: trainer
    image: tensorflow/tensorflow:latest-gpu

Este pod tolera o taint gpu=true:NoSchedule. Se esse nó estiver disponível (e preferível via Node Affinity), o pod será escalonado. Sem a toleração, seria rejeitado.

Toleração Genérica com Exists

Às vezes, você quer aceitar qualquer valor para uma chave específica:

apiVersion: v1
kind: Pod
metadata:
  name: flexible-app
spec:
  tolerations:
  - key: workload-type
    operator: Exists
    effect: NoSchedule
  containers:
  - name: app
    image: my-app:latest

Este pod tolera qualquer taint com chave workload-type, independente do valor. Útil quando você tem múltiplas variações de um taint e quer cobertura genérica.

Toleração com Duração: NoExecute

Para nós em manutenção, você quer remover pods, mas dando tempo para graceful shutdown:

apiVersion: v1
kind: Pod
metadata:
  name: temporary-app
spec:
  tolerations:
  - key: maintenance
    operator: Equal
    value: "scheduled"
    effect: NoExecute
    tolerationSeconds: 300
  containers:
  - name: app
    image: my-app:latest

Se o taint maintenance=scheduled:NoExecute for aplicado, o pod tem 300 segundos (5 minutos) para encerrar gracefully antes de ser forçadamente removido.


Combinando Affinity, Taints e Tolerations: Um Cenário Real

Na prática, você combina esses três conceitos para criar arquiteturas robustas e eficientes. Considere um cluster com nós de diferentes tipos: padrão, GPU e armazenamento em SSD. Você quer:

  1. Nós GPU exclusivos para workloads que usam GPU
  2. Nós SSD preferidos para banco de dados
  3. Nós padrão para aplicações gerais

Primeiro, você rotula e taint os nós:

# Nós GPU
kubectl label nodes gpu-1 node-type=gpu
kubectl taint nodes gpu-1 workload-type=gpu:NoSchedule

# Nós SSD
kubectl label nodes ssd-1 node-type=ssd
kubectl taint nodes ssd-1 workload-type=ssd:NoSchedule

# Nós padrão (sem taints)
kubectl label nodes standard-1 node-type=standard

Agora você cria um deployment para um treinamento de IA que exige GPU e prefere estar em um nó dedicado:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ml-training-job
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ml-training
  template:
    metadata:
      labels:
        app: ml-training
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node-type
                operator: In
                values:
                - gpu
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            preference:
              matchExpressions:
              - key: workload-type
                operator: In
                values:
                - gpu
      tolerations:
      - key: workload-type
        operator: Equal
        value: gpu
        effect: NoSchedule
      containers:
      - name: trainer
        image: tensorflow/tensorflow:latest-gpu
        resources:
          limits:
            nvidia.com/gpu: 1

E um deployment para PostgreSQL que prefere SSD e tolera o taint correspondente:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres-db
spec:
  replicas: 1
  selector:
    matchLabels:
      app: database
  template:
    metadata:
      labels:
        app: database
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            preference:
              matchExpressions:
              - key: node-type
                operator: In
                values:
                - ssd
      tolerations:
      - key: workload-type
        operator: Equal
        value: ssd
        effect: NoSchedule
      containers:
      - name: postgres
        image: postgres:14
        env:
        - name: POSTGRES_PASSWORD
          value: secret123
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
      volumes:
      - name: data
        emptyDir: {}

O treinamento de IA deve ir para GPU (required affinity) e tolera o taint. O PostgreSQL prefere SSD (preferred affinity) e tolera o taint, mas pode rodar em nós padrão se necessário. Aplicações genéricas sem tolerações não rodam em nós com taints.


Conclusão

Dominar Node Affinity, Taints e Tolerations é fundamental para otimizar o uso de recursos em Kubernetes. Node Affinity permite atrair pods para nós específicos através de labels e regras de afinidade (obrigatórias ou preferenciais), enquanto Taints criam barreiras repelindo pods, garantindo que nós especializados sejam usados apenas para suas finalidades. Tolerations são o mecanismo que permite exceções, permitindo que pods autorizados rodem em nós com taints. Juntos, esses recursos criam um sistema de controle fino sobre alocação de workloads, essencial em clusters heterogêneos com requisitos de hardware variados.


Referências


Artigos relacionados