Docker部署Kafka排障实录

Docker 部署 Kafka 排障实录:连接超时、健康检查误报、数据判定三连坑

一次用 Docker Compose 部署 Kafka + Zookeeper,再用 Offset Explorer 查看数据的完整排障经历。遇到的三个坑------连接超时Zookeeper 永远 unhealthy连上了却没数据------每一个都有明确根因和可复现的排查路径,整理成文供参考。


背景

在测试环境用 Docker Compose 拉起一套单节点 Kafka 集群:

  • Kafka : confluentinc/cp-kafka:7.6.1
  • Zookeeper : confluentinc/cp-zookeeper:7.6.1(7.6.1 内置 ZK 3.8,还是 ZK 模式,不是 KRaft)
  • 宿主机端口映射:Kafka 19092 → 9092,ZK 15998 → 2181
  • 客户端工具:Offset Explorer 2.3(Windows 桌面端)

业务侧有若干个业务 topic,期望由对端系统推送数据进来,我们用 Offset Explorer 观察数据是否到达。

需求很简单:连上 Kafka,看数据到没到。结果一路踩了三个坑。


坑一:Offset Explorer 连接超时

现象

配置好 bootstrap servers 后点连接,转圈很久后报错:

css 复制代码
1Connection Error2Error connecting to the cluster. Timeout connecting to broker(s),3bootstrap.servers=192.168.1.100:19092

注意关键词 Timeout------TCP 层面就没建立起来(如果是端口通了但配置不对,通常会报 refused / invalid url,而不是 timeout)。

排查路径

  1. IP 对不对? hostname -I 确认客户端填的 IP 确实是这台服务器。
  2. 端口在不在监听? ss -lntp | grep 19092 确认 docker-proxy 正常监听,端口映射没掉。
  3. 防火墙? firewall-cmd --list-portsfirewall-cmd --state 确认 19092 是否放行。
  4. 最终定位在 advertised.listeners------这才是真正的根因。

根因:advertised.listeners 广播了 localhost

看 Kafka 容器的监听配置:

dart 复制代码
1docker exec kafka bash -c 'echo $KAFKA_ADVERTISED_LISTENERS; echo $KAFKA_LISTENERS'

输出:

ruby 复制代码
1ADVERTISED=PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:190922LISTENERS=PLAINTEXT://0.0.0.0:29092,PLAINTEXT_HOST://0.0.0.0:9092

问题就在 PLAINTEXT_HOST://localhost:19092

Kafka 的连接机制是两步走:

  1. 客户端拿着 bootstrap.servers 里的地址,先建立第一个 TCP 连接(bootstrap 阶段);
  2. Broker 在元数据响应里返回 advertised.listeners 中配置的地址,客户端丢弃 bootstrap 地址,改用广播地址去建立真正的工作连接。

所以 localhost:19092 一广播出去,客户端(Offset Explorer)就拿着它去连自己电脑上的 19092 端口------本机哪有服务,自然是超时/拒绝。

Docker 部署特别容易踩这个坑:容器内部用的是容器名(kafka)+ 内部端口(29092),对外又是宿主机 IP + 映射端口(19092),一套配置里要同时描述两个网络视角,localhost 这种"从哪看都对、从客户端看全错"的写法很容易混进去。

修复

把对外广播地址改成客户端真正可达的地址:

ruby 复制代码
1kafka:2  environment:3    KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://192.168.1.100:19092
  • PLAINTEXT://kafka:29092 保留------容器间通信(生产/消费组件在同一个 docker 网络里)用这个;
  • PLAINTEXT_HOST://<宿主机真实IP>:19092------外部客户端用这个。端口必须写宿主机映射端口 19092,不是容器内 9092。

改完 docker compose up -d 重建,Offset Explorer 一次连上 ✅。

一个口诀

listeners 是"我监听在哪",advertised.listeners 是"我告诉客户端去哪连"。 后者必须是客户端视角的地址,不是服务器视角的地址。


坑二:Zookeeper 永远 unhealthy

现象

连上 Kafka 后回头看 docker ps

scss 复制代码
1clodopt-kafka      Up 21 minutes (healthy)2clodopt-zookeeper  Up 26 minutes (unhealthy)     ← 一直红

Zookeeper 从启动起就是 unhealthy,重启也没用。但诡异的是:Kafka 一直是 healthy

先下个判断:这是误报

Kafka 是 ZK 模式,它的所有元数据(broker 注册、topic、offset)都存在 Zookeeper 里。Kafka 能 healthy,说明它连 ZK 完全正常、读写元数据都没问题------ZK 功能是好的 。这个 unhealthy 几乎肯定是健康检查本身的问题。

定位:看 ZK 日志

复制代码
1docker logs clodopt-zookeeper

翻到启动日志末尾两行:

lua 复制代码
1INFO The list of known four letter word commands is : [..., srvr, ruok, ...]2INFO The list of enabled four letter word commands is : [[srvr]]

看到 enabled = [[srvr]] 就明白了。

根因:ZK 3.6+ 默认只放行 srvr 四字命令

Zookeeper 从 3.6.0 起,出于安全考虑,四字命令(4 letter word)默认只启用 srvr 一个 ,其他(ruokstatmntrconf 等)全部被白名单拦掉。白名单由 4lw.commands.whitelist 控制。

而我们 compose 里写的健康检查偏偏用了 ruok

bash 复制代码
1zookeeper:2  healthcheck:3    test: ["CMD-SHELL", "echo ruok | nc -w 2 localhost 2181 | grep -q imok"]

ruok 被白名单拒绝,手动执行都能看到明确回复:

python 复制代码
1$ docker exec clodopt-zookeeper bash -c 'echo ruok | nc -w 2 localhost 2181'2ruok is not executed because it is not in the whitelist.

grep -q imok 永远失败 → 永远 unhealthy

修复(推荐):healthcheck 改用默认放行的 srvr

与其放行所有四字命令(降低安全水位),不如让健康检查用 srvr------它是默认启用的:

bash 复制代码
1zookeeper:2  healthcheck:3    test: ["CMD-SHELL", "echo srvr | nc -w 2 localhost 2181 | grep -q 'Zookeeper version'"]

改前手动验证这条命令能返回内容:

bash 复制代码
1docker exec clodopt-zookeeper bash -c "echo srvr | nc -w 2 localhost 2181"2# 输出 Zookeeper version: 3.8.x-... 的 server stats 即代表可行

如果确实需要 ruok(比如自己写的脚本依赖它),也可以放行白名单,但更推荐前者:

bash 复制代码
1environment:2  ZOOKEEPER_4LW_COMMANDS_WHITELIST: "*"   # 或 ruok,srvr,stat 等按需

经验

容器显示 unhealthy 时,先别急着认为服务挂了。结合依赖它的服务是否正常 (这里 Kafka healthy 反向证明了 ZK 正常)+ 健康检查命令本身 (四字命令白名单、命令是否存在于镜像内)两步判断,很多 unhealthy 是健康检查写得不对,不是服务坏了。


坑三:连上了,但所有 topic 都没有数据

现象

Offset Explorer 连上集群了,Brokers、Topics、Consumers 都出来了,但点进任何一个 topic 的 Data 页,全是 No Data 。Messages 切到 Oldest 也没有。

这时候要回答的问题变成:到底有没有数据进来?是 Kafka 的问题还是对端没推?

定位:用命令查所有 topic 的累计 offset

GUI 里翻不如命令来得直接。一条命令看全部 topic 的消息总数:

kotlin 复制代码
1docker exec kafka kafka-run-class kafka.tools.GetOffsetShell \2  --broker-list localhost:9092 --time -1

小坑:--all-topic-listkafka-topics 的选项,kafka-get-offsets 不认识。不带 --topic 直接跑 GetOffsetShell 才是"列全部"的正确姿势。

输出格式 topic:partition:offset

makefile 复制代码
1topic_a:0:02topic_b:0:03topic_c:0:0

全部 offset 为 0 ,结论立刻清晰:从这些 topic 创建至今,Kafka 一条消息都没收到过

判断依据(可靠):Kafka 消息默认保留 7 天,offset 是只增不减的累计值,没有消费者消费它也不会自己归零。只要推送成功过一次,offset 就不可能还是 0。所以:

offset 全 0 = 对端从未推送成功。 这是 Kafka 侧拿到的硬证据,比双方嘴上扯皮可靠得多。

给"对端没推"下结论前,排除两个边界

  1. 集群数据卷是否被重建/清空过? 如果 Kafka 容器的数据卷没挂载或重建时被清空,topic 和消息会重新初始化成空的,offset 归零就是假象。确认 compose 里挂了命名卷(如 kafka-data:/var/lib/kafka/data),即可排除。
  2. 对方对接的确实是这个集群 + 这些 topic 名吗? 他可能推到了别的 Kafka、或 topic 名跟你这边不一致。让对方贴出他们的 bootstrap.servers 和 topic 名核对。

最有说服力的当场验证

与其来回确认,不如让对端现场推一条测试消息,本端实时监听:

javascript 复制代码
1# 本端开一个实时监听窗口2docker exec kafka kafka-console-consumer \3  --bootstrap-server localhost:9092 --topic topic_a --from-beginning

对端推一条后:

  • 监听窗口打印出内容 → 对端推送链路通,之前是没推/没推对;
  • 毫无输出、再查 offset 还是 0 → 数据根本没送到这个集群,结论坐实。

可以直接发给对端的话术:

我们 Kafka 侧确认:topic_a/topic_b/topic_c 等 topic 目前消息数全部为 0。请核对你们的 bootstrap 地址和 topic 名称,并抽一条 topic 现场推送一条测试数据,我们这边实时验证。


总结:Docker 部署 Kafka 的自查清单

把这三次的坑收敛成一份清单,下次部署+联调直接照着查:

| # | 检查项 | 命令 | 要点 |
|---|-------------------|----------------------------------------------------------------|------------------------------------------------------|------------------|
| 1 | 端口监听 | `ss -lntp | grep <映射端口>` | docker-proxy 在监听 |
| 2 | 防火墙 | firewall-cmd --state && firewall-cmd --list-ports | 映射端口放行(云上还要看安全组) |
| 3 | advertised 地址 | docker exec kafka bash -c 'echo $KAFKA_ADVERTISED_LISTENERS' | 外部 listener 必须写客户端可达的 IP+映射端口,绝不能是 localhost/容器名 |
| 4 | 服务健康 | docker ps | 结合依赖服务反向判断,区分"真坏"和"健康检查误报" |
| 5 | 数据是否到达 | kafka-run-class kafka.tools.GetOffsetShell --broker-list ... | offset 全 0 = 从未收到;实时验证用 kafka-console-consumer |

三个核心认知,一次排障全部到位:

  1. advertised.listeners 是客户端视角------Docker 部署下最容易写错,也最隐蔽;
  2. unhealthy 不一定是服务坏了------先看健康检查命令本身,再看依赖它的服务;
  3. offset 是判定数据是否到达的最硬证据------比日志、比口头确认都可靠。
相关推荐
程序员cxuan2 小时前
GPT - 6 Astra 的使用焚诀
人工智能·后端·程序员
南雨北斗2 小时前
Tp6 + Nginx 配置文件上传目录为public同级目录(宝塔面板)
后端
techdashen2 小时前
Go Map 详解:键值对实际上是如何存储的
开发语言·后端·golang
云上小朱2 小时前
部署安全的pg数据库,搭配pgadmin实现web界面管理
后端
行百里er2 小时前
HandlerInterceptor 和 WebFilter:两套 HTTP 请求拦截
后端·架构·监控
爱勇宝3 小时前
初创公司的“自己人”,到底能当多久?
前端·后端·程序员
掘金者阿豪3 小时前
Mac mini Intel 报错 DNS_PROBE_FINISHED_BAD_CONFIG 完整排障指南
后端
大模型丫丫3 小时前
如何提升单体 Spring Boot 应用的并发数?
java·spring boot·后端
Terra.K3 小时前
ThreadLocal解决一个线程里面跨层传递问题
java·开发语言·后端