Node Selector e Node Affinity
Node Selector
O nodeSelector utiliza Labels para especificar quais Nodes queremos direcionar. Podemos agrupar Nodes também usando Labels e definir Pods para serem implantados apenas nos Nodes que possuam essas Labels correspondentes.

Para colocar uma Label em um Node.
kubectl label nodes k3d-k3d-cluster-agent-1 size=large
node/k3d-k3d-cluster-agent-1 labeled
kubectl describe node k3d-k3d-cluster-agent-1
Name: k3d-k3d-cluster-agent-1
Roles: <none>
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/instance-type=k3s
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=k3d-k3d-cluster-agent-1
kubernetes.io/os=linux
node.kubernetes.io/instance-type=k3s
size=large
kubectl get node k3d-k3d-cluster-agent-1 --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k3d-k3d-cluster-agent-1 Ready <none> 70d v1.27.4+k3s1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=k3s,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k3d-k3d-cluster-agent-1,kubernetes.io/os=linux,node.kubernetes.io/instance-type=k3s,size=large
kubectl label nodes k3d-k3d-cluster-agent-1 size-
node/k3d-k3d-cluster-agent-1 unlabeled
Porém nem sempre o nodeSelector é suficiente para resolver os nossos problemas quando as exigências são mais complexas
Node Affinity
Nesse contexto, nos deparamos com uma limitação do nodeSelector, que não consegue lidar com complexidades como a necessidade de selecionar Nodes de determinado tamanho, como large ou medium, mas evitar os de tamanho small. Com o nodeSelector, não é possível criar expressões do tipo 'um ou outro' ou até mesmo negar uma condição específica.
Com grandes poderes vem grandes complexidades.
Esse nodeSelector e esse affinity fazem a mesma coisa!

Toda expressão de affinity vem abaixo da key affinity:
Existem affinity para Nodes e Pods, então temos os possíveis iniciais:
Esses valores não são definidos, mas podem ser setados.
apiVersion: v1
...
spec:
containers:
....
affinity:
nodeAffinity: null
podAffinity: null
podAntiAffinity: null
O nodeAffinity irá definir uma afinidade para que o Pod seja agendado para um Node.
O podAffinity e o podAntiAffinity definem a afinidade entre os Pods. Por exemplo, podemos configurar para que um Pod seja colocado onde há outro Pod específico (podAffinity) ou evite ser colocado onde há Pods de um tipo específico (podAntiAffinity). Um exemplo prático seria garantir que os Pods A e B estejam sempre no mesmo Node, não apenas no mesmo grupo de Nodes com a mesma Label, para minimizar o tráfego de rede e melhorar o desempenho.
Dentro desses tipos de afinidade podemos ter essas expressões:
-
requiredDuringSchedulingIgnoredDuringExecution: Define regras OBRIGATÓRIAS para o Scheduler. Se um Node não atender a essas regras, o Pod não será agendado nele, mas se já tiver agendado nele não precisa remover do Node se uma Label for removida por exemplo. -
preferredDuringSchedulingIgnoredDuringExecution: Igual à regra anterior mas depois de tentar ao máximo se não conseguir e houver como ser agendado para outro, pode deixar, pois não é obrigatório. -
requiredDuringSchedulingRequiredDuringExecution: A diferença de não ignorar é que se as condições mudarem, por exemplo se uma Label for removida, também removerá o Pod. NÁO É POSSÍVEL TER ESSA EXPRESSÃO JUNTO COM AS OUTRAS-
AINDA NÃO FUNCIONA COM O nodeAffinity
-
-
preferredDuringSchedulingRequiredDuringExecution: Essa expressão também é possível. NÁO É POSSÍVEL TER ESSA EXPRESSÃO JUNTO COM AS OUTRAS-
AINDA NÃO FUNCIONA COM O nodeAffinity
-
Isso seria possível.
apiVersion: v1
...
spec:
containers:
....
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
bloco
preferredDuringSchedulingIgnoredDuringExecution:
bloco
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
bloco
preferredDuringSchedulingIgnoredDuringExecution:
bloco
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
bloco
preferredDuringSchedulingIgnoredDuringExecution:
bloco
Isso seria possível, mas não funciona ainda.
apiVersion: v1
...
spec:
containers:
....
affinity:
nodeAffinity:
requiredDuringSchedulingRequiredDuringExecution: # Em particular não é possível usar essa expressão ainda, é uma issue no Kubernetes em desenvolvimento
bloco
podAffinity:
requiredDuringSchedulingRequiredDuringExecution:
bloco
podAntiAffinity:
requiredDuringSchedulingRequiredDuringExecution:
bloco
Isso ainda seria possível
apiVersion: v1
...
spec:
containers:
....
affinity:
nodeAffinity:
requiredDuringSchedulingRequiredDuringExecution: # Em particular não é possível usar essa expressão ainda, é uma issue no Kubernetes em desenvolvimento
bloco
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
bloco
preferredDuringSchedulingIgnoredDuringExecution:
bloco
podAntiAffinity:
requiredDuringSchedulingRequiredDuringExecution:
bloco
Mas isso não é possível
apiVersion: v1
...
spec:
containers:
....
affinity:
nodeAffinity:
requiredDuringSchedulingRequiredDuringExecution: # Em particular não é possível usar essa expressão ainda, é uma issue no Kubernetes em desenvolvimento
bloco
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
bloco
requiredDuringSchedulingRequiredDuringExecution: # RequiredDuringExecution não combina com ninguém
bloco
podAntiAffinity:
requiredDuringSchedulingRequiredDuringExecution:
bloco
Agora vamos nos concentrar no Bloco:
O bloco pode ser nodeSelectorTerms se estiver usando nodeAffinity ou podSelectorTerms se estiver usando podAffinity ou podAntiAffinity
nodeSelectorTerms e podSelectorTerms habilitam o possível
OU. Ou uma expressão, ou outra expressão, ou outra, etc. Poderia ser colocado no bloco diretamente uma matchExpressions sem estar dentro dos blocos terms? Sim poderia. Se você manter também não faz mal e é MELHOR.
Então vamos ao bloco.
apiVersion: v1
kind: Pod
...
spec:
containers:
....
affinity:
nodeAffinity:
requiredDuringSchedulingRequiredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: key1
operator: In
values:
- value1
- value2
# OU Regra 2
- matchFields:
- key: metadata.name
operator: In
values:
- node-1
Agora para entender melhor o que eu disse, os dois affinities abaixo são a mesma coisa.
affinity:
nodeAffinity:
requiredDuringSchedulingRequiredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: key1
operator: In
values:
- value1
- value2
# MAS NESSE AFFINITY PERDEU A OPÇÃO DO OU ENTÃO USE SEMPRE O DE CIMA
affinity:
nodeAffinity:
requiredDuringSchedulingRequiredDuringExecution:
matchExpressions:
- key: key1
operator: In
values:
- value1
- value2
Agora vamos entender os possíveis operator que podemos usar.
In: Irá comparar se os valores das chaves são IDÊNTICOSNotIn: Irá comparar se os valores das chaves são DIFERENTESExists: Só quer saber se a chave existe não importa o valorDoesNotExist: Só quer saber se a chave NÁO EXISTE não importa o valorGt: Maior que, igual o operador in mas vai comprar números mesmo que seja stringLt: menor que
No caso do Exists e do DoesNotExist um exemplo
affinity:
nodeAffinity:
requiredDuringSchedulingRequiredDuringExecution:
matchExpressions:
- key: key1
operator: Exists
Se ambas forem aplicadas a um Pod nodeSelector tem precedência sobre o affinity.
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: key1
operator: In
values:
- value1
- value2
nodeSelector:
key2: value3
containers:
- name: my-container
image: my-image
Moral da história... Se você domina o affinity você não precisa do nodeSelector, mas é bom conhecer.