1. 对象存储网关到底解决什么问题
Ceph 这套分布式存储系统,很多团队最早接触的是它的块存储(RBD)和文件存储(CephFS),但真正让它在业务侧大规模落地的,往往是对象存储网关 RGW(RADOS Gateway)。我所在的团队当初选它,核心原因就一个:业务方需要一套兼容 S3 协议、能横向扩展、又不绑定某一家云厂商的对象存储服务,而 RGW 恰好把 Ceph 底层的 RADOS 集群包装成了一层标准的 HTTP 对象接口。
说白了,RGW 就是架在 RADOS 之上的一层协议翻译官。RADOS 本身是个扁平的键值对象存储,没有目录、没有桶、没有 HTTP 接口,业务根本没法直接用。RGW 做的事情,是把 S3 或 Swift 的语义(桶、对象、ACL、生命周期、分片上传)翻译成 RADOS 的读写操作,再通过 CivetWeb 或 Beast 这类 HTTP 前端暴露出去。对业务来说,它看到的就是一个标准的 S3 端点;对运维来说,底层还是那套熟悉的 Ceph 集群。
这篇文章适合三类人看:一是刚接手 Ceph 集群、需要把 RGW 跑起来的运维;二是要在业务代码里对接 S3 接口、想搞清楚 RGW 行为边界的开发;三是正在评估对象存储方案、想了解 RGW 实际能力和坑点的架构同学。我会从部署形态、核心概念、实操配置、性能调优到问题排查,把 RGW 这套东西讲透,尽量给到可以直接抄作业的命令和参数。
需要先明确一点:RGW 不是简单装个包就能用的东西,它的部署形态、网关数量、负载均衡方式、存储池规划,每一步都会影响后面的稳定性和性能。我见过太多团队上来就ceph-deploy rgw create一把梭,结果上线后遇到大对象上传超时、桶索引分片不均、网关单点瓶颈等问题。所以下面我会把每个决策背后的逻辑讲清楚,而不是只给命令。
2. RGW 部署形态与核心概念拆解
2.1 三种典型部署形态怎么选
RGW 的部署方式直接决定了它的可用性和运维复杂度。根据我实际接触过的场景,大致可以归为三类。
第一种是单网关裸跑,就是在一台节点上起一个 radosgw 进程,前端用 CivetWeb 监听 7480 端口。这种形态只适合测试和 POC,因为网关本身是单点,一旦进程挂掉或者这台机器宕机,整个对象存储服务就断了。我早期在实验室环境就是这么干的,图省事,但绝对不建议带到生产。
第二种是多网关加外部负载均衡,这是目前生产环境最主流的做法。在多台节点上各起一个或多个 radosgw 实例,前面挂一层负载均衡(可以是硬件 LB,也可以是 Nginx、HAProxy 这类软件方案),把请求分发到各个网关。这种形态的好处是网关层可以水平扩展,单台挂了 LB 会自动摘除,业务无感知。关键点在于负载均衡要配置健康检查,探测 RGW 的/swift/healthcheck或者直接探测 7480 端口。
第三种是容器化部署,用 Rook 或者 cephadm 把 RGW 跑在容器里,配合 Kubernetes 的 Service 和 Ingress 做暴露。这种形态适合已经有 K8s 体系的团队,扩缩容和滚动升级都很方便。但要注意容器网络和存储网络的规划,别让对象数据的流量把业务网络打满。
选哪种形态,核心看两个维度:一是业务对可用性的要求,二是团队现有的运维体系。如果业务是核心链路,必须多网关加 LB;如果团队已经在 K8s 上跑一切,那就容器化;如果只是内部测试,单网关也不是不行,但心里要清楚它的边界。
2.2 桶、对象、用户、租户这几个概念别搞混
RGW 的概念体系里,最容易让新手绕晕的就是用户和租户的关系。我用一个类比来解释:把 RGW 想象成一栋写字楼,租户(tenant)就是租下整层楼的公司,用户(user)是公司里的员工,桶(bucket)是员工用的文件柜,对象(object)就是文件柜里的文件。
一个用户必须属于某个租户,默认情况下所有用户都属于一个空字符串表示的空租户。桶名在 RGW 全局是唯一的,但加上租户前缀后,不同租户可以有同名桶。这个设计在多团队共享一套 Ceph 集群时特别有用,每个团队一个租户,桶名互不干扰。
用户身上挂着几样关键东西:access key和secret key是 S3 认证用的凭证;uid是用户在 RGW 内的唯一标识;display name是展示名;还有caps表示这个用户拥有的权限,比如buckets=read表示只能读桶。创建用户的时候这些都要规划好,尤其是 access key 和 secret key,业务方拿到之后是要写进配置文件的,泄露了就等于桶被公开了。
桶这边有几个属性值得注意:索引分片数(index pool shards)决定了桶索引的并发能力,默认是 0 表示自动,但大桶一定要手动指定;存储类别(storage class)可以配合生命周期做冷热分层;版本控制和对象锁是合规场景常用的功能。这些属性在创建桶的时候就要想清楚,后面改起来很麻烦。
2.3 存储池规划:别让默认池背锅
RGW 默认会创建一堆存储池,比如.rgw.root、default.rgw.control、default.rgw.meta、default.rgw.log、default.rgw.buckets.index、default.rgw.buckets.data。这些池如果都用默认的 pg 数和副本数,在小集群上可能没问题,但在大规模集群上就是灾难。
我的经验是,元数据池和数据池要分开规划。.rgw.root、meta、log、control这些属于元数据池,数据量小但访问频繁,可以用 SSD 做后端,pg 数不用太多;buckets.index是桶索引池,随着对象数量增长会变得很大,pg 数要按预期对象数来算;buckets.data是真正存对象数据的池,数据量最大,通常用大容量 HDD,pg 数要按容量和 OSD 数量来算。
pg 数的计算有个常用公式:(OSD 总数 × 100) / 副本数,然后向上取到 2 的幂。比如 30 个 OSD、3 副本,就是30 × 100 / 3 = 1000,取 1024。但这只是起点,实际还要看单个池的预期数据量和访问模式。桶索引池因为要支持高并发列举,pg 数可以适当多给一些。
还有一个坑是RGW 的池前缀。默认前缀是default,如果你要在一套集群上跑多套 RGW(比如生产一套、测试一套),一定要用不同的前缀,否则元数据会混在一起。创建网关的时候用--rgw-realm、--rgw-zonegroup、--rgw-zone这套多站点概念来隔离,别偷懒。
3. 从零把 RGW 跑起来的完整实操
3.1 前置检查:集群健康是底线
在动 RGW 之前,先确认 Ceph 集群本身是健康的。这一步很多人会跳过,结果 RGW 起不来还以为是网关的问题,其实是底层集群在报错。
ceph -s ceph osd tree ceph dfceph -s要看到HEALTH_OK,至少也得是HEALTH_WARN且警告项和 RGW 无关。ceph osd tree确认所有 OSD 都是 up 和 in 状态。ceph df看一下容量水位,别在集群快满的时候去加 RGW,否则对象写不进去会报一堆 500。
另外要确认时间同步。RGW 的 S3 签名认证对时间敏感,客户端和网关的时间差超过 15 分钟,请求就会被拒。生产环境务必配好 NTP 或 chrony,这是血泪教训,我曾经因为一台网关时间漂移导致业务方间歇性认证失败,排查了大半天。
3.2 创建网关实例的两种方式
用 cephadm 管理的集群,创建 RGW 很简单:
ceph orch apply rgw myrgw --placement="3 node1 node2 node3" --port=7480这条命令会在三台节点上各起一个 RGW 实例,监听 7480 端口。--placement后面的数字表示实例数,后面跟节点名。cephadm 会自动处理配置下发和进程管理,升级也很方便。
如果是传统方式或者手动部署,可以用:
ceph auth get-or-create client.rgw.node1 osd 'allow rwx' mon 'allow rw' -o /etc/ceph/ceph.client.rgw.node1.keyring radosgw -n client.rgw.node1 --rgw-frontends="civetweb port=7480"这种方式需要自己管理进程和开机自启,适合对系统有完全控制权的场景。不管哪种方式,创建完之后用ceph orch ps或者ps aux | grep radosgw确认进程起来了,再用curl http://node1:7480看是否返回 XML 格式的欢迎信息。
3.3 创建第一个用户和桶
网关起来之后,第一件事是创建用户:
radosgw-admin user create --uid="testuser" --display-name="Test User" --access-key="AKIAEXAMPLE" --secret-key="secretexample"如果不指定 access key 和 secret key,RGW 会自动生成,输出里会包含这两个值,务必保存好。创建完可以用radosgw-admin user info --uid=testuser查看详情。
然后就可以用 S3 客户端操作了。我习惯用awscli配合自定义端点来测试:
aws --endpoint-url http://node1:7480 s3 mb s3://mybucket aws --endpoint-url http://node1:7480 s3 cp testfile.txt s3://mybucket/ aws --endpoint-url http://node1:7480 s3 ls s3://mybucket/这里要注意,awscli默认会走 HTTPS,测试环境用 HTTP 的话要确保 endpoint 写的是http://。另外awscli的 region 配置随便填一个就行,RGW 不校验 region,但客户端要求必须有。
创建桶的时候如果预期对象数量很大,建议手动指定索引分片:
aws --endpoint-url http://node1:7480 s3api create-bucket --bucket bigbucket --create-bucket-configuration LocationConstraint=default不过awscli不直接支持指定分片数,需要用radosgw-admin:
radosgw-admin bucket limit check --uid=testuser radosgw-admin bucket reshard --bucket=bigbucket --num-shards=64分片数一旦设定,后续调整需要 reshard,这个过程会消耗集群资源,所以最好在桶还是空的时候就规划好。经验值是:预期对象数在百万级,给 16 到 64 个分片;千万级以上,给 128 到 256 个分片。分片太多也会增加元数据开销,不是越多越好。
3.4 负载均衡配置要点
多网关部署时,前面的负载均衡配置有几个关键点。以 Nginx 为例:
upstream rgw_backend { server node1:7480; server node2:7480; server node3:7480; } server { listen 80; location / { proxy_pass http://rgw_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; client_max_body_size 0; proxy_request_buffering off; } }client_max_body_size 0表示不限制上传大小,proxy_request_buffering off让请求直接透传,避免大对象上传时 Nginx 先缓冲到本地磁盘。这两个配置对对象存储场景很重要,少了任何一个都可能遇到大文件上传失败。
健康检查建议用 RGW 自带的/swift/healthcheck接口,返回 200 表示健康。如果不想暴露 Swift 接口,也可以探测 7480 端口的 TCP 连通性,但不如 HTTP 检查准确。
4. 性能调优与常见问题排查
4.1 大对象上传的分片策略
RGW 默认的单对象大小限制是 5GB(老版本)或更大,但实际业务中经常要传几十 GB 甚至 TB 级的文件。这时候必须用分片上传(multipart upload)。S3 协议的分片上传要求每个分片至少 5MB(最后一片除外),最多 10000 片。
分片大小的选择有个权衡:分片太小,请求次数多,元数据开销大;分片太大,单个分片失败重传成本高。我的经验是,对于 10GB 以内的文件,用 64MB 到 128MB 的分片;对于更大的文件,用 256MB 到 512MB。这样既能控制重传成本,又不会让分片数超过上限。
RGW 侧有个参数rgw_max_put_size控制单次 PUT 的最大大小,默认是 5GB。如果要支持更大的单次上传,需要调整这个值,但更推荐用分片上传而不是调大单次限制,因为单次大 PUT 失败后要全部重传。
还有一个参数rgw_max_chunk_size控制 RGW 内部读写 RADOS 的块大小,默认 4MB。对于大对象顺序读写,适当调大这个值(比如 16MB)可以减少 RADOS 操作次数,提升吞吐。但调太大也会增加内存占用,要结合网关节点的内存来定。
4.2 桶索引分片不均怎么办
桶索引分片不均是个很常见的问题,表现是某些分片对应的 OSD 负载明显偏高,列举操作变慢。原因通常是桶创建时用了默认分片数,随着对象增长,哈希分布不均匀导致。
排查方法:
radosgw-admin bucket stats --bucket=mybucket输出里会显示num_shards和每个分片的对象数。如果发现某些分片对象数远超平均值,就需要 reshard。
radosgw-admin bucket reshard --bucket=mybucket --num-shards=128reshard 的过程是:RGW 创建一个新的索引池,把旧索引的数据迁移过去,然后切换。这个过程对业务是透明的,但会消耗集群 IO,建议在低峰期做。reshard 完成后,旧的索引池不会自动删除,需要手动清理,否则会一直占着空间。
预防胜于治疗,最好的办法是创建桶时就指定合适的分片数。如果业务方是通过 S3 API 创建桶的,可以在 RGW 侧配置默认分片数:
ceph config set client.rgw rgw_override_bucket_index_max_shards 64这样所有新建桶默认就是 64 个分片,省得后面 reshard。
4.3 常见错误码与排查思路
RGW 返回的错误码基本遵循 S3 规范,但有些是 Ceph 特有的。我整理了一个速查表:
| 错误码 | 常见原因 | 排查方向 |
|---|---|---|
| 403 Forbidden | 签名错误、时间偏差、权限不足 | 检查 access key、secret key、系统时间、用户 caps |
| 404 NoSuchBucket | 桶不存在或桶名拼写错误 | 确认桶名、租户前缀是否正确 |
| 409 BucketAlreadyExists | 桶名全局冲突 | 换桶名或加租户前缀 |
| 500 InternalError | 底层 RADOS 异常、池满、OSD 故障 | 查ceph -s、ceph health detail、RGW 日志 |
| 503 ServiceUnavailable | 网关过载、后端不可用 | 查网关负载、LB 健康检查、RADOS 状态 |
| RequestTimeTooSkewed | 客户端与网关时间差过大 | 同步 NTP,检查时区配置 |
排查 RGW 问题,日志是第一手资料。RGW 的日志默认在/var/log/ceph/下,文件名类似ceph-client.rgw.node1.log。把日志级别调高可以看到更详细的请求处理过程:
ceph config set client.rgw debug_rgw 20 ceph config set client.rgw debug_civetweb 10调完之后记得改回来,高日志级别对性能有影响。我一般只在排查特定问题时临时开,问题定位了就恢复默认。
4.4 实操心得与避坑清单
最后分享几条我在实际运维中踩过的坑和总结的经验。
第一条,别在业务高峰期做 reshard 和 GC。RGW 的垃圾回收(GC)是后台异步做的,删除大对象后空间不会立即释放,而是等 GC 处理。如果 GC 积压,会占用大量 IO。可以用radosgw-admin gc list查看待回收对象,用radosgw-admin gc process手动触发。高峰期做这些操作会拖慢业务请求。
第二条,网关节点的内存要给够。RGW 处理大对象时会占用较多内存,尤其是分片上传的并发场景。我建议每个网关实例至少给 4GB 到 8GB 内存,具体看并发量和对象大小。内存不足会导致 OOM,进程被杀,业务直接报 500。
第三条,监控要覆盖网关层。除了 Ceph 集群本身的监控,RGW 的请求数、延迟、错误率、各状态码分布都要监控。这些指标可以通过 RGW 的 admin API 或者 Prometheus exporter 采集。没有监控的话,出了问题只能靠猜,效率极低。
第四条,access key 和 secret key 要定期轮换。RGW 支持一个用户有多个 key,轮换的时候先加新 key,业务切换后再删旧 key,做到无缝。别等到泄露了才想起来换,那时候数据可能已经被拖走了。
第五条,跨租户访问要显式授权。默认情况下不同租户之间是完全隔离的,如果业务需要跨租户访问桶,要通过 bucket policy 或者 ACL 显式授权,别指望默认能通。这个设计是安全的,但新手容易在这里卡住。
第六条,测试环境一定要模拟生产的数据量和并发。我见过太多在测试环境跑得好好的配置,一到生产就崩,因为测试环境只有几百个对象,生产是几千万个,桶索引的行为完全不同。有条件的话,用工具灌一批接近生产规模的数据再压测。
这套 RGW 的东西,从部署到调优到排查,核心就一句话:把它当成一个需要精心规划的有状态服务来对待,而不是一个装完就忘的组件。网关数量、存储池、分片数、内存、监控,每一项都要提前想清楚。我个人的体会是,前期多花一天做规划,后期能省一周的排查时间。