194 KiB
第 11 册 · RGW 对象存储篇(三):多站点、同步与数据服务
基于 Ceph 官方文档整理 | 文档版本:Tentacle v20.2.4 | 抓取日期:2026-09-17 | 源文:ceph/ceph@v20.2.4
doc/radosgw/官网入口:https://docs.ceph.com/en/tentacle/radosgw/
本册导读
本册是 RGW 对象存储四册中的第三册,聚焦「数据在多个站点之间如何流动、如何被二次消费」。
| 分册 | 主题 | 本册是否覆盖 |
|---|---|---|
| 第 9 册(第一册) | RGW 基础:部署、frontend、pools/placement、用户与桶对象基础操作、S3/Swift 接口 | 否 |
| 第 10 册(第二册) | RGW 安全与策略:STS / IAM / Keystone / LDAP / OIDC、bucket policy、服务端加密(Vault / KMIP / Barbican)、MFA、role、Lua 脚本、multitenancy | 否 |
| 第 11 册(本册) | 多站点(realm / zonegroup / zone / period)、同步策略与同步模块、云分层与云恢复、zone features、事件通知、桶日志、S3 Select、缓存与压缩、动态分片、孤立对象 | 是 |
| 第 12 册(第四册) | 运维观测与排障:metrics、troubleshooting、NFS、QAT / UADK 加速、config-ref | 否 |
💡 补充:上表的四册划分是为便于学习而做的编排建议,官方
doc/radosgw/index.rst并不存在「四册」这种分法。本册覆盖的页面清单以文首【输入文件】为准。
本册涉及的官方页面与对应章节:
| 官方页面 | 本册章节 |
|---|---|
multisite.rst |
一、二、三 |
multisite-sync-policy.rst |
四 |
sync-modules.rst / elastic-sync-module.rst / archive-sync-module.rst / cloud-sync-module.rst |
五 |
cloud-transition.rst / cloud-restore.rst |
六 |
zone-features.rst |
七 |
notifications.rst |
八 |
s3-notification-compatibility.rst |
九 |
bucket_logging.rst |
十 |
s3select.rst |
十一 |
rgw-cache.rst / d3n_datacache.rst |
十二 |
compression.rst / dynamicresharding.rst |
十三 |
orphans.rst |
十四 |
一、多站点总览与核心概念
1.1 单 zone 与多站点配置形态
单 zone 配置(Single-zone) 通常由两样东西构成:
- 一个
zonegroup,其中包含一个zone; - 一个或多个
ceph-radosgw实例,客户端请求在这些实例之间做负载均衡。
典型的单 zone 配置是:多个 ceph-radosgw 实例共同使用同一个 Ceph 存储集群。
多站点配置(Multi-site) 自 Jewel 引入、Kraken 起支持多种形态:
| 形态 | 说明 | 数据隔离性 |
|---|---|---|
| Multi-zone | 一个 zonegroup 内包含多个 zone。每个 zone 由一个或多个 ceph-radosgw 实例组成,每个 zone 由各自独立的 Ceph 存储集群支撑 |
同一 zonegroup 内 zone 之间互相复制全部数据;每个 zone 都是 active 的,都能接收写操作 |
| Multi-zonegroups | 一个 realm 下多个 zonegroup(旧称 "region")。同一 zonegroup 内的两个 zone 共享对象数据;同一 realm 下不同 zonegroup 之间共享全局对象命名空间 | bucket 归属于创建它的 zonegroup(可被 LocationConstraint 覆盖);对象数据只复制到本 zonegroup 内的其他 zone;其他 zonegroup 收到该 bucket 的请求会被重定向到 bucket 所属 zonegroup |
| Multiple Realms | realm 是 zonegroup 的容器,realm 之间命名空间完全独立 | 每个 realm 可有自己的一套配置;不同 realm 之间不共享命名空间 |
multi-zonegroup 的典型用途:多个互联站点共享用户与 bucket 命名空间,但只希望对象数据在站点子集内复制。例如「多个站点共享一份用户体系,但每两个站点互为备份」,就可以建多个各含 2 个 zone 的 zonegroup,避免把全部对象复制到全部 zone。
1.2 核心概念关系表
| 概念 | 层级 | 定义 | 关键特性 |
|---|---|---|---|
realm |
最高层 | zonegroup 的容器,可在同一套硬件上支持多个命名空间与多套配置 | 全局唯一命名空间;关联一个 period;为兼容 Infernalis 及更早版本,默认不会自动创建 realm,但新建集群建议显式创建 |
zonegroup |
第二层 | 定义一个或多个 zone 的地理位置;旧称 "region" | 每个 realm 必须有且仅有一个 master zonegroup;zonegroup 内的 zone 之间复制数据;跨 zonegroup 只共享命名空间 |
zone |
第三层 | 由一个或多个 ceph-radosgw 实例组成的逻辑组;同一 zone 内的所有 RGW 服务同一套 pools、同一个集群上的 RADOS 对象 |
每个 zonegroup 必须有一个 master zone;可有多个 secondary zone;zone 可标记 read_only |
period |
配置版本 | 表示 zonegroup 与 zone 配置在某一时刻的状态 | 每次修改 zonegroup / zone 后都应 period update --commit;提交后 epoch +1,其他 zone 才能收到新配置 |
epoch |
版本号 | period 内用于跟踪配置变更的序号 | realm 输出中的 "epoch" 字段;新建 realm 时 epoch: 1 |
bucket / object |
数据层 | bucket 属于 zone(进而属于 zonegroup、realm);bucket 内含 object | bucket 归属于创建它的 zonegroup |
💡 补充:官方
multisite.rst中并未出现「datalog/bilog/mdlog三层日志的架构图详解」,只在「Check the logs」一节提到多站点部署下可以检查mdlog(元数据日志)、bilog(bucket index 日志)、datalog(数据日志)。下文 1.4 节的同步架构描述基于这些日志在sync status输出中的体现(metadata sync / data sync)整理,属于推断性补充。
1.3 自 Infernalis 以来的功能性变化(Kraken 起)
| 变化 | 说明 |
|---|---|
| active-active zone 模式 | 每个 RGW 都可配置为 active-active,允许向非 master zone 写入 |
| 配置容器化 | 多站点配置存放在 realm 中;realm 保存 zonegroup、zone 以及带多个 epoch 的 period |
| 去 agent 化 | 从 Kraken 起,ceph-radosgw 守护进程自身负责跨 zone 同步,不再需要独立的同步 agent |
| 默认 active-active | 上述新同步机制使得默认即为 active-active,而非 active-passive |
1.4 同步架构与流程
三类日志(仅多站点部署存在):
| 日志 | 全称 | 承载内容 | 对应 sync status 段落 |
相关 shard 数(默认值取自 src/common/options/*.yaml.in @ v20.2.4) |
|---|---|---|---|---|
mdlog |
metadata log | 用户、bucket 等元数据变更 | metadata sync |
rgw_md_log_max_shards = 64(int) |
datalog |
data log | 对象数据变更 | data sync source: <zone> |
rgw_data_log_num_shards = 128(int) |
bilog |
bucket index log | bucket 索引变更 | (由 data sync 消费) | 文档未明确 |
同步的两条主线:
- metadata sync(元数据同步):secondary zone 从 master zone 拉取
mdlog,把用户、bucket 等元数据补齐。输出形如full sync: 0/64 shards+incremental sync: 64/64 shards。 - data sync(数据同步):zone 从 zonegroup 内的其他 zone(data sync source)拉取
datalog,按 bucket 复制对象。输出每个 source 一段,形如full sync: 0/128 shards+incremental sync: 128/128 shards。
full sync 与 incremental sync:
full sync:初次同步时把该 shard 上的全量数据搬过去;incremental sync:全量完成后转为增量,只搬新增/变更的日志条目。
对象校验: 默认情况下对象同步成功后不做二次校验。把 rgw_sync_obj_etag_verify 设为 true 后,会使用 MD5 校验和验证从源传到目的的数据完整性(包括通过 HTTP 从远端获取的任何对象,含多站点同步)。该选项会增加计算量,可能降低 RGW 性能。
默认值取自 src/common/options/*.yaml.in @ v20.2.4:
rgw_sync_obj_etag_verify=false(bool)。
日志裁剪(trim): 通常无需手工 trim,因为 rgw_sync_log_trim_interval 默认为 20 分钟(源码中记为 20_min)。除非该值被手工改为 0,否则不需要 trim。
默认值取自 src/common/options/*.yaml.in @ v20.2.4:
rgw_sync_log_trim_interval=20_min= 1200 秒(int)。
1.5 部署模式:active-active 与 active-passive
| 模式 | 创建方式 | 写能力 | 适用场景 |
|---|---|---|---|
| active-active(默认) | radosgw-admin zone create 时不加 --read-only、--master(secondary zone) |
任何 zone 都可写,写后复制到 zonegroup 内其他 zone | 容灾 + 可作为 CDN 基础;地理就近写入 |
| active-passive | 创建 secondary zone 时加 --read-only |
只有 master zone 可写,secondary 只读 | 只希望从区做备份/只读副本 |
⚠️ 重要约束:元数据操作(例如创建用户)必须在 master zone 所在主机上执行。bucket 操作 master 和 secondary 都能接收,但 secondary 会把 bucket 操作重定向到 master zone;如果 master zone 宕机,bucket 操作会失败,而 object 操作仍可成功。
1.6 前提与假设
| 项 | 要求 |
|---|---|
| 存储集群数 | 至少 2 个 Ceph 存储集群(多 zone 形态下每个 zone 由独立集群支撑) |
| RGW 实例数 | 至少 2 个(每个集群一个) |
| 地理位置 | 建议地理分离;同机房也可以工作 |
| master | 必须有一个 master zonegroup 和一个 master zone;每个 zonegroup 必须有一个 master zone |
| 网络 | 不推荐在 WAN 延迟较高的情况下跑单一跨地域 Ceph 存储集群 |
| 示例主机名 | 本文档沿用官方示例:rgw1(master zone us-east)、rgw2(secondary zone us-west),realm 名 movies,zonegroup 名 us |
引自:Multi-Site
二、多站点部署全流程
本节给出从零搭建「master zone + secondary zone」的完整命令序列。
2.1 流程总览
1. realm create(master 上)
2. zonegroup create --master --default(master 上)
3. zone create --master --default(master 上)
4. 删除 default zonegroup / zone 及其 pool(仅限全新集群!)
5. user create --system(系统用户,master 上)
6. zone modify 注入 access-key / secret → period update --commit
7. 修改 ceph.conf 加 rgw_zone → 启动 RGW
--- 以下在 secondary 上 ---
8. realm pull(拉取 realm 配置)
9. realm default(若只有一个 realm)
10. zone create(不带 --master / --default,可选 --read-only)
11. 删除 secondary 上的 default zone / pool(可选)
12. 修改 ceph.conf 加 rgw_zone=us-west → period update --commit → 启动 RGW
13. radosgw-admin sync status 检查
2.2 配置 master zone
步骤 1:创建 realm
radosgw-admin realm create --rgw-realm={realm-name} [--default]
示例:
radosgw-admin realm create --rgw-realm=movies --default
- 若集群只有这一个 realm,加
--default:之后radosgw-admin默认使用该 realm; - 不加
--default:后续添加 zonegroup / zone 时必须显式用--rgw-realm或--realm-id。
回显示例:
{
"id": "0956b174-fe14-4f97-8b50-bb7ec5e1cf62",
"name": "movies",
"current_period": "1950b710-3e63-4c41-a19e-46a715000980",
"epoch": 1
}
Ceph 会为 realm 生成唯一 ID,后续可用于重命名 realm。
步骤 2:创建 master zonegroup
radosgw-admin zonegroup create --rgw-zonegroup={name} --endpoints={url} \
[--rgw-realm={realm-name}|--realm-id={realm-id}] --master --default
示例:
radosgw-admin zonegroup create --rgw-zonegroup=us --endpoints=http://rgw1:80 \
--rgw-realm=movies --master --default
回显示例:
{
"id": "f1a233f5-c354-4107-b36c-df66126475a6",
"name": "us",
"api_name": "us",
"is_master": "true",
"endpoints": [
"http:\/\/rgw1:80"
],
"hostnames": [],
"hostnames_s3website": [],
"master_zone": "",
"zones": [],
"placement_targets": [],
"default_placement": "",
"realm_id": "0956b174-fe14-4f97-8b50-bb7ec5e1cf62"
}
步骤 3:创建 master zone
⚠️ zone 必须在将属于该 zone 的 Ceph Object Gateway 节点上创建。
radosgw-admin zone create --rgw-zonegroup={zone-group-name} \
--rgw-zone={zone-name} \
--master --default \
--endpoints={http://fqdn}[,{http://fqdn}]
示例:
radosgw-admin zone create --rgw-zonegroup=us --rgw-zone=us-east \
--master --default \
--endpoints=http://rgw1:80
此处不指定
--access-key和--secret,它们会在下一步创建系统用户后写入 zone。
步骤 4:删除 default zonegroup 与 zone(仅限全新集群)
⚠️ 如果
defaultzone 已在存数据,千万不要删除,否则数据将不可恢复地丢失。
radosgw-admin zonegroup delete --rgw-zonegroup=default --rgw-zone=default
radosgw-admin period update --commit
radosgw-admin zone delete --rgw-zone=default
radosgw-admin period update --commit
radosgw-admin zonegroup delete --rgw-zonegroup=default
radosgw-admin period update --commit
删除 default pools:
ceph osd pool rm default.rgw.control default.rgw.control --yes-i-really-really-mean-it
ceph osd pool rm default.rgw.data.root default.rgw.data.root --yes-i-really-really-mean-it
ceph osd pool rm default.rgw.gc default.rgw.gc --yes-i-really-really-mean-it
ceph osd pool rm default.rgw.log default.rgw.log --yes-i-really-really-mean-it
ceph osd pool rm default.rgw.users.uid default.rgw.users.uid --yes-i-really-really-mean-it
步骤 5:创建系统用户(system user)
ceph-radosgw 守护进程在拉取 realm 和 period 信息前必须完成认证,因此需要在 master zone 创建 --system 用户:
radosgw-admin user create --uid="{user-name}" --display-name="{Display Name}" --system
示例:
radosgw-admin user create --uid="synchronization-user" --display-name="Synchronization User" --system
记下输出中的 access_key 与 secret_key——secondary zone 需要它们来向 master zone 认证。
步骤 6:把系统用户写入 master zone 并提交 period
radosgw-admin zone modify --rgw-zone={zone-name} --access-key={access-key} --secret={secret}
radosgw-admin period update --commit
步骤 7:更新 period
radosgw-admin period update --commit
更新 period 会改变 epoch,并确保其他 zone 能收到更新后的配置。
步骤 8:更新 Ceph 配置文件
在 master zone 主机的 ceph.conf 中,为相应实例条目增加 rgw_zone:
[client.rgw.rgw1]
host = rgw1
rgw frontends = "civetweb port=80"
rgw_zone=us-east
步骤 9:启动网关
systemctl start ceph-radosgw@rgw.`hostname -s`
systemctl enable ceph-radosgw@rgw.`hostname -s`
若集群由 cephadm 部署(容器化),systemctl 无服务对象可用,应改为:
ceph orch apply rgw <name> --realm=<realm> --zone=<zone> --placement --port
2.3 配置 secondary zone
步骤 1:拉取 realm 配置
使用 master zonegroup 中 master zone 的 URL、access key、secret:
radosgw-admin realm pull --url={url-to-master-zone-gateway} \
--access-key={access-key} --secret={secret}
拉取 realm 配置同时会取回远端的当前 period 配置,并使其成为本机的当前 period。
若该 realm 是唯一 realm,设为默认:
radosgw-admin realm default --rgw-realm={realm-name}
步骤 2:创建 secondary zone
radosgw-admin zone create --rgw-zonegroup={zone-group-name} \
--rgw-zone={zone-name} \
--access-key={system-key} --secret={secret} \
--endpoints=http://{fqdn}:80 \
[--read-only]
示例:
radosgw-admin zone create --rgw-zonegroup=us --rgw-zone=us-west \
--access-key={system-key} --secret={secret} \
--endpoints=http://rgw2:80
⚠️ 不要加
--master或--default。Kraken 起所有 zone 默认 active-active;如需 active-passive(secondary 只读),加--read-only。
步骤 3:删除 secondary 上的 default zone / pool(可选,仅限全新)
radosgw-admin zone delete --rgw-zone=default
ceph osd pool rm default.rgw.control default.rgw.control --yes-i-really-really-mean-it
ceph osd pool rm default.rgw.data.root default.rgw.data.root --yes-i-really-really-mean-it
ceph osd pool rm default.rgw.gc default.rgw.gc --yes-i-really-really-mean-it
ceph osd pool rm default.rgw.log default.rgw.log --yes-i-really-really-mean-it
ceph osd pool rm default.rgw.users.uid default.rgw.users.uid --yes-i-really-really-mean-it
步骤 4:更新 Ceph 配置文件
[client.rgw.rgw2]
host = rgw2
rgw frontends = "civetweb port=80"
rgw_zone=us-west
步骤 5:更新 period 并启动网关
radosgw-admin period update --commit
systemctl start ceph-radosgw@rgw.`hostname -s`
systemctl enable ceph-radosgw@rgw.`hostname -s`
2.4 zonegroup / zone 的 ceph.conf 设置项
配置 default zonegroup 与 zone 时,pool 名会带上 zone 名前缀,例如 default.rgw.control。可在每个 [client.radosgw.{instance-name}] 段中修改下列设置:
| 配置项 | 说明 | 类型 | 默认值 |
|---|---|---|---|
rgw_zone |
该网关实例的 zone 名 | String | None(默认值取自 src/common/options/*.yaml.in @ v20.2.4:str,无 default: 字段;未设置时可用 radosgw-admin zone default 配置集群级默认) |
rgw_zonegroup |
该网关实例的 zonegroup 名 | String | None(默认值取自 src/common/options/*.yaml.in @ v20.2.4:str,无 default: 字段;未设置时可用 radosgw-admin zonegroup default 配置集群级默认) |
rgw_zonegroup_root_pool |
zonegroup 的 root pool | String | .rgw.root(默认值取自 src/common/options/*.yaml.in @ v20.2.4) |
rgw_zone_root_pool |
zone 的 root pool | String | .rgw.root(默认值取自 src/common/options/*.yaml.in @ v20.2.4) |
rgw_default_zone_group_info_oid |
存放 default zonegroup 的 OID。官方不建议修改 | String | default.zonegroup(源文表格给出;在 rgw.yaml.in 中未检索到同名条目) |
引自:Multi-Site
三、多站点运维
3.1 查看同步状态
radosgw-admin sync status
典型输出:
realm b3bc1c37-9c44-4b89-a03b-04c269bea5da (earth)
zonegroup f54f9b22-b4b6-4a0e-9211-fa6ac1693f49 (us)
zone adce11c9-b8ed-4a90-8bc5-3fc029ff0816 (us-2)
metadata sync syncing
full sync: 0/64 shards
incremental sync: 64/64 shards
metadata is behind on 1 shards
oldest incremental change not applied: 2017-03-22 10:20:00.0.881361s
data sync source: 341c2d81-4574-4d08-ab0f-5a2a7b168028 (us-1)
syncing
full sync: 0/128 shards
incremental sync: 128/128 shards
data is caught up with source
source: 3b5d1a3f-3f27-4e4a-8f34-6072d4bb1275 (us-3)
syncing
full sync: 0/128 shards
incremental sync: 128/128 shards
data is caught up with source
输出字段逐项解读
| 字段 | 含义 |
|---|---|
realm <id> (<name>) |
当前 realm 的 ID 与名称 |
zonegroup <id> (<name>) |
当前 zonegroup 的 ID 与名称 |
zone <id> (<name>) |
本 zone 的 ID 与名称(即执行命令所在 zone) |
metadata sync syncing |
元数据同步正在进行中(syncing 为状态) |
full sync: 0/64 shards |
还需做全量同步的 shard 数 / 总 shard 数。0/64 表示全量已完成 |
incremental sync: 64/64 shards |
处于增量同步状态的 shard 数 / 总 shard 数。64/64 表示全部进入增量阶段 |
metadata is caught up with master |
元数据已追平 master zone |
metadata is behind on 1 shards |
有 1 个 shard 落后 |
oldest incremental change not applied: <ts> |
最老的尚未应用的增量变更时间 |
data sync source: <id> (<name>) |
一个数据同步源 zone(zonegroup 内其他 zone),每个源一段 |
data is caught up with source |
该 source 的数据已追平 |
(无 caught up 行) |
该 source 尚未追平,需结合 shard 数与时间戳判断 |
shard 的两种类型
| 类型 | 定义 | 处理建议 |
|---|---|---|
| Behind shards | 需要数据同步(全量或增量)才能追上的 shard | 正常等待同步即可 |
| Recovery shards | 同步过程中遇到错误、被标记重试的 shard。错误多为小问题(例如获取 bucket 锁失败) | 通常会自行恢复,无需干预 |
另一份「已追平」的输出示例
realm f3239bc5-e1a8-4206-a81d-e1576480804d (earth)
zonegroup c50dbb7e-d9ce-47cc-a8bb-97d9b399d388 (us)
zone 4c453b70-4a16-4ce8-8185-1893b05d346e (us-west)
metadata sync syncing
full sync: 0/64 shards
metadata is caught up with master
incremental sync: 64/64 shards
data sync source: 1ee9da3e-114d-4ae3-a8a4-056e8a17f532 (us-east)
syncing
full sync: 0/128 shards
incremental sync: 128/128 shards
data is caught up with source
3.2 检查日志
仅多站点部署可检查三类日志:mdlog(元数据日志)、bilog(bucket index 日志)、datalog(数据日志)。可以 list 也可以 trim。
- 通常不需要手工 trim:
rgw_sync_log_trim_interval默认 20 分钟; - 只有当
rgw_sync_log_trim_interval被手工设为0时,才需要 trim。
💡 补充:源文只说明「可以 list 并 trim」这三类日志,但未给出具体的
radosgw-admin mdlog list/bilog list/datalog list命令语法,因此此处不臆造命令。相关子命令语法请见官方adminops.rst(不在本册范围内)。
3.3 故障处理要点
| 现象 | 原因 / 说明 | 处理 |
|---|---|---|
metadata is behind on N shards |
元数据 shard 落后 | 等待;若长时间不收敛,检查 master zone 与网络 |
| 出现 recovery shards | 同步中遇到错误被标记重试(多为锁竞争等小问题) | 通常自愈;持续存在再排查 |
| secondary 上 bucket 操作失败 | bucket 操作会被重定向到 master zone,master 宕机则失败 | 恢复 master,或执行 failover(见 3.5) |
| object 操作在 master 宕机时仍成功 | object 操作不依赖 master zone | 无需处理 |
| 同步的对象内容可能不一致 | 默认不做同步后校验 | 打开 rgw_sync_obj_etag_verify(注意性能损耗) |
| 日志无限增长 | rgw_sync_log_trim_interval 被设为 0 |
恢复为默认值或手工 trim |
3.4 更改 metadata master zone(promote)
⚠️ 极其重要:提升一个尚未完成元数据同步的 zone 为 master,会导致它无法服务剩余条目,这些元数据变更会丢失。建议先等该 zone 的
radosgw-admin sync status完成元数据同步,再执行 promote。同样地,若 promote 期间旧 master 仍在处理元数据变更,这些变更很可能丢失。建议先关闭旧 master zone 上的所有 radosgw 实例;新 master 提升完成后,旧 master 可用
radosgw-admin period pull拉取新 period 并重启网关。
将 zonegroup us 中的 zone us-2 提升为 metadata master:
radosgw-admin zone modify --rgw-zone=us-2 --master
radosgw-admin zonegroup modify --rgw-zonegroup=us --master
radosgw-admin period update --commit
这会生成一个新的 period,us-2 中的 radosgw 实例会把该 period 推送给其他 zone。
3.5 Failover(故障切换到 secondary zone)
-
让 secondary zone 成为 master 与 default zone:
radosgw-admin zone modify --rgw-zone={zone-name} --master --default若集群是 active-passive(secondary 为 read-only),需要去掉只读状态才能写入:
radosgw-admin zone modify --rgw-zone={zone-name} --master --default \ --read-only=false -
更新 period 使变更生效:
radosgw-admin period update --commit -
重启 Ceph Object Gateway:
systemctl restart ceph-radosgw@rgw.`hostname -s`
3.6 Failback(从故障切换恢复原状)
原 master zone 恢复后,按顺序执行:
# 1. 在已恢复的 zone 上,从当前 master zone 拉取最新 realm 配置
radosgw-admin realm pull --url={url-to-master-zone-gateway} \
--access-key={access-key} --secret={secret}
# 2. 让已恢复的 zone 重新成为 master 与 default zone
radosgw-admin zone modify --rgw-zone={zone-name} --master --default
# 3. 更新 period
radosgw-admin period update --commit
# 4. 重启已恢复 zone 的 Ceph Object Gateway
systemctl restart ceph-radosgw@rgw.`hostname -s`
# 5. 若 secondary 需要回到只读配置
radosgw-admin zone modify --rgw-zone={zone-name} --read-only
# 6. 更新 period
radosgw-admin period update --commit
# 7. 重启 secondary zone 的 Ceph Object Gateway
systemctl restart ceph-radosgw@rgw.`hostname -s`
3.7 从单站点迁移到多站点
若已有一个使用 default zonegroup / zone 的单站点部署,按以下步骤迁移:
# 1. 创建 realm
radosgw-admin realm create --rgw-realm=<name> --default
# 2. 重命名 default zonegroup 与 zone
radosgw-admin zonegroup rename --rgw-zonegroup default --zonegroup-new-name=<name>
radosgw-admin zone rename --rgw-zone default --zone-new-name us-east-1 --rgw-zonegroup=<name>
# 3. 重命名 default zonegroup 的 api_name
radosgw-admin zonegroup modify --api-name=<name> --rgw-zonegroup=<name>
# 4. 配置 master zonegroup
radosgw-admin zonegroup modify --rgw-realm=<name> --rgw-zonegroup=<name> \
--endpoints http://<fqdn>:80 --master --default
# 5. 配置 master zone
radosgw-admin zone modify --rgw-realm=<name> --rgw-zonegroup=<name> \
--rgw-zone=<name> --endpoints http://<fqdn>:80 \
--access-key=<access-key> --secret=<secret-key> \
--master --default
# 6. 创建系统用户
radosgw-admin user create --uid=<user-id> \
--display-name="<display-name>" \
--access-key=<access-key> \
--secret=<secret-key> --system
# 7. 提交更新后的配置
radosgw-admin period update --commit
# 8. 重启 Ceph Object Gateway
systemctl restart ceph-radosgw@rgw.`hostname -s`
完成后,再按 2.3 节在 master zonegroup 中创建 secondary zone。
3.8 realm / zonegroup / zone 的完整命令参考
Realms
| 操作 | 命令 |
|---|---|
| 创建 realm | radosgw-admin realm create --rgw-realm={realm-name} [--default] |
| 设为默认 realm | radosgw-admin realm default --rgw-realm=movies |
| 删除 realm | radosgw-admin realm rm --rgw-realm={realm-name} |
| 获取 realm | radosgw-admin realm get --rgw-realm=<name> |
| 设置 realm(从文件) | radosgw-admin realm set --rgw-realm=<name> --infile=<infilename> |
| 列出 realm | radosgw-admin realm list |
| 列出 realm periods | radosgw-admin realm list-periods |
| 拉取 realm | radosgw-admin realm pull --url={url-to-master-zone-gateway} --access-key={access-key} --secret={secret} |
| 重命名 realm | radosgw-admin realm rename --rgw-realm=<current-name> --realm-new-name=<new-realm-name> |
realm get 输出示例:
{
"id": "0a68d52e-a19c-4e8e-b012-a8f831cb3ebc",
"name": "movies",
"current_period": "b0c5bbef-4337-4edd-8184-5aeab2ec413b",
"epoch": 1
}
⚠️ realm 重命名注意事项:realm 不属于 period,因此重命名只作用于本地,执行
realm pull时不会被拉走。若 realm 含多个 zone,需在每个 zone 上各执行一次 rename。⚠️ 不要用
realm set修改name参数——那只会改内部名,--rgw-realm仍会期待旧名字。
每个 realm 关联一个 period;每次修改 zonegroup 或 zone 后都应更新并提交 period。
Zonegroups
| 操作 | 命令 | 是否需要 period update --commit |
|---|---|---|
| 创建 | radosgw-admin zonegroup create --rgw-zonegroup=<name> [--rgw-realm=<name>][--master] [--default] |
视情况 |
| 修改 | radosgw-admin zonegroup modify --rgw-zonegroup=<zonegroup-name> ... |
视情况 |
| 设为默认 | radosgw-admin zonegroup default --rgw-zonegroup=comedy |
是 |
| 添加 zone | radosgw-admin zonegroup add --rgw-zonegroup=<name> --rgw-zone=<name> |
是 |
| 移除 zone | radosgw-admin zonegroup remove --rgw-zonegroup=<name> --rgw-zone=<name> |
是 |
| 重命名 | radosgw-admin zonegroup rename --rgw-zonegroup=<name> --zonegroup-new-name=<name> |
是 |
| 删除 | radosgw-admin zonegroup delete --rgw-zonegroup=<name> |
是 |
| 列表 | radosgw-admin zonegroup list |
否 |
| 获取 zonegroup map | radosgw-admin zonegroup-map get |
否 |
| 获取单个 zonegroup | radosgw-admin zonegroup get [--rgw-zonegroup=<zonegroup>] |
否 |
| 设置 zonegroup(从文件) | radosgw-admin zonegroup set --infile zonegroup.json |
是 |
| 设置 zonegroup map | radosgw-admin zonegroup-map set --infile zonegroupmap.json |
是 |
zonegroup list 输出示例:
{
"default_info": "90b28698-e7c3-462c-a42d-4aa780d24eda",
"zonegroups": [
"us"
]
}
若收到
failed to read zonegroup map错误,先以 root 身份执行radosgw-admin zonegroup-map update。
zonegroup get 输出示例:
{
"id": "90b28698-e7c3-462c-a42d-4aa780d24eda",
"name": "us",
"api_name": "us",
"is_master": "true",
"endpoints": [
"http:\/\/rgw1:80"
],
"hostnames": [],
"hostnames_s3website": [],
"master_zone": "9248cab2-afe7-43d8-a661-a40bf316665e",
"zones": [
{
"id": "9248cab2-afe7-43d8-a661-a40bf316665e",
"name": "us-east",
"endpoints": [
"http:\/\/rgw1"
],
"log_meta": "true",
"log_data": "true",
"bucket_index_max_shards": 0,
"read_only": "false"
},
{
"id": "d1024e59-7d28-49d1-8222-af101965a939",
"name": "us-west",
"endpoints": [
"http:\/\/rgw2:80"
],
"log_meta": "false",
"log_data": "true",
"bucket_index_max_shards": 0,
"read_only": "false"
}
],
"placement_targets": [
{
"name": "default-placement",
"tags": []
}
],
"default_placement": "default-placement",
"realm_id": "ae031368-8715-4e27-9a99-0c9468852cfe"
}
zonegroup 配置项(用于 zonegroup set 的 JSON)逐项说明:
| 序号 | 字段 | 必填 | 说明 |
|---|---|---|---|
| 1 | name |
必填 | zonegroup 名称 |
| 2 | api_name |
可选 | zonegroup 的 API 名 |
| 3 | is_master |
必填 | 是否为 master zonegroup。只能有一个 master zonegroup |
| 4 | endpoints |
可选 | zonegroup 内所有 endpoint 列表。可用多个域名指向同一 zonegroup;正斜杠需转义为 \/;可带端口 fqdn:port |
| 5 | hostnames |
可选 | zonegroup 内所有 hostname 列表。rgw dns name 设置会自动加入此列表;修改后需重启网关守护进程 |
| 6 | master_zone |
可选 | zonegroup 的 master zone。未指定则用 default zone。每个 zonegroup 只能有一个 master zone |
| 7 | zones |
可选 | zonegroup 内所有 zone 的列表。每个 zone 含 name(必填)、endpoints(可选)、以及是否记录 metadata/data 操作的开关(默认 false) |
| 8 | placement_targets |
可选 | placement target 列表。每个 target 含 name(必填)与 tags(可选,用于限制只有带该 tag 的用户可用,对应用户信息中的 placement_tags 字段) |
| 9 | default_placement |
可选 | 对象索引与对象数据的默认 placement target,默认 default-placement;也可在用户信息中为每个用户设置默认 placement |
⚠️
defaultzonegroup 的is_master默认为true。若要新建 zonegroup 并使其成为 master,必须先把defaultzonegroup 的is_master设为false,或删除defaultzonegroup。
zonegroup map 示例(含 master_zonegroup 与配额):
{
"zonegroups": [
{
"key": "90b28698-e7c3-462c-a42d-4aa780d24eda",
"val": {
"id": "90b28698-e7c3-462c-a42d-4aa780d24eda",
"name": "us",
"api_name": "us",
"is_master": "true",
"endpoints": [
"http:\/\/rgw1:80"
],
"hostnames": [],
"hostnames_s3website": [],
"master_zone": "9248cab2-afe7-43d8-a661-a40bf316665e",
"zones": [
{
"id": "9248cab2-afe7-43d8-a661-a40bf316665e",
"name": "us-east",
"endpoints": [
"http:\/\/rgw1"
],
"log_meta": "true",
"log_data": "true",
"bucket_index_max_shards": 0,
"read_only": "false"
},
{
"id": "d1024e59-7d28-49d1-8222-af101965a939",
"name": "us-west",
"endpoints": [
"http:\/\/rgw2:80"
],
"log_meta": "false",
"log_data": "true",
"bucket_index_max_shards": 0,
"read_only": "false"
}
],
"placement_targets": [
{
"name": "default-placement",
"tags": []
}
],
"default_placement": "default-placement",
"realm_id": "ae031368-8715-4e27-9a99-0c9468852cfe"
}
}
],
"master_zonegroup": "90b28698-e7c3-462c-a42d-4aa780d24eda",
"bucket_quota": {
"enabled": false,
"max_size_kb": -1,
"max_objects": -1
},
"user_quota": {
"enabled": false,
"max_size_kb": -1,
"max_objects": -1
}
}
zonegroup map 中只能有一个
is_master为true的 zonegroup,且必须在 map 末尾被指定为master_zonegroup。
Zones
| 操作 | 命令 |
|---|---|
| 创建 | 见下方代码块 |
| 删除 | 先从 zonegroup 移除 → period update --commit → zone delete → period update --commit |
| 修改 | radosgw-admin zone modify [options] |
| 列表 | radosgw-admin zone list |
| 获取 | radosgw-admin zone get [--rgw-zone=<zone>] |
| 设置(从文件) | radosgw-admin zone set --rgw-zone={zone-name} --infile zone.json → period update --commit |
| 重命名 | radosgw-admin zone rename --rgw-zone=<name> --zone-new-name=<name> → period update --commit |
创建 zone:
radosgw-admin zone create --rgw-zone=<name> \
[--zonegroup=<zonegroup-name] \
[--endpoints=<endpoint>[,<endpoint>] \
[--master] [--default] \
--access-key $SYSTEM_ACCESS_KEY \
--secret $SYSTEM_SECRET_KEY
radosgw-admin period update --commit
只有 zonegroup 中的一个 zone 可以是 master zone(
--master)。加--rgw-zonegroup可把新 zone 直接加入指定 zonegroup。
删除 zone:
radosgw-admin zonegroup remove --zonegroup=<name> \
--zone=<name>
radosgw-admin period update --commit
radosgw-admin zone delete --rgw-zone<name>
radosgw-admin period update --commit
⚠️ 必须先从 zonegroup 移除再删除 zone,否则
period update会失败。
删除已删除 zone 的 pool(仅当这些 pool 不再被使用):
ceph osd pool rm <del-zone>.rgw.control <del-zone>.rgw.control --yes-i-really-really-mean-it
ceph osd pool rm <del-zone>.rgw.meta <del-zone>.rgw.meta --yes-i-really-really-mean-it
ceph osd pool rm <del-zone>.rgw.log <del-zone>.rgw.log --yes-i-really-really-mean-it
ceph osd pool rm <del-zone>.rgw.otp <del-zone>.rgw.otp --yes-i-really-really-mean-it
ceph osd pool rm <del-zone>.rgw.buckets.index <del-zone>.rgw.buckets.index --yes-i-really-really-mean-it
ceph osd pool rm <del-zone>.rgw.buckets.non-ec <del-zone>.rgw.buckets.non-ec --yes-i-really-really-mean-it
ceph osd pool rm <del-zone>.rgw.buckets.data <del-zone>.rgw.buckets.data --yes-i-really-really-mean-it
⚠️ 只删除带 zone 名前缀的 pool。 删除 root pool(例如
.rgw.root)会清掉整个系统的配置。 ⚠️ pool 一旦删除,其中的数据将不可恢复地丢失。
zone modify 支持的 options:
| 选项 | 说明 |
|---|---|
--access-key=<key> |
系统用户 access key |
--secret/--secret-key=<key> |
系统用户 secret key |
--master |
设为 master zone |
--default |
设为 default zone |
--endpoints=<list> |
endpoint 列表 |
修改后需执行 radosgw-admin period update --commit。
default zone 的 zone get 输出示例(pool 布局):
{ "domain_root": ".rgw",
"control_pool": ".rgw.control",
"gc_pool": ".rgw.gc",
"log_pool": ".log",
"intent_log_pool": ".intent-log",
"usage_log_pool": ".usage",
"user_keys_pool": ".users",
"user_email_pool": ".users.email",
"user_swift_pool": ".users.swift",
"user_uid_pool": ".users.uid",
"system_key": { "access_key": "", "secret_key": ""},
"placement_pools": [
{ "key": "default-placement",
"val": { "index_pool": ".rgw.buckets.index",
"data_pool": ".rgw.buckets"}
}
]
}
引自:Multi-Site
四、同步策略(Multisite Sync Policy)
.. versionadded:: Octopus
4.1 为什么需要 sync policy
| 旧机制 | 新机制 |
|---|---|
zonegroup 级粗粒度配置(sync_from*) |
sync policy 取代之 |
| 所有 bucket 被对称对待:每个 data zone 持有该 bucket 的一份镜像,与其他 zone 相同 | bucket 可以发散(diverge):一个 bucket 可以从不同 zone 中不同名、不同 ID 的 bucket 拉数据 |
| 同步源与同步目的总是同一个 bucket | 不再如此 |
sync policy 可以配置在 zonegroup 级(一旦配置就取代旧式 sync_from* 配置),也可以配置在 bucket 级。
4.2 三层模型:group / flow / pipe
| 层级 | 名称 | 作用 | 可否在 bucket 级定义 |
|---|---|---|---|
| 第 1 层 | group(sync policy group) | 策略容器,带 status;内部含 flow 列表与 pipe 列表 | 可 |
| 第 2 层 | flow(data-flow) | 定义 zone 之间数据流动方向 | bucket 策略继承 zonegroup 的 flow,只能是 zonegroup flow 的子集 |
| 第 3 层 | pipe | 定义实际哪些 bucket 可以使用这些 flow,以及关联属性(如源对象前缀、tag、目标 owner、storage class 等) | 可 |
flow 的两种类型:
| flow-type | 说明 | 参数 |
|---|---|---|
symmetrical |
对称数据流:一组 zone 之间互相同步 | --zones=<zone1,zone2,...> |
directional |
定向数据流:数据从 source zone 单向流向 dest zone | --source-zone=<zone> --dest-zone=<zone> |
通配符(wildcard):
- zone 通配符
*:匹配所有符合 flow 规则的 zone; - bucket 通配符
*:在 bucket policy 上下文中表示当前 bucket 实例。
4.3 group 状态机与优先级
一个 sync policy group 可以处于 3 种状态:
| 状态值 | 含义 | 说明 |
|---|---|---|
enabled |
sync is allowed and enabled | 同步被允许且已启用 |
allowed |
sync is allowed | 同步被允许但未启用(需由 bucket 级策略 enable 才真正同步) |
forbidden |
sync(按本 group 定义)不被允许,可以覆盖其他 group | 优先级最高,用于禁止 |
优先级判定(由高到低):
| 优先级 | 状态 | 效果 |
|---|---|---|
| 1(最高) | forbidden |
覆盖其他 group 的允许/启用结论 |
| 2 | enabled |
实际执行同步 |
| 3(最低) | allowed |
允许但不主动执行;bucket 级可提升为 enabled |
💡 补充:官方源文给出了三种状态的语义,但没有给出一张显式的「优先级表」。上表的优先级顺序由以下源文事实推导:(a)
forbidden的描述中明确写了 "can override other groups";(b)allowed的描述是 "sync is allowed" 而enabled是 "sync is allowed and enabled",且 Example 3 展示了「zonegroup 设为 allowed + bucket 设为 enabled」才真正同步的用法。
bucket 级策略的继承与限制:
| 维度 | 规则 |
|---|---|
| data flow | bucket 级策略继承 zonegroup 策略的 flow,只能是其子集 |
| pipe | 同理,但 bucket 策略可以 enable 那些在 zonegroup 策略中未 enabled(但并非 forbidden)的 pipe |
⚠️ 重要:
- 对 zonegroup 策略的任何修改都必须在 zonegroup 的 master zone 上执行,并且需要
period update --commit;- 对 bucket 策略的修改也必须在 zonegroup 的 master zone 上执行,但不需要 period update,RGW 会动态处理。
推荐实践(源文建议):
整 zone 镜像的容灾场景无需在 bucket 上配置任何东西。若要精细控制 bucket 级同步,推荐做法是:在 zonegroup 层用通配符把 pipe 设为 allowed,然后在 bucket 层把特定同步设为 enabled;必要时再用 bucket 级策略把数据流动限制在特定 zone。
4.4 S3 Replication API
| 项 | 说明 |
|---|---|
| 已实现 | S3 bucket replication API,允许用户在不同 bucket 之间创建复制规则 |
| 与 AWS 差异 | AWS 的 replication 允许同一 zone 内的 bucket 复制;RGW 目前不允许 |
| RGW 扩展 | RGW 的 API 新增了一个 Zone 数组,允许用户选择该 bucket 同步到哪些 zone |
4.5 配置命令全集
获取策略
radosgw-admin sync policy get [--bucket=<bucket>]
创建 / 修改 / 查看 / 删除 group
# 创建
radosgw-admin sync group create [--bucket=<bucket>] \
--group-id=<group-id> \
--status=<enabled | allowed | forbidden>
# 修改
radosgw-admin sync group modify [--bucket=<bucket>] \
--group-id=<group-id> \
--status=<enabled | allowed | forbidden>
# 查看
radosgw-admin sync group get [--bucket=<bucket>] \
--group-id=<group-id>
# 删除
radosgw-admin sync group remove [--bucket=<bucket>] \
--group-id=<group-id>
创建 / 删除 flow
# 创建或更新 directional flow
radosgw-admin sync group flow create [--bucket=<bucket>] \
--group-id=<group-id> \
--flow-id=<flow-id> \
--flow-type=directional \
--source-zone=<source_zone> \
--dest-zone=<dest_zone>
# 创建或更新 symmetrical flow(zones 为逗号分隔列表)
radosgw-admin sync group flow create [--bucket=<bucket>] \
--group-id=<group-id> \
--flow-id=<flow-id> \
--flow-type=symmetrical \
--zones=<zones>
# 删除 directional flow
radosgw-admin sync group flow remove [--bucket=<bucket>] \
--group-id=<group-id> \
--flow-id=<flow-id> \
--flow-type=directional \
--source-zone=<source_zone> \
--dest-zone=<dest_zone>
# 从 symmetrical flow 中移除指定 zone
radosgw-admin sync group flow remove [--bucket=<bucket>] \
--group-id=<group-id> \
--flow-id=<flow-id> \
--flow-type=symmetrical \
--zones=<zones>
# 删除整个 symmetrical flow
radosgw-admin sync group flow remove [--bucket=<bucket>] \
--group-id=<group-id> \
--flow-id=<flow-id> \
--flow-type=symmetrical
创建 / 删除 pipe
# 创建或更新 pipe 参数
radosgw-admin sync group pipe create [--bucket=<bucket>] \
--group-id=<group-id> \
--pipe-id=<pipe-id> \
--source-zones=<source_zones> \
[--source-bucket=<source_buckets>] \
[--source-bucket-id=<source_bucket_id>] \
--dest-zones=<dest_zones> \
[--dest-bucket=<dest_buckets>] \
[--dest-bucket-id=<dest_bucket_id>] \
[--prefix=<source_prefix>] \
[--prefix-rm] \
[--tags-add=<tags>] \
[--tags-rm=<tags>] \
[--dest-owner=<owner>] \
[--storage-class=<storage_class>] \
[--mode=<system | user>] \
[--uid=<user_id>]
# 删除 pipe 的特定参数或整个 pipe
radosgw-admin sync group pipe remove [--bucket=<bucket>] \
--group-id=<group-id> \
--pipe-id=<pipe-id> \
[--source-zones=<source_zones>] \
[--source-bucket=<source_buckets>] \
[--source-bucket-id=<source_bucket_id>] \
[--dest-zones=<dest_zones>] \
[--dest-bucket=<dest_buckets>] \
[--dest-bucket-id=<dest_bucket_id>]
pipe 参数逐项说明:
| 参数 | 取值 | 说明 |
|---|---|---|
--source-zones |
zone 列表或 * |
通配符表示任何符合 flow 规则的 zone |
--dest-zones |
zone 列表或 * |
同上 |
--source-bucket |
bucket 名或 * |
通配符表示当前 bucket(在 bucket policy 上下文中) |
--dest-bucket |
bucket 名或 * |
同上 |
--source-bucket-id |
bucket ID | 指定源 bucket 的 ID |
--dest-bucket-id |
bucket ID | 指定目的 bucket 的 ID |
--prefix |
字符串 | 过滤源对象前缀 |
--prefix-rm |
开关 | 移除已设置的 prefix |
--tags-add |
key=value 逗号列表 |
追加 tag 过滤条件 |
--tags-rm |
key=value 逗号列表 |
移除 tag 过滤条件 |
--dest-owner |
owner 名 | 强制指定目标对象的 owner;若选择 user 模式,只能设置目标 bucket 的 owner |
--storage-class |
storage class 名 | 指定目标对象的 storage class |
--mode |
system | user |
执行同步操作时所使用的模式 |
--uid |
user id | user 模式下以哪个用户身份执行(用于权限校验) |
prefix 与 tags 可以组合,此时对象必须同时满足两者才会被同步。还可以使用
priority参数:当多条规则匹配且源/目的相同时,用它决定采用哪条规则。
查看 sync info
radosgw-admin sync info [--bucket=<bucket>] \
[--effective-zone-name=<zone>]
获取按 sync policy 推导出的预期同步源与目标。
输出字段说明:
| 字段 | 含义 |
|---|---|
sources |
本 bucket 期望的同步来源条目 |
dests |
本 bucket 期望的同步目标条目 |
hints |
bucket 依赖提示(其他 bucket 的 policy 间接引用了本 bucket) |
resolved-hints-1 / resolved-hints |
已解析的依赖提示(resolved hints) |
由于 bucket 可以定义「从我这里流向另一个 zone 的另一个 bucket」的策略,创建策略时还会生成一份 bucket 依赖列表,作为同步时的提示(hints)。一个 bucket 引用另一个 bucket 并不意味着真的会同步——还要看 data flow 是否允许。
4.6 十个官方示例
示例环境:3 个 zone ——
us-east(master zone)、us-west、us-west-2。
示例 1:两 zone 完全镜像
radosgw-admin sync group create --group-id=group1 --status=allowed
radosgw-admin sync group flow create --group-id=group1 \
--flow-id=flow-mirror --flow-type=symmetrical \
--zones=us-east,us-west
radosgw-admin sync group pipe create --group-id=group1 \
--pipe-id=pipe1 --source-zones='*' \
--source-bucket='*' --dest-zones='*' \
--dest-bucket='*'
radosgw-admin sync group modify --group-id=group1 --status=enabled
radosgw-admin period update --commit
radosgw-admin sync info --bucket=buck
在 us-east 上查看:
{
"sources": [
{
"id": "pipe1",
"source": { "zone": "us-west", "bucket": "buck:115b12b3-....4409.1" },
"dest": { "zone": "us-east", "bucket": "buck:115b12b3-....4409.1" },
"params": { }
}
],
"dests": [
{
"id": "pipe1",
"source": { "zone": "us-east", "bucket": "buck:115b12b3-....4409.1" },
"dest": { "zone": "us-west", "bucket": "buck:115b12b3-....4409.1" }
}
]
}
输出中的
"id"字段反映的是生成该条目的 pipe 规则;单条规则可以生成多个同步条目。
在 us-west 上查看时,source/dest 方向相反(sources 为 us-east→us-west,dests 为 us-west→us-east)。
示例 2:定向 —— 整个 zone 备份
新增第三个 zone us-west-2,作为 us-west 的副本,但数据不回流:
radosgw-admin sync group flow create --group-id=group1 \
--flow-id=us-west-backup --flow-type=directional \
--source-zone=us-west --dest-zone=us-west-2
radosgw-admin period update --commit
us-west的sync info会有两个 dests(us-east与us-west-2);us-west-2只有 sources,dests为空数组。
示例 3:只镜像特定 bucket
先把 zonegroup 组改为 allowed(允许但未启用):
radosgw-admin sync group modify --group-id=group1 --status=allowed
radosgw-admin period update --commit
再为已存在的 bucket buck2 创建 bucket 级策略:
radosgw-admin sync group create --bucket=buck2 \
--group-id=buck2-default --status=enabled
radosgw-admin sync group pipe create --bucket=buck2 \
--group-id=buck2-default --pipe-id=pipe1 \
--source-zones='*' --dest-zones='*'
注意:
- bucket 必须先存在才能设置其策略;
- 修改 bucket 策略的管理命令必须在 master zone 上运行;
- 不需要 period update;
- 无需改动 data flow——它继承自 zonegroup 策略。
示例 4:把 bucket 同步限制到特定 zone
只把 buck3 同步到 us-east(从任何 flow 允许同步进 us-east 的 zone):
radosgw-admin sync group create --bucket=buck3 \
--group-id=buck3-default --status=enabled
radosgw-admin sync group pipe create --bucket=buck3 \
--group-id=buck3-default --pipe-id=pipe1 \
--source-zones='*' --dest-zones=us-east
示例 5:从不同的 bucket 同步
注意:bucket 同步目前只在跨 zone 之间生效,同一 zone 内不生效。
让 buck4 从 buck5 拉数据:
radosgw-admin sync group create --bucket=buck4 \
--group-id=buck4-default --status=enabled
radosgw-admin sync group pipe create --bucket=buck4 \
--group-id=buck4-default --pipe-id=pipe1 \
--source-zones='*' --source-bucket=buck5 \
--dest-zones='*'
限制为只同步源自 us-west 的数据:
radosgw-admin sync group pipe modify --bucket=buck4 \
--group-id=buck4-default --pipe-id=pipe1 \
--source-zones=us-west --source-bucket=buck5 \
--dest-zones='*'
在 us-west 上查看 buck5 的 sync info(注意 buck5 自身策略为空,但能间接发现 buck4 在从它同步):
{
"sources": [],
"dests": [],
"hints": { "sources": [], "dests": [ "buck4:115b12b3-....14433.2" ] },
"resolved-hints-1": {
"sources": [],
"dests": [
{ "id": "pipe1", "source": { "zone": "us-west", "bucket": "buck5" },
"dest": { "zone": "us-east", "bucket": "buck4:115b12b3-....14433.2" } },
{ "id": "pipe1", "source": { "zone": "us-west", "bucket": "buck5" },
"dest": { "zone": "us-west-2", "bucket": "buck4:115b12b3-....14433.2" } }
]
},
"resolved-hints": { "sources": [], "dests": [] }
}
示例 6:同步到不同的 bucket
让 buck6 「推」数据到 buck5:
radosgw-admin sync group create --bucket=buck6 \
--group-id=buck6-default --status=enabled
radosgw-admin sync group pipe create --bucket=buck6 \
--group-id=buck6-default --pipe-id=pipe1 \
--source-zones='*' --source-bucket='*' \
--dest-zones='*' --dest-bucket=buck5
通配符 bucket 名在 bucket sync policy 上下文中表示当前 bucket。 与示例 5 组合后:写数据到
us-east的buck6→ 同步到us-west的buck5→ 再分发到us-east与us-west-2的buck4。 注意:内部实现上,数据始终是由目的 zone 从源端拉取的(pull)。
示例 7:源端过滤(prefix / tags)
只同步以 foo/ 开头的对象(buck8 → buck9):
radosgw-admin sync group create --bucket=buck8 \
--group-id=buck8-default --status=enabled
radosgw-admin sync group pipe create --bucket=buck8 \
--group-id=buck8-default --pipe-id=pipe-prefix \
--prefix=foo/ --source-zones='*' --dest-zones='*' \
--dest-bucket=buck9
同步带 color=blue 或 color=red tag 的对象:
radosgw-admin sync group pipe create --bucket=buck8 \
--group-id=buck8-default --pipe-id=pipe-tags \
--tags-add=color=blue,color=red --source-zones='*' \
--dest-zones='*' --dest-bucket=buck9
在 us-east 查看 buck8:
{
"sources": [],
"dests": [
{
"id": "pipe-prefix",
"source": { "zone": "us-east", "bucket": "buck8:115b12b3-....14433.5" },
"dest": { "zone": "us-west", "bucket": "buck9" },
"params": { "source": { "filter": { "prefix": "foo/", "tags": [] } } }
},
{
"id": "pipe-tags",
"source": { "zone": "us-east", "bucket": "buck8:115b12b3-....14433.5" },
"dest": { "zone": "us-west", "bucket": "buck9" },
"params": { "source": { "filter": { "tags": [
{ "key": "color", "value": "blue" },
{ "key": "color", "value": "red" }
] } } }
}
]
}
这里没有 sources,只有两个不同的 dests(每条配置一个)。同步发生时,会为每个对象选择匹配的规则。
示例 8:目的端参数 —— storage class
radosgw-admin sync group create --bucket=buck10 \
--group-id=buck10-default --status=enabled
radosgw-admin sync group pipe create --bucket=buck10 \
--group-id=buck10-default \
--pipe-id=pipe-storage-class \
--source-zones='*' --dest-zones=us-west-2 \
--storage-class=CHEAP_AND_SLOW
示例 9:目的端参数 —— 目标 owner 转换
把目标对象 owner 设为目标 bucket 的 owner(需指定目标 bucket 的 uid):
radosgw-admin sync group create --bucket=buck11 \
--group-id=buck11-default --status=enabled
radosgw-admin sync group pipe create --bucket=buck11 \
--group-id=buck11-default --pipe-id=pipe-dest-owner \
--source-zones='*' --dest-zones='*' \
--dest-bucket=buck12 --dest-owner=joe
示例 10:目的端参数 —— user mode
user mode 会确保该用户既有读源对象的权限、又有写目标 bucket 的权限,因此需要指定执行上下文用户的 uid:
radosgw-admin sync group pipe modify --bucket=buck11 \
--group-id=buck11-default --pipe-id=pipe-dest-owner \
--mode=user --uid=jenny
五、同步模块(Sync Modules)
.. versionadded:: Kraken
5.1 概念与原理
| 项 | 说明 |
|---|---|
| 定位 | Sync Modules 构建在 multisite 框架之上,用于把数据和元数据转发到外部 tier |
| 触发 | 每当数据发生变化时执行一组动作(元数据操作,如创建 bucket / user,也视为「数据变更」) |
| 一致性模型 | 远端站点是最终一致的,变更异步传播 |
| 典型场景 | 把对象存储备份到外部云集群或自定义备份方案(磁带库);把元数据索引到 ElasticSearch |
| 配置作用域 | sync module 配置是 zone 本地的 |
| 导出/消费 | sync module 决定该 zone 是导出数据,还是只能消费在其他 zone 上被修改的数据 |
已支持的 sync plugin(自 Luminous):
| plugin | tier-type | 说明 |
|---|---|---|
rgw |
(默认) | 默认 sync plugin,在 zone 之间同步数据。无需显式配置 |
elasticsearch |
elasticsearch |
把其他 zone 的元数据写入 ElasticSearch |
log |
log |
一个简单(trivial)的 sync plugin,记录远端 zone 中发生的元数据操作 |
cloud |
cloud |
把 zone 数据同步到远端云服务(见 5.4) |
archive |
archive |
归档 zone,保留对象历史版本(见 5.5) |
5.2 配置流程(通用)
假设已有 us-east、us-west 两个 zone,现在要新增第三个 zone us-east-es,它只处理来自其他站点的元数据(可与 us-east 在同一或不同 Ceph 集群),且该 zone 的 RGW 不直接服务终端用户请求。
# 1. 创建第三个 zone
radosgw-admin zone create --rgw-zonegroup=us --rgw-zone=us-east-es \
--access-key={system-key} --secret={secret} --endpoints=http://rgw-es:80
# 2. 为该 zone 配置 sync module
radosgw-admin zone modify --rgw-zone={zone-name} --tier-type={tier-type} --tier-config={set of key=value pairs}
# 例如 elasticsearch
radosgw-admin zone modify --rgw-zone={zone-name} --tier-type=elasticsearch \
--tier-config=endpoint=http://localhost:9200,num_shards=10,num_replicas=1
# 3. 更新 period
radosgw-admin period update --commit
# 4. 启动该 zone 的 radosgw
systemctl start ceph-radosgw@rgw.`hostname -s`
systemctl enable ceph-radosgw@rgw.`hostname -s`
5.3 ElasticSearch Sync Module
.. versionadded:: Kraken⚠️ 注意:截至 2020 年 5 月 31 日,只支持 Elasticsearch 6 及以下,不支持 Elasticsearch 7。
写入 ElasticSearch 的元数据格式(自 Luminous 起)
{
"_index" : "rgw-gold-ee5863d6",
"_type" : "object",
"_id" : "34137443-8592-48d9-8ca7-160255d52ade.34137.1:object1:null",
"_score" : 1.0,
"_source" : {
"bucket" : "testbucket123",
"name" : "object1",
"instance" : "null",
"versioned_epoch" : 0,
"owner" : {
"id" : "user1",
"display_name" : "user1"
},
"permissions" : [
"user1"
],
"meta" : {
"size" : 712354,
"mtime" : "2017-05-04T12:54:16.462Z",
"etag" : "7ac66c0f148de9519b8bd264312c4d64"
}
}
}
ElasticSearch tier-type 配置项
| 配置项 | 类型 | 说明 |
|---|---|---|
endpoint |
string | 要访问的 Elasticsearch 服务端点 |
num_shards |
integer | 数据同步初始化时 Elasticsearch 配置的 shard 数。初始化后不可更改——任何变更都需要重建 Elasticsearch 索引并重新初始化数据同步流程 |
num_replicas |
integer | 数据同步初始化时 Elasticsearch 配置的副本数 |
explicit_custom_meta |
true | false | 是否索引所有用户自定义元数据;若为 false,用户需在 bucket 级配置要索引哪些自定义元数据项。默认 false |
index_buckets_list |
逗号分隔字符串列表 | 为空则索引所有 bucket;否则只索引此处列出的 bucket。支持前缀(foo*)与后缀(*bar) |
approved_owners_list |
逗号分隔字符串列表 | 为空则(在其他限制之下)索引所有 owner 的 bucket;否则只索引指定 owner 的 bucket。同样支持前缀/后缀 |
override_index_path |
string | 非空则用作 elasticsearch 索引路径;否则索引路径在同步初始化时自动生成 |
终端用户元数据查询
.. versionadded:: Luminous
| 关注点 | 说明 |
|---|---|
| 安全 | 由于 ES 集群现在存有对象元数据,ES endpoint 不应暴露给公网,只能由集群管理员访问 |
| 鉴权难题 | 直接让用户查 ES 需要 ES 以类似 RGW 的方式认证用户,这不现实 |
| 解决方案 | Luminous 起,metadata master zone 的 RGW 可以直接服务终端用户请求,无需暴露 ES endpoint,且由 RGW 完成认证与授权 |
| API 位置 | bucket API 中新增查询,所有请求必须发往 metadata master zone |
查询语法:
GET /{bucket}?query={query-expr}
请求参数:
| 参数 | 说明 |
|---|---|
max-keys |
返回的最大条目数 |
marker |
分页 marker |
表达式语法:
expression := [(]<arg> <op> <value> [)][<and|or> ...]
支持的 op:<、<=、==、>=、>
示例:
GET /?query=name==foo
返回用户有读权限、且名为 foo 的所有已索引 key。输出为 XML 的 key 列表,类似 S3 list buckets 响应。
配置自定义元数据字段(mdsearch):
POST /{bucket}?mdsearch
x-amz-meta-search: <key [; type]> [, ...]
- 多个元数据字段用逗号分隔;
- 可用
;强制指定类型; - 允许的类型:
string(默认)、integer、date。
示例:把 x-amz-meta-year 索引为 int,x-amz-meta-release-date 索引为 date,x-amz-meta-title 索引为 string:
POST /mybooks?mdsearch
x-amz-meta-search: x-amz-meta-year;int, x-amz-meta-release-date;date, x-amz-meta-title;string
| 操作 | 语法 |
|---|---|
| 删除自定义元数据配置 | DELETE /<bucket>?mdsearch |
| 获取自定义元数据配置 | GET /<bucket>?mdsearch |
5.4 Cloud Sync Module
.. versionadded:: Mimic
| 特性 | 说明 |
|---|---|
| 作用 | 把 zone 数据同步到远端云服务 |
| 方向 | 单向——数据不会从远端 zone 同步回来 |
| 目标 | 支持同时同步到多个云 provider;目前支持的 provider 是 AWS(S3)兼容的那些 |
| 凭据 | 必须配置远端云对象存储服务的用户凭据 |
| bucket 映射 | 由于多数云服务限制单用户可创建的 bucket 数,源对象/源 bucket 到目标的映射可配置;可为不同 bucket 与 bucket 前缀配置不同目标 |
| ACL | 源 ACL 不会被保留;但可以把特定源用户映射为特定目标用户 |
| mtime / ETag | 由于 API 限制,无法保留原始对象修改时间与 ETag——cloud sync module 会把它们作为元数据属性存放在目标对象上 |
简明配置(Trivial Configuration)
{
"connection": {
"access_key": <access>,
"secret": <secret>,
"endpoint": <endpoint>,
"host_style": <path | virtual>,
},
"acls": [ { "type": <id | email | uri>,
"source_id": <source_id>,
"dest_id": <dest_id> } ... ],
"target_path": <target_path>,
}
完整配置(Non Trivial Configuration)
{
"default": {
"connection": {
"access_key": <access>,
"secret": <secret>,
"endpoint": <endpoint>,
"host_style": <path | virtual>,
},
"acls": [
{
"type" : <id | email | uri>, # optional, default is id
"source_id": <id>,
"dest_id": <id>
} ... ],
"target_path": <path> # optional
},
"connections": [
{
"connection_id": <id>,
"access_key": <access>,
"secret": <secret>,
"endpoint": <endpoint>,
"host_style": <path | virtual>, # optional
} ... ],
"acl_profiles": [
{
"acls_id": <id>, # acl mappings
"acls": [ {
"type": <id | email | uri>,
"source_id": <id>,
"dest_id": <id>
} ... ],
}
],
"profiles": [
{
"source_bucket": <source>,
"connection_id": <connection_id>,
"acls_id": <mappings_id>,
"target_path": <dest>, # optional
} ... ],
}
注:Trivial 配置可以与 Non-trivial 配置共存。
配置项逐项说明
| 配置项 | 类型 | 说明 |
|---|---|---|
connection |
container | 到远端云服务的连接,内含 connection_id、access_key、secret、endpoint、host_style |
access_key |
string | 该连接使用的远端云 access key |
secret |
string | 远端云服务的 secret key |
endpoint |
string | 远端云服务 endpoint 的 URL |
host_style |
path | virtual |
访问远端云 endpoint 时使用的 host style,默认 path |
acls |
array | acl_mappings 列表 |
acl_mapping |
container | 每个 acl_mapping 含 type、source_id、dest_id,定义对每个对象执行的 ACL 变更(把源 user id 转换为目标 id) |
type |
id | email | uri |
id=用户 id;email=按 email 定义用户;uri=按 uri 定义用户(组) |
source_id |
string | 源 zone 中的用户 ID |
dest_id |
string | 目标端的用户 ID |
target_path |
string | 定义目标路径的生成方式:一个前缀,源对象名追加在其后。可包含以下变量 |
acl_profiles |
array | acl_profile 数组 |
acl_profile |
container | 每个 profile 含 acls_id(代表该 profile 的字符串)与 acls(acl_mappings 列表) |
profiles |
array | profile 列表,每项含 source_bucket、target_path、connection_id、acls_id |
source_bucket |
string | bucket 名,或以 * 结尾的 bucket 前缀,定义该 profile 的源 bucket(s) |
target_path 可用变量:
| 变量 | 含义 |
|---|---|
sid |
代表 sync instance ID 的唯一字符串 |
zonegroup |
zonegroup 名 |
zonegroup_id |
zonegroup ID |
zone |
zone 名 |
zone_id |
zone id |
bucket |
源 bucket 名 |
owner |
源 bucket owner ID |
示例:target_path = rgwx-${zone}-${sid}/${owner}/${bucket}
S3 专有配置项
{
"multipart_sync_threshold": {object_size},
"multipart_min_part_size": {part_size}
}
| 配置项 | 类型 | 说明 |
|---|---|---|
multipart_sync_threshold |
integer | 大于等于该大小的对象将使用 multipart upload 同步到云端 |
multipart_min_part_size |
integer | 使用 multipart upload 同步对象时的最小 part 大小 |
上述两项具体默认值:文档未明确(源文只给出占位符
{object_size}/{part_size},rgw.yaml.in中也未检索到同名条目)。
如何配置
# 1. 创建新 zone,tier-type 设为 cloud
radosgw-admin zone create --rgw-zonegroup={zone-group-name} \
--rgw-zone={zone-name} \
--endpoints={http://fqdn}[,{http://fqdn}] \
--tier-type=cloud
# 2. 配置 tier
radosgw-admin zone modify --rgw-zonegroup={zone-group-name} \
--rgw-zone={zone-name} \
--tier-config={key}={val}[,{key}={val}]
# 嵌套值用「点」访问,例如:
radosgw-admin zone modify --rgw-zonegroup={zone-group-name} \
--rgw-zone={zone-name} \
--tier-config=connection.access_key={key},connection.secret={secret}
数组条目访问规则:
- 用方括号指定具体条目,例如
profiles[0]; - 用
[]追加新条目; - 索引
-1引用最后一个条目; - 目前无法在同一条命令中先创建新条目再引用它。
示例:为以 {prefix} 开头的 bucket 创建新 profile:
radosgw-admin zone modify --rgw-zonegroup={zone-group-name} \
--rgw-zone={zone-name} \
--tier-config=profiles[].source_bucket={prefix}'*'
radosgw-admin zone modify --rgw-zonegroup={zone-group-name} \
--rgw-zone={zone-name} \
--tier-config=profiles[-1].connection_id={conn_id},profiles[-1].acls_id={acls_id}
删除条目用 --tier-config-rm={key}。
5.5 Archive Sync Module
.. versionadded:: Nautilus
| 特性 | 说明 |
|---|---|
| 原理 | 利用 S3 对象的 RGW versioning 特性,维护一个 archive zone,捕获对象在其他 zone 中被更新时的历次版本 |
| 删除 | archive zone 中的对象只能通过 archive zone 关联的网关删除 |
| 部署价值 | 若干非版本化的 zone 通过各自网关互相复制数据与元数据(mirror 配置)为终端用户提供高可用,而 archive zone 负责捕获数据与元数据的更新 |
| 空间收益 | 在 multizone 配置中部署 archive zone,可以在单个 zone 中获得 S3 对象历史灵活性,同时省去「版本化 S3 对象在其他 zone 各存一份副本」的空间开销 |
配置
radosgw-admin zone create --rgw-zonegroup={zone-group-name} \
--rgw-zone={zone-name} \
--endpoints={http://fqdn}[,{http://fqdn}] \
--tier-type=archive
引自(本节其余):Sync Modules
六、云分层与云恢复
6.1 Cloud Transition(云分层)
概述
| 项 | 说明 |
|---|---|
| 功能 | 作为 object lifecycle 的一部分,把 S3 对象 transition 到远端云服务 |
| 方向 | 单向——数据不能从远端 zone transition 回来 |
| 目标 | 支持把数据 transition 到多个云 provider;支持 AWS(S3)兼容的云 provider |
| 实现 | 使用特殊 storage class,tier type 为 cloud-s3 或 cloud-s3-glacier,来配置远端云 S3 对象存储服务 |
| 定义位置 | 这些 class 定义在 zonegroup placement target 之上,且与常规 storage class 不同,不需要 data pool |
| 凭据 | 必须配置远端云对象存储服务的用户凭据 |
| ACL | 源 ACL 不会被保留;但可把特定源用户映射为特定目标用户 |
tier-type
| tier-type | 说明 |
|---|---|
cloud-s3 |
常规 S3 兼容对象存储服务 |
cloud-s3-glacier |
S3 Glacier 或 Tape 存储服务 |
Cloud Storage Class Tier Configuration
{
"access_key": <access>,
"secret": <secret>,
"endpoint": <endpoint>,
"region": <region>,
"host_style": <path | virtual>,
"acls": [ { "type": <id | email | uri>,
"source_id": <source_id>,
"dest_id": <dest_id> } ... ],
"target_path": <target_path>,
"target_storage_class": <target-storage-class>,
"multipart_sync_threshold": {object_size},
"multipart_min_part_size": {part_size},
"retain_head_object": <true | false>
}
| 配置项 | 类型 | 说明 |
|---|---|---|
access_key |
string | 远端云 S3 access key |
secret |
string | 远端云 S3 服务的 secret key |
endpoint |
string | 远端云 S3 服务的 URL |
region |
string | 远端云 S3 服务的 region 名 |
host_style |
path | virtual |
访问远端云 S3 服务时使用的 host style,默认 path |
acls |
array | acl_mappings 列表 |
acl_mapping |
container | 含 type、source_id、dest_id,定义每个对象上的 ACL 变更(把源 userid 转换为目标 userid) |
type |
id | email | uri |
id=userid;email=按 email 定义用户;uri=按 uri 定义用户(组) |
source_id |
string | 源 zone 中的用户 ID |
dest_id |
string | 目标端的用户 ID |
target_path |
string | 定义目标路径如何构造:一个前缀,源 bucket-name/object-name 追加在其后。未指定时生成为 rgwx-${zonegroup}-${storage-class}-cloud-bucket。示例:target_path = rgwx-archive-${zonegroup}/ |
target_storage_class |
string | 对象 transition 到的目标 storage class。未指定时 transition 到 STANDARD |
retain_head_object |
true | false |
true:保留已 transition 到云服务的对象的元数据;false(默认):transition 后对象被删除。对 current 版本化对象该选项被忽略 |
multipart_sync_threshold |
integer | 大于等于该大小的对象将用 multipart upload transition 到云端 |
multipart_min_part_size |
integer | 使用 multipart upload transition 对象时的最小 part 大小 |
multipart_sync_threshold/multipart_min_part_size的具体默认值:文档未明确(源文为占位符)。下文的zonegroup placement add输出示例里出现过33554432(=32 MiB),但源文未声明这是默认值。
如何配置
⚠️ 若此前未做 Multisite Configuration,系统会为你创建
defaultzone 与 zonegroup,此时对 zone/zonegroup 的修改必须重启 RGW 守护进程才生效;若已为 multisite 创建了 realm,则radosgw-admin period update --commit提交后即生效。
# 1. 添加 storage class
radosgw-admin zonegroup placement add --rgw-zonegroup={zone-group-name} \
--placement-id={placement-id} \
--storage-class={storage-class-name} \
--tier-type=cloud-s3
示例:
radosgw-admin zonegroup placement add --rgw-zonegroup=default \
--placement-id=default-placement \
--storage-class=CLOUDTIER --tier-type=cloud-s3
添加后的输出:
[
{
"key": "default-placement",
"val": {
"name": "default-placement",
"tags": [],
"storage_classes": [
"CLOUDTIER",
"STANDARD"
],
"tier_targets": [
{
"key": "CLOUDTIER",
"val": {
"tier_type": "cloud-s3",
"storage_class": "CLOUDTIER",
"retain_head_object": "false",
"s3": {
"endpoint": "",
"access_key": "",
"secret": "",
"host_style": "path",
"target_storage_class": "",
"target_path": "",
"acl_mappings": [],
"multipart_sync_threshold": 33554432,
"multipart_min_part_size": 33554432
}
}
}
]
}
}
]
⚠️ 一旦创建了
--tier-type=cloud-s3或--tier-type=cloud-s3-glacier的 storage class,之后不能改为其他 storage class 类型。
# 2. 配置 tier
radosgw-admin zonegroup placement modify --rgw-zonegroup={zone-group-name} \
--placement-id={placement-id} \
--storage-class={storage-class-name} \
--tier-config={key}={val}[,{key}={val}]
示例:
radosgw-admin zonegroup placement modify --rgw-zonegroup default \
--placement-id default-placement \
--storage-class CLOUDTIER \
--tier-config=endpoint=http://XX.XX.XX.XX:YY,\
access_key=<access_key>,secret=<secret>, \
multipart_sync_threshold=44432, \
multipart_min_part_size=44432, \
retain_head_object=true
嵌套值访问: 用「点」. 分隔,类似 jq 访问 JSON 嵌套字段。注意该点号仅用于 --tier-config 内部的 key 访问,不要与 realm / zonegroup / zone 的 Ceph RGW 命名模式混淆。
radosgw-admin zonegroup placement modify --rgw-zonegroup={zone-group-name} \
--placement-id={placement-id} \
--storage-class={storage-class-name} \
--tier-config=acls.source_id=${source-id}, \
acls.dest_id=${dest-id}
数组条目: 用方括号引用具体条目;用空的 [] 追加新条目。
radosgw-admin zonegroup placement modify --rgw-zonegroup={zone-group-name} \
--placement-id={placement-id} \
--storage-class={storage-class-name} \
--tier-config=acls[].source_id=${source-id}, \
acls[${source-id}].dest_id=${dest-id}, \
acls[${source-id}].type=email
删除条目: 用 --tier-config-rm={key}。
radosgw-admin zonegroup placement modify --rgw-zonegroup default \
--placement-id default-placement \
--storage-class CLOUDTIER \
--tier-config-rm=acls.source_id=testid
radosgw-admin zonegroup placement modify --rgw-zonegroup default \
--placement-id default-placement \
--storage-class CLOUDTIER \
--tier-config-rm=target_path
删除 storage class:
radosgw-admin zonegroup placement rm --rgw-zonegroup={zone-group-name} \
--placement-id={placement-id} \
--storage-class={storage-class-name}
示例:
radosgw-admin zonegroup placement rm --rgw-zonegroup default \
--placement-id default-placement \
--storage-class CLOUDTIER
删除后:
[
{
"key": "default-placement",
"val": {
"name": "default-placement",
"tags": [],
"storage_classes": [
"STANDARD"
]
}
}
]
对象修改与限制
在 lifecycle 规则中使用云 storage class:
<LifecycleConfiguration>
<Rule>
....
<Transition>
....
<StorageClass>CLOUDTIER</StorageClass>
</Transition>
</Rule>
</LifecycleConfiguration>
⚠️ 由于 transition 是单向的,配置 S3 lifecycle 规则时,云 storage class 应放在所有 transition 目标 storage class 的最后。transition 到云之后,后续规则(若有)不再适用。
mtime 与 ETag 的保留问题: 由于 API 限制,无法保留原始对象修改时间与 ETag,它们会作为元数据属性存放在目标对象上:
x-amz-meta-rgwx-source: rgw
x-amz-meta-rgwx-source-etag: ed076287532e86365e841e92bfc50d8c
x-amz-meta-rgwx-source-key: lc.txt
x-amz-meta-rgwx-source-mtime: 1608546349.757100363
x-amz-meta-rgwx-versioned-epoch: 0
两个额外属性(用于让云服务识别来源并映射用户自定义 x-amz-meta- 属性):
| 属性 | 取值 | 说明 |
|---|---|---|
x-rgw-cloud |
true / false |
对象从 RGW transition 而来时默认为 true |
x-rgw-cloud-keep-attrs |
true / false |
默认 true:云服务应映射并存储所有 x-amz-meta-* 属性,做不到则操作应失败;false:云服务可忽略这些属性,只存对象数据 |
retain_head_object=true 的效果: 源对象的元数据会被保留(含更新后的 storage-class 与 object-size),但对该对象执行 GET 仍会失败并返回 InvalidObjectState 错误。其他针对原始源对象的操作只作用于其元数据条目,不影响已 transition 的对象。
$ s3cmd info s3://bucket/lc.txt
s3://bucket/lc.txt (object):
File size: 12
Last mod: Mon, 21 Dec 2020 10:25:56 GMT
MIME type: text/plain
Storage: CLOUDTIER
MD5 sum: ed076287532e86365e841e92bfc50d8c
SSE: none
Policy: none
CORS: none
ACL: M. Tester: FULL_CONTROL
x-amz-meta-s3cmd-attrs: atime:1608466266/ctime:1597606156/gid:0/gname:root/md5:ed076287532e86365e841e92bfc50d8c/mode:33188/mtime:1597605793/uid:0/uname:root
$ s3cmd get s3://bucket/lc.txt lc_restore.txt
download: 's3://bucket/lc.txt' -> 'lc_restore.txt' [1 of 1]
ERROR: S3 error: 403 (InvalidObjectState)
目标对象命名格式(避免跨 bucket 名字冲突 —— 源 bucket 名会作为前缀):
s3://<target_path>/<source_bucket_name>/<source_object_name>(-<source_object_version_id>)
- 若对象为版本化对象,会追加对象的
versionid。
版本化对象(Versioned Objects)
对版本化与锁定对象,采用与 LifecycleExpiration 类似的语义:
| 情况 | 行为 |
|---|---|
| 对象是 current | transition 到云之后,它被置为 noncurrent,并创建 delete marker |
| 对象是 noncurrent 且被锁定 | 跳过其 transition |
未来工作(官方列出)
- 发送 presigned redirect,或对已 transition 到云的对象做 read-through;
- 支持 transition 到其他云 provider(如 Azure)。
6.2 Cloud Restore(云恢复)
概述与前提
| 项 | 说明 |
|---|---|
| 功能 | 把已 transition 到远端 S3 endpoint 的对象恢复到本地 RGW 部署 |
| 支持范围 | 目前支持从 S3 兼容云服务恢复 |
| 必要前提 | 配置 storage class 时,必须在 tier-config 中把 retain_head_object 设为 true |
| 恢复方式 1 | 使用 S3 RestoreObject API。恢复出的副本只在指定的 days 天数内保留;若不提供 days,恢复出的副本被视为永久副本(permanent),按常规对象对待 |
| 恢复方式 2 | 启用 allow_read_through 后,可用 S3 GetObject API 临时恢复对象 |
恢复相关 tier-config 配置项
| 配置项 | 类型 | 说明 |
|---|---|---|
restore_storage_class |
string | 对象数据要恢复到的 storage class。默认 STANDARD |
allow_read_through |
true | false |
若为 true,启用 read-through,可用 S3 GetObject API 恢复对象 |
read_through_restore_days |
integer | 通过 read-through 恢复的对象保留时长。默认 1 天 |
glacier_restore_days |
integer | 对象在远端云服务上被恢复(retrieve)后保留的时长 |
glacier_restore_tier_type |
Standard | Expedited |
云服务内部的检索类型,可能对应不同定价。支持 Standard 与 Expedited |
配置示例(含 read-through)
radosgw-admin zonegroup placement modify --rgw-zonegroup default \
--placement-id default-placement \
--storage-class CLOUDTIER \
--tier-config=endpoint=http://XX.XX.XX.XX:YY,\
access_key=<access_key>,secret=<secret>, \
retain_head_object=true, \
restore_storage_class=COLDTIER, \
allow_read_through=true, \
read_through_restore_days=10
S3 Glacier 专有配置
要恢复归档在 S3 Glacier 或 Tape 云 storage class 中的对象,必须先在该云服务上把数据 retrieve 出来,才能被读取并下载回 RGW。为此:
- storage class 必须以
--tier-type=cloud-s3-glacier配置; - 相应设置
glacier_restore_days与glacier_restore_tier_type。
radosgw-admin zonegroup placement add --rgw-zonegroup=default \
--placement-id=default-placement \
--storage-class=CLOUDTIER-GLACIER --tier-type=cloud-s3-glacier
radosgw-admin zonegroup placement modify --rgw-zonegroup default \
--placement-id default-placement \
--storage-class CLOUDTIER \
--tier-config=endpoint=http://XX.XX.XX.XX:YY,\
access_key=XXXXX,secret=YYYYY, \
retain_head_object=true, \
target_storage_class=Glacier, \
............ \
............ \
restore_storage_class=COLDTIER, \
glacier_restore_days=2, \
glacier_restore_tier_type=Expedited
配置结果示例:
[
{
"key": "default-placement",
"val": {
"name": "default-placement",
"tags": [],
"storage_classes": [
"CLOUDTIER-GLACIER",
"STANDARD"
],
"tier_targets": [
{
"key": "CLOUDTIER-GLACIER",
"val": {
"tier_type": "cloud-s3-glacier",
"storage_class": "CLOUDTIER-GLACIER",
"retain_head_object": "true",
"s3": {
"endpoint": http://XX.XX.XX.XX:YY,
"access_key": "XXXXX",
"secret": "YYYYY",
"host_style": "path",
"target_storage_class": "Glacier"
}
"allow_read_through": true,
"read_through_restore_days": 10,
"restore_storage_class": "COLDTIER",
"s3-glacier": {
"glacier_restore_days": 2
"glacier_restore_tier_type": "Expedited"
}
}
}
]
}
}
]
使用 S3 RestoreObject CLI 恢复
aws s3api restore-object --bucket <value> \
--key <value> \
[--version-id <value>] \
--restore-request (structure) { \
Days=<integer> \
}
Days是可选的;不提供则对象被永久恢复。
示例 1 —— 恢复 10 天(可指定版本):
aws s3api restore-object --bucket bucket1 --key doc1.rtf \
[--version-id 3sL4kqtJlcpXroDTDmJ+rmSpXd3dIbrHY+MTRCxf3vjVBH40Nr8X8gdRQBpUMLUo] \
--restore-request Days=10 \
....
把对象 doc1.rtf(可选指定版本)恢复 10 天。
这些临时副本的保留期可以通过重新发起请求并指定新周期来更新。
示例 2 —— 永久恢复:
aws s3api restore-object --bucket bucket1 --key doc2.rtf --restore-request {} ....
把 doc2.rtf 永久恢复,之后按常规对象对待。
使用 S3 GetObject CLI 恢复(read-through)
需确保 allow_read_through tier-config 选项已启用:
aws s3api get-object --bucket bucket1 --key doc3.rtf ....
会把 doc3.rtf 恢复 read_through_restore_days 天。
若对象恢复耗时过长,上述 CLI 命令可能超时。可先确认恢复状态再重新发起命令。
验证恢复状态
方式 1 —— S3 HeadObject: 响应中若包含 x-amz-restore 头,说明恢复正在进行或已恢复出副本。
aws s3api head-object --key doc1.rtf --bucket bucket1 ....
方式 2 —— radosgw-admin:
radosgw-admin object stat --bucket bucket1 --object doc1.rtf
恢复后对象的属性
| 维度 | 行为 |
|---|---|
| Storage | 对象恢复到 tier-config 中 restore_storage_class 配置的 storage class。但按 AWS restore-object 语义,已恢复对象的 storage class 应保持不变,因此临时副本的 x-amz-storage-class 仍反映原来的 cloud-tier storage class |
| mtime | 已 transition 和已恢复对象的 mtime 保持不变 |
| Lifecycle | 临时副本不会再被 transition 到云;但与 cloud-transitioned 对象一样,可被常规 LC 过期规则或外部 S3 delete 请求删除。永久副本按常规对象对待,受适用的 LC 策略约束 |
| Replication | 临时副本不会被复制,只保留在发起 restore 请求的那个 zone。永久副本像其他常规对象一样被复制 |
| Versioned Objects | 若对象已被 cloud-transition,它处于 non-current 状态。恢复后,同一个 non-current 对象会被下载的数据更新,其 HEAD 对象也相应被修改 |
未来工作(官方列出)
- Admin Ops
- Notifications
七、Zone Features
7.1 基本概念
| 概念 | 说明 |
|---|---|
supported_features |
每个 zone 列出它支持哪些 feature |
enabled_features |
每个 zonegroup 列出它启用了哪些 feature |
| 启用条件 | 一个 feature 要在 zonegroup 中启用,必须先被其所有 zone 支持 |
| 新建时 | 创建新的 zone 与 zonegroup 时,所有已知 feature 都被标记为 supported,其中部分(见下表)默认 enabled |
| 升级后 | 升级已有 zone 后,新 feature 必须手工启用 |
7.2 支持的 feature
| Feature | 引入版本 | 新建时默认 |
|---|---|---|
resharding |
Reef | Enabled |
compress-encrypted |
Reef | Disabled |
notification_v2 |
Squid | Enabled |
resharding
| 项 | 说明 |
|---|---|
| 作用 | 允许在 multisite 配置中对 bucket 做 reshard,而不中断其对象的复制 |
与 rgw_dynamic_resharding 的关系 |
启用 rgw_dynamic_resharding 时,它在每个 zone 上独立运行,各 zone 可为同一 bucket 选择不同的 shard 数 |
| 手工 reshard | 用 radosgw-admin bucket reshard 手工 reshard 时,只修改所在 zone 的 bucket |
| 标记 supported 的前提 | 只有在该 zone 的所有 RGW 与 OSD 都升级完成后,才能把该 zone feature 标记为 supported |
⚠️ 注意:Reef 之前的版本中,multisite 部署不支持 dynamic resharding。
compress-encrypted
| 项 | 说明 |
|---|---|
| 作用 | 支持在同一对象上同时使用 Server-Side Encryption 与 Compression。对象数据在加密之前被压缩 |
| 历史问题 | Reef 之前,multisite 无法正确复制此类对象,因此所有 zone 都必须升级到 Reef 或更高版本才能启用 |
| 安全警告 | 压缩率可能泄露加密数据的信息,使攻击者能区分两个同样大小的对象是否包含相同数据。出于这些安全考虑,该 feature 默认禁用 |
notification_v2
| 项 | 说明 |
|---|---|
| 作用 | 选择启用 bucket notification 与 topic 的新 "v2" 元数据格式 |
| 与 v1 的区别 | v1 不支持 multisite 复制且难以扩展到大量 topic;v2 支持 multisite 复制,且可扩展到很多 topic |
| 迁移 | 一旦 realm 中所有 zonegroup 都启用了该 feature,一个后台进程会把现有的 v1 topic 与 bucket notification 转换为 v2 格式 |
7.3 命令
| 操作 | 执行位置 | 命令 |
|---|---|---|
| 为 zone 增加 feature 支持 | 包含该 zone 的集群 | radosgw-admin zone modify --rgw-zone={zone-name} --enable-feature={feature-name} + radosgw-admin period update --commit |
| 移除 zone 的 feature 支持 | 包含该 zone 的集群 | radosgw-admin zone modify --rgw-zone={zone-name} --disable-feature={feature-name} + radosgw-admin period update --commit |
| 启用 zonegroup feature | realm 中的任意集群 | radosgw-admin zonegroup modify --rgw-zonegroup={zonegroup-name} --enable-feature={feature-name} + radosgw-admin period update --commit |
| 禁用 zonegroup feature | realm 中的任意集群 | radosgw-admin zonegroup modify --rgw-zonegroup={zonegroup-name} --disable-feature={feature-name} + radosgw-admin period update --commit |
⚠️
period update命令只在该 zone 属于某个 realm 时才有效;否则所有 radosgw 都需要重启才能感知变更。
八、事件通知(Bucket Notifications)
.. versionadded:: Nautilus.. versionchanged:: Squid:可通过notification_v2zone feature 启用新的 "v2" Topic 与 Notification 元数据格式。从旧版本升级后启用该 feature 会触发已有 Topic 与 Notification 元数据的迁移;全新部署(greenfield)直接使用新格式。新格式允许数据在 zonegroup 内的 zone 之间同步。
8.1 概览
| 项 | 说明 |
|---|---|
| 作用 | 当 bucket 上发生特定事件时,把信息发送出 radosgw |
| 支持的 endpoint | HTTP endpoints、AMQP0.9.1 endpoints、Kafka endpoints |
| topic | 用户可以创建 topic。topic 实体由名字定义,是 per-tenant 的;用户只能把自己拥有的 topic(通过 notification 配置)关联到自己拥有的 bucket |
| notification 实体 | 必须为特定 bucket 创建 notification 实体才能发送事件通知。可为部分事件类型创建,也可为全部 "Removed" 与 "Created" 事件类型创建(这是默认) |
| 过滤 | 可基于 (1) key、(2) 对象上附带的 metadata 属性、(3) 对象 tag 的前缀与后缀匹配来过滤事件;也可对这些使用正则表达式匹配 |
| 关系 | notification 与 topic 是多对多关系:一个 topic 可接收多个 notification;一个 notification 也可投递到多个 topic |
| 前置条件 | 要启用 bucket notifications API,rgw_enable_apis 配置参数中必须包含 "notifications" |
默认值取自 src/common/options/*.yaml.in @ v20.2.4:
rgw_enable_apis=s3, s3website, swift, swift_auth, admin, sts, iam, notifications(str)。
8.2 通知可靠性:同步 vs 异步
同步通知(Synchronous)
| 特性 | 说明 |
|---|---|
| 时机 | 作为触发它的操作的一部分发送 |
| ack 时机 | 只有通知被发送到 topic 配置的 endpoint 之后,操作才被确认(acked) |
| 延迟 | 通知的往返时间(发送到 endpoint + 收到确认)叠加到操作本身的延迟上 |
| 失败处理 | ⚠️ 即使通知失败、无法投递或超时,原始的触发操作仍被视为成功 |
异步通知(Asynchronous / Persistent)
| 特性 | 说明 |
|---|---|
| 时机 | 通知先被提交到持久化存储,之后异步发送到 topic 配置的 endpoint |
| 提交条件 | 只有当触发操作成功时,通知才会被提交到持久化存储 |
| 延迟 | 只增加「通知提交到持久化存储」的延迟 |
| 队列打满 | 若 topic 的 endpoint 长时间不可用,为该 topic 分配的持久化存储最终会被填满。此时触发操作会以 503 Service Unavailable 失败,告知客户端可以稍后重试 |
| 失败处理 | 若通知出错、无法投递或超时,会一直重试直到被成功确认 |
| 重试控制 | 可用 time_to_live / max_retries 设置时间/重试上限;用 retry_sleep_duration 控制重试频率 |
💡 建议:为把异步通知带来的延迟降到最低,建议把 "log" pool 放在快速介质上。
8.3 Topic 管理 CLI
| 操作 | 命令 |
|---|---|
| 列出(可按 tenant / uid) | radosgw-admin topic list [--tenant={tenant}] [--uid={user}] |
| 获取指定 topic | radosgw-admin topic get --topic={topic-name} [--tenant={tenant}] |
| 删除 topic | radosgw-admin topic rm --topic={topic-name} [--tenant={tenant}] |
| 查看持久化 topic 统计(reservations / entries / size) | radosgw-admin topic stats --topic={topic-name} [--tenant={tenant}] |
| 以 JSON dump 某个持久化 topic 的所有待发通知 | radosgw-admin topic dump --topic={topic-name} [--tenant={tenant}] [--max-entries={max-entries}] |
8.4 通知性能统计指标
| 指标 | 含义 |
|---|---|
persistent_topic_size |
队列大小(字节) |
persistent_topic_len |
当前在队列中等待的通知条数 |
pubsub_push_ok |
所有通知中成功推送到 endpoint 的事件的累计计数器 |
pubsub_push_fail |
所有通知中推送失败的事件的累计计数器 |
pubsub_push_pending |
已推送到 endpoint 但尚未 ack 或 nack 的事件数(gauge)。不包含持久化队列中等待的通知,只统计在持久化与非持久化两种情况下正在飞行中的通知 |
⚠️
pubsub_event_lost是按每个 notification 的每个 event 递增的;而pubsub_push_ok与pubsub_push_fail是按每个 notification 的每次 push 动作递增的。
8.5 Bucket Notification REST API —— Topic
所有 topic 操作中,参数都经过 URL 编码,并以
application/x-www-form-urlencoded作为 content type 放在消息体中发送。
Create a Topic
POST
Action=CreateTopic
&Name=<topic-name>
[&Attributes.entry.1.key=amqp-exchange&Attributes.entry.1.value=<exchange>]
[&Attributes.entry.2.key=amqp-ack-level&Attributes.entry.2.value=none|broker|routable]
[&Attributes.entry.3.key=verify-ssl&Attributes.entry.3.value=true|false]
[&Attributes.entry.4.key=kafka-ack-level&Attributes.entry.4.value=none|broker]
[&Attributes.entry.5.key=use-ssl&Attributes.entry.5.value=true|false]
[&Attributes.entry.6.key=ca-location&Attributes.entry.6.value=<file path>]
[&Attributes.entry.7.key=OpaqueData&Attributes.entry.7.value=<opaque data>]
[&Attributes.entry.8.key=push-endpoint&Attributes.entry.8.value=<endpoint>]
[&Attributes.entry.9.key=persistent&Attributes.entry.9.value=true|false]
[&Attributes.entry.10.key=cloudevents&Attributes.entry.10.value=true|false]
[&Attributes.entry.11.key=mechanism&Attributes.entry.11.value=<mechanism>]
[&Attributes.entry.12.key=time_to_live&Attributes.entry.12.value=<seconds to live>]
[&Attributes.entry.13.key=max_retries&Attributes.entry.13.value=<retries number>]
[&Attributes.entry.14.key=retry_sleep_duration&Attributes.entry.14.value=<sleep seconds>]
[&Attributes.entry.15.key=Policy&Attributes.entry.15.value=<policy-JSON-string>]
[&Attributes.entry.16.key=user-name&Attributes.entry.16.value=<user-name-string>]
[&Attributes.entry.17.key=password&Attributes.entry.17.value=<password-string>]
[&Attributes.entry.18.key=kafka-brokers&Attributes.entry.18.value=<kafka-broker-list>]
[&Attributes.entry.19.key=ssl-certificate-location&Attributes.entry.19.value=<file path>]
[&Attributes.entry.20.key=ssl-key-location&Attributes.entry.20.value=<file path>]
[&Attributes.entry.21.key=ssl-key-password&Attributes.entry.21.value=<password-string>]
💡 提示:更新 topic 使用与创建相同的命令(但使用已存在 topic 的名字和不同的 endpoint 值)。 ⚠️ 任何已与该 topic 关联的 notification 必须重新创建,topic 才会更新。
请求参数逐项说明:
| 参数 | 说明 |
|---|---|
push-endpoint |
发送推送通知的 endpoint URI |
OpaqueData |
在 topic 配置中设置的透传数据,会添加到由该 topic 触发的所有通知中 |
persistent |
到该 endpoint 的通知是否持久化(=异步)。默认 false |
time_to_live |
保留通知的时间上限(秒)。默认取自 rgw_topic_persistency_time_to_live;提供该值则覆盖全局值;0 表示无限期 |
max_retries |
通知过期前的最大重试次数。默认取自 rgw_topic_persistency_max_retries;提供该值则覆盖全局值;0 表示无限重试 |
retry_sleep_duration |
控制重试通知的频率。默认取自 rgw_topic_persistency_sleep_duration;提供该值则覆盖全局值;0 表示重试之间没有延迟 |
Policy |
控制除 topic owner 之外谁可以访问该 topic。传入的 policy 需为类似 bucket policy 的 JSON 字符串 |
Policy 示例:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": ["arn:aws:iam::usfolks:user/fred:subuser"]},
"Action": ["sns:GetTopicAttributes","sns:Publish"],
"Resource": ["arn:aws:sns:default::mytopic"],
}]
}
Policy 中目前支持的 action:
| Action | 含义 |
|---|---|
sns:GetTopicAttributes |
列出或获取已有 topic |
sns:SetTopicAttributes |
为已有 topic 设置属性 |
sns:DeleteTopic |
删除已有 topic |
sns:Publish |
能够在已有 topic 上创建/订阅 notification |
相关配置项默认值(默认值取自 src/common/options/*.yaml.in @ v20.2.4):
| 配置项 | 类型 | 默认值 |
|---|---|---|
rgw_topic_persistency_time_to_live |
uint | 0(保留持久化 topic 的时间,秒) |
rgw_topic_persistency_max_retries |
uint | 0(持久通知的最大发送尝试次数;1 表示不重试,0 表示无限重试) |
rgw_topic_persistency_sleep_duration |
uint | 0(同一持久通知两次尝试之间的最小间隔秒数;实际间隔可能更长) |
rgw_allow_notification_secrets_in_cleartext |
bool | false(允许通过非加密 HTTP 消息发送密码等 secret) |
HTTP endpoint
| 属性 | 说明 |
|---|---|
| URI | http[s]://<fqdn>[:<port] |
| port | HTTP 默认 80,HTTPS 默认 443 |
verify-ssl |
客户端是否校验服务端证书。默认 true |
cloudevents |
HTTP 头是否按 S3 CloudEvents Spec 包含属性。默认 false |
AMQP0.9.1 endpoint
| 属性 | 说明 |
|---|---|
| URI | amqp[s]://[<user>:<password>@]<fqdn>[:<port>][/<vhost>] |
| user/password | 默认 guest/guest。必须只在 HTTPS 上提供,否则 topic 创建请求会被拒绝 |
| port | 非加密连接默认 5672,SSL 加密连接默认 5671 |
| vhost | 默认 / |
verify-ssl |
客户端是否校验服务端证书。默认 true |
ca-location |
若提供且使用安全连接,将用指定的 CA 认证 broker,不使用默认 CA |
amqp-exchange |
必填。exchange 必须已存在,且必须能基于 topic 路由消息 |
amqp-ack-level |
不需要 end2end acking;消息在投递到最终目的地前可能驻留在 broker 中。三种方式见下表 |
amqp-ack-level 取值:
| 取值 | 含义 |
|---|---|
none |
消息只要发到 broker 就算 "delivered" |
broker |
消息被 broker ack 才算 "delivered"(默认) |
routable |
broker 能路由到 consumer 才算 "delivered" |
💡 提示:topic-name 会被用作 AMQP topic(topic exchange 的 "routing key")。
Kafka endpoint
| 属性 | 说明 |
|---|---|
| URI | kafka://[<user>:<password>@]<fqdn>[:<port] |
use-ssl |
true 则使用安全连接连到 broker。默认 false |
ca-location |
若提供且使用安全连接,将用指定 CA 而非默认 CA 认证 broker |
| user/password | 应在 HTTPS 上提供;否则必须把 rgw_allow_notification_secrets_in_cleartext 设为 true 才能创建 topic |
user/password 与 use-ssl |
应同时提供;否则 broker 凭据会通过不安全传输发送 |
mechanism |
可与 user/password 一起提供(默认 PLAIN)。支持的 SASL mechanism 见下表 |
user-name |
连接 Kafka broker 时使用的用户名。若同时提供了 URI user 与该参数,该参数覆盖 URI user。安全注意事项同 user/password |
password |
连接 Kafka broker 时使用的密码。若同时提供了 URI password 与该参数,该参数覆盖 URI password。安全注意事项同 user/password |
| port | 默认 9092 |
kafka-ack-level |
不需要 end2end acking。两种取值见下表 |
kafka-brokers |
逗号分隔的 host:port Kafka broker 列表。这些 broker(可含 URI 中定义的那个)会被加入 kafka uri,以支持向 Kafka 集群发送通知 |
ssl-certificate-location |
用于 mTLS 认证、呈现给 Kafka broker 的 PEM 编码客户端证书路径。必须与 ssl-key-location 和 use-ssl=true 一起使用;只指定其中一个会导致连接失败 |
ssl-key-location |
与 ssl-certificate-location 对应的 PEM 编码私钥文件路径;必须与 ssl-certificate-location 一起提供 |
ssl-key-password |
客户端私钥的密码(若私钥文件被加密)。可选,仅当私钥受密码保护时需要。安全注意事项同 user/password |
支持的 SASL mechanism:
PLAINSCRAM-SHA-256SCRAM-SHA-512GSSAPIOAUTHBEARER
kafka-ack-level 取值:
| 取值 | 含义 |
|---|---|
none |
消息只要发到 broker 就算 "delivered" |
broker |
消息被 broker ack 才算 "delivered"(默认) |
Attribute 使用注意事项
- 某个参数的 key-value 对不必与参数位于同一行,也不必按特定顺序出现,但必须使用相同的索引;
- Attribute 索引不必连续,也不必从特定值开始;
- AWS
CreateTopic解释了 endpoint attribute 的格式,但 Ceph 使用的是不同的 key 和 value。
CreateTopic 响应
<CreateTopicResponse xmlns="https://sns.amazonaws.com/doc/2010-03-31/">
<CreateTopicResult>
<TopicArn></TopicArn>
</CreateTopicResult>
<ResponseMetadata>
<RequestId></RequestId>
</ResponseMetadata>
</CreateTopicResponse>
Topic ARN 格式:
arn:aws:sns:<zone-group>:<tenant>:<topic>
Get Topic Attributes
POST
Action=GetTopicAttributes
&TopicArn=<topic-arn>
响应:
<GetTopicAttributesResponse>
<GetTopicAttributesResult>
<Attributes>
<entry>
<key>User</key>
<value></value>
</entry>
<entry>
<key>Name</key>
<value></value>
</entry>
<entry>
<key>EndPoint</key>
<value></value>
</entry>
<entry>
<key>TopicArn</key>
<value></value>
</entry>
<entry>
<key>OpaqueData</key>
<value></value>
</entry>
</Attributes>
</GetTopicAttributesResult>
<ResponseMetadata>
<RequestId></RequestId>
</ResponseMetadata>
</GetTopicAttributesResponse>
| 字段 | 说明 |
|---|---|
User |
创建该 topic 的用户名 |
Name |
topic 名 |
EndPoint |
JSON 格式的 endpoint 参数,包含下列子字段 |
↳ EndpointAddress |
push-endpoint URL |
↳ EndpointArgs |
push-endpoint 参数 |
↳ EndpointTopic |
发送到 endpoint 的 topic 名(可与上面的 topic 名不同) |
↳ HasStoredSecret |
endpoint URL 是否含 user/password 信息。为 true 时请求必须走 HTTPS,否则 "topic get" 请求会被拒绝 |
↳ Persistent |
topic 是否为持久化 topic |
↳ TimeToLive |
保留通知的时间上限(秒) |
↳ MaxRetries |
通知过期前的最大重试次数 |
↳ RetrySleepDuration |
重试通知的频率控制 |
TopicArn |
topic 的 ARN |
OpaqueData |
topic 上设置的透传数据 |
Policy |
topic 上设置的任何访问权限 |
Get Topic Information(已废弃)
POST
Action=GetTopic
&TopicArn=<topic-arn>
该 API 现已废弃(deprecated),建议使用 AWS 兼容的
GetTopicAttributesAPI。
响应:
<GetTopicResponse>
<GetTopicResult>
<Topic>
<User></User>
<Name></Name>
<EndPoint>
<EndpointAddress></EndpointAddress>
<EndpointArgs></EndpointArgs>
<EndpointTopic></EndpointTopic>
<HasStoredSecret></HasStoredSecret>
<Persistent></Persistent>
</EndPoint>
<TopicArn></TopicArn>
<OpaqueData></OpaqueData>
</Topic>
</GetTopicResult>
<ResponseMetadata>
<RequestId></RequestId>
</ResponseMetadata>
</GetTopicResponse>
Delete Topic
POST
Action=DeleteTopic
&TopicArn=<topic-arn>
⚠️ 删除一个未知的 notification(例如重复删除)不被视为错误。 ⚠️ 删除 topic 不会自动删除与之关联的所有 notification。
<DeleteTopicResponse xmlns="https://sns.amazonaws.com/doc/2010-03-31/">
<ResponseMetadata>
<RequestId></RequestId>
</ResponseMetadata>
</DeleteTopicResponse>
List Topics
POST
Action=ListTopics
<ListTopicsResponse xmlns="https://sns.amazonaws.com/doc/2010-03-31/">
<ListTopicsResult>
<Topics>
<member>
<User></User>
<Name></Name>
<EndPoint>
<EndpointAddress></EndpointAddress>
<EndpointArgs></EndpointArgs>
<EndpointTopic></EndpointTopic>
</EndPoint>
<TopicArn></TopicArn>
<OpaqueData></OpaqueData>
</member>
</Topics>
</ListTopicsResult>
<ResponseMetadata>
<RequestId></RequestId>
</ResponseMetadata>
</ListTopicsResponse>
若 topic 中任何部分的 endpoint URL 含 user/password 信息,请求必须走 HTTPS,否则 "topic list" 请求会被拒绝。
Set Topic Attributes
POST
Action=SetTopicAttributes
&TopicArn=<topic-arn>&AttributeName=<attribute-name>&AttributeValue=<attribute-value>
- 传入的
AttributeName会被更新;若不存在则被创建;- 传入任何不受支持的
AttributeName会导致 400 错误。
有效的 AttributeName 列表:
| AttributeName | 说明 |
|---|---|
push-endpoint |
发送推送通知的 endpoint URI |
OpaqueData |
透传数据,添加到该 topic 触发的所有通知 |
persistent |
是否持久化(=异步)。默认 false |
time_to_live |
保留通知的时间上限(秒) |
max_retries |
通知过期前的最大重试次数 |
retry_sleep_duration |
控制重试通知的频率 |
Policy |
控制除 owner 外谁可访问该 topic |
verify-ssl |
客户端是否必须校验服务端证书。默认 true |
use-ssl |
是否使用安全连接连到 broker。默认 false |
cloudevents |
HTTP 头是否按 S3 CloudEvents Spec 包含属性 |
amqp-exchange |
exchange 必须已存在且能基于 topic 路由消息 |
amqp-ack-level |
AMQP ack 级别(无需 end2end 确认) |
ca-location |
指定 CA 认证 broker |
mechanism |
与 user/password 一起提供(默认 PLAIN) |
kafka-ack-level |
Kafka ack 级别(无需 end2end 确认) |
kafka-brokers |
以逗号分隔的 host 或 host:port(默认端口 9092)列表设置 endpoint |
ssl-certificate-location |
mTLS 用的 PEM 客户端证书路径,必须与 ssl-key-location 一起提供,只给一个会导致连接失败 |
ssl-key-location |
与客户端证书对应的 PEM 私钥路径,必须与 ssl-certificate-location 一起提供 |
ssl-key-password |
加密私钥的密码(可选) |
8.6 Notification 与 Events
Notification 的详细定义位于官方 Bucket Operations 页面(不在本册范围内)。两点注意事项:
- "Abort Multipart Upload" 请求不会发出 notification;
- "Initiate Multipart Upload" 与 "POST Object" 请求都会发出
s3:ObjectCreated:Postnotification。
事件为 JSON 格式(与实际 endpoint 类型无关),且与 S3 兼容:
{"Records":[
{
"eventVersion":"2.1",
"eventSource":"ceph:s3",
"awsRegion":"zonegroup1",
"eventTime":"2019-11-22T13:47:35.124724Z",
"eventName":"ObjectCreated:Put",
"userIdentity":{
"principalId":"tester"
},
"requestParameters":{
"sourceIPAddress":""
},
"responseElements":{
"x-amz-request-id":"503a4c37-85eb-47cd-8681-2817e80b4281.5330.903595",
"x-amz-id-2":"14d2-zone1-zonegroup1"
},
"s3":{
"s3SchemaVersion":"1.0",
"configurationId":"mynotif1",
"bucket":{
"name":"mybucket1",
"ownerIdentity":{
"principalId":"tester"
},
"arn":"arn:aws:s3:zonegroup1::mybucket1",
"id":"503a4c37-85eb-47cd-8681-2817e80b4281.5332.38"
},
"object":{
"key":"myimage1.jpg",
"size":"1024",
"eTag":"37b51d194a7513e45b56f6524f2d51f2",
"versionId":"",
"sequencer": "F7E6D75DC742D108",
"metadata":[],
"tags":[]
}
},
"eventId":"",
"opaqueData":"me@example.com"
}
]}
事件字段逐项说明:
| 字段 | 说明 |
|---|---|
awsRegion |
zonegroup |
eventTime |
事件被触发的时间戳 |
eventName |
支持的事件见 S3 Notification Compatibility。注意 eventName 值不带 s3: 前缀 |
userIdentity.principalId |
触发变更的用户 |
requestParameters.sourceIPAddress |
不支持 |
responseElements.x-amz-request-id |
原始变更请求的 request ID |
responseElements.x_amz_id_2 |
发生变更的 RGW |
s3.configurationId |
产生该事件的 notification ID |
s3.bucket.name |
bucket 名 |
s3.bucket.ownerIdentity.principalId |
bucket 的 owner |
s3.bucket.arn |
bucket 的 ARN |
s3.bucket.id |
bucket 的 ID(这是 S3 notification API 的扩展) |
s3.object.key |
对象 key |
s3.object.size |
对象大小 |
s3.object.eTag |
对象的 etag |
s3.object.versionId |
对象版本(若 bucket 版本化)。执行 copy 时包含目标对象的版本;创建 delete marker 时包含 delete marker 的版本 |
s3.object.sequencer |
“每个对象的变更”的单调递增标识符(十六进制格式) |
s3.object.metadata |
对象上设置的、以 x-amz-meta- 发送的任何 metadata(S3 notification API 的扩展) |
s3.object.tags |
对象上设置的任何 tag(S3 notification API 的扩展) |
s3.eventId |
事件的唯一 ID,可用于 acking(S3 notification API 的扩展) |
s3.opaqueData |
topic 配置中设置的 "opaque data",添加到该 topic 触发的所有通知中(S3 notification API 的扩展) |
九、S3 事件通知兼容性
9.1 支持的目的端(Destination)
| 平台 | 支持的目的端 |
|---|---|
| AWS | SNS、SQS、Lambda(AWS 内部目的端) |
| Ceph | HTTP/S、Kafka、AMQP |
Ceph 使用 SNS ARN 来表示 HTTP/S、Kafka 和 AMQP 目的端。
9.2 Notification Configuration XML 差异
以下标签(及其内部标签)不被支持:
| 标签 | 说明 |
|---|---|
<QueueConfiguration> |
不需要,Ceph 把所有目的端都当作 SNS |
<CloudFunctionConfiguration> |
不需要,Ceph 把所有目的端都当作 SNS |
9.3 REST API 扩展
| 扩展 | 说明 |
|---|---|
删除特定 notification 或 bucket 上的所有 notification(用 DELETE 动词) |
在 S3 中,只有在 bucket 被删除、或在 bucket 上设置了空 notification 时,所有 notification 才会被删除 |
| 获取特定 notification 的信息(当 bucket 上存在多个时) | 在 S3 中只能一次获取 bucket 上的所有 notification |
| 除基于对象 key 前缀/后缀过滤之外,还支持: | (a) 基于正则表达式匹配过滤;(b) 基于对象上附加的 metadata 属性过滤;(c) 基于对象 tag 过滤 |
| 上述每一种额外过滤都是对 S3 API 的扩展 | 使用它需要扩展客户端 SDK(除非你用的是纯 HTTP) |
| 允许过滤重叠 | 同一个事件可以作为不同的 notification 被发送 |
9.4 事件记录中不支持的字段
事件记录遵循 AWS Event Message Structure 的格式,但 requestParameters.sourceIPAddress 字段会被发送为空。
9.5 事件类型(Event Types)全表
| Event | 说明 |
|---|---|
s3:ObjectCreated:* |
Supported |
s3:ObjectCreated:Put |
Supported |
s3:ObjectCreated:Post |
Supported |
s3:ObjectCreated:Copy |
Supported |
s3:ObjectCreated:CompleteMultipartUpload |
Supported |
s3:ObjectRemoved:* |
Supported |
s3:ObjectRemoved:Delete |
Supported |
s3:ObjectRemoved:DeleteMarkerCreated |
Supported |
s3:ObjectLifecycle:Expiration:Current |
Ceph extension |
s3:ObjectLifecycle:Expiration:NonCurrent |
Ceph extension |
s3:ObjectLifecycle:Expiration:DeleteMarker |
Ceph extension |
s3:ObjectLifecycle:Expiration:AbortMultipartUpload |
Ceph extension |
s3:ObjectLifecycle:Transition:Current |
Ceph extension |
s3:ObjectLifecycle:Transition:NonCurrent |
Ceph extension |
s3:LifecycleExpiration:* |
Supported。等价于 s3:LifecycleExpiration:Delete + s3:LifecycleExpiration:DeleteMarkerCreated |
s3:LifecycleExpiration:Delete |
Supported。等价于 s3:ObjectLifecycle:Expiration:Current |
s3:LifecycleExpiration:DeleteMarkerCreated |
Supported。等价于 s3:ObjectLifecycle:Expiration:DeleteMarker |
s3:LifecycleTransition |
Supported。等价于 s3:ObjectLifecycle:Transition:Current |
s3:ObjectSynced:* |
Ceph extension |
s3:ObjectSynced:Create |
Ceph extension |
s3:ObjectSynced:Delete |
Ceph extension |
s3:ObjectSynced:DeletionMarkerCreated |
已定义,Ceph extension(不会产生) |
s3:Replication:* |
Supported。等价于 s3:ObjectSynced:Create + s3:ObjectSynced:Delete |
s3:Replication:Create |
Supported。等价于 s3:ObjectSynced:Create |
s3:Replication:Delete |
Supported。等价于 s3:ObjectSynced:Delete |
s3:Replication:DeletionMarkerCreated |
已定义,Supported(不会产生) |
s3:ObjectRestore:Post |
Not applicable |
s3:ObjectRestore:Complete |
Not applicable |
s3:ReducedRedundancyLostObject |
Not applicable |
⚠️ 注意:
s3:ObjectRemoved:DeleteMarkerCreated事件呈现的是对象最新版本的信息;- 对于 multipart upload,流程结束时会发送
ObjectCreated:CompleteMultipartUploadnotification;s3:ObjectSynced:Create事件在对象成功同步到某个 zone 时发送,必须为每个 zone 显式设置。
9.6 Topic 配置兼容性
Ceph 的 topic 管理 API 派生自 AWS Simple Notification Service API。SNS API 的大部分对 Ceph 不适用,只实现了以下 action:
CreateTopicDeleteTopicListTopics
Ceph 对 topic 配置的扩展:
| 扩展点 | 说明 |
|---|---|
GetTopic |
允许获取特定 topic,而非该用户的所有 topic |
CreateTopic |
(1) 允许设置 endpoint attributes;(2) 允许设置 opaque data,它会被随通知发送到 endpoint |
十、桶日志(Bucket Logging)
.. versionadded:: T
10.1 概述
| 项 | 说明 |
|---|---|
| 作用 | 记录对 bucket 的所有访问。日志数据可用于监控 bucket 活动、检测未授权访问、洞察 bucket 使用情况,也可作为 bucket 变更的 journal |
| 存储 | 日志记录以对象形式存放在另一个 bucket 中,可后续分析 |
| 配置层级 | bucket 级,可随时启用或禁用 |
| 复用 | 一个 log bucket 可以累积来自多个 bucket 的日志 |
| 建议 | 建议为每个 bucket 配置不同的 "prefix",这样不同 bucket 的日志会存放在 log bucket 中的不同对象里 |
log bucket 的约束:
| 约束 | 说明 |
|---|---|
| 必须先存在 | 启用 logging 之前 log bucket 必须已创建 |
| 不能自记 | log bucket 不能是被记录 bucket 本身 |
| 不能套娃 | log bucket 不能自己开启 logging |
| 不能加密 | log bucket 不能设置任何加密(包括 SSE-S3 with AES-256) |
| 不能压缩 | log bucket 不能设置任何压缩 |
| 不能有 RequestPayer | log bucket 不得启用 RequestPayer |
| 同 zonegroup | 源 bucket 与 log bucket 必须在同一个 zonegroup |
| 可跨账号 | 源 bucket 与 log bucket 可以属于不同 account(需设置适当的 bucket policy) |
| 可带对象锁 | log bucket 可以启用 object lock 并带默认保留期 |
| 唯一 ID | 日志对象名中 16 字节唯一 ID 部分是字典序有序的随机字符串:由 10 字节计数器 + 6 字节随机字母数字串组成(若计数器不可用,则为随机字母数字串) |
10.2 日志可靠性
| 机制 | 说明 |
|---|---|
| 延迟可见 | 出于性能考虑,即使日志记录已写入持久化存储,日志对象也要经过一段可配置的时间后才会出现在 log bucket 中(或当达到最大对象大小 128MB 时) |
| 滚动时间 | 该时间(秒)可按源 bucket 通过 Ceph 对 REST API 的扩展设置,也可通过 rgw_bucket_logging_obj_roll_time 全局设置。未设置时默认 5 分钟 |
| 懒惰添加 | 日志对象是「懒」添加到 log bucket 的——若不再有记录写入该对象,即使配置的时间已过,它也可能仍留在 bucket 之外 |
| Flush | 可强制把某源 bucket 上所有 logging object 刷出并落盘,无论时间是否到达、是否还有记录写入。禁用 bucket logging、修改 logging 配置、或删除 bucket 时会自动 flush |
| 异步 vs 立即 | 由写入日志记录触发的添加是异步的,触发日志的操作会立即完成、不等待;flush 时是立即添加的。这可能导致在待添加对象补齐之前,bucket 内的日志出现临时空洞 |
默认值取自 src/common/options/*.yaml.in @ v20.2.4:
rgw_bucket_logging_obj_roll_time= 300(uint,秒)。
查看某个源 bucket 当前待添加到 log bucket 的日志对象:
radosgw-admin bucket logging list --bucket <source bucket>
两种日志类型
| 类型 | 默认 | 写入时机 | 失败影响 |
|---|---|---|---|
| Standard | 是(默认) | 在 bucket 操作完成之后写入 log bucket | 日志操作可能失败,但客户端无感知 |
| Journal | 否 | 在 bucket 操作完成之前写入 log bucket | 若日志操作失败,则操作不被执行并向客户端返回错误(下表 "Fails Operation" 为 No 的操作除外)。支持基于被记录对象 key 的前缀/后缀过滤,也可使用正则表达式 |
⚠️ Journal 模式下可能出现「日志已成功写入但 bucket 操作失败」的情况,因为日志是先写的。
Journal 模式支持的操作:
| Operation | Operation Name | Fails Operation |
|---|---|---|
PutObject |
REST.PUT.OBJECT |
Yes |
DeleteObject |
REST.DELETE.OBJECT |
No |
DeleteObjects |
REST.POST.DELETE_MULTI_OBJECT |
No |
CompleteMultipartUpload |
REST.POST.UPLOAD |
Yes |
CopyObject |
REST.PUT.OBJECT |
Yes |
PutObjectAcl |
REST.PUT.ACL |
Yes |
PutObjectLegalHold |
REST.PUT.LEGAL_HOLD |
Yes |
PutObjectRetention |
REST.PUT.RETENTION |
Yes |
PutObjectTagging |
REST.PUT.OBJECT_TAGGING |
Yes |
DeleteObjectTagging |
REST.DELETE.OBJECT_TAGGING |
No |
多站点(Multisite)
在多 zone 部署中,每个 zone 使用各自的日志对象,直到该日志对象被添加到 log bucket。添加到 log bucket(即 flush)之后,它才会被复制到其他 zone。这意味着在同一时间段内,可能有多于一个日志对象持有相关日志记录。
10.3 Bucket Logging Policy
- 只有源 bucket 的 owner允许启用或禁用 bucket logging;
- 一个 bucket 要被用作 log bucket,必须有允许这件事的 bucket policy(即使源 bucket 与 log bucket 属于同一用户或 account);
- 该 bucket policy 必须允许
logging.s3.amazonaws.com这个 service principal 对 log bucket 执行s3:PutObjectaction; - policy 还应指定预期写入日志的源 bucket 与 account。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowLoggingFromSourceBucket",
"Effect": "Allow",
"Principal": {
"Service": "logging.s3.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::log-bucket-name/prefix*",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "source-account-id"
},
"ArnLike": {
"aws:SourceArn": "arn:aws:s3:::source-bucket-name"
}
}
}
]
}
10.4 Bucket Logging Quota
| 项 | 说明 |
|---|---|
| 适用范围 | bucket quota 与 user quota 都会作用到 log bucket |
| 检查时机 | 每次写入日志记录时检查配额;在日志对象被添加到 log bucket 时更新 |
| Journal 模式 | 若配额超限,logging 操作失败,进而bucket 操作也失败 |
| Standard 模式 | logging 操作被跳过,但 bucket 操作继续 |
10.5 日志对象 Key 格式
Simple
<prefix><year-month-day-hour-minute-second>-<16 bytes unique-id>
示例:
fish/2024-08-06-09-40-09-0000000002AGQ6W1
Partitioned
<prefix><source bucket owner>/<zone group>/[tenant:]<source bucket name>/<year>/<month>/<day>/<year-month-day-hour-minute-second>-<16 bytes unique-id>
示例:
fish/testid/default/fish-bucket/2024/08/06/2024-08-06-10-11-18-0000000011D1FGPA
10.6 日志记录格式
日志记录是空格分隔的字符串列。
Journal 格式(Ceph 扩展,用最少数据做 journaling)
| 列序 | 内容 |
|---|---|
| 1 | bucket owner(为空则为 dash) |
| 2 | bucket name(为空则为 dash),格式 [tenant:]<bucket name> |
| 3 | time,格式 [day/month/year:hour:minute:second timezone] |
| 4 | operation,格式 WEBSITE/REST.<HTTP method>.<resource> |
| 5 | object key(为空则为 dash) |
| 6 | object size(为空则为 dash) |
| 7 | version id(为空则为 dash) |
| 8 | eTag(为空则为 dash) |
示例:
testid fish [06/Aug/2024:09:40:09 +0000] REST.PUT.OBJECT myfile - 512 4cfdfc1f58e762d3e116787cb92fac60
testid fish [06/Aug/2024:09:40:28 +0000] REST.DELETE.OBJECT myfile - - 4cfdfc1f58e762d3e116787cb92fac60
Standard 格式(基于 AWS Logging Record Format)
| 列序 | 内容 |
|---|---|
| 1 | bucket owner(为空则为 dash) |
| 2 | bucket name(为空则为 dash),格式 [tenant:]<bucket name> |
| 3 | time,格式 [day/month/year:hour:minute:second timezone],timezone 为 UTC offset |
| 4 | client IP address(为空则为 dash) |
| 5 | user 或 account(为空则为 dash) |
| 6 | request ID |
| 7 | operation,格式 WEBSITE/REST.<HTTP method>.<resource> |
| 8 | object key(为空则为 dash) |
| 9 | request URI,格式 "<HTTP method> <URI> <HTTP version>" |
| 10 | HTTP status(为 0 则为 dash)。注意多数情况下日志是在 status 已知之前写入的 |
| 11 | error code(为空则为 dash) |
| 12 | bytes sent(为 0 则为 dash) |
| 13 | object size(为 0 则为 dash) |
| 14 | total time(不支持,始终为 dash) |
| 15 | turnaround time(毫秒) |
| 16 | referer(为空则为 dash) |
| 17 | user agent(为空则为 dash),位于双引号内 |
| 18 | version id(为空则为 dash) |
| 19 | host id,取自 x-amz-id-2(为空则为 dash) |
| 20 | signature version(为空则为 dash) |
| 21 | cipher suite(为空则为 dash) |
| 22 | authentication type(常规 auth 为 AuthHeader;presigned URL 为 QueryString;未认证为 dash) |
| 23 | host header(为空则为 dash) |
| 24 | TLS version(为空则为 dash) |
| 25 | access point ARN(不支持,始终为 dash) |
| 26 | ACL flag(若授权需要 ACL 则为 Yes,否则为 dash) |
示例:
testid fish [06/Aug/2024:09:30:25 +0000] - testid 9e369a15-5f43-4f07-b638-de920b22f91b.4179.15085270386962380710 REST.PUT.OBJECT myfile "PUT /fish/myfile HTTP/1.1" 200 - 512 512 - - - - - - - - - localhost - -
testid fish [06/Aug/2024:09:30:51 +0000] - testid 9e369a15-5f43-4f07-b638-de920b22f91b.4179.7046073853138417766 REST.GET.OBJECT myfile "GET /fish/myfile HTTP/1.1" 200 - - 512 - - - - - - - - - localhost - -
testid fish [06/Aug/2024:09:30:56 +0000] - testid 9e369a15-5f43-4f07-b638-de920b22f91b.4179.10723158448701085570 REST.DELETE.OBJECT myfile "DELETE /fish/myfile1 HTTP/1.1" 200 - - 512 - - - - - - - - - localhost - -
十一、S3 Select
11.1 概述
| 项 | 说明 |
|---|---|
| 定位 | S3 Select 引擎在客户端与 Ceph 后端节点之间建立一条高效的管道,越靠近后端存储效果越好 |
| 功能 | 用类 SQL 语法从一个 S3 对象中选出受限的数据子集 |
| 上层价值 | 便于使用更高层的分析应用(例如 SPARK-SQL);只取结构化数据中合适的子集,降低延迟、提升吞吐 |
| 性能特征 | 对象越大、查询越精确,s3select 性能收益越明显 |
示例场景:用户需要从一个数 GB 大小的 CSV 对象中,取出按另一列过滤后的某一列:
select customer-id from s3Object where age>30 and age<65;
不使用 S3 Select 时,整个 S3 对象必须先经 RGW 从 OSD 取回,再做过滤与抽取;把查询「下推」到 radosgw 可省下大量网络与 CPU 开销。
11.2 基本工作流
| 步骤 | 说明 |
|---|---|
| 1 | S3 Select 查询通过 AWS-CLI 发给 RGW(select-object-content) |
| 2 | 认证与权限参数随 POST 消息传入。RGWSelectObj_ObjStore_S3::send_response_data 是入口点,按输入的对象 key 处理每个取到的 chunk;它也是第一个处理输入查询的地方——抽取查询及其他 CLI 参数 |
| 3 | RGW 对**每个新取到的 chunk(最大 4 MB)**执行 S3 Select 查询 |
| 4 | CSV 行有时会被 chunk 边界「切断」,这些断行(每个 chunk 的首行或末行)在查询处理时被跳过;被跳过的断行会被保存,并与下一个断行(属于下一个 chunk)合并后才处理 |
| 5 | 每个处理完的 chunk,按 AWS 规范格式化为输出消息发回客户端 |
支持的响应类型:
{:event-type,records} {:content-type,application/octet-stream} {:message-type,event}
对于聚合查询,最后一个 chunk 应被标识为输入结束。
基本功能: s3select 有一套与 AWS 兼容的确定功能集,架构上支持基本算术表达式、逻辑与比较表达式,包括嵌套函数调用与类型转换操作符。
11.3 错误处理
| 类别 | 说明 |
|---|---|
| 统一返回 | 检测到错误时,RGW 返回 400 Bad Request,并把特定错误消息发回客户端 |
| Syntax error(语法错误) | s3select parser 拒绝不符合本文档所述语法定义的用户请求。引擎会生成指出错误位置的错误消息,RGW 以特定的 error response 发回 |
| Processing Time error(运行时错误) | 运行时引擎可能检测到只在处理时才出现的错误,用另一类错误消息描述,RGW 同样以特定的 error response 发回 |
⚠️ 限制/未明确项:官方源文没有给出一张完整的错误码表(只给了
400 Bad Request与一个NoSuchKey的 XML 响应示例)。其余错误码:文档未明确。
11.4 功能支持总表(Features Support)
目前只实现了 AWS select command 的一部分,下表描述当前实现的功能。
| Feature | Detailed | Example / Description |
|---|---|---|
| Arithmetic operators | ^ * % / + - ( ) |
select (int(_1)+int(_2))*int(_9) from s3object; |
| (同上) | % modulo |
select count(*) from s3object where cast(_1 as int)%2 = 0; |
| (同上) | ^ power-of |
select cast(2^10 as int) from s3object; |
| Compare operators | > < >= <= = != |
select _1,_2 from s3object where (int(_1)+int(_3))>int(_5); |
| logical operator | AND OR NOT |
select count(*) from s3object where not (int(_1)>123 and int(_5)<200); |
| logical operator | is null |
对表达式中的 null 指示返回 true/false |
| logical operator | is not null |
对表达式中的 null 指示返回 true/false |
| logical operator and NULL | unknown state | select count(*) from s3object where null and (3>2); → 返回 0 |
| Arithmetic operator with NULL | unknown state | select count(*) from s3object where (null+1) and (3>2); → 返回 0 |
| compare with NULL | unknown state | select count(*) from s3object where (null*1.5) != 3; → 返回 0 |
| missing column | unknown state | select count(*) from s3object where _1 is null; |
| 过滤谓词返回非空结果的行 | —— | select count(*) from s3object where (_1 > 12 and _2 = 0) is not null; —— 该谓词在 _1 或 _2 为 null 时返回 null |
| projection column | 类似 switch/case default | select case cast(_1 as int) + 1 when 2 then "a" when 3 then "b" else "c" end from s3object; |
| projection column | 类似 if/then/else | select case when (1+1=(2+1)*3) then 'case_1' when ((4*3)=(12)) then 'case_2' else 'case_else' end, age*2 from s3object; |
| logical operator | coalesce {expression,expression ...}:返回第一个非 null 参数 |
select coalesce(nullif(5,5),nullif(1,1.0),age+12) from s3object; |
| logical operator | nullif {expr1,expr2}:两参数相等则返回 null,否则返回第一个 |
select nullif(cast(_1 as int),cast(_2 as int)) from s3object; |
| logical operator | {expression} in ( .. {expression} ..) |
select count(*) from s3object where 'ben' in (trim(_5),substring(_1,char_length(_1)-3,3),last_name); |
| logical operator | {expression} between {expression} and {expression} |
select count(*) from s3object where substring(_3,char_length(_3),1) between "x" and trim(_1) and substring(_3,char_length(_3)-1,1) = ":"; |
| logical operator | {expression} like {match-pattern} |
select count(*) from s3object where first_name like '%de_'; / select count(*) from s3object where _1 like "%a[r-s]; |
| logical operator | {expression} like {match-pattern} escape {char} |
select count(*) from s3object where "jok_ai" like "%#_ai" escape "#"; |
| true / false predicate as a projection | —— | select (cast(_1 as int)>123 = true) from s3object where address like '%new-york%'; |
| an alias to predicate as a projection | —— | select (_1 like "_3_") as *likealias*,_1 from s3object where *likealias* = true and cast(_1 as int) between 800 and 900; |
| casting operator | int |
select cast(123 as int)%2 from s3object; |
| casting operator | float |
select cast(123.456 as float)%2 from s3object; |
| casting operator | string |
select cast('ABC0-9' as string),cast(substr('ab12cd',3,2) as int)*4 from s3object; |
| casting operator | bool |
select cast(5 as bool) from s3object; |
| casting operator | timestamp |
select cast(substring('publish on 2007-01-01',12,10) as timestamp) from s3object; |
| non AWS casting operator | int() |
select int(_1),int( 1.2 + 3.4) from s3object; |
| non AWS casting operator | float() |
select float(1.2) from s3object; |
| not AWS casting operator | to_timestamp() |
select to_timestamp('1999-10-10T12:23:44Z') from s3object; |
| Aggregation Function | sum |
select sum(int(_1)) from s3object; |
| Aggregation Function | avg |
select avg(cast(_1 a float) + cast(_2 as int)) from s3object; |
| Aggregation Function | min |
select min( int(_1) * int(_5) ) from s3object; |
| Aggregation Function | max |
select max(float(_1)),min(int(_5)) from s3object; |
| Aggregation Function | count |
select count(*) from s3object where (int(_1)+int(_3))>int(_5); |
| Timestamp Functions | extract |
select count(*) from s3object where extract(year from to_timestamp(_2)) > 1950 and extract(year from to_timestamp(_1)) < 1960; |
| Timestamp Functions | date_add |
select count(0) from s3object where date_diff(year,to_timestamp(_1),date_add(day,366,to_timestamp(_1))) = 1; |
| Timestamp Functions | date_diff |
select count(0) from s3object where date_diff(month,to_timestamp(_1),to_timestamp(_2)) = 2; |
| Timestamp Functions | utcnow |
select count(0) from s3object where date_diff(hours,utcnow(),date_add(day,1,utcnow())) = 24; |
| Timestamp Functions | to_string |
select to_string(to_timestamp("2009-09-17T17:56:06.234567Z"),"yyyyMMdd-H:m:s") from s3object; → 结果 "20090917-17:56:6" |
| String Functions | substring |
select count(0) from s3object where int(substring(_1,1,4))>1950 and int(substring(_1,1,4))<1960; |
substring with from 负数 |
视为 first | select substring("123456789" from -4) from s3object; |
substring with from 零、for 越界 |
合法,等同 (first,last) | select substring("123456789" from 0 for 100) from s3object; |
| String Functions | trim(默认) |
select trim(' foobar ') from s3object; |
| String Functions | trim(trailing from ...) |
select trim(trailing from ' foobar ') from s3object; |
| String Functions | trim(leading from ...) |
select trim(leading from ' foobar ') from s3object; |
| String Functions | trim(both '12' from ...) |
select trim(both '12' from '1112211foobar22211122') from s3objects; |
| String Functions | lower / upper |
select lower('ABcD12#$e') from s3object; |
| String Functions | char_length / character_length |
select count(*) from s3object where char_length(_3)=3; |
| Complex queries | —— | select sum(cast(_1 as int)), max(cast(_3 as int)), substring('abcdefghijklm',(2-1)*3+sum(cast(_1 as int))/sum(cast(_1 as int))+1, (count() + count(0))/count(0)) from s3object; |
| alias support | —— | select int(_1) as a1, int(_2) as a2 , (a1+a2) as a3 from s3object where a3>100 and a3<300; |
11.5 NULL 处理
NULL 的定义:在 ceph-s3select 中,NULL 与其他 DB 系统一样是合法值,表示 missing / unknown。
- 任何算术运算中 NULL 都不能产生值(
a + NULL产生 NULL); - 任何与 NULL 的比较结果都是 NULL(unknown)。
NULL 真值表:
| A is NULL | Result (NULL=UNKNOWN) |
|---|---|
NOT A |
NULL |
A OR False |
NULL |
A OR True |
True |
A OR A |
NULL |
A AND False |
False |
A AND True |
NULL |
A and A |
NULL |
11.6 函数接口
Timestamp Functions(AWS 规范已完全实现)
| 函数 | 说明 |
|---|---|
to_timestamp( string ) |
把字符串转换为 timestamp 基本类型。可转换以下格式:YYYY-MM-DDTHH:mm:ss.SSSSSS+/-HH:mm、YYYY-MM-DDTHH:mm:ss.SSSSSSZ、YYYY-MM-DDTHH:mm:ss+/-HH:mm、YYYY-MM-DDTHH:mm:ssZ、YYYY-MM-DDTHH:mm+/-HH:mm、YYYY-MM-DDTHH:mmZ、YYYY-MM-DDT、YYYYT。字符串格式中缺失的时间(或部分)用 0 替代;缺失的 month 与 day 默认为 1。时区部分格式为 +/-HH:mm 或 Z("Z" 表示 UTC),时区取值范围 -12:00 ~ +14:00 |
extract(date-part from timestamp) |
从输入 timestamp 中抽取 date-part 并以 integer 返回。支持的 date-part:year、month、week、day、hour、minute、second、timezone_hour、timezone_minute |
date_add(date-part, quantity, timestamp) |
把 quantity(integer)加到 timestamp 的 date-part 上,结果以 timestamp 返回,计算包含时区。支持的 date-part:year、month、day、hour、minute、second |
date_diff(date-part, timestamp, timestamp) |
返回 integer:两个 timestamp 按 date-part 计算的差值,计算包含时区。支持的 date-part:year、month、day、hour、minute、second |
utcnow() |
返回当前时间的 timestamp |
to_string(timestamp, format_pattern) |
按给定字符串格式返回输入 timestamp 的字符串表示 |
to_string 格式参数全表
| Format | Example | Description |
|---|---|---|
yy |
69 | 2-digit year |
y |
1969 | 4-digit year |
yyyy |
1969 | Zero-padded 4-digit year |
M |
1 | Month of year |
MM |
01 | Zero-padded month of year |
MMM |
Jan | Abbreviated month year name |
MMMM |
January | Full month of year name |
MMMMM |
J | Month of year first letter(注意:不能用于 to_timestamp 函数) |
d |
2 | Day of month (1-31) |
dd |
02 | Zero-padded day of month (01-31) |
a |
AM | AM or PM of day |
h |
3 | Hour of day (1-12) |
hh |
03 | Zero-padded hour of day (01-12) |
H |
3 | Hour of day (0-23) |
HH |
03 | Zero-padded hour of day (00-23) |
m |
4 | Minute of hour (0-59) |
mm |
04 | Zero-padded minute of hour (00-59) |
s |
5 | Second of minute (0-59) |
ss |
05 | Zero-padded second of minute (00-59) |
S |
0 | Fraction of second(precision: 0.1,range: 0.0-0.9) |
SS |
6 | Fraction of second(precision: 0.01,range: 0.0-0.99) |
SSS |
60 | Fraction of second(precision: 0.001,range: 0.0-0.999) |
SSSSSS |
60000000 | Fraction of second(maximum precision: 1 nanosecond,range: 0.0-0999999999) |
n |
60000000 | Nano of second |
X |
+07 or Z | Offset in hours or "Z" if the offset is 0 |
XX or XXXX |
+0700 or Z | Offset in hours and minutes or "Z" if the offset is 0 |
XXX or XXXXX |
+07:00 or Z | Offset in hours and minutes or "Z" if the offset is 0 |
X |
7 | Offset in hours |
xx or xxxx |
700 | Offset in hours and minutes |
xxx or xxxxx |
+07:00 | Offset in hours and minutes |
Aggregation Functions
| 函数 | 说明 |
|---|---|
count() |
按匹配条件(若有)的行数返回 integer |
sum(expression) |
对所有匹配条件(若有)的行,返回表达式的汇总值 |
avg(expression) |
对所有匹配条件(若有)的行,返回表达式的平均值 |
max(expression) |
返回所有匹配条件(若有)的表达式结果中的最大值 |
min(expression) |
返回所有匹配条件(若有)的表达式结果中的最小值 |
String Functions
| 函数 | 说明 |
|---|---|
substring(string,from,to) |
即 substring( string from start [ for length ] ):按 from、to 输入从输入字符串中抽取子串。也支持 substring(string from ) 与 substring(string from for) 形式 |
char_length |
返回字符串中的字符数(character_length 同义) |
trim |
即 trim ( [[leading | trailing | both remove_chars] from] string ):从目标字符串去除 leading/trailing(或 both)字符,默认为空白字符 |
upper / lower |
转换为大写 / 小写 |
SQL LIMIT 操作符
LIMIT 用于限制查询处理的行数。达到用户设置的 limit 后,RGW 停止获取额外的 chunk。
⚠️ 源文中
LIMIT一节标注了TODO : add examples, for aggregation and non-aggregation queries.,即官方尚未给出 LIMIT 的示例。
Alias(别名)
| 特性 | 说明 |
|---|---|
| 作用 | 别名是 s3select 语言的重要组成部分,在对象列很多或查询很复杂时显著提升可读性 |
| 实现 | 解析含 alias 的语句时,会把 alias 替换为对正确 projection column 的引用;执行时该引用与其他表达式一样被求值 |
| 循环引用防护 | 存在自引用(或循环引用)导致栈溢出(死循环)的风险,因此在求值 alias 时会校验是否存在循环引用 |
| 结果缓存 | alias 维护一个结果缓存,重复使用同一 alias 不会再次求值,而是从缓存返回 |
| 缓存失效 | 每换一行,缓存失效(因为结果可能不同) |
测试框架(源文列出 3 类)
| 编号 | 方法 |
|---|---|
| (1) | 与可信引擎对比:C/C++ 编译器是可靠的表达式求值器,且算术/逻辑表达式语法与 s3select 相同,因此框架运行等价表达式并校验结果。有专门的表达式生成器,每个测试会话生成不同的表达式集合 |
| (2) | 比较「语法不同但语义等价」的查询结果,验证不同运行时流程在同一随机数据集上产生一致结果。例如在含 1-1000 随机数的 dataset 上,select count(*) from s3object where char_length(_3)=3; 与 select count(*) from s3object where cast(_3 as int)>99 and cast(_3 as int)<1000; 结果相同 |
| (3) | 常量数据集:传统测试方式,查询处理常量数据集,结果与常量结果比对 |
额外语法支持
| 特性 | 示例 |
|---|---|
| 表别名 | select s._1 from s3object s where s._2 = ‘4’; |
| 大小写不敏感 | Select SUM(Cast(_1 as int)) FROM S3Object; |
| 结尾分号可选 | select count(*) from s3object |
11.7 发送查询到 RGW
任何 HTTP 客户端都可向 RGW 发送 s3-select 请求,但必须符合 AWS Request syntax。使用 AWS CLI 时需遵循 AWS command reference。
aws --endpoint-url http://localhost:8000 s3api select-object-content
--bucket {BUCKET-NAME}
--expression-type 'SQL'
--scan-range '{"Start" : 1000, "End" : 1000000}'
--input-serialization
'{"CSV": {"FieldDelimiter": "," , "QuoteCharacter": "\"" , "RecordDelimiter" : "\n" , "QuoteEscapeCharacter" : "\\" , "FileHeaderInfo": "USE" }, "CompressionType": "NONE"}'
--output-serialization '{"CSV": {"FieldDelimiter": ":", "RecordDelimiter":"\t", "QuoteFields": "ALWAYS"}}'
--key {OBJECT-NAME}
--request-progress '{"Enabled": True}'
--expression "select count(0) from s3object where int(_1)<10;" output.csv
Input Serialization
| 参数 | 取值 / 说明 |
|---|---|
FileHeaderInfo |
NONE:第一行不是 header。IGNORE:第一行是 header,但不能用 header 值在表达式中指示列;可以用列位置(_1、_2 …)指示列,例如 SELECT s._1 FROM S3OBJECT s。USE:第一行是 header,可以用 header 值在表达式中标识列,例如 SELECT column_name FROM S3OBJECT |
QuoteEscapeCharacter |
单个字符,用于在已被转义的值内部转义引号字符 |
RecordDelimiter |
单个字符,用于分隔输入中的各条记录;可指定任意分隔符以替代默认值 |
FieldDelimiter |
单个字符,用于分隔记录中的各个字段;可指定任意分隔符 |
Output Serialization(AWS CLI 示例)
aws s3api select-object-content \
--bucket "mybucket" \
--key keyfile1 \
--expression "SELECT * FROM s3object s" \
--expression-type 'SQL' \
--request-progress '{"Enabled": false}' \
--input-serialization '{"CSV": {"FieldDelimiter": ","}, "CompressionType": "NONE"}' \
--output-serialization '{"CSV": {"FieldDelimiter": ":", "RecordDelimiter":"\\t", "QuoteFields": "ALWAYS"}}' /dev/stdout
| 参数 | 取值 / 说明 |
|---|---|
QuoteFields |
是否在输出字段周围使用引号。ALWAYS:总是使用引号。ASNEEDED:仅在需要时使用引号(未实现) |
RecordDelimiter |
单个字符,用于分隔输出中的各条记录;可指定任意分隔符 |
FieldDelimiter |
用于分隔记录中各个字段的值;可指定任意分隔符 |
Scan Range Option
--scan-range 让客户端只扫描和处理对象的选定部分,跳过不感兴趣的部分,从而减少 I/O 与带宽。
⚠️ 源文中该节标注了
TODO : different data-sources (CSV, JSON, Parquet),即不同数据源下的 scan range 行为文档未明确。
11.8 CSV 解析行为
s3-select 引擎内含一个 CSV parser,按以下规则解析 s3 对象:
| 规则 | 说明 |
|---|---|
| 行结束 | 每行以 row-delimiter 结尾 |
| 字段分隔 | field-separator 分隔相邻列;连续的 field separator 定义 NULL 列 |
| 引号 | quote-character 覆盖 field separator——引号之间的 field separator 被当作普通字符 |
| 转义 | escape character 使特殊字符失去特殊含义,但 row delimiter 除外 |
CSV 解析规则示例:
| Feature | Description | input ==> tokens |
|---|---|---|
| NULL | 连续的 field delimiter | ,,1,,2, ==> {null}{null}{1}{null}{2}{null} |
| QUOTE | quote character 覆盖 field delimiter | 11,22,"a,b,c,d",last ==> {11}{22}{"a,b,c,d"}{last} |
| Escape | escape char 覆盖元字符,escape 被移除 | 11,22,str=\"abcd\"\,str2=\"123\",last ==> {11}{22}{str="abcd",str2="123"}{last} |
| row delimiter | 无闭合引号时,row delimiter 作为行的结束 | 11,22,a="str,44,55,66 ==> {11}{22}{a="str,44,55,66} |
| csv header info | FileHeaderInfo 标签 |
"USE":第一行每个 token 都是列名;"IGNORE":跳过第一行 |
11.9 JSON 支持
| 项 | 说明 |
|---|---|
| 集成方式 | 一个 JSON reader 已集成进 s3select-engine,让客户端能用 SQL 语句扫描并抽取 JSON 文档中的信息 |
| 架构 | CSV、Parquet、JSON 的 data reader 与 parser 与 SQL 引擎本身分离,因此这些 reader 共用同一套 SQL 引擎 |
| 嵌套 | JSON 文档中的值可以以各种方式嵌套(在 object 或 array 内),object 与 array 可无限制互相嵌套 |
| 定位 | 用 SQL 查询 JSON 文档中的特定值时,客户端必须在 SELECT 语句中通过**路径(path)**指定该值的位置 |
| 行列结构 | SQL 引擎以行式处理 SELECT 语句,用语句中指定的列做投影计算,每行包含这些列的值。但JSON 文档本身没有像 CSV 或 Parquet 那样的行列结构——查询 JSON 文档时,是 SQL 语句本身定义了行与列 |
| 行边界 | FROM 子句定义行边界,类似 CSV 用 row delimiter 定义行、Parquet 用 row group 定义行。语句 SELECT ... FROM s3object[*].aaa.bb.cc 指示 reader 搜索路径 aaa.bb.cc,并基于该路径的出现来定义行边界:reader 遇到该路径时一行开始,退出该路径最内层(此例为 object cc)时该行结束 |
⚠️ 注意(源文原话):查询 JSON 文档的语义可能会变化,未必与当前描述的方法一致。 ⚠️ 源文中该节标注了
TODO : relevant example for object and array values.
JSON 查询示例:
{
"firstName": "Joe",
"lastName": "Jackson",
"gender": "male",
"age": "twenty",
"address": {
"streetAddress": "101",
"city": "San Diego",
"state": "CA"
},
"firstName": "Joe_2",
"lastName": "Jackson_2",
"gender": "male",
"age": 21,
"address": {
"streetAddress": "101",
"city": "San Diego",
"state": "CA"
},
"phoneNumbers": [
{ "type": "home1", "number": "734928_1","addr": 11 },
{ "type": "home2", "number": "734928_2","addr": 22 },
{ "type": "home3", "number": "734928_3","addr": 33 },
{ "type": "home4", "number": "734928_4","addr": 44 },
{ "type": "home5", "number": "734928_5","addr": 55 },
{ "type": "home6", "number": "734928_6","addr": 66 },
{ "type": "home7", "number": "734928_7","addr": 77 },
{ "type": "home8", "number": "734928_8","addr": 88 },
{ "type": "home9", "number": "734928_9","addr": 99 },
{ "type": "home10", "number": "734928_10","addr": 100 }
],
"key_after_array": "XXX",
"description" : {
"main_desc" : "value_1",
"second_desc" : "value_2"
}
}
-- the from-clause define a single row.
-- _1 points to root object level.
-- _1.age appears twice in Documnet-row, the last value is used for the operation.
select _1.firstname,_1.key_after_array,_1.age+4,_1.description.main_desc,_1.description.second_desc from s3object[*];
-- expected_result = Joe_2,XXX,25,value_1,value_2
-- the from-clause points the phonenumbers array (it defines the _1)
-- each element in phoneNumbers array define a row.
-- in this case each element is an object contains 3 keys/values.
-- the query "can not access" values outside phonenumbers array, the query can access only values appears on _1.phonenumbers path.
select cast(substring(_1.number,1,6) as int) *10 from s3object[*].phonenumbers where _1.type='home2';
-- expected_result = 7349280
11.10 Parquet 支持
| 项 | 状态 |
|---|---|
| 是否存在 Parquet reader | 源文两处提到 Parquet 的 data reader 与 parser(与 CSV、JSON 并列),说明存在 Parquet 支持 |
| 语法文档 | 文档未明确——源文未给出任何 Parquet 的查询语法、示例或限制;相关段落标注为 TODO |
11.11 数据类型
⚠️ 文档未明确:官方 s3select 文档没有单独列出一张「支持的数据类型」表。从 cast 与函数描述中可以确定引擎至少处理以下类型:
| 类型 | 来源依据 |
|---|---|
int / integer |
cast(... as int)、int(...)、extract 返回 integer、date_diff 返回 integer |
float |
cast(... as float)、float(...) |
string |
cast(... as string)、to_string(...) |
bool |
cast(... as bool)、true/false predicate |
timestamp |
cast(... as timestamp)、to_timestamp(...)、utcnow() |
| NULL | 独立的 NULL 语义章节(unknown state) |
11.12 BOTO3 示例
由于 AWS-cli 支持,使用 BOTO3 是「自然且简单」的。
import pprint
def run_s3select(bucket,key,query,column_delim=",",row_delim="\n",quot_char='"',esc_char='\\',csv_header_info="NONE"):
s3 = boto3.client('s3',
endpoint_url=endpoint,
aws_access_key_id=access_key,
region_name=region_name,
aws_secret_access_key=secret_key)
result = ""
try:
r = s3.select_object_content(
Bucket=bucket,
Key=key,
ExpressionType='SQL',
InputSerialization = {"CSV": {"RecordDelimiter" : row_delim, "FieldDelimiter" : column_delim,"QuoteEscapeCharacter": esc_char, "QuoteCharacter": quot_char, "FileHeaderInfo": csv_header_info}, "CompressionType": "NONE"},
OutputSerialization = {"CSV": {}},
Expression=query,
RequestProgress = {"Enabled": progress})
except ClientError as c:
result += str(c)
return result
for event in r['Payload']:
if 'Records' in event:
result = ""
records = event['Records']['Payload'].decode('utf-8')
result += records
if 'Progress' in event:
print("progress")
pprint.pprint(event['Progress'],width=1)
if 'Stats' in event:
print("Stats")
pprint.pprint(event['Stats'],width=1)
if 'End' in event:
print("End")
pprint.pprint(event['End'],width=1)
return result
run_s3select(
"my_bucket",
"my_csv_object",
"select int(_1) as a1, int(_2) as a2 , (a1+a2) as a3 from s3object where a3>100 and a3<300;")
11.13 S3 SELECT 响应
Error Response
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>NoSuchKey</Code>
<Message>The resource you requested does not exist</Message>
<Resource>/mybucket/myfoto.jpg</Resource>
<RequestId>4442587FB7D0A2F9</RequestId>
</Error>
Report Response
HTTP/1.1 200
<?xml version="1.0" encoding="UTF-8"?>
<Payload>
<Records>
<Payload>blob</Payload>
</Records>
<Stats>
<Details>
<BytesProcessed>long</BytesProcessed>
<BytesReturned>long</BytesReturned>
<BytesScanned>long</BytesScanned>
</Details>
</Stats>
<Progress>
<Details>
<BytesProcessed>long</BytesProcessed>
<BytesReturned>long</BytesReturned>
<BytesScanned>long</BytesScanned>
</Details>
</Progress>
<Cont>
</Cont>
<End>
</End>
</Payload>
消息类型说明
| 消息类型 | 说明 |
|---|---|
| Records message | 可包含单条记录、部分记录或多条记录。根据结果大小,一个响应可包含一条或多条此类消息 |
| Error message | 检测到错误时,RGW 返回 400 Bad Request,并按错误类型把特定错误消息发回客户端 |
| Continuation message | Ceph S3 周期性发送此消息以保持 TCP 连接打开。这些消息在响应中随机出现,客户端必须检测消息类型并作相应处理 |
| Progress message | 若请求了进度信息,Ceph S3 会周期性发送此消息,包含已开始但尚未完成的查询的进度信息 |
| Stats message | Ceph S3 在请求结束时发送此消息,包含查询的统计信息 |
| End message | 表示请求已完成,且不会再发送消息。在客户端收到 End message 之前,不应假定请求已完成 |
十二、缓存与性能
12.1 RGW Data Caching and CDN(基于 Nginx)
.. versionadded:: Octopus
原理
| 项 | 说明 |
|---|---|
| 作用 | 让 RGW 能安全地缓存对象,把负载从集群上卸载下来,使用 Nginx 实现 |
| 缓存时机 | 对象首次被访问后会存入 Nginx 缓存目录;数据已缓存时无需再从 RGW 取 |
| 权限校验 | 缓存命中时仍会向 RGW 做一次权限检查,确保请求用户有访问权限 |
| 依赖 | 基于 Nginx 模块 ngx_http_auth_request_module、nginx-aws-auth-module,以及提供 Lua 能力的 OpenResty |
| 当前范围 | 仅缓存 AWSv4 请求(仅 S3 请求):对第一个 GET 请求 cache-in,后续 GET 请求 cache-out;对 PUT、POST、HEAD、DELETE、COPY 请求透明透传 |
💡 注意:
D3N RGW Data Cache是另一种原生实现在 RADOS Gateway 内部的数据缓存机制(见 12.2)。
两个新 API
| API | 说明 |
|---|---|
| Auth API | 缓存用它来验证用户能否访问缓存数据。它利用 RGW 对客户端凭据与存储访问策略的认知:若被封装的请求会被授权,则返回成功 |
| Cache API | 提供安全地覆写 Range 头的能力,使 Nginx 能在 S3 之上使用自己的 smart cache。使用该 API 可在客户端请求特定 range 时**预读(read ahead)**对象。后续访问已缓存对象时,Nginx 从缓存中满足已缓存 range 的请求;未缓存的 range 则从 RGW 读取(并缓存) |
创建 cache 用户
radosgw-admin user create --uid=<uid for cache user> --display-name="cache user" --caps="amz-cache=read"
该用户可以向 RGW 发送 Cache API 头 X-Amz-Cache,其中包含原始请求的头部(在修改 Range 头之前):
- 各原始头部之间用 ASCII 码 177(十进制) 的字符分隔;
- 每个原始头部与其值之间用 ASCII 码 178(十进制) 的字符分隔。
RGW 会检查该用户是否为已授权用户、且为 cache user。两项检查通过后,RGW 会用 X-Amz-Cache 中保存的原始头部重新校验用户权限,并在此过程中覆写 Range 头。
部署步骤
# 1. 下载 OpenResty 源码
wget https://openresty.org/download/openresty-1.15.8.3.tar.gz
# 2. 克隆 Nginx AWS 认证模块
git clone https://github.com/kaltura/nginx-aws-auth-module
# 3. 解包
tar xvzf openresty-1.15.8.3.tar.gz
cd openresty-1.15.8.3
# 4. 编译(需 pcre、openssl 等库)
sudo yum install pcre-devel openssl-devel gcc curl zlib-devel nginx
./configure --add-module=<the nginx-aws-auth-module dir> --with-http_auth_request_module --with-http_slice_module --conf-path=/etc/nginx/nginx.conf
gmake -j $(nproc)
sudo gmake install
sudo ln -sf /usr/local/openresty/bin/openresty /usr/bin/nginx
示例配置文件位置:https://github.com/ceph/ceph/tree/main/examples/rgw/rgw-cache
| 文件 | 放置位置 |
|---|---|
nginx.conf |
/etc/nginx/nginx.conf |
nginx-lua-file.lua |
/etc/nginx/nginx-lua-file.lua |
nginx-default.conf |
/etc/nginx/conf.d/nginx-default.conf |
最可能需要按环境调整的参数(位于 nginx-default.conf):
proxy_cache_path /data/cache levels=2:2:2 keys_zone=mycache:999m max_size=20G inactive=1d use_temp_path=off;
server rgw1:8000 max_fails=2 fail_timeout=5s;
server rgw2:8000 max_fails=2 fail_timeout=5s;
server rgw3:8000 max_fails=2 fail_timeout=5s;
⚠️ 必须用具有
amz-cachecaps 的用户的 access key 与 secret key 替换nginx.conf中的示例值:
radosgw-admin user create --uid=cacheuser --display-name="cache user" --caps="amz-cache=read" --access-key <access> --secret <secret>
可选配置:
| 需求 | 做法 |
|---|---|
| 使用 Nginx slicing(适合流式场景) | 用 nginx-slicing.conf 代替 nginx-default.conf |
| 不使用 prefetch caching | 用 nginx-noprefetch.conf 代替 nginx-default.conf。禁用 prefetch 后,Nginx 会分别缓存每个 range 请求,range 请求之间的重叠部分会被重复从 RGW 取。例如客户端先请求 0-4095 再请求 0-4096,两个请求都会完整地从 RGW 取 |
启动 Nginx(OpenResty):
systemctl restart nginx
性能附注
在某些场景(例如开发环境)下,禁用认证可能显著提升性能,因为它省去了到 RADOS Gateway 的 auth API 调用(是否提升取决于硬件)。做法是注释掉 nginx-default.conf 中的这一行:
#auth_request /authentication;
12.2 D3N RGW Data Cache
原理
| 项 | 说明 |
|---|---|
| 全称 | Datacenter-Data-Delivery Network (D3N) |
| 手段 | 使用 NVMe flash 或 DRAM 等高速存储,在访问侧缓存数据集 |
| 收益 | 让大数据作业可以利用每个 Rados Gateway 节点边缘上的算力与快速存储资源 |
| 背景 | 很多数据中心有低成本的集中式存储库(data lake),存 TB/PB 级数据集;Hadoop、Spark 等分布式大数据分析集群必须访问相对较远的集中式 data lake。即使数据中心网络设计良好,集群到 data lake 的带宽通常远低于边缘节点本地固态存储的带宽 |
| 效果 | D3N 通过加速对 data lake 的重复读取来提升大数据作业性能。Rados Gateways 充当后端对象存储(OSDs)的缓存服务器,在本地存数据以供复用 |
架构
- 缓存服务器位于数据中心中潜在网络与存储瓶颈的访问侧;
- D3N 的两层逻辑缓存构成一个传统的缓存层次结构(注:目前只有 layer 1 cache 被 upstream):离客户端越近的缓存访问延迟与开销越低,层次越高(需要多跳访问)的缓存越慢;
- 离客户端最近的 layer 1 缓存服务器处理对象请求时:把请求拆成 block,返回本地已缓存的 block,并把未命中的请求转发给下一层中由一致性哈希确定的 block home location;
- 缓存未命中会被转发到后续逻辑缓存层,直到最顶层的未命中通过向 data lake(Rados)发起请求来解决。
实现
- D3N 缓存同时支持 S3 与 Swift 对象存储接口;
- D3N 目前只缓存 tail 对象,因为它们是不可变的(默认是大于 4MB 的对象的各个部分);
- 对比:NGINX
RGW Data cache and CDN支持缓存所有大小的对象。
- 对比:NGINX
需求
- 一块 SSD(
/dev/nvme、/dev/pmem、/dev/shm)或类似的块存储设备,需已格式化(除 XFS 外的文件系统未做测试)并挂载,用作 cache backing store; - 根据设备性能,多个 RGW 可以共享同一块设备,但每个 RGW 在该设备文件系统上需要各自的独立目录。
限制
| 限制 | 说明 |
|---|---|
| 压缩对象 | D3N 不会缓存被 Rados Gateway Compression 压缩的对象(OSD 层压缩是支持的) |
| 加密对象 | D3N 不会缓存被 Rados Gateway Encryption 加密的对象 |
| chunk/stripe 不一致 | 若 rgw_max_chunk_size 与 rgw_obj_stripe_size 配置值不相等,D3N 会被禁用 |
默认值取自 src/common/options/*.yaml.in @ v20.2.4:
rgw_max_chunk_size= 4_M(size);rgw_obj_stripe_size= 4_M(size)。二者默认相等,因此默认不会触发该禁用条件。
配置
在每个 Rados Gateway 的 ceph.conf client 段中加入以下配置(以 [client.rgw.8000] 为例):
[client.rgw.8000]
rgw_d3n_l1_local_datacache_enabled = true
rgw_d3n_l1_datacache_persistent_path = "/mnt/nvme0/rgw_datacache/client.rgw.8000/"
rgw_d3n_l1_datacache_size = 10737418240
上例假设 cache backing-store 固态设备挂载在
/mnt/nvme0,且有10 GB可用空间用于缓存。 ⚠️ persistent path 目录必须在启动 Gateway 之前创建(mkdir -p /mnt/nvme0/rgw_datacache/client.rgw.8000/)。
容器化部署中,缓存目录应作为 volume 挂载:
extra_container_args:
- "-v"
- "/mnt/nvme0/rgw_datacache/client.rgw.8000/:/mnt/nvme0/rgw_datacache/client.rgw.8000/"
同一机器上共存多个 Gateway 时,为每个 Gateway 配置独立的 persistent path,例如 [client.rgw.8001]:
[client.rgw.8001]
rgw_d3n_l1_datacache_persistent_path = "/mnt/nvme0/rgw_datacache/client.rgw.8001/"
💡 在多 Gateway 共存配置中,建议不使用 balancer,把不同工作负载的客户端分配给各自的 Gateway,以避免缓存数据重复。 ⚠️ 每次重启 Rados Gateway,缓存目录内容会被清空(purged)。
D3N 配置项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
rgw_d3n_l1_local_datacache_enabled |
bool | false(默认值取自 src/common/options/*.yaml.in @ v20.2.4) | 启用 datacenter-scale dataset delivery local cache |
rgw_d3n_l1_datacache_persistent_path |
str | /tmp/rgw_datacache/(默认值取自 src/common/options/*.yaml.in @ v20.2.4) |
存放本地缓存对象数据的目录路径 |
rgw_d3n_l1_datacache_size |
size | 1_G(默认值取自 src/common/options/*.yaml.in @ v20.2.4) | datacache 在磁盘上的最大大小(字节) |
rgw_d3n_l1_eviction_policy |
str | lru(默认值取自 src/common/options/*.yaml.in @ v20.2.4) | 选择 d3n 缓存的驱逐策略 |
日志
radosgw.*.log中与 D3N 相关的日志行包含字符串d3n(大小写不敏感);- 底层 D3N 日志可通过
debug_rgw_datacache子系统开启(最高debug_rgw_datacache=30)。
十三、压缩与动态分片
13.1 Compression(压缩)
.. versionadded:: Kraken
| 项 | 说明 |
|---|---|
| 功能 | Ceph Object Gateway 支持对上传的对象做服务端压缩 |
| 插件 | 可使用任何已有的 compression plugin |
支持的 compression plugin:
lz4snappyzlibzstd
⚠️ Reef 版本增加了
compress-encryptedzonegroup feature,用于启用「压缩 +Server-Side Encryption」的组合(详见第七章)。
配置
在 Zone 的 placement target 上,为某个 storage class 启用压缩,方法是给 radosgw-admin zone placement modify 传入 --compression=<type>:
radosgw-admin zone placement modify \
--rgw-zone default \
--placement-id default-placement \
--storage-class STANDARD \
--compression zlib
输出片段:
{
...
"placement_pools": [
{
"key": "default-placement",
"val": {
"index_pool": "default.rgw.buckets.index",
"storage_classes": {
"STANDARD": {
"data_pool": "default.rgw.buckets.data",
"compression_type": "zlib"
}
},
"data_extra_pool": "default.rgw.buckets.non-ec",
"index_type": 0,
}
}
],
...
}
要点:
| 项 | 说明 |
|---|---|
type 含义 |
写入新对象数据时使用的 compression plugin 名 |
| 已存在对象 | 每个被压缩的对象会记住自己用的是哪个 plugin,因此修改该设置既不影响 Ceph 解压已有对象的能力,也不要求对已有对象重新压缩 |
| 作用范围 | 压缩设置适用于通过该 placement target 上传的所有新对象 |
| 禁用 | 把 type 设为空字符串或 none 即可禁用压缩 |
若此前未做 Multisite Configuration,系统会为你创建
defaultzone。
统计
radosgw-admin bucket stats --bucket=<name>
{
...
"usage": {
"rgw.main": {
"size": 1075028,
"size_actual": 1331200,
"size_utilized": 592035,
"size_kb": 1050,
"size_kb_actual": 1300,
"size_kb_utilized": 579,
"num_objects": 104
}
},
...
}
| 字段 | 说明 |
|---|---|
size / size_kb |
未压缩数据大小(其他命令与 API 报告的对象与 bucket 大小都基于未压缩数据) |
size_actual / size_kb_actual |
实际占用大小 |
size_utilized / size_kb_utilized |
压缩后数据的总大小,分别以字节与 KB 为单位 |
引自:Compression
13.2 Dynamic Bucket Index Resharding(动态分片)
.. versionadded:: Luminous
背景与原理
| 项 | 说明 |
|---|---|
| 问题 | 一个 bucket index 对象中的条目过多会导致性能问题 |
| 历史 | Luminous 之前,改变 bucket shard 数(resharding)只能离线进行,需停用 RGW 服务;Luminous 起支持在线 bucket resharding |
| 触发机制 | 每个 bucket index shard 在条目数达到一定阈值前都能高效处理;超过后系统可能出现性能问题。dynamic resharding 会自动检测这种情况并自动增加 bucket index 使用的 shard 数,从而降低每个 shard 的条目数 |
| 用户感知 | 对用户透明;resharding 期间对目标 bucket 的写入可能被短暂阻塞,但读取不受影响 |
| 上限 | 默认情况下 dynamic bucket index resharding 最多只能把 shard 数增加到 1999,该上界是可配置参数 |
| 选数策略 | 可能时,该过程会选择一个质数个 shard,以便把条目更均匀地分布到各 bucket index shard 上 |
| 执行模型 | resharding 机会的检测作为一个后台进程周期性扫描所有 bucket;需要 resharding 的 bucket 被加入队列;一个线程在后台逐个处理队列中的 resharding 任务 |
| 缩减(Tentacle 起) | 自 Tentacle 起,dynamic resharding 具备减少 shard 数的能力。一旦注意到允许缩减的条件,会先延迟一段时间再真正执行,以防近期对象数又增加——目的是避免抖动(thrashing),即对象数上下波动的 bucket 被反复 reshard |
Multisite 注意事项
⚠️ Reef 之前的 Ceph 版本,RGW 在 multisite 部署中不支持 dynamic resharding。参见
reshardingzone feature(第七章)。
配置项全表
| 配置项 | 类型 | 默认值(默认值取自 src/common/options/*.yaml.in @ v20.2.4) | 说明 |
|---|---|---|---|
rgw_dynamic_resharding |
bool | true | 启用动态 resharding。若为 true,RGW 会对「每 shard 对象数偏高」的 bucket 动态增加 shard 数 |
rgw_max_objs_per_shard |
uint | 100000 | dynamic resharding 允许的每 bucket index shard 最大对象数。RGW 会在超过该值时触发自动 reshard |
rgw_max_dynamic_shards |
uint | 1999 | dynamic resharding 自身能够创建的最大 bucket index shard 数。这不限制用户请求的(手工)shard 数 |
rgw_dynamic_resharding_may_reduce |
bool | true | dynamic resharding 是否允许减少 shard 数(当看起来 shard 过多时) |
rgw_dynamic_resharding_reduction_wait |
uint | 120 | 缩减 bucket index shard 数前的延迟小时数(为避免对象数频繁上下波动的 bucket 被反复 reshard) |
rgw_reshard_bucket_lock_duration |
uint | 360 | reshard 锁(bucket reshard lock 与 reshard log lock)的超时秒数。reshard 进行过程中这些锁可以被续期/延长。若太短,reshard 可能无法完成 |
rgw_reshard_thread_interval |
uint | 600 | 处理 reshard log 条目之间的间隔秒数 |
rgw_reshard_num_logs |
uint | 16 | (源文未给出 desc 内容) |
rgw_reshard_progress_judge_interval |
uint | 120 | 判定 bucket reshard 是否卡在 block 状态的间隔秒数 |
rgw_reshard_progress_judge_ratio |
float | 0.5 | reshard 进度判定间隔的随机浮动比例。给 rgw_reshard_progress_judge_interval 增加一个随机延迟,用于决定何时判定 reshard 进程 |
Admin 命令
| 操作 | 命令 |
|---|---|
| 把 bucket 加入 resharding 队列 | radosgw-admin reshard add --bucket <bucket_name> --num-shards <new number of shards> |
| 列出 resharding 队列 | radosgw-admin reshard list |
| 处理队列中的任务 | radosgw-admin reshard process |
| 查看 bucket resharding 状态 | radosgw-admin reshard status --bucket <bucket_name> |
| 取消待处理的 bucket resharding | radosgw-admin reshard cancel --bucket <bucket_name> |
| 手工立即 reshard | radosgw-admin bucket reshard --bucket <bucket_name> --num-shards <new number of shards> |
| 设置 bucket 的最小 shard 数 | radosgw-admin bucket set-min-shards --bucket <bucket_name> --num-shards <min number of shards> |
⚠️ 一旦 bucket resharding 任务从初始的
not-resharding状态转换为in-progress状态,就无法取消。
reshard status 输出解读
输出是一个 JSON 数组,每个 shard 一个元素,每个元素含 3 个属性:reshard_status、new_bucket_instance_id、num_shards。
1) resharding 之前:
[
{
"reshard_status": "not-resharding",
"new_bucket_instance_id": "",
"num_shards": -1
}
]
2) resharding 进行中:
[
{
"reshard_status": "in-progress",
"new_bucket_instance_id": "1179f470-2ebf-4630-8ec3-c9922da887fd.8652.1",
"num_shards": 2
},
{
"reshard_status": "in-progress",
"new_bucket_instance_id": "1179f470-2ebf-4630-8ec3-c9922da887fd.8652.1",
"num_shards": 2
}
]
3) resharding 完成之后:
[
{
"reshard_status": "not-resharding",
"new_bucket_instance_id": "",
"num_shards": -1
},
{
"reshard_status": "not-resharding",
"new_bucket_instance_id": "",
"num_shards": -1
}
]
手工 reshard 的选数建议
| 建议 | 说明 |
|---|---|
| 预估峰值 | 管理员必须预估每个 bucket 的对象数峰值。理想目标是任意时刻每个 shard 不超过 100000 条条目 |
| 使用质数 | 质数个 bucket index shard 在均匀分布条目方面更有效。例如 7001 比 7000 更好(前者是质数) |
设置 bucket 最小 shard 数
由于 dynamic resharding 现在可以减少 shard 数,管理员可能希望防止 shard 数变得过低(例如预期未来对象数会增加)。bucket set-min-shards 允许设置每 bucket 的最小值。
⚠️ 但这不能阻止管理员手工 reshard 到更低的 shard 数。
排障
Stale Instances(陈旧实例):
Luminous 12.2.11 与 Mimic 13.2.5 之前的集群会遗留陈旧的 bucket instance 条目,且不会自动清理。该问题还影响 lifecycle 策略——它们不再应用于已 reshard 的 bucket。两者都可通过
radosgw-admin命令修复。
# 列出集群中可清理的 stale instances
radosgw-admin reshard stale-instances list
# 清理集群中的 stale instances
radosgw-admin reshard stale-instances delete
⚠️ 不应在 multisite 部署中执行 stale instances 清理。
Lifecycle Fixes(生命周期修复):
对于含已 reshard 实例的集群,旧的 lifecycle 进程很可能在 reshard 期间因 bucket instance 变化而标记并删除 lifecycle 处理。虽然较新 Ceph 版本(Mimic 13.2.6 与 Luminous 12.2.12 起)部署的 bucket 已修复该问题,但更老的、有 lifecycle 策略且经历过 resharding 的 bucket 必须手工修复:
radosgw-admin lc reshard fix --bucket {bucketname}
若未提供
--bucket参数,该命令会尝试修复集群中所有 bucket 的 lifecycle 策略。
Object Expirer Fixes(对象过期修复):
旧集群上受 Swift object expiration 约束的对象,可能在 bucket 被 reshard 后从 log pool 中丢失且从未被删除。若其过期时间早于集群升级时间就会发生;若过期时间在升级之后,对象会被正确处理。radosgw-admin 提供两个子命令管理这些 expire-stale 对象:
# 列出(以 JSON 格式显示对象名与过期时间)
radosgw-admin objects expire-stale list --bucket {bucketname}
# 删除(发起删除,并以 JSON 格式显示对象名、过期时间与删除状态)
radosgw-admin objects expire-stale rm --bucket {bucketname}
十四、孤立对象(Orphans)
.. versionadded:: Luminous
14.1 产生原因
| 项 | 说明 |
|---|---|
| 定义 | Orphans(孤立对象)是指其关联的 RGW 对象被移除后遗留下来的 RADOS 对象 |
| 正常路径 | 通常这些 RADOS 对象会被自动移除——要么立即移除,要么通过称为 "garbage collection"(垃圾回收) 的过程移除 |
| 产生原因 | 在 RGW 的历史上,可能存在阻止这些 RADOS 对象被删除的 bug |
| 后果 | 这些 RADOS 对象占用 Ceph 集群空间却毫无用处 |
| 视角 | 从 RGW 的视角看,我们把这类 RADOS 对象称为 "orphans" |
14.2 已废弃的 orphans find
radosgw-admin 曾有(或曾有)三个子命令用于管理 orphans,但这些子命令已经(或即将)被废弃:
radosgw-admin orphans find ...
radosgw-admin orphans finish ...
radosgw-admin orphans list-jobs ...
废弃的两个关键原因:
| 编号 | 原因 |
|---|---|
| 1 | 这些子命令没有被积极维护,因此没有跟上 RGW 在特性与更新方面的演进。结果是,它们能准确识别真正 orphan 的可信度目前很低 |
| 2 | 这些子命令把中间结果存放在集群自身。当集群管理员面临存储空间不足、想通过删除 orphans 来解决时,这会是个问题——中间结果可能进一步加剧集群存储容量的紧张 |
基于以上原因,"orphans find" 已被废弃。
14.3 替代工具:rgw-orphan-list
| 项 | 说明 |
|---|---|
| 引入原因 | 由于 "orphans find" 被废弃,RGW 现在包含一个额外工具 rgw-orphan-list |
| 工作方式 | 运行时它会列出可用 pool,并提示用户输入 data pool 的名字。随后(可能需要较长时间)该工具会产出一个本地文件,其中包含指定 pool 中看起来是 orphan 的 RADOS 对象 |
| 后续 | 管理员可自由检查该文件,然后决定采取何种行动,例如把这些 RADOS 对象从指定 pool 中移除 |
| 存储位置 | 所有中间结果存放在本地文件系统,而非 Ceph 集群。因此运行 rgw-orphan-list 对集群存储消耗没有明显影响 |
三条警告
| 警告 | 内容 |
|---|---|
| WARNING: Experimental Status | rgw-orphan-list 工具是新的,因此目前被视为实验性(experimental)。产出的 orphan 列表在用于大规模删除操作之前应进行 "sanity check"(合理性检查) |
| WARNING: Specifying a Data Pool | 若指定的 pool 不是 RGW data pool,工具的结果将是错误的——在该 pool 上找到的所有 RADOS 对象都会被错误地判定为 orphan |
| WARNING: Unindexed Buckets | RGW 允许存在 unindexed bucket(不维护内容索引的 bucket)。这不是典型配置,但受支持。由于 rgw-orphan-list 工具使用 bucket indices来判断「应该存在哪些 RADOS 对象」,unindexed bucket 中的对象会被错误地列为 orphan |
14.4 RADOS List(子步骤)
计算 orphan 列表的一个子步骤是:把每个 RGW 对象映射到其对应的 RADOS 对象集合。这通过 radosgw-admin 的一个子命令完成:
radosgw-admin bucket radoslist [--bucket={bucket-name}]
- 该子命令会产出支撑所有 RGW 对象的 RADOS 对象列表;
- 若指定了 bucket,则只产出与该 bucket 中 RGW 对象对应的 RADOS 对象列表。
注意:共享的 Bucket Markers
| 项 | 说明 |
|---|---|
| 命名方案 | 一些管理员知道,对应 RGW 对象的 RADOS 对象命名方案中包含一个对给定 bucket 唯一的 "marker" |
| 反直觉现象 | 然而,对应某个 RGW bucket 内容的 RADOS 对象,可能包含一个指向不同 bucket 的 marker |
| 原因 | 这是 RGW "shallow copy"(浅拷贝)优化的结果:较大的对象在 bucket 之间复制时,只有 "head" 对象被真正复制,tail 对象被共享。那些被共享的对象会包含原始 bucket 的 marker |
十五、命令速查表
15.1 realm / zonegroup / zone / period
# ---- realm ----
radosgw-admin realm create --rgw-realm=<name> [--default]
radosgw-admin realm default --rgw-realm=<name>
radosgw-admin realm rm --rgw-realm=<name>
radosgw-admin realm get --rgw-realm=<name>
radosgw-admin realm set --rgw-realm=<name> --infile=<file>
radosgw-admin realm list
radosgw-admin realm list-periods
radosgw-admin realm pull --url=<url> --access-key=<ak> --secret=<sk>
radosgw-admin realm rename --rgw-realm=<old> --realm-new-name=<new>
# ---- zonegroup ----
radosgw-admin zonegroup create --rgw-zonegroup=<name> [--rgw-realm=<name>] [--master] [--default]
radosgw-admin zonegroup modify --rgw-zonegroup=<name> [...]
radosgw-admin zonegroup default --rgw-zonegroup=<name>
radosgw-admin zonegroup add --rgw-zonegroup=<name> --rgw-zone=<zone>
radosgw-admin zonegroup remove --rgw-zonegroup=<name> --rgw-zone=<zone>
radosgw-admin zonegroup rename --rgw-zonegroup=<name> --zonegroup-new-name=<new>
radosgw-admin zonegroup delete --rgw-zonegroup=<name>
radosgw-admin zonegroup list
radosgw-admin zonegroup get [--rgw-zonegroup=<name>]
radosgw-admin zonegroup set --infile zonegroup.json
radosgw-admin zonegroup-map get
radosgw-admin zonegroup-map set --infile zonegroupmap.json
radosgw-admin zonegroup-map update
# ---- zone ----
radosgw-admin zone create --rgw-zone=<name> [--zonegroup=<zg>] [--endpoints=<ep>] \
[--master] [--default] --access-key <ak> --secret <sk>
radosgw-admin zone modify [options] # --access-key/--secret/--master/--default/--endpoints
radosgw-admin zone delete --rgw-zone=<name>
radosgw-admin zone rename --rgw-zone=<name> --zone-new-name=<new>
radosgw-admin zone list
radosgw-admin zone get [--rgw-zone=<name>]
radosgw-admin zone set --rgw-zone=<name> --infile zone.json
# ---- period ----
radosgw-admin period update --commit
radosgw-admin period pull
# ---- system user ----
radosgw-admin user create --uid=<uid> --display-name="<name>" --system
15.2 同步状态与日志
radosgw-admin sync status
radosgw-admin sync info [--bucket=<bucket>] [--effective-zone-name=<zone>]
15.3 sync policy
radosgw-admin sync policy get [--bucket=<bucket>]
radosgw-admin sync group create [--bucket=<b>] --group-id=<id> --status=<enabled|allowed|forbidden>
radosgw-admin sync group modify [--bucket=<b>] --group-id=<id> --status=<enabled|allowed|forbidden>
radosgw-admin sync group get [--bucket=<b>] --group-id=<id>
radosgw-admin sync group remove [--bucket=<b>] --group-id=<id>
radosgw-admin sync group flow create [--bucket=<b>] --group-id=<id> --flow-id=<fid> \
--flow-type=directional --source-zone=<sz> --dest-zone=<dz>
radosgw-admin sync group flow create [--bucket=<b>] --group-id=<id> --flow-id=<fid> \
--flow-type=symmetrical --zones=<z1,z2>
radosgw-admin sync group flow remove [--bucket=<b>] --group-id=<id> --flow-id=<fid> \
--flow-type=directional --source-zone=<sz> --dest-zone=<dz>
radosgw-admin sync group flow remove [--bucket=<b>] --group-id=<id> --flow-id=<fid> \
--flow-type=symmetrical [--zones=<z1,z2>]
radosgw-admin sync group pipe create [--bucket=<b>] --group-id=<id> --pipe-id=<pid> \
--source-zones=<*> [--source-bucket=<*>] [--source-bucket-id=<id>] \
--dest-zones=<*> [--dest-bucket=<*>] [--dest-bucket-id=<id>] \
[--prefix=<p>] [--prefix-rm] [--tags-add=<k=v,...>] [--tags-rm=<k=v,...>] \
[--dest-owner=<o>] [--storage-class=<sc>] [--mode=<system|user>] [--uid=<u>]
radosgw-admin sync group pipe remove [--bucket=<b>] --group-id=<id> --pipe-id=<pid> [...]
15.4 sync module / tier
radosgw-admin zone create --rgw-zonegroup=<zg> --rgw-zone=<z> --endpoints=<ep> --tier-type=<archive|cloud|elasticsearch>
radosgw-admin zone modify --rgw-zone=<z> --tier-type=<t> --tier-config=<k=v>[,<k=v>]
radosgw-admin zone modify --rgw-zone=<z> --tier-config-rm=<key>
radosgw-admin zonegroup placement add --rgw-zonegroup=<zg> --placement-id=<pid> --storage-class=<sc> --tier-type=<cloud-s3|cloud-s3-glacier>
radosgw-admin zonegroup placement modify --rgw-zonegroup=<zg> --placement-id=<pid> --storage-class=<sc> --tier-config=<k=v>[,<k=v>]
radosgw-admin zonegroup placement modify --rgw-zonegroup=<zg> --placement-id=<pid> --storage-class=<sc> --tier-config-rm=<key>
radosgw-admin zonegroup placement rm --rgw-zonegroup=<zg> --placement-id=<pid> --storage-class=<sc>
15.5 zone features
radosgw-admin zone modify --rgw-zone=<z> --enable-feature=<f>
radosgw-admin zone modify --rgw-zone=<z> --disable-feature=<f>
radosgw-admin zonegroup modify --rgw-zonegroup=<zg> --enable-feature=<f>
radosgw-admin zonegroup modify --rgw-zonegroup=<zg> --disable-feature=<f>
radosgw-admin period update --commit
15.6 通知与桶日志
radosgw-admin topic list [--tenant=<t>] [--uid=<u>]
radosgw-admin topic get --topic=<name> [--tenant=<t>]
radosgw-admin topic rm --topic=<name> [--tenant=<t>]
radosgw-admin topic stats --topic=<name> [--tenant=<t>]
radosgw-admin topic dump --topic=<name> [--tenant=<t>] [--max-entries=<n>]
radosgw-admin bucket logging list --bucket <source bucket>
15.7 压缩 / reshard / orphan
radosgw-admin zone placement modify --rgw-zone <z> --placement-id <pid> --storage-class <sc> --compression <lz4|snappy|zlib|zstd|none>
radosgw-admin bucket stats --bucket=<name>
radosgw-admin reshard add --bucket <b> --num-shards <n>
radosgw-admin reshard list
radosgw-admin reshard process
radosgw-admin reshard status --bucket <b>
radosgw-admin reshard cancel --bucket <b>
radosgw-admin bucket reshard --bucket <b> --num-shards <n>
radosgw-admin bucket set-min-shards --bucket <b> --num-shards <n>
radosgw-admin reshard stale-instances list
radosgw-admin reshard stale-instances delete
radosgw-admin lc reshard fix --bucket {bucketname}
radosgw-admin objects expire-stale list --bucket {bucketname}
radosgw-admin objects expire-stale rm --bucket {bucketname}
radosgw-admin bucket radoslist [--bucket=<bucket-name>]
rgw-orphan-list
15.8 S3 Select / 云恢复
aws --endpoint-url http://localhost:8000 s3api select-object-content \
--bucket {BUCKET-NAME} --key {OBJECT-NAME} \
--expression-type 'SQL' \
--expression "select count(0) from s3object where int(_1)<10;" \
--input-serialization '{"CSV": {"FieldDelimiter": ",", "RecordDelimiter": "\n", "FileHeaderInfo": "USE"}, "CompressionType": "NONE"}' \
--output-serialization '{"CSV": {"FieldDelimiter": ":", "RecordDelimiter":"\t", "QuoteFields": "ALWAYS"}}' \
--request-progress '{"Enabled": true}' output.csv
aws s3api restore-object --bucket bucket1 --key doc1.rtf --restore-request Days=10
aws s3api restore-object --bucket bucket1 --key doc2.rtf --restore-request {}
aws s3api get-object --bucket bucket1 --key doc3.rtf
aws s3api head-object --bucket bucket1 --key doc1.rtf
radosgw-admin object stat --bucket bucket1 --object doc1.rtf
文档未明确项
以下条目在 v20.2.4 官方源文中未给出明确信息,本册不做臆测:
| # | 项目 | 说明 |
|---|---|---|
| 1 | rgw_default_zone_group_info_oid 的真实默认值 |
multisite.rst 表格写 default.zonegroup,但在 rgw.yaml.in 中未检索到同名条目 |
| 2 | multipart_sync_threshold / multipart_min_part_size 的默认值 |
cloud-sync-module 与 cloud-transition 两处都只给占位符;cloud-transition.rst 的示例输出里出现过 33554432,但源文未声明其为默认值 |
| 3 | S3 Select 的完整错误码表 | 源文只说明「400 Bad Request + 语法错误 / 运行时错误」两类,并给了 NoSuchKey 的 XML 示例 |
| 4 | S3 Select 支持的数据类型清单 | 源文未单独列出;本册从 cast / 函数描述反推出 int / float / string / bool / timestamp / NULL |
| 5 | S3 Select 的 Parquet 查询语法与限制 | 只提到存在 Parquet reader/parser,相关段落标注 TODO |
| 6 | S3 Select LIMIT 的示例 |
源文标注 TODO : add examples, for aggregation and non-aggregation queries. |
| 7 | JSON 查询中 object 与 array 值相关示例 | 源文标注 TODO : relevant example for object and array values. |
| 8 | Scan Range 在 CSV / JSON / Parquet 不同数据源下的行为 | 源文标注 TODO : different data-sources (CSV, JSON, Parquet) |
| 9 | mdlog / bilog / datalog 的 list / trim 具体命令语法 |
源文只说「可以 list 也可以 trim」 |
| 10 | rgw_reshard_num_logs 的描述 |
源文 confval 对应条目在 rgw.yaml.in 中 desc 为空 |
| 11 | sync policy group 三种状态的显式优先级表 | 源文未给出;本册表格依据 forbidden 的 "can override other groups" 描述推导 |
| 12 | ElasticSearch sync module 各 tier-config 项的默认值 | 除 explicit_custom_meta(false)与 host_style(path)外,其余未说明 |
| 13 | rgw_zone / rgw_zonegroup 的默认值 |
rgw.yaml.in 中为 str 类型且无 default: 字段;源文表格写 None;可通过 radosgw-admin zone default / zonegroup default 设置集群级默认 |
| 14 | 桶日志 REST API 的具体配置语法 | 源文在 "Bucket Logging REST API" 一节指向 Bucket Operations 页面(不在本册范围内) |
| 15 | Notification 的 REST API 具体配置语法 | 源文在 "Notifications" 一节指向 Bucket Operations 页面(不在本册范围内) |
| 16 | rgw-orphan-list 的命令行参数与输出文件格式 |
源文只描述其为交互式工具,未给参数 |
| 17 | D3N rgw_d3n_l1_eviction_policy 除 lru 外的可选值 |
源文未列出 |
| 18 | cloud sync module connection.host_style 之外的 connection_id 生成规则 |
源文未说明 |