【 Kafka进阶5】使用 Docker 部署 Apache Kafka 集群完全指南

从单节点快速尝鲜到生产级隔离模式集群,一文打通容器化 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 配置,需遵循严格的命名转换规则:

  1. 将原配置键中的 . 替换为 _
  2. _ 替换为 __(双下划线);
  3. - 替换为 ___(三下划线);
  4. 整体加上前缀 KAFKA_
原配置键 环境变量名
abc.def KAFKA_ABC_DEF
abc-def KAFKA_ABC___DEF
abc_def KAFKA_ABC__DEF

注意 :若只通过环境变量配置,必须提供所有必需的属性 (如 KAFKA_NODE_IDKAFKA_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 镜像支持)

  1. 挂载 JAAS 配置文件 到容器的 /etc/kafka/secrets/ 目录。
  2. 设置 KAFKA_OPTS-Djava.security.auth.login.config=/etc/kafka/secrets/<jaas_file>
  3. 设置 KAFKA_SASL_ENABLED_MECHANISMS(如 PLAINSCRAM-SHA-256)。
  4. KAFKA_ADVERTISED_LISTENERS 中使用 SASL_PLAINTEXT://SASL_SSL://
  5. 若需 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_FILENAMEKAFKA_SSL_KEYSTORE_CREDENTIALS
    • KAFKA_SSL_KEY_CREDENTIALS
    • KAFKA_SSL_TRUSTSTORE_FILENAMEKAFKA_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. 验证与健康检查

启动后,进行以下验证:

  1. 查看容器状态

    bash 复制代码
    docker compose ps

    所有服务应显示 Up

  2. 查看日志(确保无 ERROR)

    bash 复制代码
    docker logs kafka-1
    docker logs kafka-controller-1
  3. 创建主题并生产消费(进入任一 Broker 容器)

    bash 复制代码
    docker 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
  4. 外部客户端访问 :在宿主机使用 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

🌟 最佳实践

  1. 生产环境选用 JVM 镜像,Native 镜像仅限开发测试。
  2. 始终使用 KRaft 模式(抛弃 ZooKeeper),简化架构。
  3. 隔离模式 + 3 个 Controller 是最小高可用配置,Controller 数量应为奇数(如 3、5)。
  4. 监听器规划
    • 内部通信使用容器 hostname 和独立端口(如 19092)。
    • 外部通信通过宿主机映射端口,并正确填写 ADVERTISED_LISTENERS
  5. 日志与监控:挂载日志目录,接入 Prometheus + JMX Exporter 进行监控。
  6. 升级策略:先升级 Controller,再逐台升级 Broker,保证集群可用。

🎯 总结

本文从 Docker 部署 Kafka 的动机出发,系统介绍了两种官方镜像、三种配置输入方式、SASL/SSL 安全机制,并重点剖析了多节点集群的合并与隔离模式。尤其给出了生产级隔离模式的完整 Compose 配置,涵盖持久化、网络、健康检查等关键点。通过容器化,你可以轻松构建一个稳定、可扩展、易运维的 Kafka 环境,为微服务和事件驱动架构打下坚实基础。

💡 下一步:可将此 Compose 文件迁移到 Kubernetes,利用 StatefulSet 和 Headless Service 实现更强大的编排能力。


本文档基于 Apache Kafka 官方 Docker 镜像 4.x 版本编写,具体配置请以最新官方文档为准。 🚀

相关推荐
sel_92 小时前
【Docker】Docker 安装与使用详解:从零搭建到日常实战
人工智能·深度学习·算法·docker
垂钓的小鱼14 小时前
用关联图谱做量化选股:让基本面惊喜沿着网络扩散
服务器·php·apache
卷无止境12 小时前
Windows 上丝滑开发 Python,并稳定构建 Docker 镜像
后端·python·docker
深维AI随笔16 小时前
《构建之法》| 第二章个人技术和流程:单元测试不是“可选“,而是“必需“
单元测试·log4j·apache
吕海洋17 小时前
目前可用的docker源2026.08
运维·docker·容器
gsls20080819 小时前
Penpot Docker 部署与 MCP 服务配置文档
运维·docker·容器·原型·penpot
xrlfreedom20 小时前
大厂 MCP 面试实录:本地 Server 远程化改造中的技术选型与落地实践
docker·json schema·mcp
极客灵风1 天前
免费约课小程序免费约课系统的区别,一文看懂怎么选
apache
xixiaoyunya1 天前
中小微企业轻量组网方案的技术选型与实践:从传统专线到软定义隧道的成本与效能分析
服务器·php·apache