理解 QEMU/KVM 和 Ceph(3):存儲卷掛接和設備名稱


本系列文章會總結 QEMU/KVM 和 Ceph 之間的整合:

(1)QEMU-KVM 和 Ceph RBD 的 緩存機制總結

(2)QEMU 的 RBD 塊驅動(block driver)

(3)存儲卷掛接和設備名稱

 

這篇文章分析一下一個 Ceph RBD 卷是如何被映射到一個 QEMU/KVM 客戶機的,以及客戶機中設備的命名問題。

1. 遇到的設備命名問題

1.1 通過 Nova 和 Cinder 做 Ceph RDB 卷掛接和卸載步驟

掛接一個卷:

#運行nova-attach 命令
nova volume-attach INSTANCE_ID VOLUME_ID auto

#在虛機中操作
vm$ ls /dev/disk/by-id/ virtio-15a9f901-ba9d-45e1-8 vm$ mkfs.ext4 /dev/disk/by-id/virtio-15a9f901-ba9d-45e1-8 vm$ mkdir -p /mnt/volume vm$ mount /dev/disk/by-id/virtio-15a9f901-ba9d-45e1-8 /mnt/volume vm$ echo "Hello OpenStack" > /mnt/volume/test.txt

卸載一個卷:

#在虛機中操作
vm$ umount /mnt/volume

#運行 Nova 命令 $ nova volume
-detach <instanceid> <volumeid>

1.2 兩個問題

Nova 的 volume-attach 命令為:

root@hkg02kvm004ccz023:~# nova help volume-attach
usage: nova volume-attach <server> <volume> [<device>]

Attach a volume to a server.
Positional arguments:
  <server>  Name or ID of server.
  <volume>  ID of the volume to attach.
  <device>  Name of the device e.g. /dev/vdb. Use "auto" for autoassign (if supported). Libvirt driver will use default device name.

1.2.1 在 KVM 環境中,nova 的 volume-attach 命令中的 device 和 虛機中該設備的 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 -l

Disk /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
...

1.2.2 虛機中設備的 device name 在虛機重啟后發生改變

比如通過以下步驟來重現:

  1. 掛接 volume1 到 /dev/vda,掛接 volume2 到 /dev/vdb
  2. 將 volume1 下載
  3. 重新啟動虛機
  4. 發現 volume2 的設備名稱變為了 /dev/vda 

2. 原因分析

2.1 QEMU/KVM Linux 客戶機中的 virtio-blk 設備

 

整個過程涉及到以下一些模塊:

  • QEMU/KVM 的 virtio-blk 的幾個模塊,這在前兩篇文章中都有提到
  • udv 和 udev rules,下面會提到
  • /dev 目錄

2.2 Linux udev 和 udev rules

udev 是 Linux 內核的設備管理器(device manager),它主要是負責管理 /dev 目錄中的設備節點(device nodes)和 /dev/disk 子目錄中的與設備 ID 相關的的符號連接文件。當新的硬件設備(hardware devices)加入系統或者從系統刪除時,Linux 內核通過 netlink socket 通知 udev,然后 udev 根據存在的 udev rules 來做相應的處理,默認地,它會在 /dev 目錄中創建或者刪除設備節點。

2.2.1 一個 virtio-blk 設備的示例

從下面的輸出可以看出,內核分配的 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==""

2.2.2 udev rules

udev rules 由在 /lib/udev/rules.d 目錄中的文件來定義。通過這些文件,你可以做到:

  • 修改一個 devide node 的名稱 (Rename a device node from the default name to something else)
  • Provide an alternative/persistent name for a device node by creating a symbolic link to the default device node
  • Name a device node based on the output of a program
  • 修改權限和所有權(Change permissions and ownership of a device node)
  • 啟動一個腳本(Launch a script when a device node is created or deleted (typically when a device is attached or unplugged))
  • 修改網卡名稱(Rename network interfaces)

可見,udev 在收到 linux 內核發來的消息后,首先會查找 udev rules:如果存在,則執行其中定義的操作;如果不存在,則執行默認的操作,即使用 linux kernel 分配的默認名稱來創建一個 device node。

關於 udev rules 的詳細信息,以及如何創建新的 rules,可以參考 http://www.reactivated.net/writing_udev_rules.html

2.2.3 udev 從 linux 內核收到的消息

步驟:

  1. 在虛機中運行 udevadm monitor 命令
  2. 在 OpenStack 中運行 nova attach-volume 和 detach-volume 命令
  3. 在虛機的console 中就可以看到下面的輸出
#刪除設備時
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。

2.3 virtio-blk 驅動模塊

那現在要看的是,客戶機的 linux 內核傳給 udev 的device name 是它自己產生的,還是由 QEMU/KVM 傳入的。

2.3.1 虛機的 libvirt xml 文件中確實指定了 taget device name

   <disk type='network' device='disk'>
      <driver name='qemu' type='raw' cache='writeback'/>
      ...
      <target dev='vdb' bus='virtio'/>
      <serial>6e5424e4-4e4c-4178-a4cc-e63698716e9b</serial>
      <alias name='virtio-disk1'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
    </disk>

2.3.2 虛機的 qemu 進程中沒有該 dev 參數

-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(是不是結論,還需要進一步研究)。

2.4 客戶機linux 內核分配 device name 的方法

2.4.1 major number 和 minor number

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 -l

Disk /dev/vda: 42.9 GB, 42949672960 bytes
...
   Device Boot      Start         End      Blocks   Id  System
/dev/vda1   *           6      238312    41941888   83  Linux

Disk /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/vda
P: /devices/pci0000:00/0000:00:04.0/virtio1/block/vda
N: vda
W: 4
S: block/252:0
S: disk/by-path/pci-0000:00:04.0-virtio-pci-virtio1
E: UDEV_LOG=3
E: DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1/block/vda
E: MAJOR=252
E: MINOR=0
E: DEVNAME=/dev/vda
E: DEVTYPE=disk
E: SUBSYSTEM=block
E: ID_PATH=pci-0000:00:04.0-virtio-pci-virtio1
E: ID_PART_TABLE_TYPE=dos
E: LVM_SBIN_PATH=/sbin
E: DEVLINKS=/dev/block/252:0 /dev/disk/by-path/pci-0000:00:04.0-virtio-pci-virtio1

而 Linux 內核是在檢測到每一個磁盤的時候來分配這兩個數字的,包括系統啟動和新設備加入以后,DEVNAME 和這兩個數字是直接相關的。因此,每個disk 的 device name 不是持久的 (persistent),而是可變的。

3. 解決辦法

3.1 在 KVM 環境中,在 nova attach-volume 命令中使用 ‘auto’,在一般情況下,會解決device name 不一致的問題

比如 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 不支持,它無法做到完美。

3.2 自定義 udev rules,來根據 device 的屬性來命名一個 device

udevadm info 命令輸出的各個參數,都可以作為 udev rules 的過濾條件來定位到某一個 device,並給它命名為一個非默認名字。

3.3 不使用 device name,而使用 device ID

Linux 系統中,udev 負責根據 udev rules 維護如下幾個目錄中的系統連接符:

[ibmcloud@ap-4oe4-4rz25pvadal7-cs7nmsu4izfy-server-uplthyfxulbx ~]$ ls -l /dev/disk
total 0
drwxr-xr-x 2 root root 100 Mar 27 07:16 by-id
drwxr-xr-x 2 root root 140 Mar 27 07:16 by-path
drwxr-xr-x 2 root root  80 Mar 27 07:16 by-uuid

3.3.1 UUID

[ibmcloud@ap-4oe4-4rz25pvadal7-cs7nmsu4izfy-server-uplthyfxulbx ~]$ ls -l /dev/disk/by-uuid/
total 0
lrwxrwxrwx 1 root root 10 Mar 27 07:16 002f642d-f277-49fd-b5ff-47a652b63fe3 -> ../../vda1
lrwxrwxrwx 1 root root  9 Mar 27 07:16 1655a8ca-9627-4a81-acca-79fec66e1131 -> ../../vdb

UUID 是給每一個文件系統分配一個唯一標識符的機制。這些標識符是被文件系統工具在分區被格式化的時候產生的,比如 mkfs.*。

3.3.2 ID

[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 -> ../../vdc

ID 是依賴於硬件的序列號(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-blk
KERNEL=="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 是宿主機上一個鏡像文件掛接而來的:

<disk type='file' device='disk'>
      <driver name='qemu' type='qcow2' cache='none'/>
      <source file='/opt/stack/data/nova/instances/9ca18b9e-aeef-44ff-81ba-87a59a0c8eec/disk'/>
      <target dev='vda' bus='virtio'/>
      <alias name='virtio-disk0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </disk>

它的 udev 屬性中 ID_SERIAL 的值為空 (ATTR{serial}=="") (sudo udevadm info --query=all --name=/dev/vda)。這是為什么呢?需要進一步研究。

3.3.3 path

[ibmcloud@ap-4oe4-4rz25pvadal7-cs7nmsu4izfy-server-uplthyfxulbx ~]$ ls -l /dev/disk/by-path/
total 0
lrwxrwxrwx 1 root root  9 Mar 27 07:16 pci-0000:00:04.0-virtio-pci-virtio1 -> ../../vda
lrwxrwxrwx 1 root root 10 Mar 27 07:16 pci-0000:00:04.0-virtio-pci-virtio1-part1 -> ../../vda1
lrwxrwxrwx 1 root root  9 Mar 27 07:16 pci-0000:00:06.0-virtio-pci-virtio3 -> ../../vdb
lrwxrwxrwx 1 root root  9 Mar 27 07:16 pci-0000:00:07.0-virtio-pci-virtio4 -> ../../vdc
lrwxrwxrwx 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"
 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM