块设备持久化命名
linux系统块设备持久化命名
问题和原因
现象:对于一台配有多块磁盘的虚拟机,重启后发现卷标顺序可能会发生变化,并进一步导致各个挂载点下的文件系统不一致。
原因:linux系统对磁盘的扫描机制不保证卷标和块设备之间稳定的映射关系。在 Linux 中,这种映射关系取决于三个因素的综合作用:磁盘驱动程序的加载顺序,主机PCI插槽的检测顺序和磁盘本身的插入顺序。例如,对于一个开机之前就挂载好的盘,假设它的驱动程序最先被加载,同时硬件上最先被PCI检测到信号,那么它就占有靠前的卷标。
解决方法
本文实验环境统一为百度智能云BCC,使用 CentOS Linux release 7.5.1804,存储采用百度智能云磁盘 CDS。
简单但不建议使用的 UUID 方法
使用卷标的问题在于磁盘和卷标的映射关系会发生变化。因此我们可以绕过卷标,通过使用与磁盘有着固定映射关系的 UUID,找到指定的磁盘从而保证重启后挂载正确的文件系统。只需一个步骤便可以轻松使用:
将 /etc/fstab 中的使用卷标的项目从
/dev/your-target-label /your-mnt-dir ext4 defaults 1 1
替换为
UUID=the-uuid-of-the-device /your-mnt-dir ext4 defaults 1 1
通过 lsblk -f
或 blkid
命令便可以获得相应设备的 UUID。
之所以可以这样完成文件系统的挂载,是因为一个UUID 其实是指向对应 /dev/vd* 的一个链接。这个链接的维护是自动的,系统会保证一个 UUID 总指向固定的设备—— 即使卷标发生了漂移。
# UUID 是一个链接
[root@instance-iqil7m0l ~] ls -l /dev/disk/by-uuid/
lrwxrwxrwx 1 root root 9 12月 18 11:33 49454dd5-1891-4d68-ba0a-e0be35e3c6cd -> ../../vdc
lrwxrwxrwx 1 root root 9 12月 18 11:20 9a97ecfb-98e1-4bc7-9d05-9ccbfa9a11da -> ../../vdb
lrwxrwxrwx 1 root root 10 12月 17 19:59 ce4f29f2-f1a9-4c31-95e6-5bc32a698d2b -> ../../vda1
# 接着我们重启后颠倒磁盘挂载顺序,使得上次的 vdc 前移成为了 vdb,但是 UUID 依然保持了正确的指向
[root@instance-iqil7m0l ~] ls -l /dev/disk/by-uuid/
lrwxrwxrwx 1 root root 9 12月 18 11:35 49454dd5-1891-4d68-ba0a-e0be35e3c6cd -> ../../vdb
lrwxrwxrwx 1 root root 9 12月 18 12:24 9a97ecfb-98e1-4bc7-9d05-9ccbfa9a11da -> ../../vdc
lrwxrwxrwx 1 root root 10 12月 18 11:36 ce4f29f2-f1a9-4c31-95e6-5bc32a698d2b -> ../../vda1
UUID 的方式简单奏效,但却有两个不足:
- 由快照创建磁盘的 UUID 与源盘保持一致。如果都挂载(attach)到主机上,UUID 指向后挂载的磁盘(后面的挂载动作覆盖先前的挂载)。正因如此,我们无法通过 UUID 同时挂载(mount)源盘和由源盘快照创建的盘的文件系统。
# 使用当前的 vdc 制作快照,并从该快照创建磁盘挂载到主机上成为 vdd。两者 UUID 一致。
[root@instance-iqil7m0l ~] blkid
/dev/vda1: UUID="ce4f29f2-f1a9-4c31-95e6-5bc32a698d2b" TYPE="ext4"
/dev/vdb: UUID="49454dd5-1891-4d68-ba0a-e0be35e3c6cd" TYPE="ext4"
/dev/vdc: UUID="9a97ecfb-98e1-4bc7-9d05-9ccbfa9a11da" TYPE="ext4"
/dev/vdd: UUID="9a97ecfb-98e1-4bc7-9d05-9ccbfa9a11da" TYPE="ext4"
# 并且 UUID 链接指向后来挂载的 vdd (vdc被覆盖)
[root@instance-iqil7m0l ~] ls -l /dev/disk/by-uuid/
lrwxrwxrwx 1 root root 9 12月 18 11:35 49454dd5-1891-4d68-ba0a-e0be35e3c6cd -> ../../vdb
lrwxrwxrwx 1 root root 9 12月 18 12:31 9a97ecfb-98e1-4bc7-9d05-9ccbfa9a11da -> ../../vdd
lrwxrwxrwx 1 root root 10 12月 18 11:36 ce4f29f2-f1a9-4c31-95e6-5bc32a698d2b -> ../../vda1
-
只能用于文件系统,不能用于裸设备。因为UUID是文件系统的属性,没有经过格式化的块设备是没有UUID的。
# 我们擦除了vdc的文件系统,vdc便失去了UUID。 [root@instance-iqil7m0l ~] lsblk -f NAME FSTYPE LABEL UUID MOUNTPOINT vda └─vda1 ext4 ce4f29f2-f1a9-4c31-95e6-5bc32a698d2b / vdb ext4 49454dd5-1891-4d68-ba0a-e0be35e3c6cd vdc vdd ext4 9a97ecfb-98e1-4bc7-9d05-9ccbfa9a11da
解决绝大部分问题的 Disk ID 方法
使用 Disk ID 可以完美解决 UUID 的两个问题。对于由快照创建磁盘,其 Disk ID 区别于源盘。因此在挂载时不会产生冲突。对于操作裸设备,只需使用 /dev/disk/by-id/ 目录下的链接即可。文件系统挂载用法:
首先确定卷标的对应的 Disk ID:
ls -l /dev/disk/by-id/ | grep your-target-label | cut -d " " -f 10
再将 /etc/fstab 中的使用卷标的项目从
/dev/your-target-label /your-mnt-dir ext4 defaults 1 1
替换为
/dev/disk/your-disk-id /your-mnt-dir ext4 defaults 1 1
也许您不愿意记住冗长的 Disk ID 或者 UUID?不用担心!参考下文 udev 方案,您可以绑定自定义的"卷标"。
大杀器 udev
使用 udev 的操作相对前面介绍的方式较为复杂,因此不建议使用——除非您想完成一些进阶操作。udev 可以解决几乎所有相关的问题。实际上,上述的 UUID 或是 Disk ID 都是利用 udev 机制实现的。我们可以借助 udev 实现自己的块设备持久化命名机制。下面我们以绑定设备到自定义的“卷标”为例,介绍一种使用案例。
首先我们需要确定待绑定的磁盘设备,这里使用具有唯一性的磁盘序列号,可以通过以下命令获得:
udevadm info --query=all --name=/dev/vdd | grep ID_SERIAL | cut -d "=" -f 2
接着新增加 /etc/udev/rules.d/90-myblk.rules 规则(开头的数字表示规则执行的次序,越大越靠后。我们希望系统在我们的规则执行前已经创建了相应的设备文件,所以这里选用了比较大的数字 90)。内容为:
ENV{ID_SERIAL}=="your-serial-number", SYMLINK="myblk_a" #若匹配到序列号则创建对应链接
接着重启主机,我们就可以通过 /dev/myblk_a
来引用指定的磁盘,对它进行裸盘的操作、创建文件系统或者挂载文件系统等等。这种方法可以保证磁盘和我们自定义卷标的对应关系是持久化的。
# 重启之前: vdc <-> 046b... & vdd <-> da0a...
[root@instance-iqil7m0l ~] udevadm info --query=all --name=/dev/vdc | grep ID_SERIAL
E: ID_SERIAL=046b4230-760d-42db-8
[root@instance-iqil7m0l ~] udevadm info --query=all --name=/dev/vdd | grep ID_SERIAL
E: ID_SERIAL=da0a787e-fb65-4e0b-b
# 添加规则文件: myblk_a <-> 046b... & myblk_b <-> da0a...
ENV{ID_SERIAL}=="046b4230-760d-42db-8", SYMLINK="myblk_a"
ENV{ID_SERIAL}=="da0a787e-fb65-4e0b-b", SYMLINK="myblk_b"
# 重启后磁盘和系统卷标对应关系改变: vdc <-> da0a... & vdd <-> 046b...
[root@instance-iqil7m0l ~] udevadm info --query=all --name=/dev/vdc | grep ID_SERIAL
E: ID_SERIAL=da0a787e-fb65-4e0b-b
[root@instance-iqil7m0l ~] udevadm info --query=all --name=/dev/vdd | grep ID_SERIAL
E: ID_SERIAL=046b4230-760d-42db-8
# 但是磁盘和自定义的卷标对应关系是正确的: myblk_a <-> 046b... & myblk_b <-> da0a...
[root@instance-iqil7m0l ~] udevadm info --query=all --name=/dev/myblk_a | grep ID_SERIAL
E: ID_SERIAL=046b4230-760d-42db-8
[root@instance-iqil7m0l ~] udevadm info --query=all --name=/dev/myblk_b | grep ID_SERIAL
E: ID_SERIAL=da0a787e-fb65-4e0b-b
udev 是内核和用户态程序共同提供的一种服务,其原理可以简单理解为:设备接入系统后,支持热插拔的设备驱动程序会通过内核向用户态程序udevd发通知,通知中带有一些必要信息(例如我们用到的磁盘序列号)。收到通知后,用户态程序会对照规则表(例如我们编写的 90-myblk.rules),利用通知中的信息进行过滤,并对目标消息执行预定义的操作(例如我们定义操作为创建链接myblock_a)。
总结
这一系列问题的根本原因是卷标和设备映射关系不稳定。为了让映射关系持久化,我们使用 UUID 来绕过卷标进行操作。但鉴于UUID引入了相关问题我们不建议使用这种方案,而推荐使用更加适合云环境的 Disk ID 的持久化命名方式。最后我们介
绍了 UUID和Disk ID 的底层实现—— udev 机制。使用 udev 可以灵活地实现自定义持久化命名,不过代价是相较于其他方案配置更加复杂。