Привет!
Сегодня мы рассмотрим, как перезапустить полноценный 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 " Все поды обновлены"
Что происходит:
- partition= 2 — контроллер убивает- zk-2, поднимает новый с 3.9.4, ждёт Ready.
- Цикл ставит - partition= 1 — очередь доходит до- zk-1.
- 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 ноды | 
 | Временно снижаем  | 
| CrashLoopReadiness | Readiness Probe ложится, под считается Unready, кворум падает | Перед обновлением убедитесь, что проба ruok стабильно отвечает | 
| Смена конфигурации ZK | Требуется reload, а не restart | Используйте  | 
| Включён Istio | Sidecar мешает быстрому отсоединению от сети | Добавляем  | 
Автоматизируем в 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 часто остаётся точкой боли: внешние сервисы ломают изоляцию, пайплайны зависимы от ручного вмешательства, а деплой — это всё ещё зона повышенного риска. Если вы хотите перевести доставку кода в предсказуемую, автономную и контролируемую систему — эти уроки помогут на практике разобраться, как это сделать.
- 28 июля, 20:00 
 GitlabCI + ArgoCD — сборка и доставка приложений, не покидая кластер
 Как собрать и доставить код, не выходя за границы Kubernetes: связка GitlabCI + ArgoCD для полной автономии CI/CD.
- 12 августа, 20:00 
 CI/CD: 90 минут от платформы до конвейера
 От пустого проекта до рабочего пайплайна за 100 секунд, а дальше — по шагам: всё, что нужно знать, чтобы CI/CD действительно работал.
Чтобы открыть доступ ко всем открытым урокам, а заодно проверить своей уровень знаний Kubernetes, пройдите вступительное тестирование.
 
          