Kafka 3.6 и KRaft: планируем миграцию с ZooKeeper, тестируем rolling migration в staging
Apache Kafka 3.6 объявила KRaft production-ready и deprecated ZooKeeper. Тестируем rolling migration в staging, оцениваем риски и готовим план отката.
Apache Kafka 3.6 выпущен - KRaft mode получил production-ready status, ZooKeeper объявлен deprecated, ноябрь 2023
Kafka 3.6 вышла в начале ноября с формальным объявлением: KRaft mode готов к production, ZooKeeper-режим переходит в deprecated. Для тех, кто следил за этой темой последние пару лет, новость ожидаемая - KRaft развивался планомерно, и в 3.6 команда Kafka признала его достаточно зрелым для замены. Для тех, у кого живой кластер с ZooKeeper - это сигнал начинать планировать.
Мы начали. Вот что происходит на managed-сопровождении прямо сейчас.
Что изменилось в 3.6 по части KRaft
KRaft заменяет ZooKeeper как хранилище метаданных кластера. Вместо отдельного ZooKeeper-ансамбля контроллеры Kafka сами ведут журнал метаданных через Raft-консенсус. Это убирает ZooKeeper как отдельный компонент с его операционной сложностью: отдельный мониторинг, отдельные сессии, отдельные баги в тайминге.
В 3.6 конкретно:
- KRaft получил статус production-ready. До этого с 3.3 был доступен для продакшн-кластеров, но с оговорками - часть фич работала только в ZooKeeper-режиме. В 3.6 feature parity между режимами объявлен достигнутым.
- ZooKeeper-режим помечен deprecated. Это не immediate removal - Kafka не выключит ZooKeeper-поддержку завтра, но направление ясное. Релизы после 3.6 будут получать новые фичи только для KRaft.
- Migration tool для rolling migration из ZooKeeper в KRaft. Именно это нас и интересует: инструмент позволяет мигрировать действующий кластер без даунтайма, не пересоздавая его с нуля.
Схема кластера и контекст
Клиентский кластер: пять брокеров, версия Kafka 3.3.x, ZooKeeper-ансамбль из трёх нод на отдельных хостах. Несколько сотен топиков, разные retention-политики, трафик неравномерный - несколько высоконагруженных топиков и длинный хвост спокойных.
Обновление до 3.6 само по себе некритично - стандартный rolling upgrade по одному брокеру. Интересная часть начинается дальше: собственно переезд на KRaft.
Rolling migration: как это работает
Kafka 3.6 migration tool реализует следующую схему:
- Разворачиваются KRaft-контроллеры как новые процессы - пока параллельно с существующим ZooKeeper-ансамблем.
- Контроллеры синхронизируют метаданные из ZooKeeper.
- Когда метаданные синхронизированы, кластер переключается на KRaft как источник правды.
- ZooKeeper-ансамбль выводится из эксплуатации.
Теоретически - без остановки брокеров и без недоступности топиков.
# Генерация нового cluster ID для KRaft-режима
kafka-storage.sh random-uuid
# Форматирование логов контроллеров
kafka-storage.sh format -t <cluster-id> --kraft-controller-config controller.properties
# Запуск migration через admin API (упрощённо)
kafka-features.sh upgrade --feature metadata.version=<target>
Реальная последовательность длиннее - несколько шагов с проверками между ними.
Что показал staging
Развернули staging, максимально близкий к production: те же версии, аналогичная топология, репрезентативный набор топиков.
Обновление с 3.3 до 3.6 прошло штатно. Rolling upgrade по одному брокеру, каждый раз дожидались, пока брокер поднялся и partition reassignment устаканился. Здесь неожиданностей нет - путь с 3.3 на 3.6 хорошо накатан.
KRaft-контроллеры подняли без проблем. Синхронизация метаданных из ZooKeeper заняла несколько минут на нашем объёме топиков - в пересчёте на production с большим количеством топиков и ACL это нужно закладывать отдельно.
Переключение - вот тут стало интереснее. На один из тестовых прогонов получили ошибку на этапе migration finalization: METADATA_QUORUM_ERROR при попытке зафиксировать переключение. Кластер не сломался - откат прошёл чисто, ZooKeeper остался активным. Но разобраться пришлось: оказалось, что у одного из контроллеров чуть разошлось время с остальными - NTP был настроен, но drift на стендовых машинах оказался на границе допустимого для Raft-консенсуса. Синхронизировали, повторили - прошло.
Это хорошее напоминание: KRaft значительно чувствительнее к тайм-синхронизации, чем ZooKeeper. ZooKeeper тоже требует синхронного времени, но на практике его допуски шире. В production нужно убедиться, что NTP работает стабильно на всех хостах контроллеров.
Риски и план отката
На этапе staging мы сформировали список рисков, которые нужно снять перед production-миграцией:
- Тайминг синхронизации метаданных. На большом кластере с тысячами топиков и объёмными ACL синхронизация может занять значительно больше времени. Нужно замерить на staging с реалистичным объёмом ACL - у нас их не так много, но у клиента есть legacy-правила.
- Consumer group offsets. В процессе migration проверяем, что consumer groups корректно восстановили offsets после переключения. На staging всё хорошо, но этот момент не хочется упустить в production.
- Мониторинг. Текущие дашборды заточены под ZooKeeper-метрики -
zookeeper_*в Prometheus. После миграции их нужно переключить на KRaft-метрики контроллеров. Делать это нужно заранее, не в день переезда.
План отката формально есть: до финализации migration ZooKeeper остаётся активным, и теоретически можно откатиться. На практике мы проверили это на staging - откат действительно работает. Но «теоретически можно» и «комфортно откатиться в 3 часа ночи при инциденте» - разные вещи. Готовим runbook с явными шагами и критериями принятия решения об откате.
Где сейчас
Staging пройден, инцидент с NTP разобран и задокументирован. Следующий шаг - прогнать migration на staging ещё раз с реалистичным объёмом ACL и consumer groups, замерить время каждого этапа, проверить мониторинг.
До production пока не торопимся: ZooKeeper deprecated, но не removed. Кластер работает, и мигрировать ради галочки нет смысла. Делаем это тогда, когда staging даст достаточно уверенности в предсказуемости каждого шага.
Один вывод, который уже можно сделать по staging: migration tool в 3.6 работает и откатывается чисто. Это важно - год назад такого инструмента не было, и переезд на KRaft означал пересоздание кластера с нуля. Теперь путь есть.