Global Cluster 灾难恢复

目录

Overview

该方案针对 global 集群的灾难恢复场景设计。global 集群作为平台的控制平面,负责管理其他集群。为确保在 global 集群故障时平台服务的持续可用性,本方案部署两个 global 集群:主集群和备用集群。

灾难恢复机制基于主集群到备用集群的 etcd 数据实时同步。当主集群因故障不可用时,服务可快速切换至备用集群。

支持的灾难场景

  • 主集群发生不可恢复的系统级故障,导致无法正常运行;
  • 托管主集群的物理机或虚拟机故障,导致无法访问;
  • 主集群所在位置的网络故障,导致服务中断;

不支持的灾难场景

  • global 集群内部部署的应用故障;
  • 存储系统故障导致的数据丢失(etcd 同步范围之外);

主集群备用集群的角色是相对的:当前为平台提供服务的集群为主集群(DNS 指向该集群),备用集群为备用集群。故障切换后,角色互换。

注意事项

  • 本方案仅同步 global 集群的 etcd 数据;不包含 registry、chartmuseum 或其他组件的数据;

  • 为便于排查和管理,建议节点命名风格如 standby-global-m1,以标明节点所属集群(主集群或备用集群)。

  • 不支持集群内应用数据的灾难恢复;

  • 两个集群间需保持稳定的网络连接,确保 etcd 同步可靠;

  • 若集群基于异构架构(如 x86 和 ARM),需使用双架构安装包;

  • 以下命名空间不参与 etcd 同步,若在这些命名空间创建资源,需用户自行备份:

    cpaas-system
    cert-manager
    default
    global-credentials
    cpaas-system-global-credentials
    kube-ovn
    kube-public
    kube-system
    nsx-system
    cpaas-solution
    kube-node-lease
    kubevirt
    nativestor-system
    operators
  • 若两个集群均使用内置镜像仓库,容器镜像需分别上传;

  • 若主集群部署了 DevOps Eventing v3(knative-operator)及其实例,备用集群必须预先部署相同组件。

流程概述

  1. 准备统一的域名作为平台访问地址;
  2. 将域名指向主集群的 VIP 并安装主集群
  3. 临时将 DNS 解析切换至备用 VIP,安装备用集群;
  4. 主集群的 ETCD 加密密钥复制到备用集群后续作为控制平面节点的节点上;
  5. 安装并启用 etcd 同步插件;
  6. 验证同步状态并定期检查;
  7. 发生故障时,将 DNS 切换至备用集群完成灾难恢复。

所需资源

  • 统一域名作为 Platform Access Address,以及该域名的 TLS 证书和私钥,用于 HTTPS 服务;

  • 每个集群专用的虚拟 IP 地址——一个用于主集群,另一个用于备用集群;

    • 预先配置负载均衡器,将端口 804436443237911443 的 TCP 流量路由到对应 VIP 后端的控制平面节点。

流程

第 1 步:安装主集群

DR(灾难恢复环境)安装注意事项

安装 DR 环境的主集群时,

  • 首先,记录安装 Web UI 指南中设置的所有参数。安装备用集群时需保持部分选项一致。
  • 必须预配置用户自建的负载均衡器,将流量路由至虚拟 IP。不可使用“自建 VIP”选项。
  • Platform Access Address 字段必须为域名,Cluster Endpoint 必须为虚拟 IP 地址。
  • 两个集群必须配置使用“已有证书”(且为同一证书),必要时申请合法证书。不可使用“自签名证书”选项。
  • Image Repository 设置为 Platform Deployment 时,UsernamePassword 字段均不能为空;IP/Domain 字段必须设置为作为 Platform Access Address 使用的域名。
  • Platform Access AddressHTTP PortHTTPS Port 必须分别为 80 和 443。
  • 安装指南第二页(步骤:Advanced)中,Other Platform Access Addresses 字段必须包含当前集群的虚拟 IP。

参考以下文档完成安装:

第 2 步:安装备用集群

  1. 临时将域名指向备用集群的 VIP;

  2. 登录主集群的第一个控制平面节点,将 etcd 加密配置复制到所有备用集群控制平面节点:

    # 假设主集群控制平面节点为 1.1.1.1、2.2.2.2 和 3.3.3.3
    # 备用集群控制平面节点为 4.4.4.4、5.5.5.5 和 6.6.6.6
    for i in 4.4.4.4 5.5.5.5 6.6.6.6  # 替换为备用集群控制平面节点 IP
    do
      ssh "<user>@$i" "sudo mkdir -p /etc/kubernetes/"
      scp /etc/kubernetes/encryption-provider.conf "<user>@$i:/tmp/encryption-provider.conf"
      ssh "<user>@$i" "sudo install -o root -g root -m 600 /tmp/encryption-provider.conf /etc/kubernetes/encryption-provider.conf && rm -f /tmp/encryption-provider.conf"
    done
  3. 按照主集群相同方式安装备用集群

安装备用集群注意事项

安装 DR 环境的备用集群时, 以下选项必须与主集群保持一致:

  • Platform Access Address 字段;
  • Certificate 的所有字段;
  • Image Repository 的所有字段;
  • 重要:确保镜像仓库凭据和 管理员用户与主集群设置一致。

并且务必遵循第 1 步中 DR(灾难恢复环境)安装注意事项

参考以下文档完成安装:

第 3 步:启用 etcd 同步

  1. 配置负载均衡器,将端口 2379 转发至对应集群的控制平面节点。仅支持 TCP 模式,不支持 L7 转发。

  2. 使用备用 global 集群的 VIP 访问 Web 控制台,切换至 Administrator 视图;

  3. 进入 Marketplace > Cluster Plugins,选择 global 集群;

  4. 找到 etcd Synchronizer,点击 Install,配置参数:

    • 使用默认同步间隔;
    • 除非排查问题,否则关闭日志开关。

验证同步 Pod 在备用集群运行:

kubectl get po -n cpaas-system -l app=etcd-sync
kubectl logs -n cpaas-system $(kubectl get po -n cpaas-system -l app=etcd-sync --no-headers | head -1) | grep -i "Start Sync update"

出现 “Start Sync update” 后,重建一个 Pod 以重新触发带有 ownerReference 依赖的资源同步:

kubectl delete po -n cpaas-system $(kubectl get po -n cpaas-system -l app=etcd-sync --no-headers | head -1)

检查同步状态:

mirror_svc=$(kubectl get svc -n cpaas-system etcd-sync-monitor -o jsonpath='{.spec.clusterIP}')
ipv6_regex="^[0-9a-fA-F:]+$"
if [[ $mirror_svc =~ $ipv6_regex ]]; then
  export mirror_new_svc="[$mirror_svc]"
else
  export mirror_new_svc=$mirror_svc
fi
curl $mirror_new_svc/check

输出说明:

  • LOCAL ETCD missed keys:主集群存在但备用集群缺失的键,通常因同步时资源顺序导致的 GC。重启一个 etcd-sync Pod 可修复;
  • LOCAL ETCD surplus keys:备用集群独有的多余键,删除前请与运维确认。

若安装了以下组件,重启其服务:

  • Elasticsearch 日志存储:

    kubectl delete po -n cpaas-system -l service_name=cpaas-elasticsearch
  • VictoriaMetrics 监控:

    kubectl delete po -n cpaas-system -l 'service_name in (alertmanager,vmselect,vminsert)'

灾难恢复流程

  1. 如有必要,重启备用集群的 Elasticsearch:

    # 将 installer/res/packaged-scripts/for-upgrade/ensure-asm-template.sh 复制到 /root:
    # 不可跳过此步骤
    
    # 如有必要,切换至 root 用户
    sudo -i
    
    # 检查 global 集群是否安装 Elasticsearch 日志存储
    _es_pods=$(kubectl get po -n cpaas-system | grep cpaas-elasticsearch | awk '{print $1}')
    if [[ -n "${_es_pods}" ]]; then
        # 若脚本返回 401 错误,重启 Elasticsearch
        # 然后执行脚本再次检查集群
        bash /root/ensure-asm-template.sh
    
        # 重启 Elasticsearch
        xargs -r -t -- kubectl delete po -n cpaas-system <<< "${_es_pods}"
    fi
  2. 验证备用集群数据一致性(同第 3 步检查);

  3. 卸载 etcd 同步插件;

  4. 取消两个 VIP 上的 2379 端口转发;

  5. 将平台域名 DNS 切换至备用 VIP,备用集群成为新的主集群;

  6. 验证 DNS 解析:

    kubectl exec -it -n cpaas-system deployments/sentry -- nslookup <platform access domain>
    # 若解析不正确,重启 coredns Pod 并重试,直至成功
  7. 清理浏览器缓存,访问平台页面,确认显示为原备用集群;

  8. 重启以下服务(如已安装):

    • Elasticsearch 日志存储:

      kubectl delete po -n cpaas-system -l service_name=cpaas-elasticsearch
    • VictoriaMetrics 监控:

      kubectl delete po -n cpaas-system -l 'service_name in (alertmanager,vmselect,vminsert)'
    • cluster-transformer:

      kubectl delete po -n cpaas-system -l service_name=cluster-transformer
  9. 若业务集群向主集群发送监控数据,重启业务集群中的 warlock:

    kubectl delete po -n cpaas-system -l service_name=warlock
  10. 在原主集群上重复启用 etcd 同步步骤,将其转为新的备用集群。

日常检查

定期检查备用集群的同步状态:

curl $(kubectl get svc -n cpaas-system etcd-sync-monitor -o jsonpath='{.spec.clusterIP}')/check

若发现缺失或多余键,按输出提示处理。

上传软件包

使用 violet 向备用集群上传软件包时,必须指定 --dest-repo 参数为备用集群的 VIP。 若省略该参数,软件包将上传至主集群的镜像仓库,导致备用集群无法安装或升级对应扩展。

常见问题

  • 若备用集群的 ETCD 加密密钥未与主集群同步,安装备用集群前请按以下步骤操作。
  1. 在任一备用集群控制平面节点获取 ETCD 加密密钥:

    ssh <user>@<STANDBY cluster control plane node> sudo cat /etc/kubernetes/encryption-provider.conf

示例内容:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - aescbc:
        keys:
        - name: key1
          secret: MTE0NTE0MTkxOTgxMA==
  1. 在任一主集群控制平面节点(如 1.1.1.1)备份 ETCD 加密密钥:

    # 登录主集群控制平面节点 1.1.1.1
    ssh "<user>@1.1.1.1"
    sudo install -o root -g root -m 600 /etc/kubernetes/encryption-provider.conf /etc/kubernetes/encryption-provider.conf.bak
  2. 将备用集群的 ETCD 加密密钥合并至节点 1.1.1.1/etc/kubernetes/encryption-provider.conf 文件,确保密钥名称唯一。 例如,若主集群密钥为 key1,则将备用集群密钥重命名为 key2

    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
        - secrets
        providers:
        - aescbc:
            keys:
            - name: key1
              secret: My4xNDE1OTI2NTM1ODk3
            - name: key2
              secret: MTE0NTE0MTkxOTgxMA==
  3. 确保新的 /etc/kubernetes/encryption-provider.conf 文件覆盖两个集群控制平面节点的所有副本:

    # 假设主集群控制平面节点为 1.1.1.1、2.2.2.2 和 3.3.3.3
    # 备用集群控制平面节点为 4.4.4.4、5.5.5.5 和 6.6.6.6
    
    # 由于 1.1.1.1 已配置使用两个 ETCD 加密密钥,
    # 登录节点 1.1.1.1,执行以下命令:
    for i in \
        2.2.2.2 3.3.3.3 \
        4.4.4.4 5.5.5.5 6.6.6.6 \
    ; do
        scp /etc/kubernetes/encryption-provider.conf "<user>@${i}:/tmp/encryption-provider.conf"
        ssh "<user>@${i}" '
    #!/bin/bash
    set -euo pipefail
    
    sudo install -o root -g root -m 600 \
        /etc/kubernetes/encryption-provider.conf /etc/kubernetes/encryption-provider.conf.bak
    sudo install -o root -g root -m 600 \
        /tmp/encryption-provider.conf            /etc/kubernetes/encryption-provider.conf
    sudo rm -f /tmp/encryption-provider.conf
    
    _pod_name="kube-apiserver"
    _pod_id=$(sudo crictl ps --name "${_pod_name}" --no-trunc --quiet)
    if [[ -z "${_pod_id}" ]]; then
        echo "FATAL: could not find pod `kube-apiserver` on node $(hostname)"
        exit 1
    fi
    sudo crictl rm --force "${_pod_id}"
    sudo systemctl restart kubelet.service
    '
    done
  4. 重启节点 1.1.1.1 上的 kube-apiserver

    _pod_name="kube-apiserver"
    _pod_id=$(sudo crictl ps --name "${_pod_name}" --no-trunc --quiet)
    if [[ -z "${_pod_id}" ]]; then
        echo "FATAL: could not find pod `kube-apiserver` on node $(hostname)"
        exit 1
    fi
    sudo crictl rm --force "${_pod_id}"
    sudo systemctl restart kubelet.service