← Назад к вопросам

Как поднять контейнер в двух репликах, чтобы они не встретились на одной worker-ноде

2.0 Middle🔥 181 комментариев
#Kubernetes

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

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Обеспечение anti-affinity для Docker Swarm и Kubernetes

Для гарантии, что реплики контейнера не окажутся на одной worker-ноде, используются механизмы anti-affinity (распределенности). Конкретная реализация зависит от оркестратора.

Docker Swarm Mode

В Docker Swarm используется распределение задач (tasks) и ограничения размещения (placement constraints).

Способ 1: Глобальная служба (global service) При создании службы в глобальном режиме Docker автоматически запускает ровно одну задачу на каждой подходящей ноде в кластере. Это гарантирует, что реплики не встретятся.

docker service create --name my_global_service --mode global nginx:alpine

Способ 2: Ограничения размещения с anti-affinity Для реплицируемой службы (replicated service) можно использовать --placement-pref, чтобы задания не размещались на одной ноде.

# Создание overlay-сети (если требуется)
docker network create -d overlay my_network

# Создание службы с двумя репликами и anti-affinity
docker service create \
  --name my_service \
  --replicas 2 \
  --placement-pref 'spread=node.labels.az' \
  --network my_network \
  nginx:alpine

Ключевой параметр spread=node.labels.az указывает Swarm максимально "размазать" задачи по значениям метки az (например, availability zone) нод. Можно использовать и другие метки, например, spread=node.id для распределения по уникальным ID нод.

Kubernetes

В Kubernetes управление размещением Pod'ов осуществляется через affinity/anti-affinity правила в спецификации Pod или Deployment.

Пример: Anti-affinity на уровне Pod (podAntiAffinity)

Создадим Deployment с двумя репликами и правилом, которое не позволит планировщику разместить Pod'ы на одной ноде.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - nginx
            topologyKey: "kubernetes.io/hostname"

Ключевые моменты этого манифеста:

  • podAntiAffinity: Определяет правило "отталкивания" Pod'ов.
  • requiredDuringSchedulingIgnoredDuringExecution: Жесткое правило. Если планировщик не может найти подходящую ноду, Pod останется в состоянии Pending. Есть более мягкий вариант preferredDuringSchedulingIgnoredDuringExecution.
  • labelSelector: Указывает, к каким Pod'ам применять правило. Здесь правило применяется к Pod'ам с меткой app: nginx, включая сами Pod'и этого Deployment. Это заставляет их "избегать" друг друга.
  • topologyKey: "kubernetes.io/hostname": Это самый важный параметр. Он определяет домен топологии. Планировщик гарантирует, что в рамках одного значения этого ключа (то есть на одной физической или виртуальной ноде) не будет больше одного Pod'а, соответствующего labelSelector. Использование стандартной метки ноды kubernetes.io/hostname — самый прямой способ гарантировать размещение на разных нодах.

Более надежная стратегия:

В продакшн-средах часто используют метки failure-domain, такие как topology.kubernetes.io/zone, чтобы распределять Pod'ы не только по разным нодам, но и по разным зонам доступности (Availability Zones), повышая отказоустойчивость.

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchLabels:
          app: nginx
      topologyKey: "topology.kubernetes.io/zone"
    - labelSelector:
        matchLabels:
          app: nginx
      topologyKey: "kubernetes.io/hostname"

Этот пример определяет два правила: сначала требуется разместить Pod'ы в разных зонах, а затем, внутри зоны, на разных нодах.

Выводы и рекомендации

  • Для Docker Swarm предпочтительным способом является использование глобального режима (--mode global) для системных сервисов или правил размещения с spread (--placement-pref) для контроля распределения.
  • Для Kubernetes стандартом де-факто является Pod Anti-Affinity с topologyKey: "kubernetes.io/hostname". Это четкое и ясное указание планировщику.
  • Важно помнить: Если в кластере меньше worker-нод, чем требуется реплик, "лишние" Pod'ы или задачи останутся в состоянии ожидания (Pending в K8s), так как правило не может быть выполнено.
  • Для проверки в Kubernetes используйте команды:
    kubectl get pods -o wide # Покажет, на каких нодах запущены Pod'ы
    kubectl describe pod <pod_name> # В событиях (Events) можно увидеть логику принятия решений планировщиком
    

Таким образом, использование механизмов anti-affinity — обязательная практика для развертывания отказоустойчивых stateful или критичных приложений, гарантирующая, что выход из строя одной физической ноды не приведет к полной недоступности сервиса.