lolspider / notes
Clipping · 6 分钟

CSGHub cStor 存储故障排查报告

📝 摘要

本文记录了 CSGHub 集群一次由 OpenEBS cStor 3.6.0 存储故障引发的 Redis Pod CrashLoopBackOff 排查全过程,故障波及四个 CStorPoolInstance 全部进入只读状态。排查揭示三层根因:池容量达 82% 触发 cStor 硬编码的 85% PoolReadOnlyThreshold 只读保护阈值(不可配置、不可自动解除);清理容量后卷副本 rebuild 陷入状态机死锁(CVR 停留 Rebuilding、finalizer 卡住、zfs destroy 报 dataset is busy),怀疑为 cStor 3.6.0 已知健壮性缺陷;iSCSI 连接中断导致上层 ext4 触发保护性只读(经 e2fsck -n 验证无实际数据损坏)。

核心概念: cStor 存储引擎的容量只读阈值与 rebuild 状态机死锁是本次故障的核心——前者硬编码不可配置,后者缺乏根治手段,最终只能靠降副本数(RF=1)规避。

关键技术栈 / 关键要点:

  1. PoolReadOnlyThreshold(85% 硬编码) — cStor 3.6.0 池容量达阈值后强制只读,需人工降容量并重启 pool pod 解除,建议建立 75%/80% 预警线主动监控 -- 20260731 updates: 85% 并非硬编码,可更改 cspi 配置 roThresholdLimit
  2. 容量虚高问题 — cStor istgt 不支持 discard/UNMAP,fstrim 报 Not supported,上层删除文件后 zvol USED 不回落,容量长期只涨不跌
  3. rebuild 状态机死锁 — CVR 卡在 Rebuilding,pool-mgmt 报 listsnap err 11,target 反复打印 Rebuild is already in progress;cstor-pool 主进程 livenesstimestamp 持续刷新使底层数据集句柄无法释放,删除/清 finalizer/重启均无法打破循环
  4. 用户态 uzfs 架构 — cStor 3.6.0 使用用户态 uzfs 而非内核 zvol,宿主机无 /dev/zvol 路径,故非内核层持有句柄
  5. iSCSI 中断触发 ext4 保护性只读 — session recovery 超时后 ext4 errors=remount-ro 默认行为触发,e2fsck -n 验证文件系统 clean,属 I/O 中断的保护性反应而非永久损坏
  6. 最终处置 — 放弃死锁副本(nfc2)、将 desired replication factor 降至 1 恢复卷读写,wcjl 副本数据完整(70.0G),但单副本运行存在单点故障风险

关键结论 / 实践建议: cStor 85% 只读阈值不可配置且不自动恢复,必须主动监控池容量(建议 75%/80% 预警);rebuild 死锁属结构性缺陷无根治手段,降副本数是可操作的应急规避方案,但需尽快补做备份并评估恢复多副本;discard/UNMAP 缺失导致容量虚高需纳入容量预留系数;cStor/Jiva 已被社区普遍视为 legacy/maintenance,新特性重心转向 Mayastor,建议纳入长期存储选型评估。


CSGHub cStor 存储故障排查报告

故障时间: 2026-07-23 ~ 2026-07-24 排查人: Chein 环境: k8s + OpenEBS cStor 3.6.0 + NFS-backed storage


一、故障现象

csghub-redis-0 Pod 反复 CrashLoopBackOff,重启次数持续增长(176+ 次),对应 PVC 挂载失败。

排查过程中发现问题范围扩大:四个 CStorPoolInstance(cstor-disk-pool-7d4d、c6bt、nfc2、wcjl,分别位于 minio-opencsg、db-opencsg、master-opencsg、gitaly-opencsg 四个节点)全部处于 READONLY: true 状态,波及该集群上几乎所有 cStor 卷。


二、根因分析

2.1 第一层根因:池容量触发只读保护阈值

  • zpool list 显示池容量占用 82% CAP(未真正写满,zpool status 显示 ONLINE 无 READ/WRITE/CKSUM 错误)
  • 通过 pool-mgmt 日志确认触发了 cStor 内置的 PoolReadOnlyThreshold 保护机制:
    plaintext
    Warning 'PoolReadOnlyThreshold' Pool storage limit reached to read only threshold limit.
    
  • 该阈值为 85%,硬编码在 cStor 3.6.0 版本的 pool-mgmt 逻辑中,不支持配置修改,超过阈值后不会自动解除,需人工降低使用率并重启 pool pod 触发重新评估。
  • 触发后,池上所有卷的副本因写入失败而被标记 Offline,进而因副本数低于 consistency factor(quorum)导致对应 CV(CStorVolume)整体 Offline,redis 因此无法挂载。

2.2 容量分析方法

  • zfs list -o name,used -Hp | sort -k2 -nr 按用量排序,定位大户
  • 确认部分容量为「虚高」:ext4/上层文件系统删除文件后,若链路不支持 discard/UNMAP(本次确认 cStor istgt 不支持,fstrim 报 Not supported),底层 zvol 的 USED 不会自动回落,长期只涨不跌
  • 清理了若干确认无人使用的 PVC,四个池容量降至阈值以下,READONLY 恢复为 false

2.3 第二层根因:副本 rebuild 状态机死锁(本次故障最棘手部分)

容量问题解决后,卷 pvc-00b012f6-9775-44fe-9b2d-ee128c249d18(对应上层 nfs-pvc-64ef81aa-...,200Gi)的 CV 状态卡在 Degraded,其中一个副本(nfc2)反复出现以下现象:

  • CVR 状态长期停留在 Rebuilding,zfs list 显示的 USED 数值持续不变(数小时无变化)
  • pool-mgmt 日志反复报错:failed listsnap command ... err 11(该池上所有卷的 CVR 均受影响,非单一卷问题)
  • target(cstor-istgt)日志反复打印 Rebuild is already in progress,Mesh rebuild will be attempted,rebuild 从未真正完成或失败退出,陷入无限循环
  • 尝试 delete cvr 触发重建,遭遇 DeletionFailed;追查发现是 finalizer 卡住,底层原因是 zfs destroy 报 dataset is busy
  • 排查确认非 ZFS 内核层持有(宿主机无 /dev/zvol 路径,cStor 3.6.0 使用用户态 uzfs,不走内核 zvol 设备节点),而是 cstor-pool 主进程内部的 replica manager 线程持续将该副本标记为"活跃"(io.openebs:livenesstimestamp 持续刷新),导致无论是否删除 K8s 层 CVR 对象,底层数据集句柄都无法释放
  • 多次尝试(清 finalizer、重启 pool pod、重建 CVR)均未能打破循环,问题具有结构性、可复现的特征,怀疑是 cStor 3.6.0 已知的 rebuild 状态机健壮性缺陷

2.4 第三层影响:iSCSI 连接异常导致上层文件系统临时保护性只读

在多次重启 target/pool pod 期间,iSCSI session 反复中断:

plaintext
connection27:0: detected conn error (1020)
session recovery timed out after 120 secs
sd 8:0:0:0: rejecting I/O to offline device

上层 ext4 文件系统检测到 I/O 中断后触发保护性只读(errors=remount-ro 默认行为),dmesg 报 EXT4-fs error ... reading directory lblock 0(含根目录 inode #2)。

经 e2fsck -n 只读检查验证:文件系统本身是 clean 的,未发生实际数据损坏,只读报错是 I/O 中断触发的保护性反应,非永久性损坏。


三、最终处理与当前状态

  1. 由于副本 4(nfc2)rebuild 死锁反复尝试解不开,最终放弃该副本、接受降级运行
  2. 该卷所有 CVR 一度被清理到仅剩 1 个(wcjl),target 侧确认 desired replication factor 已生效降为 1,consistency factor 随之降为 1
  3. target 日志确认 volume ... is ready for IOs now,卷恢复读写能力,实测 touch 写入成功
  4. 通过 zfs list 确认 wcjl 副本数据完整(70.0G,与故障前一致)
  5. 当前该卷处于单副本(RF=1)运行状态,无冗余,存在单点故障风险

四、遗留风险与后续建议

风险/待办 说明 优先级
该卷单副本运行,无冗余 wcjl 所在节点/磁盘任何故障都会导致数据不可用 高,需尽快处理
未完成数据备份 曾尝试 zfs send 导出快照,过程中中止,未确认是否有完整备份产出 高,建议优先补做
需评估恢复多副本 待业务低峰期,通过 CVC 配置重新扩充副本数,从现有健康副本发起全新 rebuild 中
cStor 85% 只读阈值不可配置、不可自动解除 需建立主动监控告警(如 75%/80% 预警线),避免被动触发 中
discard/UNMAP 不支持 cStor istgt 默认关闭 Unmap,导致容量长期"虚高",无法通过 fstrim 回收,社区已有 OEP(#4074)但目前仅针对 Mayastor 引擎、仍处设计阶段 中,长期规划需纳入容量预留系数
cStor 引擎生态定位 cStor/Jiva 已被社区评测普遍视为"legacy/maintenance",新特性重心转向 Mayastor;官方文档仍将其列为正式支持引擎 低,建议纳入长期存储选型评估
rebuild 状态机健壮性缺陷 本次核心难点,多次操作(删除、清 finalizer、destroy、重启)均未能可靠打破死锁循环,缺乏根治手段,仅能靠"降副本数、接受数据丢失风险"规避 建议记录归档,作为后续同类故障的排查参考

五、关键排查命令速查(供后续参考)

bash
# 查看 CV / CVR 状态
kubectl -n openebs get cv
kubectl -n openebs get cvr -l openebs.io/persistent-volume=<pv-name>

# 查看池容量与只读状态
kubectl -n openebs get cspi -o custom-columns=NAME:.metadata.name,READONLY:.status.readOnly,FREE:.status.free,CAPACITY:.status.capacity

# 按用量排序 zfs 数据集
kubectl -n openebs exec -it <pool-pod> -c cstor-pool-mgmt -- zfs list -o name,used -Hp | sort -k2 -nr

# 查看只读阈值触发历史
kubectl -n openebs logs <pool-pod> -c cstor-pool-mgmt --tail=200 | grep -i -E "readonly|threshold|capacity"

# 查看 target 侧 quorum / healthy count 判断
kubectl -n openebs logs <target-pod> -c cstor-istgt --tail=50 | grep -i "healthy count"

# 强制清理卡住的 CVR(谨慎操作)
kubectl -n openebs patch cvr <cvr-name> -p '{"metadata":{"finalizers":null}}' --type=merge

# 基于健康副本导出备份
kubectl -n openebs exec -it <pool-pod> -c cstor-pool-mgmt -- zfs snapshot <pool>/<pvc-uuid>@backup
kubectl -n openebs exec -i <pool-pod> -c cstor-pool-mgmt -- \
  sh -c "zfs send <pool>/<pvc-uuid>@backup | gzip" > ./backup.zfs.gz