Kafka KRaft 模式 Docker Compose 部署手册(apache/kafka 官方镜像)
目标:用Apache 官方镜像
apache/kafka:4.3.1以Docker Compose方式部署KRaft(无 ZooKeeper)Kafka。
随本手册交付 4 个可直接使用的文件:
| 文件 | 内容 | 适用场景 |
|---|---|---|
docker-compose.env.example | 变量模板(镜像、集群 ID、对外地址、端口) | 所有编排文件共用,先复制为.env |
docker-compose-single-node.yml | 单容器(broker + controller 合一) | 本地开发、功能测试、单机小规模 |
docker-compose-cluster-3node.yml | 3 容器 combined 集群,可容 1 节点故障 | 小规模生产、预发、联调 |
docker-compose-isolated.yml | 3 controller + 3 broker,角色分离 | 生产取向,可独立扩缩容 |
一、方案概览
| 拓扑 | 容器数 | 副本因子 | 容错 | 备注 |
|---|---|---|---|---|
| 单节点 | 1 | 1 | 无 | 开发测试;KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 |
| 三节点 combined | 3 | 3 | 容 1 节点 | broker 与 controller 同进程,最简单的高可用形态 |
| 角色分离 | 6 | 3 | controller 容 1、broker 容 1 | controller 不受业务流量影响,可分别扩缩容 |
三个编排文件互不冲突(项目名分别为kafka-single/kafka-cluster/kafka-isolated),但同一端口不能同时占用(单节点用 9092,三节点用 29092/39092/49092),按需启动其中一个即可。
二、前置条件
- Docker ≥ 20.10.4(必须)。低于该版本时容器创建
/opt/kafka/config等目录的权限不正确,启动会直接报错退出:===> Configuring …之后跟Running in KRaft mode… /opt/kafka/config/ file not writable。 - Docker Compose v2(
docker compose子命令形式)。 - 建议宿主机:单节点 2C2G 起;三节点 4C8G 起;数据盘按业务量预留(Kafka 吃磁盘顺序写,SSD 最佳)。
- 端口占用检查:单节点
9092;三节点29092 / 39092 / 49092;角色分离再加 broker 的同样三个端口(controller 不暴露端口)。 - 宿主机时钟同步(KRaft 对时钟敏感)。
三、关键设计说明(为什么这么写)
1. 镜像与启动流程
官方镜像的启动命令是/etc/kafka/docker/run(Dockerfile 中由CMD指定)。它依次做三件事:
configureDefaults:为未设置的变量填默认值(包括CLUSTER_ID,镜像内置了一个默认集群 ID);configure:校验必需变量(CLUSTER_ID必填、controller-only 节点不允许设置KAFKA_ADVERTISED_LISTENERS等);launch:调用kafka.docker.KafkaDockerWrapper setup把「默认配置 + 挂载配置 +KAFKA_*环境变量」合并写入/opt/kafka/config/server.properties,并在数据目录未格式化时自动格式化(已格式化则跳过并打印already formatted),最后启动 broker。
也就是说:不需要手动执行kafka-storage.sh format,镜像会自己处理。
2. 环境变量命名规则
配置项 → 环境变量的转换规则:.→_、_→__、-→___,再统一加前缀KAFKA_。
| 配置项 | 环境变量 |
|---|---|
node.id | KAFKA_NODE_ID |
log.dirs | KAFKA_LOG_DIRS |
offsets.topic.replication.factor | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR |
abc-def | KAFKA_ABC___DEF |
abc_def | KAFKA_ABC__DEF |
KAFKA_HEAP_OPTS、KAFKA_OPTS、KAFKA_JMX_*、KAFKA_LOG4J_*属于脚本特殊处理的变量,不遵循上述映射。
3. 三种配置注入方式(优先级从低到高)
- 内置默认配置(镜像自带的单节点 combined 配置);
- 挂载配置文件:把
*.properties挂到容器/mnt/shared/config,会覆盖默认配置; - 环境变量:优先级最高,覆盖同名的文件配置。
注意:即使用挂载文件方式,CLUSTER_ID、KAFKA_NODE_ID、KAFKA_LISTENERS、KAFKA_CONTROLLER_QUORUM_VOTERS这类被启动脚本直接读取的项仍需用环境变量提供。
4. 监听器模型(Compose 部署最容易踩的坑)
容器内用三个监听器把「集群内部」「控制器」「宿主机客户端」彻底分开:
| 监听器 | 用途 | 是否映射到宿主机 | 是否出现在 advertised 列表 |
|---|---|---|---|
PLAINTEXT | broker 之间、容器之间通信 | 否 | 是(用容器名,如kafka-1:19092) |
CONTROLLER | KRaft 控制器通信 | 否 | 否(控制器不是客户端入口) |
PLAINTEXT_HOST | 宿主机/外部客户端 | 是 | 是(用宿主机 IP/域名 + 映射端口) |
advertised.listeners里的地址必须是客户端真正能连到的地址。客户端第一次连上 bootstrap 后,会拿到 broker 自报的地址并直连,所以:
- 客户端在本机 →
localhost; - 客户端在局域网其他机器 → 宿主机内网 IP;
- 客户端在公网 → 公网 IP/域名。
填错的典型症状是「能连上 bootstrap,随后超时/连接被拒」。本手册已把这一项抽成.env里的HOST_ADVERTISED_HOST,改一处即可。
5. 数据持久化与权限
- 镜像内建用户
appuser(uid=1000 / gid=1000),进程以该用户运行。 - 官方示例把
KAFKA_LOG_DIRS设为/tmp/kraft-combined-logs,该路径在容器可写层内,容器一删数据即丢。本手册统一改为/var/lib/kafka/data并挂载数据卷(镜像已预建该目录并声明为VOLUME)。 - 用命名卷(named volume):Docker 会按镜像内的属主初始化,开箱即用,无需处理权限。
- 用宿主机目录(bind mount):必须先改属主,否则启动报权限错误:
sudomkdir-p/data/kafka&&sudochown-R1000:1000 /data/kafka
6. 容器内路径速查
| 路径 | 用途 |
|---|---|
/opt/kafka/bin/ | 所有 Kafka 命令行工具(kafka-topics.sh、kafka-metadata-quorum.sh等) |
/opt/kafka/config/server.properties | 启动脚本合并生成的最终配置(排查配置问题时看这里) |
/etc/kafka/docker/ | 镜像自带的默认配置与启动脚本 |
/mnt/shared/config/ | 用户挂载的配置文件目录(覆盖默认配置) |
/etc/kafka/secrets/ | 证书、JAAS 等敏感文件目录(SSL/SASL 用) |
/var/lib/kafka/data | 数据目录(KAFKA_LOG_DIRS) |
四、部署步骤
第 1 步:准备变量文件
cpdocker-compose.env.example .env# 至少修改两处:# CLUSTER_ID —— 用下面命令生成# HOST_ADVERTISED_HOST —— 客户端所在机器能访问到的主机名/IP生成集群 ID:
dockerrun--rmapache/kafka:4.3.1 /opt/kafka/bin/kafka-storage.sh random-uuid输出形如4L6g3nShT-eMCtK--X86sw,填进.env。同一集群的所有节点必须一致;集群 ID 只在首次格式化时写入,后续更换必须清空数据卷。
第 2 步:校验编排文件(可选但推荐)
dockercompose-fdocker-compose-single-node.yml config该命令只做解析与变量替换,能提前发现语法/变量问题,不会启动容器。
第 3 步:启动
# 单节点dockercompose-fdocker-compose-single-node.yml up-d# 三节点 combined 集群dockercompose-fdocker-compose-cluster-3node.yml up-d# 角色分离(3 controller + 3 broker)dockercompose-fdocker-compose-isolated.yml up-d第 4 步:等待就绪
dockercompose-fdocker-compose-cluster-3node.ymlps# 期望 STATUS 为 healthydockercompose-fdocker-compose-cluster-3node.yml logs-fkafka-1日志中出现Kafka Server started即为启动成功;首次启动还会看到格式化数据目录的相关输出。
五、验证
1. 容器内自检(推荐,最省事)
dockercompose-fdocker-compose-cluster-3node.ymlexeckafka-1bash容器内依次执行:
# 1) KRaft 元数据 quorum 状态:应看到 LeaderId、3 个 voter、MaxFollowerLag=0/opt/kafka/bin/kafka-metadata-quorum.sh --bootstrap-server localhost:19092 describe--status# 2) 复制明细:3 个节点 LogEndOffset 应一致、Lag 全为 0/opt/kafka/bin/kafka-metadata-quorum.sh --bootstrap-server localhost:19092 describe--replication# 3) 建 topic(三节点用 3 副本,单节点用 1)/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:19092\--create--topicdemo--partitions3--replication-factor3# 4) 确认分区分布/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:19092--describe--topicdemo# 5) 生产 / 消费/opt/kafka/bin/kafka-console-producer.sh --bootstrap-server localhost:19092--topicdemo /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server localhost:19092\--topicdemo --from-beginning --max-messages5# 6) 数据面健康检查:两条都应无输出/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:19092--describe--under-replicated-partitions /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:19092--describe--unavailable-partitions2. 宿主机/其他容器验证
# 另起一个临时客户端容器,接入同一网络(网络名 = 项目名 + _default)dockerrun--rm-it--networkkafka-cluster_default apache/kafka:4.3.1\/opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka-1:19092--list# 宿主机上若装有 Kafka CLI,可直接用映射端口kafka-topics.sh --bootstrap-server localhost:29092--list3. 故障演练(三节点)
dockercompose-fdocker-compose-cluster-3node.yml stop kafka-2# 观察 quorum 仍正常、topic 仍可读写dockercompose-fdocker-compose-cluster-3node.yml start kafka-2六、客户端接入
| 客户端位置 | bootstrap 地址 | 前提 |
|---|---|---|
| 宿主机上的进程 | localhost:9092(单节点)/localhost:29092(三节点任一) | .env中HOST_ADVERTISED_HOST=localhost |
| 同一 Compose 网络内的容器 | kafka:19092/kafka-1:19092 | 加入同一网络,且 advertised 的容器名可解析 |
| 局域网其他机器 | <宿主机IP>:29092 | .env中HOST_ADVERTISED_HOST=宿主机IP,且防火墙放行 |
| 公网客户端 | <公网IP或域名>:29092 | 需要公网映射 +务必启用 SASL_SSL |
安全提醒:
PLAINTEXT暴露到公网等于无认证无加密。生产环境请改用SASL_SSL:
把证书与 JAAS 文件挂载到/etc/kafka/secrets,用KAFKA_OPTS=-Djava.security.auth.login.config=/etc/kafka/secrets/<jaas文件>指定 JAAS,
再用KAFKA_SSL_KEYSTORE_FILENAME、KAFKA_SSL_KEYSTORE_CREDENTIALS、KAFKA_SSL_TRUSTSTORE_FILENAME等变量提供证书(镜像脚本会自动补全路径与密码)。
七、日常运维
查看日志
dockercompose-fdocker-compose-single-node.yml logs-fkafkadockercompose-fdocker-compose-single-node.yml logs--tail=200kafka重启与停止
dockercompose-fdocker-compose-single-node.yml restart kafka# 重启单个服务dockercompose-fdocker-compose-single-node.yml stop# 停止(保留容器与数据卷)dockercompose-fdocker-compose-single-node.yml down# 删除容器与网络(数据卷保留)dockercompose-fdocker-compose-single-node.yml down-v# 连数据卷一起删(数据全丢,慎用)升级镜像版本
三节点/角色分离场景不要一次性up -d全量重建,应逐个滚动:
# 1) 改 .env 中的 KAFKA_IMAGE# 2) 逐个重建并等待健康,再处理下一个dockercompose-fdocker-compose-cluster-3node.yml pull kafka-1dockercompose-fdocker-compose-cluster-3node.yml up-d--no-deps --force-recreate kafka-1dockercompose-fdocker-compose-cluster-3node.ymlpskafka-1# 等到 healthy 再继续# 依次对 kafka-2、kafka-3 重复扩缩容
- 角色分离模式:broker 层直接加服务即可(新 broker 用新的
node.id与端口,KAFKA_CONTROLLER_QUORUM_VOTERS不用改,因为 broker 不是 voter);controller 数量建议保持奇数(3 或 5)。 - combined 模式:加节点等于同时加一个 controller,必须把所有节点的
KAFKA_CONTROLLER_QUORUM_VOTERS一起更新为新列表再逐个重建,期间集群会有短暂不可用。 - 缩容:先迁移分区副本(
kafka-reassign-partitions.sh),再停容器;数据卷不会自动删除,需手动清理。
备份与恢复
# 逻辑备份(推荐):用 MirrorMaker 2 复制到另一个集群# 物理备份:停容器后打包数据卷dockercompose-fdocker-compose-single-node.yml stop kafkadockerrun--rm-vkafka-single_kafka-data:/data-v"$PWD":/backup alpine\tarczf /backup/kafka-data-$(date+%F).tar.gz-C/data.dockercompose-fdocker-compose-single-node.yml start kafka监控(JMX)
镜像的启动脚本支持通过环境变量开启 JMX:
environment:KAFKA_JMX_PORT:9099KAFKA_JMX_HOSTNAME:${HOST_ADVERTISED_HOST:-localhost}ports:-"9099:9099"需要指标接入 Prometheus 时,可用KAFKA_OPTS挂 JMX Exporter 的 javaagent,或部署独立的 kafka-exporter 容器。
八、生产加固清单
- 副本与一致性:
KAFKA_DEFAULT_REPLICATION_FACTOR=3、KAFKA_MIN_INSYNC_REPLICAS=2;生产者acks=all;unclean.leader.election.enable=false。 - 关闭自动建 Topic:
KAFKA_AUTO_CREATE_TOPICS_ENABLE=false(清单已设置),避免误建单副本 topic。 - 资源与 JVM:
KAFKA_HEAP_OPTS取容器内存上限的约 1/2,其余留给页缓存;内存 limits 过小会被 OOM Kill。 - 文件句柄:
ulimits.nofile调到 65536 以上(清单已设置)。 - 重启策略:
restart: unless-stopped(清单已设置);宿主机重启后自动恢复。 - 优雅退出:
stop_grace_period: 60s(清单已设置),保证关停前完成落盘。 - 存储:SSD/独立数据盘;配置磁盘使用率告警,磁盘写满会导致 broker 不可用。
- 安全:启用 SASL/SSL;
/etc/kafka/secrets只读挂载;对外只暴露必要端口。 - 监控:JMX 或 kafka-exporter;重点看 UnderReplicatedPartitions、OfflinePartitionsCount、ActiveControllerCount、请求延迟、磁盘使用率。
- 日志与保留:按业务量调整
KAFKA_LOG_RETENTION_HOURS、log.segment.bytes;可用KAFKA_LOG4J_ROOT_LOGLEVEL调整日志级别。 - 备份容灾:跨集群用 MirrorMaker 2;关键 topic 单独设置保留策略。
- 配置版本化:
.env与 compose 文件纳入 Git 管理(注意不要把密钥提交进仓库)。
九、常见问题排查
| 现象 | 原因 | 处理 |
|---|---|---|
| 客户端能连上 bootstrap,随后超时/连接被拒 | advertised.listeners里的地址客户端不可达(如填了localhost) | 改.env的HOST_ADVERTISED_HOST为客户端可达地址后重建容器 |
启动即退出,日志KAFKA_ADVERTISED_LISTENERS is not supported on a KRaft controller. | controller-only 节点设置了KAFKA_ADVERTISED_LISTENERS | 从该服务删除此变量(本手册 isolated 文件已规避) |
日志/opt/kafka/config/ file not writable | Docker 版本 < 20.10.4 | 升级 Docker |
bind mount 后报Permission denied写数据目录 | 宿主机目录属主不是 uid 1000 | sudo chown -R 1000:1000 /data/kafka |
| 容器重建后数据全没了 | 没设KAFKA_LOG_DIRS到挂载卷,用了默认的/tmp/kraft-combined-logs | 设置KAFKA_LOG_DIRS=/var/lib/kafka/data并挂卷 |
改了CLUSTER_ID后启动失败 / 集群 ID 不一致 | 数据目录已用旧集群 ID 格式化过 | 清空数据卷后重新启动 |
端口被占用(bind: address already in use) | 宿主机端口冲突 | 改.env的HOST_KAFKA_PORT或 compose 里的映射端口 |
| 三节点中某个 broker 起不来 | KAFKA_CONTROLLER_QUORUM_VOTERS与实际hostname/node.id不匹配 | 逐项核对三个服务的 hostname、KAFKA_NODE_ID、voters 列表 |
容器状态一直unhealthy但业务正常 | 健康检查每次要启动一个 JVM,首次启动或负载高时超时 | 调大healthcheck.timeout/retries/start_period |
改了.env但配置没生效 | 容器未重建(环境变量只在创建时注入) | docker compose up -d --force-recreate <service> |
常用排查命令:
dockercompose-fdocker-compose-single-node.ymlpsdockercompose-fdocker-compose-single-node.yml logs--tail=100kafkadockercompose-fdocker-compose-single-node.ymlexeckafkacat/opt/kafka/config/server.propertiesdockercompose-fdocker-compose-single-node.ymlexeckafkals-l/var/lib/kafka/datadockerinspect kafka--format'{{json .State.Health}}'dockervolumels|grepkafka十、参考来源
- Apache Kafka 官方 Docker 页面(镜像与版本):https://kafka.apache.org/43/getting-started/docker/
- Apache Kafka 官方 Docker 镜像使用指南(三种配置方式、环境变量命名规则、SASL/SSL、集群 ID):https://github.com/apache/kafka/blob/trunk/docker/examples/README.md
- 官方 Compose 示例(单节点 / combined 集群 / isolated 集群):https://github.com/apache/kafka/tree/trunk/docker/examples/docker-compose-files
- Apache Kafka 4.3.1 发布公告:https://kafka.apache.org/blog/2026/06/25/apache-kafka-4.3.1-release-announcement/
- KRaft 运维文档(quorum 状态查看、controller 增删):https://kafka.apache.org/43/operations/kraft/
附:三个编排文件的关键差异速查
| 项 | 单节点 | 三节点 combined | 角色分离 |
|---|---|---|---|
| 服务数 | 1 | 3 | 6(3 controller + 3 broker) |
node.id | 1 | 1/2/3 | controller 1/2/3,broker 4/5/6 |
process.roles | broker,controller | broker,controller | controller 或 broker |
| 宿主机端口 | 9092 | 29092/39092/49092 | 29092/39092/49092(controller 不暴露) |
| 内部端口 | 19092 | 19092 | 19092 |
| 控制器端口 | 29093 | 9093 | 9093 |
| 副本因子 | 1 | 3 | 3 |
| 数据卷 | kafka-data | kafka-1/2/3-data | controller-1/2/3-data + kafka-1/2/3-data |