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)规避。
关键技术栈 / 关键要点:
- PoolReadOnlyThreshold(85% 硬编码) — cStor 3.6.0 池容量达阈值后强制只读,需人工降容量并重启 pool pod 解除,建议建立 75%/80% 预警线主动监控 -- 20260731 updates: 85% 并非硬编码,可更改 cspi 配置
roThresholdLimit - 容量虚高问题 — cStor istgt 不支持 discard/UNMAP,
fstrim报Not supported,上层删除文件后 zvol USED 不回落,容量长期只涨不跌 - rebuild 状态机死锁 — CVR 卡在 Rebuilding,pool-mgmt 报
listsnap err 11,target 反复打印Rebuild is already in progress;cstor-pool 主进程livenesstimestamp持续刷新使底层数据集句柄无法释放,删除/清 finalizer/重启均无法打破循环 - 用户态 uzfs 架构 — cStor 3.6.0 使用用户态 uzfs 而非内核 zvol,宿主机无
/dev/zvol路径,故非内核层持有句柄 - iSCSI 中断触发 ext4 保护性只读 — session recovery 超时后 ext4
errors=remount-ro默认行为触发,e2fsck -n验证文件系统 clean,属 I/O 中断的保护性反应而非永久损坏 - 最终处置 — 放弃死锁副本(
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保护机制:plaintextWarning '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 反复中断:
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 中断触发的保护性反应,非永久性损坏。
三、最终处理与当前状态
- 由于副本 4(
nfc2)rebuild 死锁反复尝试解不开,最终放弃该副本、接受降级运行 - 该卷所有 CVR 一度被清理到仅剩 1 个(
wcjl),target 侧确认 desired replication factor 已生效降为 1,consistency factor 随之降为 1 - target 日志确认
volume ... is ready for IOs now,卷恢复读写能力,实测touch写入成功 - 通过
zfs list确认wcjl副本数据完整(70.0G,与故障前一致) - 当前该卷处于单副本(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、重启)均未能可靠打破死锁循环,缺乏根治手段,仅能靠"降副本数、接受数据丢失风险"规避 | 建议记录归档,作为后续同类故障的排查参考 |
五、关键排查命令速查(供后续参考)
# 查看 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