Files
wiki/存储/Ceph/知识库/11-RGW-多站点与数据服务篇.md
T
2026-09-18 13:49:11 +08:00

194 KiB
Raw Blame History

第 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) 通常由两样东西构成:

  1. 一个 zonegroup,其中包含一个 zone;
  2. 一个或多个 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 消费) 文档未明确

同步的两条主线:

  1. metadata sync(元数据同步):secondary zone 从 master zone 拉取 mdlog,把用户、bucket 等元数据补齐。输出形如 full sync: 0/64 shards + incremental sync: 64/64 shards。
  2. 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(仅限全新集群)

⚠️ 如果 default zone 已在存数据,千万不要删除,否则数据将不可恢复地丢失。

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)

  1. 让 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
    
  2. 更新 period 使变更生效:

    radosgw-admin period update --commit
    
  3. 重启 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

⚠️ default zonegroup 的 is_master 默认为 true。若要新建 zonegroup 并使其成为 master,必须先把 default zonegroup 的 is_master 设为 false,或删除 default zonegroup。

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

引自:Multisite Sync Policy


五、同步模块(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

引自:ElasticSearch Sync Module

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}。

引自:Cloud Sync Module

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

引自:Archive Sync Module

引自(本节其余):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,系统会为你创建 default zone 与 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)。

引自:Cloud Transition

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。为此:

  1. storage class 必须以 --tier-type=cloud-s3-glacier 配置;
  2. 相应设置 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

引自:Cloud Restore


七、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 都需要重启才能感知变更。

引自:Zone Features


八、事件通知(Bucket Notifications)

.. versionadded:: Nautilus .. versionchanged:: Squid:可通过 notification_v2 zone 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:

  • PLAIN
  • SCRAM-SHA-256
  • SCRAM-SHA-512
  • GSSAPI
  • OAUTHBEARER

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 兼容的 GetTopicAttributes API。

响应:

<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:Post notification。

事件为 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 的扩展)

引自:Bucket Notifications


九、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:CompleteMultipartUpload notification;
  • s3:ObjectSynced:Create 事件在对象成功同步到某个 zone 时发送,必须为每个 zone 显式设置。

9.6 Topic 配置兼容性

Ceph 的 topic 管理 API 派生自 AWS Simple Notification Service API。SNS API 的大部分对 Ceph 不适用,只实现了以下 action:

  • CreateTopic
  • DeleteTopic
  • ListTopics

Ceph 对 topic 配置的扩展:

扩展点 说明
GetTopic 允许获取特定 topic,而非该用户的所有 topic
CreateTopic (1) 允许设置 endpoint attributes;(2) 允许设置 opaque data,它会被随通知发送到 endpoint

引自:S3 Bucket Notifications Compatibility


十、桶日志(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:PutObject action;
  • 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 - -

引自:Bucket Logging


十一、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 之前,不应假定请求已完成

引自:Ceph s3 select


十二、缓存与性能

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-cache caps 的用户的 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;

引自:RGW Data Caching and CDN

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 支持缓存所有大小的对象。

需求

  • 一块 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)。

引自:D3N RGW Data Cache


十三、压缩与动态分片

13.1 Compression(压缩)

.. versionadded:: Kraken

项 说明
功能 Ceph Object Gateway 支持对上传的对象做服务端压缩
插件 可使用任何已有的 compression plugin

支持的 compression plugin:

  • lz4
  • snappy
  • zlib
  • zstd

⚠️ Reef 版本增加了 compress-encrypted zonegroup 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,系统会为你创建 default zone。

统计

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。参见 resharding zone 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}

引自:RGW Dynamic Bucket Index Resharding


十四、孤立对象(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

引自:Orphan List and Associated Tooling


十五、命令速查表

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 生成规则 源文未说明