Самовосстановление и отказоустойчивость в Kubernetes: probes, реплики и стратегии обновления | AdminWiki

Самовосстановление и отказоустойчивость в Kubernetes: probes, реплики и стратегии обновления

26 сентября 2026 14 мин. чтения
Содержание статьи

Отказоустойчивость в Kubernetes держится на трёх независимых уровнях: kubelet перезапускает контейнеры внутри пода, контроллеры следят за числом подов, scheduler перепланирует поды с упавших узлов. Чтобы сервис переживал сбои без ручного вмешательства, настраивают четыре вещи: probes, количество реплик, PodDisruptionBudget и стратегию обновления. Отдельная зона риска - etcd, хранилище состояния всего кластера.

Коротко о логике работы. Liveness probe перезапускает зависший контейнер, readiness probe убирает неготовый под из балансировки, startup probe даёт медленно стартующему приложению время без ложных перезапусков. Две-три реплики плюс PDB с minAvailable: 2 позволяют пережить отказ узла и плановый drain. Rolling update с maxUnavailable: 0 обновляет версию без простоя, blue-green и canary добавляют контроль риска.

Ниже разбираем каждый механизм до уровня манифеста: параметры probes, YAML для Deployment и PDB, примеры blue-green и canary, команды для etcd и набор проверок, которые стоит прогнать перед выходом в прод.

Как Kubernetes обеспечивает самовосстановление и отказоустойчивость

Самовосстановление работает без внешних скриптов. Достаточно, чтобы объект в API описывал желаемое состояние: контроллеры сравнивают его с текущим и устраняют расхождение. Упал контейнер - kubelet поднимет его снова. Удалили под - ReplicaSet создаст замену. Узел ушёл в NotReady - поды переедут на живые узлы.

Устройство control plane и объектов API, включая Pods, Deployments и Services, разбираем в материале про архитектуру кластера. Здесь важен практический вывод: самовосстановление включается самой моделью декларативных объектов, а не дополнительной настройкой.

Роль kubelet и контроллеров в перезапуске подов

kubelet работает на каждом узле и следит за контейнерами пода. Поведение при завершении контейнера задаёт restartPolicy: Always перезапускает всегда, OnFailure только при ненулевом коде выхода, Never оставляет контейнер остановленным. При серии падений под уходит в CrashLoopBackOff, а задержка между попытками растёт экспоненциально: 10 с, 20 с, 40 с и дальше до пяти минут.

Контроллеры отвечают за количество подов, а не за их внутреннее состояние. ReplicaSet сравнивает фактическое число подов с spec.replicas и создаёт либо удаляет поды. Deployment управляет ReplicaSet и историей версий. Ручное удаление пода проверяется за секунды:

kubectl delete pod nginx-7d8f9c6b5-xk2lp
kubectl get pods -l app=nginx -w

Через несколько секунд в выводе появится новый под с другим суффиксом, а счётчик AGE сбросится. Если под не создаётся, смотрите Events в описании ReplicaSet: чаще всего причина в квотах, ресурсах или невыполнимых требованиях affinity.

Одна деталь окружения, которую легко упустить: kubelet и container runtime должны использовать один и тот же cgroup driver и одинаковую конфигурацию. Иначе поведение контейнеров становится непредсказуемым. Требование зафиксировано в документации по container runtime, там же описаны поддерживаемые CRI-рантаймы.

Реакция на отказ узла: перепланирование подов

Когда узел перестаёт отвечать, контроллер узла помечает его как NotReady. По истечении таймаута (по умолчанию около пяти минут) поды на нём переводятся в Terminating и удаляются из API, а контроллеры создают замены на доступных узлах. Время недоступности складывается из таймаута обнаружения, выбора нового узла, скачивания образа и прохождения readiness probe.

Два условия обязательны. Первое: replicas больше единицы, иначе сервис недоступен всё время перепланирования. Второе: корректные probes, по которым Service понимает, что под готов принимать трафик.

Чтобы реплики не собирались на одном узле или в одной зоне, задайте распределение через topologySpreadConstraints:

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: api

Альтернатива - podAntiAffinity с topologyKey: kubernetes.io/hostname. Правило гарантирует, что два пода одной роли не окажутся на одном узле, и отказ машины не уносит весь сервис.

Настройка probes: liveness, readiness и startup

Probes - самый частый источник ложных перезапусков. Контейнер, которому не хватает времени на старт, циклически убивается kubelet, хотя приложение исправно. Три типа проверок решают разные задачи, и их нельзя заменять друг другом.

ProbeЧто проверяетРеакция при провалеТипичный сценарий
startupProbeПриложение завершило инициализациюПерезапуск контейнераДолгий прогрев кэша, миграции
livenessProbeПроцесс жив и не зависПерезапуск контейнераДедлок, зависший воркер
readinessProbeПод готов принимать трафикИсключение из EndpointsПотеря соединения с БД, перегрузка

Liveness probe: когда перезапускать контейнер

Liveness probe проверяет, что процесс отвечает. Если проверка не проходит failureThreshold раз подряд, kubelet перезапускает контейнер. Проверка должна быть дешёвой и не зависеть от внешних сервисов: недоступная база не повод убивать под, это задача readiness.

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  timeoutSeconds: 2
  failureThreshold: 3
  successThreshold: 1

С таким набором контейнер перезапускается после трёх провалов по 5 секунд, то есть примерно через 15 секунд реального зависания. Агрессивные значения опасны: timeoutSeconds: 1 при пиковой нагрузке даст ложные срабатывания и шторм перезапусков. Держите отдельный эндпоинт /healthz, который проверяет только внутреннее состояние процесса, и отдельный /ready для зависимости от БД и кэша.

Для не-HTTP сервисов используйте TCP-сокет или команду:

livenessProbe:
  tcpSocket:
    port: 5432
  initialDelaySeconds: 15
  periodSeconds: 10

livenessProbe:
  exec:
    command: ["pg_isready", "-U", "postgres"]
  periodSeconds: 10
  failureThreshold: 6

Readiness probe: как не отправлять трафик на неготовый под

Readiness probe определяет, попадёт ли под в список Endpoints сервиса. Провал проверки не перезапускает контейнер, под просто выводится из балансировки. Это ключевой механизм rolling update: новый под не получит трафик, пока не станет готов, а старый не будет удалён раньше времени.

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  timeoutSeconds: 2
  failureThreshold: 2
  successThreshold: 1

Эндпоинт /ready должен проверять зависимости, без которых запросы не обрабатываются: соединение с базой, доступность Redis, загруженные конфиги. Если проверка ходит в БД на каждый вызов, поставьте periodSeconds не меньше 5-10 секунд, чтобы не создавать лишнюю нагрузку. successThreshold больше единицы нужен для приложений, которые прогреваются постепенно и отвечают неустойчиво на первых запросах.

Startup probe: защита медленно стартующих приложений

Startup probe закрывает классическую проблему: приложение стартует две минуты, а liveness probe убивает его через 30 секунд. Пока startup probe не завершилась успешно, liveness и readiness не запускаются вообще.

startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  periodSeconds: 10
  failureThreshold: 30

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  periodSeconds: 10
  failureThreshold: 3

Расчёт простой: 30 попыток по 10 секунд дают до пяти минут на старт. Как только startup probe прошла, в дело вступает liveness с обычными интервалами. Startup probe появилась в Kubernetes 1.16 и стабильна с 1.20, поэтому на актуальных версиях feature gate не нужен.

Реплики и PodDisruptionBudget: сколько подов нужно для отказоустойчивости

Одна реплика в проде означает простой при любом обновлении, drain узла или падении пода. Минимум для отказоустойчивого сервиса - две реплики, практический стандарт для критичных сервисов - три. Три реплики позволяют потерять один под и сохранить запас на время восстановления.

ReplicaSet и Deployment: управление количеством подов

ReplicaSet следит за числом подов, Deployment управляет ReplicaSet и версиями. На практике вы описываете Deployment, а ReplicaSet создаётся автоматически.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry.example.com/api:1.8.4
          ports:
            - containerPort: 8080

Проверка состояния выполняется двумя командами:

kubectl get deploy api
kubectl get rs -l app=api

В колонке READY у Deployment должно быть 3/3, у ReplicaSet - ровно три желаемых пода. Готовые манифесты с лимитами ресурсов и probes собраны в руководстве по Deployment, включая откат версии без простоя.

PodDisruptionBudget: защита от одновременного удаления подов

PodDisruptionBudget ограничивает количество подов, которые одновременно недоступны при добровольных disruptions: drain узла, обновление кластера, удаление через Eviction API. Без PDB команда kubectl drain может выселить все поды сервиса сразу.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: api

С тремя репликами и minAvailable: 2 выселение третьего пода заблокируется до тех пор, пока один из оставшихся не станет готов. Альтернатива - maxUnavailable: 1, что даёт тот же эффект при трёх репликах, но по-другому масштабируется при изменении replicas.

kubectl get pdb
kubectl describe pdb api-pdb
kubectl drain node-2 --ignore-daemonsets --delete-emptydir-data

В describe смотрите поля ALLOWED DISRUPTIONS и CURRENT HEALTHY: если разрешённых выселений ноль, drain зависнет в ожидании. И помните про границу применимости: PDB защищает только от плановых работ. Аппаратный сбой узла, отключение питания или падение зоны PDB не остановит. Подробные примеры связки PDB, StatefulSet и PersistentVolume приведены в гайде по отказоустойчивому кластеру.

Стратегии обновления: rolling update, blue-green и canary

Стратегия определяет, как новая версия появляется в проде и насколько быстро можно откатиться. Для большинства сервисов подходит rolling update, для критичных релизов с миграциями схемы - blue-green, для проверки гипотез на живом трафике - canary.

Rolling update: постепенное обновление без простоя

Rolling update заменяет поды партиями. Управляют процессом два параметра: maxSurge задаёт, сколько подов можно создать сверх желаемого числа, maxUnavailable - сколько подов допустимо вывести из строя одновременно.

spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  minReadySeconds: 10
  progressDeadlineSeconds: 600
  template:
    spec:
      containers:
        - name: api
          image: registry.example.com/api:1.9.0
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            periodSeconds: 5
            failureThreshold: 2

Схема с maxUnavailable: 0 поднимает новый под, ждёт readiness, только затем удаляет старый. Простой нулевой, но требуется запас ресурсов на один дополнительный под. minReadySeconds добавляет паузу перед признанием пода готовым: полезно, если приложение падает через несколько секунд после старта.

kubectl rollout status deploy/api
kubectl rollout history deploy/api
kubectl rollout undo deploy/api --to-revision=3

Прогресс удобно отслеживать по колонкам READY и UP-TO-DATE, а причину застревания искать в ReplicaSet через kubectl describe. Развёрнутое сравнение параметров и способов отката есть в обзоре стратегий обновления.

Blue-green deployment: переключение трафика между версиями

Blue-green держит два окружения одновременно. Оба Deployment несут общий label app: web и различаются меткой версии, а Service выбирает активную версию через selector.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-blue
  labels:
    version: blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
      version: blue
  template:
    metadata:
      labels:
        app: web
        version: blue
    spec:
      containers:
        - name: web
          image: registry.example.com/web:1.8.4
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-green
  labels:
    version: green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
      version: green
  template:
    metadata:
      labels:
        app: web
        version: green
    spec:
      containers:
        - name: web
          image: registry.example.com/web:1.9.0
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
    version: blue
  ports:
    - port: 80
      targetPort: 8080

Переключение выполняется правкой одного поля:

kubectl patch svc web -p '{"spec":{"selector":{"app":"web","version":"green"}}}'
kubectl get endpoints web

Откат занимает столько же времени, сколько переключение: меняете selector обратно на blue. Цена подхода - двойной расход ресурсов и необходимость совместимости схемы данных между версиями. Для инкрементальных релизов blue-green избыточен.

Canary deployment: постепенный выпуск на часть трафика

Canary отправляет малую долю запросов на новую версию, пока основная масса идёт на стабильную. В базовом варианте достаточно двух Deployment с общим label и Service, который выбирает поды по нему, а точные веса задаёт Ingress-контроллер.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-canary
                port:
                  number: 80

Вес поднимают постепенно: 5%, 10%, 25%, 50%, затем полный перевод. Ключевое условие - метрики. Без сравнения latency, доли ошибок и насыщения ресурсов между stable и canary эксперимент превращается в лотерею: вес можно увеличивать только пока различия остаются в пределах допустимого. Снимать canary нужно мгновенно, поэтому Deployment canary держат отдельно от stable.

СтратегияСложностьРиск для пользователейСкорость откатаРасход ресурсов
Rolling updateНизкаяСредний: новая версия сразу на всехОткат через rollout undoПлюс maxSurge подов
Blue-greenСредняяНизкий: трафик переключается целикомСекунды: правка selectorДвойной на время релиза
CanaryВысокаяНизкий: доля трафика ограниченаЗависит от веса, обычно минутыПлюс несколько подов canary

Отказоустойчивость etcd: кворум и резервное копирование

etcd хранит всё состояние кластера: объекты API, конфигурации, секреты. Потеря кворума останавливает запись и переводит control plane в режим только для чтения, поэтому etcd защищают отдельно от рабочих нагрузок.

Кворум etcd: сколько узлов нужно для отказоустойчивости

etcd использует алгоритм Raft и требует кворума, то есть большинства голосов, для фиксации записи. Чётное число узлов смысла не имеет: четыре узла дают кворум три и переносят отказ только одного, как и три узла.

Узлов etcdКворумПереносимый отказ
110
321 узел
532 узла
743 узла

Для продакшена минимально допустимы три узла etcd, оптимально пять для крупных кластеров. Семь и больше увеличивают задержку записи из-за большего числа подтверждений. Проверить состояние можно так:

ETCDCTL_API=3 etcdctl endpoint health \
  --endpoints=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379

ETCDCTL_API=3 etcdctl member list \
  --endpoints=https://10.0.0.11:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

Команда member list показывает идентификаторы, адреса и роли участников. Если число здоровых узлов падает до кворума минус один, запись прекращается, а в логах kube-apiserver появляются ошибки вида etcdserver: request timed out. Узлы etcd размещайте на отдельных дисках с низкой задержкой и на разных физических хостах.

Резервное копирование и восстановление etcd

Снимок состояния снимается одной командой. Ключи и сертификаты указывайте явно: без них etcdctl не подключится к защищённому эндпоинту.

ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%F-%H%M).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-2026-09-26-0300.db --write-out=table

snapshot status выводит хеш, ревизию, общее число ключей и размер файла. Эти значения стоит сохранять в лог бэкапа: они подтверждают, что снимок не пустой, и позволяют сравнить его с состоянием кластера. Бэкап по расписанию удобно вынести в cron на управляющем узле, а копии складывать вне кластера, в объектное хранилище или на отдельный сервер.

Восстановление требует остановки etcd на всех узлах и запуска со снимком. Порядок такой: сохраните текущий каталог данных etcd, восстановите снимок в новый каталог, укажите его в конфигурации, верните остальные узлы в кластер и проверьте, что member list показывает все участники, а kube-apiserver отвечает на запросы.

ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-2026-09-26-0300.db \
  --name=etcd-1 \
  --initial-cluster=etcd-1=https://10.0.0.11:2380,etcd-2=https://10.0.0.12:2380,etcd-3=https://10.0.0.13:2380 \
  --initial-advertise-peer-urls=https://10.0.0.11:2380 \
  --data-dir=/var/lib/etcd-restored

Восстановление etcd - операция, которую нельзя проводить впервые во время аварии. Прогоните её на стенде минимум раз в квартал и зафиксируйте фактическое время простоя: обычно от 10 до 30 минут на трёх узлах. Снимки храните не меньше 30 дней, а на кластерах с частыми релизами - до 90 дней.

Проверка и отладка отказоустойчивости: команды и чек-лист

Настроенные probes и реплики нужно проверить до инцидента. Набор команд ниже закрывает большинство вопросов: почему под перезапускается, почему трафик не идёт, почему обновление зависло.

Команды kubectl для диагностики probes и реплик

kubectl describe pod api-7d8f9c6b5-xk2lp
kubectl get events --sort-by=.metadata.creationTimestamp
kubectl logs api-7d8f9c6b5-xk2lp --previous
kubectl exec -it api-7d8f9c6b5-xk2lp -- curl -s localhost:8080/ready
kubectl get endpoints web

В describe pod смотрите секцию Events: строки вида Liveness probe failed: HTTP probe failed with statuscode: 500 показывают, какая именно проверка падает и с какой периодичностью. Запись Unhealthy с последующим Killing означает, что kubelet перезапустил контейнер по liveness. Если под долго в Ready 0/1, проверяйте readiness через kubectl exec: часто эндпоинт отвечает 500 из-за недоступной зависимости.

Симуляция отказа занимает минуту и показывает реальное поведение:

kubectl delete pod api-7d8f9c6b5-xk2lp
kubectl get pods -l app=api -w
kubectl drain node-2 --ignore-daemonsets --delete-emptydir-data
kubectl get pdb api-pdb

Ожидаемая картина: удалённый под пересоздаётся, drain не проходит дальше лимита PDB, ALLOWED DISRUPTIONS остаётся равным единице при трёх репликах и minAvailable: 2. Если drain выселяет все поды сразу, значит PDB не совпадает по selector с метками пода.

Чек-лист отказоустойчивости Kubernetes

  1. Readiness probe настроена и проверяет реальные зависимости, а не только ответ процесса.
  2. Liveness probe отделена от readiness и не зависит от внешних сервисов.
  3. Для приложений со стартом дольше 30 секунд задана startup probe.
  4. replicas не меньше 2 для критичных сервисов, практически достаточно 3.
  5. Реплики распределены по узлам через topologySpreadConstraints или podAntiAffinity.
  6. PodDisruptionBudget задан для всех сервисов, которые должны переживать drain.
  7. Стратегия обновления выбрана осознанно, maxUnavailable и maxSurge проверены на стенде.
  8. Откат версии протестирован командой rollout undo.
  9. Кластер etcd содержит нечётное число узлов, минимум 3.
  10. Снимки etcd снимаются по расписанию, хранятся вне кластера и проверяются через snapshot status.
  11. Восстановление из снимка отработано на стенде с замером времени.
  12. Мониторинг и алерты настроены на перезапуски контейнеров, недоступность подов и потерю кворума etcd.

Типичные ошибки, которые ломают отказоустойчивость: одна реплика в проде, отсутствие PDB, aggressive liveness probe с timeoutSeconds: 1 и общий эндпоинт /healthz для всех проверок. Логику самовосстановления и причины, по которым поды иногда не поднимаются, разбираем отдельно в материале про self-healing в Kubernetes.

Актуальные версии и совместимость: что учесть в 2026 году

Отказоустойчивость зависит не только от манифестов, но и от версии кластера и рантайма. Несколько фактов, которые влияют на настройку.

Kubernetes 1.37 требует container runtime с поддержкой CRI, а dockershim удалён из проекта начиная с версии 1.24. Если манифесты или скрипты деплоя всё ещё ссылаются на docker как на рантайм узла, их нужно переводить на containerd или CRI-O. Список поддерживаемых вариантов и требования к конфигурации перечислены в документации Kubernetes по container runtime. Помните, что kubelet и рантайм обязаны использовать один cgroup driver.

На создание подов влияют admission controllers. Это код, который перехватывает запросы к API-серверу после аутентификации и авторизации, но до сохранения объекта. Контроллеры применяются к созданию, удалению и изменению объектов и не могут блокировать чтение. Процесс идёт в две фазы: сначала mutating, затем validating; отклонение на любой фазе немедленно отклоняет весь запрос. Практический вывод: если под не создаётся, а Events пусты, проверьте validating-политики и квоты. Механика описана в справочнике по admission control.

Наблюдаемость самовосстановления строится на метриках и трейсах системных компонентов: перезапуски контейнеров, статусы проб, доступность узлов. Способы сбора системных метрик Kubernetes описаны в разделе документации по мониторингу.

Ограничение: точные номера версий и статус стабильности отдельных полей меняются от релиза к релизу, а часть переводов документации отстаёт от англоязычного оригинала. Перед изменением продакшена сверяйтесь с release notes и англоязычной версией документации для вашей версии Kubernetes.

Практический порядок внедрения: сначала probes и три реплики, затем PodDisruptionBudget, потом отработка стратегии обновления на стенде и только после этого регулярные бэкапы etcd с проверенным восстановлением. Такой порядок закрывает самые частые причины простоя и оставляет читаемый набор манифестов, который можно передать коллегам.

Поделиться: