1. 程式人生 > >ceph(4)--Ceph 的基礎數據結構

ceph(4)--Ceph 的基礎數據結構

The seq openstack hit 設備 字符串 占用 ado alt

本系列文章會深入研究 Ceph 以及 Ceph 和 OpenStack 的集成:

(1)安裝和部署

(2)Ceph RBD 接口和工具

(3)Ceph 物理和邏輯結構

(4)Ceph 的基礎數據結構

(5)Ceph 與 OpenStack 集成的實現

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

(7)Ceph 的基本操作和常見故障排除方法

(8)關於Ceph PGs

1 Pool(池)

Pool 的概念前面講過了,Ceph 支持豐富的對 Pool 的操作,主要的包括:

技術分享圖片
列表、創建和刪除 pool
ceph osd pool create {pool-name} {pg-num} [{pgp-num}] [replicated] [crush-ruleset-name]
ceph osd pool create {pool-name} {pg-num} {pgp-num} erasure [erasure-code-profile] [crush-ruleset-name]
ceph osd pool delete {pool-name} [{pool-name} --yes-i-really-really-mean-it]
QoS 支持:
ceph osd pool set-quota {pool-name} [max_objects {obj-count}] [max_bytes {bytes}]
快照創建和刪除:
ceph osd pool mksnap {pool-name} {snap-name}
ceph osd pool rmsnap {pool-name} {snap-name}
元數據修改
ceph osd pool set {pool-name} {key} {value}
設置對象拷貝份數(註意該份數包括對象自身)
ceph osd pool set {poolname} size {num-replicas}
在 degraded 模式下的拷貝份數
ceph osd pool set data min_size 2
技術分享圖片

2 卷(image)

2.1 image 之用戶所見

Image 對應於 LVM 的 Logical Volume,它將被條帶化為 N 個子數據塊,每個數據塊將會被以對象(object)形式保存在 RADOS 對象存儲中的簡單塊設備(simple block devicees)。比如:

技術分享圖片
#創建 100 MB 大小的名字為 ‘myimage’ 的 RBD Image,默認情況下,它被條帶化為 4MB 大小的 25 個對象 (註意 rdb create 命令的 size 參數的單位為 MB)
rbd create mypool/myimage --size 100

#同樣是 100MB 大小的 RBD Image,但是它被被條帶化為 8MB 大小的13 個對象
rbd create mypool/myimage --size 100 --order 23

#將 image mount 到linux 主機稱為一個 deivce /dev/rbd1
rbd map mypool/myimage

#向 /dev/rbd1 寫入數據
dd if=/dev/zero of=/dev/rbd1 bs=1047586 count=4 #刪除 image rbd rm mypool/myimage
技術分享圖片

2.2 image 之 ceph 系統所見

接下來我們來看看 image 的一些內部信息。

(1)創建新的對象

首先在一個空的 pool 中創建一個 100 GB 的 image

root@ceph1:~# rbd create -p pool100 image1 --size 102400 --image-format 2
root@ceph1:~# rbd list pool100
image1

這時候在 pool 中看到多了一些對象:

root@ceph1:~# rados -p pool100 ls
rbd_directory
rbd_id.image1
rbd_header.a89c2ae8944a

從名字也能看出來,這些 object 存放的不是 image 的數據,而是 ID,header 之類的元數據信息。其中,rbd_directory 中保存了pool內所有image的 ID 和 name 信息:

技術分享圖片
root@ceph1:~# rados -p pool100 listomapvals rbd_directory
id_a89c2ae8944a
value: (10 bytes) :
0000 : 06 00 00 00 69 6d 61 67 65 31                   : ....image1

name_image1
value: (16 bytes) :
0000 : 0c 00 00 00 61 38 39 63 32 61 65 38 39 34 34 61 : ....a89c2ae8944a
技術分享圖片

而 rbd_header 保存的是一個 RBD 鏡像的元數據:

技術分享圖片
root@ceph1:~# rados -p pool100 listomapvals rbd_header.a89c2ae8944a
features
value: (8 bytes) :
0000 : 01 00 00 00 00 00 00 00                         : ........

object_prefix
value: (25 bytes) :
0000 : 15 00 00 00 72 62 64 5f 64 61 74 61 2e 61 38 39 : ....rbd_data.a89
0010 : 63 32 61 65 38 39 34 34 61                      : c2ae8944a

order
value: (1 bytes) :
0000 : 16                                              : .

size
value: (8 bytes) :
0000 : 00 00 00 00 19 00 00 00                         : ........

snap_seq
value: (8 bytes) :
0000 : 00 00 00 00 00 00 00 00                         : ........
技術分享圖片

這些信息正是下面命令的信息來源:

技術分享圖片
root@ceph1:~# rbd -p pool100 info image1
rbd image ‘image1‘:
    size 102400 MB in 25600 objects
    order 22 (4096 kB objects)
    block_name_prefix: rbd_data.a89c2ae8944a
    format: 2
    features: layering
技術分享圖片

同時還能看出來,該 image 的數據對象的名稱前綴是 rbd_header.a89c2ae8944a。而對於一個新建的 image,因為沒有數據對象,其實際占用的存儲空間只是元數據對象所占的非常小的空間。

(2)向該對象中寫入數據 8MB 的數據(該 pool 中一個 object 是 4MB)

技術分享圖片
root@ceph1:~# rbd map pool100/image1
root@ceph1:~# rbd showmapped
id pool    image  snap device    
1  pool100 image1 -    /dev/rbd1 
root@ceph1:~# dd if=/dev/zero of=/dev/rbd1 bs=1048576 count=8 8+0 records in 8+0 records out 8388608 bytes (8.4 MB) copied, 0.316369 s, 26.5 MB/s
技術分享圖片

在來看看 pool 中的對象:

root@ceph1:~# rados -p pool100 ls
rbd_directory
rbd_id.image1
rbd_data.a89c2ae8944a.0000000000000000
rbd_data.a89c2ae8944a.0000000000000001
rbd_header.a89c2ae8944a

可以看出來多了 2 個 4MB 的 object。繼續看第一個對象所在的 OSD:

root@ceph1:~# ceph osd map pool100 rbd_data.a89c2ae8944a.0000000000000000
osdmap e81 pool ‘pool100‘ (7) object ‘rbd_data.a89c2ae8944a.0000000000000000‘ -> pg 7.df059252 (7.52) -> up ([8,6,7], p8) acting ([8,6,7], p8)

PG 的 ID 是 7.52,主 OSD 是 8,從 OSD 是 6 和 7。從 OSD 樹中可以獲知 OSD 8 所在的節點為 ceph3:

root@ceph3:/data/osd2/current/7.52_head# ceph osd tree
# id    weight    type name    up/down    reweight
-1    0.1399    root default    
-4    0.03998        host ceph3
5    0.01999            osd.5    up    1    
8    0.01999            osd.8    up    1

登錄 ceph3,查看 /var/lib/ceph/osd 目錄,能看到 ceph-8 目錄:

root@ceph3:/var/lib/ceph/osd# ls -l
total 0
lrwxrwxrwx 1 root root  9 Sep 18 02:59 ceph-5 -> /data/osd
lrwxrwxrwx 1 root root 10 Sep 18 08:22 ceph-8 -> /data/osd2

查看 7.52 開頭的目錄,可以看到兩個數據文件:

root@ceph3:/data/osd2/current# find . -name ‘*a89c2ae8944a*‘
./7.5c_head/rbd\uheader.a89c2ae8944a__head_36B2DADC__7
./7.52_head/rbd\udata.a89c2ae8944a.0000000000000001__head_9C6139D2__7
./7.52_head/rbd\udata.a89c2ae8944a.0000000000000000__head_DF059252__7

可見:

(1)RBD image 是簡單的塊設備,可以直接被 mount 到主機,成為一個 device,用戶可以直接寫入二進制數據。

(2)image 的數據被保存為若幹在 RADOS 對象存儲中的對象。

(3)image 的數據空間是 thin provision 的,意味著ceph 不預分配空間,而是等到實際寫入數據時按照 object 分配空間。

(4)每個 data object 被保存為多份。

(5)pool 將 RBD 鏡像的ID和name等基本信息保存在 rbd_directory 中,這樣,rbd ls 命令就可以快速返回一個pool中所有的 RBD 鏡像了。

(6)每個 RBD 鏡像的元數據將保存在一個對象中,命名為 rbd_header.<image id>。

(7)RBD 鏡像保存在多個對象中,這些對象的命名為 rbd_data.<image id>.<順序編號序列>。

(8)RADOS 對象以 OSD 文件系統上的文件形式被保存,其文件名為 udata<image id>.<順序編號序列>.<其它字符串>。

3 快照 (snapshot)

3.1 snapshot 之用戶所見

RBD image 的快照(snapshot)是該 image 在特定時刻的狀態的一份只讀拷貝 (A snapshot is a read-only copy of the state of an image at a particular point in time.)。需要註意的是,在做 snapshot 之前,需要停止 I/O;如果 image 中包含文件系統,系統確保文件系統是處於連續狀態。

用戶可以使用 rbd 工具或者其它 API 操作 snapshot:

技術分享圖片
rbd create -p pool101 --size 102400 image1 --format 2 #創建 image
rbd snap create pool101/image1@snap1 #創建snapshot rbd snap ls pool101/image1 #列表 rbd snap protect pool101/image1@snap1 #保護 rbd snap unprotect pool101/image1@snap1 #去保護 rbd snap rollback pool101/image1@snap1 #回滾snapshot 到 image。註意這是個耗時操作,rbd 會顯示進度條 rbd snap rm pool101/image1@snap1 #刪除
rbd snap purge pool101/image1 #刪除 image 的所有snapshot
rbd clone pool101/image1@snap1 image1snap1clone1 #創建 clone
rbd children pool101/image1@snap1 #列表它的所有 clone
技術分享圖片

3.2 snapshot 之 Ceph 系統所見

我們也來看看 snapshot 的內部原理。

(1)創建 image1,寫入 4MB 的數據,然後創建一個 snapshot: rbd snap create pool100/image1@snap1

(2)Ceph 在該 pool 中沒有創建新的的對象,也就是說這時候並沒有分配存儲空間來給 snap1 創建 data objects。

技術分享圖片

root@ceph1:~# rbd map pool101/image1
root@ceph1:~# rbd showmapped
id pool image snap device
1 pool100 image1 - /dev/rbd1
2 pool101 image1 - /dev/rbd2

root@ceph1:~# dd if=/dev/sda1 of=/dev/rbd2 bs=1048576 count=4
4+0 records in
4+0 records out
4194304 bytes (4.2 MB) copied, 0.123617 s, 33.9 MB/s
root@ceph1:~# rados -p pool101 ls
rbd_directory
rb.0.fc9d.238e1f29.000000000000
image1.rbd

root@ceph1:~# rbd snap create pool101/image1@snap1

root@ceph1:~# rbd snap ls pool101/image1
SNAPID NAME SIZE
10 snap1 102400 MB

root@ceph1:~# rados -p pool101 ls
rbd_directory
rb.0.fc9d.238e1f29.000000000000
image1.rbd

技術分享圖片

(3)Ceph 而是將 snapshot 的信息增加到了 rbd_header.{image_id} 對象中

技術分享圖片

root@ceph1:~# rados -p pool101 listomapvals rbd_header.a9262ae8944a
snapshot_0000000000000006

value: (74 bytes) :
0000 : 03 01 44 00 00 00 06 00 00 00 00 00 00 00 05 00 : ..D.............
0010 : 00 00 73 6e 61 70 31 00 00 00 00 19 00 00 00 01 : ..snap1.........
0020 : 00 00 00 00 00 00 00 01 01 1c 00 00 00 ff ff ff : ................
0030 : ff ff ff ff ff 00 00 00 00 fe ff ff ff ff ff ff : ................
0040 : ff 00 00 00 00 00 00 00 00 00 : ..........

技術分享圖片

(4)再向 image1 中寫入 4MB 數據(其實是覆蓋第一個 object 中的數據),發現數據目錄中多了一個 4MB 的文件:

root@ceph3:/data/osd2/current/8.2c_head# ls /data/osd/current/8.3e_head/ -l
total 8200
-rw-r--r-- 1 root root 4194304 Sep 28 03:25 rb.0.fc9d.238e1f29.000000000000__a_AE14D5BE__8
-rw-r--r-- 1 root root 4194304 Sep 28 03:25 rb.0.fc9d.238e1f29.000000000000__head_AE14D5BE__8

可見 Ceph 使用 COW (copy on write)方式實現 snapshot:在寫入object 之前,將其拷貝出來,作為 snapshot 的 data object,然後繼續修改 object 中的數據。

(5)再執行命令 dd if=/dev/sda1 of=/dev/rdb1 bs=1048576 seek=4 count=4 向 image 寫入 [4MB,8MB)的數據。該操作創建了第二個 data object。因為這是在做 snapshot 之後創建的,所有它和 snapshot 沒有關系。

(6)再創建一個snapshot,然後修改第二個 data object,這時候第二個 data object 所在的文件夾中多出了 snapshot 的一個 data object 文件:

技術分享圖片
root@ceph3:/data/osd2/current/8.2c_head# ls /data/osd/current/8.a_head/ -l
total 4100
-rw-r--r-- 1 root root 4194304 Sep 28 03:31 rb.0.fc9d.238e1f29.000000000001__head_9C84738A__8
root@ceph3:/data/osd2/current/8.2c_head# ls /data/osd/current/8.a_head/ -l
total 8200
-rw-r--r-- 1 root root 4194304 Sep 28 03:35 rb.0.fc9d.238e1f29.000000000001__b_9C84738A__8
-rw-r--r-- 1 root root 4194304 Sep 28 03:35 rb.0.fc9d.238e1f29.000000000001__head_9C84738A__8
技術分享圖片

因此,

(1)snapshot 的 data objects 是和 image 的 data objects 保存在同一個目錄中。

(2)snapshot 的粒度不是整個 image,而是RADOS 中的 data object。

(3)當 snapshot 創建時,只是在 image 的元數據對象中增加少量字節的元數據;當 image 的 data objects 被修改(write)時,變修改的 objects 會被拷貝(copy)出來,作為 snapshot 的 data objects。這就是 COW 的含義。

技術分享圖片

4 克隆(clone)

創建 Clone 是將 image 的某一個 Snapshot 的狀態復制變成一個 image。如 imageA 有一個 Snapshot-1,clone 是根據 ImageA 的 Snapshot-1 克隆得到 imageB。imageB 此時的狀態與Snapshot-1完全一致,並且擁有 image 的相應能力,其區別在於 ImageB 此時可寫。

4.1 Clone 之 用戶所見

從用戶角度來看,一個 clone 和別的 RBD image 完全一樣。你可以對它做 snapshot、讀/寫、改變大小 等等,總之從用戶角度來說沒什麽限制。同時,創建速度很快,這是因為 Ceph 只允許從 snapshot 創建 clone,而 snapshot 一直是只讀的。

技術分享圖片
rbd clone pool101/image1@snap1 image1snap1clon3
root@ceph1:~# rbd info image1snap1clon3
rbd image ‘image1snap1clon3‘:
        size 102400 MB in 25600 objects
        order 22 (4096 kB objects)
        block_name_prefix: rbd_data.a8f63d1b58ba
        format: 2
        features: layering
        parent: pool101/image1@snap1
        overlap: 102400 MB
技術分享圖片

4.2 Clone 之 Ceph 系統所見

從系統角度,clone 也是使用 COW 技術,現在來通過下面的步驟具體了解一下:

(1)創建一個 clone (創建之前,需要 protect snapshot)。你會發現 RADOS 中多了三個object:

技術分享圖片

root@ceph1:~# rbd clone pool101/image1@snap1 pool101/image1snap1clone1
root@ceph1:~# rbd ls -p pool101
image1
image1snap1clone1
root@ceph1:~# rados -p pool101 ls
rbd_header.89903d1b58ba
rbd_directory
rbd_id.image1snap1clone1
rbd_id.image1
rbd_children
rbd_header.a9532ae8944a
rbd_data.a9532ae8944a.0000000000000000

技術分享圖片

其中,rbd_children 記錄了父子關系:

技術分享圖片

root@ceph1:~# rados -p pool101 listomapvals rbd_children
key: (32 bytes):
0000 : 08 00 00 00 00 00 00 00 0c 00 00 00 61 39 35 33 : ............a953
0010 : 32 61 65 38 39 34 34 61 0e 00 00 00 00 00 00 00 : 2ae8944a........

value: (20 bytes) :
0000 : 01 00 00 00 0c 00 00 00 38 39 39 30 33 64 31 62 : ........89903d1b
0010 : 35 38 62 61 : 58ba

技術分享圖片

相比 rbd_header.a9532ae8944a,rbd_header.89903d1b58ba 只是多了 partent 信息:

parent
value: (46 bytes) :
0000 : 01 01 28 00 00 00 08 00 00 00 00 00 00 00 0c 00 : ..(.............
0010 : 00 00 61 39 35 33 32 61 65 38 39 34 34 61 0e 00 : ..a9532ae8944a..
0020 : 00 00 00 00 00 00 00 00 00 00 19 00 00 00 : ..............

這裏父子關系和 rbd children 結果的來源:

root@ceph1:~# rbd children pool101/image1@snap1
pool101/image1snap1clone1

可見,Clone 也是對 snapshot 使用 COW 方式實現的。

技術分享圖片

(2)從 clone 讀數據

從本質上是 clone 的 RBD image 中讀數據,對於不是它自己的 data objects,ceph 會從它的 parent snapshot 上讀,如果它也沒有,繼續找它的parent image,直到一個 data object 存在。從這個過程也看得出來,該過程是缺乏效率的。

(3)向 clone 中的 object 寫數據

Ceph 會首先檢查該 clone image 上的 data object 是否存在。如果不存在,則從 parent snapshot 或者 image 上拷貝該 data object,然後執行數據寫入操作。這時候,clone 就有自己的 data object 了。

root@ceph2:/data/osd3/current/8.32_head# ls -l
total 4100
-rw-r--r-- 1 root root 4194304 Sep 28 05:14 rbd\udata.89903d1b58ba.0000000000000000__head_CEDDC1B2__8

這是增加了 clone 後的各對象之間的關系:

技術分享圖片

4.3 Flatten clone

從上面的分析我們知道,克隆操作本質上復制了一個 metadata object,而 data objects 是不存在的。因此在每次讀操作時會先向本卷可能的 data object 訪問。在返回對象不存在錯誤後會向父卷訪問對應的對象最終決定這塊數據是否存在。因此當存在多個層級的克隆鏈後,讀操作需要更多的損耗去讀上級卷的 data objects。只有當本卷的 data object 存在後(也就是寫操作後),才不需要訪問上級卷。

為了防止父子層數過多,Ceph 提供了 flattern 函數將 clone 與 parent snapshot 共享的 data objects 復制到 clone,並刪除父子關系。

rbd 工具的 flatten 方法:

rbd flatten <image-name>: fill clone (image-name) with data of parent (make it independent)
如果一個 image 是個 clone,從它的 parent snapshot 拷貝所有共享的 blocks (data objects),刪除與其 parent 的依賴關系。此時,其 parent snapshot 可以被去保護(unprotected),如果沒有其它的 clone 的話,它是允許被刪除的。 該功能要求 image 為 format 2 格式。

註意這是一個非常耗時的操作。Flatten 之後,clone 與原來的父 snapshot 之間也不再有關系了,真正成為一個獨立的 image:

技術分享圖片
root@ceph1:~# rbd flatten image1snap1clon2
Image flatten: 100% complete...done.
root@ceph1:~# rbd info image1snap1clon2
rbd image ‘image1snap1clon2‘:
        size 102400 MB in 25600 objects
        order 22 (4096 kB objects)
        block_name_prefix: rbd_data.fb173d1b58ba
        format: 2
        features: layering
技術分享圖片

令人奇怪的是,該操作在parent image 和 clone 產生的 image 的目錄中產生了大量空的文件:

技術分享圖片
root@ceph1:/data/osd/current# ls /data/osd/current/8.7f_head/ -l
total 788
-rw-r--r-- 1 root root 0 Sep 28 05:33 rbd\udata.89903d1b58ba.000000000000014f__head_17C7607F__8
-rw-r--r-- 1 root root 0 Sep 28 05:33 rbd\udata.89903d1b58ba.0000000000000232__head_96D143FF__8
-rw-r--r-- 1 root root 0 Sep 28 05:33 rbd\udata.89903d1b58ba.0000000000000399__head_4D4E557F__8
-rw-r--r-- 1 root root 0 Sep 28 05:33 rbd\udata.89903d1b58ba.00000000000003ae__head_CE165DFF__8
-rw-r--r-- 1 root root 0 Sep 28 05:33 rbd\udata.89903d1b58ba.00000000000003e1__head_42EA8A7F__8
-rw-r--r-- 1 root root 0 Sep 28 05:33 rbd\udata.89903d1b58ba.0000000000000445__head_701607FF__8
技術分享圖片

判斷父子關系層數,達到一定數目後 flatten clone,在刪除 snapshot 的偽代碼:

img = self.rbd.Image(client.ioctx, img_name) #根據輸入的 img_name 獲得其 RBD Image 對象
_pool, parent, snap = self._get_clone_info(img_name) #當 name 為 img_name 的 RBD image 是個 clone 時,通過遞歸,獲取它的 parent image 和 parent snapshot img.flatten() # 將 parent snapshot 的 data 拷貝到該 clone 中 parent_volume = self.rbd.Image(client.ioctx, parent) #獲取 parent image parent_volume.unprotect_snap(snap) #將 snap 去保護 parent_volume.remove_snap(snap) #如果snapshot 沒有其它的 clone,將其刪除

5. 小結

RBD image 的 head,object,snapshot 以及 與 client 和 parent 之間的關系:

技術分享圖片

參考鏈接:

http://www.wzxue.com/ceph-librbd-block-library/

http://docs.ceph.com/docs/master/architecture/

https://www.ustack.com/blog/ceph_infra/

https://hustcat.github.io/rbd-image-internal-in-ceph/

http://www.wzxue.com/category/ceph-2/

ceph(4)--Ceph 的基礎數據結構