Kafka Docker 容器化实战:KRaft 模式下的生产级部署与 Compose 最佳实践

发布时间:2026/7/7 22:09:39
Kafka Docker 容器化实战:KRaft 模式下的生产级部署与 Compose 最佳实践 1. 为什么今天还在用 Docker 跑 Kafka一个老数据平台工程师的坦白Kafka Docker 这个组合我从 2017 年第一次在团队里搭起三节点本地集群开始用到现在已经七年。不是因为“时髦”而是因为——它真能救命。你有没有经历过开发同学说“我本地跑得好好的”测试环境一上就报UnknownTopicOrPartitionException运维同事深夜被叫醒只因线上 Kafka 集群某台 broker 的 JVM 参数和测试环境差了 512MB又或者 CI 流水线里跑通的端到端测试到了 staging 环境突然卡在消费者组 rebalance 上查了六小时发现是group.initial.rebalance.delay.ms配置没生效这些不是故事是我上周刚处理的三个工单。而 Docker Compose就是我们团队现在唯一能同时守住“开发效率”、“环境一致性”和“故障可复现性”这三条底线的方案。核心关键词其实就四个Kafka、Docker、Compose、生产级可用性。注意不是“玩具级可用”也不是“能跑就行”而是指你能用同一份docker-compose.yml文件在 MacBook M3 上启动单节点验证逻辑在 AWS EC2 上拉起三节点做压力预演在 GitLab CI 的 runner 容器里跑通 12 个集成测试用例且所有环节的 topic 分区分布、消费者 offset 提交行为、网络延迟特征都高度一致。这才是我们谈 Kafka Docker 的真实语境——它不是替代 ZooKeeper 或 KRaft 的技术选型而是把 Kafka 这套复杂分布式系统“封装成可搬运的乐高积木”的工程实践。适合谁读如果你是后端工程师正在为微服务间事件通知写重试逻辑却总被“本地测不出、上线才暴露”的问题折磨如果你是 DevOps 工程师被研发反复追问“为什么我的本地 Kafka 和测试环境行为不一致”如果你是数据平台负责人正评估是否要把 Kafka 集群容器化以统一交付标准——那么这篇文章里的每一个配置项、每一处坑、每一条调优建议都是我亲手踩过、记在笔记本上、又在团队 Wiki 里反复修订过的实操记录。它不讲 Kafka 基础概念比如什么是 partition leader但会告诉你为什么KAFKA_ADVERTISED_LISTENERS写成PLAINTEXT://localhost:9092在 Mac 上能通在 Linux 服务器上必挂为什么KAFKA_LOG_DIRS绝对不能映射到宿主机根目录下的临时文件夹为什么KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR1在单节点开发环境是刚需但在 CI 环境里必须强制设为 3。这些细节才是决定你能否把 Kafka Docker 从“能跑”推进到“敢用”的分水岭。2. 架构设计与方案选型为什么放弃 ZooKeeper又为什么没直接上 Kubernetes2.1 从 ZooKeeper 到 KRaft不是为了新潮而是为了少一个故障点2022 年之前我们所有 Docker 化 Kafka 部署都依赖 ZooKeeper。当时用的是 Confluent 的cp-zookeeper和cp-kafka镜像docker-compose.yml里固定写死zookeeper:2181。表面看很稳实际埋了三个雷第一ZooKeeper 自身需要奇数节点3/5/7才能保证高可用但我们本地开发和 CI 环境根本不可能起 3 个 ZooKeeper 容器——资源开销太大启动时间太长第二ZooKeeper 的tickTime和initLimit参数和 Kafka 的session.timeout.ms必须严格匹配差 100ms 就可能触发 broker 频繁掉线而这两个组件的镜像版本更新节奏完全不同第三也是最致命的ZooKeeper 的日志清理策略autopurge.purgeInterval如果没配好CI 流水线跑十次后ZooKeeper 容器磁盘直接爆满整个流水线卡死。KRaft 模式Kafka Raft Metadata mode的出现对我们来说是雪中送炭。它把元数据管理从外部组件内聚到 Kafka 自身用 Raft 协议实现 controller quorum。这意味着单节点 Kafka 容器就能独立运行不用再配 ZooKeepercontroller 和 broker 角色可以共存于同一进程减少网络跳数元数据变更通过内部 RPC 同步避免 ZooKeeper 的 watch 机制带来的延迟抖动。我们实测过同样硬件条件下KRaft 模式下 topic 创建耗时比 ZooKeeper 模式平均快 40%且没有“ZooKeeper session expired”这类诡异错误。所以现在所有新项目Docker Compose 默认采用 KRaft只有维护老系统时才保留 ZooKeeper 兼容配置。提示KRaft 并非万能。它要求 Kafka 版本 ≥ 3.3生产就绪且KAFKA_PROCESS_ROLES必须明确指定broker,controller不能只写broker。我们曾因漏掉controller导致容器启动后立即退出日志里只有一行ERROR [ControllerServer id1] Controller is not ready to serve requests排查了两小时才发现是角色声明缺失。2.2 为什么坚持用 Docker Compose而不是直接上 Kubernetes很多人看到“生产级 Kafka”第一反应是 Kubernetes StatefulSet。但我们的经验是Kubernetes 是为大规模、多租户、自动扩缩容场景设计的而绝大多数团队的 Kafka 使用场景是“小规模、固定拓扑、人工干预为主”。我们做过对比测试在一台 16C32G 的物理服务器上部署 3 节点 KafkaDocker Compose 方案docker-compose up -d启动耗时 12 秒docker-compose ps查状态 0.3 秒重启单个 brokerdocker-compose restart kafka-25 秒完成所有配置都在单个 YAML 文件里新人 clone 仓库后make up一键启动Kubernetes 方案需要编写 StatefulSet、Headless Service、PersistentVolumeClaim、ConfigMap 四个 YAML 文件kubectl apply -f后等待kubectl get pods显示 Running 平均耗时 47 秒kubectl rollout restart statefulset/kafka触发滚动更新需 2 分钟以上且一旦 Pod 处于 Pending 状态新人根本看不懂Events里 “0/3 nodes are available: 3 node(s) didnt match pod affinity/anti-affinity rules” 这类报错。更关键的是Kubernetes 的网络模型CNI 插件和 Kafka 的监听器配置存在天然冲突。Kafka 要求每个 broker 对外暴露两个地址一个是给客户端连接的advertised.listeners如PLAINTEXT://kafka-1.my-ns.svc.cluster.local:9092另一个是 broker 间通信的inter.broker.listener.name如INTERNAL://kafka-1.my-ns.svc.cluster.local:29092。而 Kubernetes 的 Service DNS 名称在不同命名空间下解析规则复杂稍有不慎就会导致 broker 间无法同步元数据。我们曾因此在 staging 环境出现过持续 3 小时的分区不可用最终发现是KAFKA_ADVERTISED_LISTENERS里写的 Service 名称和实际 DNS 解析结果不一致。所以我们的结论很务实Docker Compose 是 Kafka 容器化的“黄金起点”它覆盖了 80% 的开发、测试、CI 场景Kubernetes 是“进阶选项”只在真正需要跨 AZ 部署、自动故障转移、或与现有 K8s 生态深度集成时才启用。这篇文章的所有配置都基于 Compose但我会在关键位置标注“若迁移到 Kubernetes 需额外注意什么”帮你平滑过渡。2.3 镜像选择Confluent、Bitnami、Apache 官方到底怎么选镜像不是越新越好也不是越大越好而是要匹配你的使用阶段。我们团队制定了三档镜像使用规范Confluent Platform 镜像confluentinc/cp-kafka:7.5.0这是我们的“生产预演镜像”。它包含完整的 Confluent 生态Schema Registry、REST Proxy、KSQL、Control Center。优势是功能全、文档齐、企业级支持好劣势是镜像体积大1.2GB、启动慢JVM 初始化耗时长、默认开启大量监控埋点影响性能测试准确性。我们只在 staging 环境和压测环境使用它目的是提前暴露 Schema 兼容性、Avro 序列化、ACL 权限等生产级问题。Bitnami Kafka 镜像bitnami/kafka:3.7.0这是我们的“日常开发镜像”。它基于 Alpine Linux体积仅 320MB启动时间 5 秒配置方式极简所有参数通过环境变量注入无需修改server.properties。最关键的是它默认禁用所有非必要服务如 JMX exporterCPU 占用比 Confluent 低 60%。我们要求所有研发本地开发必须用 Bitnami因为它的轻量和确定性能最大程度减少“环境差异导致的问题”。Apache 官方 Kafka 镜像apache/kafka:3.7.0这是我们的“定制化构建基座”。它不提供任何预装工具只包含 Kafka 核心二进制文件和最小依赖。我们用它来构建自己的镜像在Dockerfile中加入自定义的 JVM 参数模板、日志轮转脚本、健康检查探针并预置常用 topic 创建命令。这个镜像只用于 CI 流水线确保每次构建的 Kafka 环境完全一致不受上游镜像更新影响。注意永远不要混用镜像。我们曾因在同一个 Compose 文件里混用confluentinc/cp-kafka和bitnami/kafka导致KAFKA_BROKER_ID的环境变量名不一致Confluent 用KAFKA_BROKER_IDBitnami 用KAFKA_CFG_BROKER_ID容器启动失败却报错信息模糊浪费了整整一个下午。3. 核心细节解析与实操要点那些官方文档不会告诉你的硬核细节3.1 KRaft 模式下KAFKA_CONTROLLER_QUORUM_VOTERS的数字到底代表什么这是 Kafka Docker 最常被误解的配置。很多教程直接抄1localhost:9093但没人解释1是什么。它不是 broker ID而是controller quorum 的投票者 IDvoter ID必须是正整数且在整个集群中唯一。在单节点 KRaft 部署中它通常等于KAFKA_NODE_ID即1localhost:9093但在多节点部署中它必须和KAFKA_NODE_ID错开否则会触发 controller 选举冲突。我们实测过一个三节点 KRaft 集群# docker-compose.yml services: kafka-1: environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_CONTROLLER_QUORUM_VOTERS: 1kafka-1:9093,2kafka-2:9093,3kafka-3:9093 KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka-1:9092,CONTROLLER://kafka-1:9093 kafka-2: environment: KAFKA_NODE_ID: 2 KAFKA_PROCESS_ROLES: broker,controller # 注意多节点 KRaft 中controller 角色可以分散 KAFKA_CONTROLLER_QUORUM_VOTERS: 1kafka-1:9093,2kafka-2:9093,3kafka-3:9093 # ... 其他配置同上这里KAFKA_CONTROLLER_QUORUM_VOTERS是一个全局配置所有节点必须完全一致且每个 voter ID1,2,3必须对应一个真实的 controller 节点。如果误写成1kafka-1:9093,1kafka-2:9093voter ID 重复集群将永远无法选出 controller所有 broker 日志里都会循环打印INFO [QuorumController] Waiting for quorum voters to be ready...。3.2KAFKA_ADVERTISED_LISTENERS和KAFKA_LISTENERS的生死关系这是 Kafka Docker 连接问题的头号杀手。KAFKA_LISTENERS定义 broker监听哪些地址和端口即“我在哪里等连接”而KAFKA_ADVERTISED_LISTENERS定义 broker告诉客户端“你应该连我哪个地址”即“我对外宣称的地址”。两者必须严格匹配否则客户端拿到错误地址连接必然失败。我们总结出一套“三段式配置法”适用于所有场景本地开发Mac/LinuxLISTENERSPLAINTEXT://0.0.0.0:9092ADVERTISED_LISTENERSPLAINTEXT://localhost:9092broker 监听所有网卡 9092 端口但告诉客户端连 localhostLinux 服务器部署无 Docker 网络隔离LISTENERSPLAINTEXT://0.0.0.0:9092ADVERTISED_LISTENERSPLAINTEXT://服务器公网IP:9092注意必须用真实 IP不能用hostname -I取到的内网 IP否则外网客户端连不上Docker Compose 多容器通信LISTENERSINTERNAL://0.0.0.0:29092,EXTERNAL://0.0.0.0:9092ADVERTISED_LISTENERSINTERNAL://kafka:29092,EXTERNAL://localhost:9092LISTENER_SECURITY_PROTOCOL_MAPINTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXTINTER_BROKER_LISTENER_NAMEINTERNAL这是关键INTERNAL用于容器间通信EXTERNAL用于宿主机应用连接INTER_BROKER_LISTENER_NAME指定 broker 间用INTERNAL地址通信实操心得永远用docker-compose exec kafka bash -c echo $KAFKA_ADVERTISED_LISTENERS检查容器内实际生效的值。我们曾因.env文件里KAFKA_ADVERTISED_LISTENERS被其他环境变量覆盖导致实际值为空容器日志里没有任何报错但客户端死活连不上。3.3 数据持久化为什么./data绑定挂载比 named volume 更可靠Docker Volumenamed volume看起来更“Docker 原生”但 Kafka 对存储有特殊要求它需要稳定的 inode、低延迟的随机 I/O、以及对fsync()的强依赖。而 Docker 的 overlay2 存储驱动在某些 Linux 发行版如 CentOS 7上对 named volume 的 fsync 性能优化不足会导致 Kafka 日志刷盘变慢进而触发RequestTimedOutException。我们对比测试过Named Volume (volumes: [kafka-data])在 CI 流水线中当并发 producer 数 50 时kafka-producer-perf-test.sh吞吐量下降 35%且docker stats显示容器 IO wait 时间飙升Bind Mount (volumes: [./data:/var/lib/kafka/data])同样负载下吞吐量稳定IO wait 时间低于 1%。原因在于 bind mount 直接映射宿主机目录绕过了 Docker 存储驱动层让 Kafka 能直接利用宿主机文件系统的优化如 ext4 的dataordered模式。但 bind mount 有代价你必须手动管理宿主机目录权限。Kafka 容器默认以 UID 1001 运行而宿主机目录通常是 root 创建的会导致容器启动失败报错Permission denied。解决方案是在docker-compose.yml中显式指定用户kafka: image: bitnami/kafka:3.7.0 user: 1001:1001 # 强制以 UID:GID 1001 运行 volumes: - ./data:/var/lib/kafka/data或者更彻底地在宿主机上创建目录并赋权mkdir -p ./data sudo chown -R 1001:1001 ./data3.4 JVM 调优为什么KAFKA_JVM_PERFORMANCE_OPTS比-Xmx更重要Kafka 是典型的 I/O 密集型应用而非 CPU 密集型。官方文档建议-Xmx设为物理内存的 50%但这在 Docker 环境下是危险的。Docker 容器的内存限制mem_limit是硬上限一旦 JVM heap 超过该限制容器会被 OOM Killer 直接杀死。而 Kafka 的堆外内存off-heap memory占用很大用于网络缓冲区、PageCache如果只调Xmx很可能 heap 没超限但总内存超了。我们的做法是用KAFKA_JVM_PERFORMANCE_OPTS精确控制所有 JVM 参数而非只设Xmx。一个经过生产验证的配置如下environment: KAFKA_JVM_PERFORMANCE_OPTS: -Xms2g -Xmx2g -XX:UseG1GC -XX:MaxGCPauseMillis20 -XX:ExplicitGCInvokesConcurrent -Djava.awt.headlesstrue -Dcom.sun.management.jmxremotefalse关键点解析-Xms2g -Xmx2g堆内存固定为 2GB避免 GC 时动态扩容导致内存抖动-XX:UseG1GCG1 垃圾收集器更适合大堆内存且可预测停顿时间-XX:MaxGCPauseMillis20G1 的目标停顿时间防止 GC 导致 broker 响应延迟-XX:ExplicitGCInvokesConcurrent禁用System.gc()的 Full GC改用并发 GC避免阻塞请求-Dcom.sun.management.jmxremotefalse关闭 JMX 远程管理Docker 环境下基本用不到且有安全风险。注意KAFKA_JVM_PERFORMANCE_OPTS的值必须用双引号包裹且内部不能有换行。我们曾因在 YAML 中写成多行字符串导致 Kafka 启动时解析 JVM 参数失败报错Unrecognized VM option UseG1GC。4. 实操过程与核心环节实现从零搭建一个可落地的 Kafka Docker 环境4.1 单节点 KRaft 开发环境5 分钟启动零依赖这是所有新手的起点也是我们团队的“标准开发模板”。它不追求高可用只追求启动快、配置少、连接稳。以下是完整、可直接复制粘贴的docker-compose.ymlversion: 3.8 services: kafka: image: bitnami/kafka:3.7.0 container_name: kafka user: 1001:1001 ports: - 9092:9092 - 9093:9093 # KRaft controller 端口调试用 environment: # KRaft 核心配置 KAFKA_ENABLE_KRAFT: yes KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_CONTROLLER_QUORUM_VOTERS: 1kafka:9093 # 监听器配置关键 KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER # 存储与日志 KAFKA_LOG_DIRS: /var/lib/kafka/data KAFKA_AUTO_CREATE_TOPICS_ENABLE: true KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_LOG_RETENTION_HOURS: 168 KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 # JVM 调优 KAFKA_JVM_PERFORMANCE_OPTS: -Xms1g -Xmx1g -XX:UseG1GC -XX:MaxGCPauseMillis20 # 其他实用配置 KAFKA_LOG4J_ROOT_LOGLEVEL: INFO KAFKA_LOG4J_LOGGERS: kafka.controllerINFO,kafka.serverINFO volumes: - ./data:/var/lib/kafka/data healthcheck: test: [CMD-SHELL, kafka-broker-api-versions.sh --bootstrap-server localhost:9092 --command-config /tmp/client.properties 2/dev/null | grep -q API versions request failed || exit 1] interval: 30s timeout: 10s retries: 5 start_period: 40s启动与验证步骤创建空目录mkdir kafka-dev cd kafka-dev将上述 YAML 保存为docker-compose.yml执行docker-compose up -d等待约 15 秒检查状态docker-compose ps确认kafka状态为healthy创建测试 topicdocker-compose exec kafka kafka-topics.sh --create --topic test-topic --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1启动 console producerdocker-compose exec kafka kafka-console-producer.sh --topic test-topic --bootstrap-server localhost:9092在另一个终端启动 console consumerdocker-compose exec kafka kafka-console-consumer.sh --topic test-topic --bootstrap-server localhost:9092 --from-beginning在 producer 终端输入消息确认 consumer 能实时收到实操心得healthcheck的命令是关键。它用kafka-broker-api-versions.sh检查 broker 是否能正常响应 API 请求比简单的curl http://localhost:9092更准确因为它真正模拟了 Kafka 客户端的行为。我们曾因健康检查用错命令导致 CI 流水线在 broker 还未完全初始化时就执行测试大量测试用例失败。4.2 多节点 KRaft 集群模拟真实生产拓扑当需要测试分区副本同步、leader 选举、消费者组 rebalance 等高级特性时单节点不够用了。以下是三节点 KRaft 集群的docker-compose.yml它能在一台机器上完美模拟生产环境的网络拓扑version: 3.8 services: kafka-1: image: bitnami/kafka:3.7.0 container_name: kafka-1 user: 1001:1001 ports: - 9092:9092 - 9093:9093 environment: KAFKA_ENABLE_KRAFT: yes KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_CONTROLLER_QUORUM_VOTERS: 1kafka-1:9093,2kafka-2:9093,3kafka-3:9093 KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka-1:9092,CONTROLLER://kafka-1:9093 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_LOG_DIRS: /var/lib/kafka/data KAFKA_AUTO_CREATE_TOPICS_ENABLE: false # 关闭自动创建强制用脚本 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3 KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3 KAFKA_JVM_PERFORMANCE_OPTS: -Xms1g -Xmx1g -XX:UseG1GC -XX:MaxGCPauseMillis20 volumes: - ./data-1:/var/lib/kafka/data networks: kafka-net: aliases: - kafka-1 kafka-2: image: bitnami/kafka:3.7.0 container_name: kafka-2 user: 1001:1001 ports: - 9094:9092 # 映射到宿主机不同端口避免冲突 - 9095:9093 environment: KAFKA_ENABLE_KRAFT: yes KAFKA_NODE_ID: 2 KAFKA_PROCESS_ROLES: broker,controller KAFKA_CONTROLLER_QUORUM_VOTERS: 1kafka-1:9093,2kafka-2:9093,3kafka-3:9093 KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka-2:9092,CONTROLLER://kafka-2:9093 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_LOG_DIRS: /var/lib/kafka/data KAFKA_AUTO_CREATE_TOPICS_ENABLE: false KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3 KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3 KAFKA_JVM_PERFORMANCE_OPTS: -Xms1g -Xmx1g -XX:UseG1GC -XX:MaxGCPauseMillis20 volumes: - ./data-2:/var/lib/kafka/data networks: kafka-net: aliases: - kafka-2 kafka-3: image: bitnami/kafka:3.7.0 container_name: kafka-3 user: 1001:1001 ports: - 9096:9092 - 9097:9093 environment: KAFKA_ENABLE_KRAFT: yes KAFKA_NODE_ID: 3 KAFKA_PROCESS_ROLES: broker,controller KAFKA_CONTROLLER_QUORUM_VOTERS: 1kafka-1:9093,2kafka-2:9093,3kafka-3:9093 KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka-3:9092,CONTROLLER://kafka-3:9093 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_LOG_DIRS: /var/lib/kafka/data KAFKA_AUTO_CREATE_TOPICS_ENABLE: false KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3 KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3 KAFKA_JVM_PERFORMANCE_OPTS: -Xms1g -Xmx1g -XX:UseG1GC -XX:MaxGCPauseMillis20 volumes: - ./data-3:/var/lib/kafka/data networks: kafka-net: aliases: - kafka-3 # 附加服务Kafka UI方便可视化管理 kafka-ui: image: provectuslabs/kafka-ui:latest container_name: kafka-ui ports: - 8080:8080 environment: KAFKA_CLUSTERS_0_NAME: Local-Cluster KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka-1:9092,kafka-2:9092,kafka-3:9092 KAFKA_CLUSTERS_0_ZOOKEEPER: # KRaft 模式下留空 depends_on: - kafka-1 - kafka-2 - kafka-3 networks: - kafka-net networks: kafka-net: driver: bridge关键设计说明网络隔离显式定义kafka-net网络并为每个 broker 设置aliases确保容器间可通过kafka-1、kafka-2等名称互相解析这是多节点通信的基础端口映射三个 broker 的9092端口映射到宿主机的9092、9094、9096避免端口冲突同时保持容器内端口一致便于配置主题创建自动化由于KAFKA_AUTO_CREATE_TOPICS_ENABLEfalse我们提供一个初始化脚本init-topics.sh#!/bin/bash # 等待所有 broker 启动 sleep 30 # 创建系统 topic必须先于普通 topic docker-compose exec kafka-1 kafka-topics.sh --create --topic __consumer_offsets --bootstrap-server kafka-1:9092 --partitions 50 --replication-factor 3 --config cleanup.policycompact docker-compose exec kafka-1 kafka-topics.sh --create --topic __transaction_state --bootstrap-server kafka-1:9092 --partitions 50 --replication-factor 3 --config cleanup.policycompact # 创建业务 topic docker-compose exec kafka-1 kafka-topics.sh --create --topic orders --bootstrap-server kafka-1:9092 --partitions 6 --replication-factor 3 docker-compose exec kafka-1 kafka-topics.sh --create --topic payments --bootstrap-server kafka-1:9092 --partitions 6 --replication-factor 3执行chmod x init-topics.sh ./init-topics.sh即可完成初始化。4.3 CI/CD 流水线集成如何让 Kafka 成为可测试的“第一公民”在 GitLab CI 中我们要求每个 PR 的流水线必须包含 Kafka 集成测试。为此我们构建了一个专用的 CI 镜像ourorg/kafka-ci:3.7.0它预装了 Kafka CLI 工具、Python Kafka client、以及我们自定义的测试框架。以下是.gitlab-ci.yml的核心片段stages: - test variables: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: test-integration: stage: test image: docker:stable services: - docker:dind before_script: - apk add --no-cache python3 py3-pip - pip3 install kafka-python pytest script: # 启动 Kafka 集群使用精简版 compose无 UI - docker-compose -f docker-compose.ci.yml up -d # 等待 Kafka 就绪 - | for i in $(seq 1 60); do if docker-compose -f docker-compose.ci.yml exec -T kafka-1 kafka-broker-api-versions.sh --bootstrap-server localhost:9092 /dev/null 21; then echo Kafka is ready break fi sleep 2 done # 运行 Python 集成测试 - python3 -m pytest tests/integration/ --kafka-bootstraplocalhost:9092 after_script: - docker-compose -f docker-compose.ci.yml down -v artifacts: when: always paths: - junit.xml coverage: /^TOTAL.*\s([\d\.])/docker-compose.ci.yml是专为 CI 优化的版本移除了所有非必要服务如 Kafka UIKAFKA_LOG_RETENTION_HOURS设为 1避免磁盘占满KAFKA_AUTO_CREATE_TOPICS_ENABLEtrue简化测试 setup并启用--rm模式确保容器退出后自动清理。实操心得CI 环境的 Kafka 必须“快启快停”。我们曾用 Confluent 镜像启动耗时 45 秒导致流水线平均增加 2