理解 QEMU/KVM 和 Ceph(2):QEMU 的 RBD 塊驅動(block driver)


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

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

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

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

 

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] != '\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] != '\0') { 
        r = qemu_rbd_set_conf(s->cluster, conf, errp); #繼續將配置保存到 handle cluster,如果包含 'conf=‘,則調用 rados_conf_read_file 函數讀取該文件並將其內容保存到 cluster handle if (r < 0) {
            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 0;
...
}

 

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") == 0) {
            ret = rados_conf_read_file(cluster, value); #如果配置中包括 "conf",則將其內容讀取到 cluster handle。可見,如果配置文件中有 rbd cache 的話,則會覆蓋qemu之前所做的設置 ...
        } else if (strcmp(name, "id") == 0) {
            /* 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 的重要工具。

1: 修改 /etc/ceph/ceph.conf,添加 log file 和 admin socket 
    [global]
    log file = /var/log/ceph/$name.log
    max open files = 131072
    auth cluster required = none
    auth service required = none
    auth client required = none
    rbd cache = true
    debug perfcounter = 20
    admin socket=/var/run/ceph/rbd-$pid.asok

2: 修改 /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 打開還是關閉受該配置項控制;
客戶機的磁盤的cache 模式受它自己的配置項控制。其它 RBDCache 參數會從 ceph.conf 中讀取。   

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-00000068 | 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-10588.asok config show | grep rbd_cache "rbd_cache": "true", "rbd_cache_writethrough_until_flush": "true", "rbd_cache_size": "33554432", "rbd_cache_max_dirty": "25165824", "rbd_cache_target_dirty": "16777216", "rbd_cache_max_dirty_age": "1", "rbd_cache_max_dirty_object": "0", (這是因為 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。  安全性得到增強


免責聲明!

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



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