Содержание
- Зачем нужны три брокера?
- Как Kafka KRaft строит кворум в кластере
- Структура проекта по развертыванию Kafka KRaft кластера в Docker
- docker-compose.yml для трёхнодового кластера
- Разбор ключевых переменных окружения
- Три listener-а вместо двух
- CLUSTER_ID - одинаковый для всех
- Параметры репликации
- Запуск кластера Apache Kafka Kraft в Docker
- Проверка работы кластера Kafka в Docker
- Создание топика и проверка репликации
- Симуляция отказа брокера Kafka
- Остановка и очистка Kafka кластер Docker
- Что дальше?
- Референсные ссылки
- Все уроки курса
Изучаем Apache Kafka с нуля.
Урок 4. Kafka Docker KRaft 3х узловый кластер
ПО: Apache Kafka 4.2.0 (образ apache/kafka:4.2.0) | Docker: Engine 28.1, Compose v2.35 | Окружение: Ubuntu 22.04 LTS / macOS 14+ | Уровень: начинающий+
В прошлом уроке мы подняли один брокер Kafka в Docker за несколько минут и убедились, что образ apache/kafka работает без лишних телодвижений. Один брокер удобен для отладки, но в нём нет ни отказоустойчивости, ни нормальной репликации. Если контейнер упадёт — вся очередь сообщений пропадёт вместе с ним.
В этом уроке мы развернём кластер из трёх брокеров через Docker Compose. По ходу разберём, как KRaft распределяет роли контроллеров между узлами, как настроить внутреннюю сеть для межброкерного общения и что происходит с кластером, когда один из брокеров отключается. В конце вы увидите реальную картину — топик с репликацией 3, лидер партиции, ISR-список и смену лидера при аварии.
Если вы планируете администрировать Kafka в продакшне — курс KAFKA. Администрирование кластера Kafka даёт именно такую практику: продакшн-топологии, настройку репликации и мониторинг кластера на живых примерах.
Зачем нужны три брокера?
Три — минимальное число узлов для кворума в KRaft. Понять почему легко через математику: кворум — это большинство, то есть N/2 + 1 узлов. При трёх брокерах кворум составляет два узла. Это значит, что кластер продолжает работать, если один брокер недоступен, но не более одного.
Три брокера дают три важных свойства, которых нет в однонодовом варианте.
- Отказоустойчивость. Кластер переживает падение одного брокера без потери данных и без остановки работы.
- Репликация данных. Топик с replication-factor 3 хранит копию каждой партиции на всех трёх брокерах. Данные не теряются даже при аварии.
- Распределённый кворум KRaft. Все три узла участвуют в выборах лидера контроллера. Metadata-лог реплицируется между всеми тремя, и нет единой точки отказа для метаданных.
В продакшне минимальный рекомендуемый размер кластера — именно три брокера. Два брокера не дают кворума при потере одного, а четыре — избыточны без явных причин для масштабирования.
Как Kafka KRaft строит кворум в кластере
В уроке 2 мы разбирали KRaft в общих чертах. Теперь посмотрим конкретно на то, как это работает в кластере. В нашей конфигурации каждый брокер совмещает роли — одновременно является и брокером (принимает сообщения от клиентов), и контроллером (участвует в кворуме метаданных). Это допустимо для небольших кластеров и удобно для учебного окружения.
Параметр KAFKA_CONTROLLER_QUORUM_VOTERS перечисляет всех участников кворума в формате nodeId@hostname:controllerPort. Этот список одинаковый для всех трёх брокеров — каждый знает обо всех остальных ещё до старта. Один из контроллеров становится active controller и управляет metadata-логом. При его падении оставшиеся два выбирают нового лидера.
Ключевой момент: межброкерное общение идёт по внутренней Docker-сети, а внешние клиенты (ваш терминал, приложение) подключаются через проброшенные порты на localhost. Поэтому нам нужны два разных listener-а — один для внутреннего трафика, другой для внешнего.
Структура проекта по развертыванию Kafka KRaft кластера в Docker
Создаём отдельную директорию — лучше не смешивать с файлами из урока 3.
mkdir kafka-lesson4 cd kafka-lesson4
Нам понадобится один файл docker-compose.yml. Никаких дополнительных конфигов и Dockerfile — вся настройка через переменные окружения.
docker-compose.yml для трёхнодового кластера
Вот полный файл. Разберём его по частям сразу после листинга.
# apache/kafka:4.2.0, Docker Compose v2.35
# Трёхнодовый KRaft-кластер: каждый узел совмещает роли broker+controller
services:
kafka1:
image: apache/kafka:4.2.0
container_name: kafka-kraft-1
ports:
- "9092:9092"
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT_HOST://0.0.0.0:9092,PLAINTEXT://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT_HOST://localhost:9092,PLAINTEXT://kafka1:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka1:9093,2@kafka2:9093,3@kafka3:9093
CLUSTER_ID: "MkU3OEVBNTcwNTJENDM2Qk"
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 2
KAFKA_DEFAULT_REPLICATION_FACTOR: 3
KAFKA_MIN_INSYNC_REPLICAS: 2
networks:
- kafka-net
kafka2:
image: apache/kafka:4.2.0
container_name: kafka-kraft-2
ports:
- "9094:9092"
environment:
KAFKA_NODE_ID: 2
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT_HOST://0.0.0.0:9092,PLAINTEXT://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT_HOST://localhost:9094,PLAINTEXT://kafka2:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka1:9093,2@kafka2:9093,3@kafka3:9093
CLUSTER_ID: "MkU3OEVBNTcwNTJENDM2Qk"
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 2
KAFKA_DEFAULT_REPLICATION_FACTOR: 3
KAFKA_MIN_INSYNC_REPLICAS: 2
networks:
- kafka-net
kafka3:
image: apache/kafka:4.2.0
container_name: kafka-kraft-3
ports:
- "9096:9092"
environment:
KAFKA_NODE_ID: 3
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT_HOST://0.0.0.0:9092,PLAINTEXT://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT_HOST://localhost:9096,PLAINTEXT://kafka3:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka1:9093,2@kafka2:9093,3@kafka3:9093
CLUSTER_ID: "MkU3OEVBNTcwNTJENDM2Qk"
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 2
KAFKA_DEFAULT_REPLICATION_FACTOR: 3
KAFKA_MIN_INSYNC_REPLICAS: 2
networks:
- kafka-net
networks:
kafka-net:
driver: bridge
Разбор ключевых переменных окружения
Большинство переменных вы уже видели в уроке 3. Сосредоточимся на том, что появилось впервые или изменилось по сравнению с однонодовым вариантом.
Три listener-а вместо двух
В уроке 3 нам хватало двух listener-ов: PLAINTEXT для внешних клиентов и CONTROLLER для кворума. В кластере добавляется третий — PLAINTEXT_HOST для внешних клиентов через localhost, и PLAINTEXT становится внутренним межброкерным каналом.
- PLAINTEXT_HOST://0.0.0.0:9092. Порт, который пробрасывается на хост. Через него вы подключаетесь с localhost:9092 (kafka1), localhost:9094 (kafka2), localhost:9096 (kafka3).
- PLAINTEXT://0.0.0.0:29092. Внутренний порт для межброкерного трафика. Снаружи не виден, работает только внутри Docker-сети kafka-net. Брокеры обращаются друг к другу как kafka1:29092, kafka2:29092, kafka3:29092.
- CONTROLLER://0.0.0.0:9093. Порт кворума KRaft. Используется только контроллерами для репликации metadata-лога.
Переменная KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT говорит Kafka, какой listener использовать для репликации данных между брокерами. Именно поэтому в KAFKA_ADVERTISED_LISTENERS каждый брокер анонсирует себя по имени контейнера: kafka1:29092, kafka2:29092 и т.д.
CLUSTER_ID — одинаковый для всех
Значение CLUSTER_ID должно быть идентичным во всех трёх сервисах. Это Base64-строка, которая однозначно идентифицирует кластер. Если хотите сгенерировать своё — используйте команду внутри контейнера или любой UUID-генератор с переводом в Base64 без паддинга. Для урока можете оставить значение из примера.
Параметры репликации
Четыре переменные регулируют поведение репликации на уровне кластера.
- KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3. Системный топик __consumer_offsets будет храниться на всех трёх брокерах.
- KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3. То же для топика транзакций.
- KAFKA_DEFAULT_REPLICATION_FACTOR: 3. Все новые топики по умолчанию создаются с репликацией 3.
- KAFKA_MIN_INSYNC_REPLICAS: 2. Запись считается подтверждённой, только если как минимум 2 реплики записали данные. Это гарантирует, что данные не потеряются при падении одного брокера.
Параметр KAFKA_MIN_INSYNC_REPLICAS работает в связке с настройкой acks=all на продюсере. Без неё производитель может не дожидаться подтверждения от реплик — и тогда при аварии часть сообщений рискует потеряться.
Запуск кластера Apache Kafka Kraft в Docker
Убедитесь, что порты 9092, 9094 и 9096 свободны. Если контейнер из урока 3 ещё жив — остановите его.
# Остановить контейнер из урока 3, если он запущен docker stop kafka-kraft-single
Теперь запускаем кластер из директории kafka-lesson4.
# Проверено: Apache Kafka 4.2.0, Docker Compose v2.35 docker compose up -d
Docker Compose подтянет образ (если ещё нет в кэше), создаст сеть kafka-net и запустит три контейнера. Убедитесь, что все три в статусе running.
docker compose ps
Вывод должен показать три контейнера kafka-kraft-1, kafka-kraft-2, kafka-kraft-3 со статусом Up. Подождите 15-20 секунд после старта — контроллерам нужно время на выборы лидера и инициализацию metadata-лога.
Проверка работы кластера Kafka в Docker
Проверим кластер изнутри первого контейнера. Для этого используем docker exec и утилиту kafka-metadata-quorum.sh из папки bin/ образа.
# Проверено: Apache Kafka 4.2.0, Docker Compose v2.35 docker exec -it kafka-kraft-1 kafka-metadata-quorum.sh \ --bootstrap-server kafka1:29092 \ describe --status
В выводе увидите строчку LeaderId — это номер узла, который сейчас управляет metadata-логом. Также видны CurrentVoters (все три контроллера) и CurrentObservers (пусто, т.к. у нас нет отдельных брокеров без роли контроллера).
# Посмотреть список брокеров через kafka-broker-api-versions.sh docker exec -it kafka-kraft-1 kafka-broker-api-versions.sh \ --bootstrap-server kafka1:29092 | grep Node
Три строки Node 1, Node 2, Node 3 подтверждают, что все брокеры видят друг друга и регистрируются в кластере.
Создание топика и проверка репликации
Создадим тестовый топик с тремя партициями и фактором репликации 3.
# Проверено: Apache Kafka 4.2.0, Docker Compose v2.35 docker exec -it kafka-kraft-1 kafka-topics.sh \ --bootstrap-server kafka1:29092 \ --create \ --topic cluster-test \ --partitions 3 \ --replication-factor 3
Теперь посмотрим, как Kafka распределила партиции и реплики между брокерами.
docker exec -it kafka-kraft-1 kafka-topics.sh \ --bootstrap-server kafka1:29092 \ --describe \ --topic cluster-test
Вывод будет выглядеть примерно так.
Topic: cluster-test TopicId: ... PartitionCount: 3 ReplicationFactor: 3 Topic: cluster-test Partition: 0 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3 Topic: cluster-test Partition: 1 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1 Topic: cluster-test Partition: 2 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2
Разберём что здесь что. Каждая партиция имеет одного лидера — брокер, который принимает записи от продюсеров. Replicas — полный список брокеров, хранящих копии этой партиции (порядок определяет приоритет при выборах лидера). Isr — это In-Sync Replicas, реплики, которые синхронизированы с лидером в данный момент. Пока все брокеры работают, Isr совпадает с Replicas.
Обратите внимание: Kafka автоматически распределила лидерство между тремя брокерами. Партиция 0 живёт на брокере 1, партиция 1 — на брокере 2, партиция 2 — на брокере 3. Нагрузка сбалансирована.
Симуляция отказа брокера Kafka
Вот ради чего мы всё это строили. Остановим второй брокер и посмотрим, что произойдёт с кластером.
# Останавливаем kafka-kraft-2 docker stop kafka-kraft-2
Подождём 10-15 секунд и снова проверим топик.
docker exec -it kafka-kraft-1 kafka-topics.sh \ --bootstrap-server kafka1:29092 \ --describe \ --topic cluster-test
Вывод изменится — и это самое интересное.
Topic: cluster-test Partition: 0 Leader: 1 Replicas: 1,2,3 Isr: 1,3 Topic: cluster-test Partition: 1 Leader: 3 Replicas: 2,3,1 Isr: 3,1 Topic: cluster-test Partition: 2 Leader: 3 Replicas: 3,1,2 Isr: 3,1
Что произошло. Брокер 2 выпал из всех ISR-списков — Kafka поняла, что он не отвечает. Партиция 1, у которой лидером был брокер 2, выбрала нового лидера из ISR — им стал брокер 3. Оставшиеся два брокера продолжают работу, кластер живёт. Параметр KAFKA_MIN_INSYNC_REPLICAS: 2 при этом выполняется — в ISR осталось по два узла у каждой партиции.
Вернём брокер 2 обратно.
docker start kafka-kraft-2
Через несколько секунд брокер 2 переподключится, наверстает отставание в репликации и снова войдёт в ISR. Лидер при этом автоматически не вернётся к брокеру 2 — для этого нужна команда kafka-leader-election.sh, которую разберём в уроке 21.
Остановка и очистка Kafka кластер Docker
Когда работа с кластером закончена — останавливаем всё одной командой.
# Остановить кластер, сохранив данные docker compose down # Остановить и удалить все данные (volumes) docker compose down -v
Флаг -v удаляет анонимные volumes, в которых Kafka хранит данные. Без него данные сохраняются между запусками. Для учёбы удобнее каждый раз стартовать с чистого листа — используйте -v.
Что дальше?
Мы развернули трёхнодовый кластер Kafka в Docker, увидели репликацию партиций и проверили поведение кластера при отказе одного брокера. Это базовая топология для любой дальнейшей работы — именно такой кластер мы будем использовать в примерах из следующих уроков.
В уроке 5 начнём систематически разбирать утилиты из папки bin/. Первым делом разберёмся с переменными окружения, путями и разницей между флагами —bootstrap-server и —controller-quorum-voters — это база, без которой остальные уроки воспринимаются сложнее.
Референсные ссылки
- Apache Kafka 4.2.0 — KRaft Mode (официальная документация)
- apache/kafka — Docker Hub, официальный образ
- Apache Kafka — Replication (официальная документация)
- Docker Compose — Networking (официальная документация)
- Apache Kafka Blog — релизы и обновления 2025-2026


