Pular para o conteúdo principal

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.

Alt text

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!

Alt text

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ÊNTICOS
  • NotIn: Irá comparar se os valores das chaves são DIFERENTES
  • Exists: Só quer saber se a chave existe não importa o valor
  • DoesNotExist: Só quer saber se a chave NÁO EXISTE não importa o valor
  • Gt: Maior que, igual o operador in mas vai comprar números mesmo que seja string
  • Lt: 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.