理解 OpenStack + Ceph 系列文章:
(1)安裝和部署
(3)Ceph 物理和邏輯結構
(4)Ceph 的基礎數據結構
(6)QEMU-KVM 和 Ceph RBD 的 緩存機制總結
(8)關於Ceph PGs
1. Glance 與 Ceph RBD 集成
1.1 代碼
Kilo 版本中,glance-store 代碼被從 glance 代碼中分離出來了,地址在 https://github.com/openstack/glance_store。
Glance 中與 Ceph 相關的配置項:
配置項 | 含義 | 默認值 |
rbd_pool | 保存rbd 卷的ceph pool 名稱 | images |
rbd_user | rbd user id,僅僅在使用 cephx 認證時使用 | none。讓 librados 根據 ceph 配置文件決定 |
rbd_ceph_conf | Ceph 配置文件的完整路徑 | /etc/ceph/ceph.conf |
rbd_store_chunk_size | 卷會被分成對象的大小(單位為 MB) | 64 |
import rbd
- class StoreLocation(location.StoreLocation) #表示 Glance image 在 RBD 中的location,格式為 "rbd://<FSID>/<POOL>/<IMAGE>/<SNAP>"
- class ImageIterator(object) #實現從 RBD image 中讀取數據塊
- class Store(driver.Store): #實現將 RBD 作為Nova 鏡像后端(backend)
class Store(driver.Store) 實現的主要方法:
(1)獲取 image 數據:根據傳入的Glance image location,獲取 image 的 IO Interator
def get(self, location, offset=0, chunk_size=None, context=None): return (ImageIterator(loc.pool, loc.image, loc.snapshot, self), self.get_size(location)) 1. 根據 location,定位到 rbd image 2. 調用 image.read 方法,按照 chunk size 讀取 image data 3. 將 image data 返回調用端
(2)獲取 image 的 size
def get_size(self, location, context=None): 1. 找到 store location:loc = location.store_location 2. 使用 loc 中指定的 pool 或者配置的默認pool 3. 建立 connection 和 打開 IO Context:with rbd.Image(ioctx, loc.image, snapshot=loc.snapshot) as image 4. 獲取 image.stat() 並獲取 “size”
(3)添加 image
def add(self, image_id, image_file, image_size, context=None): 1. 將 image_id 作為 rbd image name:image_name = str(image_id) 2. 創建 rbd image:librbd.create(ioctx, image_name, size, order, old_format=False, features=rbd.RBD_FEATURE_LAYERING)。如果 rbd 支持 RBD_FEATURE_LAYERING 的話,創建一個 clonable snapshot:rbd://fsid/pool/image/snapshot;否則,創建一個 rbd image:rbd://image 3. 調用 image.write 方法將 image_file 的內容按照 chunksize 依次寫入 rbd image 4. 如果要創建 snapshot 的話,調用 image.create_snap(loc.snapshot) 和 image.protect_snap(loc.snapshot) 創建snapshot
(4)刪除 image
def delete(self, location, context=None): 1. 根據 location 計算出 rbd image,snapshot 和 pool 2. 如果它是一個 snapshot (location 是 rbd://fsid/pool/image/snapshot),則 unprotect_snap,再 remove_snap,然后刪除 image;這時候有可能出錯,比如存在基於該 image 的 volume。 3. 如果它不是一個 snapshot (location 是 rbd://image),直接刪除 rbd image。這操作也可能會出錯。
1.2 使用
+------------------+----------------------------------------------------------------------------------+
| Property | Value |
+------------------+----------------------------------------------------------------------------------+
| checksum | 56730d3091a764d5f8b38feeef0bfcef |
| container_format | bare |
| created_at | 2015-09-18T10:22:49Z |
| direct_url | rbd://4387471a-ae2b-47c4-b67e-9004860d0fd0/images/71dc76da-774c- |
root@ceph1:~# rbd info images/71dc76da-774c-411f-a958-1b51816ec50f rbd image '71dc76da-774c-411f-a958-1b51816ec50f': size 40162 kB in 5 objects order 23 (8192 kB objects) block_name_prefix: rbd_data.103d2246be0e format: 2 features: layering
root@ceph1:~# rbd snap ls images/71dc76da-774c-411f-a958-1b51816ec50f SNAPID NAME SIZE 2 snap 40162 kB
root@ceph1:~# rbd info images/71dc76da-774c-411f-a958-1b51816ec50f@snap
rbd image '71dc76da-774c-411f-a958-1b51816ec50f':
size 40162 kB in 5 objects
order 23 (8192 kB objects)
block_name_prefix: rbd_data.103d2246be0e
format: 2
features: layering
protected: True
基於該 Glance image 創建的 Cinder volume 都是該 snapshot 的 clone:
root@ceph1:~# rbd children images/71dc76da-774c-411f-a958-1b51816ec50f@snap volumes/volume-65dbaf38-0b9d-4654-bba4-53f12cc906e3 volumes/volume-6868a043-1412-4f6c-917f-bbffb1a8d21a
此時如果試着去刪除該 image,則會報錯:
The image cannot be deleted because it is in use through the backend store outside of Glance. 這是因為該 image 的 rbd image 的 snapshot 被使用了。
2. Cinder 與 Ceph RBD 的集成
OpenStack Cinder 組件和 Ceph RBD 集成的目的是將 Cinder 卷(volume)保存在 Ceph RBD 中。當使用 Ceph RBD 作為 Cinder 的后端存儲時,你不需要單獨的一個 Cinder-volume 節點.
2.1 配置項
配置項 | 含義 | 默認值 |
rbd_pool | 保存rbd 卷的ceph pool 名稱 | rbd |
rbd_user | 訪問 RBD 的用戶的 ID,僅僅在使用 cephx 認證時使用 | none |
rbd_ceph_conf | Ceph 配置文件的完整路徑 | ‘’,表示使用 librados 的默認ceph 配置文件 |
rbd_secret_uuid | rbd secret uuid | |
rbd_flatten_volume_from_snapshot | RBD Snapshot 在底層會快速復制一個元信息表,但不會產生實際的數據拷貝,因此當從 Snapshot 創建新的卷時,用戶可能會期望不要依賴原來的 Snapshot,這個選項開啟會在創建新卷時對原來的 Snapshot 數據進行拷貝來生成一個不依賴於源 Snapshot 的卷。 | false |
rbd_max_clone_depth | 卷克隆的最大層數,超過的話則使用 fallter。設為 0 的話,則禁止克隆。 與上面這個選項類似的原因,RBD 在支持 Cinder 的部分 API (如從 Snapshot 創建卷和克隆卷)都會使用 rbd clone 操作,但是由於 RBD 目前對於多級卷依賴的 IO 操作不好,多級依賴卷會有比較嚴重的性能問題。因此這里設置了一個最大克隆值來避免這個問題,一旦超出這個閥值,新的卷會自動被 flatten。 |
5 |
rbd_store_chunk_size | 每個 RBD 卷實際上就是由多個對象組成的,因此用戶可以指定一個對象的大小來決定對象的數量,默認是 4 MB | 4 |
rados_connect_timeout | 連接 ceph 集群的超時時間,單位為秒。如果設為負值,則使用默認 librados 中的值 | -1 |
從這里也能看出來,
(1)Cinder 不支持單個 volume 的條帶化參數設置,而只是使用了公共配置項 rbd_store_chunk_size 來指定 order。
(2)Cinder 不支持卷被附加到客戶機時設置緩存模式。
2.2 代碼
Cinder 使用的就是之前介紹過的 rbd phthon 模塊:
import rados import rbd
它實現了以下主要接口。
2.2.1 與 RBD 的連接
def initialize_connection(self, volume, connector):
hosts, ports = self._get_mon_addrs() #調用 args = ['ceph', 'mon', 'dump', '--format=json'] 獲取 monmap,再獲取 hosts 和 ports data = { 'driver_volume_type': 'rbd', 'data': { 'name': '%s/%s' % (self.configuration.rbd_pool, volume['name']), 'hosts': hosts, 'ports': ports, 'auth_enabled': (self.configuration.rbd_user is not None), 'auth_username': self.configuration.rbd_user, 'secret_type': 'ceph', 'secret_uuid': self.configuration.rbd_secret_uuid, } }
(2)連接到 ceph rados
client = self.rados.Rados(rados_id=self.configuration.rbd_user, conffile=self.configuration.rbd_ceph_conf)
client.connect(timeout= self.configuration.rados_connect_timeout) ioctx = client.open_ioctx(pool)
(3)斷開連接
ioctx.close()
client.shutdown()
2.2.2 創建卷
with RADOSClient(self) as client: self.rbd.RBD().create(client.ioctx, encodeutils.safe_encode(volume['name']), size, order, old_format=old_format, features=features)
2.2.3 克隆卷
#創建克隆卷 def create_cloned_volume(self, volume, src_vref):
# 因為 RBD 的 clone 方法是基於 snapshot 的,所有 cinder 會首先創建一個 snapshot,再創建一個 clone。 if CONF.rbd_max_clone_depth <= 0: #如果設置的 rbd_max_clone_depth 為負數,則做一個完整的 rbd image copy vol.copy(vol.ioctx, dest_name) depth = self._get_clone_depth(client, src_name) #判斷 volume 對應的 image 的 clone depth,如果已經達到 CONF.rbd_max_clone_depth,則需要做 flattern src_volume = self.rbd.Image(client.ioctx, src_name) #獲取 source volume 對應的 rbd image #如果需要 flattern, _pool, parent, snap = self._get_clone_info(src_volume, src_name) #獲取 parent 和 snapshot src_volume.flatten() # 將 parent 的data 拷貝到該 clone 中 parent_volume = self.rbd.Image(client.ioctx, parent) #獲取 paraent image parent_volume.unprotect_snap(snap) #將 snap 去保護 parent_volume.remove_snap(snap) #刪除 snapshot src_volume.create_snap(clone_snap) #創建新的 snapshot src_volume.protect_snap(clone_snap) #將 snapshot 加保護 self.rbd.RBD().clone(client.ioctx, src_name, clone_snap, client.ioctx, dest_name, features=client.features) #在 snapshot 上做clone self._resize(volume) #如果 clone 的size 和 src volume 的size 不一樣,則 resize
3. Nova 與 Ceph 的集成
3.1 Nova 與 Ceph 集成的配置項
配置項 | 含義 | 默認值 |
images_type | 其值可以設為下面幾個選項中的一個:
|
default |
images_rbd_pool | 存放 vm 鏡像文件的 RBD pool | rbd |
images_rbd_ceph_conf | Ceph 配置文件的完整路徑 | ‘’ |
hw_disk_discard | 設置使用或者不使用discard 模式,使用的話需要 Need Libvirt(1.0.6)、 Qemu1.5 (raw format) 和 Qemu1.6(qcow2 format)') 的支持 "unmap" : Discard requests("trim" or "unmap") are passed to the filesystem. |
none |
rbd_user | rbd user ID | |
rbd_secret_uuid | rbd secret UUID |
- 設備直接暴露給客戶機(device pass-through to directly expose physical storage devices to guests)
- 更好的性能(better performance and support for true SCSI device)
- 標准的設備命名方法(common and standard device naming identical to the physical world thus virtualising physical applications is made easier)
- 更好的擴展性(better scalability of the storage where virtual machines can attach more device (more LUNs etc…))
要讓虛機使用該接口,需要設置 Glance image 的屬性:
$ glance image-update --property hw_scsi_model=virtio-scsi --property hw_disk_bus=scsi
(2)在 nova.conf 中配置 hw_disk_discard = unmap
注意目前 cinder 尚不支持 discard。
3.2 Nova 中實現的 RBD image 操作
Nova 在 \nova\virt\libvirt\imagebackend.py 文件中添加了支持 RBD 的新類 class Rbd(Image) 來支持將虛機的image 放在 RBD 中。其主要方法包括:
def create_image(self, prepare_template, base, size, *args, **kwargs): # 調用 'rbd', 'import' 命令將 image file 的數據保存到 rbd image 中
import [–image-format format-id] [–order bits] [–stripe-unit size-in-B/K/M –stripe-count num] [–image-feature feature-name]... [–image-shared] src-path[image-spec] Creates a new image and imports its data from path (use - for stdin). The import operation will try to create sparse rbd images if possible. For import from stdin, the sparsification unit is the data block size of the destination image (1 << order). The –stripe-unit and –stripe-count arguments are optional, but must be used together.
虛機創建成功后,在 RBD 中查看創建出來的 image:
root@ceph1:~# rbd ls vms 74cbdb41-3789-4eae-b22e-5085de8caba8_disk.local root@ceph1:~# rbd info vms/74cbdb41-3789-4eae-b22e-5085de8caba8_disk.local rbd image '74cbdb41-3789-4eae-b22e-5085de8caba8_disk.local': size 1024 MB in 256 objects order 22 (4096 kB objects) block_name_prefix: rbd_data.11552ae8944a format: 2 features: layering
查看虛機的 xml 定義文件,能看到虛機的系統盤、臨時盤和交換盤的鏡像文件都在 RBD 中,而且可以使用特定的 cache 模式(由 CONF.libvirt.disk_cachemodes 配置項指定)和 discard 模式(由 CONF.libvirt.hw_disk_discard 配置項設置):
<devices> <disk type="network" device="disk"> <driver type="raw" cache="writeback" discard="unmap"/> <source protocol="rbd" name="vms/74cbdb41-3789-4eae-b22e-5085de8caba8_disk.local"> <host name="9.115.251.194" port="6789"/> <host name="9.115.251.195" port="6789"/> <host name="9.115.251.218" port="6789"/> </source> <auth username="cinder"> <secret type="ceph" uuid="e21a123a-31f8-425a-86db-7204c33a6161"/> </auth> <target bus="virtio" dev="vdb"/> </disk> <disk type="network" device="disk"> <driver name="qemu" type="raw" cache="writeback"/> <source protocol="rbd" name="volumes/volume-6868a043-1412-4f6c-917f-bbffb1a8d21a"> <host name="9.115.251.194" port="6789"/> <host name="9.115.251.195" port="6789"/> <host name="9.115.251.218" port="6789"/> </source> <auth username="cinder"> <secret type="ceph" uuid="e21a123a-31f8-425a-86db-7204c33a6161"/> </auth> <target bus="virtio" dev="vda"/> <serial>6868a043-1412-4f6c-917f-bbffb1a8d21a</serial> </disk>
關於 libvirt.disk_cachemodes 配置項,可以指定鏡像文件的緩存模式,其值的格式為 ”A=B",其中:
- A 可以為 'file','block','network','mount'。其中,file 是針對 file-backend 的disk(比如使用 qcow2 格式的鏡像文件),block 是針對塊設備的disk(比如使用 cinder volume 或者 lvm),network 是針對通過網絡連接的設備的disk(比如 Ceph)。
- B 可以為 none,writethrough,writeback,directsync,unsafe。writethrough 和 writeback 可以參考 理解 OpenStack + Ceph (2):Ceph 的物理和邏輯結構,其它的請自行google。
- 比如,disk_cachemodes="network=writeback",disk_cachemodes="block=writeback"。
上面的虛機 XML 定義文件是在Nova 配置為 disk_cachemodes="network=writeback" 和 hw_disk_discard = unmap 的情形下生成的。
(2)image clone API
def clone(self, context, image_id_or_uri): 1. 通過 Glance API 獲取 image 的 rbd location 2. 檢查 rbd image 是否可以被克隆(檢查它是不是在本 ceph cluster 內、是不是 raw 格式、是不是可以訪問等) 3. 調用 rbd 的 clone 方法來創建 clone
3.3 鏡像 clone 而非下載
使用傳統存儲作為 image 的后端存儲時,在創建虛機的過程中的創建鏡像文件時,都是調用 _try_fetch_image_cache 方法來從 Glance 中將鏡像文件下載到本地(第一次會緩存,以后就直接讀緩存而不用下載),然后再創建鏡像文件的方法。而在使用 RBD 作為鏡像的后端存儲時,如果 Glance 鏡像文件被保存在 RBD 中,那么該過程將是重復的(先通過 Glance 從 RDB 中倒出鏡像,然后再由 Nova 放到RBD中),而且是非常耗時的。針對這種情況,Nova 實現了一種新的辦法,具體見下面的藍色字體部分:
def _create_image(self, context, instance, disk_mapping, suffix='', disk_images=None, network_info=None, block_device_info=None, files=None, admin_pass=None, inject_files=True, fallback_from_host=None): if not booted_from_volume: #如果不是從 volume 啟動虛機 root_fname = imagecache.get_cache_fname(disk_images, 'image_id') size = instance.root_gb * units.Gi backend = image('disk') if backend.SUPPORTS_CLONE: #如果 image backend 支持 clone 的話(目前的各種 image backend,只有 RBD 支持 clone) def clone_fallback_to_fetch(*args, **kwargs): backend.clone(context, disk_images['image_id']) #直接調用 backend 的 clone 函數做 image clone fetch_func = clone_fallback_to_fetch else: fetch_func = libvirt_utils.fetch_image #否則走常規的 image 下載-導入過程 self._try_fetch_image_cache(backend, fetch_func, context, root_fname, disk_images['image_id'], instance, size, fallback_from_host)
可見,當 nova 后端使用 ceph 時,nova driver 調用 RBD imagebackend 命令,直接在 ceph 存儲層完成鏡像拷貝動作(無需消耗太多的nova性能,也無需將鏡像下載到hypervisor本地,再上傳鏡像到ceph),如此創建虛擬機時間將會大大提升。當然,這個的前提是 image 也是保存在 ceph 中,而且 image 的格式為 raw,否則 clone 過程會報錯。 具體過程如下:
(1)這是 Glance image 對應的 rbd image:
root@ceph1:~# rbd info images/0a64fa67-3e34-42e7-b7b0-423c11850e18 rbd image '0a64fa67-3e34-42e7-b7b0-423c11850e18': size 564 MB in 71 objects order 23 (8192 kB objects) block_name_prefix: rbd_data.16d21e1d755b format: 2 features: layering
(2)使用該 image 創建第一個虛機
root@ceph1:~# rbd info vms/982b8eac-6bcc-4a21-bd04-b67e26188be0_disk rbd image '982b8eac-6bcc-4a21-bd04-b67e26188be0_disk': size 3072 MB in 384 objects order 23 (8192 kB objects) block_name_prefix: rbd_data.130a36a6b435 format: 2 features: layering parent: images/0a64fa67-3e34-42e7-b7b0-423c11850e18@snap overlap: 564 MB root@ceph1:~# rbd info vms/982b8eac-6bcc-4a21-bd04-b67e26188be0_disk.local rbd image '982b8eac-6bcc-4a21-bd04-b67e26188be0_disk.local': size 2048 MB in 512 objects order 22 (4096 kB objects) block_name_prefix: rbd_data.a69b2ae8944a format: 2 features: layering root@ceph1:~# rbd info vms/982b8eac-6bcc-4a21-bd04-b67e26188be0_disk.swap rbd image '982b8eac-6bcc-4a21-bd04-b67e26188be0_disk.swap': size 102400 kB in 25 objects order 22 (4096 kB objects) block_name_prefix: rbd_data.a69e74b0dc51 format: 2 features: layering
(3)創建第二個虛機
root@ceph1:~# rbd info vms/a9670d9a-8aa7-49ba-baf5-9d7a450172f3_disk rbd image 'a9670d9a-8aa7-49ba-baf5-9d7a450172f3_disk': size 3072 MB in 384 objects order 23 (8192 kB objects) block_name_prefix: rbd_data.13611f6abac6 format: 2 features: layering parent: images/0a64fa67-3e34-42e7-b7b0-423c11850e18@snap overlap: 564 MB
(4)會看到 Glance image 對應的 RBD image 有兩個克隆,分別是上面虛機的系統盤
root@ceph1:~# rbd children images/0a64fa67-3e34-42e7-b7b0-423c11850e18@snap vms/982b8eac-6bcc-4a21-bd04-b67e26188be0_disk vms/a9670d9a-8aa7-49ba-baf5-9d7a450172f3_disk
4. 其它集成
除了上面所描述的 Cinder、Nova 和 Glance 與 Ceph RBD 的集成外,OpenStack 和 Ceph 之間還有其它的集成點:
(1)使用 Ceph 替代 Swift 作為對象存儲 (網絡上有很多比較 Ceph 和 Swift 的文章,比如 1,2,3,)
(2)CephFS 作為 Manila 的后端(backend)
(3)Keystone 和 Ceph Object Gateway 的集成,具體可以參考文章 (1)(2)
參考文檔:
http://www.sebastien-han.fr/blog/2015/02/02/openstack-and-ceph-rbd-discard/