基本原理
整體概念:
官方文檔:CEPH BLOCK DEVICE
rbd總體架構(gòu)和原理:《ceph設(shè)計(jì)原理與實(shí)現(xiàn)》第六章
rbd快照和克隆補(bǔ)充:《ceph源碼分析》第九章
其他一些不錯(cuò)的資料:
ceph rbd快照原理解析
理解 OpenStack + Ceph (3):Ceph RBD 接口和工具 [Ceph RBD API and Tools]
理解 OpenStack + Ceph (4):Ceph 的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu) [Pool, Image, Snapshot, Clone]
對(duì)外接口
rbd使用方法有兩種:
- 通過(guò)librbd,通過(guò)函數(shù)接口來(lái)操作曙搬。
- 通過(guò)kernel module彼乌,走kernel 的路徑,使用時(shí)類(lèi)似于普通塊設(shè)備,可以mkfs演训、mount。
kernel module的方式可以參考相關(guān)文檔膝擂。
目前應(yīng)用比較廣泛的是librbd的方式,接入openstack隙弛、iscsi等都是使用的這種方式架馋。下面對(duì)librbd進(jìn)行介紹。
librbd
librbd大部分操作通過(guò)librados來(lái)實(shí)現(xiàn)全闷,部分元數(shù)據(jù)相關(guān)的操作通過(guò)cls模塊直接注冊(cè)在osd上叉寂。
librbd使用方法
作為一個(gè)對(duì)外接口庫(kù),librbd默認(rèn)支持c和cpp总珠,其對(duì)應(yīng)的頭文件在src/include/rbd下屏鳍,針對(duì)c和cpp分別是librdb.h和librbd.hpp勘纯。具體的實(shí)現(xiàn)在src/rbd目錄下。cls相關(guān)的代碼在cls/rbd目錄下钓瞭,具體見(jiàn)附錄A驳遵。
src/include/rbd/librbd.hpp中最主要的兩個(gè)類(lèi)是class CEPH_RBD_API RBD
和class CEPH_RBD_API Image
。RBD 主要負(fù)責(zé)創(chuàng)建降淮、刪除超埋、克隆鏡像等操作, 而Image 類(lèi)負(fù)責(zé)鏡像的讀寫(xiě),以及快照相關(guān)的操作等等佳鳖。
要使用librbd,需要先安裝下面兩個(gè)包媒惕∠捣裕可以通過(guò)yum安裝,也可以通過(guò)下載ceph源碼編譯后妒蔚,通過(guò)make install
進(jìn)行安裝穿挨。
$ yum list | grep librbd
librbd1.x86_64 1:0.80.7-3.el7 base
librbd1-devel.x86_64 1:0.80.7-3.el7 base
至于如何使用librbd來(lái)編程,請(qǐng)參考下面的代碼肴盏,這是使用librbd的一般流程科盛。
編譯時(shí)記得加上鏈接參數(shù):g++ librbdtest.cpp -lrados -lrbd
。
更多函數(shù)的使用請(qǐng)參考 librbd.hpp菜皂。另外 這里 有一些不錯(cuò)的示例贞绵。
#include <rbd/librbd.hpp>
#include <rados/librados.hpp>
#include <cstring>
#include <iostream>
#include <string>
void err_msg(int ret, const std::string &msg = ""){
std::cerr<< "[error] msg:" << msg << " strerror: " << strerror(-ret) << std::endl;
}
void err_exit(int ret, const std::string &msg = ""){
err_msg(ret, msg);
exit(EXIT_FAILURE);
}
int main(int argc, char* argv[]) {
int ret = 0;
// rados
librados::Rados rados;
// use client.admin keyring
ret = rados.init("admin");
if (ret < 0)
err_exit(ret,"failed to initialize rados");
// read ceph.conf
ret = rados.conf_read_file("/path/to/ceph.conf");
if (ret < 0)
err_exit(ret, "failed to parse ceph.conf");
// connect to cluster
ret = rados.connect();
if (ret < 0)
err_exit(ret, "failed to connect to rados cluster");
std::string pool_name = "rbd";
librados::IoCtx io_ctx;
ret = rados.ioctx_create(pool_name.c_str(), io_ctx);
if (ret < 0) {
rados.shutdown();
err_exit(ret, "failed to create ioctx");
}
// rbd
librbd::RBD rbd;
std::string image_name = "image1";
librbd::Image image;
// open image
ret = rbd.open(io_ctx, image, image_name.c_str());
if (ret < 0) {
io_ctx.close();
rados.shutdown();
err_exit(ret, "failed to open rbd image");
}
// now, you can operate image
// check the image info
librbd::image_info_t info;
ret = image.stat(info, sizeof info);
if (ret < 0) {
err_msg(ret, "get image stat failed");
} else {
std::cout << "info.size:" << info.size << std::endl;
std::cout << "info.obj_size:" << info.obj_size << std::endl;
std::cout << "info.num_objs:" << info.num_objs << std::endl;
std::cout << "info.order:" << info.order << std::endl;
std::cout << "info.block_name_prefix:" << info.block_name_prefix << std::endl;
}
done:
image.close();
io_ctx.close();
rados.shutdown();
exit(EXIT_SUCCESS);
}
librbd代碼小窺
這里只是librbd最簡(jiǎn)單的代碼流程,只是為了告知你各個(gè)功能的實(shí)現(xiàn)函數(shù)在哪恍飘,至于執(zhí)行這個(gè)功能所使用的異步機(jī)制等過(guò)程沒(méi)有提及榨崩。
讓我們通過(guò)一些函數(shù)來(lái)看一下librbd的代碼流程。rbd是基于rados實(shí)現(xiàn)的章母,但rados中并沒(méi)有rbd的邏輯母蛛,可以說(shuō),librbd就是rbd的完整實(shí)現(xiàn)乳怎,rados只是作為存儲(chǔ)彩郊。
另外,在librbd中蚪缀,使用了一種獨(dú)特的代碼風(fēng)格秫逝。操作對(duì)外提供的api在class RBD和class Image中,但這些函數(shù)的實(shí)現(xiàn)僅僅是一些參數(shù)的解析和傳遞椿胯,其最終的功能實(shí)現(xiàn)筷登,都會(huì)封裝到一個(gè)名為<operation>Request
的類(lèi)中,這個(gè)類(lèi)往往包含完成該操作所必須的數(shù)據(jù)和功能哩盲。在類(lèi)的注釋中還會(huì)包含該操作執(zhí)行邏輯的流程圖前方,或者狀態(tài)圖狈醉。
所以當(dāng)你需要尋找某個(gè)功能的實(shí)現(xiàn)代碼時(shí),直接尋找以這個(gè)功能命名的<operation>Request
類(lèi)吧惠险。
以RBD::create為例:
1) 首先在librbd.hpp/librbd.cc中苗傅,定義了對(duì)外的create函數(shù)接口。其函數(shù)實(shí)現(xiàn)班巩,簡(jiǎn)單調(diào)用了internal.h/internal.cc中定義的librbd::create函數(shù)渣慕。
2)librbd::create函數(shù)做的主要工作就是準(zhǔn)備create需要的各種參數(shù),準(zhǔn)備create操作可能用到的工具(線程池等)抱慌,然后將這些數(shù)據(jù)封裝到image::CreateRequest
對(duì)象中逊桦。最后調(diào)用對(duì)象的send()
函數(shù)開(kāi)始執(zhí)行流程。最后通過(guò)cond.wait()
來(lái)等待操作的完成抑进。代碼如下:
int create(IoCtx& io_ctx, const std::string &image_name,
const std::string &image_id, uint64_t size,
ImageOptions& opts,
const std::string &non_primary_global_image_id,
const std::string &primary_mirror_uuid,
bool skip_mirror_enable)
{
// 此處省略代碼內(nèi)容:
// 根據(jù)參數(shù)準(zhǔn)備image id强经、order、format等屬性寺渗,沒(méi)有則設(shè)默認(rèn)值
...
if (old_format) {
// 如果是舊版的format匿情,調(diào)用format v1的create函數(shù),現(xiàn)在很少使用
r = create_v1(io_ctx, image_name.c_str(), size, order);
} else {
// 從ceph ctx中獲得全局的線程池和隊(duì)列
ThreadPool *thread_pool;
ContextWQ *op_work_queue;
ImageCtx::get_thread_pool_instance(cct, &thread_pool, &op_work_queue);
C_SaferCond cond;
// 創(chuàng)建image::CreateRequest對(duì)象信殊,
// 其實(shí)就是new 了一個(gè)image::CreateRequest對(duì)象炬称。
image::CreateRequest<> *req = image::CreateRequest<>::create(
io_ctx, image_name, id, size, opts, non_primary_global_image_id,
primary_mirror_uuid, skip_mirror_enable, op_work_queue, &cond);
req->send();
// 等待創(chuàng)建請(qǐng)求完成
r = cond.wait();
}
int r1 = opts.set(RBD_IMAGE_OPTION_ORDER, order);
assert(r1 == 0);
return r;
}
3)image::CreateRequest
對(duì)象中給出的流程圖,或者說(shuō)狀態(tài)圖涡拘。這幅圖描述了send()函數(shù)執(zhí)行后的邏輯玲躯,其中的狀態(tài)轉(zhuǎn)移是通過(guò)if判斷和函數(shù)調(diào)用來(lái)實(shí)現(xiàn)的。
Image在rados中的創(chuàng)建過(guò)程如下:
- 創(chuàng)建一個(gè)
rbd_id.<name>
對(duì)象鲸伴,映射image name到image id府蔗。 - 增加
name_<name>->image id
和id_<id>->name
的映射到rbd_directorty
對(duì)象的omap。 - 創(chuàng)建
rbd_header.<id>
對(duì)象汞窗,在其omap和xattr中記錄該image的metadata姓赤。 - 如果開(kāi)啟了object map特性,創(chuàng)建
rbd_object_map.<id>
對(duì)象仲吏,記錄該image所有data object的情況 - 數(shù)據(jù)對(duì)象不會(huì)被創(chuàng)建不铆,直到有數(shù)據(jù)寫(xiě)入
/**
* @verbatim
*
* <start> . . . . > . . . . .
* | .
* v .
* VALIDATE POOL v (pool validation
* | . disabled)
* v .
* VALIDATE OVERWRITE .
* | .
* v .
* (error: bottom up) CREATE ID OBJECT. . < . . . . .
* _______<_______ |
* | | v
* | | ADD IMAGE TO DIRECTORY
* | | / |
* | REMOVE ID OBJECT<-------/ v
* | | NEGOTIATE FEATURES (when using default features)
* | | |
* | | v (stripingv2 disabled)
* | | CREATE IMAGE. . . . > . . . .
* v | / | .
* | REMOVE FROM DIR<--------/ v .
* | | SET STRIPE UNIT COUNT .
* | | / | \ . . . . . > . . . .
* | REMOVE HEADER OBJ<------/ v /. (object-map
* | |\ OBJECT MAP RESIZE . . < . . * v disabled)
* | | \ / | \ . . . . . > . . . .
* | | *<-----------/ v /. (journaling
* | | FETCH MIRROR MODE. . < . . * v disabled)
* | | / | .
* | REMOVE OBJECT MAP<--------/ v .
* | |\ JOURNAL CREATE .
* | | \ / | .
* v | *<------------/ v .
* | | MIRROR IMAGE ENABLE .
* | | / | .
* | JOURNAL REMOVE*<-------/ | .
* | v .
* |_____________>___________________<finish> . . . . < . . . .
*
* @endverbatim
*/
C_InvokeAsyncRequest
相關(guān)的流程之后補(bǔ)充。
附錄A cls/rbd介紹
元數(shù)據(jù)相關(guān)的操作裹唆,通過(guò)cls注冊(cè)到osd上誓斥。
cls是ceph的一個(gè)模塊擴(kuò)展,用戶(hù)可以自定義對(duì)象的接口的實(shí)現(xiàn)方法许帐,通過(guò)動(dòng)態(tài)鏈接的形式加入osd中劳坑,在osd上直接執(zhí)行。在此不做展開(kāi)成畦,具體可以參考這里距芬。
這部分rbd代碼主要包含兩部分涝开,cls_rbd.h/cc
和 cls_rbd_client.h/cc
。類(lèi)似于服務(wù)端和客戶(hù)端的關(guān)系框仔,前者定義了具體在osd上執(zhí)行的函數(shù)舀武,后者在客戶(hù)端執(zhí)行,將函數(shù)參數(shù)封裝后發(fā)送給服務(wù)端(osd)离斩,然后在osd上執(zhí)行银舱。
以snapshot_add
函數(shù)為例,該函數(shù)主要負(fù)責(zé)在rbd_header
對(duì)象中增加新的snapshot元數(shù)據(jù)信息:
- 在
cls_rbd.cc
函數(shù)中跛梗,對(duì)函數(shù)進(jìn)行定義和注冊(cè)寻馏。下面的代碼注冊(cè)了rbd模塊,以及snapshot_add
函數(shù)核偿。
cls_register("rbd", &h_class);
cls_register_cxx_method(h_class, "snapshot_add",
CLS_METHOD_RD | CLS_METHOD_WR,
snapshot_add, &h_snapshot_add);
-
cls_rbd_client.h/cc
定義了通過(guò)客戶(hù)端訪問(wèn)osd注冊(cè)的cls函數(shù)的方法操软。以snapshot_add
函數(shù)為例,這個(gè)函數(shù)將參數(shù)封裝進(jìn)bufferlist宪祥,通過(guò)ioctx->exec方法,把操作發(fā)送給osd處理家乘。
void snapshot_add(librados::ObjectWriteOperation *op, snapid_t snap_id,
const std::string &snap_name, const cls::rbd::SnapshotNamespace &snap_namespace)
{
bufferlist bl;
::encode(snap_name, bl);
::encode(snap_id, bl);
::encode(cls::rbd::SnapshotNamespaceOnDisk(snap_namespace), bl);
op->exec("rbd", "snapshot_add", bl);
}
-
cls_rbd.cc
定義了方法在服務(wù)端的實(shí)現(xiàn)蝗羊,其一般流程是:從bufferlist將客戶(hù)端傳入的參數(shù)解析出來(lái),調(diào)用對(duì)應(yīng)的方法實(shí)現(xiàn)仁锯,然后將結(jié)果返回客戶(hù)端耀找。
/**
* Adds a snapshot to an rbd header. Ensures the id and name are unique.
*/
int snapshot_add(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
{
bufferlist snap_namebl, snap_idbl;
cls_rbd_snap snap_meta;
uint64_t snap_limit;
// 從bl中解析參數(shù)
try {
bufferlist::iterator iter = in->begin();
::decode(snap_meta.name, iter);
::decode(snap_meta.id, iter);
if (!iter.end()) {
::decode(snap_meta.snapshot_namespace, iter);
}
} catch (const buffer::error &err) {
return -EINVAL;
}
// 判斷參數(shù)合法性,略
......
// 完成操作业崖,在rbd_header對(duì)象中增加新的snapshot元數(shù)據(jù)野芒,并更新sanp_seq。
map<string, bufferlist> vals;
vals["snap_seq"] = snap_seqbl;
vals[snapshot_key] = snap_metabl;
r = cls_cxx_map_set_vals(hctx, &vals);
if (r < 0) {
CLS_ERR("error writing snapshot metadata: %s", cpp_strerror(r).c_str());
return r;
}
return 0;
}