Kubernetes произвел настоящую революцию в распределенных вычислениях. Хотя он решает ряд сверхсложных проблем, появляются и новые вызовы. Одна из таких проблем - обеспечение того, чтобы кластеры Kubernetes были спроектированы с учетом доменов отказов. Проектирование с учетом доменов отказов включает в себя такие аспекты, как предоставление инфраструктуры в зонах доступности, обеспечение того, чтобы физические серверы находились на разных стойках, также необходимо убедиться, что поды, поддерживающие ваше приложение, не окажутся в одном и том же физическом воркере Kubernetes.
Для решения последней задачи можно использовать правила совместного или раздельного существования между подами (inter-pod affinity and anti-affinity rules), и в официальной документации Kubernetes они описаны очень хорошо:
“Сходство между подами (inter-pod affinity) и анти-сходство (anti-affinity) позволяет вам ограничивать, на каких узлах может быть запланирован ваш под, основываясь на метках подов, которые уже запущены на узле, а не на основе меток на узлах. Правила имеют вид "этот под должен (или, в случае anti-affinity, не должен) работать на узле X, если на этом X уже работает один или несколько подов, удовлетворяющих правилу Y". Y выражается как LabelSelector с опциональным ассоциированным списком пространств имен; в отличие от узлов, поскольку поды разделены по именам (и поэтому метки на поды также неявно разделены по именам), селектор меток должен указать, к каким пространствам имен он должен применяться. Концептуально X - это домен топологии, такой как узел, стойка, зона облачного провайдера, регион облачного провайдера и т. д. Вы выражаете его с помощью topologyKey, который является ключом для метки узла, используемой системой для обозначения такого топологического домена; например, см. ключи меток, перечисленные выше в разделе "Интерлюдия: встроенные метки узлов".
Affinity могут быть определены с помощью утверждения о принадлежности в манифесте развертывания. Например, дан кластер из 3 узлов:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
test-control-plane Ready master 22d v1.18.2
test-worker Ready <none> 22d v1.18.2
test-worker2 Ready <none> 22d v1.18.2
test-worker3 Ready <none> 22d v1.18.2
Вы можете создать affinity-правило, добавив affinity-строфу в спецификацию подов:
$ cat nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- nginx
topologyKey: "kubernetes.io/hostname"
В разделе Affinity много всего происходит, поэтому я разберу каждую часть. Affinity предоставляет следующие 3 ограничения планирования:
$ kubectl explain pod.spec.affinity
KIND: Pod
VERSION: v1
RESOURCE: affinity <Object>
DESCRIPTION:
If specified, the pod's scheduling constraints
Affinity is a group of affinity scheduling rules.
FIELDS:
nodeAffinity <Object>
Describes node affinity scheduling rules for the pod.
podAffinity <Object>
Describes pod affinity scheduling rules (e.g. co-locate this pod in the
same node, zone, etc. as some other pod(s)).
podAntiAffinity <Object>
Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod
in the same node, zone, etc. as some other pod(s)).
В приведенном выше примере я использую правило podAntiAffinity, которое можно применять, чтобы избежать размещения двух похожих подов вместе. Карта labelSelector содержит выражение для соответствия подам, к которым будут применены affinity-правила. И, наконец, topologyKey используется для указания элемента, к которому вы хотите применить это правило. В этом примере я указал ключ топологии hostname, который предотвратит размещение двух подов, соответствующих labelSelector, на одном узле.
После создания развертывания мы можем проверить то, что каждый под был запланирован на уникальный рабочий узел:
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-75db5d94dc-4w8q9 1/1 Running 0 72s 10.11.3.2 test-worker3 <none> <none>
nginx-75db5d94dc-5wwm2 1/1 Running 0 72s 10.11.1.5 test-worker <none> <none>
nginx-75db5d94dc-cbxs5 1/1 Running 0 72s 10.11.2.2 test-worker2 <none> <none>
Но при любой реализации affinity всегда есть тонкости, о которых необходимо знать. В приведенном выше примере, что будет если потребуется масштабировать развертывание для обработки дополнительной нагрузки? Это можно увидеть наглядно:
$ kubectl scale deploy nginx --replicas 6
Если мы просмотрим список подов:
$ kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-75db5d94dc-2sltl 0/1 Pending 0 21s
nginx-75db5d94dc-4w8q9 1/1 Running 0 14m
nginx-75db5d94dc-5wwm2 1/1 Running 0 14m
nginx-75db5d94dc-cbxs5 1/1 Running 0 14m
nginx-75db5d94dc-jxkqs 0/1 Pending 0 21s
nginx-75db5d94dc-qzxmb 0/1 Pending 0 21s
Мы видим, как новые поды застряли в состоянии Pending. Это потому, что у нас всего три узла, и affinity-правило не позволяет запланировать два одинаковых пода на один узел. Планировщик Kubernetes отлично справляется со своей работой, но иногда необходимо немного больше контроля над тем, где окажутся ваши поды. Это особенно актуально, когда вы используете несколько зон доступности в "облаке", и вам нужно обеспечить распределение подов между ними. Я вернусь к этой теме в одной из следующих статей, где я обсужу ключи топологии зон и приоритеты распределения.
Материал подготовлен в рамках курса «Инфраструктурная платформа на основе Kubernetes». Если вам интересно узнать подробнее о формате обучения и программе, познакомиться с преподавателем курса — приглашаем на день открытых дверей онлайн. Регистрация здесь.