本系列文章会总结 QEMU/KVM 和 Ceph 之间的整合:
(1)QEMU-KVM 和 Ceph RBD 的 缓存机制总结
(2)QEMU 的 RBD 块驱动(block driver)
1. QEMU 的 RBD 块驱动
QEMU/KVM 虚机中的磁盘(disk drive),可能虚拟自 Hypervisor 上的 qcow2,raw 等格式的镜像文件,也可能来自网络块设备存储系统比如 Ceph 的一个卷等。QEMU 使用一套统一的插件式的块设备驱动架构,它定义了若干需要每种块设备驱动实现的接口。Ceph RBD 作为其中的一种,与其它种类的块设备驱动没有本质区别。
1.1 QEMU 存储设备
客户机可以拥有的设备和介质:Floppy, CD-ROM, USB stick, SD card, harddisk
主机上的存储设备和介质:
- 文件,包括 img,iso,NFS 等
- CD-ROM (/dev/cdrom)块设备,包括 /dev/sda3, LVM volumes, iSCSI LUNs 等
- 分布式存储,比如 Sheepdog, Ceph 等
客户机中的块设备驱动的定义:qemu -drive file=path/to/img,if=none|ide|virtio|scsi,cache=writethrough|writeback|none|unsafe
其中,file 指定主机上的镜像文件或者块设备的路径,if 指定存储接口,cache 指定缓存模式。
比如:
(1)使用镜像文件虚拟的 diskdrive
-drive file=/var/lib/nova/instances/cc388037-18dc-4159-896c-2b7180e7dd20/disk,if=none,id=drive-virtio-disk0,format=qcow2,cache=none -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1
(2)使用 Ceph 卷虚拟的 diskdrive
-drive file=rbd:volumes/volume-512c91d8-a4da-4dcf-b5aa-ef43cf25cb3a:id=cinder:key=AQBc4vtV+JywHhAAqX8N+M69PhIJuUzf1mqNAg==:auth_supported=cephx;none:mon_host=9.115.251.194:6789;9.115.251.195:6789;9.115.251.218:6789,if=none,id=drive-virtio-disk1,format=raw,serial=512c91d8-a4da-4dcf-b5aa-ef43cf25cb3a,cache=writeback -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x6,drive=drive-virtio-disk1,id=virtio-disk1
1.2 QEMU 存储栈
Virtio 是准虚拟化存储接口,提供较好的性能,其中,virtio_blk 是准虚拟化块设备接口。IDE 是 QEMU 全虚拟化接口,提供最好的兼容性,但是性能最差。SCSI 是新的给特定设备的接口。本文以 virtio 为阐述对象。
Virtio 的工作流程(更详细的流程,请访问 KVM 介绍(3):I/O 全虚拟化和准虚拟化):
客户机中的应用通过 vfs (linux 虚拟文件系统)访问其由 Ceph image 映射而来的磁盘,该访问通过 virtio 传到 QEMU,它调用响应的块设备驱动来访问该磁盘对应的块存储。
QEMU 需要支持多种块设备,因此,在其代码中,它定义了一个块设备数据结构(BlockDriver),其中包括各种属性,以及各种块设备驱动需要实现的函数。
1.3 QEMU 的 Ceph RBD 块设备驱动概述
(以 QEMU 2.2 代码为分析目标)
对 RBD 驱动来说,QEMU 对于通过 virtio 传过来的虚拟磁盘读写请求,会将其转化为通过 librbd 对 Ceph MON 和 OSD 服务的访问。主要操作包括:
static BlockDriver bdrv_rbd = { .format_name = "rbd", .instance_size = sizeof(BDRVRBDState), .bdrv_needs_filename = true, .bdrv_file_open = qemu_rbd_open, .bdrv_close = qemu_rbd_close, .bdrv_create = qemu_rbd_create, .bdrv_has_zero_init = bdrv_has_zero_init_1, .bdrv_get_info = qemu_rbd_getinfo, .create_opts = &qemu_rbd_create_opts, .bdrv_getlength = qemu_rbd_getlength, .bdrv_truncate = qemu_rbd_truncate, .protocol_name = "rbd", .bdrv_aio_readv = qemu_rbd_aio_readv, .bdrv_aio_writev = qemu_rbd_aio_writev, #ifdef LIBRBD_SUPPORTS_AIO_FLUSH .bdrv_aio_flush = qemu_rbd_aio_flush, #else .bdrv_co_flush_to_disk = qemu_rbd_co_flush, #endif #ifdef LIBRBD_SUPPORTS_DISCARD .bdrv_aio_discard = qemu_rbd_aio_discard, #endif .bdrv_snapshot_create = qemu_rbd_snap_create, .bdrv_snapshot_delete = qemu_rbd_snap_remove, .bdrv_snapshot_list = qemu_rbd_snap_list, .bdrv_snapshot_goto = qemu_rbd_snap_rollback, #ifdef LIBRBD_SUPPORTS_INVALIDATE .bdrv_invalidate_cache = qemu_rbd_invalidate_cache, #endif };
其中,在一个 Ceph 卷第一次被连接到虚机,以及虚机启动时,QEMU 都会为它调用 qemu_rbd_open 函数。注意,qemu 是通过动态链接库的方式来使用 librbd 库的。
1.4 QEMU 的 qemu_rbd_open 函数
在虚机中使用一个从 Ceph volume 中虚拟而来的 disk drive 的第一步,是打开这个设备。
static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags, Error **errp) # options 参数见下文描述 { BDRVRBDState *s = bs->opaque; ... opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort); qemu_opts_absorb_qdict(opts, options, &local_err); ... filename = qemu_opt_get(opts, "filename"); if (qemu_rbd_parsename(filename, pool, sizeof(pool), snap_buf, sizeof(snap_buf), s->name, sizeof(s->name), conf, sizeof(conf), errp) < 0) { r = -EINVAL; goto failed_opts; } clientname = qemu_rbd_parse_clientname(conf, clientname_buf); r = rados_create(&s->cluster, clientname); #创建一个handle,其中,cluster 是保存 handle 的数据结构,clientname 是访问 ceph 的username ... s->snap = NULL; if (snap_buf[0] != '