背景
RGW處理的報文本質上是一個HTTP報文,通常情況下使用http://<rgw-ip>:<rgw-port>/<bucket-name>
的方式來訪問一個bucket。實際應用尤其是公有云環境中,通常要在rgw前架設Haproxy等負載均衡設備,且將Haproxy的ip:port
映射成一個域名,方便用戶使用,這個域名也叫Endpoint。
在擁有Endpoint后,訪問一個bucket的url變成了http://<endpoint>/<bucket-name>
形式。不過這也只是一個ip到域名映射,和RGW關系不大,但后面要說的功能就和RGW密切相關了。
使用url方式訪問bucket主要用于靜態網站托管,既然是網站,當然就要盡量滿足常規網站訪問形式。為此出現了兩個需求:
需求1:bucket-domain方式訪問
bucket-domain方式是指以http://<bucket-name>.<endpoint>
形式訪問bucket。
如果用戶在公有云上托管了一個網站,以http://<myblog>.aws-s3.com
形式訪問肯定要好于http://aws-s3.com/myblog
,前者域名看起來更像是一個獨立網站。
需求2:private-domain
private-domain方式是指使用自己的域名訪問特定bucket。
如果用戶自己已經有了現成的域名,那直接使用肯定是更接地氣,而且訪問者完全不知道這個網站到底是托管在公有云上還是使用的獨立主機。
細說
bucket-domain方式代碼實現
首先,對象存儲服務提供方需要設置DNS,將<endpoint>
下的子域解析到RGW或其前端的負載均衡設備。
當HTTP請求到達RGW后,請求中會攜帶初始host請求信息,即<bucket-name>.<endpoint>
,RGW會根據配置的domain信息,將這個host信息解析成subdomain和domain兩部分,分別對應bucket-name和endpoint,隨后重新構造一個request url path,格式為/<bucket-name>,至此,整個邏輯回到了最原始的以http://<endpoint>/<bucket-name>
訪問時的狀態。
int RGWREST::preprocess(struct req_state *s, RGWClientIO* cio)
{
req_info& info = s->info; //info中存有此次請求相關的信息
...
if (info.host.size()) { // info.host中存放的就是用戶請求的url domain部分
ldout(s->cct, 10) << "host=" << info.host << dendl;
string domain;
string subdomain;
bool in_hosted_domain_s3website = false;
bool in_hosted_domain = rgw_find_host_in_domains(info.host, &domain, &subdomain, hostnames_set);
...
if (in_hosted_domain && !subdomain.empty()) { //重新構建request uri
string encoded_bucket = "/";
encoded_bucket.append(subdomain);
if (s->info.request_uri[0] != '/')
encoded_bucket.append("/");
encoded_bucket.append(s->info.request_uri);
s->info.request_uri = encoded_bucket;
}
...
private-domain方式代碼實現
首先,用戶需要將自己的域名配置一條CNAME,使對域名的請求跳轉到<bucket-name>.<endpoint>
。
當HTTP請求到達RGW后,請求中攜帶當host信息是<private-domain>
,RGW首先查詢自己的domain配置信息,如果沒有找到和這個域名相關的內容,則向DNS服務器請求,期待返回一個自己能使用的CNAME domain。
int RGWREST::preprocess(struct req_state *s, RGWClientIO* cio)
{
req_info& info = s->info;
...
/* 這一段和bucket-domain一樣,首先嘗試在rgw已配置的domain信息中進行解析 */
if (info.host.size()) {
ldout(s->cct, 10) << "host=" << info.host << dendl;
string domain;
string subdomain;
bool in_hosted_domain_s3website = false;
bool in_hosted_domain = rgw_find_host_in_domains(info.host, &domain, &subdomain, hostnames_set);
string s3website_domain;
string s3website_subdomain;
if (s3website_enabled) {
in_hosted_domain_s3website = rgw_find_host_in_domains(info.host, &s3website_domain, &s3website_subdomain, hostnames_s3website_set);
if (in_hosted_domain_s3website) {
in_hosted_domain = true; // TODO: should hostnames be a strict superset of hostnames_s3website?
domain = s3website_domain;
subdomain = s3website_subdomain;
}
}
...
/*解析失敗后嘗試請求DNS,得到CNAME后使用CNAME重新解析*/
if (g_conf->rgw_resolve_cname
&& !in_hosted_domain
&& !in_hosted_domain_s3website) {
string cname;
bool found;
int r = rgw_resolver->resolve_cname(info.host, cname, &found);
if (r < 0) {
ldout(s->cct, 0)
<< "WARNING: rgw_resolver->resolve_cname() returned r=" << r
<< dendl;
}
if (found) {
ldout(s->cct, 5) << "resolved host cname " << info.host << " -> "
<< cname << dendl;
in_hosted_domain =
rgw_find_host_in_domains(cname, &domain, &subdomain, hostnames_set);
...
/* 解析成功后,后面的邏輯就又回到了bucket-domain上,即重新構建request uri,然后就進入了常規處理階段。*/
...
配置RGW domain信息
前面提到RGW會根據自己配置的domain信息對用戶的host進行解析,這個domain信息是一個域名列表,列表包括RGW可以識別的domain,由于存在常規s3和s3website兩種訪問方式,因此會有兩個domain信息配置列表
//file: src/rgw/rgw_rest.cc
static set<string> hostnames_set;
static set<string> hostnames_s3website_set;
列表初始化時會加載rgw_dns_name配置項,但此配置項只能配置一條domain,因此如果需要增加多條domain(比如使用private-domain方式,但RGW又無法和解析private-domain的DNS通信的情況下),需要修改zonegroup的hostnames和hostnames_s3website配置。
radosgw-admin zonegroup get > zonegroup.conf
按需修改 zonegroup.conf文件中的hostnames和hostnames_s3website
radosgw-admin zonegroup set --infile=zonegroup.conf
總結
域名訪問分三個方式
- 初級方式:
http://<public-cloud-domain>/<bucket-name>
- 中級方式:
http://<bucket-name>.<public-cloud-domain>
- 高級方式:
http://<private-domain>
前兩種方式比較簡單,無需用戶進行額外操作。
第三種方式需要用戶配置DNS CNAME,將請求轉發到http://<bucket-name>.<public-cloud-domain>
上。這種場景需要注意的是,RGW要能夠訪問到用戶配置了CNAME的DNS服務器,否則只能通過增加RGW domain配置信息的方式來進行彌補。