对象存储灾备

Ceph RGW Multi-Site 功能是一种跨集群异步数据复制机制,旨在同步地理分布的 Ceph 集群之间的对象存储数据,提供高可用(HA)和灾难恢复(DR)能力。

术语

术语说明
Primary Cluster当前提供存储服务的集群。
Secondary Cluster用于备份的备用集群。
Realm, ZoneGroup, Zone
  • Realm:Ceph 对象存储中最高级别的逻辑分组,代表一个完整的对象存储命名空间,通常用于多站点复制和同步。一个 Realm 可以跨越不同地理位置或数据中心。
  • ZoneGroup:Realm 内的逻辑分组,包含多个 Zone。ZoneGroup 实现跨 Zone 的数据同步和复制,通常位于同一地理区域内。
  • Zone:ZoneGroup 内的逻辑分组,负责物理存储数据。每个 Zone 独立管理和存储对象,并可拥有自己的数据/元数据池配置。

前提条件

  • 准备两个可部署 Rook-Ceph 的集群(Primary 和 Secondary),并确保它们之间网络连通。
  • 两个集群必须使用相同的平台版本(v3.12 或更高)。
  • 确保 Primary 和 Secondary 集群上均未部署 Ceph 对象存储。
  • 参考创建存储服务文档部署 Operator 并创建集群。集群创建完成后,不要通过向导创建对象存储池,而是按照下述说明使用 CLI 工具进行配置。

架构

操作流程

本指南提供同一 ZoneGroup 内两个 Zone 之间的同步方案。

在 Primary 集群创建对象存储

本步骤创建 Realm、ZoneGroup、Primary Zone 及 Primary Zone 的网关资源。

在 Primary 集群的 Control 节点执行以下命令:

  1. 设置参数

    export REALM_NAME=<realm-name>
    export ZONE_GROUP_NAME=<zonegroup-name>
    export PRIMARY_ZONE_NAME=<primary-zone-name>
    export PRIMARY_OBJECT_STORE_NAME=<primary-object-store-name>

    参数说明

    • <realm-name>:Realm 名称。
    • <zonegroup-name>:ZoneGroup 名称。
    • <primary-zone-name>:Primary Zone 名称。
    • <primary-object-store-name>:网关名称。
  2. 创建对象存储

    命令
    输出
    cat << EOF | kubectl apply -f -
    ---
    apiVersion: ceph.rook.io/v1
    kind: CephObjectRealm
    metadata:
      name: $REALM_NAME
      namespace: rook-ceph
    
    ---
    apiVersion: ceph.rook.io/v1
    kind: CephObjectZoneGroup
    metadata:
      name: $ZONE_GROUP_NAME
      namespace: rook-ceph
    spec:
      realm: $REALM_NAME
    
    ---
    apiVersion: ceph.rook.io/v1
    kind: CephObjectZone
    metadata:
      name: $PRIMARY_ZONE_NAME
      namespace: rook-ceph
    spec:
      zoneGroup: $ZONE_GROUP_NAME
      metadataPool:
        failureDomain: host
        replicated:
          size: 3
          requireSafeReplicaSize: true
      dataPool:
        failureDomain: host
        replicated:
          size: 3
          requireSafeReplicaSize: true
        parameters:
          compression_mode: none
      preservePoolsOnDelete: false
    
    ---
    apiVersion: ceph.rook.io/v1
    kind: CephBlockPool
    metadata:
      labels:
        cpaas.io/builtin: "true"
      name: builtin-rgw-root
      namespace: rook-ceph
    spec:
      name: .rgw.root
      application: rgw
      enableCrushUpdates: true
      failureDomain: host
      replicated:
        size: 3
      parameters:
        pg_num: "8"
    
    ---
    apiVersion: ceph.rook.io/v1
    kind: CephObjectStore
    metadata:
      name: $PRIMARY_OBJECT_STORE_NAME
      namespace: rook-ceph
    spec:
      gateway:
        port: 7480
        instances: 2
      zone:
        name: $PRIMARY_ZONE_NAME
    EOF

配置 Primary Zone 的外部访问

  1. 获取 ObjectStore 的 UID

    export PRIMARY_OBJECT_STORE_UID=$(kubectl -n rook-ceph get cephobjectstore $PRIMARY_OBJECT_STORE_NAME -o jsonpath='{.metadata.uid}')
  2. 创建外部访问 Service

    cat << EOF | kubectl apply -f -
    apiVersion: v1
    kind: Service
    metadata:
      name: rook-ceph-rgw-$PRIMARY_OBJECT_STORE_NAME-external
      namespace: rook-ceph
      labels:
        app: rook-ceph-rgw
        rook_cluster: rook-ceph
        rook_object_store: $PRIMARY_OBJECT_STORE_NAME
      ownerReferences:
        - apiVersion: ceph.rook.io/v1
          kind: CephObjectStore
          name: $PRIMARY_OBJECT_STORE_NAME
          uid: $PRIMARY_OBJECT_STORE_UID
    spec:
      ports:
        - name: rgw
          port: 7480
          targetPort: 7480
          protocol: TCP
      selector:
        app: rook-ceph-rgw
        rook_cluster: rook-ceph
        rook_object_store: $PRIMARY_OBJECT_STORE_NAME
      sessionAffinity: None
      type: NodePort
    EOF
  3. 将外部端点添加到 CephObjectZone。

    IP=$(kubectl get nodes -l 'node-role.kubernetes.io/control-plane' -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}' | cut -d ' ' -f1 | tr -d '\n')
    PORT=$(kubectl -n rook-ceph get svc rook-ceph-rgw-$PRIMARY_OBJECT_STORE_NAME-external -o jsonpath='{.spec.ports[0].nodePort}')
    ENDPOINT=http://$IP:$PORT
    kubectl -n rook-ceph patch cephobjectzone $PRIMARY_ZONE_NAME --type merge -p "{\"spec\":{\"customEndpoints\":[\"$ENDPOINT\"]}}"

获取 access-keysecret-key

kubectl -n rook-ceph get secrets $REALM_NAME-keys -o jsonpath='{.data.access-key}'
kubectl -n rook-ceph get secrets $REALM_NAME-keys -o jsonpath='{.data.secret-key}'

创建 Secondary Zone 并配置 Realm 同步

本节说明如何创建 Secondary Zone 并通过从 Primary 集群拉取 Realm 信息配置同步。

在 Secondary 集群的 Control 节点执行以下命令:

  1. 设置参数

    export REALM_NAME=<realm-name>
    export ZONE_GROUP_NAME=<zonegroup-name>
    
    export REALM_ENDPOINT=<realm-endpoint>
    export ACCESS_KEY=<access-key>
    export SECRET_KEY=<secret-key>
    
    export SECONDARY_ZONE_NAME=<secondary-zone-name>
    export SECONDARY_OBJECT_STORE_NAME=<secondary-object-store-name>

    参数说明

    • <realm-name>Realm 名称
    • <zone-group-name>ZoneGroup 名称
    • <realm-endpoint>:从 Primary 集群获取的外部地址
    • <access-key>:从此处获取的 AK。
    • <secret-key>:从此处获取的 SK。
    • <secondary-zone-name>:Secondary Zone 名称。
    • <secondary-object-store-name>:Secondary 网关名称。
  2. 创建 Secondary Zone 并配置 Realm 同步

    cat << EOF | kubectl apply -f -
    apiVersion: v1
    kind: Secret
    metadata:
      name: $REALM_NAME-keys
      namespace: rook-ceph
    data:
      access-key: $ACCESS_KEY
      secret-key: $SECRET_KEY
    
    ---
    apiVersion: ceph.rook.io/v1
    kind: CephObjectRealm
    metadata:
      name: $REALM_NAME
      namespace: rook-ceph
    spec:
      pull:
        endpoint: $REALM_ENDPOINT
    
    ---
    apiVersion: ceph.rook.io/v1
    kind: CephObjectZoneGroup
    metadata:
      name: $ZONE_GROUP_NAME
      namespace: rook-ceph
    spec:
      realm: $REALM_NAME
    
    ---
    apiVersion: ceph.rook.io/v1
    kind: CephObjectZone
    metadata:
      name: $SECONDARY_ZONE_NAME
      namespace: rook-ceph
    spec:
      zoneGroup: $ZONE_GROUP_NAME
      metadataPool:
        failureDomain: host
        replicated:
          size: 3
          requireSafeReplicaSize: true
      dataPool:
        failureDomain: host
        replicated:
          size: 3
          requireSafeReplicaSize: true
      preservePoolsOnDelete: false
    
    ---
    apiVersion: ceph.rook.io/v1
    kind: CephBlockPool
    metadata:
      labels:
        cpaas.io/builtin: "true"
      name: builtin-rgw-root
      namespace: rook-ceph
    spec:
      name: .rgw.root
      application: rgw
      enableCrushUpdates: true
      failureDomain: host
      replicated:
        size: 3
      parameters:
        pg_num: "8"
        
    ---
    apiVersion: ceph.rook.io/v1
    kind: CephObjectStore
    metadata:
      name: $SECONDARY_OBJECT_STORE_NAME
      namespace: rook-ceph
    spec:
      gateway:
        port: 7480
        instances: 2
      zone:
        name: $SECONDARY_ZONE_NAME
    EOF

配置 Secondary Zone 的外部访问

  1. 获取 Secondary 网关的 UID

    export SECONDARY_OBJECT_STORE_UID=$(kubectl -n rook-ceph get cephobjectstore $SECONDARY_OBJECT_STORE_NAME -o jsonpath='{.metadata.uid}')
  2. 创建外部访问 Service

    cat << EOF | kubectl apply -f -
    apiVersion: v1
    kind: Service
    metadata:
      name: rook-ceph-rgw-$SECONDARY_OBJECT_STORE_NAME-external
      namespace: rook-ceph
      labels:
        app: rook-ceph-rgw
        rook_cluster: rook-ceph
        rook_object_store: $SECONDARY_OBJECT_STORE_NAME
      ownerReferences:
        - apiVersion: ceph.rook.io/v1
          kind: CephObjectStore
          name: $SECONDARY_OBJECT_STORE_NAME
          uid: $SECONDARY_OBJECT_STORE_UID
    spec:
      ports:
        - name: rgw
          port: 7480
          targetPort: 7480
          protocol: TCP
      selector:
        app: rook-ceph-rgw
        rook_cluster: rook-ceph
        rook_object_store: $SECONDARY_OBJECT_STORE_NAME
      sessionAffinity: None
      type: NodePort
    EOF
  3. 将外部端点添加到 Secondary CephObjectZone

    IP=$(kubectl get nodes -l 'node-role.kubernetes.io/control-plane' -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}' | cut -d ' ' -f1 | tr -d '\n')
    PORT=$(kubectl -n rook-ceph get svc rook-ceph-rgw-$SECONDARY_OBJECT_STORE_NAME-external -o jsonpath='{.spec.ports[0].nodePort}')
    ENDPOINT=http://$IP:$PORT
    kubectl -n rook-ceph patch cephobjectzone $SECONDARY_ZONE_NAME --type merge -p "{\"spec\":{\"customEndpoints\":[\"$ENDPOINT\"]}}"

检查 Ceph 对象存储同步状态

在 Secondary 集群的 rook-ceph-tools pod 中执行以下命令

# 进入 rook-ceph-tools pod
kubectl -n rook-ceph exec -it $(kubectl -n rook-ceph get po -l app=rook-ceph-tools -o jsonpath='{range .items[*]}{@.metadata.name}') -- bash

radosgw-admin sync status

输出示例

          realm 962d6b80-b218-4fc8-8198-e498fabc4e9d (relam-primary)
      zonegroup 9de3acd7-0b01-4a04-ac84-1421c6103a16 (zonegroup-primary)
           zone 7b3ce7f5-7090-46f6-afb1-d1bb156053da (zone-secondary)
   current time 2025-12-04T06:27:15Z
zonegroup features enabled: resharding
                   disabled: compress-encrypted
  metadata sync syncing
                full sync: 0/64 shards
                incremental sync: 64/64 shards
                metadata is caught up with master
      data sync source: 6319ca70-964e-47be-8b96-7c5bf5a128b1 (zone-primary)
                        syncing
                        full sync: 0/128 shards
                        incremental sync: 128/128 shards
                        data is caught up with source

metadata is caught up with masterdata is caught up with source 表示同步状态正常。

故障切换

当 Primary 集群发生故障时,需要将 Secondary Zone 提升为 Primary Zone。切换后,Secondary Zone 的网关可以继续提供对象存储服务。

操作流程

在 Secondary 集群的 rook-ceph-tools pod 中执行以下命令

# 进入 rook-ceph-tools pod
kubectl -n rook-ceph exec -it $(kubectl -n rook-ceph get po -l app=rook-ceph-tools -o jsonpath='{range .items[*]}{@.metadata.name}') -- bash

# 将恢复的 zone 设置为主 zone 和默认 zone
radosgw-admin zone modify --rgw-realm=<realm-name> --rgw-zonegroup=<zone-group-name> --rgw-zone=<secondary-zone-name> --master

# 更新 period 使更改生效
radosgw-admin period update --commit --rgw-realm=<realm-name> --rgw-zonegroup=<zone-group-name>

参数

  • <realm-name>:Realm 名称。
  • <zone-group-name>:Secondary ZoneGroup 名称。
  • <secondary-zone-name>:Secondary Zone 名称。

故障恢复

当主站点的故障集群恢复后,若需从 Secondary 站点切换回主站点,请按以下步骤操作。

操作流程

在 Primary 集群的 rook-ceph-tools pod 中执行以下命令

# 进入 rook-ceph-tools pod
kubectl -n rook-ceph exec -it $(kubectl -n rook-ceph get po -l app=rook-ceph-tools -o jsonpath='{range .items[*]}{@.metadata.name}') -- bash

# 检查同步状态,等待从 Secondary 站点同步完成
radosgw-admin sync status

#           realm 962d6b80-b218-4fc8-8198-e498fabc4e9d (relam-primary)
#       zonegroup 9de3acd7-0b01-4a04-ac84-1421c6103a16 (zonegroup-primary)
#            zone 6319ca70-964e-47be-8b96-7c5bf5a128b1 (zone-primary)
#    current time 2025-12-04T07:18:26Z
# zonegroup features enabled: resharding
#                    disabled: compress-encrypted
#   metadata sync syncing
#                 full sync: 0/64 shards
#                 incremental sync: 64/64 shards
#                 metadata is caught up with master
#       data sync source: 7b3ce7f5-7090-46f6-afb1-d1bb156053da (zone-secondary)
#                         syncing
#                         full sync: 0/128 shards
#                         incremental sync: 128/128 shards
#                         data is caught up with source

# 将恢复的 zone 设置为主 zone 和默认 zone
radosgw-admin zone modify --rgw-realm=<realm-name> --rgw-zonegroup=<zone-group-name> --rgw-zone=<primary-zone-name> --master

# 更新 period 使更改生效
radosgw-admin period update --commit --rgw-realm=<realm-name> --rgw-zonegroup=<zone-group-name>

参数

  • <realm-name>:Realm 名称。
  • <zone-group-name>:ZoneGroup 名称。
  • <primary-zone-name>:Primary Zone 名称。