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:
- appdeploy
kubectl apply -f magnet-node-app.ymlcheck
kubectl get po magnet-node-app -o wideresult: приложение всегда будет приезжать но app ноду
cleanup
kubectl delete -f magnet-node-app.ymlmagnet-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/hostnamedeploy
kubectl apply -f magnet-pods.ymlcheck
kubectl get po magnet-pods -o wide
kubectl get po magnet-pods -o wideresult: redis pod будет стрататься запуститься на той же ноде что и приложение
cleanup
kubectl delete -f magnet-pods.ymlspread-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/hostnamedeploy
kubectl apply -f spread-pods.ymlcheck
kubectl get po -l app=spread-pods -o wideresult: кубер будет стараться запустить каждый экземпляр приложения на отдельном сервере
cleanup
kubectl delete -f spread-pods.ymlmagnet-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/hostnamedeploy
kubectl apply -f magnet-combi.ymlcheck
kubectl get po -l app=magnet-combi -o wide
kubectl get po magnet-combi-redis -o wideresult: nginx должен будет постараться задеплоиться на 3 разных сервера, redis должен будет постараться запуститься на одном из них
cleanup
kubectl delete -f magnet-combi.yml