kmod-ceph

作为一个大规模使用的开源分布式统一存储系统,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.koceph.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/Makefileobj-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)、关闭(-prbd.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