ceph rbd:总览

基本原理

整体概念:
官方文档:CEPH BLOCK DEVICE
rbd总体架构和原理:《ceph设计原理与实现》第六章
rbd快照和克隆补充:《ceph源码分析》第九章

其他一些不错的资料:
ceph rbd快照原理解析
理解 OpenStack + Ceph (3):Ceph RBD 接口和工具 [Ceph RBD API and Tools]
理解 OpenStack + Ceph (4):Ceph 的基础数据结构 [Pool, Image, Snapshot, Clone]

对外接口

rbd使用方法有两种:

  1. 通过librbd,通过函数接口来操作。
  2. 通过kernel module,走kernel 的路径,使用时类似于普通块设备,可以mkfs、mount。

kernel module的方式可以参考相关文档
目前应用比较广泛的是librbd的方式,接入openstack、iscsi等都是使用的这种方式。下面对librbd进行介绍。

librbd

librbd大部分操作通过librados来实现,部分元数据相关的操作通过cls模块直接注册在osd上。

librbd使用方法

作为一个对外接口库,librbd默认支持c和cpp,其对应的头文件在src/include/rbd下,针对c和cpp分别是librdb.h和librbd.hpp。具体的实现在src/rbd目录下。cls相关的代码在cls/rbd目录下,具体见附录A。

src/include/rbd/librbd.hpp中最主要的两个类是class CEPH_RBD_API RBDclass CEPH_RBD_API Image。RBD 主要负责创建、删除、克隆镜像等操作, 而Image 类负责镜像的读写,以及快照相关的操作等等。

要使用librbd,需要先安装下面两个包。可以通过yum安装,也可以通过下载ceph源码编译后,通过make install进行安装。

$  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来编程,请参考下面的代码,这是使用librbd的一般流程。
编译时记得加上链接参数:g++ librbdtest.cpp -lrados -lrbd
更多函数的使用请参考 librbd.hpp。另外 这里 有一些不错的示例。

#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最简单的代码流程,只是为了告知你各个功能的实现函数在哪,至于执行这个功能所使用的异步机制等过程没有提及。

让我们通过一些函数来看一下librbd的代码流程。rbd是基于rados实现的,但rados中并没有rbd的逻辑,可以说,librbd就是rbd的完整实现,rados只是作为存储。

另外,在librbd中,使用了一种独特的代码风格。操作对外提供的api在class RBD和class Image中,但这些函数的实现仅仅是一些参数的解析和传递,其最终的功能实现,都会封装到一个名为<operation>Request的类中,这个类往往包含完成该操作所必须的数据和功能。在类的注释中还会包含该操作执行逻辑的流程图,或者状态图。

所以当你需要寻找某个功能的实现代码时,直接寻找以这个功能命名的<operation>Request类吧。

RBD::create为例:
1) 首先在librbd.hpp/librbd.cc中,定义了对外的create函数接口。其函数实现,简单调用了internal.h/internal.cc中定义的librbd::create函数。

2)librbd::create函数做的主要工作就是准备create需要的各种参数,准备create操作可能用到的工具(线程池等),然后将这些数据封装到image::CreateRequest对象中。最后调用对象的send()函数开始执行流程。最后通过cond.wait()来等待操作的完成。代码如下:

  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)
  {
    // 此处省略代码内容:
    // 根据参数准备image id、order、format等属性,没有则设默认值
    ...
    if (old_format) {
      // 如果是旧版的format,调用format v1的create函数,现在很少使用
      r = create_v1(io_ctx, image_name.c_str(), size, order);
    } else {
      // 从ceph ctx中获得全局的线程池和队列
      ThreadPool *thread_pool;
      ContextWQ *op_work_queue;
      ImageCtx::get_thread_pool_instance(cct, &thread_pool, &op_work_queue);

      C_SaferCond cond;
      // 创建image::CreateRequest对象,
      // 其实就是new 了一个image::CreateRequest对象。
      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();
      // 等待创建请求完成
      r = cond.wait();
    }

    int r1 = opts.set(RBD_IMAGE_OPTION_ORDER, order);
    assert(r1 == 0);

    return r;
  }

3)image::CreateRequest对象中给出的流程图,或者说状态图。这幅图描述了send()函数执行后的逻辑,其中的状态转移是通过if判断和函数调用来实现的。

Image在rados中的创建过程如下:

  • 创建一个rbd_id.<name>对象,映射image name到image id。
  • 增加name_<name>->image idid_<id>->name的映射到rbd_directorty对象的omap。
  • 创建rbd_header.<id>对象,在其omap和xattr中记录该image的metadata。
  • 如果开启了object map特性,创建rbd_object_map.<id>对象,记录该image所有data object的情况
  • 数据对象不会被创建,直到有数据写入
  /**
   * @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相关的流程之后补充。

附录A cls/rbd介绍

元数据相关的操作,通过cls注册到osd上。

cls是ceph的一个模块扩展,用户可以自定义对象的接口的实现方法,通过动态链接的形式加入osd中,在osd上直接执行。在此不做展开,具体可以参考这里

这部分rbd代码主要包含两部分,cls_rbd.h/cccls_rbd_client.h/cc。类似于服务端和客户端的关系,前者定义了具体在osd上执行的函数,后者在客户端执行,将函数参数封装后发送给服务端(osd),然后在osd上执行。

snapshot_add函数为例,该函数主要负责在rbd_header对象中增加新的snapshot元数据信息:

  • cls_rbd.cc函数中,对函数进行定义和注册。下面的代码注册了rbd模块,以及snapshot_add函数。
  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定义了通过客户端访问osd注册的cls函数的方法。以snapshot_add函数为例,这个函数将参数封装进bufferlist,通过ioctx->exec方法,把操作发送给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定义了方法在服务端的实现,其一般流程是:从bufferlist将客户端传入的参数解析出来,调用对应的方法实现,然后将结果返回客户端。
/**
 * 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中解析参数
  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;
  }
  // 判断参数合法性,略
  ......
  // 完成操作,在rbd_header对象中增加新的snapshot元数据,并更新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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 原因:2017年4月14日 星期五 学习记录。说明:整理ceph资料。我的博客 : http://minichao...
    nicocoi阅读 8,181评论 1 9
  • ceph简介 Ceph是一个分布式存储系统,诞生于2004年,是最早致力于开发下一代高性能分布式文件系统的项目。随...
    爱吃土豆的程序猿阅读 6,011评论 0 21
  • 系统环境: centos73.10.0-514.26.2.el7.x86_64 机器数量:五台 硬盘:四块一块为系...
    think_lonely阅读 4,622评论 0 5
  • Ceph Pool操作总结一个ceph集群可以有多个pool,每个pool是逻辑上的隔离单位,不同的pool可以有...
    think_lonely阅读 10,871评论 0 0
  • 集群管理 每次用命令启动、重启、停止Ceph守护进程(或整个集群)时,必须指定至少一个选项和一个命令,还可能要指定...
    Arteezy_Xie阅读 18,472评论 0 19