Kubernetes: Node affinity, pod affinity, pod anti affinity

Концепт affinity и anti affinity проще всего описать на примере магнитов которые могут притягиваться друг к другу (affinity) или отталкиваться (anti affinity)

Affinity - имеем деплоймент dictionaries и dictionaries-redis, было бы круто, что бы они бежали на одном и том же сервере, соответсвенно они должны как бы “притягиваться” друг к другу

Anti affinity - имеем кластер эластика из трех нод, было бы круто что бы каждый бежал на отдельном сервере - соответственно они должны как бы “отталкиваться” друг от друга

Node affinity - говорим что бы приложенька запускалась на app нодах

В этом примере мы просим что бы наше приложение запускалось на нодах с лейбочкой poolDestination=app

Примечание: сейчас у нас в кластере есть infra и app ноды, первые не предпологают хостить приложения, именно по этому мы тут используем именно required, а не preferred. В будущем этот же концепт будет использоваться для других видов серверов (условно preemtible, сервера с gpu и т.п.)

Pod affinity - держим редис по ближе к приложению

В этом примере, для деплоймента dictionaries-redis мы выставляем pod affinity в app=dictionary, соответственно приложение будет стараться запуститься на том же сервере где уже бежит dictionary

Примечание: мы намерянно используем preferred т.к. required вылезет боком, ведь если сам dictionaries запущен больше чем в одном экземпляре и на разных серверах то этот affinity просто разорвет редиску надвое

Pod anti affinity - размазываем приложение по кластеру

В этом примере мы просим сервис dictionary по возможности запускаться на тех серверах где он еще не бежит (размазываем по кластеру)

Примечание: тут так же как и с affinity мы используем именно preferred, а не required, ведь иначе куберу пришлось бы добавлять по целому серверу на каждый экземпляр приложения

ВАЖНО: required в pod affinity и anti affinity может наделать делов и стоит его избегать

Дополнительная информация

Дальше серия дополнительных примеров и уже не обязательна для ознакомления

topologyKey

topologyKey используется для дележки доступных серверов, во всех наших сценариях мы используем kubernetes.io/hostname что равносильно дележке до конретного сервера.

Идея этой штуки для чуть более больших кластеров, предположим у нас глобальное приложение и есть сервера в штатах и европе и у каждой ноды есть соответствующая лейбочка, тогда мы бы могли использовать ее за вместо hostname и работать уже с группами серверов

Другие примеры - наличие серверов с разными операционными системами или архитектурами (тот же arm)

Example deployments

Далее парочка примеров которые можно задеплоить что бы глазами увидеть как оно работает на деле

magnet-node-app.yml

apiVersion: v1
kind: Pod
metadata:
  name: magnet-node-app
spec:
  containers:
    - name: magnet-node-app
      image: nginx
  affinity:
    nodeAffinity:
      # allow deployment only to this nodes: kubectl get nodes -l poolDestination=app
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: poolDestination
                operator: In
                values:
                  - app

deploy

kubectl apply -f magnet-node-app.yml

check

kubectl get po magnet-node-app -o wide

result: приложение всегда будет приезжать но app ноду

cleanup

kubectl delete -f magnet-node-app.yml

magnet-pods.yml

---
apiVersion: v1
kind: Pod
metadata:
  name: magnet-pods
spec:
  containers:
    - name: magnet-pods
      image: nginx
---
apiVersion: v1
kind: Pod
metadata:
  name: magnet-pods-redis
spec:
  containers:
    - name: magnet-pods-redis
      image: redis
  affinity:
    podAffinity:
      # try to deploy to this node: kubectl get po magnet-pods -o wide
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 1
          podAffinityTerm:
            labelSelector:
              matchExpressions:
                - key: app
                  operator: In
                  values:
                    - magnet-pods
            topologyKey: kubernetes.io/hostname

deploy

kubectl apply -f magnet-pods.yml

check

kubectl get po magnet-pods -o wide
kubectl get po magnet-pods -o wide

result: redis pod будет стрататься запуститься на той же ноде что и приложение

cleanup

kubectl delete -f magnet-pods.yml

spread-pods.yml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spread-pods
  labels:
    app: spread-pods
spec:
  replicas: 3
  selector:
    matchLabels:
      app: spread-pods
  template:
    metadata:
      labels:
        app: spread-pods
    spec:
      containers:
        - name: spread-pods
          image: nginx
      affinity:
        podAntiAffinity:
          # try to deploy to any other nodes than this: kubectl get po -l app=spread-pods -o wide
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 1
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - spread-pods
                topologyKey: kubernetes.io/hostname

deploy

kubectl apply -f spread-pods.yml

check

kubectl get po -l app=spread-pods -o wide

result: кубер будет стараться запустить каждый экземпляр приложения на отдельном сервере

cleanup

kubectl delete -f spread-pods.yml

magnet-combi.yml

---
apiVersion: v1
kind: Pod
metadata:
  name: magnet-combi-redis
spec:
  containers:
    - name: magnet-combi-redis
      image: redis
  affinity:
    podAffinity:
      # try to deploy to one of nodes from here: kubectl get po -l app=magnet-combi -o wide
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 1
          podAffinityTerm:
            labelSelector:
              matchExpressions:
                - key: app
                  operator: In
                  values:
                    - magnet-combi
            topologyKey: kubernetes.io/hostname
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: magnet-combi
  labels:
    app: magnet-combi
spec:
  replicas: 3
  selector:
    matchLabels:
      app: magnet-combi
  template:
    metadata:
      labels:
        app: magnet-combi
    spec:
      containers:
        - name: magnet-combi
          image: nginx
      affinity:
        podAntiAffinity:
          # try to deploy to any other nodes than this: kubectl get po -l app=magnet-combi -o wide
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 1
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - magnet-combi
                topologyKey: kubernetes.io/hostname

deploy

kubectl apply -f magnet-combi.yml

check

kubectl get po -l app=magnet-combi -o wide
kubectl get po magnet-combi-redis -o wide

result: nginx должен будет постараться задеплоиться на 3 разных сервера, redis должен будет постараться запуститься на одном из них

cleanup

kubectl delete -f magnet-combi.yml