安装 global 集群

本文介绍如何在 Alauda OS 上安装 global 集群。global 集群是平台控制平面,通过 Cluster API 进行置备。当平台控制平面必须运行在 Alauda OS 等不可变操作系统上时,请使用此路径。

对于 Huawei DCS 上的 global 集群,请使用为此 版本发布的 Alauda OS 镜像创建 VM 模板。请参阅下载 Alauda OS。

适用场景

在满足以下所有条件时选择此安装路径:

  • 您希望 global 集群运行在不可变操作系统上。目前支持的镜像是 Alauda OS。
  • 您的基础设施属于有文档说明的 provider:Huawei DCS、VMware vSphere、Huawei Cloud Stack 或裸金属。
  • 您可以运行一个临时 bootstrap 节点,该节点能够访问目标 IaaS 平台。

对于 Ubuntu 或 RHEL 等非 Alauda OS,请改用在非 Alauda OS 上安装。

通用先决条件

以下先决条件适用于每个 provider:

  • 一个满足Bootstrap 节点要求的 bootstrap 节点。灾难恢复部署需要两个节点,分别用于主集群和备用 global 集群;请参阅安装主集群和备用集群。
  • 来自 Customer Portal 的 Core Package。
  • 来自 Common ACP Extensions 的八个必需 Aligned Extension 软件包,其 版本和架构必须与 Core Package 相同。请参阅必需的 Aligned Extension。
  • Alauda Container Platform Cluster Bootstrap Provider 软件包。
  • 适用于目标平台的基础设施 provider 软件包。
  • bootstrap 节点与目标 IaaS 平台 API 端点之间的网络可达性。请参阅网络。
  • 为 global 控制平面和工作节点规划 IP 和主机名。有关各 provider 使用的资源模型,请参阅基础设施资源。
  • global 集群的稳定 Kubernetes API 端点。创建集群前,请在规划控制平面端点中选择并验证特定于 provider 的端点模式。
  • 平台访问地址、registry 地址以及 Pod 和 Service CIDR 范围。如果要使用客户管理的外部平台镜像 registry,请先完成准备;请参阅准备外部平台镜像 registry。
  • 对于使用 ACP 提供的 Alauda OS 镜像的 x86_64 节点,其底层 CPU 必须支持 x86-64-v2 ISA 基线。请参阅操作系统支持矩阵。
命名约定(必需)

此规则适用于此安装路径支持的每个基础设施 provider,包括 Huawei DCS、Huawei Cloud Stack、VMware vSphere 以及未来添加的任何 provider。您在步骤 4 中编写的每个 manifest 都必须遵循此规则。这些资源命名错误会导致两种不同的故障模式,具体如下;一种会破坏初始置备,另一种只会在灾难恢复期间出现。

  • CAPI Cluster 和 provider 的基础设施集群资源(例如 Huawei DCS 的 DCSCluster 或 Huawei Cloud Stack 的 HCSCluster;每个 provider 都有其对应资源)必须严格命名为 global。cpaas-installer 会按字面名称查找这些资源;只有在基础设施集群命名为 global 时,Huawei Cloud Stack provider 才会分配全局 ELB 监听器端口(registry 和控制台使用 11443,DR etcd 同步使用 2379,Web 访问使用 443)。使用其他名称会静默破坏 registry 拉取、DR etcd 同步和 Web 控制台。
  • 每个其他 CAPI 资源(KubeadmControlPlane、KubeadmConfigTemplate、MachineDeployment)以及每个其他 provider 基础设施资源(machine template、IP/主机名池、machine config pool 和其他任何特定于 provider 的资源)都必须使用带有 global- 前缀的名称。DR(故障转移)机制使用此前缀来识别由 global 集群拥有的资源。不带 global- 前缀的 global 集群资源对 DR 不可见,并会导致备用集群的机器在故障转移时被删除——集群会正常完成置备并运行,但首次执行 DR 时会丢失节点。这是硬性要求,而非样式约定。
  • Cluster.spec.controlPlaneRef.name 及其他所有交叉引用都必须与带前缀的名称完全匹配。

Bootstrap 节点要求

bootstrap 节点是一台运行 Core Package 中 setup.sh 的临时机器。在安装期间,它承载基于 KIND 的 minialauda 管理集群、内置平台 registry、Cluster API bootstrap 和基础设施 provider,以及 cpaas-installer。它不会加入 global 集群,交接完成后即会移除——请参阅停用 Bootstrap 集群。

请根据其提供的镜像负载而非稳定运行时负载来规划规格:在集群置备期间,每个 global 节点都会从此主机拉取平台镜像集。

硬件

资源最低要求(x86_64)
CPU4 个核心
内存8 GB
磁盘300 GB 或更多,可供提取目录(/root/cpaas-install)和 /var/cpaas 使用

CPU 和内存最低要求来自此安装路径的标准交付实践。在非 Alauda OS 安装路径中,运行 setup.sh 的机器会成为第一个控制平面节点,因此必须满足前提条件中的控制平面最低要求;此处的 bootstrap 节点不会加入集群——它只提取 Core Package、运行临时的基于 KIND 的 bootstrap 集群,并在交接前提供平台 registry。这是较轻的临时负载,4 个核心 / 8 GB 是标准交付所采用并验证的规格。

在 arm64 上,请遵循同一页面中的平台 ARM 约定:至少使用 x86_64 规格的 1.5 倍,最好使用 2 倍,因此最低要求为 6 个核心 / 12 GB,推荐配置为 8 个核心 / 16 GB。

磁盘是最常导致 bootstrap 停滞的资源。请从以下三个方面规划磁盘空间:

使用者空间要求
已提取的 Core Package提取后至少 100 GB。请参阅在非 Alauda OS 上安装
/var/cpaas 下的内置 registry 数据目标版本的平台镜像负载,以及复制到 plugins/ 中的 Aligned Extension 软件包
暂存待上传的 provider 软件包和 OS 工件Cluster Bootstrap Provider 和基础设施 provider 软件包,以及每种节点架构各一个 Alauda OS 镜像或 VM 模板工件

操作系统

bootstrap 节点不运行 Alauda OS。它需要一个带有 Bash 和 root 权限的 64 位 Linux 发行版,以便 setup.sh 能够启动步骤 2 中所述的 bootstrap 集群。

节点预处理中的要求——经过验证的 OS 和内核列表、SSH 用户以及 sshd_config 设置——不适用于此安装路径中的 bootstrap 节点。这些要求针对的是平台通过 SSH 加入集群的非 Alauda OS 机器,而 bootstrap 节点不会加入 global 集群。这与非 Alauda OS 安装路径不同,在该路径中,运行 setup.sh 的机器会成为第一个控制平面节点,因此必须满足这些要求。

在没有其他约束时,从该经过验证的列表中选择一个发行版仍然是安全的默认选择。

x86-64-v2 基线适用于集群节点,不适用于 bootstrap 节点

OS 支持矩阵中的 x86-64-v2 ISA 基线适用于从 ACP 提供的 Alauda OS 镜像创建的集群节点。平台不会对运行用户提供的非 Alauda OS 的 x86_64 机器强制实施该基线,因此它不是 bootstrap 节点本身的要求。请参阅节点预处理。

如果 bootstrap 节点是运行集群节点的同一基础设施上的虚拟机,检查其 CPU 标志可以及早发现缺少基线的问题。但这不能替代在实际运行 Alauda OS 节点的主机上验证 x86-64-v2,因为来宾机可见的标志取决于 hypervisor 的 CPU 模型。

网络

bootstrap 节点需要一个在整个安装生命周期内保持不变的静态 IP 地址。HOST_IP 会写入置备节点用于拉取镜像的 cpaas.io/registry-address annotation;对于裸金属,还会写入注册端点和其服务证书的 SAN。安装过程中途更改地址会导致节点置备失败。

来源目标端口用途
Bootstrap 节点目标 IaaS 平台 APIprovider 特定:DCS VRM TCP/7443 和 DCS 物理主机 MGMT(通常为 TCP/8443);vCenter TCP/443;HCS API 和 IAM 端点Cluster API provider 在 minialauda 中运行,并通过 IaaS API 调谐机器。对于 DCS,此路径还承载 Ignition ISO 上传;请参阅在 Huawei DCS 上创建集群
Bootstrap 节点global 控制平面端点TCP/6443创建控制平面时,Cluster API 必须能够访问该端点。请参阅规划控制平面端点
正在置备的 global 节点Bootstrap 节点 registryTCP/11443在 global 集群自身的 registry 接管之前,节点从 NODE_REGISTRY_ADDRESS 拉取平台镜像
global 主机,仅限裸金属Bootstrap 节点TCP/12443bootstrap 阶段使用的 Elemental 注册端点

兼容性和版本输入

安装前,记录交付包支持的版本集:

输入用途
Core Package 版本提供安装程序、本地 registry 和基础平台负载。
Cluster Bootstrap Provider chart 版本必须与全局 manifest 使用的 Cluster API 控制平面资源匹配。
基础设施 provider chart 版本使用目标版本随附的 VMware vSphere、DCS、HCS 或 Bare Metal provider chart 版本。
Alauda OS 镜像或 VM 模板必须包含 K8S_VERSION 使用的 Kubernetes 版本。
K8S_VERSION使用与目标 Alauda OS 镜像匹配的带 v 前缀 semver,例如 v<major>.<minor>.<patch>。

安装生命周期

全局集群安装生命周期

使用以下阶段图确定安装每个部分的输入、预期结果和首个诊断位置。

阶段输入应用预期结果首先诊断
引导环境Core Package 和引导节点运行 setup.shminialauda 管理集群、本地 registry 和安装程序 Pod 正在运行。cpaas-system 中的引导节点容器和 Pod。
Provider 交付Cluster Bootstrap Provider 和基础设施 provider 软件包推送软件包并应用 provider AppRelease 对象。Provider CRD 和控制器均为 Ready。AppRelease 状态、provider Pod 以及 registry 访问。
集群定义Provider 凭证、端点、网络、节点和镜像值应用完整的 Cluster API manifest。Cluster、基础设施集群、控制平面和机器资源出现。资源条件和 provider-controller 日志。
节点置备VM 模板或已注册主机让基础设施 provider 调谐 Machine。VM 或物理主机启动并接收 kubeadm 引导数据。Provider Machine 对象、IaaS 事件和客户机初始化日志。
控制平面Kubernetes 和组件版本等待 KubeadmControlPlane。预期的控制平面副本均为 Ready,且 API 端点响应正常。KubeadmControlPlane、Machine、etcd 和端点健康状况。
平台安装安装程序请求和导入资源触发安装程序。ACP Core 组件、registry、平台访问和基础模块变为健康状态。cpaas-system 中的安装程序进度 API 和 Pod。
移交已导入的 provider 资源和凭证验证最终的 global 集群以及每个 provider 特定的移交门控,然后仅停用 minialauda。最终集群持有已导入的 provider 资源和凭证,不再依赖临时管理集群。对于 Bare Metal,system-agent 移交 ConfigMap 与当前端点、身份验证配置文件和完整目标集匹配。已导入资源、provider 凭证、最终集群 annotation,以及 provider 特定的移交 Job 和 ConfigMap。

操作步骤

步骤 1 — 准备通用变量

在引导节点上设置通用变量。

export HOST_IP="<bootstrap-host-ip>"
export LOCAL_REGISTRY_ADDRESS="127.0.0.1:11443"
export BOOTSTRAP_REGISTRY_ADDRESS="172.18.0.1:11443"
export NODE_REGISTRY_ADDRESS="${HOST_IP}:11443"
export CONTROL_PLANE_VIP="<global-control-plane-vip>"
export PLATFORM_HOST="<platform-access-domain-or-vip>"
export REGISTRY_DOMAIN="<platform-registry-domain-or-vip>:11443"
export CLUSTER_CIDR="100.3.0.0/16"
export SERVICE_CIDR="100.4.0.0/16"
export KUBE_OVN_JOIN_CIDR="<kube-ovn-join-cidr>"
export K8S_VERSION="<target-kubernetes-version>"
export INGRESS_CLASS_NAME="global-alb2"
export PROVIDER_SECRET_NAME="global-secret"
# Use v-prefixed semver that matches the target Alauda OS image.

从引导节点推送软件包时使用 LOCAL_REGISTRY_ADDRESS。在 AppRelease chart 仓库值中使用 BOOTSTRAP_REGISTRY_ADDRESS,因为 provider Pod 会从引导集群的网络内部读取 chart 仓库。由于置备的 global 节点在置备期间必须通过其子网可访问的地址拉取镜像,因此在 Cluster API registry annotation 中使用 NODE_REGISTRY_ADDRESS(引导节点的 registry,即 <bootstrap-host-ip>:11443)。这是一个临时值:global 集群自身的 registry 启动后,安装程序会自动将 Cluster 和 DCSCluster 上的 cpaas.io/registry-address annotation 重写为永久平台 registry,因此后续调谐会从 global 集群而不是引导节点拉取镜像。

请区分 Registry 的格式:

用途格式示例
Registry HTTP API 请求带有 /v2/ 路径的 URLhttp://${LOCAL_REGISTRY_ADDRESS}/v2/acp/chart-cluster-api-provider-kubeadm/tags/list
容器镜像引用Registry、仓库以及 tag 或 digest${BOOTSTRAP_REGISTRY_ADDRESS}/acp/kubeadm-bootstrap-controller:<tag>
cpaas.io/registry-address annotation仅使用 <host>:<port>${NODE_REGISTRY_ADDRESS}
AppRelease repoURL不带 /v2/ 的 Registry 地址${BOOTSTRAP_REGISTRY_ADDRESS}

/v2/ 片段仅属于 Registry HTTP API 请求。不要将其添加到镜像引用、cpaas.io/registry-address annotation 或 AppRelease repoURL 中。

步骤 2 — 创建引导集群

使用 Bash 运行 Core Package 提供的引导脚本。该脚本会在引导节点上创建一个名为 minialauda 的临时、基于 KIND 的引导集群——这是仅用于置备 global 集群的临时 Cluster API 管理集群。完成后,将引导控制平面容器中的匹配 kubectl 客户端提供给引导节点,并配置导出的 kubeconfig。

mkdir -p /root/cpaas-install
tar -xvf <core-package> -C /root/cpaas-install
cd /root/cpaas-install/installer

Core Package 不包含所需的 Aligned Extension 软件包。在运行 setup.sh 之前,将 Required Aligned Extensions 中列出的八个软件包分别复制到解压后的 Core Package 的 plugins/ 目录中。如果缺少这些软件包,则无法安装所需的 Aligned Extensions。每个软件包运行一次以下命令:

cp <path-to-extension-package> /root/cpaas-install/installer/plugins/

下载的文件名为 <package-name>.<arch>.<version>.tgz。请保持文件名不变。这八个软件包名称为 console、console-central、console-marketplace、application-catalog、aiops-base、container-platform、acp-business 和 gatewayapi-plugin。保留 plugins/ 中已有的文件,并确认八个软件包均已存在:

ls -1 /root/cpaas-install/installer/plugins/

不要将 Common ACP Extensions 中的其他软件包复制到此目录中。Cluster Bootstrap Provider 和基础设施 provider 软件包也不复制到此处;步骤 3 会将它们上传到引导 Registry。

然后运行引导脚本。要使用外部平台镜像 registry,请使用 bash setup.sh --registry "<registry-address>" 替代 bash setup.sh。Registry 地址采用 <host-or-IP>[:port] 格式,不包含 http:// 或 https://。如果 registry 需要身份验证,还要同时提供拉取 --username 和 --password;有关如何在不暴露密码的情况下输入这些值,请参阅 Start the Installer。按照 Prepare an External Platform Image Registry 中的说明,在执行此步骤前准备好 registry。

bash setup.sh
mkdir -p "${HOME}/.local/bin" ~/.kube

if ! command -v kubectl >/dev/null 2>&1; then
  nerdctl cp minialauda-control-plane:/usr/bin/kubectl \
    "${HOME}/.local/bin/kubectl"
  chmod 0755 "${HOME}/.local/bin/kubectl"
  export PATH="${HOME}/.local/bin:${PATH}"
fi

cp /var/cpaas/data/alauda.kubeconfig ~/.kube/config
kubectl get nodes

引导脚本会置备内置 registry、Cluster API 控制平面以及驱动 global 集群安装的安装程序组件。

默认情况下,内置引导 Registry 为匿名访问,不会创建 global-registry-auth Secret。如果在引导设置期间同时配置了 Registry 用户名和密码,setup.sh 会在 cpaas-system 中创建该 Secret。

步骤 3 — 上传并安装 Provider 软件包

将 Cluster Bootstrap Provider 软件包和基础设施 provider 软件包上传到本地 registry。

下面的 AppRelease 示例使用默认的匿名引导 Registry,因此不引用 global-registry-auth。

如果引导 Registry 需要身份验证,请在创建任何 provider AppRelease 之前确认 Secret 存在:

kubectl -n cpaas-system get secret global-registry-auth -o name

然后在应用每个 provider AppRelease 之前添加这两个身份验证字段:

spec:
  source:
    chartPullSecret: global-registry-auth
  values:
    global:
      registry:
        imagePullSecrets:
          - global-registry-auth

如果 Registry 为匿名访问,则不要创建占位 Secret,也不要添加这些字段。引导 Secret 也不是凭证传输机制:默认情况下,业务集群通过 global 集群的 public-registry-credential 流程接收其 Registry 拉取 Secret;也可以按照 Choose the Image Registry for a Workload Cluster 中的说明,将业务集群绑定到专用 registry。不要将引导集群的 global-registry-auth Secret 复制到业务集群中。

为什么每个 provider 的 cluster.type 都是 Baremetal

下面选项卡中的 AppRelease 值均设置了 global.cluster.type: Baremetal。这是 chart 内部的分类器,而不是 IaaS provider 名称。在 Huawei DCS、VMware vSphere、Huawei Cloud Stack 和 Bare Metal 的 global 安装中,都应保留 Baremetal。该值决定平台如何配置节点级组件;它不会选择基础设施 provider。

Huawei DCS
VMware vSphere
Huawei Cloud Stack
Bare Metal

设置 provider 软件包路径和 chart 版本。

export DCS_PROVIDER_PACK="/root/cluster-api-provider-dcs.amd64.<version>.tgz"
export KUBEADM_PROVIDER_PACK="/root/cluster-api-provider-kubeadm.amd64.<version>.tgz"
export DCS_PROVIDER_VERSION="<dcs-provider-chart-version>"
export KUBEADM_PROVIDER_VERSION="<kubeadm-provider-chart-version>"

上传软件包。

/root/cpaas-install/installer/res/amd64/packtool pack push \
  -r "${LOCAL_REGISTRY_ADDRESS}" -c "${DCS_PROVIDER_PACK}"

/root/cpaas-install/installer/res/amd64/packtool pack push \
  -r "${LOCAL_REGISTRY_ADDRESS}" -c "${KUBEADM_PROVIDER_PACK}"

创建并应用 Cluster Bootstrap Provider 和 DCS provider 的 AppRelease 资源。

mkdir -p /root/yamls
export DCS_PROVIDER_APPRELEASES="/root/yamls/dcs-provider-appreleases.yaml"

cat > "${DCS_PROVIDER_APPRELEASES}" <<EOF
---
apiVersion: operator.alauda.io/v1alpha1
kind: AppRelease
metadata:
  annotations:
    auto-recycle: "true"
    interval-sync: "true"
  name: cluster-api-provider-kubeadm
  namespace: cpaas-system
spec:
  destination:
    cluster: ""
    namespace: ""
  source:
    charts:
      - name: acp/chart-cluster-api-provider-kubeadm
        releaseName: cluster-api-provider-kubeadm
        targetRevision: ${KUBEADM_PROVIDER_VERSION}
    repoURL: ${BOOTSTRAP_REGISTRY_ADDRESS}
  timeout: 120
  values:
    global:
      albName: ${INGRESS_CLASS_NAME}
      auth:
        default_admin: admin@cpaas.io
      cluster:
        isGlobal: true
        name: global
        networkType: kube-ovn
        type: Baremetal
      host: ${PLATFORM_HOST}
      ingress:
        ingressClassName: ${INGRESS_CLASS_NAME}
      labelBaseDomain: cpaas.io
      namespace: cpaas-system
      platformUrl: https://${PLATFORM_HOST}
      protectSecretFiles:
        enabled: false
      region: global
      registry:
        address: ${BOOTSTRAP_REGISTRY_ADDRESS}
      replicas: 1
      scheme: https
---
apiVersion: operator.alauda.io/v1alpha1
kind: AppRelease
metadata:
  annotations:
    auto-recycle: "true"
    interval-sync: "true"
  name: cluster-api-provider-dcs
  namespace: cpaas-system
spec:
  destination:
    cluster: ""
    namespace: ""
  source:
    charts:
      - name: acp/chart-cluster-api-provider-dcs
        releaseName: cluster-api-provider-dcs
        targetRevision: ${DCS_PROVIDER_VERSION}
    repoURL: ${BOOTSTRAP_REGISTRY_ADDRESS}
  timeout: 120
  values:
    global:
      albName: ${INGRESS_CLASS_NAME}
      auth:
        default_admin: admin@cpaas.io
      cluster:
        isGlobal: true
        name: global
        networkType: kube-ovn
        type: Baremetal
      host: ${PLATFORM_HOST}
      ingress:
        ingressClassName: ${INGRESS_CLASS_NAME}
      labelBaseDomain: cpaas.io
      namespace: cpaas-system
      platformUrl: https://${PLATFORM_HOST}
      protectSecretFiles:
        enabled: false
      region: global
      registry:
        address: ${BOOTSTRAP_REGISTRY_ADDRESS}
      replicas: 1
      scheme: https
EOF

kubectl apply -f "${DCS_PROVIDER_APPRELEASES}"

until kubectl get crd kubeadmcontrolplanes.controlplane.cluster.x-k8s.io --ignore-not-found 2>/dev/null | grep -q kubeadmcontrolplanes.controlplane.cluster.x-k8s.io; do
  sleep 10
done

until kubectl get crd dcsclusters.infrastructure.cluster.x-k8s.io --ignore-not-found 2>/dev/null | grep -q dcsclusters.infrastructure.cluster.x-k8s.io; do
  sleep 10
done

步骤 4 — 配置 Provider 特定的全局 Manifest

为 global 集群创建一个 provider 特定的 manifest。该 manifest 使用与业务集群相同的 provider 资源,但还必须包含平台控制平面所需的全局专用标签、annotation、registry 值、与安装程序兼容的 kubeadm 设置以及持久化数据路径。

使用 provider 创建指南作为详细资源参考:

将 Common Prerequisites 中的命名约定 应用于你在下面编写的 manifest 中的每个资源。

将 KubeadmControlPlane.spec.kubeadmConfigSpec.format 设置为目标 provider 接受的值。这是一个 API 字段:输入 cloud-config,而不是 cloud-init。对于下面列出的 provider,cloud-init 是接收并应用生成的 cloud-config 数据的客户机操作系统软件。Provider controller 会强制执行该字段值:

Provider引导 userdata 格式
Huawei DCSignition(由 provider 强制执行;DCS provider 会以 invalid format, expected ignition, got <other> 拒绝任何其他格式)。
VMware vSpherecloud-config(CAPI 引导数据由客户机中的 cloud-init 软件处理;不支持设置 ignition)。
Huawei Cloud Stackcloud-config(HCS provider 会拒绝 ignition;客户机会使用 cloud-init 处理生成的数据)。
Bare Metalcloud-config(裸金属 provider 使用 CAPI 引导数据,并渲染由客户机使用 cloud-init 处理的 elemental plan)。
Huawei DCS
VMware vSphere
Huawei Cloud Stack
裸金属

在渲染 DCS global manifest 之前,设置其输出路径。

export GLOBAL_DCS_YAML="/root/yamls/new-global.yaml"

按照Cloud Credentials中的说明,在组装 manifest 之前创建 DCS API 凭证 Secret。不要将其作为 YAML 写入 manifest:第 5 步使用 kubectl apply 应用 manifest,这会将凭证记录在 Secret 的 kubectl.kubernetes.io/last-applied-configuration annotation 中。manifest 通过 DCSCluster.spec.credentialSecretRef 引用该凭证。

然后,DCS global manifest 必须在 cpaas-system namespace 中包含以下资源:

资源用途
用于控制平面节点的 DCSIpHostnamePool分配静态 IP、主机名、网络设置以及由资源池管理的持久化磁盘。
用于控制平面节点的 DCSMachineTemplate定义 DCS VM 模板、文件夹、CPU、内存和模板本地磁盘。
KubeadmControlPlane引导 Kubernetes 控制平面。将 spec.version 设置为 ${K8S_VERSION}。
DCSCluster定义 DCS 基础设施集群和控制平面端点。
Cluster将 Cluster API Cluster 连接到 DCSCluster 和 KubeadmControlPlane。
用于工作节点的 DCSIpHostnamePool、DCSMachineTemplate、KubeadmConfigTemplate 和 MachineDeployment创建工作节点。

使用在 Huawei DCS 上创建集群和Huawei DCS 基础设施资源中的 DCS 资源字段。对于 global 集群,还需满足以下要求:

  • 将 Cluster.metadata.name 和 DCSCluster.metadata.name 设置为 global(基础设施集群与 CAPI 共享 Cluster 名称)。为其他每个 CAPI 资源和 provider 资源添加 global- 前缀;下面的连接片段使用 KubeadmControlPlane.metadata.name: global-kcp。
  • 将 DCSCluster.spec.credentialSecretRef.name 设置为 ${PROVIDER_SECRET_NAME}。第 7 步会将此 Secret 导入最终的 global 集群。
  • 添加 Cluster.metadata.labels.is-global: "true" 和 Cluster.metadata.labels.cluster-type: DCS。
  • 添加包含 ${NODE_REGISTRY_ADDRESS} 的 Cluster.metadata.annotations["cpaas.io/registry-address"]。
  • 为 Alauda OS 设置 KubeadmControlPlane.spec.kubeadmConfigSpec.format: ignition。
  • 使用外部 LoadBalancer 并设置 DCSCluster.spec.controlPlaneLoadBalancer.type: external,或使用 type: internal Self-built VIP。对于内部模式,在控制平面 Layer 2 网络中预留 IPv4 VIP,并使用在 Huawei DCS 上创建集群中的 YAML 工作流。对于外部模式,按照规划控制平面端点中的说明,验证 listener、后端、运行状况检查和可达性。
  • 保留 KubeadmControlPlane.spec.kubeadmConfigSpec.users 条目,并确保其 sshAuthorizedKeys 列表非空(boot 用户)。DCS ignition 格式会拒绝空的 SSH 密钥列表,因此,即使你不计划通过 SSH 访问 global 集群,也必须设置此字段。如果不需要交互式密钥,请参阅解析占位符值了解应提供的值。
  • 保留非加密 kubeadm 文件、kubelet 补丁、审计策略和安装程序 RBAC 条目。文件内容(PodSecurity admission 配置、kubelet 补丁和审计策略)以及完整的 clusterConfiguration、preKubeadmCommands、postKubeadmCommands、init 和 join 节点注册补丁,都与业务集群相同。请从完整的 KubeadmControlPlane 配置附录中复制这些内容,或引用其中说明的 dcs-kubernetes-<major.minor>-files Secret。下面的连接片段仅展示叠加在该基础配置之上的 global 特定字段。
  • 对于正常的非 DR 部署,不要设置 DCSCluster.spec.encryptionProviderConfigRef。按照附录中的配置保留 clusterConfiguration.apiServer.extraArgs.encryption-provider-config:DCS provider 会自行将 /etc/kubernetes/encryption-provider.conf 写入每个控制平面节点——第一个机器使用随机密钥,其余机器使用正在运行的 API server 文件——因此该参数可以解析,并且 global 集群会启用 etcd 静态数据加密。不要将该文件添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.files;provider 会丢弃该路径的任何条目,因此添加在其中的条目会被静默忽略。请参阅如何交付加密 provider 配置。
  • 将 /var/cpaas 保留为平台状态。如果需要在滚动替换期间保留磁盘,请在 DCSIpHostnamePool.spec.pool[].persistentDisk 中声明它;不要依赖 DCSMachineTemplate 模板磁盘来保留状态。
  • 对 DCS 本地存储使用具体的 datastoreName 值,除非你已确认所选 datastore 集群可以将卷放置在能够运行目标 VM 的主机上。
片段范围

以下 YAML 是差异片段,不是可以直接应用的完整 manifest。将这些 global 特定变更合并到根据 DCS 创建集群参考资料准备的 manifest 中,然后应用完整的 manifest 文件。如果希望从完整文件开始,而不是从片段组装,请在本页末尾调整示例:Huawei DCS 的完整 global Manifest。

以下片段展示了 global 特定的 Cluster API 连接配置。使用上面的 DCS 创建集群参考资料填写 provider 资源字段。

apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
  name: global
  namespace: cpaas-system
  labels:
    cluster-type: DCS
    is-global: "true"
  annotations:
    capi.cpaas.io/resource-group-version: infrastructure.cluster.x-k8s.io/v1beta1
    capi.cpaas.io/resource-kind: DCSCluster
    cpaas.io/registry-address: "${NODE_REGISTRY_ADDRESS}"
spec:
  clusterNetwork:
    pods:
      cidrBlocks:
        - ${CLUSTER_CIDR}
    services:
      cidrBlocks:
        - ${SERVICE_CIDR}
  controlPlaneRef:
    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
    kind: KubeadmControlPlane
    name: global-kcp
  infrastructureRef:
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
    kind: DCSCluster
    name: global
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
  name: global-kcp
  namespace: cpaas-system
  annotations:
    controlplane.cluster.x-k8s.io/skip-kube-proxy: ""
spec:
  replicas: 3
  version: ${K8S_VERSION}
  rolloutStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 0
  machineTemplate:
    nodeDrainTimeout: 1m
    nodeDeletionTimeout: 5m
    infrastructureRef:
      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
      kind: DCSMachineTemplate
      name: global-cp-template
  kubeadmConfigSpec:
    format: ignition
    clusterConfiguration:
      etcd:
        local:
          serverCertSANs:
            - "${CONTROL_PLANE_VIP}"
            - "${PLATFORM_HOST}"

步骤 5 — 应用 global 清单

将 provider 特定的清单应用到 bootstrap 集群。

Huawei DCS
VMware vSphere
Huawei Cloud Stack
Bare Metal
kubectl apply -f "${GLOBAL_DCS_YAML}"

步骤 6 — 等待控制平面

等待 Cluster API provider 置备机器并启动 Kubernetes 控制平面。

kubectl get clusters.cluster.x-k8s.io -n cpaas-system
kubectl get kubeadmcontrolplane -n cpaas-system
kubectl get machines -n cpaas-system

当 KubeadmControlPlane 报告 Ready: True,且 Cluster 报告 Phase: Provisioned 时,控制平面即已就绪。

步骤 7 — 导入 Provider 资源

对于需要额外导入资源的 provider,请在触发安装程序之前,在 cpaas-system 命名空间中创建 dcs-import-extra-resources ConfigMap。即使 provider 不是 Huawei DCS,ConfigMap 名称仍保留 dcs 前缀,以兼容旧版安装程序。

每个 provider 都使用此 ConfigMap 将 IaaS 凭证 Secret 导入新的 global 集群。对于 VMware vSphere、Huawei Cloud Stack 和 Bare Metal,它还会导入 provider 的 Cluster API 资源;对于 Huawei DCS,这些资源由内置流程迁移,因此正常安装时,DCS ConfigMap 只需包含凭证 Secret 条目。正常安装和灾难恢复 global 安装都需要 VMware vSphere、Huawei Cloud Stack 和 Bare Metal 的 ConfigMap。

对于灾难恢复安装,通过 Secret 引用提供加密 provider 配置的两个 provider——Huawei DCS(DCSCluster.spec.encryptionProviderConfigRef)和 Bare Metal(BaremetalCluster.spec.encryptionProviderConfigRef)——还必须通过此 ConfigMap 导入该 Secret。内置的 DCS 迁移流程只迁移 Cluster API 资源,不会复制所引用的 Secret。

Huawei DCS
VMware vSphere
Huawei Cloud Stack
Bare Metal

DCS provider 的 Cluster API 资源由内置流程迁移,因此正常安装时,ConfigMap 只需导入 DCSCluster.spec.credentialSecretRef.name 引用的凭证 Secret。使用在 global 清单中设置的相同 Secret 名称。

mkdir -p /root/yamls
cat > /root/yamls/dcs-import-extra-resources.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: dcs-import-extra-resources
  namespace: cpaas-system
data:
  resources.yaml: |
    resources:
    - resource: "secrets"
      names: ["${PROVIDER_SECRET_NAME}"]
      method: kubectl
EOF

kubectl apply -f /root/yamls/dcs-import-extra-resources.yaml

对于 DR 安装,请改用以下形式。它会添加 DCSCluster.spec.encryptionProviderConfigRef.name 引用的加密 provider Secret,该 provider 必须传递到最终的 global 集群。DCS_ENCRYPTION_PROVIDER_SECRET 来自准备共享 DR 变量;如果未导出该变量,第一行会明确报错,因为否则 heredoc 会将其展开为空的 Secret 名称。

mkdir -p /root/yamls
: "${DCS_ENCRYPTION_PROVIDER_SECRET:?export it first — see Prepare Shared DR Variables}"
cat > /root/yamls/dcs-import-extra-resources.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: dcs-import-extra-resources
  namespace: cpaas-system
data:
  resources.yaml: |
    resources:
    - resource: "secrets"
      names:
        - "${PROVIDER_SECRET_NAME}"
        # The name must match the Secret referenced by
        # DCSCluster.spec.encryptionProviderConfigRef.
        - "${DCS_ENCRYPTION_PROVIDER_SECRET}"
      method: kubectl
EOF

kubectl apply -f /root/yamls/dcs-import-extra-resources.yaml
kubectl -n cpaas-system get cm dcs-import-extra-resources -o yaml

继续之前,请检查渲染后的 ConfigMap:两个 names 条目都必须非空,且第二个条目必须与 DCSCluster.spec.encryptionProviderConfigRef.name 完全匹配。

对于 DR,请在安装后验证最终的 global 集群中包含已导入的加密 provider Secret。

kubectl --kubeconfig <global-kubeconfig> -n cpaas-system \
  get secret "${DCS_ENCRYPTION_PROVIDER_SECRET}" \
  -o jsonpath='{.data.encryption-provider\.conf}{"\n"}'

步骤 8 — 触发平台安装

向嵌入式安装程序 REST API 提交平台安装请求。安装程序会将 Cluster API 资源导入新的 global 集群,部署基础 operator,并安装所选插件。

export INSTALLER_IP=$(kubectl get pods -n cpaas-system -l service_name=cpaas-installer \
  -o jsonpath='{.items[0].status.podIP}')
网络范围

INSTALLER_IP 是 bootstrap 集群中嵌入式安装程序的 Pod IP。该端点仅在安装期间使用。

在当前 bootstrap 节点上创建 provider 特定的安装程序配置 JSON 文件,然后将其提交到安装程序端点。此安装路径中的所有 provider 使用相同的端点路径,但请求正文不同。

字段Huawei DCSVMware vSphereHuawei Cloud StackBare Metal
端点路径/cpaas-installer/api/config/dcs/cpaas-installer/api/config/dcs/cpaas-installer/api/config/dcs/cpaas-installer/api/config/dcs
console.host["${CONTROL_PLANE_VIP}"]["${CONTROL_PLANE_VIP}"]["${CONTROL_PLANE_VIP}"]["${CONTROL_PLANE_VIP}"]
console.globalHost平台访问地址平台访问地址平台访问地址平台访问地址
cluster.clusterCIDR 和 cluster.serviceCIDR必需未设置;集群 CIDR 在 VMware vSphere Cluster 清单中声明未设置必需
cluster.features.ha必需,vip 为带有 isThirdParty: true 的 ${CONTROL_PLANE_VIP}必需,vip 为带有 isThirdParty: true 的 ${CONTROL_PLANE_VIP}。两种端点模式均相同;VIP 也在 VSphereCluster.spec.controlPlaneEndpoint.host 中声明未设置;HCS ELB 在 HCSCluster 中声明必需,vip 为带有 isThirdParty: true 的 ${CONTROL_PLANE_VIP}
hostIP当前 bootstrap 节点 IP当前 bootstrap 节点 IP当前 bootstrap 节点 IP当前 bootstrap 节点 IP

每个 console.host 条目都是裸地址:不带 https:// 前缀,也不带端口。端口由 console.httpPort 和 console.httpsPort 指定。

如果在步骤 2 中启动 bootstrap 脚本时使用了外部 platform 镜像 registry,请在请求的 registry 对象中,将 registry.externalAddress 设置为 registry 地址,以替代 registry.domain;不要同时设置两者。保留示例中的 registry.username 和 registry.password:它们是用于从 registry 拉取镜像的凭证。如果 registry 允许匿名拉取,则省略这两个字段。安装程序不会将镜像或软件包复制到外部 registry,因此目标版本的负载必须已上传;请参阅准备外部平台镜像 Registry。

Huawei DCS
VMware vSphere
Huawei Cloud Stack
Bare Metal

DCS 安装程序请求包含 global 控制平面 VIP。使用外部负载均衡器时,这是外部预配的 VIP。使用 Self-built VIP 时,请使用 provider 管理的 alive 运行时所拥有的保留 VIP。

mkdir -p /root/yamls
export INSTALLER_CONFIG_JSON="/root/yamls/installer-config-dcs.json"

cat > "${INSTALLER_CONFIG_JSON}" <<EOF
{
  "basic": {
    "username": "admin@cpaas.io",
    "password": "<base64-platform-admin-password>"
  },
  "registry": {
    "domain": "${REGISTRY_DOMAIN}",
    "username": "<registry-username>",
    "password": "<base64-registry-password>"
  },
  "console": {
    "host": [
      "${CONTROL_PLANE_VIP}"
    ],
    "globalHost": "${PLATFORM_HOST}",
    "httpPort": 80,
    "httpsPort": 443,
    "cert": {
      "selfSigned": {}
    }
  },
  "cluster": {
    "clusterCIDR": "${CLUSTER_CIDR}",
    "serviceCIDR": "${SERVICE_CIDR}",
    "features": {
      "ha": {
        "vip": "${CONTROL_PLANE_VIP}",
        "vport": 6443,
        "isThirdParty": true
      }
    }
  },
  "product": [
    "base",
    "acp"
  ],
  "deployMode": "normal",
  "hostIP": "${HOST_IP}"
}
EOF

curl -k -X POST "http://${INSTALLER_IP}:8080/cpaas-installer/api/config/dcs" \
  -H 'Content-Type: application/json' \
  -d @"${INSTALLER_CONFIG_JSON}"

将 console.host 设置为本地 global 控制平面 VIP,即 ${CONTROL_PLANE_VIP}。不要在 console.host 中使用 platform 域名;请使用 console.globalHost 作为 platform 访问地址。cluster.features.ha.vip 使用相同的地址。

第三方 Console 证书

示例使用自签名 Console 证书。如果环境要求使用第三方证书,请在提交安装程序请求前,将 console.cert 替换为 thirdParty 块,其中包含以 base64 编码的完整证书链、私钥以及可选的 PKCS#12 值。此证书仅用于 platform HTTPS 入口;不会配置由 KubeadmControlPlane 生成的 kube-apiserver 或 etcd 证书。端口 11443 上的 Global registry 使用独立管理的 global-registry-server 证书链,而不是 console.cert 或 dex.tls。

DR 证书要求

对于主/备用 Bare Metal global DR 部署,不要让两侧分别生成互不相关的自签名证书。请在两侧配置相同的受信任 thirdParty platform 证书。其必需的 SAN 是稳定的 ${PLATFORM_HOST} 域名。默认情况下,不要添加主集群或备用集群控制平面 VIP,也不要添加内部 Service 名称。只有当客户端有意通过该 VIP 直接访问 platform HTTPS 时,才添加 VIP SAN。通过端口 11443 访问 Registry 不使用此证书;Registry 证书管理是独立的,不属于此证书步骤。交接给 https://<control-plane-vip>:6443 的 Global 主机将验证独立的 kube-apiserver 证书和 CA,而不是 console.cert 或 dex.tls。

步骤 9 — 监控安装

安装程序接受请求后,安装会经过多个阶段;这些阶段可从 bootstrap 节点观察。Alauda OS 上一个典型的 global 集群需要 30–60 分钟;总耗时取决于 IaaS 预配速度、镜像拉取时间以及所选插件数量。

可观察到的阶段

阶段发生的情况首先查看的位置
Bootstrapbootstrap 集群、嵌入式 Registry 和 Cluster API provider 在 bootstrap 节点上运行。已在步骤 2 和步骤 3 中完成。bootstrap 节点终端;kubectl get pods -n cpaas-system
基础设施预配Cluster API provider 根据 Alauda OS 模板在目标 IaaS 平台上创建 VM。kubectl get machines -n cpaas-system
控制平面引导KubeadmControlPlane 引导第一个控制平面节点,etcd 启动,其他控制平面节点加入。kubectl get kubeadmcontrolplane -n cpaas-system
网络和核心附加组件CAPI provider 在新集群上协调 Kube-OVN、CoreDNS 和 kube-proxy。kubectl --kubeconfig <global-kubeconfig> get pods -n kube-system
Platform 安装安装程序将 Cluster API 资源导入新的 global 集群,部署基础 operator,并安装所选插件。安装程序进度 API;安装程序日志
完成安装程序将请求标记为 Success,并将最终集群状态写入 ClusterModule/global。安装程序进度 API;kubectl --kubeconfig <global-kubeconfig> get clustermodule global

安装期间的信号

同时查看安装程序进度 API 和安装程序日志。如果其中一项看起来停滞,请直接在 bootstrap 节点上检查底层 Cluster API 资源。

# Installer progress and live log
curl "http://${INSTALLER_IP}:8080/cpaas-installer/api/progress"
tail -f /var/cpaas/data/installer.log

# Cluster API resources on the bootstrap node
kubectl get clusters.cluster.x-k8s.io -A
kubectl get kubeadmcontrolplane -A
kubectl get machines -A

安装程序日志会记录每次阶段转换。临时错误会按较短的时间间隔重试;持久性错误会保留在日志中,并在进度 API 中显示为停滞阶段。

安装程序报告成功后,检查 global 集群。

kubectl --kubeconfig <global-kubeconfig> get nodes
kubectl --kubeconfig <global-kubeconfig> get pods -n cpaas-system
kubectl --kubeconfig <global-kubeconfig> get clustermodule global

常见停滞情况及查看位置

症状首先查看的位置要查找的内容
机器保持在 Pending,或未出现kubectl describe machine -n cpaas-system <machine>机器 Bootstrap 和 Infrastructure 条件中的 provider 特定故障原因。IaaS 配额、网络和凭证问题会在此处显示。
KubeadmControlPlane 未达到 Ready使用新集群 kubeconfig 的 kubectl get nodes 和 kubectl describe kubeadmcontrolplane -n cpaas-system第一个控制平面节点上的 etcd 健康状况,以及其余节点的加入进度。
kube-system 中的 Pod 保持在 Pending,或无法拉取镜像kubectl --kubeconfig <global-kubeconfig> describe pod -n kube-system <pod>镜像拉取错误通常表示面向节点的 Registry 地址无法从新集群的子网访问。
安装程序进度 API 显示阶段停滞/var/cpaas/data/installer.log最近的阶段行和最近的错误消息。重试错误会按较短的时间间隔重复出现;持久性错误不会推进。
ClusterModule/global 未达到健康阶段kubectl --kubeconfig <global-kubeconfig> describe clustermodule globalStatus.conditions 会说明哪个模块阻止集群完成。

此处未列出的问题通常与环境特定原因有关。请收集安装程序日志、进度 API 响应以及相关的 kubectl describe 输出,然后进行升级处理。

可选的灾难恢复部署

当您为灾难恢复部署主集群和备用 global 集群时,请使用本节。在应用每个 global 集群的 provider 特定清单之前,请先完成这些附加步骤。

开始之前,请完成双向 DR 网络要求。在两个集群的 VIP 都配置了所需的负载均衡器侦听器,且集群间网络规则允许所需流量双向传输之前,请勿启动任何一侧的安装。正常运行期间的流量方向是从备用集群到主集群;故障转移后方向会反转。

Bare Metal Fresh-install Scope

本节中的 Bare Metal 拆分身份验证操作步骤仅适用于全新安装主集群和备用 global 集群。本节不定义或验证现有 Bare Metal DR 集群对的原地升级或迁移,也不涵盖故障环境的修复或恢复。对于这些生命周期任务,请使用经过单独验证的运维手册。

以下并非每个小节都适用于所有 provider。请完成适用于您基础设施的小节:

小节Huawei DCSVMware vSphereHuawei Cloud StackBare Metal
准备共享 DR 变量✓✓✓✓
Bare Metal:准备共享 ServiceAccount 签名密钥———✓
添加 DR etcd Server 证书 SAN✓✓✓✓
添加 provider 特定的 DR 字段✓✓✓✓
安装主集群和备用集群✓✓✓✓
Bare Metal:在弃用前进行验证———✓
Bare Metal:应用规则 ConfigMap———✓
安装 etcd-sync✓✓✓✓
Bare Metal:验证动态规则来源———✓
启动并验证同步✓✓✓✓

将仅适用于 Bare Metal 的步骤应用于其他 provider,会更改该 provider 不期望出现的对象——尤其是不要将 Bare Metal KubeadmControlPlane 签名密钥 files 条目添加到 DCS、VMware vSphere 或 Huawei Cloud Stack 清单中。

所有 provider:主集群和备用集群必须使用相同的加密 provider 配置。对于 DCS 和 Bare Metal,provider 特定的集群资源会引用包含 encryption-provider.conf 的 Secret;对于 HCS,常规非 DR 部署不会将 /etc/kubernetes/encryption-provider.conf 添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.files。VMware vSphere 保留发布清单中的 /etc/kubernetes/encryption-provider.conf 文件条目。

仅适用于 Bare Metal:主集群和备用集群还必须使用相同的 Kubernetes ServiceAccount 签名密钥,以便主集群上创建的固定 baremetal-system-agent 令牌在故障转移后能被备用 API server 接受。组成每个 global 集群的机器仍使用各自集群本地的身份。

准备共享 DR 变量

在主集群和备用集群的安装环境中设置相同的加密密钥值。

export ENCRYPTION_PROVIDER_CONF="/root/yamls/encryption-provider.conf"
export ENCRYPTION_PROVIDER_SECRET_B64="<base64-shared-etcd-encryption-key>"
export PRIMARY_CLUSTER_VIP="<primary-ha-vip>"
export STANDBY_CLUSTER_VIP="<standby-ha-vip>"
export DCS_ENCRYPTION_PROVIDER_SECRET="encryption-provider-config"
export BAREMETAL_ENCRYPTION_PROVIDER_SECRET="global-encryption-provider-config"
export SERVICE_ACCOUNT_ISSUER="https://kubernetes.default.svc.cluster.local"

在两个安装环境中创建加密 provider 配置文件。

mkdir -p "$(dirname "${ENCRYPTION_PROVIDER_CONF}")"
cat > "${ENCRYPTION_PROVIDER_CONF}" <<EOF_CONF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: ${ENCRYPTION_PROVIDER_SECRET_B64}
EOF_CONF

Bare Metal:准备共享 ServiceAccount 签名密钥

Bare Metal only

只有 Bare Metal 需要共享 ServiceAccount 签名密钥。Huawei DCS、VMware vSphere 和 Huawei Cloud Stack 部署请跳过本节——它们共享加密 provider 配置,但两侧各自保留自己的签名密钥。不要将这些 files 条目添加到非 Bare Metal 的 KubeadmControlPlane 中。

仅生成一次 ServiceAccount 签名密钥,并在主集群和备用 KubeadmControlPlane 清单中使用相同的文件。只有两侧使用相同密钥签名时,备用 API server 才会接受在主集群上创建的固定 baremetal-system-agent 令牌。

mkdir -p /root/global-dr-sa
openssl genrsa -out /root/global-dr-sa/sa.key 2048
openssl rsa -in /root/global-dr-sa/sa.key -pubout -out /root/global-dr-sa/sa.pub
chmod 0600 /root/global-dr-sa/sa.key
chmod 0644 /root/global-dr-sa/sa.pub

kubectl -n cpaas-system create secret generic global-sa-signing-key \
  --from-file=sa.key=/root/global-dr-sa/sa.key \
  --from-file=sa.pub=/root/global-dr-sa/sa.pub \
  --dry-run=client -o yaml | kubectl apply -f -

将以下条目添加到主集群和备用集群的 KubeadmControlPlane.spec.kubeadmConfigSpec 中。两侧的文件内容以及 issuer/audience 值必须完全一致。

files:
  - path: /etc/kubernetes/pki/sa.key
    owner: root:root
    permissions: "0600"
    contentFrom:
      secret:
        name: global-sa-signing-key
        key: sa.key
  - path: /etc/kubernetes/pki/sa.pub
    owner: root:root
    permissions: "0644"
    contentFrom:
      secret:
        name: global-sa-signing-key
        key: sa.pub
clusterConfiguration:
  apiServer:
    extraArgs:
      service-account-key-file: /etc/kubernetes/pki/sa.pub
      service-account-signing-key-file: /etc/kubernetes/pki/sa.key
      service-account-issuer: https://kubernetes.default.svc.cluster.local
      api-audiences: https://kubernetes.default.svc.cluster.local
  controllerManager:
    extraArgs:
      service-account-private-key-file: /etc/kubernetes/pki/sa.key

集群安装完成后,在两侧各选取一个控制平面节点,验证文件和 kubeadm 静态 Pod 参数。

sha256sum /etc/kubernetes/pki/sa.key /etc/kubernetes/pki/sa.pub
grep -E 'service-account-issuer|api-audiences|service-account-key-file|service-account-signing-key-file' \
  /etc/kubernetes/manifests/kube-apiserver.yaml
grep -E 'service-account-private-key-file' \
  /etc/kubernetes/manifests/kube-controller-manager.yaml

将 DR etcd Server 证书 SAN 添加到 KubeadmControlPlane

在第 4 步生成的清单中,将您正在安装的集群的控制平面 VIP、平台访问地址和 etcd.kube-system 添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.clusterConfiguration.etcd.local.serverCertSANs 中。每个安装环境使用自己的列表:主集群仅列出 ${PRIMARY_CLUSTER_VIP},备用集群仅列出 ${STANDBY_CLUSTER_VIP}。这些值用于配置 kubeadm 生成的 etcd server 证书;它们独立于平台 console.cert,不得复制到其 thirdParty SAN 列表中。

Primary installation environment
serverCertSANs:
  - "${PRIMARY_CLUSTER_VIP}"
  - "${PLATFORM_HOST}"
  - "etcd.kube-system"
Standby installation environment
serverCertSANs:
  - "${STANDBY_CLUSTER_VIP}"
  - "${PLATFORM_HOST}"
  - "etcd.kube-system"

添加 provider 特定的 DR 字段

Huawei DCS
VMware vSphere
Huawei Cloud Stack
Bare Metal

在两个安装环境中,分别在 bootstrap 集群中创建加密 provider Secret。该 Secret 必须与 DCSCluster 位于同一命名空间,并且必须包含一个名为 encryption-provider.conf 的键。

kubectl create secret generic "${DCS_ENCRYPTION_PROVIDER_SECRET}" \
  --from-file=encryption-provider.conf="${ENCRYPTION_PROVIDER_CONF}" \
  -n cpaas-system \
  --dry-run=client -o yaml | kubectl apply -f -

在 DCSCluster.spec 中添加对该 Secret 的引用。

encryptionProviderConfigRef:
  name: encryption-provider-config

DCS 使用 DCSCluster.spec.encryptionProviderConfigRef 来传递灾难恢复加密 provider 配置。每次构建控制平面 bootstrap 数据时,DCS provider 都会读取此 Secret。对于 DCS DR 路径,不要将 /etc/kubernetes/encryption-provider.conf 添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.files 中。

还必须将此 Secret 包含在第 7 步的 DCS dcs-import-extra-resources ConfigMap 中。它不能只留在 bootstrap 集群中,因为移交后的 global 集群会保留 DCSCluster 对象及其 encryptionProviderConfigRef,并且还必须拥有被引用的 Secret,以供后续 provider 调和使用。内置的 DCS 迁移流程会迁移 Cluster API 资源,但不会复制此 Secret。

在两个安装环境中创建 DCS dcs-import-extra-resources ConfigMap,并使用第 7 步中 ConfigMap 的 DR 形式,而不是常规形式。将 PROVIDER_SECRET_NAME 设置为 DCSCluster.spec.credentialSecretRef.name 使用的同一 Secret 名称,并确保 DCS_ENCRYPTION_PROVIDER_SECRET 与 DCSCluster.spec.encryptionProviderConfigRef.name 保持一致。

缺少 Secret 不会立即报错,而是在替换控制平面机器时才会失败

设置 DCSCluster.spec.encryptionProviderConfigRef 后,DCS provider 会要求存在被引用的 Secret,不会回退到生成或读取配置。不会创建控制平面机器的调和不受影响,因此,如果 Secret 留在 bootstrap 集群中,安装看起来会正常运行,直到需要新的控制平面机器时才会出问题——例如 Kubernetes 升级、KubeadmControlPlane 滚动更新或控制平面节点替换。随后,新建的 Machine 会停留在 Provisioning 中,且不会创建 VM;provider 日志会记录 failed to create vm: failed to resolve user data: get encryption provider config secret。

检查现有 DR 对的两侧;如果缺少 Secret,请补充创建:

kubectl --kubeconfig <global-kubeconfig> -n cpaas-system get dcscluster global \
  -o jsonpath='{.spec.encryptionProviderConfigRef.name}{"\n"}'
kubectl --kubeconfig <global-kubeconfig> -n cpaas-system get secret <name>

使用安装时所用的同一 encryption-provider.conf 重新创建该 Secret,以确保新控制平面机器继续使用运行中的 API 服务器已在使用的密钥。如果该文件已不在 bootstrap 节点上,请从同一集群中正在运行的控制平面节点复制它(/etc/kubernetes/encryption-provider.conf)。不同的密钥会导致无法解密 DR 对已有的 etcd 数据。

安装主集群和备用集群

对主、备用 global 集群分别执行第 1 步至第 9 步。

使用两个独立的 bootstrap 节点

对每个 provider 使用两个独立的 bootstrap 节点,一个用于主集群安装,一个用于备用集群安装。不要在两侧重复使用同一个 bootstrap 集群:在移交之前,bootstrap 环境承载着安装程序、内嵌 Registry,以及其所安装的 global 集群的 provider 资源;对于 Bare Metal,还包括 AppRelease 对象、Registry Secret、MachineRegistration、SeedImage 和移交状态。共用一个 bootstrap 环境会污染两次 global 安装,并可能导致移交或清理操作作用于错误的一侧。

两侧均使用 provider 特定的安装程序配置差异:

Provider主集群安装备用集群安装
Huawei DCS将 console.host 设置为主控制平面 VIP。cluster.features.ha.vip 使用相同地址。根据第 7 步创建 DCS dcs-import-extra-resources ConfigMap,并确保 PROVIDER_SECRET_NAME 与 DCSCluster.spec.credentialSecretRef.name 保持一致,且 DCS_ENCRYPTION_PROVIDER_SECRET 与 DCSCluster.spec.encryptionProviderConfigRef.name 保持一致。将 console.host 设置为备用控制平面 VIP。cluster.features.ha.vip 使用相同地址。根据第 7 步创建 DCS dcs-import-extra-resources ConfigMap,并确保 PROVIDER_SECRET_NAME 与 DCSCluster.spec.credentialSecretRef.name 保持一致,且 DCS_ENCRYPTION_PROVIDER_SECRET 与 DCSCluster.spec.encryptionProviderConfigRef.name 保持一致。
VMware vSphere将 console.host 设置为主控制平面 VIP。cluster.features.ha.vip 和 VSphereCluster.spec.controlPlaneEndpoint.host 使用相同地址。根据第 7 步创建 VMware vSphere dcs-import-extra-resources ConfigMap,并确保 global-vsphere-credentials 与 VSphereCluster.spec.identityRef.name 保持一致。将 console.host 设置为备用控制平面 VIP。cluster.features.ha.vip 和 VSphereCluster.spec.controlPlaneEndpoint.host 使用相同地址。根据第 7 步创建 VMware vSphere dcs-import-extra-resources ConfigMap,并确保 global-vsphere-credentials 与 VSphereCluster.spec.identityRef.name 保持一致。
Huawei Cloud Stack将 console.host 设置为主控制平面 VIP。HCSCluster.spec.controlPlaneLoadBalancer.vipAddress 使用相同地址。根据第 7 步创建 HCS dcs-import-extra-resources ConfigMap,并确保 HCS_SECRET_NAME 与 HCSCluster.spec.identityRef.name 保持一致。将 console.host 设置为备用控制平面 VIP。HCSCluster.spec.controlPlaneLoadBalancer.vipAddress 使用相同地址。根据第 7 步创建 HCS dcs-import-extra-resources ConfigMap,并确保 HCS_SECRET_NAME 与 HCSCluster.spec.identityRef.name 保持一致。
Bare Metal将 console.host 设置为主控制平面 VIP。cluster.features.ha.vip、BaremetalCluster.spec.controlPlaneLoadBalancer.host 和 handoffHook.controlPlaneVIP 使用相同地址。设置 handoffHook.directAPIServer: true、elemental.systemAgent.splitAuthEnabled: true 和 elemental.systemAgent.sharedAuthReadOnly: false。根据第 7 步创建 Bare Metal dcs-import-extra-resources ConfigMap。将 console.host 设置为备用控制平面 VIP。cluster.features.ha.vip、BaremetalCluster.spec.controlPlaneLoadBalancer.host 和 handoffHook.controlPlaneVIP 使用相同地址。设置 handoffHook.directAPIServer: true、elemental.systemAgent.splitAuthEnabled: true 和 elemental.systemAgent.sharedAuthReadOnly: true。根据第 7 步创建 Bare Metal dcs-import-extra-resources ConfigMap。

对于主集群,确保平台域名解析到主控制平面 VIP。在第 8 步中,将 hostIP 设置为主 bootstrap 节点 IP,并将 console.host 以及上表列出的 provider 字段设置为主控制平面 VIP。

主集群安装成功后,按照 DR 操作步骤将平台域名切换到备用控制平面 VIP。然后安装备用集群。备用集群安装前必须进行此 DNS 切换,因为多个平台资源会使用平台域名进行渲染,因此备用安装程序运行期间,该域名必须解析到备用入口。在备用 bootstrap 节点上执行第 8 步时,将 hostIP 设置为备用 bootstrap 节点 IP。将 console.host 以及上表列出的 provider 字段设置为备用控制平面 VIP。对于 Bare Metal,保持 REGISTRY_DOMAIN 为 ${PLATFORM_HOST}:11443,即与主集群安装相同的值。从备用 bootstrap 节点上的 cpaas-installer Pod 获取 INSTALLER_IP;不要复用主 bootstrap 节点的值。

Bare Metal:在移除任一 bootstrap 集群前进行验证

仅适用于 Bare Metal

从此处到本小节结束的所有内容仅适用于 Bare Metal 路径。Huawei DCS、 VMware vSphere 和 Huawei Cloud Stack 部署在两个安装均报告成功后, 继续执行 安装 etcd-sync。

在安装 etcd-sync 之前,验证每个最终 global 集群上的 Registry 传播情况。将 ProductBase.spec.registry.preferPlatformURL 保持为安装程序生成的值。两侧的 spec.registry.address 和两个 Cluster 注解中都必须报告相同的 ${PLATFORM_HOST}:11443 值;只有该值一致, Global Cluster Disaster Recovery 才能复制这些键而不覆盖任何内容。

verify_baremetal_global_registry() {
  kubeconfig=$1
  expected_registry=$2

  test "$(kubectl --kubeconfig "${kubeconfig}" \
    get productbase.product.alauda.io base \
    -o jsonpath='{.spec.registry.address}')" = "${expected_registry}"
  test "$(kubectl --kubeconfig "${kubeconfig}" \
    get cluster.platform.tkestack.io global \
    -o jsonpath='{.metadata.annotations.cpaas\.io/registry-address}')" = \
    "${expected_registry}"
  test "$(kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
    get cluster.cluster.x-k8s.io global \
    -o jsonpath='{.metadata.annotations.cpaas\.io/registry-address}')" = \
    "${expected_registry}"
  test "$(kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
    get secret public-registry-credential -o jsonpath='{.data.registry}' | \
    base64 -d)" = "${expected_registry}"

  # Only deployments that enabled Registry authentication have this Secret. On an
  # anonymous Registry it does not exist, and checking it unconditionally aborts
  # the whole function under `set -e` before the remaining assertions run.
  if kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
    get secret global-registry-auth >/dev/null 2>&1; then
    kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
      get secret global-registry-auth -o jsonpath='{.data.\.dockerconfigjson}' | \
      base64 -d | jq -e --arg registry "${expected_registry}" \
        '.auths[$registry] != null' >/dev/null
  fi

  kubectl --kubeconfig "${kubeconfig}" get apprelease -A -o json | \
    jq -e --arg registry "${expected_registry}" '
      all(.items[];
        .spec.source.repoURL == $registry and
        .spec.values.global.registry.address == $registry)
    ' >/dev/null
}

export GLOBAL_REGISTRY_ADDRESS="${PLATFORM_HOST}:11443"

verify_baremetal_global_registry \
  "${PRIMARY_GLOBAL_KUBECONFIG}" "${GLOBAL_REGISTRY_ADDRESS}"
verify_baremetal_global_registry \
  "${STANDBY_GLOBAL_KUBECONFIG}" "${GLOBAL_REGISTRY_ADDRESS}"

在移除任一 bootstrap KIND 集群前,先在两侧完成完整的移交检查。在该侧已安装的 Global 机器中,GLOBAL_HOSTS 是以空格分隔的管理地址列表。请在能够使用两个 Global kubeconfig 并通过 SSH 连接到这些机器的安全主机上运行此操作。由于运行时连接文件包含 bearer token,请保持 shell 跟踪禁用。

set -euo pipefail
set +x

export PRIMARY_GLOBAL_KUBECONFIG="<path-to-primary-global-kubeconfig>"
export STANDBY_GLOBAL_KUBECONFIG="<path-to-standby-global-kubeconfig>"
export PRIMARY_BOOTSTRAP_KUBECONFIG="<path-to-primary-bootstrap-kubeconfig>"
export STANDBY_BOOTSTRAP_KUBECONFIG="<path-to-standby-bootstrap-kubeconfig>"
export PRIMARY_GLOBAL_HOSTS="<primary-global-host-1> <primary-global-host-2> <primary-global-host-3>"
export STANDBY_GLOBAL_HOSTS="<standby-global-host-1> <standby-global-host-2> <standby-global-host-3>"
export PRIMARY_GLOBAL_REGISTRATION_NAME="<primary-global-machine-registration-name>"
export STANDBY_GLOBAL_REGISTRATION_NAME="<standby-global-machine-registration-name>"

verify_global_auth_scope() {
  kubeconfig=$1
  registration_name=$2

  kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
    get machineregistration.elemental.cattle.io "${registration_name}" -o json | \
  jq -e '
    .metadata.annotations["baremetal.cluster.io/system-agent-auth-scope"] ==
      "global"
  ' >/dev/null

  global_inventory_names="$({
    kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
      get baremetalmachines.infrastructure.cluster.x-k8s.io -o json | \
    jq -c '[
      .items[]
      | select(
          .metadata.labels["cluster.x-k8s.io/cluster-name"] == "global"
        )
      | .status.machineInventoryRef.name? // empty
    ] | unique | sort'
  })"
  printf '%s\n' "${global_inventory_names}" | \
    jq -e 'length > 0' >/dev/null

  while IFS= read -r inventory_name; do
    kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
      get machineinventory.elemental.cattle.io "${inventory_name}" -o json | \
    jq -e '
      .metadata.annotations["baremetal.cluster.io/system-agent-auth-scope"] ==
        "global"
    ' >/dev/null
  done < <(printf '%s\n' "${global_inventory_names}" | jq -r '.[]')
}

verify_baremetal_handoff() {
  bootstrap_kubeconfig=$1
  kubeconfig=$2
  control_plane_vip=$3
  global_hosts=$4
  registration_name=$5
  expected_endpoint="https://${control_plane_vip}:6443"

  kubectl --kubeconfig "${bootstrap_kubeconfig}" -n cpaas-system \
    wait --for=condition=complete \
    job/baremetal-system-agent-handoff --timeout=30m

  kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
    get configmap baremetal-system-agent-handoff -o json | \
    jq -e '.data.ready == "true"' >/dev/null

  verify_global_auth_scope "${kubeconfig}" "${registration_name}"

  if kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
    get rolebinding baremetal-global-system-agent-handoff-bridge \
    >/dev/null 2>&1; then
    echo "temporary handoff bridge still exists" >&2
    return 1
  fi

  kubectl --kubeconfig "${kubeconfig}" -n cpaas-system get \
    serviceaccount/baremetal-global-system-agent \
    secret/baremetal-global-system-agent-token \
    role/baremetal-global-system-agent \
    rolebinding/baremetal-global-system-agent >/dev/null

  global_plan_names="$({
    kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
      get role baremetal-global-system-agent -o json | \
    jq -c '[
      .rules[]?
      | select(any(.apiGroups[]?; . == ""))
      | select(any(.resources[]?; . == "secrets"))
      | .resourceNames[]?
    ] | unique | sort'
  })"
  printf '%s\n' "${global_plan_names}" | jq -e 'length > 0' >/dev/null

  while IFS= read -r plan_secret; do
    test "$({
      kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
        get secret "${plan_secret}" -o jsonpath='{.type}'
    })" = "elemental.cattle.io/plan"
  done < <(printf '%s\n' "${global_plan_names}" | jq -r '.[]')

  expected_token_sha="$({
    kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
      get secret baremetal-global-system-agent-token \
      -o jsonpath='{.data.token}' | base64 -d | \
    sha256sum | awk '{print $1}'
  })"
  expected_ca_sha="$({
    kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
      get secret baremetal-global-system-agent-token \
      -o jsonpath='{.data.ca\.crt}' | base64 -d | \
    sha256sum | awk '{print $1}'
  })"

  for host in ${global_hosts}; do
    actual_endpoint="$({
      ssh "root@${host}" \
        'cat /var/lib/elemental/agent/elemental_connection.json' | \
      jq -r '.kubeConfig' | \
      awk '$1 == "server:" {print $2; exit}'
    })"
    test "${actual_endpoint}" = "${expected_endpoint}"

    actual_namespace="$({
      ssh "root@${host}" \
        'cat /var/lib/elemental/agent/elemental_connection.json' | \
      jq -r '.namespace'
    })"
    test "${actual_namespace}" = "cpaas-system"

    actual_plan_secret="$({
      ssh "root@${host}" \
        'cat /var/lib/elemental/agent/elemental_connection.json' | \
      jq -r '.secretName'
    })"
    printf '%s\n' "${global_plan_names}" | \
      jq -e --arg name "${actual_plan_secret}" 'index($name) != null' \
      >/dev/null

    actual_token_sha="$({
      ssh "root@${host}" \
        'cat /var/lib/elemental/agent/elemental_connection.json' | \
      jq -r '.kubeConfig' | \
      awk '$1 == "token:" {print $2; exit}' | \
      tr -d '\r\n' | sha256sum | awk '{print $1}'
    })"
    test "${actual_token_sha}" = "${expected_token_sha}"

    actual_ca_sha="$({
      ssh "root@${host}" \
        'cat /var/lib/elemental/agent/elemental_connection.json' | \
      jq -r '.kubeConfig' | \
      awk '$1 == "certificate-authority-data:" {print $2; exit}' | \
      base64 -d | sha256sum | awk '{print $1}'
    })"
    test "${actual_ca_sha}" = "${expected_ca_sha}"

    ssh "root@${host}" \
      "grep -Fq -- '${expected_endpoint}' /oem/elemental-system-agent.yaml"
    ssh "root@${host}" \
      'test "$(stat -c %a /oem/elemental-system-agent.yaml)" = 600'
    ssh "root@${host}" \
      'grep -Fqx -- "CATTLE_AGENT_STRICT_VERIFY=\"true\"" /etc/rancher/elemental/agent/envs'
    ssh "root@${host}" \
      'systemctl is-active --quiet elemental-system-agent.service'
  done
}

verify_baremetal_handoff \
  "${PRIMARY_BOOTSTRAP_KUBECONFIG}" "${PRIMARY_GLOBAL_KUBECONFIG}" \
  "${PRIMARY_CLUSTER_VIP}" \
  "${PRIMARY_GLOBAL_HOSTS}" "${PRIMARY_GLOBAL_REGISTRATION_NAME}"
verify_baremetal_handoff \
  "${STANDBY_BOOTSTRAP_KUBECONFIG}" "${STANDBY_GLOBAL_KUBECONFIG}" \
  "${STANDBY_CLUSTER_VIP}" \
  "${STANDBY_GLOBAL_HOSTS}" "${STANDBY_GLOBAL_REGISTRATION_NAME}"

还要验证两侧 Global 本地的权限边界。在首个非 global workload plan 存在之前,共享的 baremetal-system-agent ServiceAccount、token Secret、Role 和 RoleBinding 在两个集群上都可能不存在。不要仅为满足 Global 安装检查而手动创建或复制该资源集;当首个共享范围的注册被协调时,活动 operator 会创建该资源集,随后由 etcd-sync 将其复制到备用集群。

verify_global_local_system_agent_rbac() {
  kubeconfig=$1

  kubectl --kubeconfig "${kubeconfig}" -n cpaas-system get \
    serviceaccount/baremetal-global-system-agent \
    secret/baremetal-global-system-agent-token \
    role/baremetal-global-system-agent \
    rolebinding/baremetal-global-system-agent >/dev/null

  local_plan_names="$({
    kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
      get role baremetal-global-system-agent -o json | \
    jq -c '[
      .rules[]?
      | select(any(.apiGroups[]?; . == ""))
      | select(any(.resources[]?; . == "secrets"))
      | .resourceNames[]?
    ] | unique | sort'
  })"
  printf '%s\n' "${local_plan_names}" | jq -e 'length > 0' >/dev/null

  expected_local_plan_names="$({
    kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
      get machineinventories.elemental.cattle.io -o json | \
    jq -c '[
      .items[]
      | select(
          .metadata.annotations["baremetal.cluster.io/system-agent-auth-scope"] ==
            "global"
        )
      | .status.plan.secretRef.name? // empty
    ] | unique | sort'
  })"
  test "${local_plan_names}" = "${expected_local_plan_names}"

  while IFS= read -r plan_secret; do
    test "$({
      kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
        get secret "${plan_secret}" -o jsonpath='{.type}'
    })" = "elemental.cattle.io/plan"

    test "$({
      kubectl --kubeconfig "${kubeconfig}" \
        --as=system:serviceaccount:cpaas-system:baremetal-global-system-agent \
        -n cpaas-system auth can-i get "secret/${plan_secret}"
    })" = yes
    test "$({
      kubectl --kubeconfig "${kubeconfig}" \
        --as=system:serviceaccount:cpaas-system:baremetal-global-system-agent \
        -n cpaas-system auth can-i patch "secret/${plan_secret}"
    })" = yes
    test "$({
      kubectl --kubeconfig "${kubeconfig}" \
        --as=system:serviceaccount:cpaas-system:baremetal-system-agent \
        -n cpaas-system auth can-i get "secret/${plan_secret}"
    })" = no
  done < <(printf '%s\n' "${local_plan_names}" | jq -r '.[]')

  for verb in list create delete; do
    test "$({
      kubectl --kubeconfig "${kubeconfig}" \
        --as=system:serviceaccount:cpaas-system:baremetal-global-system-agent \
        -n cpaas-system auth can-i "${verb}" secrets
    })" = no
  done

  test "$({
    kubectl --kubeconfig "${kubeconfig}" \
      --as=system:serviceaccount:cpaas-system:baremetal-global-system-agent \
      -n cpaas-system auth can-i get secret/global-registry-auth
  })" = no
}

verify_global_local_system_agent_rbac "${PRIMARY_GLOBAL_KUBECONFIG}"
verify_global_local_system_agent_rbac "${STANDBY_GLOBAL_KUBECONFIG}"

不要仅凭 ready 信号判定验收通过。如果运行时 token 或 CA 不匹配、持久化 OEM 文件中的 bootstrap 端点过期、plan 探测失败或权限边界不正确,则即使 data.ready 为 true,全新移交也尚未完成。

备用集群安装成功后,将平台域名切回主入口。首次同步期间,主集群是活动源。

Bare Metal:应用 Rule ConfigMap

仅适用于 Bare Metal

只有 Bare Metal 需要自定义 etcd-sync 规则源。Huawei DCS、VMware vSphere 和 Huawei Cloud Stack 部署可跳过本节,直接继续执行 安装 etcd-sync;其安装顺序不影响结果。

在安装 etcd-sync 之前,先在主集群和备用集群上应用此 ConfigMap。

将相同的 baremetal-dr-rules ConfigMap 应用到主集群和备用集群。使用 Bare Metal etcd-sync Rules 中的模板。将 plan 占位符替换为两个集群中当前所有 Global plan Secret 名称。两侧的移交都完成后,从 MachineInventory.status.plan.secretRef.name 读取这些名称;全新移交会创建 Global plan Secret,因此提前收集的名称已过期。插件 chart 会提供其余 Bare Metal 规则。每当添加或替换 Global plan Secret 时,都要更新此 ConfigMap。

在镜像启动前创建规则源

如果安装 etcd-sync 时已设置 active_cluster_vip 和活动集群 token,同步路径会立即开启。如果 Bare Metal 规则源尚不存在,首次同步将不会应用 Global plan Secret 排除规则,并会用主集群的 Secret 覆盖备用集群的 plan Secret——这是 故障排查 中列出的 A Global plan is overwritten 故障模式。随后,备用 Global 主机将收到属于主集群的 plans。

先创建 ConfigMap 不会产生副作用:插件安装之前,它只是一个带标签的普通 ConfigMap,不会被任何组件读取。如果必须先安装插件,请将 active_cluster_vip 留空,仅在规则源报告 accepted 后再设置。

安装 etcd-sync (ACP 4.4.0+)

仅在当前备用 Global 上安装 v4.4.0 或更高版本的插件。不要在两侧同时安装或运行镜像。

安装前,将平台域名切回主控制平面 VIP——安装备用集群时,域名曾指向备用集群。主集群是首次同步的活动源;插件启动时会解析平台域名,如果域名指向的集群本身运行着 etcd-sync,插件将拒绝启动。若域名仍指向备用集群,即将执行的安装会触发此检查。

插件包发布为 etcd-sync,而不是 global-etcd-sync:从软件包服务器下载 packages/etcd-sync/<minor>/etcd-sync.amd64.<version>.tgz。它不包含在安装程序 bundle 中,也不在 bootstrap Registry 内,因此应像其他插件包一样将其上传到目标集群。

配置如下:

参数值
活动 Global 集群 VIP活动 global 集群的控制平面 VIP
活动 Global 集群 ETCD Endpointshttps://<active-control-plane-vip>:2379;除非负载均衡器转发端口 2379
备用集群 ETCD Endpoints默认值
活动 Global 集群 Token Secretetcd-sync-active-cluster-token
数据检查间隔默认值
打印详细日志除故障排查外禁用

安装前,在备用集群上创建 token Secret。其值为活动集群 cpaas-system/k8sadmin ServiceAccount 的 bearer token:

# On the active cluster:
kubectl -n cpaas-system get secret k8sadmin -o jsonpath='{.data.token}' | base64 -d

# On the standby cluster, with the value copied from above:
ACTIVE_CLUSTER_TOKEN='<paste-the-token-from-the-active-cluster>'
kubectl -n cpaas-system create secret generic etcd-sync-active-cluster-token \
  --from-literal=token="${ACTIVE_CLUSTER_TOKEN}" \
  --dry-run=client -o yaml | kubectl apply -f -

安装期间,etcd-sync-bootstrap Job 会在 etcd-sync Deployment 启动之前运行。只有在 Job 准备好 remote-etcd-ca、remote-etcd-issuer 和 remote-etcd-client 后,插件安装才会继续。

Bare Metal:验证动态规则源

开启同步路径前,确认规则源报告 accepted,且出现在 ConfigMap/etcd-sync-rule-snapshot 中:

kubectl --kubeconfig <standby-kubeconfig> -n cpaas-system \
  get configmap baremetal-dr-rules \
  -o jsonpath='{.metadata.annotations.etcd-sync\.cpaas\.io/status}{"\n"}'
kubectl --kubeconfig <standby-kubeconfig> -n cpaas-system \
  get configmap etcd-sync-rule-snapshot \
  -o jsonpath='{.data.snapshot\.json}'

被拒绝的规则源不会处于活动状态。

启动并验证同步

触发一次 etcd-sync 监控检查。收到 HTTP 425 时重试,直到检查获准执行,然后要求遗漏键和多余键均为零。确认共享 workload system-agent 资源集要么在两侧都不存在,要么在两侧都完整存在;只在一侧存在或部分存在均视为失败。

不要仅凭 AppRelease 就绪状态作为 DR 验收结果。有关故障切换顺序和故障切换后的测试,请继续阅读 Global Cluster Disaster Recovery。

重启需要重新加载 DR 和端点配置的 Pod。在主控制平面节点和备用控制平面节点上运行相同的命令。

sudo kubectl delete po -n cpaas-system -l 'service_name in (alertmanager,vmselect,vminsert)'
sudo kubectl delete po -n cpaas-system -l service_name=cpaas-elasticsearch
sudo kubectl delete po -n cpaas-system -l service_name=cluster-transformer

有关安装后的 DR 生命周期,请参阅 Global Cluster Disaster Recovery。

验证

安装程序报告完成后,请按照 验证 中的说明,确认 global 集群运行正常,其中包括 Alauda OS 检查。

如果 global 集群安装在裸金属上,请接下来完成将 global 集群准备为管理集群。这是弃用 bootstrap 集群之前的必要步骤;交接完成后重新配置 global 节点时,会从 global Registry 拉取该节点上传的基础映像。

弃用 Bootstrap 集群

安装程序报告成功后,并且在裸金属环境中完成将 global 集群准备为管理集群后,global 集群便不再依赖 bootstrap 集群。常规验证以及下面针对特定 provider 的交接门槛均通过后,从 bootstrap 节点移除临时 bootstrap 集群——只删除本地 bootstrap 集群(minialauda)及其 KIND 容器网络。

裸金属:必须通过最终交接门槛

不要仅因为 minialauda 处于 Ready 状态、安装程序报告成功,或 baremetal-system-agent-handoff Job 处于 Complete 状态,就移除 KubeadmControlPlane。每台已导入的 Global 主机都必须先通过最终 global 集群执行的探测,之后才能停用 bootstrap 端点。

从新建的 global 集群读取最终交接记录:

kubectl --kubeconfig <global-kubeconfig> -n cpaas-system \
  get configmap baremetal-system-agent-handoff \
  -o jsonpath='{.data.ready}{"\n"}'

完成状态 ConfigMap 恰好包含一个键:ready。它仅用作安装程序清理门槛:记录交接和 global 侧探测已完成,不代表端点、TLS 输入或各主机最终使用的身份状态。不要手动修改 data.ready,也不要单独将其视为身份验证分离正确的证明——以下检查才能确认这一点。

只有满足以下所有条件后,才能继续:

  • 当前安装程序交接钩子使用已部署的 provider 配置运行了该 Job,且 Job 成功完成。
  • 在最终的 global 集群中,data.ready 为 true。
  • 在最终的 global 集群中,每个已导入的目标 MachineInventory 均报告 status.plan.state: Applied。这是 global 侧探测结果,也是证明每台主机现在都向新集群报告的检查项。
  • 最终集群中存在每个已导入的目标对象,以及该对象所引用的计划 Secret。
  • 最终集群中的 system-agent 身份与已部署的模式一致,并且临时跨身份 RoleBinding 已被移除。

使用以下命令检查最后三项条件:

kubectl --kubeconfig <global-kubeconfig> -n cpaas-system \
  get machineinventories.elemental.cattle.io \
  -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.plan.state}{"\n"}{end}'

kubectl --kubeconfig <global-kubeconfig> -n cpaas-system \
  get serviceaccount,secret,role,rolebinding \
  | grep -E 'baremetal-(global-)?system-agent'

kubectl --kubeconfig <global-kubeconfig> -n cpaas-system \
  get rolebinding baremetal-global-system-agent-handoff-bridge

对于 elemental.systemAgent.splitAuthEnabled: false,最终集群仅包含共享的 baremetal-system-agent 身份。对于 true,集群还包含 Global-local baremetal-global-system-agent 身份,并且两个 Role 不得指定同一个计划 Secret。最后一条命令必须报告 NotFound。

如果当前 Job 失败或超时、目标集不完整、缺少被引用的计划 Secret,或探测计划不是 Applied,请保持 minialauda 运行,并检查交接 Job 和 provider 日志。

确认 DCS Secret 已到达 global 集群

安装期间,你在步骤 7 中创建的 dcs-import-extra-resources ConfigMap 会将 DCS API 凭证 Secret 复制到 global 集群——它会导入 DCSCluster.spec.credentialSecretRef 中指定的 Secret。移除 bootstrap 节点之前,请确认该 Secret 已存在:kubectl --kubeconfig <global-kubeconfig> get secret <name> -n cpaas-system。如果缺少,请先将其复制过去——否则,你在 global 集群上安装的 DCS provider 将没有 DCS API 凭证,无法协调资源(例如,之后扩容会失败)。

对于 DR 安装,请对 DCSCluster.spec.encryptionProviderConfigRef 中指定的 Secret 执行相同检查。DCSCluster 对象会在交接后保留该引用,因此 bootstrap 集群移除后,如果缺少该 Secret,之后每次创建控制平面机器都会失败——请参阅添加特定于 provider 的 DR 字段。

不要通过删除 Cluster API 对象来清理

不要运行 kubectl delete cluster global,也不要将删除 Cluster、KubeadmControlPlane 或 provider 基础设施对象作为清理步骤。安装完成后,这些对象会管理正在运行的 global 控制平面机器;删除它们会连带删除控制平面 VM,并破坏刚安装的集群。弃用操作仅限于在 bootstrap 节点上移除本地 bootstrap 集群(其 KIND 容器);请保留 Cluster API 对象。

后续步骤

  • 在创建业务集群之前,请先在 global 集群上上传并安装目标 IaaS 平台的基础设施 provider 和 Cluster Bootstrap Provider,无论 global 集群运行在哪个 IaaS 平台上:请参阅准备。对于 DR 集群对,请在两个 global 集群上都执行此操作。
  • 配置业务集群的基础设施资源:请参阅基础设施资源。
  • 创建第一个业务集群:请参阅在 Alauda OS 上创建。
  • 规划灾难恢复:请参阅Global 集群灾难恢复。

示例:Huawei DCS global 完整清单

这是一个完整的单文件清单,用于在 Huawei DCS 上创建具有三个副本的控制平面 global 集群。它包含步骤 4 中所述的同一组资源,已整合在一起,因此你无需从不同页面合并片段。示例中使用的值仅用于文档演示:请替换每个 <placeholder>,并复用在步骤 1 中导出的 ${...} 变量。在步骤 5 中应用该清单。

此示例适用于非 DR 集群。为避免维护两份副本,这里不再重复 KubeadmControlPlane kubeadmConfigSpec 主体——它与业务集群相同,取自完整的 KubeadmControlPlane 配置附录,资源 3 中内联注明了两个 global / 非 DR 差异。

该清单不会创建 DCS API 凭证 Secret。该 Secret 已在上述先决条件中创建,并在此处通过 DCSCluster.spec.credentialSecretRef 按名称引用。步骤 7 会通过 dcs-import-extra-resources ConfigMap 将其复制到 global 集群;你必须确保其中的 names 列表与同一 Secret 名称保持一致。

应用前:在 DCS 上准备以下内容

清单会引用以下内容,但不会创建它们。请先按顺序准备好:

  1. DCS API 访问权限及据此创建的凭证 Secret——端点(https://<host>:7443)、一个 administrator 用户及其密码,以及站点 ID。四项信息都需写入凭证 Secret;如果该字段留空,provider 会从中读取站点 ID 并将其写入 DCSCluster.spec.site,因此下面的清单不会设置该字段。

    按照云凭证中的说明,在 cpaas-system 中创建名为 ${PROVIDER_SECRET_NAME} 的 Secret。不要将其编写为 YAML 后再应用:kubectl apply 会将密码记录在 Secret 的 kubectl.kubernetes.io/last-applied-configuration 注解中。

    global 安装还要求添加清单中所有其他资源都带有的集群名称标签:

    kubectl label secret "${PROVIDER_SECRET_NAME}" --namespace cpaas-system \
      --overwrite cpaas.io/cluster-name=global
  2. VM 模板——上传 Alauda OS 镜像并据此创建 VM 模板;记录其名称,以供 vmTemplateName 使用。请使用 4.2.1+ 模板,以便在替换节点期间可以分离并重新挂载 /var/cpaas 持久磁盘。请参阅机器模板。

  3. 计算集群、分布式虚拟交换机、端口组和数据存储——选择目标 DCS 计算集群、一个分布式虚拟交换机、一个端口组,以及具有足够可用容量的数据存储。它们分别对应 resource、dvSwitchName、portGroupName 和 datastoreName。请参阅DCS 平台容量与放置。

  4. IP 和 API 端点——为控制平面准备三个可用的节点 IP(包含网关和 DNS);它们对应 DCSIpHostnamePool。然后选择一个符合控制平面端点约定的外部 LoadBalancer,并将 TCP 6443 转发到这三个节点;或者为 Self-built VIP 选择同一 Layer 2 网络中的独立 IPv4 VIP。所选端点的主机和端口分别对应 DCSCluster.spec.controlPlaneLoadBalancer 和 controlPlaneEndpoint。请参阅在 Huawei DCS 上创建集群。

  5. 需要查阅的版本和 ID——${K8S_VERSION},以及从 cpaas.io/dcs-vm-template ConfigMap 获取的 CoreDNS 和 etcd 镜像标签(请参阅解析占位值);从OS 支持矩阵获取的 kube-ovn chart 版本;以及你在步骤 1 中导出的 registry 地址、VIP 和 CIDR 值。

准备好这些内容后,即可应用下面的清单。示例使用外部 LoadBalancer。如果选择 Self-built VIP,请在创建 global 集群之前,先应用清单之后所示的内部模式替换项。

---
# 1. Control-plane IP / hostname pool (one entry per control-plane replica).
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DCSIpHostnamePool
metadata:
  name: global-cp-pool
  namespace: cpaas-system
  labels:
    cpaas.io/cluster-name: "global"
spec:
  pool:
  # /var/cpaas holds platform state and must survive node replacement, so it is
  # declared here as a persistentDisk bound to the IP slot (not as a
  # DCSMachineTemplate disk). Requires a DCS VM template 4.2.1+ and maxSurge: 0.
  - ip: "192.0.2.11"
    mask: "24"
    gateway: "192.0.2.1"
    dns: "192.0.2.2"
    hostname: "global-cp-1"
    machineName: "global-cp-1"
    persistentDisk:
    - {slot: 0, quantityGB: 100, datastoreName: <datastore-name>, path: /var/cpaas, format: xfs, mountOptions: [defaults]}
  - ip: "192.0.2.12"
    mask: "24"
    gateway: "192.0.2.1"
    dns: "192.0.2.2"
    hostname: "global-cp-2"
    machineName: "global-cp-2"
    persistentDisk:
    - {slot: 0, quantityGB: 100, datastoreName: <datastore-name>, path: /var/cpaas, format: xfs, mountOptions: [defaults]}
  - ip: "192.0.2.13"
    mask: "24"
    gateway: "192.0.2.1"
    dns: "192.0.2.2"
    hostname: "global-cp-3"
    machineName: "global-cp-3"
    persistentDisk:
    - {slot: 0, quantityGB: 100, datastoreName: <datastore-name>, path: /var/cpaas, format: xfs, mountOptions: [defaults]}
---
# 2. Control-plane VM spec.
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DCSMachineTemplate
metadata:
  name: global-cp-template
  namespace: cpaas-system
  labels:
    cpaas.io/cluster-name: "global"
spec:
  template:
    spec:
      vmTemplateName: <vm-template-name>
      # Places the cloned VMs in a DCS compute cluster.
      resource:
        type: cluster
        name: <dcs-cluster-name>
      vmConfig:
        dvSwitchName: <dvswitch-name>
        portGroupName: <port-group-name>
        dcsMachineCpuSpec: {quantity: 16}
        dcsMachineMemorySpec: {quantity: 32768}   # MB
        dcsMachineDiskSpec:
        - {quantity: 0,   datastoreName: <datastore-name>, systemVolume: true}
        - {quantity: 10,  datastoreName: <datastore-name>, path: /var/lib/etcd,       format: xfs}
        - {quantity: 100, datastoreName: <datastore-name>, path: /var/lib/kubelet,    format: xfs}
        - {quantity: 100, datastoreName: <datastore-name>, path: /var/lib/containerd, format: xfs}
        # /var/cpaas is intentionally NOT a template disk — it is declared as a
        # persistentDisk on the IP pool above so it survives node replacement.
      ipHostPoolRef:
        name: global-cp-pool
---
# 3. Control plane.
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
  name: global-kcp
  namespace: cpaas-system
  labels:
    cpaas.io/cluster-name: "global"
  annotations:
    controlplane.cluster.x-k8s.io/skip-kube-proxy: ""
spec:
  replicas: 3
  version: ${K8S_VERSION}
  rolloutStrategy:
    type: RollingUpdate
    rollingUpdate: {maxSurge: 0}
  machineTemplate:
    nodeDrainTimeout: 1m
    nodeDeletionTimeout: 5m
    infrastructureRef:
      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
      kind: DCSMachineTemplate
      name: global-cp-template
  kubeadmConfigSpec:
    format: ignition
    users:
    - name: boot
      sshAuthorizedKeys:
      - "ssh-ed25519 AAAA...replace-with-your-public-key... global-boot"
    # The rest of kubeadmConfigSpec — files, clusterConfiguration,
    # preKubeadmCommands, postKubeadmCommands, initConfiguration,
    # joinConfiguration — is identical to a workload cluster, so it is not
    # duplicated here. Take it verbatim from the Complete KubeadmControlPlane
    # Configuration appendix in the DCS create-cluster guide
    # (#complete-kubeadmcontrolplane-configuration). The three large files
    # (psa-config.yaml, control-plane-kubelet-patch.json, audit-policy.yaml) may
    # use contentFrom the dcs-kubernetes-<major.minor>-files Secret. Apply these
    # global / non-DR deltas to that body:
    #   1. Add clusterConfiguration.etcd.local.serverCertSANs:
    #        ["${CONTROL_PLANE_VIP}", "${PLATFORM_HOST}"]
    #        A DR pair uses a different list — see Optional Disaster Recovery
    #        Deployment. etcd.kube-system belongs to that list, not this one.
    #   2. Omit the /etc/kubernetes/encryption-provider.conf files entry: the
    #        provider writes that file on control-plane nodes and drops any
    #        files entry for it. Keep apiServer.extraArgs.encryption-provider-
    #        config — see the note after this example.
---
# 4. DCS infrastructure cluster.
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DCSCluster
metadata:
  name: "global"
  namespace: cpaas-system
  labels:
    cpaas.io/cluster-name: "global"
  annotations:
    cpaas.io/registry-address: "${NODE_REGISTRY_ADDRESS}"
spec:
  controlPlaneLoadBalancer: {host: "${CONTROL_PLANE_VIP}", port: 6443, type: external}
  controlPlaneEndpoint: {host: "${CONTROL_PLANE_VIP}", port: 6443}
  credentialSecretRef: {name: "${PROVIDER_SECRET_NAME}"}   # created in the prerequisites above
  networkType: kube-ovn
  site: <dcs-site-id>
---
# 5. Top-level CAPI Cluster: global wiring, labels, and annotations.
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
  name: global
  namespace: cpaas-system
  labels:
    cpaas.io/cluster-name: "global"
    cluster-type: DCS
    is-global: "true"
  annotations:
    capi.cpaas.io/resource-group-version: infrastructure.cluster.x-k8s.io/v1beta1
    capi.cpaas.io/resource-kind: DCSCluster
    capi.cpaas.io/kubernetes: ${K8S_VERSION}   # same value as KubeadmControlPlane.spec.version
    cpaas.io/registry-address: "${NODE_REGISTRY_ADDRESS}"
    cpaas.io/nodes-mode: self-managed   # node lifecycle managed by CAPI + the DCS provider
    cpaas.io/kube-ovn-join-cidr: <kube-ovn-join-cidr>   # a /16 you choose; must not overlap the pod / service CIDRs or another cluster's join CIDR
    cpaas.io/kube-ovn-version: <kube-ovn-chart-version>
spec:
  clusterNetwork:
    pods:     {cidrBlocks: ["${CLUSTER_CIDR}"]}
    services: {cidrBlocks: ["${SERVICE_CIDR}"]}
  controlPlaneRef:
    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
    kind: KubeadmControlPlane
    name: global-kcp
  infrastructureRef:
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
    kind: DCSCluster
    name: global

内部 Self-built VIP 变体

上述示例使用 type: external。应用前,可以将资源 4 的端点字段替换为以下内部配置。VIP 必须预留在控制平面 Layer 2 网络中,并且 vrid 在该网络中必须唯一:

spec:
  controlPlaneLoadBalancer:
    host: "${CONTROL_PLANE_VIP}"
    port: 6443
    type: internal
    vrid: <unique-vrid-1-255>
    # interface: eth0  # optional; omit for provider/alive auto-detection
  controlPlaneEndpoint: {host: "${CONTROL_PLANE_VIP}", port: 6443}

需要替换的值

占位符 / 变量来源
${K8S_VERSION}、${CONTROL_PLANE_VIP}、${PLATFORM_HOST}、${NODE_REGISTRY_ADDRESS}、${CLUSTER_CIDR}、${SERVICE_CIDR}、${PROVIDER_SECRET_NAME}在步骤 1 中导出。
<dcs-api-user> / <dcs-api-password> / <dcs-api-host> / <dcs-site-id>DCS 平台凭证和站点信息。它们不会出现在清单中——而是写入用于在先决条件中创建 ${PROVIDER_SECRET_NAME} 的凭证文件。请参阅云凭证。
<vm-template-name>、<dns-image-tag>、<etcd-image-tag>cpaas.io/dcs-vm-template ConfigMap。请参阅解析占位值。
<dcs-cluster-name>、<dvswitch-name>、<port-group-name>、<datastore-name>DCS 平台对象。请向 DCS 管理员确认。
192.0.2.x、<kube-ovn-join-cidr>、<kube-ovn-chart-version>网络的节点 IP/网关/DNS;一个不会与 Pod / Service CIDR 或其他集群的 join CIDR 重叠的 /16 Kube-OVN join CIDR;以及从OS 支持矩阵获取的 kube-ovn chart 版本。
ssh-ed25519 AAAA...有效的 OpenSSH 公钥。ignition 格式不接受空列表;即使你不打算通过 SSH 登录,也请提供任意有效密钥。
Secret 加密与灾难恢复

此示例启用了 etcd 静态 Secret 加密。apiServer.extraArgs.encryption-provider-config 保留在你从附录复制的 clusterConfiguration 中,DCS provider 会提供它所指向的文件:为第一台控制平面机器生成一个随机密钥,之后的每台机器则使用正在运行的 API server 自己的文件。不要将 /etc/kubernetes/encryption-provider.conf 添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.files——provider 会丢弃指向该路径的任何条目。请参阅如何交付加密 provider 配置。

只有在你明确希望停用静态加密时,才移除此参数。对于 DR 集群对,两侧的密钥必须相同,因此必须按照可选的灾难恢复部署中的说明,使用 DCSCluster.spec.encryptionProviderConfigRef 固定该密钥。