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». Если вам интересно узнать подробнее о формате обучения и программе, познакомиться с преподавателем курса — приглашаем на день открытых дверей онлайн. Регистрация здесь.

Комментарии (0)