Ceph RGW 设计与实现

1.总体架构

       rgw 作为对象存储网关系统, 一方面扮演RADOS集群客户端角色, 为对象存储应用提供数据存储; 另一方面扮演HTTP 服务端角色, 接受并解析互联网传送的数据。

       通过 HTTP 协议与 Swift 和 S3 应用通讯, 后端与 librados 结合, 通过socket 与 RADOS 集群通讯。 RGW 支持目前主流的WEB服务器, 包括 Civetweb、 APACHE、 Nginx等, 其中Civetweb 是一个C++库, 可以内嵌到RGW框架中, 是RGW默认的WEB服务器; Apache 与 Nginx 需要以独立进程存在, 收到请求后, 通过RGW注册的监听端口将请求转发到RGW上处理。

2.相关概念及定义

2.1几个内部概念

  • zone:包含多个RGW实例的一个逻辑概念。zone不能跨集群。同一个zone的数据保存在同一组pool中。
  • zonegroup:一个zonegroup如果包含1个或多个zone。如果一个zonegroup包含多个zone,必须指定 一个zone作为master zone,用来处理bucket和用户的创建。一个集群可以创建多个zonegroup,一个zonegroup也可以跨多个集群。
  • realm:一个realm包含1个或多个zonegroup。如果realm包含多个zonegroup,必须指定一个zonegroup为master zonegroup,用来处理系统操作。一个系统中可以包含多个realm,多个realm之间资源完全隔离。

2.2几个外部概念

  • 用户: 对象应用的使用者。 一个用户拥有一个或多个存储通。
  • 存储桶: 存储桶是对象的容器。 是为了方便管理和操作具有同一属性的一类对象引入的一层管理单元。
  • 对象: 对象是对象存储系统数据组织和存储的基本单位,一个对象包括数据和元数据。

尽管不同的对象存储系统在设计上有所不同,但是对外呈现的基础数据实体大同小异。比如Amazon S3的基础数据实体包含user,bucket,object与以上的用户,存储桶,对象一致。而openStack Swift将用户的概念细分为account和user.

2.2.1 用户

       一个用户包含的信息包括用户认证信息、 访问控制权限信息配额信息

 用户认证

要了解用户认证信息有哪些,必须要先了解RGW的认证机制, RGW针对S3 API和Swift API采用不同的认证机制。

S3认证过程
  1. 应用在发送请求前, 使用用户私有密钥(secret_key), 请求内容, 采用与RGW网关约定好的算法计算出数字签名后, 将数字签名以及用户访问密钥(access_key) 封装在请求中发送给RGW网关。
  2. RGW网关收到请求后, 使用用户访问密钥作为索引从RADOS集群中读取用户信息, 并从用户信息中获取用户私有密钥。
  3. 使用用户私有密钥请求内容等, 采用与应用约定好的算法计算数字签名。
  4. 判断RGW生成的数字签名与请求的签名是否匹配, 如果匹配, 则认为请求是真实的, 用户认证通过。

    可以看出,在S3认证机制中,用户信息中必须包含访问密钥私有密钥信息

Swift认证过程 
  1. 应用在发出真正的操作请求前, 向RGW网关请求一个令牌(注: 该令牌有有效期, 过了有效期后, 需要重新请求新的令牌)。
  2. RGW收到令牌请求后, 使用子用户ID作为索引从RADOS集群中读取出子用户信息, 并从子用户信息中获取到Swift私有密钥(swift_key) 生成一个令牌返回给应用。
  3. 应用后续操作中携带该令牌, RGW收到操作请求后, 采用与步骤2相同的方式生成一个令牌, 并判断与请求中的令牌是否一致, 如果一致, 身份验证通过。

    可以看出,Swift认证机制中必须包含Swift私有密钥。

RGW将用户信息保存在Rados对象的数据部分一个用户对应RADOS对象,由于大部分情况下,我们需要使用用户ID作为所以获取用户信息,因此该对象以用户ID命名(RADOS通过‘‘pool名+对象吗’’来查询一个对象)

RGW需要将访问密钥,字用户,email跟用户信息所在的RADOS对象建立索引关系,针对这种情况,RGW采用了二级索引的方式,及分别创建以用户访问密钥,子用户,email命名的三个对象(即索引对象),并将用户ID保存在对象的数据部分,当需要某个索引查询用户信息时,所有从所有对象中读出用户ID,然后使用用户ID作为所以读取用户信息

 2.2.2存储桶

    一个存储桶对应要一个RADOS对象。一个存储桶包含的信息包括两类,一类是对RGW网关透明的信息,这类信息通常指用户自定义的元数据,RGW网关直接将这些信息保存在扩展属性中,一个KV键值对对应一个扩展属性条目,另一类是RGW网关关注的信息,这类信息包括存储桶中对象的存储策略存储桶中索引对象的数目以及应用对象和索引对象的映射关系、存储桶的配额等,此类信息由数据结构RGWBucketInfo管理。

    在创建存储桶时,RGW网关会同步创建一个或多个索引(index)对象,用于保存该存储桶下的对象列表,以支持查询存储桶对象列表(List Bucket)功能,因此在存储桶中有心的对象上传或者删除的时候必须更新索引对象。

2.2.3对象

应用上传的对象包括数据和元数据两部分,数据部分保存在一个火多个RADOS对象的数据部分,元数据保存在其中一个RADOS对象的拓展属性中。RGW对单个对象提供了两种上传接口:整体上传分段上传

2.2.4Pool

RGW中含有多种pool,通过以下命令可以获得:

rados lspools
rbd
.rgw.root
default.rgw.control
default.rgw.data.root
default.rgw.gc
default.rgw.log
default.rgw. users .uid
default.rgw. users .keys
default.rgw. users .swift
default.rgw.buckets.index
default.rgw.buckets.data

各种pool的作用
  1. .rgw.root 包含realm,zonegroup和zone
  2. default.rgw.control在RGW上电时,在control pool创建若干个对象用于watch-notify,主要作用为当一个zone对应多个RGW,且cache使能时, 保证数据的一致性,其基本原理为利用librados提供的对象watch-notify功能,当有数据更新时,通知其他RGW刷新cache, 后面会有文档专门描述RGW cache
  3. default.rgw.data.root:包含bucket和bucket元数据,bucket创建了两个对象一个:一个是< bucket_name > 另一个是.bucket.meta.< bucket_name >.< marker > 这个marker是创建bucket中生成的。同时用户创建的buckets在.rgw.buckets.index都对应一个object对象,其命名是格式:.dir.< marker 
  4. default.rgw.gc:RGW中大文件数据一般在后台删除,该pool用于记录那些待删除的文件对象
  5. default.rgw.log:各种log信息
  6. default.rgw.users.uid:保存用户信息,和用户下的bucket信息
  7. default.rgw.users.keys:包含注册用户的access_key
  8. default.rgw.users.swift:包含注册的子用户(用于swift)
  9. default.rgw.buckets.index:包含bucket信息,和default.rgw.data.root对应
  10. default.rgw.buckets.data:包含每个bucket目录下的object

default.rgw.data.root,default.rgw.buckets.index和default.rgw.buckets.data 之间有直接的联系

3.功能实现

这里主要是介绍RGW对外提供的功能,I/O路径以及存储桶创建,对象上传下载等几个功能的实现。

3.1功能特性

对象存储最基本的功能呢包括用户,存储桶,对象的增删改查等,RGW网关最近几年在不断地跟进和对齐AmazonS3和OpenStack Swift功能。目前RGW网关兼容的S3和Swift的API见链接:

  • https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html
  • https://docs.openstack.org/swift/latest/api/object_api_v1_overview.html

3.1.1I/O路径

RGW网关使用OP线程处理应用的I/O请求(OP线程在上电时创建,当前端WEB服务器为Civetweb时,通过修改配置项rgw_thread_pool_size指定OP线程数目)。OP线程内部逻辑可分为HTTP前端REST API通用处理层API操作执行层RADOS接口适配层librados接口层等几个关键流程。OP线程从HTTP前端收到I/O请求后,首先在REST API通用处理层,从HTTP语义中解析出S3或Swift数据并进行一系列的检查,检查通过后,根据不同API操作请求执行不同的处理流程,如需从RASDOS集群获取数据或这往RADOS集群写入数据,则通过RGW与RADOS接口适配层调用librados接口将请求发送到RADOS集群中获取或写入相应数据,完成整个I/O过程。

RGW实例内部I/O路径如下图:

 

REST API通过处理层的关键步骤如下图所示,大概分为用户认证用户/存储桶/对象的访问控制和用户/存储桶配额检查等几项。

这里以Civetweb作为Web服务器的Request处理流程如下:

1.在main函数中若选择使用civetweb作为前端Web服务器,在设置相关配置后调用run函数启动civetweb.

run函数中在mg_start函数启动web server.该函数还会传入一个回调函数,用于处理每个request请求。

int  RGWCivetWebFrontend::run()
{
     ...
     struct  mg_callbacks cb;
     memset (( void  *)&cb, 0,  sizeof (cb));
     /* 回调函数设置 */
     cb.begin_request = civetweb_callback;
     cb.log_message = rgw_civetweb_log_callback;
     cb.log_access = rgw_civetweb_log_access_callback;
     /* 启动服务 */
     ctx = mg_start(&cb,  this , options.data());
     return  ! ctx ? -EIO : 0;
}
2.经过上一步的设置,在civetweb_callback中每一个request请求都需要经过process_request()进行处理,注意每个request请求都会绑定一组RGWRados(负责底层Librados的数据读写)/RGWREST(对应request和Response的处理)/OpsLogSocket(日志消息记录)。

static  int  civetweb_callback( struct  mg_connection* conn)
{
   const  struct  mg_request_info*  const  req_info = mg_get_request_info(conn);
   return  static_cast (req_info->user_data)->process(conn);
}
  
int  RGWCivetWebFrontend::process( struct  mg_connection*   const  conn)
{
   /* Hold a read lock over access to env.store for reconfiguration. */
   RWLock::RLocker lock(env.mutex);
   RGWCivetWeb cw_client(conn);
   auto real_client_io = rgw::io::add_reordering(
                           rgw::io::add_buffering(dout_context,
                             rgw::io::add_chunking(
                               rgw::io::add_conlen_controlling(
                                 &cw_client))));
   RGWRestfulIO client_io(dout_context, &real_client_io);
   RGWRequest req(env.store->get_new_req_id());
   int  ret = process_request(env.store, env.rest, &req, env.uri_prefix,
                             *env.auth_registry, &client_io, env.olog);
   if  (ret < 0) {
     /* We don't really care about return code. */
     dout(20) <<  "process_request() returned "  << ret << dendl;
   }
   /* Mark as processed. */
   return  1;
}

3.process_request会根据传入的RGWRados参数获取RGWOp,再去认证该Op

int  process_request(RGWRados*  const  store,
                     RGWREST*  const  rest,
                     RGWRequest*  const  req,
                     const  std::string& frontend_prefix,
                     const  rgw_auth_registry_t& auth_registry,
                     RGWRestfulIO*  const  client_io,
                     OpsLogSocket*  const  olog)
{
     ...
     /*
     得到RGWHandler_REST类实例
   */
   RGWRESTMgr *mgr;
   RGWHandler_REST *handler = rest->get_handler(store, s,
                                                auth_registry,
                                                frontend_prefix,
                                                client_io, &mgr, &init_error);
     /*
     根据request的op得到对应的操作处理类,如:GET/PUT/DELETE/POST等
   */
   op = handler->get_op(store);
   if  (!op) {
     abort_early(s, NULL, -ERR_METHOD_NOT_ALLOWED, handler);
     goto  done;
   }
   req->op = op;
   dout(10) <<  "op="  <<  typeid (*op).name() << dendl;
   s->op_type = op->get_type();
   req-> log (s,  "verifying requester" );
   /*
     执行认证操作
   */
   ret = op->verify_requester(auth_registry);
   if  (ret < 0) {
     dout(10) <<  "failed to authorize request"  << dendl;
     abort_early(s, NULL, ret, handler);
     goto  done;
   }
   /* FIXME: remove this after switching all handlers to the new authentication
    * infrastructure. */
   if  (nullptr == s->auth.identity) {
     s->auth.identity = rgw::auth::transform_old_authinfo(s);
   }
   req-> log (s,  "normalizing buckets and tenants" );
   /*
     检查bucket以及object名字的有效性。
   */
   ret = handler->postauth_init();
   if  (ret < 0) {
     dout(10) <<  "failed to run post-auth init"  << dendl;
     abort_early(s, op, ret, handler);
     goto  done;
   }
   /*
     用户被禁
   */
   if  (s->user->suspended) {
     dout(10) <<  "user is suspended, uid="  << s->user->user_id << dendl;
     abort_early(s, op, -ERR_USER_SUSPENDED, handler);
     goto  done;
   }
   /*
      验证并执行操作
   */
   ret = rgw_process_authenticated(handler, op, req, s);
   if  (ret < 0) {
     abort_early(s, op, ret, handler);
     goto  done;
   }
done:
   try  {
     client_io->complete_request();
   catch  (rgw::io::Exception& e) {
     dout(0) <<  "ERROR: client_io->complete_request() returned "
             << e.what() << dendl;
   }
   if  (should_log) {
     rgw_log_op(store, rest, s, (op ? op->name() :  "unknown" ), olog);
   }
   int  http_ret = s->err.http_ret;
   int  op_ret = 0;
   if  (op) {
     op_ret = op->get_ret();
   }
   req->log_format(s,  "op status=%d" , op_ret);
   req->log_format(s,  "http status=%d" , http_ret);
   if  (handler)
     handler->put_op(op);
   rest->put_handler(handler);
   dout(1) <<  "====== req done req="  << hex << req << dec
       <<  " op status="  << op_ret
       <<  " http_status="  << http_ret
       <<  " ======"
       << dendl;
   return  (ret < 0 ? ret : s->err.ret);
/* process_request */

从之前RGW的启动流程中可以看到,根据不同的模块RGW会注册(实例化)出对应的RGWRESTMgr类,从request处理

流程(request_process)中可以看到根据不同的处理对象(如针对桶的操作、针对对象的操作)会从RGWRESTMgr类中实

例化出对应的RGWHandler_REST类,再根据不同的方法(如post、put、delet、update)从RGWHandler_REST类中实例化

出RGWOp类,从而关联到了具体的操作,不同的RGWOp类(即不同的操作)有自己对应的acl,从中验证用户是否具有相应

的访问权限。 每个RGWOp子类有自己的verify_permission函数来做验证。

3.1.2用户认证

对于S3 API RGW 支持认证用户和匿名用户的访问,所有没有通过认证的访问则被认为是匿名用户的访问。RGW支持V2和V4两种方式认证,RGW V2认证支持本地认证LDAPkeystone三张方式,RGW V4认证兼容AWS V4认证机制。

对于Swift API, 支持临时URL认证SignedToken认证KeyStore认证ExternalToken认证匿名认证5种认证引擎。RGW对每个请求依次使用上述5个认证引擎进行认证没如果某个引擎认证通过,认证结束,用户操作通过认证。

3.1.3用户/存储桶/对象访问权限控制

通过身份验证后,并不意味着一定有访问资源(桶,对象等)的权限,用户必须具备相应的访问权限(ACL)才能访问对应的对象。

针对S3 API,访问控制列表分为存储桶访问控制列表对象访问控制列表,分别作用域存储桶本身和对象本身。

对于Swift API在权限访问控制上与S3 API有所不同,Swift访问控制权限分为用户访问控制列表存储桶访问控制列表

3.1.3.1bucket/用户配额

通常限制单个应用或者用户可以使用的最大存储空间称为配额管理,配额管理是对最大存放的对象数目和对象的总大小进行限制的,支持对单个用户和单个用户下存储桶的配额限制, 分别用user_quotabucket_quota表示。当两种配额模式同时启用时,任何一种先打到了配额限制都会生效。

3.1.3.2用户操作权限判断

         在实际操作前,需要对用户权限进行判断,如:只有具有删除权限的用户才能对对象进行删除操作,对应用户信息的op_mask

字段。

经过以上4步完成后,针对不同的操作请求执行具体的请求操作,下面以存储桶创建,对象上传,对象下载操作请求为例,具体介绍实现流程。

 3.1.2存储桶创建

创建存储桶的流程大致分一下几步:

  • 从HTTP请求中解析出相关参数:如桶的访问控制列表,存储策略
  • 判断存储桶是否存在:由于同一租户下不同用户不能创建同名的存储桶,因此如果该存储桶已存在,且其拥有者不是当前
    的用户,返回存储桶已存在。
  • 创建存储桶:首先应用指定的对象存储策略,并将存储策略保存在存储桶的管理结构RGWBucketInfo中,然后将存储桶的
    访问控制列表跨资源访问信息自定义元数据封装对应的KV条目,在索引存储池创建一个或多个索引对象成功后,在
    domain_root存储池创建一个对象,同时将管理结构RGWBucketInfo保存在该对象的内容中,将KV条目保存到对象的扩展
    属性中
  • 更新user_id.buckets对象:创建一个用户的同时,创建一个名为.buckets的对象,用于记录该用户下的所有存储
    桶列表,保存在该对象的OMAP中。OMAP头部保存用户使用空间统计信息(cls_user_header),OMAP的KV条目保存
    一个存储桶使用空间统计信息(cls_user_bucket_entry)。

3.1.3对象上传

RGW针对对象上传操作设计了两个接口:整体上传对象接口和分段上传对象接口,当单个对象大于5G时,必须调用分段

上传接口才能成功上传对象,当单个对象小于条带大小时,不能采用分段上传对象。

3.1.3.1整体上传
  1. prepare:在prepare阶段的主要工作时初始化 manifest 数据结构
  2. handle_data:handle_data阶段,RGW每次从HTTP Server 缓冲区中取出rgw_max_chunk_size字节的数据,存放在bufferlist
    中,然后分成一个或多个I/O异步下发到RADOS层,每个I/O的大小等于MIN(rgw_max_chunk_size, next_part_ofs - data_ofs),
    其中next_part_ofs表示下一个RADOS对象保存的用户数据偏位置,data_ofs 表示当前数据的偏移位置。
  3. complete:等所有数据上传成功后,对象上传进入complate阶段,该阶段的主要工作时将对象的元数据更新到head_obj中,同时
    将对象条目更新到索引对象中,以便连续列举对象。
3.1.3.2分段上传

分段上传对象比单个操作对象流程复杂,设计三个接口的调用:

  1. 初始化:在分段上传数据之前,应用首先调用INITIATE MULTI-PART UPLOAD 接口进行初始化,应用在调用该接口的请求中携带对象的访问控制列表,用户对上传对象自定义的元数据等信息。RGW网关在此操作中生成一个Upload返回给应用,同时在data_extra_pool中生成一个临时对象,用于保存每个分段的信息,并经对象的访问控制列表信息,元数据信息等保存到该对象的xattar中。
  2. 分段上传:分段上传对象流程跟单个操作上传流程基本一致,不同之处在complete阶段,分段上传对象除了需要将每个分段对象更新到索引对象之处外,还需要将每个分段信息更新到初始化时在data_extar_pool中生成的临时对象中。
  3. 分段上传完成:所有的分段上传完成后,应用需要调用COMPLETE MULTIPART UPLOAD表示对象上传完成。在此操作中RGW会从初始化阶段生成的临时对象中读出各个分段信息,主要是分段的manifest,组成一个manifest,然后生成一个head_obj,将这些信息保存到head_obj后,将临时对象删除。

3.1.4对象下载

        用户除了上传对象外,还可以指定对象的某一段(采用off,length的形式)下载对象的部分内容,RGW首先从head_obj中读出manifest管理结构,然后根据manufest中定义的规则计算出用户请求的数据段保存在哪些对象中,最终从这些对象中读出数据合并后发送给客户端。

3.2 RGW下实现多活的机制

RGW多活方式是在同一zonegroup的多个zone之间进行,即同一zonegroup中多个zone之间的数据是完全一致的,用户可以通过任意zone读写同一份数据。 但是,对元数据的操作,比如创建桶、创建用户,仍然只能在master zone进行。对数据的操作,比如创建桶中的对象,访问对象等,可以在任意zone中 处理。

下面通过一个例子说明双活配置下,对象存储业务如何访问集群和多个zone之间数据如何同步。该例子实现跨集群的一个zonegroup下两个zone之间的双活。

4总结和展望

RGW在很多领域得到广泛应用,虽然RGW基本功能已经比较完备,但是还是有些小毛病,比如与S3 Java语言SDK不兼容,AWS4认证机制不完善,对象多版本功能存在小问题,但是基础模块已经比较稳定,完全可以使用于生产环境。除此之外,RGW以下几个功能特性也值得期待:

  1. 多站点朱博士数据容灾备份
  2. 支持元数据搜索功能
  3. 支持NFS访问

你可能感兴趣的:(ceph)