从单节点快速尝鲜到生产级隔离模式集群,一文打通容器化 Kafka 部署全链路。
1. Docker 部署 Kafka
传统的 Kafka 部署需要手动配置 JVM、ZooKeeper(或 KRaft 控制器)以及复杂的网络调优。使用 Docker 后:
- ✅ 环境一致性 ------ 开发、测试、生产使用相同镜像,消灭"在我机器上能跑"问题。
- ✅ 快速扩缩容 ------ 一行命令即可增加或减少 Broker 节点。
- ✅ 资源隔离 ------ 每个容器拥有独立的 CPU、内存和文件系统。
- ✅ 简化运维 ------ 配合 Docker Compose / Kubernetes 实现声明式管理。
更重要的是,Docker 官方镜像已从 3.7.0 版本开始提供 JVM 版,3.8.0 开始提供基于 GraalVM 的原生镜像,让部署变得更加轻量和便捷。
2. Docker 镜像概览
Apache Kafka 官方在 Docker Hub 上提供两类镜像:
| 镜像名称 | 基础技术 | 适用场景 | 备注 |
|---|---|---|---|
apache/kafka |
JVM (OpenJDK) | 生产环境、通用场景 | 稳定,从 3.7.0 开始支持 |
apache/kafka-native |
GraalVM 原生编译 | 本地开发、测试、快速启动 | 实验性,不建议生产,启动速度极快,但 SASL 等功能受限 |
拉取镜像
bash
# JVM 版
docker pull apache/kafka:4.3.1
docker pull apache/kafka:latest
# Native 版(实验)
docker pull apache/kafka-native:4.3.1
docker pull apache/kafka-native:latest
⚠️ Native 镜像限制 :由于缺少
java.security.AccessController的反射配置,SASL 认证目前不可用(参见 KAFKA-19584)。且仅推荐用于本地测试。
3. 准备工作
3.1 Docker 版本要求
-
必须 ≥ 20.10.4 ,否则在容器启动时可能因目录权限问题报错:
/opt/kafka/config/ file not writable旧版 Docker 无法正确设置容器内路径权限,升级即可解决。
3.2 KRaft 模式简介
从 Kafka 3.0 起,官方推荐使用 KRaft(Kafka Raft)替代 ZooKeeper 进行元数据管理。在 KRaft 中,节点角色分为:
- Controller(控制器):负责管理集群元数据、选举 Leader 等。
- Broker(数据节点):负责存储消息、处理生产/消费请求。
根据角色是否合一,集群分为两种模式(见 7 节)。
3.3 核心配置参数速查
| 环境变量 | 含义 | 示例 |
|---|---|---|
KAFKA_NODE_ID |
节点唯一 ID(整数) | 1 |
KAFKA_PROCESS_ROLES |
角色:controller / broker / controller,broker(合并) |
broker |
KAFKA_CONTROLLER_QUORUM_VOTERS |
控制器投票者列表,格式:id@host:port |
1@controller-1:9093,2@controller-2:9093 |
KAFKA_LISTENERS |
监听器列表(协议://地址:端口) | PLAINTEXT://0.0.0.0:9092 |
KAFKA_ADVERTISED_LISTENERS |
对外公布的监听器(客户端连接用) | PLAINTEXT://kafka-1:19092,PLAINTEXT_HOST://localhost:29092 |
CLUSTER_ID |
集群唯一标识(base64 编码,长度 22) | 4L6g3nShT-eMCtK--X86sw |
KAFKA_LOG_DIRS |
数据日志存储目录 | /tmp/kraft-combined-logs |
4. 三种配置输入方式
Kafka Docker 镜像支持三种方式提供配置,优先级从高到低为:环境变量 > 文件挂载 > 默认配置。
4.1 默认配置(开箱即用)
不提供任何自定义配置时,容器使用打包好的默认 KRaft 单节点配置(合并模式),监听 9092 端口。
bash
docker run -p 9092:9092 apache/kafka:4.3.1
4.2 文件挂载输入
将包含 server.properties 等配置文件的本地文件夹挂载到容器的 /mnt/shared/config,镜像启动时会自动替换默认配置。
bash
docker run --volume /path/to/property/folder:/mnt/shared/config -p 9092:9092 apache/kafka:latest
4.3 环境变量输入(最常用)
通过环境变量设置 Kafka 配置,需遵循严格的命名转换规则:
- 将原配置键中的
.替换为_; - 将
_替换为__(双下划线); - 将
-替换为___(三下划线); - 整体加上前缀
KAFKA_。
| 原配置键 | 环境变量名 |
|---|---|
abc.def |
KAFKA_ABC_DEF |
abc-def |
KAFKA_ABC___DEF |
abc_def |
KAFKA_ABC__DEF |
注意 :若只通过环境变量配置,必须提供所有必需的属性 (如 KAFKA_NODE_ID、KAFKA_CONTROLLER_QUORUM_VOTERS 等)。若同时使用文件挂载,环境变量会覆盖文件中的同名值。
4.4 Log4j 日志配置
KAFKA_LOG4J_ROOT_LOGLEVEL:设置根日志级别(如 INFO、DEBUG)。KAFKA_LOG4J_LOGGERS:逗号分隔的 logger 列表,例如property1=value1,property2=value2,会追加到log4j2.yaml中。
5. 安全认证配置(SASL / SSL)
5.1 SASL 模式(仅 JVM 镜像支持)
- 挂载 JAAS 配置文件 到容器的
/etc/kafka/secrets/目录。 - 设置
KAFKA_OPTS:-Djava.security.auth.login.config=/etc/kafka/secrets/<jaas_file>。 - 设置
KAFKA_SASL_ENABLED_MECHANISMS(如PLAIN、SCRAM-SHA-256)。 - 在
KAFKA_ADVERTISED_LISTENERS中使用SASL_PLAINTEXT://或SASL_SSL://。 - 若需 Broker 间 SASL 通信,设置
KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL和对应的KAFKA_INTER_BROKER_LISTENER_NAME。
⚠️ Native 镜像暂不支持 SASL,详见2节。
5.2 SSL 模式
推荐使用环境变量 + 挂载证书文件的方式:
- 将密钥库、信任库等文件挂载到
/etc/kafka/secrets。 - 设置以下环境变量:
KAFKA_SSL_KEYSTORE_FILENAME、KAFKA_SSL_KEYSTORE_CREDENTIALSKAFKA_SSL_KEY_CREDENTIALSKAFKA_SSL_TRUSTSTORE_FILENAME、KAFKA_SSL_TRUSTSTORE_CREDENTIALS
- 镜像内的脚本会自动提取密码并正确填充
server.properties。 - 同时
KAFKA_ADVERTISED_LISTENERS必须包含SSL://监听器。
若使用文件挂载方式提供 SSL 配置,需注意 advertised.listeners 必须与 SSL 属性在同一文件内,且不可再通过环境变量单独覆盖(否则会冲突,见优先级规则)。
6. 单节点快速上手示例
官方提供了丰富的 Docker Compose 示例,https://gitee.com/apache/kafka/tree/trunk/docker/examples/docker-compose-files/single-node,位于 docker/examples/docker-compose-files/single-node/。我们以最常见的 Plaintext(无加密) 为例:
yaml
# docker-compose.yml(简化)
services:
kafka:
image: ${IMAGE:-apache/kafka:latest}
environment:
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 # 单节点必须设为1
CLUSTER_ID: "4L6g3nShT-eMCtK--X86sw"
ports:
- "9092:9092"
启动命令(从仓库根目录执行):
bash
IMAGE=apache/kafka:latest docker compose -f docker/examples/docker-compose-files/single-node/plaintext/docker-compose.yml up
生产消息测试:
bash
bin/kafka-console-producer.sh --topic test --bootstrap-server localhost:9092
其他单节点示例(SSL、File Input、SASL_PLAINTEXT)结构类似,具体可查阅官方示例目录。
7. 多节点集群部署
在生产环境中,我们通常需要多节点集群来保证高可用。根据 Controller 和 Broker 是否合并,分为两种模式:
#mermaid-svg-W3cFkMM0CTO2klCJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-W3cFkMM0CTO2klCJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W3cFkMM0CTO2klCJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W3cFkMM0CTO2klCJ .error-icon{fill:#552222;}#mermaid-svg-W3cFkMM0CTO2klCJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W3cFkMM0CTO2klCJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W3cFkMM0CTO2klCJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W3cFkMM0CTO2klCJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W3cFkMM0CTO2klCJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W3cFkMM0CTO2klCJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W3cFkMM0CTO2klCJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W3cFkMM0CTO2klCJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W3cFkMM0CTO2klCJ .marker.cross{stroke:#333333;}#mermaid-svg-W3cFkMM0CTO2klCJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W3cFkMM0CTO2klCJ p{margin:0;}#mermaid-svg-W3cFkMM0CTO2klCJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-W3cFkMM0CTO2klCJ .cluster-label text{fill:#333;}#mermaid-svg-W3cFkMM0CTO2klCJ .cluster-label span{color:#333;}#mermaid-svg-W3cFkMM0CTO2klCJ .cluster-label span p{background-color:transparent;}#mermaid-svg-W3cFkMM0CTO2klCJ .label text,#mermaid-svg-W3cFkMM0CTO2klCJ span{fill:#333;color:#333;}#mermaid-svg-W3cFkMM0CTO2klCJ .node rect,#mermaid-svg-W3cFkMM0CTO2klCJ .node circle,#mermaid-svg-W3cFkMM0CTO2klCJ .node ellipse,#mermaid-svg-W3cFkMM0CTO2klCJ .node polygon,#mermaid-svg-W3cFkMM0CTO2klCJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-W3cFkMM0CTO2klCJ .rough-node .label text,#mermaid-svg-W3cFkMM0CTO2klCJ .node .label text,#mermaid-svg-W3cFkMM0CTO2klCJ .image-shape .label,#mermaid-svg-W3cFkMM0CTO2klCJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-W3cFkMM0CTO2klCJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-W3cFkMM0CTO2klCJ .rough-node .label,#mermaid-svg-W3cFkMM0CTO2klCJ .node .label,#mermaid-svg-W3cFkMM0CTO2klCJ .image-shape .label,#mermaid-svg-W3cFkMM0CTO2klCJ .icon-shape .label{text-align:center;}#mermaid-svg-W3cFkMM0CTO2klCJ .node.clickable{cursor:pointer;}#mermaid-svg-W3cFkMM0CTO2klCJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-W3cFkMM0CTO2klCJ .arrowheadPath{fill:#333333;}#mermaid-svg-W3cFkMM0CTO2klCJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-W3cFkMM0CTO2klCJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-W3cFkMM0CTO2klCJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W3cFkMM0CTO2klCJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-W3cFkMM0CTO2klCJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W3cFkMM0CTO2klCJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-W3cFkMM0CTO2klCJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-W3cFkMM0CTO2klCJ .cluster text{fill:#333;}#mermaid-svg-W3cFkMM0CTO2klCJ .cluster span{color:#333;}#mermaid-svg-W3cFkMM0CTO2klCJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-W3cFkMM0CTO2klCJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-W3cFkMM0CTO2klCJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-W3cFkMM0CTO2klCJ .icon-shape,#mermaid-svg-W3cFkMM0CTO2klCJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W3cFkMM0CTO2klCJ .icon-shape p,#mermaid-svg-W3cFkMM0CTO2klCJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-W3cFkMM0CTO2klCJ .icon-shape .label rect,#mermaid-svg-W3cFkMM0CTO2klCJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W3cFkMM0CTO2klCJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-W3cFkMM0CTO2klCJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-W3cFkMM0CTO2klCJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Kafka 集群部署模式
合并模式 Combined
隔离模式 Isolated
单节点同时承担 Controller 和 Broker
适用于开发/测试/小规模
Controller 和 Broker 分离
各自独立容器
适用于生产/大规模
| 模式 | 节点角色 | 典型场景 | 优点 | 缺点 |
|---|---|---|---|---|
| Combined | 每个节点都是 controller,broker |
开发测试、POC、资源受限环境 | 配置简单,节省资源 | 故障隔离差,不适合大规模 |
| Isolated | 专用 Controller 节点 + 专用 Broker 节点 | 生产环境、关键业务 | 高可用,职责清晰,易扩展 | 配置稍复杂,需更多资源 |
官方示例分别在 docker/examples/docker-compose-files/cluster/combined/ 和 cluster/isolated/ 下提供了 Plaintext、SSL、SASL_PLAINTEXT 三种场景。
7.1 合并模式(Combined)
以 Plaintext 为例,3 个 Broker 均配置 KAFKA_PROCESS_ROLES: 'controller,broker',并各自暴露不同主机端口(如 29092、39092、49092)。关键设计点:
- 多监听器 :每个 Broker 同时监听两个端口
PLAINTEXT(内部,用于 Broker 间通信)------ 地址为容器 hostname(如kafka-1:19092)PLAINTEXT_HOST(对外,用于客户端连接)------ 地址为localhost:29092
- 通过
KAFKA_INTER_BROKER_LISTENER_NAME指定内部使用哪个监听器。 - 这样,Broker 之间通过 Docker 网络用 hostname 互通,而客户端通过宿主机映射端口访问。
#mermaid-svg-wQ7HvvKyL8JThO3w{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wQ7HvvKyL8JThO3w .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wQ7HvvKyL8JThO3w .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wQ7HvvKyL8JThO3w .error-icon{fill:#552222;}#mermaid-svg-wQ7HvvKyL8JThO3w .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wQ7HvvKyL8JThO3w .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wQ7HvvKyL8JThO3w .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wQ7HvvKyL8JThO3w .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wQ7HvvKyL8JThO3w .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wQ7HvvKyL8JThO3w .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wQ7HvvKyL8JThO3w .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wQ7HvvKyL8JThO3w .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wQ7HvvKyL8JThO3w .marker.cross{stroke:#333333;}#mermaid-svg-wQ7HvvKyL8JThO3w svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wQ7HvvKyL8JThO3w p{margin:0;}#mermaid-svg-wQ7HvvKyL8JThO3w .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wQ7HvvKyL8JThO3w .cluster-label text{fill:#333;}#mermaid-svg-wQ7HvvKyL8JThO3w .cluster-label span{color:#333;}#mermaid-svg-wQ7HvvKyL8JThO3w .cluster-label span p{background-color:transparent;}#mermaid-svg-wQ7HvvKyL8JThO3w .label text,#mermaid-svg-wQ7HvvKyL8JThO3w span{fill:#333;color:#333;}#mermaid-svg-wQ7HvvKyL8JThO3w .node rect,#mermaid-svg-wQ7HvvKyL8JThO3w .node circle,#mermaid-svg-wQ7HvvKyL8JThO3w .node ellipse,#mermaid-svg-wQ7HvvKyL8JThO3w .node polygon,#mermaid-svg-wQ7HvvKyL8JThO3w .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wQ7HvvKyL8JThO3w .rough-node .label text,#mermaid-svg-wQ7HvvKyL8JThO3w .node .label text,#mermaid-svg-wQ7HvvKyL8JThO3w .image-shape .label,#mermaid-svg-wQ7HvvKyL8JThO3w .icon-shape .label{text-anchor:middle;}#mermaid-svg-wQ7HvvKyL8JThO3w .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wQ7HvvKyL8JThO3w .rough-node .label,#mermaid-svg-wQ7HvvKyL8JThO3w .node .label,#mermaid-svg-wQ7HvvKyL8JThO3w .image-shape .label,#mermaid-svg-wQ7HvvKyL8JThO3w .icon-shape .label{text-align:center;}#mermaid-svg-wQ7HvvKyL8JThO3w .node.clickable{cursor:pointer;}#mermaid-svg-wQ7HvvKyL8JThO3w .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wQ7HvvKyL8JThO3w .arrowheadPath{fill:#333333;}#mermaid-svg-wQ7HvvKyL8JThO3w .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wQ7HvvKyL8JThO3w .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wQ7HvvKyL8JThO3w .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wQ7HvvKyL8JThO3w .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wQ7HvvKyL8JThO3w .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wQ7HvvKyL8JThO3w .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wQ7HvvKyL8JThO3w .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wQ7HvvKyL8JThO3w .cluster text{fill:#333;}#mermaid-svg-wQ7HvvKyL8JThO3w .cluster span{color:#333;}#mermaid-svg-wQ7HvvKyL8JThO3w div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wQ7HvvKyL8JThO3w .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wQ7HvvKyL8JThO3w rect.text{fill:none;stroke-width:0;}#mermaid-svg-wQ7HvvKyL8JThO3w .icon-shape,#mermaid-svg-wQ7HvvKyL8JThO3w .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wQ7HvvKyL8JThO3w .icon-shape p,#mermaid-svg-wQ7HvvKyL8JThO3w .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wQ7HvvKyL8JThO3w .icon-shape .label rect,#mermaid-svg-wQ7HvvKyL8JThO3w .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wQ7HvvKyL8JThO3w .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wQ7HvvKyL8JThO3w .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wQ7HvvKyL8JThO3w :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Docker网络
宿主机
localhost:29092
localhost:39092
内部通信 via kafka-1:19092
客户端
Broker-1
listener: PLAINTEXT://kafka-1:19092
PLAINTEXT_HOST://:9092
Broker-2
listener: PLAINTEXT://kafka-2:19092
PLAINTEXT_HOST://:9092
启动命令(替换 IMAGE 即可切换 JVM/Native):
bash
IMAGE=apache/kafka:latest docker compose -f docker/examples/docker-compose-files/cluster/combined/plaintext/docker-compose.yml up
7.2 隔离模式(Isolated)------ 生产推荐
在此模式中,Controller 和 Broker 完全分离:
- 3 个 Controller 节点 :仅运行 Controller 角色(
KAFKA_PROCESS_ROLES: 'controller'),监听CONTROLLER端口(9093)用于 Raft 选举。 - 3 个 Broker 节点 :仅运行 Broker 角色(
KAFKA_PROCESS_ROLES: 'broker'),监听数据端口(9092 内外双监听器)。
这种架构更稳健,Controller 故障不影响 Broker 的数据服务,Broker 扩缩容不影响元数据管理。
8. 隔离模式完整部署实战(含 Compose 文件)
以下是一份可直接投入测试环境的 compose.yaml(基于官方示例优化,增加了持久化卷、明确容器名和专用网络)。
yaml
# compose.yaml - 隔离模式 (Isolated) 无 SSL
networks:
kafka:
name: kafka
driver: bridge
volumes:
controller-1: { name: kafka-controller-1 }
controller-2: { name: kafka-controller-2 }
controller-3: { name: kafka-controller-3 }
kafka1-logs: { name: kafka1-logs }
kafka2-logs: { name: kafka2-logs }
kafka3-logs: { name: kafka3-logs }
services:
# 初始化权限(修复容器内目录所有者)
init-kafka-perms:
image: busybox:latest
container_name: kafka-perms-fix
command: sh -c "chown -R 1000:1000 /controller-1 /controller-2 /controller-3 /kafka1 /kafka2 /kafka3"
volumes:
- controller-1:/controller-1
- controller-2:/controller-2
- controller-3:/controller-3
- kafka1-logs:/kafka1
- kafka2-logs:/kafka2
- kafka3-logs:/kafka3
networks: [ kafka ]
restart: "no"
# ---- Controller 节点 ----
controller-1:
image: apache/kafka:4.2.0 # 可替换为 kafka-native
container_name: kafka-controller-1
hostname: controller-1
restart: unless-stopped
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: 'controller'
KAFKA_CONTROLLER_QUORUM_VOTERS: '1@controller-1:9093,2@controller-2:9093,3@controller-3:9093'
KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
KAFKA_LISTENERS: 'CONTROLLER://0.0.0.0:9093'
CLUSTER_ID: '4L6g3nShT-eMCtK--X86sw'
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3
KAFKA_SHARE_COORDINATOR_STATE_TOPIC_REPLICATION_FACTOR: 3
KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs'
volumes:
- controller-1:/tmp/kraft-combined-logs
networks: [ kafka ]
depends_on:
init-kafka-perms: { condition: service_completed_successfully }
healthcheck:
test: nc -z localhost 9093 || exit 1
interval: 30s; timeout: 5s; retries: 3; start_period: 10s
# controller-2 和 controller-3 配置相同,仅 NODE_ID 和 volume 不同(省略,类似)
# ---- Broker 节点 ----
kafka-1:
image: apache/kafka:4.2.0
container_name: kafka-1
hostname: kafka-1
ports:
- "29092:9092" # 对外暴露端口
restart: unless-stopped
environment:
KAFKA_NODE_ID: 4
KAFKA_PROCESS_ROLES: 'broker'
KAFKA_CONTROLLER_QUORUM_VOTERS: '1@controller-1:9093,2@controller-2:9093,3@controller-3:9093'
# 内部监听(Broker间)和外部监听(客户端)
KAFKA_LISTENERS: 'PLAINTEXT://:19092,PLAINTEXT_HOST://:9092'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT'
KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT'
KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka-1:19092,PLAINTEXT_HOST://localhost:29092'
KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
CLUSTER_ID: '4L6g3nShT-eMCtK--X86sw'
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 2
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3
KAFKA_SHARE_COORDINATOR_STATE_TOPIC_REPLICATION_FACTOR: 3
KAFKA_SHARE_COORDINATOR_STATE_TOPIC_MIN_ISR: 2
KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs'
volumes:
- kafka1-logs:/tmp/kraft-combined-logs
networks: [ kafka ]
depends_on:
controller-1: { condition: service_healthy }
controller-2: { condition: service_healthy }
controller-3: { condition: service_healthy }
healthcheck:
test: nc -z localhost 9092 || exit 1
interval: 60s; timeout: 5s; retries: 2; start_period: 30s
# kafka-2 和 kafka-3 类似,修改 NODE_ID, ports, hostname, volume 映射即可
🔑 关键设计解读:
- 数据持久化 :每个容器挂载独立命名卷(如
controller-1),即使容器删除,数据仍保留。 - 明确容器名 :通过
container_name固定名称,避免自动生成的随机名导致管理混乱。 - 专用网络 :自定义网络
kafka,容器间可通过 hostname 直接通信(如controller-1),无需关心 IP 变化。 - 健康检查 :使用
nc探测端口,确保依赖顺序正确(Broker 等待所有 Controller 就绪)。
启动命令:
bash
docker compose -f compose.yaml up -d
9. 验证与健康检查
启动后,进行以下验证:
-
查看容器状态
bashdocker compose ps所有服务应显示
Up。 -
查看日志(确保无 ERROR)
bashdocker logs kafka-1 docker logs kafka-controller-1 -
创建主题并生产消费(进入任一 Broker 容器)
bashdocker exec -it kafka-1 bash # 创建主题(replication-factor 不能超过 Broker 数) kafka-topics.sh --create --topic test --bootstrap-server localhost:9092 --partitions 3 --replication-factor 3 # 列出主题 kafka-topics.sh --list --bootstrap-server localhost:9092 # 生产消息 kafka-console-producer.sh --topic test --bootstrap-server localhost:9092 # 另开终端消费 kafka-console-consumer.sh --topic test --bootstrap-server localhost:29092 --from-beginning -
外部客户端访问 :在宿主机使用
localhost:29092(对应 kafka-1)即可连接。
10. 常见问题与最佳实践
❗常见问题
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 容器启动失败,权限错误 | Docker 版本 < 20.10.4 | 升级 Docker 或手动 chown 挂载目录 |
| 客户端连接超时 | KAFKA_ADVERTISED_LISTENERS 地址不可达 |
检查是否使用了 localhost 或正确的 IP,确保端口映射正确 |
| Broker 无法加入集群 | 节点 ID 重复或 CONTROLLER_QUORUM_VOTERS 配置错误 |
核对每个节点的 KAFKA_NODE_ID 和投票者列表是否一致 |
| 数据丢失 | 未挂载持久化卷,容器删除后数据消失 | 使用命名卷或绑定挂载,并定期备份 |
| SASL 无法使用(Native 镜像) | GraalVM 原生镜像限制 | 切换到 JVM 版镜像 apache/kafka |
🌟 最佳实践
- 生产环境选用 JVM 镜像,Native 镜像仅限开发测试。
- 始终使用 KRaft 模式(抛弃 ZooKeeper),简化架构。
- 隔离模式 + 3 个 Controller 是最小高可用配置,Controller 数量应为奇数(如 3、5)。
- 监听器规划 :
- 内部通信使用容器 hostname 和独立端口(如 19092)。
- 外部通信通过宿主机映射端口,并正确填写
ADVERTISED_LISTENERS。
- 日志与监控:挂载日志目录,接入 Prometheus + JMX Exporter 进行监控。
- 升级策略:先升级 Controller,再逐台升级 Broker,保证集群可用。
🎯 总结
本文从 Docker 部署 Kafka 的动机出发,系统介绍了两种官方镜像、三种配置输入方式、SASL/SSL 安全机制,并重点剖析了多节点集群的合并与隔离模式。尤其给出了生产级隔离模式的完整 Compose 配置,涵盖持久化、网络、健康检查等关键点。通过容器化,你可以轻松构建一个稳定、可扩展、易运维的 Kafka 环境,为微服务和事件驱动架构打下坚实基础。
💡 下一步:可将此 Compose 文件迁移到 Kubernetes,利用 StatefulSet 和 Headless Service 实现更强大的编排能力。
本文档基于 Apache Kafka 官方 Docker 镜像 4.x 版本编写,具体配置请以最新官方文档为准。 🚀