Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи? Написать
Дайджесты новостей
Схема равномерного распределения подов в Kubernetes по трем зонам доступности с помощью Topology Spread Constraints.

Равномерное распределение подов в Kubernetes: настройка Topology Spread Constraints для отказоустойчивости

Представьте классическую ситуацию: вы разворачиваете критически важный сервис в облачном кластере Kubernetes. Инфраструктура честно разделена на три независимые зоны доступности (дата-центра). В манифесте Deployment указано шесть реплик приложения — кажется, что сервис защищен от любых катаклизмов.

Но вот в одном из дата-центров происходит авария питания, и ваш сервис внезапно становится полностью недоступен. Расследование показывает неприятную картину: стандартный планировщик kube-scheduler, руководствуясь исключительно доступностью оперативной памяти и ядер процессора на серверах, разместил пять из шести ваших подов именно в том дата-центре, который обесточило.

Решить эту проблему раз и навсегда позволяет механизм Topology Spread Constraints, дающий декларативный контроль над географией размещения контейнеров.

Почему стандартного podAntiAffinity больше недостаточно

Долгое время инженеры пытались решать проблему концентрации с помощью правил анти-сродства (podAntiAffinity). Но этот механизм слишком категоричен:

  • Правило requiredDuringScheduling работает бинарно: оно либо разрешает запустить строго один под на узел, либо блокирует развертывание. Если у вас 10 подов и всего 3 сервера, планировщик просто откажется запускать «лишние» реплики, оставив их висеть в статусе Pending;
  • Правило preferredDuringScheduling работает слишком мягко: планировщик воспринимает его как ни к чему не обязывающее пожелание и при малейшей нехватке ресурсов охотно сваливает все поды на один хост.

Topology Spread Constraints устраняет эту крайность, вводя математически выверенную степень допустимого перекоса между зонами или серверами.

Математика перекоса: как рассчитывается maxSkew

Центральное понятие механизма — параметр maxSkew (максимальный перекос). Это предельная разница между числом подов вашего приложения в рассматриваемой зоне и минимальным количеством подов в любой другой подходящей зоне:

перекос = поды[текущая_зона] + 1 - минимум[подходящие_зоны]

Если значение maxSkew равно 1, планировщик никогда не допустит ситуации, при которой в зоне А работают три пода, пока в зоне Б запущен всего один. Разница между зонами никогда не превысит единицу.

Поведение при невозможности соблюсти баланс регулируется атрибутом whenUnsatisfiable:

  • DoNotSchedule: жесткий запрет. Под не запустится, если его появление нарушит баланс (полезно для строгих отказоустойчивых кластеров);
  • ScheduleAnyway: мягкий режим. Планировщик отдаст максимальный приоритет наименее загруженной зоне, но если свободных ресурсов там нет, запустит под там, где есть место.

Процедура аудита и выявления незащищенных сервисов

Перед настройкой ограничений необходимо убедиться, что узлы кластера размечены стандартными топологическими метками облачного провайдера, и найти уязвимые рабочие нагрузки:

# 1. Проверка наличия топологических меток зон доступности
kubectl get nodes --show-labels | grep -E "topology.kubernetes.io/(zone|region)"

# 2. Поиск подов, у которых не настроены ограничения распределения
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.topologySpreadConstraints == null) | "\(.metadata.namespace)/\(.metadata.name)"'

Новые политики для гибридных кластеров: Honor для taints и affinity

В современных версиях Kubernetes механизм получил важные улучшения: параметры nodeAffinityPolicy и nodeTaintsPolicy.

Раньше планировщик при подсчете минимального числа подов в зоне учитывал абсолютно все узлы, даже если они были помечены специальными ограничениями (taints) — например, предназначались исключительно под тяжелые базы данных или видеокарты. Из-за этого расчет минимального числа сбивался, и поды зависали в бесконечном ожидании.

Значение Honor указывает планировщику учитывать только реально доступные для приложения узлы:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
  namespace: production
spec:
  replicas: 6
  selector:
    matchLabels:
      app: payment-service
  template:
    metadata:
      labels:
        app: payment-service
    spec:
      topologySpreadConstraints:
        # Равномерное распределение по зонам доступности дата-центров
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          nodeAffinityPolicy: Honor
          nodeTaintsPolicy: Honor
          labelSelector:
            matchLabels:
              app: payment-service
        # Дополнительная балансировка по физическим серверам внутри зон
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app: payment-service
      containers:
        - name: web
          image: registry.example.com/payment:v2.4.1
          ports:
            - containerPort: 8080

Практический регламент внедрения в продакшен

Золотое правило настройки надежности: комбинируйте уровни топологии. На уровне зон доступности (topology.kubernetes.io/zone) используйте строгий maxSkew: 1 с режимом DoNotSchedule, гарантируя, что падение целого дата-центра уничтожит лишь расчетную долю реплик. А на уровне отдельных серверов (kubernetes.io/hostname) задавайте мягкий режим ScheduleAnyway, предотвращая проблемы при локальной нехватке серверных мощностей.