MAATRIX / Блог / Apache Kafka в Docker Compose: готовый файл

Apache Kafka в Docker Compose: готовый файл

MAATRIX

Apache Kafka — не просто ещё одна очередь сообщений, а платформа потоковой обработки событий, на которой держится обмен данными между сервисами почти в любой крупной системе: от логов и метрик до event sourcing и интеграции микросервисов. У Kafka репутация тяжёлого и капризного в поднятии сервиса — во многом заслуженная во времена, когда для запуска обязательно требовался отдельный ZooKeeper. Сейчас Kafka умеет работать в режиме KRaft без него, и поднять её одним compose-файлом стало заметно проще. Ниже — рабочий конфиг и разбор мест, где новички чаще всего спотыкаются.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Zookeeper или KRaft: какой режим выбрать в 2026 году

До версии 3.3 Kafka обязательно работала поверх ZooKeeper — отдельного координационного сервиса для метаданных кластера, списка брокеров и конфигурации топиков. Это означало два процесса вместо одного и отдельный отказоустойчивый ZooKeeper-кластер для прода.

KRaft (Kafka Raft) — встроенный в саму Kafka протокол консенсуса, который заменяет ZooKeeper: метаданные хранятся в самой Kafka, брокер и контроллер могут работать в одном процессе. Apache Software Foundation считает KRaft основным режимом для новых инсталляций, а ZooKeeper — устаревающим путём. Разумный дефолт для новой Kafka — KRaft, и в этой статье используется именно он. ZooKeeper стоит рассматривать, только если вы поддерживаете уже существующую систему на нём или инструментарий, жёстко требующий этот режим (изредка встречается у legacy-коннекторов Kafka Connect).

Для теста, разработки и небольшой продакшн-нагрузки достаточно одного узла в комбинированном режиме — брокер и контроллер в одном процессе. Для настоящего отказоустойчивого кластера нужно минимум 3 узла с раздельными ролями controller/broker и репликацией — это уже отдельная тема, выходящая за рамки одного compose-файла.

Готовый docker-compose.yml

Используем официальный образ apache/kafka — он собран под KRaft и не тянет за собой ZooKeeper по умолчанию. Проверьте актуальный тег на Docker Hub перед запуском — ниже версия, актуальная на момент написания статьи.

services:
  kafka:
    image: apache/kafka:3.9.0
    container_name: kafka
    restart: unless-stopped
    ports:
      - "9092:9092"
    environment:
      # брокер + контроллер в одном процессе
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller

      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093,PLAINTEXT_HOST://0.0.0.0:9094
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://<PUBLIC_IP_OR_DOMAIN>:9094
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      # кворум контроллеров: node_id@host:port, для single-node — сам на себя
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093

      KAFKA_CLUSTER_ID: "MkU3OEVBNTcwNTJENDM2Qk"

      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"

      # retention 7 дней (в мс) — подстройте под задачу
      KAFKA_LOG_RETENTION_MS: 604800000
      KAFKA_LOG_RETENTION_BYTES: -1
      KAFKA_HEAP_OPTS: "-Xms1g -Xmx1g"
    volumes:
      - kafka_data:/var/lib/kafka/data
    healthcheck:
      test: ["CMD-SHELL", "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092 || exit 1"]
      interval: 15s
      timeout: 10s
      retries: 10
    mem_limit: 2g

volumes:
  kafka_data:

Замените <PUBLIC_IP_OR_DOMAIN> на реальный IP сервера или домен — без этого клиенты снаружи Docker-сети получат от брокера внутренний адрес kafka:9092, к которому у них нет доступа, и подключение отвалится с непонятной ошибкой таймаута. Про это — отдельно ниже.

KAFKA_CLUSTER_ID — произвольная base64-строка, уникальная для кластера; можно сгенерировать свою командой kafka-storage.sh random-uuid внутри контейнера, но для одного окружения подойдёт и захардкоженное значение из примера.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Listeners: самая частая причина «не могу подключиться»

У Kafka есть разделение между тем, на каких адресах брокер слушает соединения (KAFKA_LISTENERS), и тем, какой адрес он сообщает клиентам как адрес для подключения (KAFKA_ADVERTISED_LISTENERS). После первого рукопожатия клиент переподключается уже по advertised-адресу — и если тот указывает на внутреннее docker-имя kafka, клиент за пределами compose-сети просто не сможет его разрешить.

Решение — завести отдельный listener для внешнего доступа:

  • PLAINTEXT (порт 9092) — для обращений из других контейнеров той же docker-сети, advertised-адрес — kafka:9092.
  • PLAINTEXT_HOST (порт 9094) — для клиентов снаружи, advertised-адрес — публичный IP или домен сервера.
  • CONTROLLER (порт 9093) — служебный, только для консенсуса между контроллерами, наружу не пробрасывается.

Если Kafka используется только другими контейнерами в том же compose-проекте, достаточно PLAINTEXT-listener'а по имени сервиса kafka — отдельный внешний listener не нужен, и порт 9094 наружу можно не открывать.

Для продакшена поверх PLAINTEXT стоит поднять SASL/SSL — без аутентификации любой, кто достучится до порта 9092/9094, может писать и читать любые топики. Общий принцип: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP расширяется значением SASL_SSL, добавляются KAFKA_SASL_ENABLED_MECHANISMS и сертификаты через volume. Если Kafka торчит только во внутренней сети за файрволом — риск ниже, но открытый листенер лучше не оставлять даже там.

Проверка: создаём топик, пишем и читаем сообщения

После docker compose up -d подождите, пока healthcheck станет healthy (обычно 15–30 секунд), зайдите внутрь контейнера и создайте топик с 3 партициями:

docker exec -it kafka bash

/opt/kafka/bin/kafka-topics.sh --create \
  --topic events \
  --bootstrap-server localhost:9092 \
  --partitions 3 \
  --replication-factor 1

# список топиков и детали
/opt/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092
/opt/kafka/bin/kafka-topics.sh --describe --topic events --bootstrap-server localhost:9092

Запустите консольного продюсера и напишите пару сообщений (каждая строка — отдельное сообщение, Ctrl+D для выхода):

/opt/kafka/bin/kafka-console-producer.sh --topic events --bootstrap-server localhost:9092

В другом терминале — консольный консьюмер, читающий с начала топика:

docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh \
  --topic events \
  --bootstrap-server localhost:9092 \
  --from-beginning

Если сообщения из продюсера появляются в консьюмере — брокер работает корректно и можно переходить к реальному приложению.

Kafka UI: веб-интерфейс вместо консольных скриптов

Ковыряться в топиках через консольные скрипты неудобно на постоянной основе. Добавьте в тот же compose-файл веб-интерфейс — он покажет топики, партиции, consumer group'ы, лаг потребления и содержимое сообщений:

  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    container_name: kafka-ui
    restart: unless-stopped
    depends_on:
      - kafka
    ports:
      - "8080:8080"
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092

Откройте http://<IP-сервера>:8080 — интерфейс подключится к брокеру по внутреннему docker-имени kafka:9092, отдельный внешний listener для UI не нужен. Порт 8080 в проде разумно закрыть файрволом или спрятать за реверс-прокси с базовой аутентификацией — сам интерфейс пароль не проверяет, если не настроить в нём auth отдельно.

Для наблюдения за самим сервером под Kafka стоит поднять отдельный стек метрик, например по схеме из статьи про установку Grafana и Prometheus: Kafka экспортирует JMX-метрики, Prometheus собирает их через JMX exporter, а Grafana визуализирует лаг консьюмеров и throughput по топикам.

Память, диск и ресурсы: сколько закладывать

Kafka не так прожорлива по памяти, как может показаться из репутации JVM-сервисов, но профиль потребления у неё специфический.

Heap JVM. Kafka не держит данные сообщений в heap — она полагается на page cache ОС и sendfile в обход heap. 1 ГБ (-Xms1g -Xmx1g из примера) достаточен для умеренной нагрузки; раздувать его обычно не нужно и даже вредно — вы отбираете память у page cache, который и делает Kafka быстрой.

RAM контейнера в целом. Ориентировочно закладывайте heap плюс 1–2 ГБ под page cache — от 2 ГБ на лёгкую нагрузку, от 4 ГБ на что-то серьёзнее. Это грубый ориентир, а не измеренная цифра — фактическое потребление зависит от числа партиций, размера сообщений и скорости записи.

Диск. Kafka пишет последовательно (append-only лог) — в этом её основная надёжность, поэтому SSD даёт заметный прирост по сравнению с HDD. Место определяется KAFKA_LOG_RETENTION_MS (по времени) и KAFKA_LOG_RETENTION_BYTES (по объёму на партицию) — уменьшите retention, если данные нужны только для короткоживущей обработки, а не replay истории.

Партиции. Каждая партиция — это файлы на диске и накладные расходы на брокере. Для теста хватает 1–3 партиций на топик; сотни партиций на single-node без реальной нужды в параллелизме — верный способ упереться в лимиты файловых дескрипторов.

Если Kafka разворачивается вместе с остальным бэкендом на одном сервере, полезно почитать про общие принципы продакшн-конфигурации Docker Compose — лимиты памяти, healthcheck'и и restart-политики там разобраны подробнее. А если Kafka — один из нескольких окружений в одном репозитории, посмотрите на профили Docker Compose, чтобы не плодить дублирующиеся compose-файлы.

Продакшн-нюансы, которые не видны на старте

Однонодовый KRaft-брокер из этой статьи хорошо подходит для разработки, staging-окружений и небольших нагрузок, где потеря узла означает «перезапустить контейнер», а не «инцидент на 3 часа». Прежде чем нести такую конфигурацию в серьёзный прод, держите в голове три ограничения.

Во-первых, replication-factor: 1 означает отсутствие отказоустойчивости на уровне данных — если контейнер или диск умрёт до того, как данные вычитаны консьюмером, они потеряны безвозвратно. Настоящий отказоустойчивый кластер требует минимум 3 брокера, replication-factor: 3 и min.insync.replicas: 2 — это уже оркестрация с раздельными volume на разных дисках или серверах.

Во-вторых, автосоздание топиков удобно для теста, но в проде превращается в источник опечаток — сервис, отправивший сообщение не в тот топик, тихо создаст новый вместо явной ошибки. Выключите флаг и создавайте топики явно.

В-третьих, мониторинг consumer lag — не опция, а необходимость: если потребитель отстаёт от продюсера быстрее, чем разгребает очередь, вы узнаете об этом не по ошибке, а по тому, что данные уже «протухли». Kafka UI показывает лаг наглядно, для алертинга его стоит выгружать в Prometheus.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Нужен ли ZooKeeper для Kafka в 2026 году?

Нет, для новых инсталляций Apache рекомендует KRaft — режим без ZooKeeper из этой статьи. ZooKeeper остаётся только для поддержки уже существующих кластеров, ещё не мигрировавших.

Почему клиент с моего компьютера не подключается, хотя изнутри Docker всё работает?

Почти всегда причина в KAFKA_ADVERTISED_LISTENERS — брокер сообщает клиенту внутренний docker-адрес вместо публичного IP. Проверьте, что advertised-адрес внешнего listener'а указывает на реальный IP сервера, а порт 9094 открыт в файрволе.

Можно ли использовать Kafka вместо RabbitMQ или наоборот?

Они решают разные задачи. RabbitMQ — брокер сообщений с гибкой маршрутизацией и упором на доставку конкретному потребителю; Kafka — распределённый лог событий под высокий throughput, replay истории и множество независимых консьюмеров одного потока. Для простых task-очередей проще RabbitMQ, для event-driven архитектур — Kafka.

Сколько RAM минимально нужно для однонодового Kafka?

Для теста и лёгкой нагрузки хватает 2–4 ГБ, если на сервере не крутится ничего тяжёлого параллельно. Под реальный прод закладывайте запас — конкретную цифру проверяйте на своём профиле сообщений.

Как посмотреть, сколько данных занимает Kafka на диске?

Данные лежат в volume kafka_datadocker volume inspect kafka_data покажет путь на хосте, du -sh по нему — размер. Уменьшить объём можно снижением retention или compaction для топиков с состоянием (cleanup.policy=compact).

Что делать, если контейнер падает сразу после старта?

Смотрите docker logs kafka — чаще всего причина в несовпадении KAFKA_CLUSTER_ID с уже существующими данными в volume после смены конфигурации (тогда проще удалить volume и начать заново) или в нехватке памяти при слишком жёстком mem_limit.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →