PDB для StatefulSet с minAvailable=100 % и контролируемый rolling-update через Partition

от автора

Привет!

Сегодня мы рассмотрим, как перезапустить полноценный ZooKeeper‑кластер в Kubernetes так, чтобы ни один из узлов не потерял кворум даже на микросекунду. Берём два проверенных инструмента — строгий PodDisruptionBudget с minAvailable: 100% и StatefulSet с updateStrategy.RollingUpdate.partition.

Зачем вообще это

  • ZooKeeper теряет кворум, если одновременно «отваливаются» больше ⌈N/2⌉ нод.

  • Классический kubectl rollout restart sts/zk бьёт по последнему поду, дожидается его готовности и переходит к предыдущему. На бумаге это безопасненько, но при drain-операциях или хаотичных эвикшенах можно внезапно уронить нод.

  • Подцепив PDB с 100 % мы закручиваем гайки: eviction-контроллер не сможет снести под даже при drain-узла.

  • partition даёт ручной триггер для каждого пода: пока мы явно не скажем «- 1», ничего не перезапустится.

Готовим строгий PDB

apiVersion: policy/v1 kind: PodDisruptionBudget metadata:   name: zk-pdb-strict spec:   minAvailable: 100%   # нулевая терпимость к эвикшенам   selector:     matchLabels:       app: zookeeper

Создаём:

kubectl apply -f zk-pdb-strict.yaml kubectl get pdb zk-pdb-strict

Поле ALLOWED DISRUPTIONS всегда «0», и это именно то, что нам нужно. Теперь ни плановый drain, ни kubectl delete pod не разрушат кластер (контроллер просто скажет «eviction forbidden»).

Настраиваем StatefulSet

Напишем минимальный фрагмент StatefulSet с RollingUpdate-стратегией и явно заданным partition. Пусть у нас три реплики (replicas: 3).

apiVersion: apps/v1 kind: StatefulSet metadata:   name: zk spec:   serviceName: zk-hs   replicas: 3   updateStrategy:     type: RollingUpdate     rollingUpdate:       partition: 3   # freeze-точка — выше самого большого ординала   selector:     matchLabels:       app: zookeeper   template:     metadata:       labels:         app: zookeeper     spec:       containers:       - name: zk         image: bitnami/zookeeper:3.9.3         readinessProbe:           exec:             command: ["zkServer.sh", "status"]         livenessProbe:           exec:             command: ["zkServer.sh", "status"]

При изменении шаблона подов (новый тег образа, другие ресурсы) контроллер не заденет ни один экземпляр, потому что ординалы 0 – 2 меньше partition.

Патчим шаблон подов

Например, хотим обновить образ до 3.9.4:

kubectl patch sts zk --type='json' \   -p='[{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"bitnami/zookeeper:3.9.4"}]'

Смотрим, что ничего не происходит:

kubectl rollout status sts/zk   # висит на Current revision

Это нормально — partition держит обновление.

Самописный one-liner для чистого rolling-update

#!/usr/bin/env bash set -euo pipefail  STS="zk" REPLICAS=$(kubectl get sts "$STS" -o=jsonpath='{.spec.replicas}') for ((ORD=$((REPLICAS-1)); ORD>=0; ORD--)); do   echo "Понижаем partition до $ORD"   kubectl patch sts "$STS" -p \     "{\"spec\":{\"updateStrategy\":{\"rollingUpdate\":{\"partition\":$ORD}}}}"   echo " Ждём готовность pod/$STS-$ORD"   kubectl rollout status --watch --timeout=600s sts/"$STS" \     --revision=$(kubectl get sts "$STS" -o=jsonpath='{.status.updateRevision}') done echo " Все поды обновлены" 

Что происходит:

  1. partition = 2 — контроллер убивает zk-2, поднимает новый с 3.9.4, ждёт Ready.

  2. Цикл ставит partition = 1 — очередь доходит до zk-1.

  3. partition = 0 — наконец обновляется zk-0.

В любой момент только одна реплика недоступна, PDB не нарушается, кворум не покидает нас ни на минуту.

Проверяем, что кворум жив

После каждого шага можно проверять лидерство:

kubectl exec zk-0 -- zkCli.sh stat | grep -E 'Mode|Zxid' kubectl exec zk-1 -- zkCli.sh stat | grep -E 'Mode|Zxid' kubectl exec zk-2 -- zkCli.sh stat | grep -E 'Mode|Zxid'

Или сделать sanity-тест:

kubectl exec zk-0 -- zkCli.sh create /hello "$(date +%s)" kubectl exec zk-2 -- zkCli.sh get /hello

Данные доступны сразу на всех серверах при условии двух готовых подов из трёх — именно то, что мы и сохраняем.

Возможные проблемы

Ситуация

Что пойдёт не так

Как лечить

Drain ноды

kubectl drain зависнет — PDB запрещает эвикшен

Временно снижаем minAvailable до 67 %, проведите drain, верните 100 %

CrashLoopReadiness

Readiness Probe ложится, под считается Unready, кворум падает

Перед обновлением убедитесь, что проба ruok стабильно отвечает

Смена конфигурации ZK

Требуется reload, а не restart

Используйте reconfig прямо из zkCli.sh, не трогая StatefulSet

Включён Istio

Sidecar мешает быстрому отсоединению от сети

Добавляем preStop с graceful shutdown в 25 сек, иначе лидер не успеет пересчитаться

Автоматизируем в CI

Простейший GitLab-job:

update_zk:   stage: deploy   script:     - kubectl config use-context prod     - ./scripts/partition-rolling.sh   when: manual

Можно хранить желаемый тег образа в Helm-values, а скрипт брать replicas и release из helm status. Главное — никогда не трогайте partition и PDB в разных MR: держите их под одной ревизией, иначе рискуете схлопотать «Eviction is refused» в самый неудобный момент.

Что насчёт maxUnavailable

С Kubernetes 1.25 можно задать maxUnavailable прямо в StatefulSet — это альтернатива нашему циклу. Но ZooKeeper-кластеру критичен порядок остановки (последний ординал первым), а maxUnavailable не гарантирует этот порядок. Плюс нам нужна совместимость с версиями до 1.25, где поля ещё нет. Поэтому комбинация partition + скрипт пока быстрее и надёжнее.

Итог

Спасибо, что дочитали до конца — надеюсь, теперь раскатывать ZooKeeper без потери кворума для вас будет так же естественно, как kubectl get pods. Делитесь своими кейсами, подходами и замечаниями в комментариях — всегда интересно увидеть, как это решают в других задачах.


Даже в зрелых командах CI/CD часто остаётся точкой боли: внешние сервисы ломают изоляцию, пайплайны зависимы от ручного вмешательства, а деплой — это всё ещё зона повышенного риска. Если вы хотите перевести доставку кода в предсказуемую, автономную и контролируемую систему — эти уроки помогут на практике разобраться, как это сделать.

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


ссылка на оригинал статьи https://habr.com/ru/articles/930390/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *