故障告警
- 告警发生时间:2026-10-09 15:11:00
- 告警标题: ceph 文件系统降级
故障现象
- ceph mds 故障,进程自动重启
- mds 自动恢复
- 故障时间约 2 分钟
分析
- [用户操作 readdir 导致 (通过日志分析 发现大量用户 readdir 行为发生)
- [对应 用户信息 (参考日志,定位用户 ip 地址)
- 主机 gd18-k8s-noah-ai-llm-gpu-prod002-node-s0109
故障定位方法
- 从日志检测用户操作,找到操作最多,最可以的操作
- 根据操作定位到主机
- 登录到主机,利用 epbf 找到执行对应操作类型的 PID
- 根据 PID 获取到 Pod 信息 进程信息
故障根因
- 进程启动命令时候直接用 CephFS 根目录作为 logdir
排查方法
重点 !! 必须要当前用户还在操作才可以发现 !!!
参考日志,先获取 readdir 用户 id caller_uid=0, caller_gid=0{})
2026-10-09T15:11:16.711+0800 7f19354e4700 1 -- [v2:10.42.134.21:6929/3265411376,v1:10.42.134.21:6930/3265411376]
<== client.222085723 v1:10.42.144.23:0/3207384292 1485045558
==== client_request(client.222085723:26024257309 readdir #0x10003b67ef9 2026-10-09T15:11:16.708921+0800
caller_uid=0, caller_gid=0{})
v2 ==== 166+0+0 (unknown 3475745249 0 0) 0x55f3aead78c0 con 0x55f261402400 |
需要登录到 10.42.134.21 节点上进行后续处理
在节点中进行排查
执行下面脚本
输出格式为 用户ID - 进程PID - 进程名称 - 重复执行次数
ceph_readdir 是 linux 内核中的调用函数, 后续有说明
yum install -y bpftrace
bpftrace -e '
kprobe:ceph_readdir {
@[uid, pid, comm] = count();
}
interval:s:10 { print(@); clear(@); }'
Attaching 2 probes...
@[1000, 1326516, jupyterhub-sing]: 2
@[1000, 3516740, /opt/conda/bin/]: 4
@[1000, 976884, node]: 10
@[0, 2920201, tensorboard]: 113682
@[1000, 1326516, jupyterhub-sing]: 2
@[0, 2920201, tensorboard]: 87347
@[1000, 1326516, jupyterhub-sing]: 2
@[0, 2920201, tensorboard]: 140600
@[1000, 1326516, jupyterhub-sing]: 2
@[0, 2920201, tensorboard]: 119331 <--- uid = 0 pid = 2920201
@[1000, 3516743, /opt/conda/bin/]: 2
@[1000, 3516740, /opt/conda/bin/]: 2
@[1000, 1326516, jupyterhub-sing]: 2
@[1000, 3516742, /opt/conda/bin/]: 2
PID 为 2920201
根据 PID 获取 POD 信息
# cat /proc/2920201/cgroup
13:rdma:/
12:hugetlb:/kubepods/besteffort/podf99049b4-1ddb-4af3-bbd2-1405d918b961/5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
11:cpu,cpuacct:/kubepods/besteffort/podf99049b4-1ddb-4af3-bbd2-1405d918b961/5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
10:memory:/kubepods/besteffort/podf99049b4-1ddb-4af3-bbd2-1405d918b961/5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
9:ioasids:/
8:cpuset:/kubepods/besteffort/podf99049b4-1ddb-4af3-bbd2-1405d918b961/5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
7:perf_event:/kubepods/besteffort/podf99049b4-1ddb-4af3-bbd2-1405d918b961/5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
6:net_cls,net_prio:/kubepods/besteffort/podf99049b4-1ddb-4af3-bbd2-1405d918b961/5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
5:blkio:/kubepods/besteffort/podf99049b4-1ddb-4af3-bbd2-1405d918b961/5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
4:devices:/kubepods/besteffort/podf99049b4-1ddb-4af3-bbd2-1405d918b961/5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
3:pids:/kubepods/besteffort/podf99049b4-1ddb-4af3-bbd2-1405d918b961/5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
2:freezer:/kubepods/besteffort/podf99049b4-1ddb-4af3-bbd2-1405d918b961/5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
1:name=systemd:/kubepods/besteffort/podf99049b4-1ddb-4af3-bbd2-1405d918b961/5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
POD 信息为 5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd
获取 POD name 方法
yum install -y crictl
# crictl inspect 5fd83966896c0aaaf8de0124217ef6062f462640d4bde50bf475fa6ba92ec7fd | \
> jq -r '.status.labels | "namespace=\(.["io.kubernetes.pod.namespace"]) pod=\(.["io.kubernetes.pod.name"])"'
WARN[0000] runtime connect using default endpoints: [unix:///var/run/dockershim.sock unix:///run/containerd/containerd.sock unix:///run/crio/crio.sock unix:///var/run/cri-dockerd.sock]. As the default settings are now deprecated, you should set the endpoint instead.
namespace=mlp pod=tf-board-default-5bdf4c5df5-sxzwg
获取 docker 信息
# docker ps | grep tf-board-default-5bdf4c5df5-sxzwg
5fd83966896c 4b333ee54d27 "/apps/sh/start-tfbo..." 9 days ago Up 9 days k8s_tf-board-default_tf-board-default-5bdf4c5df5-sxzwg_mlp_f99049b4-1ddb-4af3-bbd2-1405d918b961_20
b351b6be1c12 google_containers/pause:0.8.0 "/pause" 4 months ago Up 4 months k8s_POD_tf-board-default-5bdf4c5df5-sxzwg_mlp_f99049b4-1ddb-4af3-bbd2-1405d918b961_0
获取进程方法
# ps -fp 2920201
UID PID PPID C STIME TTY TIME CMD
root 2920201 2920149 56 Oct01 ? 5-07:11:00 /usr/local/bin/python /usr/local/bin/tensorboard --logdir /apps/dat/training/ --host 0.0.0.0 --port 185
获取 IP 方法
# nsenter -t 2920201 -n ip -4 addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
59: eth0@if58: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link-netnsid 0
inet 10.43.144.212/24 scope global eth0
valid_lft forever preferred_lft forever
信息汇总
- pod name namespace=mlp pod=tf-board-default-5bdf4c5df5-sxzwg
- pod ip 10.43.144.212
- 进程 /usr/local/bin/python /usr/local/bin/tensorboard --logdir /apps/dat/training/ --host 0.0.0.0 --port 185
- 进程会重复遍历日志目录导致该故障
解决方法
- logdir 不要使用 CephFS 文件系统或指定 CephFS 中某一个目录(不是最优选择) 不要使用 CephFS 根目录
CephFS MDS 实时监控用户操作方式
- 在 CephFS MDS 中可以执行命令, 可以实时检测用户操作的统计
- 需要利用 ceph daemon mds.xxx.asok perf dump 命令定期获取 mds 性能状态
- 对 ceph 性能状态进行比对就可以得到用户操作
参考表格中,代表了 perf dump 中常见的用户操作类型
| 操作 | 代码 |
|---|---|
| 创建文件(open O_CREAT/create/mknod 写文件) | mds_server.req_create_latency |
| 打开文件 | mds_server.req_open_latency |
| 删除文件 | mds_server.req_unlink_latency,异步清理看 mds_cache.strays_created、purge_queue.pq_executed |
| 修改文件属性(chmod/chown/truncate/touch mtime) | mds_server.req_setattr_latency |
| 设置/删除扩展属性 | mds_server.req_setxattr_latency / req_rmxattr_latency |
| 改名/移动(文件和目录共用) | mds_server.req_rename_latency |
| 硬链接 / 软链接 / 设备节点 | req_link_latency / req_symlink_latency / req_mknod_latency |
| 加锁 / 查锁 | req_setfilelock_latency / req_getfilelock_latency |
| 创建目录 | mds_server.req_mkdir_latency |
| 删除目录 | mds_server.req_rmdir_latency |
| 遍历目录(ls/find/du 的主体) | mds_server.req_readdir_latency |
| 路径解析(stat/open 未命中缓存时的逐级 lookup) | mds_server.req_lookup_latency |
| 查属性(stat 未命中 cap 时) | mds_server.req_getattr_latency |
| 目录改名 | 并入 req_rename_latency(无单独计数) |
| 大目录信号:目录分片分裂/合并 | mds.dir_split / mds.dir_merge(普通计数器,非 latency 结构) |
| mds.request / mds.reply_latency | 总请求速率 / 总回复延迟 |
| mds.traverse / traverse_hit | 元数据树遍历量 / 缓存命中率(命中率跌 = 冷路径扫描多) |
| mds_log.jlat | journal 写延迟(之前发现 ~1s 的就是它) |
| mds_server.cap_revoke_eviction | cap 强制回收次数(客户端不配合时升高) |
| mds.load_cent / mds_sessions.total_load | MDS 负载 |
-
利用下面命令就可以对用户操作进行监控
-
只针对某一种类型进行监控方法
ASOK=/var/run/ceph/vip-ceph-mds.(hostname -s ).asok prev=(ceph daemon ASOK perf dump | jq '.mds_server.req_setfilelock_latency.avgcount') while true; do sleep 10 cur=(ceph daemon ASOK perf dump | jq '.mds_server.req_setfilelock_latency.avgcount') echo "(date +%H:%M:%S) setfilelock_rate=(( (cur-prev)*6/10 ))/s" prev=cur
done -
函数方式的监控方法
ASOK=/var/run/ceph/vip-ceph-mds.$(hostname -s).asok
watch_rate() {
local c=1 i={2:-10} prev cur
prev=(ceph -c /etc/ceph/vip-ceph.conf daemon ASOK perf dump | jq ".c.avgcount // .c")
while true; do
sleep i cur=(ceph daemon ASOK perf dump | jq ".c.avgcount // .c") echo "(date +%H:%M:%S) c rate=(( (cur-prev)/i ))/s"
prev=$cur
done
} -
执行持续监控方法 watch_rate mds_server.req_create_latency 10
20:44:13 mds_server.req_create_latency rate=18/s
20:44:23 mds_server.req_create_latency rate=15/s
20:44:33 mds_server.req_create_latency rate=16/s
20:44:44 mds_server.req_create_latency rate=20/s
20:44:54 mds_server.req_create_latency rate=16/s -
执行通用监控方式
ASOK=/var/run/ceph/vip-ceph-mds.(hostname -s).asok INTERVAL=10 prev=(ceph -c /etc/ceph/vip-ceph.conf daemon ASOK perf dump | jq '.mds_server') while true; do sleep INTERVAL
cur=(ceph daemon ASOK perf dump | jq '.mds_server')
echo "===== (date +%H:%M:%S) MDS user-behavior rates (ops/s) =====" jq -rn --argjson p "prev" --argjson c "cur" --argjson i INTERVAL '
[ c | to_entries[] | select(.key | test("^req_.*_latency")) |
{ op: (.key | sub("^req_";"") | sub("_latency";"")), rate: (((.value.avgcount - (p[.key].avgcount // 0)) / i) * 10000 | round / 10000) } ] | sort_by(-.rate) | .[] | select(.rate > 0) | "\(.op)\t\(.rate)"' prev=cur
done===== 20:46:32 MDS user-behavior rates (ops/s) =====
readdir 147.2
lookup 26.3
setfilelock 13.5
create 9.6
rename 9.3
getattr 2.7
open 0.3
unlink 0.3
setattr 0.1
===== 20:46:42 MDS user-behavior rates (ops/s) =====
readdir 125.9
lookup 25.1
create 10
rename 9.6
setfilelock 2
open 0.7
setattr 0.4
getattr 0.2
lookupino 0.1
unlink 0.1
在 K8S 主机上执行日常排查
- 先找到操作量最大服务器]
- 查询当前服务器操作量最大的操作
k8s 下利用 EBPF 进行实时 CephFS 操作监控方法
kport 函数对应信息
| 文件操作 | 内核入口 | MDS OPS |
|---|---|---|
| 列目录 | ceph_readdir | readdir |
| 路径解析/查文件 | ceph_lookup | lookup |
| 打开 | ceph_open / ceph_atomic_open | open |
| 创建文件 | ceph_create | create |
| 建目录 | ceph_mkdir | mkdir |
| 删除文件 | ceph_unlink | unlink |
| 删除目录 | 当前版本不支持 | rmdir |
| 改名/移动 | ceph_rename | rename |
| 软链接 | ceph_symlink | symlink |
| 硬链接 | ceph_link | link |
| 改属性(chmod/chown/truncate) | ceph_setattr | setattr |
| 改扩展属性 | 当前版本不支持 | setxattr |
| 读数据 | ceph_read_iter | (走 caps/OSD) |
| 写数据 | ceph_write_iter | (走 caps/OSD) |
函数确认
-
不同版本可能函数名称不一样
-
利用下面方法确认函数是否可用
bpftrace -l 'kprobe:ceph_*'
kprobe:ceph_add_cap
kprobe:ceph_adjust_caps_max_min
kprobe:ceph_adjust_quota_realms_count
kprobe:ceph_aes_crypt
kprobe:ceph_aio_complete_req
。。。。。。。
这里太多了, 忽略, 每个内存版本都可能使用了不一样的函数,因此没必要详细输出
。。。。。。。
kprobe:ceph_zero_page_vector_range
kprobe:ceph_zero_partial_object
利用 ebpf 进行监控
-
利用该脚本就可以很方便地获取到当前正在执行疯狂操作的进程
bpftrace -e '
// 打开: ceph_open(struct inode *inode, struct file *file)
kprobe:ceph_open {
f = (struct file *)arg1; @open[str(f->f_path.dentry->d_name.name), uid, comm] = count();
}// 创建文件: ceph_create(struct inode *dir, struct dentry *dentry, umode_t mode)
kprobe:ceph_create {
d = (struct dentry *)arg1; printf("%s CREATE uid=%d comm=%s dir=%s/%s\n", strftime("%H:%M:%S", nsecs), uid, comm, str(d->d_parent->d_name.name), str($d->d_name.name));
}// 建目录: ceph_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
kprobe:ceph_mkdir {
d = (struct dentry *)arg1; printf("%s MKDIR uid=%d comm=%s %s/%s\n", strftime("%H:%M:%S", nsecs), uid, comm, str(d->d_parent->d_name.name), str($d->d_name.name));
}// 删除文件: ceph_unlink(struct inode *dir, struct dentry *dentry)
kprobe:ceph_unlink {
d = (struct dentry *)arg1; printf("%s UNLINK uid=%d comm=%s %s/%s\n", strftime("%H:%M:%S", nsecs), uid, comm, str(d->d_parent->d_name.name), str($d->d_name.name));
}// 改名: ceph_rename(struct inode *old_dir, struct dentry *old, struct inode *new_dir, struct dentry *new)
kprobe:ceph_rename {
o = (struct dentry *)arg1; n = (struct dentry *)arg3;
printf("%s RENAME uid=%d comm=%s %s -> %s\n", strftime("%H:%M:%S", nsecs),
uid, comm, str(o->d_name.name), str(n->d_name.name));
}// 写数据: ceph_write_iter(struct kiocb *iocb, struct iov_iter *to)
kprobe:ceph_write_iter {
k = (struct kiocb *)arg0; @write[str(k->ki_filp->f_path.dentry->d_name.name), uid, comm] = count();
}
interval:s:10 { print(@open); print(@write); clear(@open); clear(@write); }'
返回执行结果
@open[vscode.lock, 1000, libuv-worker]: 10
@write[events.out.tfevents.1791603961.jupyter-stan-2eluo.1428341.1, 1000, /opt/conda/bin/]: 1
@write[events.out.tfevents.1791601538.jupyter-stan-2eluo.1427656.0, 1000, /opt/conda/bin/]: 1
@write[events.out.tfevents.1791603947.jupyter-stan-2eluo.1428341.0, 1000, /opt/conda/bin/]: 3
@open[vscode.lock, 1000, libuv-worker]: 10
@write[events.out.tfevents.1791603947.jupyter-stan-2eluo.1428341.0, 1000, /opt/conda/bin/]: 1
@open[vscode.lock, 1000, libuv-worker]: 10
@write[events.out.tfevents.1791601543.jupyter-stan-2eluo.1427656.1, 1000, /opt/conda/bin/]: 1
@write[events.out.tfevents.1791601538.jupyter-stan-2eluo.1427656.0, 1000, /opt/conda/bin/]: 2
说明
- open | write 代表操作
-
vscode.lock, 1000, libuv-worker\] 格式为 \[被操作的对象, uid, 进程
- 最后代表每 10 秒发生的次数