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:
- Nós GPU exclusivos para workloads que usam GPU
- Nós SSD preferidos para banco de dados
- 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.