本文共 12568 字,大约阅读时间需要 41 分钟。
本系列文章会总结 QEMU/KVM 和 Ceph 之间的整合:
这篇文章分析一下一个 Ceph RBD 卷是如何被映射到一个 QEMU/KVM 客户机的,以及客户机中设备的命名问题。
挂接一个卷:
#运行nova-attach 命令 nova volume-attach INSTANCE_ID VOLUME_ID auto #在虚机中操作 vm$ ls /dev/disk/by-id/virtio-15a9f901-ba9d-45e1-8vm$ mkfs.ext4 /dev/disk/by-id/virtio-15a9f901-ba9d-45e1-8vm$ mkdir -p /mnt/volumevm$ mount /dev/disk/by-id/virtio-15a9f901-ba9d-45e1-8 /mnt/volumevm$ echo "Hello OpenStack" > /mnt/volume/test.txt
卸载一个卷:
#在虚机中操作 vm$ umount /mnt/volume #运行 Nova 命令$ nova volume-detach
Nova 的 volume-attach 命令为:
root@hkg02kvm004ccz023:~# nova help volume-attachusage: nova volume-attach[ ]Attach a volume to a server.Positional arguments: Name or ID of server. ID of the volume to attach. Name of the device e.g. /dev/vdb. Use "auto" for autoassign (if supported). Libvirt driver will use default device name.
命令:
root@hkg02kvm004ccz023:~# nova volume-attach ap-uplthyfxulbx 8a309ad1-369c-483b-9a95-e4f330bb104e /dev/vdh+----------+--------------------------------------+| Property | Value |+----------+--------------------------------------+| device | /dev/vdh |
虚机中:
[root@ap-4oe4-4rz25pvadal7-cs7nmsu4izfy-server-uplthyfxulbx rules.d]# fdisk -lDisk /dev/vda: 42.9 GB, 42949672960 bytes...Disk /dev/vdb: 1073 MB, 1073741824 bytes...Disk /dev/vdc: 2147 MB, 2147483648 bytes...Disk /dev/vdd: 3221 MB, 3221225472 bytes...
比如通过以下步骤来重现:
整个过程涉及到以下一些模块:
udev 是 Linux 内核的设备管理器(device manager),它主要是负责管理 /dev 目录中的设备节点(device nodes)和 /dev/disk 子目录中的与设备 ID 相关的的符号连接文件。当新的硬件设备(hardware devices)加入系统或者从系统删除时,Linux 内核通过 netlink socket 通知 udev,然后 udev 根据存在的 udev rules 来做相应的处理,默认地,它会在 /dev 目录中创建或者删除设备节点。
从下面的输出可以看出,内核分配的 device name 为 vdb,它是一个 block 设备,其父设备为 virtio,驱动为 virtio-blk,它属于 pci 设备类别。
[root@ap-4oe4-4rz25pvadal7-cs7nmsu4izfy-server-uplthyfxulbx ibmcloud]# udevadm info -a -n /dev/vdb looking at device '/devices/pci0000:00/0000:00:07.0/virtio3/block/vdb': KERNEL=="vdb" SUBSYSTEM=="block" DRIVER=="" ... looking at parent device '/devices/pci0000:00/0000:00:07.0/virtio3': KERNELS=="virtio3" SUBSYSTEMS=="virtio" DRIVERS=="virtio_blk" ... looking at parent device '/devices/pci0000:00/0000:00:07.0': KERNELS=="0000:00:07.0" SUBSYSTEMS=="pci" DRIVERS=="virtio-pci" ... looking at parent device '/devices/pci0000:00': KERNELS=="pci0000:00" SUBSYSTEMS=="" DRIVERS==""
udev rules 由在 /lib/udev/rules.d 目录中的文件来定义。通过这些文件,你可以做到:
可见,udev 在收到 linux 内核发来的消息后,首先会查找 udev rules:如果存在,则执行其中定义的操作;如果不存在,则执行默认的操作,即使用 linux kernel 分配的默认名称来创建一个 device node。
关于 udev rules 的详细信息,以及如何创建新的 rules,可以参考 。
步骤:
#删除设备时 KERNEL[1458809817.791365] remove /devices/virtual/bdi/252:48 (bdi)UDEV [1458809817.791426] remove /devices/virtual/bdi/252:48 (bdi)KERNEL[1458809817.791447] remove /devices/pci0000:00/0000:00:0a.0/virtio5/block/vdd (block)UDEV [1458809817.791485] remove /devices/pci0000:00/0000:00:0a.0/virtio5/block/vdd (block)KERNEL[1458809817.795016] remove /devices/pci0000:00/0000:00:0a.0/virtio5 (virtio)UDEV [1458809817.796617] remove /devices/pci0000:00/0000:00:0a.0/virtio5 (virtio)KERNEL[1458809817.796695] remove /devices/pci0000:00/0000:00:0a.0 (pci)KERNEL[1458809817.796871] remove /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:24 (acpi)UDEV [1458809817.797283] remove /devices/pci0000:00/0000:00:0a.0 (pci)UDEV [1458809817.797301] remove /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:24 (acpi)#添加设备时KERNEL[1458809874.181538] remove /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0c (acpi)KERNEL[1458809874.182088] add /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:25 (acpi)UDEV [1458809874.182104] remove /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0c (acpi)UDEV [1458809874.186103] add /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:25 (acpi)KERNEL[1458809874.234695] add /devices/pci0000:00/0000:00:0b.0 (pci)UDEV [1458809874.238377] add /devices/pci0000:00/0000:00:0b.0 (pci)KERNEL[1458809874.241057] add /devices/pci0000:00/0000:00:0b.0/virtio5 (virtio)UDEV [1458809874.241358] add /devices/pci0000:00/0000:00:0b.0/virtio5 (virtio)KERNEL[1458809874.242006] add /devices/virtual/bdi/252:48 (bdi)UDEV [1458809874.242370] add /devices/virtual/bdi/252:48 (bdi)KERNEL[1458809874.243944] add /devices/pci0000:00/0000:00:0b.0/virtio5/block/vdd (block)UDEV [1458809874.258996] add /devices/pci0000:00/0000:00:0b.0/virtio5/block/vdd (block)
也可以看出,Linux 内核向 udev 传入了该 device 所使用的 device name。
那现在要看的是,客户机的 linux 内核传给 udev 的device name 是它自己产生的,还是由 QEMU/KVM 传入的。
... 6e5424e4-4e4c-4178-a4cc-e63698716e9b
-drive file=rbd:default/volume-6e5424e4-4e4c-4178-a4cc-e63698716e9b:id=cinder:key=AQBM+qVWdTNYKhAAMwULMEcH7TOIcVNyKjIaIg==: auth_supported=cephx\;none:mon_host=10.110.156.54\:6789\;10.110.156.55\:6789\;10.110.156.56\:6789,if=none,id=drive-virtio-disk1, format=raw,serial=6e5424e4-4e4c-4178-a4cc-e63698716e9b,cache=writeback -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x6, drive=drive-virtio-disk1,id=virtio-disk1
其中,选项 '-device' 指定了前端的设备类型,而 '-drive' 选项定义了后端存储,并且通过设备的'drive'属性把设备和存储关联起来。
这里没有看到 dev ='vdb' 相关的设置,初步推断,KVM 目前不支持执行虚机中的 device name(是不是结论,还需要进一步研究)。
Linux 中,每个磁盘由一个 major number 和 一个 minor number 共同组成其 device name 来唯一标识它。以下图为例,/dev/vd{a,b,c,d}表示四个磁盘,/dev/vda1 表示磁盘 /dev/vda 的第一个分区。
[root@ap-4oe4-4rz25pvadal7-cs7nmsu4izfy-server-uplthyfxulbx rules.d]# fdisk -lDisk /dev/vda: 42.9 GB, 42949672960 bytes... Device Boot Start End Blocks Id System/dev/vda1 * 6 238312 41941888 83 LinuxDisk /dev/vdb: 1073 MB, 1073741824 bytes...Disk /dev/vdc: 2147 MB, 2147483648 bytes...Disk /dev/vdd: 3221 MB, 3221225472 bytes...
vda 设备的属性如下,其中可以看到 major number 和 minor number,以及 device name:
[ibmcloud@ap-4oe4-4rz25pvadal7-cs7nmsu4izfy-server-uplthyfxulbx rules.d]$ sudo udevadm info --query=all --name=/dev/vdaP: /devices/pci0000:00/0000:00:04.0/virtio1/block/vdaN: vdaW: 4S: block/252:0S: disk/by-path/pci-0000:00:04.0-virtio-pci-virtio1E: UDEV_LOG=3E: DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1/block/vdaE: MAJOR=252E: MINOR=0E: DEVNAME=/dev/vdaE: DEVTYPE=diskE: SUBSYSTEM=blockE: ID_PATH=pci-0000:00:04.0-virtio-pci-virtio1E: ID_PART_TABLE_TYPE=dosE: LVM_SBIN_PATH=/sbinE: DEVLINKS=/dev/block/252:0 /dev/disk/by-path/pci-0000:00:04.0-virtio-pci-virtio1
而 Linux 内核是在检测到每一个磁盘的时候来分配这两个数字的,包括系统启动和新设备加入以后,DEVNAME 和这两个数字是直接相关的。因此,每个disk 的 device name 不是持久的 (persistent),而是可变的。
比如 nova volume-attach 9ca18b9e-aeef-44ff-81ba-87a59a0c8eec 8a309ad1-369c-483b-9a95-e4f330bb104e auto
当使用 ‘auto’时,nova 会调用下面的方法来分配一个最小的可用 device name。因为它的分配方法和 linux 内核的分配方式一致,因此,当一个虚机的所有 device 都由 nova 来管理时,cinder 看到的 device name 和 虚机中的 device name 将是一致的。当然了,如果通过别的方法给虚机在挂接volume,两者又会出现不一致。
最后的函数,会从 nova 自己维护的 device mapping list 中通过从头开始遍历来分配一个未使用的 device name:
def find_disk_dev_for_disk_bus(mapping, bus, last_device=False, assigned_devices=None): """Identify a free disk dev name for a bus. Determines the possible disk dev names for the bus, and then checks them in order until it identifies one that is not yet used in the disk mapping. If 'last_device' is set, it will only consider the last available disk dev name. Returns the chosen disk_dev name, or raises an exception if none is available. """ dev_prefix = get_dev_prefix_for_disk_bus(bus) if dev_prefix is None: return None if assigned_devices is None: assigned_devices = [] max_dev = get_dev_count_for_disk_bus(bus) if last_device: devs = [max_dev - 1] else: devs = range(max_dev) for idx in devs: disk_dev = dev_prefix + chr(ord('a') + idx) if not has_disk_dev(mapping, disk_dev): if disk_dev not in assigned_devices: return disk_dev
可以看出,nova 还是做了不少事情来尽量保证 device name 的一致性,它已经做了它该做的了,只是由于 KVM 不支持,它无法做到完美。
udevadm info 命令输出的各个参数,都可以作为 udev rules 的过滤条件来定位到某一个 device,并给它命名为一个非默认名字。
Linux 系统中,udev 负责根据 udev rules 维护如下几个目录中的系统连接符:
[ibmcloud@ap-4oe4-4rz25pvadal7-cs7nmsu4izfy-server-uplthyfxulbx ~]$ ls -l /dev/disktotal 0drwxr-xr-x 2 root root 100 Mar 27 07:16 by-iddrwxr-xr-x 2 root root 140 Mar 27 07:16 by-pathdrwxr-xr-x 2 root root 80 Mar 27 07:16 by-uuid
[ibmcloud@ap-4oe4-4rz25pvadal7-cs7nmsu4izfy-server-uplthyfxulbx ~]$ ls -l /dev/disk/by-uuid/total 0lrwxrwxrwx 1 root root 10 Mar 27 07:16 002f642d-f277-49fd-b5ff-47a652b63fe3 -> ../../vda1lrwxrwxrwx 1 root root 9 Mar 27 07:16 1655a8ca-9627-4a81-acca-79fec66e1131 -> ../../vdb
UUID 是给每一个文件系统分配一个唯一标识符的机制。这些标识符是被文件系统工具在分区被格式化的时候产生的,比如 mkfs.*。
[ibmcloud@ap-4oe4-4rz25pvadal7-cs7nmsu4izfy-server-uplthyfxulbx ~]$ ls -l /dev/disk/by-id/
total 0 lrwxrwxrwx 1 root root 9 Mar 27 07:16 virtio-6e5424e4-4e4c-4178-a -> ../../vdb lrwxrwxrwx 1 root root 9 Mar 27 07:16 virtio-8a309ad1-369c-483b-9 -> ../../vdd lrwxrwxrwx 1 root root 9 Mar 27 07:16 virtio-e4585a93-9a90-4967-8 -> ../../vdcID 是依赖于硬件的序列号(hardware serial number)产生的。该 ID 是持久的、与系统无关的、SCSI 标准强制要求的、与设备而不是其中保存的数据比如文件系统相关的 ID。
当该设备是由 cinder volume 挂接而来时,可以看出 device id 和 volume id 的映射关系:
root@hkg02kvm004ccz023:~# cinder list | grep 6e5424e4-4e4c-4178-a| 6e5424e4-4e4c-4178-a4cc-e63698716e9b | in-use | sammyvol3 | 2 | None | false | 9ca18b9e-aeef-44ff-81ba-87a59a0c8eec |
这也证明了 ID 只和 device (volume)相关。
查看 /lib/udev/rules.d/60-persistent-storage.rules 文件中的 virtio-blk 部分:
# virtio-blkKERNEL=="vd*[!0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}"KERNEL=="vd*[0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}-part%n"
比较奇怪的是,vda 怎么没出现在列表中。vda 是宿主机上一个镜像文件挂接而来的:
它的 udev 属性中 ID_SERIAL 的值为空 (ATTR{serial}=="") (sudo udevadm info --query=all --name=/dev/vda)。这是为什么呢?需要进一步研究。
[ibmcloud@ap-4oe4-4rz25pvadal7-cs7nmsu4izfy-server-uplthyfxulbx ~]$ ls -l /dev/disk/by-path/total 0lrwxrwxrwx 1 root root 9 Mar 27 07:16 pci-0000:00:04.0-virtio-pci-virtio1 -> ../../vdalrwxrwxrwx 1 root root 10 Mar 27 07:16 pci-0000:00:04.0-virtio-pci-virtio1-part1 -> ../../vda1lrwxrwxrwx 1 root root 9 Mar 27 07:16 pci-0000:00:06.0-virtio-pci-virtio3 -> ../../vdblrwxrwxrwx 1 root root 9 Mar 27 07:16 pci-0000:00:07.0-virtio-pci-virtio4 -> ../../vdclrwxrwxrwx 1 root root 9 Mar 27 07:16 pci-0000:00:08.0-virtio-pci-virtio5 -> ../../vdd
该 path 和设备的最短物理访问路径(shortest physical path)有关,包括 SCSI host, channel, target, LUN numbers 以及可能得 partition number.等。需要注意的是,这里的硬件访问路径有可能变化,比如 pci slot 改变后。
看看 path 相关的 udev rules:
# by-path (parent device path)ENV{DEVTYPE}=="disk", ENV{ID_PATH}=="", IMPORT{program}="path_id %p"ENV{DEVTYPE}=="disk", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}"ENV{DEVTYPE}=="partition", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}-part%n"
,如需转载请自行联系原作者