理解 QEMU/KVM 和 Ceph(2):QEMU 的 RBD 块驱动(block driver)
本系列文章会总结 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, , &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) < ) {
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') {
s->snap = g_strdup(snap_buf);
} /*
* Fallback to more conservative semantics if setting cache
* options fails. Ignore errors from setting rbd_cache because the
* only possible error is that the option does not exist, and
* librbd defaults to no caching. If write through caching cannot
* be set up, fall back to no caching.
*/
if (flags & BDRV_O_NOCACHE) { #当缓存模式为 nocache 时,设置 cluster 中的配置为 '关闭 rbd cache'
rados_conf_set(s->cluster, "rbd_cache", "false");
} else { #其它 cache 模式下,设置 cluster handle 中的配置为 '打开 rbd cache'
rados_conf_set(s->cluster, "rbd_cache", "true");
}
if (strstr(conf, "conf=") == NULL) { #当没有制定 ceph 配置文件时,调用 rados_conf_read_file 函数去读取默认的文件来配置 cluster handle。
/* try default location, but ignore failure */
rados_conf_read_file(s->cluster, NULL); #默认文件主要为
- /etc/ceph/ceph.conf
}
if (conf[] != '\0') {
r = qemu_rbd_set_conf(s->cluster, conf, errp); #继续将配置保存到 handle cluster,如果包含 'conf=‘,则调用 rados_conf_read_file 函数读取该文件并将其内容保存到 cluster handle
if (r < ) {
goto failed_shutdown;
}
}
r = rados_connect(s->cluster); #使用 cluster handle 连接到 ceph 集群,cluster handle 中的配置只有到此时才得到应用,之前一直在准备它。
...
r = rados_ioctx_create(s->cluster, pool, &s->io_ctx); #创建 ioctx
...
r = rbd_open(s->io_ctx, s->name, &s->image, s->snap); #打开客户机磁盘对应的 Ceph image
...
bs->read_only = (s->snap != NULL); #如果是 snapshot 的,则只读
qemu_opts_del(opts);
return ;
...
}
static int qemu_rbd_set_conf(rados_t cluster, const char *conf, Error **errp)
{
...
buf = g_strdup(conf);
p = buf; while (p) {
ret = qemu_rbd_next_tok(name, sizeof(name), p,'=', "conf option name", &p, errp);
...if (strcmp(name, "conf") == ) {
ret = rados_conf_read_file(cluster, value); #如果配置中包括 "conf",则将其内容读取到 cluster handle。可见,如果配置文件中有 rbd cache 的话,则会覆盖qemu之前所做的设置
...
} else if (strcmp(name, "id") == ) {
/* ignore, this is parsed by qemu_rbd_parse_clientname() */
} else {
ret = rados_conf_set(cluster, name, value); #将 conf 中的配置保存到 cluster handle
...
}
}
g_free(buf);
return ret;
}
说明,
(1)options 是在 libvirt xml 中该 driver 的各种参数,比如 file、id、mon_hosts 等。比如
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
(2)注意,目前 nova 启动的虚机的 options 中,没有使用 ”conf=“ 来指定 Ceph 配置文件。因此,qemu 能否读到,取决于所调用的 rados_conf_read_file(s->cluster, NULL) 函数能否在默认位置读取到用户放置的文件,包括:
$CEPH_CONF (environment variable)
/etc/ceph/ceph.conf
~/.ceph/config
ceph.conf (in the current working directory)
(3)如果在默认位置有ceph.conf 文件,并且设置了 rbd cache,那么根据上面代码的执行顺序,ceph.conf 中的配置将覆盖 QEMU 设置的 rbd cache 的值。
(4)如果在默认位置没有 ceph.conf 文件,那么 rados_conf_read_file(s->cluster, NULL) 将会失败,那么 rbd cache 是否开启将完全由 QEMU 根据 disk drive 的 cache mode 决定。
(5)从配置文件读 RBDCache 配置也是有道理的,因为一个 hypervisor 上的 RBDCache,不管各个客户机上的 disk drive 设置如何,其配置应该是唯一的。
(6)如果只需要支持将 ceph volume 连接到 Nova 虚机,完全只需要在 Hypervisor 节点上的 ceph.conf 中方式 RBDCache 配置参数,而不需其它比如 MON 地址这样的参数。因为,如果 Ceph 支持多个Ceph 集群的话,如果在 Ceph.conf 中放置 MON 地址等参数的话,由于 ceph.conf 会覆盖 QEMU 中 cinder 带来的配置,反而会带来问题。
(7)如果更改了 ceph 配置文件,需要重新挂接磁盘或者重启虚机。
(8)上面的分析是基于 qemu 2.2。但是,qemu 的代码变化很快,似乎在 qemu 2.4 里面行为发生了变化,可以参考 http://my.oschina.net/u/1047616/blog/525156?p=1。看起来,cache mode 只要不是 none,qemu 都会打开 rbd cache,不管 rbd cache 在配置文件中是 false 还是 true。因此,调试 qemu + rbd 问题,一定要注意代码版本之间逻辑的差异。
/*设置cache的参数*/
if (flags & BDRV_O_NOCACHE) {
rados_conf_set(s->cluster, "rbd_cache", "false");
} else {
rados_conf_set(s->cluster, "rbd_cache", "true");
}
r = rados_connect(s->cluster); //连接cluster
2. 各种情况下的测试结果
2.1 打开 librbd log 和 admin socket
librdb 的日志和 admin socket 是调试 librbd 的重要工具。
: 修改 /etc/ceph/ceph.conf,添加 log file 和 admin socket
[global]
log file = /var/log/ceph/$name.log
max open files =
auth cluster required = none
auth service required = none
auth client required = none
rbd cache = true
debug perfcounter =
admin socket=/var/run/ceph/rbd-$pid.asok : 修改 /etc/apparmor.d/abstractions/libvirt-qemu,添加下列行,使得运行 qemu 的用户有权限读写 log 和 admin socket 文件
# for rbd
capability mknod,
# for rbd
/etc/ceph/ceph.conf r,
/var/log/ceph/* rw,
/var/run/ceph/** rw,
3. 重启 libvirt-bin 和 nova-compute 服务
4. boot 一个新的虚机,或者重启一个已经存在的虚机
5. 使用 admin socket: ceph --admin-daemon /var/run/ceph/rbd-12856.asok perf dump
2.2 各种 QEMU 和 ceph 缓存配置的测试结果
2.2.1 测试结果
| # | host ceph.conf | rbd_cache 配置项 | guest cache 配置项 | 实际 RBDCache 模式 | 实际客户机 drive cache 模式 | 结论 |
| 1 | 有 | true | writeback | 打开 | writeback |
ceph.conf 中有 rbd cache 配置项时,RDBCache 打开还是关闭受该配置项控制; |
| 2 | 有 | true | none | 打开 | none | |
| 3 | 有 | false | none | 关闭 | none | |
| 4 | 有 | false | writeback | 关闭 | writeback | |
| 5 | 有 | 不配置 | none | 关闭 | none | ceph.conf 中没有 rbd cache 配置项时,RDBCache 打开还是关闭受磁盘驱动的cache 模式控制:'none' 则关闭RBDCache,‘writeback' 则打开RBDCache。其它 RBDCache 参数会从 ceph.conf 中读取。 |
| 6 | 有 | 不配置 | writeback | 打开 | writeback | |
| 7 | 没有 | 同 #5 情况,RDBCache 打开还是关闭受磁盘驱动的cache 模式控制:'none' 则关闭RBDCache,‘writeback' 则打开RBDCache。其它 RBDCache 参数完全使用默认值。 |
以 #1 为例,
root@compute1:/var/log/ceph# cat /etc/ceph/ceph.conf | grep 'rbd cache'
rbd cache = true
rbd cache writethrough until flush = true
root@compute1:/var/log/ceph# virsh dumpxml instance- | grep cache
<driver name='qemu' type='qcow2' cache='none' discard='unmap'/>
<driver name='qemu' type='raw' cache='writeback'/> root@compute1:/var/log/ceph# ceph --admin-daemon rbd-.asok config show | grep rbd_cache
"rbd_cache": "true",
"rbd_cache_writethrough_until_flush": "true",
"rbd_cache_size": "",
"rbd_cache_max_dirty": "",
"rbd_cache_target_dirty": "",
"rbd_cache_max_dirty_age": "",
"rbd_cache_max_dirty_object": "", (这是因为 rbd cache writethrough until flush = true 而此时 librbd 还没有收到 flush 操作过)
"rbd_cache_block_writes_upfront": "false",
2.2.2 不使用 ceph 配置文件时的行为
关于 RBDCache 的默认参数,需要注意不同 librbd 版本中使用的不同值。
| librbd 版本 | librbd 使用的默认值 |
不使用 ceph 配置文件,而且 qemu drive 的 cache 模式 为 ’writeback‘ 时的实际 cache 模式 |
影响 |
| 0.87 之前,比如 0.80 |
rbd cache = false rbd cache writethrough until flush = false |
qemu 设置 rbd cache = true,使用 witeback 模式。 | 当客户机操作系统不支持 barrier 时,writeback 是不安全的。 |
| 0.87 |
rbd cache = true rbd cache writethrough until flush = true |
qemu 设置 rbd cache = true,使用 rbd cache writethrough until flush 默认值 true。再收到第一个 flush 指令前,使用 writethrough,之后使用 writeback。 | 安全性得到增强 |
理解 QEMU/KVM 和 Ceph(2):QEMU 的 RBD 块驱动(block driver)的更多相关文章
- 理解 QEMU/KVM 和 Ceph(3):存储卷挂接和设备名称
本系列文章会总结 QEMU/KVM 和 Ceph 之间的整合: (1)QEMU-KVM 和 Ceph RBD 的 缓存机制总结 (2)QEMU 的 RBD 块驱动(block driver) (3)存 ...
- 理解 QEMU/KVM 和 Ceph(1):QEMU-KVM 和 Ceph RBD 的 缓存机制总结
本系列文章会总结 QEMU/KVM 和 Ceph 之间的整合: (1)QEMU-KVM 和 Ceph RBD 的 缓存机制总结 (2)QEMU 的 RBD 块驱动(block driver) (3)存 ...
- ceph 005 赋权补充 rbd块映射
我的ceph版本 [root@serverc ~]# ceph -v ceph version 16.2.0-117.el8cp (0e34bb74700060ebfaa22d99b7d2cdc037 ...
- 理解 Linux 网络栈(3):QEMU/KVM + VxLAN 环境下的 Segmentation Offloading 技术(发送端)
本系列文章总结 Linux 网络栈,包括: (1)Linux 网络协议栈总结 (2)非虚拟化Linux环境中的网络分段卸载技术 GSO/TSO/UFO/LRO/GRO (3)QEMU/KVM + Vx ...
- KVM 介绍(8):使用 libvirt 迁移 QEMU/KVM 虚机和 Nova 虚机 [Nova Libvirt QEMU/KVM Live Migration]
学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...
- QEMU KVM Libvirt手册(11): Managing Storage
When managing a VM Guest on the VM Host Server itself, it is possible to access the complete file sy ...
- KVM(八)使用 libvirt 迁移 QEMU/KVM 虚机和 Nova 虚机
1. QEMU/KVM 迁移的概念 迁移(migration)包括系统整体的迁移和某个工作负载的迁移.系统整理迁移,是将系统上所有软件包括操作系统完全复制到另一个物理机硬件机器上.虚拟化环境中的迁移, ...
- QEMU/KVM磁盘在线备份
QEMU/KVM磁盘的在线完整及增量备份,是"打包"方案的一种具体实现,可实现基于时间点的备份,同时支持本地与远程2种备份方式,并可指定备份文件进行恢复. tag: qemu, k ...
- KVM 介绍(7):使用 libvirt 做 QEMU/KVM 快照和 Nova 实例的快照 (Nova Instances Snapshot Libvirt)
学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...
随机推荐
- [Cordova] 无法编译Visual Studio项目里Plugin副本的Native Code
[Cordova] 无法编译Visual Studio项目里Plugin副本的Native Code 问题情景 开发Cordova Plugin的时候,开发的流程应该是: 建立Cordova Plug ...
- GifShot - 创建动态 GIF 的 JavaScript 库
GifShot 是一个可以创建流媒体,视频或图像的 GIF 动画的 JavaScript 库.该库的客户端特性使其非常便携,易于集成到几乎任何网站.利用最先进的浏览器 API ,包括 WebRTC , ...
- 优秀工具推荐:两款很棒的 HTML5 游戏开发工具
HTML5 众多强大特性让我们不需要多么高深技术就能创建好玩的网页游戏,同时证明了开放的 Web 技术能与任何其他在游戏开发中使用的技术竞争.正如标题所说,这篇文章推荐的几款很棒 HTML5 游戏开发 ...
- 7个你可能不认识的CSS单位
众所周知CSS技术我们虽然很熟悉,在使用的过程却很容易被困住,这让我们在新问题出现的时候变得很不利.随着web继续不断地发展,对于新技术新解决方案的要求也会不断增长.因此,作为网页设计师和前端开发人员 ...
- angularjs 指令—— 绑定策略(@、=、&)
angularjs 指令—— 绑定策略(@.=.&) 引入主题背景:angular 的指令配置中的template可以直接使用硬编码写相应的代码,不过也可以根据变量,进行动态更新.那么需要用到 ...
- 详细解读XMLHttpRequest(一)同步请求和异步请求
本文主要参考:MDN XMLHttpRequest 让发送一个HTTP请求变得非常容易.你只需要简单的创建一个请求对象实例,打开一个URL,然后发送这个请求.当传输完毕后,结果的HTTP状态以及返回的 ...
- pip安装教程
首先你得安装了Python,这个网上教程大把. 关于pip的安装教程网上也很多,但是安装过程中遇到了很多问题. 我把安装pip需要的资源都放到云盘上了,直接下载就行,省得去找.(点我下载) 里面有两个 ...
- Egret白鹭H5小游戏开发入门(三)
前言: 在上一篇文章中着重介绍了H5小游戏开发的起步阶段,如Wing面板的使用,素材的处理,类的说明等等,那么今天主要是涉及到场景的创建,loading的修改等等的代码编写. 对于这一节,我在讲解的过 ...
- ionic + cordova 配置和开发过程中的一些问题
1.Android sdk:ERROR: SWT folder '' does not exist.问题 在win x86系统中,如果运行Android的 sdk manager,会出现Android ...
- iOS:JSON格式字符串转字典,字典转JSON格式字符串
在iOS开发中,和服务器交互中,经常用到字典和JSON格式字符串相互转换. 代码如下: 1.JSON格式字符串转字典 + (NSDictionary *)dictionaryWithJsonStrin ...