单节点 Kafka(KRaft,无 Zookeeper)Docker Compose 部署手册(测试/生产双环境)

目标:把原本较重的 Kafka 集群部署,简化为 单节点 Kafka(KRaft 模式),降低内存占用、减少组件数量,同时满足:

  • 测试环境:既能给“测试机本地服务(服务A / 服务B)”访问,也能给“外部客户端(例如开发电脑)”访问
  • 生产环境:只允许“生产机本机服务”访问(不对外暴露端口)

背景:为什么单节点可以不要 Zookeeper?

Kafka 新版本支持 KRaft(Kafka Raft) 模式:把原先依赖 Zookeeper 的元数据管理,改成由 Kafka 自己的 controller quorum 管理。

对于 单节点 场景:

  • 不需要额外部署 Zookeeper
  • 配置更少、资源更省、运维更简单

本文采用 Bitnami Kafka 镜像开启 KRaft。


目录结构

仓库中放在(推荐):

  • kafka/single-lite/docker-compose-test.yml:测试环境(对外开放 19092)
  • kafka/single-lite/docker-compose-prod.yml:生产环境(仅绑定 127.0.0.1)

从 0 到 1:部署前准备(小白照抄)

本节以 Ubuntu / Debian 系为例(你当前服务器环境也是这个方向)。如果你用的是其他发行版,把“安装 Docker”那一步替换成对应系统的安装方式即可。

1)安装 Docker 与 Docker Compose

确认 Docker 是否已安装:

docker version
docker compose version || docker-compose version

如果没有安装,按 Docker 官方文档安装(推荐使用官方源):https://docs.docker.com/engine/install/

安装完成后,建议把当前用户加入 docker 组(可选):

sudo usermod -aG docker $USER
newgrp docker

2)准备目录(建议统一放到 /docker/kafka)

sudo mkdir -p /docker/kafka
cd /docker/kafka

3)准备数据目录(Kafka 数据会持久化在这里)

生产建议放数据盘,比如 /mnt/data/...;测试随便放也可以。

sudo mkdir -p /mnt/data/kafka-single

4)防火墙/安全组(非常重要)

  • 测试环境:如果要让外部访问,至少需要放行:
    • 19092/tcp(Kafka 外部端口)
    • 18080/tcp(Kafka-UI)
  • 生产环境:本教程将端口绑定到 127.0.0.1,外部默认无法访问(但仍建议在安全组层面不要放行这些端口)

关键概念:listeners / advertised.listeners(最容易踩坑)

Kafka 有两组概念:

  • listeners:broker 在哪些地址/端口上“监听”
  • advertised.listeners:broker 告诉客户端“你应该用哪个地址/端口来连我”

典型故障

  • 客户端连的是 192.168.x.x:19092,但 broker 回给客户端的却是 kafka:9092127.0.0.1:19092
  • 客户端拿到这个地址后再去连,就会出现:
    • java.net.UnknownHostException: kafka
    • Connection to node -1 ... could not be established
    • Kafka-UI 500(创建 AdminClient 失败)

所以:测试环境对外开放时,advertised.listeners 的 EXTERNAL 地址必须是“外部客户端能访问到的 IP/域名”。


测试环境部署(外部可访问)

1)准备 docker-compose-test.yml(直接复制粘贴)

你可以直接使用仓库中的 kafka/single-lite/docker-compose-test.yml

如果你是在“新环境从 0 手动搭建”,也可以把下面内容保存为 /docker/kafka/docker-compose-test.yml

它的关键点:

  • 对外映射端口:19092:19092
  • advertised.listeners 中的 EXTERNAL://<测试机IP>:19092 可通过环境变量 KAFKA_EXTERNAL_HOST 注入
  • 单节点副本因子全部为 1(否则会因为没有足够 broker 而报错)
  • 通过 KAFKA_HEAP_OPTS=-Xms512m -Xmx512m 降低内存占用
version: "3.8"

# 测试环境:允许“外部客户端(例如你的本地电脑)”直连测试机 Kafka
services:
  kafka:
    image: ${KAFKA_IMAGE:-bitnami/kafka:3.7}
    container_name: kafka-single
    hostname: kafka
    ports:
      # 对外端口:允许你本地通过 <测试机IP>:19092 访问
      - "19092:19092"
    environment:
      # ===== KRaft(无 Zookeeper)=====
      - KAFKA_ENABLE_KRAFT=yes
      - KAFKA_CFG_PROCESS_ROLES=broker,controller
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka:9093
      - KAFKA_CFG_NODE_ID=1

      # ===== 监听器 =====
      # 内部:kafka:9092(给同 compose 内的 kafka-ui 用)
      # 外部:<测试机IP>:19092(给你本地/外部客户端用)
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,EXTERNAL://:19092,CONTROLLER://:9093
      # 关键:对外 advertised 必须是“外部客户端可解析/可路由”的地址(否则会出现 UnknownHostException: kafka)
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://${KAFKA_EXTERNAL_HOST:-<TEST_SERVER_IP>}:19092
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT

      # ===== 单机关键:副本因子一律 1 =====
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=1
      - KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1
      - KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR=1
      - KAFKA_CFG_DEFAULT_REPLICATION_FACTOR=1
      - KAFKA_CFG_MIN_INSYNC_REPLICAS=1

      # ===== 其他常用 =====
      - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
      - KAFKA_CFG_NUM_PARTITIONS=3
      - KAFKA_CFG_COMPRESSION_TYPE=lz4

      # ===== 保留策略 =====
      - KAFKA_CFG_LOG_RETENTION_HOURS=72
      - KAFKA_CFG_LOG_SEGMENT_BYTES=536870912

      # ===== 内存(JVM)=====
      - KAFKA_HEAP_OPTS=-Xms512m -Xmx512m

      # 允许明文(测试/内网)
      - ALLOW_PLAINTEXT_LISTENER=yes
    volumes:
      - ${KAFKA_DATA_DIR:-/mnt/data/kafka-single}:/bitnami/kafka
    restart: unless-stopped

  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    container_name: kafka-ui-single
    depends_on:
      - kafka
    ports:
      # Kafka-UI 对外(测试环境)
      - "18080:8080"
    environment:
      - KAFKA_CLUSTERS_0_NAME=kafka-test
      - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092
      - DYNAMIC_CONFIG_ENABLED=true
    restart: unless-stopped

2)启动命令(在测试机上)

KAFKA_EXTERNAL_HOST 设置成“你的开发电脑能访问到的测试机 IP”(通常是内网 IP):

export KAFKA_EXTERNAL_HOST="<TEST_SERVER_IP>"
export KAFKA_DATA_DIR="/mnt/data/kafka-single"     # 可选:改成真实数据盘
export KAFKA_IMAGE="bitnami/kafka:3.7"             # 可选:以你环境可拉取为准

docker compose -f /docker/kafka/docker-compose-test.yml up -d

3)验证(测试机本地)

docker ps | grep kafka-single

docker exec -it kafka-single bash -lc "/opt/bitnami/kafka/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list"

4)验证(外部客户端,例如你的电脑)

外部客户端应使用:

  • <TEST_SERVER_IP>:19092

如果你用的是 Kafka-UI(测试 compose 里带了),浏览器访问:

  • http://<测试机IP>:18080

如果 Kafka-UI 打开报 500,优先检查 advertised.listeners 是否把 EXTERNAL 配成了正确的测试机 IP。


生产环境部署(仅本机可访问)

1)准备 docker-compose-prod.yml(直接复制粘贴)

你可以直接使用仓库中的 kafka/single-lite/docker-compose-prod.yml

如果你是在“新环境从 0 手动搭建”,也可以把下面内容保存为 /docker/kafka/docker-compose-prod.yml

它的关键点:

  • Kafka 对外端口 只绑定到本机
    • 127.0.0.1:19092:19092
  • Kafka-UI 也只绑定到本机:
    • 127.0.0.1:18080:8080
  • advertised.listeners 的 EXTERNAL 也固定为:
    • EXTERNAL://127.0.0.1:19092

这意味着:

  • 生产机上跑的服务(同机)可以直接连 127.0.0.1:19092
  • 外部机器无法直接连到 Kafka(减少暴露面)
version: "3.8"

# 生产环境:仅本机可访问 Kafka(不对外暴露)
services:
  kafka:
    image: ${KAFKA_IMAGE:-bitnami/kafka:3.7}
    container_name: kafka-single
    hostname: kafka
    ports:
      # 仅本机可访问:绑定到 127.0.0.1
      - "127.0.0.1:19092:19092"
    environment:
      # ===== KRaft(无 Zookeeper)=====
      - KAFKA_ENABLE_KRAFT=yes
      - KAFKA_CFG_PROCESS_ROLES=broker,controller
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka:9093
      - KAFKA_CFG_NODE_ID=1

      # ===== 监听器 =====
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,EXTERNAL://:19092,CONTROLLER://:9093
      # 生产同机:对外 advertised 固定为 127.0.0.1,避免客户端拿到 kafka:19092
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://127.0.0.1:19092
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT

      # ===== 单机关键:副本因子一律 1 =====
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=1
      - KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1
      - KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR=1
      - KAFKA_CFG_DEFAULT_REPLICATION_FACTOR=1
      - KAFKA_CFG_MIN_INSYNC_REPLICAS=1

      # ===== 其他常用 =====
      - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
      - KAFKA_CFG_NUM_PARTITIONS=3
      - KAFKA_CFG_COMPRESSION_TYPE=lz4

      # ===== 保留策略 =====
      - KAFKA_CFG_LOG_RETENTION_HOURS=72
      - KAFKA_CFG_LOG_SEGMENT_BYTES=536870912

      # ===== 内存(JVM)=====
      - KAFKA_HEAP_OPTS=-Xms512m -Xmx512m

      - ALLOW_PLAINTEXT_LISTENER=yes
    volumes:
      - ${KAFKA_DATA_DIR:-/mnt/data/kafka-single}:/bitnami/kafka
    restart: unless-stopped

  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    container_name: kafka-ui-single
    depends_on:
      - kafka
    ports:
      # Kafka-UI 不建议对外暴露;这里同样只绑定本机
      - "127.0.0.1:18080:8080"
    environment:
      - KAFKA_CLUSTERS_0_NAME=kafka-prod
      - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092
      - DYNAMIC_CONFIG_ENABLED=true
    restart: unless-stopped

2)启动命令(在生产机上)

export KAFKA_DATA_DIR="/mnt/data/kafka-single"     # 建议指向数据盘
export KAFKA_IMAGE="bitnami/kafka:3.7"             # 生产环境建议固定版本

docker compose -f /docker/kafka/docker-compose-prod.yml up -d

3)验证(生产机本地)

1)确认端口监听(宿主机)

ss -lntp | grep 19092 || netstat -lntp | grep 19092

2)确认端口可连(宿主机)

nc -vz 127.0.0.1 19092

3)确认 Kafka 可用(容器内)

注意:下面命令是在 kafka 容器内127.0.0.1:19092,表示容器内 EXTERNAL listener 正常。

docker exec -it kafka-single bash -lc "/opt/bitnami/kafka/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:19092 --list"

如果你要验证“宿主机能否用 127.0.0.1 访问 Kafka”,请优先看第 2 步的 nc 结果。


常用运维命令(启动/停止/重建/看日志)

# 启动(后台)
docker compose -f /docker/kafka/docker-compose-<test|prod>.yml up -d

# 停止并删除容器(数据目录不会删除)
docker compose -f /docker/kafka/docker-compose-<test|prod>.yml down

# 强制重建(改了环境变量或 advertised.listeners 很常用)
docker compose -f /docker/kafka/docker-compose-<test|prod>.yml up -d --force-recreate

# 看日志
docker logs kafka-single --tail 200

应用侧配置(Spring Boot)

我们的应用使用 bootstrap-servers 指向 Kafka。

常见两种部署方式:

  • 同机部署 + host 网络:应用可直接连 127.0.0.1:19092
  • 跨机器访问测试 Kafka:应用需连 <测试机IP>:19092

仓库里相关配置(示例):

  • iot-server/src/main/resources/application-test.yaml
  • iot-server/src/main/resources/application-prod.yaml

其中生产的 Kafka 段落写法(节选)强调了 host 网络方案:

  • 生产同机:127.0.0.1:19092
  • 若生产 Kafka 在独立服务器:改为对应内网地址

分区数(partitions)单节点需要多少?

单节点时:

  • 副本因子(replication factor)必须是 1
  • 分区数(num.partitions)与“并发/吞吐/消费组并行度”相关,不是单节点必须 1

本文默认设置为 3

  • 6 更省资源
  • 仍保留一定并行度

如果你未来发现:

  • topic 很少、吞吐不高:可以降低
  • 需要更高并行消费:可以提高(注意磁盘和句柄数量)

常见故障与排查

1)Kafka-UI 500 / AdminClient 创建失败

现象

  • Kafka-UI 页面 500
  • 日志提示无法创建 AdminClient / broker 不可达

优先检查

  • 测试环境 KAFKA_CFG_ADVERTISED_LISTENERSEXTERNAL 是否是“外部可达 IP”
  • 是否忘了 --force-recreate 导致容器还是旧环境变量

建议操作:

docker compose -f kafka/single-lite/docker-compose-test.yml down
docker compose -f kafka/single-lite/docker-compose-test.yml up -d --force-recreate

2)应用日志:UnknownHostException: kafka

原因

  • broker 把 kafka:9092 当成对外地址 advertised 给了客户端
  • 客户端不在 docker 网络里,自然解析不了 kafka

解决

  • 测试环境:advertised.listeners 的 EXTERNAL 必须写外部可访问地址
  • 生产环境:如果应用与 Kafka 同机,使用 127.0.0.1:19092(并确保应用运行在 host 网络或宿主机)

3)应用日志:Connection to node -1 ... could not be established

常见原因

  • broker 实际没起来 / 端口未监听
  • advertised.listeners 返回的地址客户端不可达

排查

docker logs kafka-single --tail 200
ss -lntp | grep 19092
nc -vz 127.0.0.1 19092

4)manifest unknown:拉不到 bitnami/kafka:3.7 / latest

现象

manifest for bitnami/kafka:3.7 not found: manifest unknown

关键结论

即使两台机器 daemon.json mirror 配置相同,由于 DNS/CDN/节点缓存差异,不同机器可能命中不同 mirror 节点,从而出现:

  • 测试可拉
  • 生产不可拉

最稳解决方案(保证测试/生产版本一致):从测试机把镜像“搬运”到生产机:

在测试机:

docker save -o /tmp/bitnami-kafka.tar bitnami/kafka:3.7
gzip -1 /tmp/bitnami-kafka.tar

传到生产机后:

gunzip -c /tmp/bitnami-kafka.tar.gz | docker load

这样生产不依赖公网 mirror,也能做到 版本完全一致


安全提示(必须知道)

本文为简化部署使用了明文监听(PLAINTEXT)。建议至少做到:

  • 测试环境:可对外开放,但务必通过防火墙限制来源 IP
  • 生产环境:只绑定 127.0.0.1,避免公网暴露

若未来需要更高安全级别,可进一步启用 SASL/TLS 与 ACL。


一键复用清单(新环境照抄)

  • 复制 kafka/single-lite/docker-compose-test.yml / docker-compose-prod.yml
  • 测试环境设置 KAFKA_EXTERNAL_HOST=<测试机IP>
  • 生产环境端口只绑定 127.0.0.1
  • 应用同机(host 网络)用 127.0.0.1:19092
  • 遇到连不上优先排查 advertised.listeners
  • 镜像拉不下来就用 docker save/load 保证版本一致
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐