rgw 作为对象存储网关系统, 一方面扮演RADOS集群客户端角色, 为对象存储应用提供数据存储; 另一方面扮演HTTP 服务端角色, 接受并解析互联网传送的数据。
通过 HTTP 协议与 Swift 和 S3 应用通讯, 后端与 librados 结合, 通过socket 与 RADOS 集群通讯。 RGW 支持目前主流的WEB服务器, 包括 Civetweb、 APACHE、 Nginx等, 其中Civetweb 是一个C++库, 可以内嵌到RGW框架中, 是RGW默认的WEB服务器; Apache 与 Nginx 需要以独立进程存在, 收到请求后, 通过RGW注册的监听端口将请求转发到RGW上处理。
尽管不同的对象存储系统在设计上有所不同,但是对外呈现的基础数据实体大同小异。比如Amazon S3的基础数据实体包含user,bucket,object与以上的用户,存储桶,对象一致。而openStack Swift将用户的概念细分为account和user.
一个用户包含的信息包括用户认证信息、 访问控制权限信息和配额信息。
要了解用户认证信息有哪些,必须要先了解RGW的认证机制, RGW针对S3 API和Swift API采用不同的认证机制。
应用在发送请求前, 使用用户私有密钥(secret_key), 请求内容, 采用与RGW网关约定好的算法计算出数字签名后, 将数字签名以及用户访问密钥(access_key) 封装在请求中发送给RGW网关。
RGW网关收到请求后, 使用用户访问密钥作为索引从RADOS集群中读取用户信息, 并从用户信息中获取用户私有密钥。
使用用户私有密钥请求内容等, 采用与应用约定好的算法计算数字签名。
判断RGW生成的数字签名与请求的签名是否匹配, 如果匹配, 则认为请求是真实的, 用户认证通过。
可以看出,在S3认证机制中,用户信息中必须包含访问密钥和私有密钥信息
RGW将用户信息保存在Rados对象的数据部分,一个用户对应RADOS对象,由于大部分情况下,我们需要使用用户ID作为所以获取用户信息,因此该对象以用户ID命名(RADOS通过‘‘pool名+对象吗’’来查询一个对象)
RGW需要将访问密钥,字用户,email跟用户信息所在的RADOS对象建立索引关系,针对这种情况,RGW采用了二级索引的方式,及分别创建以用户访问密钥,子用户,email命名的三个对象(即索引对象),并将用户ID保存在对象的数据部分,当需要某个索引查询用户信息时,所有从所有对象中读出用户ID,然后使用用户ID作为所以读取用户信息。
一个存储桶对应要一个RADOS对象。一个存储桶包含的信息包括两类,一类是对RGW网关透明的信息,这类信息通常指用户自定义的元数据,RGW网关直接将这些信息保存在扩展属性中,一个KV键值对对应一个扩展属性条目,另一类是RGW网关关注的信息,这类信息包括存储桶中对象的存储策略、存储桶中索引对象的数目以及应用对象和索引对象的映射关系、存储桶的配额等,此类信息由数据结构RGWBucketInfo管理。
在创建存储桶时,RGW网关会同步创建一个或多个索引(index)对象,用于保存该存储桶下的对象列表,以支持查询存储桶对象列表(List Bucket)功能,因此在存储桶中有心的对象上传或者删除的时候必须更新索引对象。
应用上传的对象包括数据和元数据两部分,数据部分保存在一个火多个RADOS对象的数据部分,元数据保存在其中一个RADOS对象的拓展属性中。RGW对单个对象提供了两种上传接口:整体上传和分段上传。
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
default.rgw.data.root,default.rgw.buckets.index和default.rgw.buckets.data 之间有直接的联系
这里主要是介绍RGW对外提供的功能,I/O路径以及存储桶创建,对象上传下载等几个功能的实现。
对象存储最基本的功能呢包括用户,存储桶,对象的增删改查等,RGW网关最近几年在不断地跟进和对齐AmazonS3和OpenStack Swift功能。目前RGW网关兼容的S3和Swift的API见链接:
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;
}
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函数来做验证。
对于S3 API RGW 支持认证用户和匿名用户的访问,所有没有通过认证的访问则被认为是匿名用户的访问。RGW支持V2和V4两种方式认证,RGW V2认证支持本地认证,LDAP和keystone三张方式,RGW V4认证兼容AWS V4认证机制。
对于Swift API, 支持临时URL认证、SignedToken认证,KeyStore认证,ExternalToken认证和匿名认证5种认证引擎。RGW对每个请求依次使用上述5个认证引擎进行认证没如果某个引擎认证通过,认证结束,用户操作通过认证。
通过身份验证后,并不意味着一定有访问资源(桶,对象等)的权限,用户必须具备相应的访问权限(ACL)才能访问对应的对象。
针对S3 API,访问控制列表分为存储桶访问控制列表和对象访问控制列表,分别作用域存储桶本身和对象本身。
对于Swift API在权限访问控制上与S3 API有所不同,Swift访问控制权限分为用户访问控制列表和存储桶访问控制列表。
通常限制单个应用或者用户可以使用的最大存储空间称为配额管理,配额管理是对最大存放的对象数目和对象的总大小进行限制的,支持对单个用户和单个用户下存储桶的配额限制, 分别用user_quota和bucket_quota表示。当两种配额模式同时启用时,任何一种先打到了配额限制都会生效。
在实际操作前,需要对用户权限进行判断,如:只有具有删除权限的用户才能对对象进行删除操作,对应用户信息的op_mask
字段。
经过以上4步完成后,针对不同的操作请求执行具体的请求操作,下面以存储桶创建,对象上传,对象下载操作请求为例,具体介绍实现流程。
创建存储桶的流程大致分一下几步:
更新user_id.buckets对象:创建一个用户的同时,创建一个名为
桶列表,保存在该对象的OMAP中。OMAP头部保存用户使用空间统计信息(cls_user_header),OMAP的KV条目保存
一个存储桶使用空间统计信息(cls_user_bucket_entry)。
RGW针对对象上传操作设计了两个接口:整体上传对象接口和分段上传对象接口,当单个对象大于5G时,必须调用分段
上传接口才能成功上传对象,当单个对象小于条带大小时,不能采用分段上传对象。
分段上传对象比单个操作对象流程复杂,设计三个接口的调用:
用户除了上传对象外,还可以指定对象的某一段(采用off,length的形式)下载对象的部分内容,RGW首先从head_obj中读出manifest管理结构,然后根据manufest中定义的规则计算出用户请求的数据段保存在哪些对象中,最终从这些对象中读出数据合并后发送给客户端。
RGW多活方式是在同一zonegroup的多个zone之间进行,即同一zonegroup中多个zone之间的数据是完全一致的,用户可以通过任意zone读写同一份数据。 但是,对元数据的操作,比如创建桶、创建用户,仍然只能在master zone进行。对数据的操作,比如创建桶中的对象,访问对象等,可以在任意zone中 处理。
下面通过一个例子说明双活配置下,对象存储业务如何访问集群和多个zone之间数据如何同步。该例子实现跨集群的一个zonegroup下两个zone之间的双活。
RGW在很多领域得到广泛应用,虽然RGW基本功能已经比较完备,但是还是有些小毛病,比如与S3 Java语言SDK不兼容,AWS4认证机制不完善,对象多版本功能存在小问题,但是基础模块已经比较稳定,完全可以使用于生产环境。除此之外,RGW以下几个功能特性也值得期待: