作为一个大规模使用的开源分布式统一存储系统,Ceph 支持多种形式的客户端访问,其中 Linux 内核原生支持访问 CephFS 文件存储(kcephfs)和 RBD 块存储(krbd)。
Ceph 内核客户端
Ceph 的内核客户端模块有三个:drivers/block/rbd.ko
, fs/ceph/ceph.ko
, net/ceph/libceph.ko
。显然 rbd.ko
对应 RBD 客户端,ceph.ko
对应 CephFS 客户端,而 libceph.ko
起类似于用户态客户端中 librados
的角色,是 rbd.ko
和 ceph.ko
的依赖模块。
虽然从 RHEL 7 开始 RedHat 的工程师在持续的 backport 内核客户端补丁到 RHEL 商用内核上来,但显然 Linux 内核客户端的支持本来一直以来都是滞后于服务端的发展,然后 backport 的补丁 bugfix 多于 feature,而且由于外场部署的商用内核升级又明显要滞后 RHEL 的内核,因此不可避免的,如果需要在商用产品上继续使用 in-tree 内核态客户端,会存在诸多限制。
kmod
要将 in-tree 的内核客户端迁移到成 out-of-tree 模块(kmod)来进行维护并不复杂,可以参考 ELRepo 中大量的 kmod 项目依葫芦画瓢。
当然,直接参考我的 kmod-ceph 工程会更靠谱一些,因为 kmod-ceph 解决了一个 kmod 模块包含多个 ko 的问题,同时解决了内核头文件的引用优先级问题,而 wip-rbd
分支更是示例了如何从内核主线 backport 到商用内核。
kmod-ceph
kmod-ceph 出现的主要目的是为了解决 RHEL 7.x 早期版本(< 7.4)中 Ceph 内核客户端的 bugfix 或者 feature 缺失,先后支持了 3.10.0-229 和 3.10.0-327 两个版本的内核,从 3.10.0-693(即 RHEL 7.4)开始由于内核自带的内核模块已经解决了所有 kmod-ceph 曾经解决过的故障,同时也已经完全满足外场需求,因此 kmod-ceph 后续已不再维护,这里的记录仅供学习之用。
kmod-ceph 中 ko 相关的源代码以 RHEL 内核为基础,然后从 Linux 内核主线或者 RHEL 高版本内核 backport 了一部分内容或者直接 backport 整个 RHEL 高版本内核(当然可能需要大量的修改以适应内核 API 的修改),像当前的 3.10.0-693
分支的代码就是直接 backport 整个 RHEL 3.10.0-1062.12.1 内核的实现,然后适配了内存管理部分的修改(kmod-ceph/mm)。
kmod-ceph 构建
如果需要构建 rpm 包,执行源代码目录中的 build-rpm.sh
脚本即可:
$ tree -L 2 kmod-ceph/
kmod-ceph/
├── build-rpm.sh
├── configure.ac
├── find-requires.ksyms
├── kmod-ceph
│ ├── drivers
│ ├── fs
│ ├── include
│ ├── Makefile
│ ├── mm
│ └── net
└── kmod-ceph.spec.in
生成的 rpm 包在 output
目录:
$ ls output/
kmod-ceph-3.10.0-693.11.el7.x86_64.rpm
$ rpm -qpl output/kmod-ceph-3.10.0-693.11.el7.x86_64.rpm
/etc/depmod.d/kmod-ceph.conf
/lib/modules/3.10.0-693.el7.x86_64
/lib/modules/3.10.0-693.el7.x86_64/extra
/lib/modules/3.10.0-693.el7.x86_64/extra/ceph
/lib/modules/3.10.0-693.el7.x86_64/extra/ceph/ceph.ko
/lib/modules/3.10.0-693.el7.x86_64/extra/ceph/libceph.ko
/lib/modules/3.10.0-693.el7.x86_64/extra/ceph/rbd.ko
rpm 的版本号默认由 uname -r
生成,因此是不可以修改的,而 release 号由 configure.ac
中的 AC_INIT([kmod-ceph], [11], [runsisi@hust.edu.cn])
第二个字段决定,可以根据需要增加这个字段的数值。
如果不需要构建 rpm 包,如仅构建 ko 用于快速测试,可以切换至源代码根目录下的 kmod-ceph
目录执行 make 命令直接进行构建:
$ make
make -C /lib/modules/3.10.0-693.el7.x86_64/build M=/home/runsisi/kmod-ceph/kmod-ceph modules
make[1]: Entering directory `/usr/src/kernels/3.10.0-693.el7.x86_64'
CC [M] /home/runsisi/kmod-ceph/kmod-ceph/drivers/block/rbd.o
CC [M] /home/runsisi/kmod-ceph/kmod-ceph/fs/ceph/super.o
...
CC [M] /home/runsisi/kmod-ceph/kmod-ceph/net/ceph/string_table.o
LD [M] /home/runsisi/kmod-ceph/kmod-ceph/net/ceph/libceph.o
Building modules, stage 2.
MODPOST 3 modules
CC /home/runsisi/kmod-ceph/kmod-ceph/drivers/block/rbd.mod.o
LD [M] /home/runsisi/kmod-ceph/kmod-ceph/drivers/block/rbd.ko
CC /home/runsisi/kmod-ceph/kmod-ceph/fs/ceph/ceph.mod.o
LD [M] /home/runsisi/kmod-ceph/kmod-ceph/fs/ceph/ceph.ko
CC /home/runsisi/kmod-ceph/kmod-ceph/net/ceph/libceph.mod.o
LD [M] /home/runsisi/kmod-ceph/kmod-ceph/net/ceph/libceph.ko
make[1]: Leaving directory `/usr/src/kernels/3.10.0-693.el7.x86_64'
如果想选择性的构建 kcephfs 或者 krbd,可以修改 kmod-ceph/Makefile
中 obj-y
的定义(当然,net/ceph
用于构建 libceph.ko 是不能去掉的):
obj-y := net/ceph/ drivers/block/ fs/ceph/
kmod-ceph 使用
如果构建出来的是 rpm 包,直接执行 rpm -Uvh
进行安装即可,手工构建的 ko 需要拷贝至 /lib/modules/$(uname -r)/extra/ceph/
目录。
可以通过 modprobe
, modprobe -r
命令加载、移除相关的内核模块,执行 mount -t ceph
, rbd map
也会自动加载相应的内核模块,在测试新的内核模块之前记得先释放相关的资源(umount
, rbd unmap
),并移除相应的老的内核模块。
Ceph 内核客户端问题定位
当前,定位 Ceph 内核客户端问题的手段相对匮乏,主要还是通过内核调试打印并结合代码阅读的方式进行分析。
debugfs
Ceph 内核客户端通过 debugfs 暴露了一些内部的信息,如 osdmap,在途 IO 等,这些信息在处理 IO 卡住(util 100%)时非常有意义:
$ sudo tree /sys/kernel/debug/ceph/
/sys/kernel/debug/ceph/
└── 9d1cca5b-5058-4b92-985c-a08b21d7aa43.client134135
├── client_options
├── monc
├── monmap
├── osdc
└── osdmap
通过 cat 可以查看相关的信息(注意需要 root 权限):
# cat osdmap
epoch 279 barrier 0 flags 0x588000
pool 2 'rbd' type 1 size 1 min_size 1 pg_num 32 pg_num_mask 31 flags 0x1 lfor 0 read_tier -1 write_tier -1
pool 3 'p1' type 1 size 1 min_size 1 pg_num 32 pg_num_mask 31 flags 0x1 lfor 0 read_tier -1 write_tier -1
osd0 192.168.34.12:6801 100% (exists, up) 100%
osd1 192.168.34.12:6803 100% (exists, up) 100%
pg_upmap_items 2.1b [1->0]
pg_upmap_items 3.1a [1->0]
# cat osdc
REQUESTS 0 homeless 0
LINGER REQUESTS
18446462598732840961 osd0 2.97e0fa54 2.14 [0]/0 [0]/0 e279 rbd_header.1e4e26b8b4567 0x20 0 WC/0
BACKOFFS
dmesg 日志
Ceph 内核模块的调试日志打印通过内核所谓的 dynamic debug 功能[1] 控制,这里不准备对 dynamic debug 进行解释,我们只要通过例子理解如果使用它就行。
当然,调试日志能够打印的前提是相关的 ko 已经加载,另外移除 ko 之后相应的日志控制也就失效了,如果需要启用日志打印,则需要配置新的规则。
日志打印控制的开关如下(同样需要 root 权限):
# cat /sys/kernel/debug/dynamic_debug/control | head
# filename:lineno [module]function flags format
init/main.c:751 [main]do_one_initcall_debug =p "initcall %pF returned %d after %lld usecs\012"
init/main.c:744 [main]do_one_initcall_debug =p "calling %pF @ %i\012"
init/main.c:715 [main]initcall_blacklisted =p "initcall %s blacklisted\012"
init/main.c:691 [main]initcall_blacklist =p "blacklisting initcall %s\012"
arch/x86/events/intel/core.c:3989 [core]fixup_ht_bug =_ "failed to disable PMU erratum BJ122, BV98, HSD29 workaround\012"
arch/x86/events/intel/pt.c:650 [pt]pt_topa_dump =_ "# entry @%p (%lx sz %u %c%c%c) raw=%16llx\012"
arch/x86/events/intel/pt.c:641 [pt]pt_topa_dump =_ "# table @%p (%016Lx), off %llx size %zx\012"
arch/x86/kernel/tboot.c:103 [tboot]tboot_probe =_ "tboot_size: 0x%x\012"
arch/x86/kernel/tboot.c:102 [tboot]tboot_probe =_ "tboot_base: 0x%08x\012"
我常用到的日志控制开关如下(dynamic debug 还有更精细的控制):
- 打开(
+p
)、关闭(-p
)rbd.ko
模块(module)中的日志打印
# echo 'module rbd +p' > /sys/kernel/debug/dynamic_debug/control
# echo 'module rbd -p' > /sys/kernel/debug/dynamic_debug/control
- 打开、关闭
osd_client.c
文件(file)中的日志打印
# echo 'file osd_client.c +p' > /sys/kernel/debug/dynamic_debug/control
# echo 'file osd_client.c -p' > /sys/kernel/debug/dynamic_debug/control
- 打开、关闭
handle_osds_timeout
函数(func)中的日志打印
# echo 'func handle_osds_timeout +p' > /sys/kernel/debug/dynamic_debug/control
# echo 'func handle_osds_timeout -p' > /sys/kernel/debug/dynamic_debug/control
显然,触类旁通,很容易应用到其它的模块、文件、函数。
所有的日志都打印的 dmesg 日志中,通过 dmesg
命令输出到终端即可,当然,有时 dmesg
加上适当的选项会更有助于问题定位:
- 清除 dmesg 日志
# dmesg -C
- 以 tail -f 的形式打印 dmesg 日志(同时加上颜色和墙上时间显示)
# dmesg -wH
[Apr12 16:59] Key type dns_resolver registered
[ +0.006271] Key type ceph registered
[ +0.000210] libceph: loaded (mon/osd proto 15/24)
[ +0.002690] rbd: loaded (major 252)
[ +0.004635] libceph: mon0 192.168.34.12:6789 session established
[ +0.000320] libceph: client134143 fsid 9d1cca5b-5058-4b92-985c-a08b21d7aa43
[ +0.031655] rbd: rbd0: capacity 1073741824 features 0x5
[ +7.174484] rbd: rbd_dev_create rbd_dev ffff88003c4b5800 dev_id 1
[ +0.000010] rbd: rbd id object name is rbd_id.i2
[ +0.001055] rbd: rbd_dev_image_id: rbd_obj_method_sync returned 17
[ +0.000017] rbd: image_id is 20bf96b8b4567
[ +0.000006] rbd: __rbd_register_watch rbd_dev ffff88003c4b5800
[ +0.020125] rbd: _rbd_dev_v2_snap_size: rbd_obj_method_sync returned 9
[ +0.000007] rbd: order 22
参考资料
[1] Dynamic debug
https://www.kernel.org/doc/html/latest/admin-guide/dynamic-debug-howto.html
最后修改于 2021-08-22