单节点 Kafka(KRaft,无 Zookeeper)Docker Compose 部署手册(测试/生产双环境)
目标:把原本较重的 Kafka 集群部署,简化为。
单节点 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:9092或127.0.0.1:19092 - 客户端拿到这个地址后再去连,就会出现:
java.net.UnknownHostException: kafkaConnection 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.yamliot-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_LISTENERS的EXTERNAL是否是“外部可达 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保证版本一致
更多推荐
所有评论(0)