Kafka集群ISR收缩告警误报排查
记录时间:2026-08-03
环境:信创 ARM64 / RKE1 / Kafka 4.0.0 KRaft 3 节点(controller-0/1/2)/ namespace=tools / 存储 NFS(storageClass=nfs-dyck)
一、问题现象
生产 dyck Kafka 集群触发 Prometheus 告警 KafkaISR持续收缩,告警规则如下:
expr: kafka_server_replicamanager_total_isrshrinkspersec_oneminuterate > 0
for: 5m
告警来源 pod dyck-kafka-cluster-controller-2(节点 xc17-11)。告警触发后长时间无法自动消除,卡在 firing 状态,即便集群早已恢复正常。
排查时三个 broker 的指标值:
controller-2 (10.247.16.48): 3.77e-111 ← 触发告警的节点
controller-0 (10.247.227.143): 0
controller-1 (10.247.122.105): 0
二、排查过程
2.1 理解指标含义
ISRShrinksPerSec 属于 Kafka ReplicaManager 的一个 Meter,记录副本被移出 ISR(同步副本集合)的速率。ISR 收缩本身是 Kafka 的自愈机制——follower 跟不上 leader 时被踢出 ISR,追上后再加回来(ISR Expand)。Shrink 和 Expand 是成对动作。
2.2 查 Kafka 日志,定位收缩事件
kubectl logs -n tools dyck-kafka-cluster-controller-2 -c kafka --tail=300 \
| grep -iE "shrinking|expanding|under replicated|replica|timeout"
关键日志(时间戳为北京时间):
[17:39:49] Disconnecting from node 2 due to request timeout.
[17:39:49] Cancelled in-flight BROKER_HEARTBEAT ... request timeout: 4500ms
[17:40:03] Disconnecting from node 2 due to socket connection setup timeout. timeout=11224ms
[17:40:15] Did not receive fetch request from the majority of the voters within 3000ms.
[17:40:17] leaderEndpoints=...controller-0...svc.cluster.local/<unresolved>:9093
[17:40:20] GroupCoordinator ... timed out after 5000ms
[17:40:29] Shrinking ISR from 2,0 to 2. Out of sync replicas: (brokerId:0, lastCaughtUpTimeMs:1785749983328)
[17:40:29] Shrinking ISR from 2,1,0 to 2,1 ...(批量收缩 __consumer_offsets-49 等)
把 lastCaughtUpTimeMs:1785749983328 换算成可读时间 = 北京时间 17:39:43。所有被踢的分区,broker 0 的最后同步时间都停在这个点,说明 broker 0 是整台失联,不是单个分区抖动。
2.3 排查 broker 0 是否重启(试错)
我最初怀疑 controller-0(broker 0)重启或 OOM:
kubectl get pod -n tools -l app.kubernetes.io/instance=dyck-kafka-cluster -o wide
controller-0 3/3 Running 2 (110d ago) 110d 10.247.227.143 xc17-8
controller-1 3/3 Running 2 (110d ago) 110d 10.247.122.105 xc17-10
controller-2 3/3 Running 2 (110d ago) 110d 10.247.16.48 xc17-11
RESTARTS=2 都是 110 天前,与本次无关;controller-0 最近 4 小时无任何 ERROR 日志,也没有 previous container。排除 broker 进程重启。
试错:「broker 0 重启」的假设不成立。进程一直活着,但所有分区同一毫秒停止 catch up,指向节点级隔离,不是进程问题。
2.4 查节点级证据,权限受阻
想确认节点 xc17-8 是否 NotReady 或网络抖动:
kubectl describe node xc17-8
# → Forbidden: User "swj" cannot get resource "nodes" at cluster scope
kubectl get events -A
# → Forbidden: cannot list events at cluster scope
运维账号 swj 只有命名空间级(Pod/Service/logs)读权限,查不了 nodes 和集群级 events。节点级证据(NodeNotReady 事件、dmesg、CNI 日志)需 cluster-admin 账号才能取。
2.5 通过监控补位:发现负载飙升
虽然 kubectl 查 node/events 受权限限制,但 Prometheus 监控补上了关键证据:故障时段(17:39 前后)节点 xc17-8 的 load average 突然飙升到 150+。这个数据把根因从「网络瞬断」修正为「负载飙升导致节点卡死」——网络层超时只是 CPU 过载的连锁反应(详见三、根因分析)。
2.6 确认偶发首次 + 识别 EWMA 残值
用户确认该告警偶发一次、历史首次。排查期间多次查指标,发现一个关键现象:
| 第几次查 | controller-2 的值 | 指数 |
|---|---|---|
| 第 1 次 | 3.77e-111 | -111 |
| 第 2 次 | 1.26e-114 | -114 |
| 第 3 次 | 5.75e-119 | -119 |
指数持续向负方向走,值在持续变小趋零。第三次差点误读:看到 5.75e-119,尾数 5.75 看着变大,但指数从 -114 变 -119(小了 10 万倍),整体反而缩小约 2 万倍。
三、根因分析
3.1 直接诱因:节点 xc17-8 网络瞬断
证据链:
- broker 0 进程健康(无重启、无 GC、无错误)→ 排除进程
- 所有分区同一毫秒(17:39:43)停止 catch up → 整机隔离
- CoreDNS
<unresolved>+ socket 建立即超时 11s → 网络层断连 - broker 1(xc17-10)未受影响(ISR
2,1,0→2,1保留 broker 1)→ 非全局故障,xc17-8 单边问题
结论:节点 xc17-8 在 17:39:43 出现一次突发短事件(持续约 1-2 分钟),controller-0 的 Kafka 关键线程(fetch/heartbeat/网络处理)被瞬时阻塞,被时任 leader(controller-2)按 replica.lag.time.max.ms=40000 规则批量踢出 ISR;事件分钟后恢复,ISR 自动 re-expand。
监控数据汇总(事后从 Prometheus 调取):
| 指标(17:40 故障时刻) | 数值 |
|---|---|
| 1 分钟负载(load1) | 瞬时尖峰 120.41 |
| 15 分钟负载(load15) | 16.66(平滑值,尖峰被长窗口拉平) |
| 用户使用率(user) | 6.06% |
| 系统使用率(system) | 0.85% |
| 磁盘 IO(iowait) | 4.90% |
| CPU 核数 | 96 |
根因判断的曲折(排查过程如实记录):日志先指向「网络瞬断」→ 看到 load 飙升(实为 load1 尖峰 120)一度改成「负载飙升卡死」→ 补充 96 核后又推测「NFS IO wait 堆积」。但 iowait 才 5%、user 才 6%,磁盘 IO 和 CPU 都不是主导;load1 尖峰 120 在 CPU/磁盘都不忙的情况下,更像 D 状态进程短暂堆积(等待网络/锁/内存回收等非磁盘资源)。
收敛结论(5 节点同时异常 → 集群级共享资源事件):17:40 发生了一次集群级事件——5 个 worker 节点同时出现 IO+CPU 异常拉升,xc17-8 的 Kafka 因对延迟最敏感(replica fetch 40s 阈值 + GroupCoordinator 写 5s 超时连锁)率先触发 ISR shrink,其他节点业务容忍度高未告警。几分钟后恢复,ISR 自动 re-expand。
根因首选:NFS 服务端 / 共享存储后端瞬时抖动。5 节点同时异常必然源于共享资源;多节点共享 NFS + Kafka 数据在 NFS + GroupCoordinator 写超时 5s(NFS 慢铁证),NFS 服务端最符合。这套解释第一次串通所有现象:
– 5 节点同时 IO+CPU 异常 = 共享 NFS 服务端抖动
– 只有 xc17-8 告警 = Kafka 延迟敏感度最高,其他业务容忍
– load1 飙到 120 = 进程等 NFS 进 D 状态堆积
– iowait 才 5% = NFS 等待部分不计入本地 iowait
– node_nfs_requests 平稳 = 服务端慢时客户端请求卡在等响应、发不出去,速率反而不升(平稳是服务端慢的症状,不是排除依据)
– TCP 重传平稳 = 不是网络丢包,是服务端处理慢待确认:NFS 服务端 17:40 的 CPU/IO/负载、NFS 服务端日志、存储后端(Ceph/本地阵列/分布式)是否抖动。
跨组织边界(最终结论):分布式存储由运营商托管,无权查看服务端。
【2026-08-04 厂家反馈】运营商反馈 NFS 响应时间无异常(17:39-17:50 时段),排除了「NFS 服务端处理慢」。客户端证据(5 节点同时异常 + 写超时 5s)与服务端反馈矛盾,缺口在「客户端到服务端的存储网络」或客户端共性,但该路径运营商托管无权查。根因搁置,待下次复现或运营商提供存储网络层数据。
客户端侧防御现状:
– ✅ ① 告警规则修正已 apply 落地(2026-08-04,>0.5阈值生效,旧告警自动停止)
– ❌ ② Kafka 日志刷新延迟告警:实测kafka_log_*整组指标为空(JMX Exporter 未采集kafka.log:*),flush 延迟不可用,搁置
3.2 告警误报根因:EWMA 永不归零 + >0 阈值
ISRShrinksPerSec 是 Yammer Metrics 的 Meter 类型,_oneminuterate 是其 OneMinuteRate,一个指数加权移动平均(EWMA),时间常数约 1 分钟。
EWMA 事件后指数衰减,浮点数下永不精确归零。
>0 阈值 + EWMA 不归零 = 告警一旦触发就持续 firing 数小时甚至数天
17:39 那次收缩事件后 4-5 小时,EWMA 残值仍观测到 1e-114 量级,满足 >0,叠加 for: 5m,告警一直卡在 firing 无法自动恢复,会一直响到人工干预。
三节点残值差异也印证这一点:ISR Shrink 由分区 leader 执行,controller-2 当时是多数分区 leader(执行了批量踢出),故残留 EWMA 尾巴;controller-0 当时失联不执行 shrink、controller-1 leader 分区少,EWMA 早已归零。
四、解决方案
4.1 告警规则修正(保留 + 改阈值,非删除)
试错:最初我推荐直接删除该规则,理由是「UnderReplicated 三道防线已覆盖」。用户指出 ISR Shrink 作为过程指标有「早期预警 + 捕捉慢性抖动」的独特价值,三道防线(都要求持续状态)不完全覆盖。采纳,改为保留并修正。
修正后的规则:
- alert: KafkaISR频繁收缩
# _count 指标未暴露,只能用 OneMinuteRate(EWMA)。EWMA 事件后指数衰减但浮点永不归零,
# 故禁用 >0 阈值(会致告警永久 firing 卡死)。取 0.5/s + for 10m:
# - 过滤 EWMA 衰减尾巴(如 1e-114 残值)与瞬时抖动(几分钟内衰减到 0.5 以下,撑不满 10m)
# - 只在"持续显著收缩"(约 30+ 次/分钟、持续 10 分钟)时告警
expr: kafka_server_replicamanager_total_isrshrinkspersec_oneminuterate > 0.5
for: 10m
改动要点:
| 参数 | 原值 | 新值 | 作用 |
|---|---|---|---|
| 阈值 | > 0 |
> 0.5 |
修复 EWMA 永不归零导致的告警卡死 |
| for | 5m | 10m | 过滤单次批量收缩事件的衰减尾巴 |
| alert 名 | KafkaISR持续收缩 | KafkaISR频繁收缩 | 更贴合「rate 超阈值」的语义 |
4.2 ISR Shrink 告警的独特价值(为什么保留)
三道防线只覆盖「副本持续不足」的状态:
| 规则 | 覆盖场景 |
|---|---|
| UnderReplicatedPartitions > 0 (for 3m) | 副本持续不足 |
| UnderMinIsrPartitionCount > 0 (for 1m) | 副本数跌破 min.insync.replicas |
| OfflinePartitionsCount > 0 (for 1m) | 分区无 leader |
ISR 频繁收缩补两个盲区:
- 早期预警:Shrink 发生时 UnderReplicated 才刚 >0,还没到 3m 阈值
- 慢性抖动捕捉:broker 反复让副本进出 ISR(每次自愈快,UnderReplicated < 3m 不告警),是节点不健康信号
五、验证
已 apply 落地(2026-08-04):用户用运维账号执行
kubectl apply -f test.yaml(内容=仓库 kafka-rules.yaml),prometheusrule/kafka-rules configured生效。Warning(missing last-applied-configuration annotation)无害——资源最初非 apply 创建,本次自动补注解,不影响生效。当前 firing 的旧告警(>0)因阈值变0.5自动停止。
apply 命令(参考,正式维护用仓库原文件而非 test.yaml):
kubectl apply -f kafka-rules.yaml
apply 后验证:
# 确认规则名已更新
kubectl get prometheusrule kafka-rules -n monitoring \
-o jsonpath='{.spec.groups[?(@.name=="kafka.replication")].rules[*].alert}'
# 预期:Kafka存在未同步副本 KafkaISR频繁收缩
# 当前那条 firing 的旧告警(>0)会因阈值变 0.5 立即停止
阈值校准参考(apply 前或后):
# 查 17:39 事件 OneMinuteRate 的峰值,作为阈值参考
max_over_time(kafka_server_replicamanager_total_isrshrinkspersec_oneminuterate{systemName="dyck",pod="dyck-kafka-cluster-controller-2"}[6h])
峰值远大于 0.5 则阈值合理;接近 0.5 则下调到 0.1。
六、注意事项
6.1 科学计数法误读
5.75e-119 不是「5 点多」,是 5.75 × 10⁻¹¹⁹。看这种 EWMA 指标,只看 e 后面的指数:
- 指数
e0或正数 → 真实速率,值得警惕 - 指数
e-1~e-2→ 接近阈值,留意 - 指数
e-100级别 → EWMA 残值,等于 0,忽略
6.2 swj 运维账号权限边界
swj 账号只有命名空间级(Pod/Service/logs)读权限,kubectl describe node 和集群级 kubectl get events -A 均 Forbidden。排查节点级故障(NodeNotReady、dmesg、CNI 日志)需 cluster-admin 或更高权限账号。
6.3 _count counter 未暴露
JMX Exporter 只暴露了 _oneminuterate(EWMA),没有暴露 _count(累计计数 counter)。否则可用更精确的净收缩判据:
# 理想方案(_count 存在时):10 分钟净收缩超阈值才告警
increase(..._isrshrinkspersec_count[10m])
- on(systemName, instance) increase(..._isrexpandspersec_count[10m]) > 5
6.4 NFS 存储风险
本次 GroupCoordinator 写 __consumer_offsets-49 超时 5s,印证 NFS 写延迟。Kafka 用 NFS(storageClass nfs-dyck)是已知风险,长期治本应迁 Longhorn 本地块存储,涉及数据迁移,需单独规划。
6.5 排查「网络瞬断」的正确指标
排查网络层问题不要看 TCP 套接字数量(Netdata Sockstat 的 Allocated/In-Use/TIME_WAIT),连接数平稳不能证明网络质量正常——网络瞬断时连接往往还在,但包在丢、在重传。要看的是:
# TCP 重传段数(网络丢包/不稳定的直接信号)
rate(node_netstat_Tcp_RetransSegs{instance=~".*xc17-8.*"}[5m])
# 网卡错误/丢包
increase(node_network_receive_errs_total{instance=~".*xc17-8.*"}[5m])
另外注意监控图的时间跨度:排查瞬时事件(持续 1-2 分钟)要用 1-2 小时内的窗口,跨 6 小时以上的图会把尖峰平滑掉,看不到真相。
6.6 磁盘负载指标:iowait / node_disk / node_nfs 的区别(按存储类型选)
判断磁盘/IO 是否瓶颈,要分清三个指标,且必须区分本地盘和 NFS 挂载:
| 指标 | 反映什么 | 适用 |
|---|---|---|
iowait |
CPU 干等 IO 的时间占比(CPU 视角) | 不能单独判断磁盘负载,iowait 低 ≠ 磁盘不忙 |
node_disk_io_time_seconds_total(util) |
本地块设备繁忙度 | 只反映本地盘(系统盘/本地数据盘),不含 NFS |
node_nfs_requests_total |
NFS 客户端请求量 | 反映 NFS 挂载的 IO 活跃度 |
本项目 Kafka 数据盘是 NFS(storageClass=nfs-dyck),所以:
- 查 Kafka 数据盘负载 → 用
node_nfs_*,不是node_disk_*(后者只看本地系统盘) node_disk_io_time_seconds_total对本场景只能看系统盘,不能判断 Kafka 数据 IO
坑(本次实测):曾想用
node_disk_io_time_seconds_total查 Kafka 数据盘是否打满,被指出无效——因为数据在 NFS,本地盘指标看不到。指标选择必须匹配存储类型,NFS 存储要看node_nfs_*。
七、参考资料
- Kafka Metrics:
kafka.server:type=ReplicaManager,name=ISRShrinksPerSec(Yammer Meter 类型) - JMX Exporter:OneMinuteRate = EWMA,时间常数约 1 分钟,事件后浮点永不归零
- 相关指标:
UnderReplicatedPartitions/OfflinePartitionsCount/UnderMinIsrPartitionCount/ActiveControllerCount