安装 global 集群

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

对于部署在 Huawei DCS 上的 global 集群,请先查看 Alauda OS 和 Provider 兼容性,再选择 DCS Provider 软件包和 VM 模板。

何时使用此路径

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

  • 您希望 global 集群运行在不可变操作系统上。目前支持的镜像是 Alauda OS。
  • 您使用的是文档中列出的 Provider 之一:Huawei DCS、VMware vSphere、Huawei Cloud Stack 或裸金属。
  • 您可以运行一个能够访问目标 IaaS 平台的临时引导节点。

如果使用 Ubuntu 或 RHEL 等非 Alauda OS,请改用 标准安装路径。

通用先决条件

以下先决条件适用于所有 Provider:

  • 一个满足引导节点要求的引导节点。灾难恢复部署需要两个引导节点,分别用于主 global 集群和备用集群;请参阅安装主集群和备用集群。
  • 来自 Customer Portal 的 Core Package。
  • Alauda Container Platform Cluster Bootstrap Provider(以前称为 Alauda Container Platform Kubeadm Provider)软件包。
  • 目标平台对应的基础设施 Provider 软件包。
  • 引导节点与目标 IaaS 平台 API 端点之间的网络连通性。请参阅网络。
  • 为 global 控制平面和工作节点规划 IP 和主机名。请参阅基础设施资源,了解各 Provider 使用的资源模型。
  • 为 global 集群设置稳定的 Kubernetes API 端点。在创建集群前,请在规划控制平面端点中选择并验证特定于 Provider 的端点模式。
  • 平台访问地址、Registry 地址,以及 Pod 和 Service CIDR 范围。
  • 对于使用 ACP 提供的 Alauda OS 镜像的 x86_64 节点,底层 CPU 必须支持 x86-64-v2 ISA 基线。请参阅操作系统支持矩阵。
命名约定(必需)

此规则适用于此安装路径支持的所有基础设施 Provider——Huawei DCS、Huawei Cloud Stack、VMware vSphere,以及未来新增的任何 Provider。您在步骤 4 中编写的每个清单都必须遵循此规则。资源命名错误会导致两种不同的故障,具体说明如下:一种会导致初始置备失败,另一种仅会在灾难恢复期间显现。

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

引导节点要求

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

应根据它要提供的镜像负载来确定其规格,而非依据稳态负载:在集群置备期间,每个 global 节点都会从此主机拉取平台镜像集。

硬件

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

CPU 和内存的最低配置依据此安装路径的标准交付实践制定。在传统安装路径中,运行 setup.sh 的机器会成为第一个控制平面节点,因此必须满足 先决条件中的控制平面最低要求;而此处的引导节点不会加入集群——它只负责提取 Core Package、运行临时的基于 KIND 的引导集群,并在交接前提供平台 Registry。这种负载较轻且只是临时的,标准交付所采用并验证的配置为 4 核 / 8 GB。

对于 arm64,请遵循同一页面中的平台 ARM 约定:至少为 x86_64 配置的 1.5 倍,最好为 2 倍,因此最低配置为 6 核 / 12 GB,推荐配置为 8 核 / 16 GB。

磁盘空间是最常导致引导过程停滞的资源。请分三部分规划:

使用方空间
提取后的 Core Package提取后至少 100 GB。请参阅 安装
/var/cpaas 下的内嵌 Registry 数据目标版本的平台镜像负载,以及复制到 plugins/ 的 Aligned Extension 软件包
为上传暂存的 Provider 软件包和 OS 构件Kubeadm 和基础设施 Provider 软件包,以及每种节点架构各一个 Alauda OS 镜像或 VM 模板构件

操作系统

引导节点运行传统操作系统,而非 Alauda OS。它需要安装 64 位 Linux 发行版,提供 Bash 和 root 权限,以便 setup.sh 能够启动第 2 步所述的引导集群。

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

如果没有其他限制,仍可将经过验证列表中的发行版作为稳妥的默认选择。

x86-64-v2 基线适用于集群节点,而非引导节点

OS 支持矩阵中的 x86-64-v2 ISA 基线适用于由 ACP 提供的 Alauda OS 镜像创建的集群节点。对于运行用户提供的传统操作系统的 x86_64 机器,平台不强制要求满足此基线,因此引导节点本身无需满足该要求。请参阅 节点预处理。

如果引导节点是与集群节点位于同一基础设施上的虚拟机,检查其 CPU 标志可以及早发现基线缺失问题。但这不能替代对实际运行 Alauda OS 节点的主机执行 x86-64-v2 验证,因为虚拟机访客系统看到的标志取决于虚拟机管理程序的 CPU 模型。

网络

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

来源目标端口用途
引导节点目标 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 上创建集群
引导节点global 控制平面端点TCP/6443创建控制平面期间,Cluster API 必须能够访问该端点。请参阅规划控制平面端点
正在置备的 global 节点引导节点 RegistryTCP/11443在 global 集群自己的 Registry 接管之前,节点会从 NODE_REGISTRY_ADDRESS 拉取平台镜像
global 主机,仅限裸金属引导节点TCP/12443引导阶段使用的 Elemental 注册端点

兼容性和版本输入

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

输入用途
Core Package 版本提供安装程序、本地 Registry 和基础平台负载。
Kubeadm provider chart 版本必须与 global 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>。

安装生命周期

global 集群安装生命周期

使用以下阶段图确定安装各部分的输入、预期结果以及首先进行诊断的位置。

阶段输入应用预期结果首先诊断
Bootstrap 环境Core Package 和 bootstrap 节点运行 setup.shminialauda 管理集群、本地 Registry 和安装程序 Pod 正在运行。cpaas-system 中 bootstrap 节点的容器和 Pod。
Provider 交付Kubeadm 和基础设施 provider 软件包推送软件包并应用 provider AppRelease 对象。Provider CRD 和控制器处于 Ready 状态。AppRelease 状态、provider Pod 以及 Registry 访问。
集群定义Provider 凭证、端点、网络、节点和镜像值应用完整的 Cluster API manifest。Cluster、基础设施集群、控制平面和机器资源出现。资源条件和 provider-controller 日志。
节点置备VM 模板或已注册的主机让基础设施 provider 调谐 Machine。VM 或物理主机启动并接收 kubeadm bootstrap 数据。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 凭证、最终集群注解,以及 provider 特定的交接 Job 和 ConfigMap。

操作步骤

步骤 1 — 准备通用变量

在 bootstrap 节点上设置通用变量。

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.

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

请区分 registry 格式:

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

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

步骤 2 — 创建 Bootstrap 集群

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

mkdir -p /root/cpaas-install
tar -xvf <core-package> -C /root/cpaas-install
cd /root/cpaas-install/installer
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

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

默认情况下,嵌入式 bootstrap Registry 允许匿名访问,且不会创建 global-registry-auth Secret。如果在 bootstrap 设置期间配置了 Registry 用户名和密码,setup.sh 会在 cpaas-system 中创建该 Secret。

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

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

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

如果 bootstrap 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,也不要添加这些字段。bootstrap Secret 也不是凭证传输机制:默认情况下,业务集群通过 global 集群的 public-registry-credential 流程获取其 Registry 拉取 Secret;也可以按照为业务集群选择镜像 Registry中的说明,将业务集群绑定到专用 registry。不要将 bootstrap global-registry-auth Secret 复制到业务集群中。

为何所有 provider 的 cluster.type 都是 Baremetal

下面选项卡中的 AppRelease 值都将 global.cluster.type: Baremetal 设置为 Baremetal。这是 chart 内部使用的分类器,不是 IaaS provider 名称。对于 Huawei DCS、VMware vSphere、Huawei Cloud Stack 和 Bare Metal global 安装,均应保留此值。该值决定平台如何配置节点级组件;它不会选择基础设施 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}"

创建并应用 Kubeadm 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: ait/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: ait/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 特定的 global Manifest

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

请将 provider 集群创建指南作为详细资源参考:

对下方编写的 manifest 中每个资源应用通用先决条件中的命名约定。

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

ProviderBootstrap userdata 格式
Huawei DCSignition(由 provider 强制执行;DCS provider 会使用 invalid format, expected ignition, got <other> 拒绝任何其他格式)。
VMware vSpherecloud-config(CAPI bootstrap 数据由来宾 cloud-init 软件处理;不支持设置 ignition)。
Huawei Cloud Stackcloud-config(HCS provider 会拒绝 ignition;来宾使用 cloud-init 处理生成的数据)。
Bare Metalcloud-config(裸金属 provider 使用 CAPI bootstrap 数据,并渲染由来宾使用 cloud-init 处理的 elemental 计划)。
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 注解中。manifest 通过 DCSCluster.spec.credentialSecretRef 引用该凭证。

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

资源用途
用于控制平面节点的 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。
  • 对于 DCS Provider v1.0.22,使用外部 LoadBalancer 并设置 DCSCluster.spec.controlPlaneLoadBalancer.type: external;或者在与 ACP v4.4+ 配合使用时,使用 type: internal Self-built VIP。对于内部模式,在控制平面 Layer 2 网络中预留 IPv4 VIP,并按照在 Huawei DCS 上创建集群中的 YAML 操作流程进行配置。对于外部模式,请按照规划控制平面端点中的说明,验证监听器、后端、健康检查和可达性。
  • 保留 KubeadmControlPlane.spec.kubeadmConfigSpec.users 条目,并确保 sshAuthorizedKeys 列表非空(boot 用户)。DCS ignition 格式不接受空的 SSH 密钥列表,因此即使是不打算通过 SSH 访问的 global 集群,也必须设置此字段。若无需交互式密钥,请参阅解析占位符值,了解应提供什么内容。
  • 保留非加密 kubeadm 文件、kubelet 补丁、审计策略和安装程序 RBAC 条目。文件内容(PodSecurity admission 配置、kubelet 补丁和审计策略),以及完整的 clusterConfiguration、preKubeadmCommands、postKubeadmCommands 和初始化及加入节点注册补丁,都与业务集群相同。请从完整的 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 模板磁盘来保留状态。
  • 除非已确认所选 datastore 集群能够将卷放置到可运行目标 VM 的主机上,否则请为 DCS 本地存储使用具体的 datastoreName 值。
片段范围

以下 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
裸金属
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 和裸金属,它还会导入 provider 的 Cluster API 资源;对于 Huawei DCS,这些资源由内置流程迁移,因此在正常安装中,DCS ConfigMap 只需要包含凭证 Secret 条目。VMware vSphere、Huawei Cloud Stack 和裸金属在正常和灾难恢复 global 安装中都需要该 ConfigMap。

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

Huawei DCS
VMware vSphere
Huawei Cloud Stack
裸金属

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 Stack裸金属
端点路径/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。

Huawei DCS
VMware vSphere
Huawei Cloud Stack
Bare Metal

DCS 安装程序请求包含 global 控制平面 VIP。使用外部 LoadBalancer 时,这是外部预置的 VIP。使用 DCS Provider v1.0.22+ 和 ACP v4.4+ 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 中使用平台域名;请使用 console.globalHost 作为平台访问地址。cluster.features.ha.vip 使用相同的地址。

第三方 Console 证书

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

DR 证书要求

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

步骤 9 — 监控安装

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

你将观察到的阶段

阶段正在发生的操作首先查看的位置
Bootstrapbootstrap 集群、嵌入式 Registry 和 Cluster API providers 正在 bootstrap 节点上运行。已在步骤 2 和步骤 3 中完成。bootstrap 节点终端;kubectl get pods -n cpaas-system
基础设施置备Cluster API provider 根据目标 IaaS 平台上的 Alauda OS 模板创建 VM。kubectl get machines -n cpaas-system
控制平面 bootstrapKubeadmControlPlane bootstrap 第一个控制平面节点,etcd 启动,其他控制平面节点加入。kubectl get kubeadmcontrolplane -n cpaas-system
网络和核心附加组件CAPI provider 在新集群上协调 Kube-OVN、CoreDNS 和 kube-proxy。kubectl --kubeconfig <global-kubeconfig> get pods -n kube-system
平台安装安装程序将 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>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 签名密钥———✓
为 etcd 服务器证书添加 DR 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 服务器接受。组成每个 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 清单中使用相同的文件。只有两侧使用相同密钥进行签名时,在主集群上创建的固定 baremetal-system-agent 令牌才会被备用 API 服务器接受。

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

为 KubeadmControlPlane 添加 DR etcd 服务器证书 SAN

在第 4 步生成的清单中,将主集群和备用集群的控制平面 VIP、平台访问地址以及 etcd.kube-system 都包含在 KubeadmControlPlane.spec.kubeadmConfigSpec.clusterConfiguration.etcd.local.serverCertSANs 中。在主集群和备用集群的安装环境中使用相同的 SAN 列表。这些值用于配置 kubeadm 生成的 etcd 服务器证书;它们独立于平台 console.cert,不得复制到其 thirdParty SAN 列表中。

serverCertSANs:
  - "${PRIMARY_CLUSTER_VIP}"
  - "${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 -

将该 Secret 的引用添加到 DCSCluster.spec。

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 步中的 DR 版 ConfigMap,而不是普通版本。将 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 缺失,请补充该 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 Secrets、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 值;该相同值使 etcd Synchronizer 能够复制这些键而不会覆盖任何内容。

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_HOSTS 是该侧已安装 Global 机器的、以空格分隔的管理地址列表。请从能够使用两个 Global kubeconfig 并通过 SSH 连接到这些机器的安全主机执行此操作。保持 shell 跟踪处于禁用状态,因为运行时连接文件包含 bearer token。

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 业务计划存在之前,共享的 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 信号进行验收。运行时令牌或 CA 不匹配、持久 OEM 文件中的 bootstrap 端点陈旧、计划探测失败或权限边界不正确,都表示新的移交尚未完成,即使 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 规则 中的模板。将计划占位符替换为两个集群中当前所有 Global 计划 Secret 名称。在两侧的移交完成后,从 MachineInventory.status.plan.secretRef.name 读取这些名称——新的移交会创建 Global 计划 Secret,因此更早收集的名称已陈旧。插件 chart 会提供其余 Bare Metal 规则。每当添加或替换 Global 计划 Secret 时,都要更新此 ConfigMap。

在镜像启动前创建规则源

使用已设置 active_cluster_vip 和活动集群令牌的 etcd-sync 会立即打开同步路径。如果 Bare Metal 规则源尚不存在,第一次同步会在没有 Global 计划 Secret 排除项的情况下运行,并使用主集群的计划 Secret 覆盖备用集群的计划 Secret——这就是 故障排除 中列为 A Global plan is overwritten 的故障模式。随后,备用 Global 主机会接收到属于主集群的计划。

先创建 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 中,因此请像其他插件包一样将其上传到目标集群。

INFO

从 ACP 4.5 开始, etcd Synchronizer 更名为 Global Cluster Disaster Recovery。在更名之前发布的插件包上传后,平台中仍会显示 etcd Synchronizer。

配置参数如下:

参数值
活动 Global 集群 VIP活动 global 集群的控制平面 VIP
活动 Global 集群 ETCD 端点https://<active-control-plane-vip>:2379,除非负载均衡器转发端口 2379
备用集群 ETCD 端点默认值
活动 Global 集群令牌 Secretetcd-sync-active-cluster-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 集群是否健康。

kubectl --kubeconfig <global-kubeconfig> get nodes
kubectl --kubeconfig <global-kubeconfig> get clusters.platform.tkestack.io global \
  -o jsonpath='{.status.phase}'
kubectl --kubeconfig <global-kubeconfig> get pods -n cpaas-system
kubectl --kubeconfig <global-kubeconfig> get clustermodule global

满足以下所有条件时,安装成功:

  • 安装程序进度 API 报告 status: Success 和 type: Complete。
  • 所有 global 集群节点均为 Ready。
  • cpaas-system 中的关键 Pod 为 Running 或 Completed。
  • ClusterModule/global 报告基础模块健康。

裸金属:将 global 集群准备为管理集群

仅适用于裸金属

本节仅适用于裸金属安装路径。Huawei DCS、VMware vSphere 和 Huawei Cloud Stack 部署不需要执行这些步骤:其 global 集群会在安装期间接收所需的 provider 组件和构件。

上述步骤仅安装 global 集群本身。之后创建裸金属业务集群是另一项工作:此时,global 集群将作为 Cluster API 管理集群,并且需要 bootstrap 准备未放置其中的 provider 组件和构件。在 DR 对的两个 global 集群上都完成本节操作——故障转移后,备用集群会成为 活动管理集群,并且必须能够协调相同的业务集群。

在安装期间导入 Cluster API 资源只会创建 CRD。它不会在 global 集群上安装 Bare Metal 或 Kubeadm provider。

设置本节使用的值。每个 global 集群执行一次,并将 GLOBAL_KUBECONFIG 指向要准备的集群。

export GLOBAL_KUBECONFIG="<path-to-this-global-kubeconfig>"
# Tag of the Bare Metal Alauda OS images imported in Step 3.
export OS_IMAGE_TAG="<os-image-tag>"

从要准备的集群自身的 ProductBase 和 cpaas-system/registry-admin Secret 中读取 Registry 地址和密码:

GLOBAL_REGISTRY_ADDRESS="$(kubectl --kubeconfig "${GLOBAL_KUBECONFIG}" \
  get productbase.product.alauda.io base -o jsonpath='{.spec.registry.address}')"
GLOBAL_REGISTRY_PASSWORD="$(kubectl --kubeconfig "${GLOBAL_KUBECONFIG}" -n cpaas-system \
  get secret registry-admin -o jsonpath='{.data.password}' | base64 -d)"

在 global 集群上安装 Cluster API provider

将用于 bootstrap 集群的相同 Bare Metal 和 Kubeadm provider 软件包上传到 global 集群,然后安装两个插件。global 集群不会继承 bootstrap release 的值。在 Bare Metal provider 上设置以下值:

  • elemental.tls.caCertSecretKey:dex.tls 实际包含的键,通常为 tls.crt。 请在安装前找到该键,具体说明请参阅 将 provider 指向实际存在的平台证书。
  • 在 DR 对中,按照 bootstrap release 中的设置方式准确设置 DR 字段: 两侧均设置 elemental.systemAgent.splitAuthEnabled: true,在活动集群上设置 sharedAuthReadOnly: false,在 备用集群上设置 true。每侧保留自己的值;DR 同步不会复制 provider 的 ModuleInfo。

按照 通过 YAML 安装 中的说明,使用 YAML 安装 Bare Metal provider,并将这些值放入其 ModuleInfo 的 spec.valuesOverride 中。Web Console 中的安装表单没有这些字段。在此 global 集群上创建 ModuleInfo,并将 version 设置为已上传的 provider 版本:

apiVersion: cluster.alauda.io/v1alpha1
kind: ModuleInfo
metadata:
  labels:
    cpaas.io/cluster-name: global
    cpaas.io/module-name: cluster-api-provider-baremetal
    cpaas.io/module-type: plugin
  name: global-temporary-name
spec:
  config: {}
  version: <provider-plugin-version>
  valuesOverride:
    ait/chart-cluster-api-provider-baremetal:
      elemental:
        tls:
          caCertSecretKey: <key-found-in-dex.tls>
        systemAgent:                  # DR pair only
          splitAuthEnabled: true
          sharedAuthReadOnly: false   # true on the standby

如果插件已安装,请将相同的 valuesOverride 添加到其现有的 ModuleInfo 中:

kubectl --kubeconfig "${GLOBAL_KUBECONFIG}" get moduleinfo \
  -l cpaas.io/cluster-name=global,cpaas.io/module-name=cluster-api-provider-baremetal
kubectl --kubeconfig "${GLOBAL_KUBECONFIG}" edit moduleinfo <name>

不要编辑 provider 的 AppRelease:平台会根据 ModuleInfo 重新生成它,因此 所做的更改会丢失。

继续之前,验证 manager 和 elemental-operator 正在运行:

kubectl --kubeconfig "${GLOBAL_KUBECONFIG}" -n cpaas-system get deploy \
  cluster-api-provider-baremetal-manager elemental-operator

将 provider 指向实际存在的平台证书

Bare Metal provider 从 elemental.tls.caCertSecretName / caCertSecretKey 为 platformUrl 挂载 CA bundle,这些字段默认为 dex.tls 和 ca.crt。 这些默认值假定存在由 cert-manager 签发的 dex.tls。在 global 集群上,dex.tls 则来自 安装程序的 console.cert 流程,而 thirdParty 证书生成的 Secret 仅包含 tls.crt 和 tls.key。使用默认键时,operator 将永远无法启动:

MountVolume.SetUp failed for volume "elemental-ca-cert":
  references non-existent secret key: ca.crt

检查该 Secret 实际具有哪些键,并在 provider 的 ModuleInfo 中相应设置 caCertSecretKey,如在 global 集群上安装 Cluster API provider 中所示。 对于自签名平台证书,该证书本身就是其 CA,因此 tls.crt 是正确的键:

kubectl --kubeconfig "${GLOBAL_KUBECONFIG}" -n cpaas-system \
  get secret dex.tls -o jsonpath='{.data}' | jq -r 'keys | join(",")'

将基础镜像上传到 global Registry

业务 SeedImage 构建在 global 集群上运行,并从 ProductBase.spec.registry.address 中记录的 Registry 拉取基础 ISO。交接后,重新置备 global 节点时也会 从该 Registry 拉取 baremetal-base-image,因为安装程序会将 global 集群的 cpaas.io/registry-address 注解重写为该 Registry。第 3 步导入的镜像仅存在于 bootstrap Registry 中,因此在将这两个镜像推送到 global Registry 之前,ISO 构建会因 MANIFEST_UNKNOWN 失败。在 bootstrap 节点上,从 bare-metal-os containerd 命名空间推送镜像。--skip-verify 需要 --local:

for repo in baremetal-base-image baremetal-base-image-iso; do
  ctr -n bare-metal-os images push --local --skip-verify \
    -u "admin:${GLOBAL_REGISTRY_PASSWORD}" \
    "${GLOBAL_REGISTRY_ADDRESS}/tkestack/${repo}:${OS_IMAGE_TAG}" "${repo}:${OS_IMAGE_TAG}"
  curl -sk -u "admin:${GLOBAL_REGISTRY_PASSWORD}" \
    "https://${GLOBAL_REGISTRY_ADDRESS}/v2/tkestack/${repo}/tags/list"
done

每个响应都必须列出 ${OS_IMAGE_TAG}。完成后,使用 ctr -n bare-metal-os images rm "baremetal-base-image:${OS_IMAGE_TAG}" "baremetal-base-image-iso:${OS_IMAGE_TAG}" 删除本地副本。 如果镜像已不在 bootstrap 节点上,请改为从 OS 归档导入,具体说明请参阅 已导入 OS 镜像并填充镜像目录。

创建 elemental 镜像目录

provider 通过由 --image-catalog-namespace 和 --image-catalog-name 定位的 ConfigMap,将 Kubernetes 版本映射到 elemental 升级镜像;这两个字段默认为 cpaas-system 和 elemental-image-catalog。provider chart 会创建此 ConfigMap,但不包含任何版本条目,并且 global 集群不会继承你在 bootstrap release 中设置的条目。在添加条目之前,业务集群的第一次 BaremetalMachine 会立即失败:

ImageResolved=False  ImageCatalogMiss:
  no elemental upgrade image mapped for Kubernetes version "<version>"

为此 global 集群管理的每个 Kubernetes 版本添加一个条目,包括其自身节点使用的 ${K8S_VERSION}。请修补现有 ConfigMap,而不是替换它,以保留 之前添加的条目:

kubectl --kubeconfig "${GLOBAL_KUBECONFIG}" -n cpaas-system \
  patch configmap elemental-image-catalog --type merge \
  -p "{\"data\":{\"${K8S_VERSION}\":\"${GLOBAL_REGISTRY_ADDRESS}/tkestack/baremetal-base-image:${OS_IMAGE_TAG}\"}}"

kubectl --kubeconfig "${GLOBAL_KUBECONFIG}" -n cpaas-system \
  get configmap elemental-image-catalog -o jsonpath='{.data}'
ImageCatalogMiss 不会自行恢复

遇到 ImageCatalogMiss 的 BaremetalMachine 在修复目录后仍会保持 Failed。重新协调对象和重启 provider 都不会改变其状态;协调器会 有意将缺少映射视为终止状态,而不是回退到默认镜像。 删除 Machine,使其所有者 KubeadmControlPlane 或 MachineSet 重新创建它。 在应用第一个业务集群 manifest 之前填充目录,可完全避免此问题。

业务注册使用平台域名

业务 MachineRegistration 不得携带 baremetal.cluster.io/system-agent-auth-scope: global 注解。其注册 URL 会解析为 https://<platform-domain>/...,生成的 MachineInventory 会使用 system-agent-auth-scope: shared 添加注解。该共享身份与共享的 ServiceAccount 签名密钥共同使业务主机能够在故障转移后继续通过平台域名报告状态。

卸载引导集群

安装程序报告成功,并且完成将 global 集群准备为管理集群后,global 集群将运行自己的 Cluster API providers。在通过常规验证和下面的 provider 专属交接闸门后,从引导节点中移除临时引导集群——仅删除本地引导集群(minialauda)及其 KIND 容器网络。

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

不要仅因为 KubeadmControlPlane 为 Ready、安装程序报告成功,或 baremetal-system-agent-handoff Job 为 Complete,就移除 minialauda。每个导入的 Global 主机都必须在最终的 global 集群中完成探测,之后才能停用引导端点。

从新的 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 已成功完成。
  • data.ready 在最终的 global 集群中为 true。
  • 每个导入的目标 MachineInventory 都在最终的 global 集群中报告 status.plan.state: Applied。这是 global 侧探测结果,也是证明每个主机现在都向新集群报告状态的检查。
  • 每个导入的目标对象及其引用的 plan 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 不得指定同一个 plan Secret。最后一条命令必须报告 NotFound。

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

验证 DCS Secret 已到达 global 集群

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

对于 DR 安装,请对 DCSCluster.spec.encryptionProviderConfigRef 中指定的 Secret 执行相同检查。DCSCluster 对象会携带该引用完成交接,因此引导集群消失后,Secret 缺失会导致之后每次控制平面机器创建都失败——请参阅添加 provider 专属 DR 字段。

不要删除 Cluster API 对象来执行清理

不要运行 kubectl delete cluster global,也不要将删除 Cluster、KubeadmControlPlane 或 provider 基础设施对象作为清理步骤。安装后,这些对象会拥有运行中的 global 控制平面机器,因此删除它们会级联删除控制平面 VM,并销毁刚刚安装的集群。卸载操作仅限于在引导节点上移除本地引导集群(其 KIND 容器);请保留 Cluster API 对象。

后续步骤

操作示例:Huawei DCS 的完整 global Manifest

这是一个用于 Huawei DCS 上三副本控制平面 global 集群的完整单文件 Manifest。它包含第 4 步中所述的同一组资源,已预先组装完成,因此无需跨页面合并片段。示例使用仅用于文档的值:请替换每个 <placeholder>,并重新使用你在第 1 步导出的 ${...} 变量。在第 5 步应用它。

此示例面向非 DR 集群。为避免维护两个副本,这里不重复 KubeadmControlPlane kubeadmConfigSpec 主体——它与业务集群完全相同,取自完整 KubeadmControlPlane 配置附录,其中两个 global / 非 DR 差异已在下面资源 3 中内联标注。

该 Manifest 不会创建 DCS API 凭证 Secret。该 Secret 已在前置条件中创建,此处通过 DCSCluster.spec.credentialSecretRef 按名称引用。第 7 步会通过 dcs-import-extra-resources ConfigMap 将其复制到 global 集群;你必须确保其 names 列表始终与同一个 Secret 名称一致。

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

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

  1. DCS API 访问权限及由此创建的凭证 Secret——端点(https://<host>:7443)、administrator 用户名和密码,以及站点 ID。四项内容全部写入凭证 Secret;provider 会从其中读取站点 ID,并在该字段为空时将其写入 DCSCluster.spec.site,因此下面的 Manifest 不设置该字段。

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

    global 安装还要求设置集群名称标签,该标签会出现在 Manifest 的所有其他资源中:

    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);这些 IP 填入 DCSIpHostnamePool。然后选择一个符合控制平面端点约定的外部 LoadBalancer,并将 TCP 6443 转发到这三个节点;或者,为 DCS Provider v1.0.22+ Self-built VIP(ACP v4.4+)在同一 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 值。

准备好以上内容后,应用下面的 Manifest。该操作示例使用外部 LoadBalancer,因此对所有受支持的 DCS provider 版本均有效。如果你选择 DCS Provider v1.0.22+,并使用 ACP v4.4+ Self-built VIP,请在创建 global 集群前,应用 Manifest 后面所示的内部模式替换内容。

---
# 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

Internal Self-built VIP 变体

上面的操作示例使用 type: external。在应用它之前,DCS Provider v1.0.22+ 配合 ACP v4.4+ 时,可以使用以下内部配置替换资源 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 平台凭证和站点。它们不会出现在 Manifest 中,而是写入用于在前置条件中创建 ${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 自身的文件。不要向 KubeadmControlPlane.spec.kubeadmConfigSpec.files 添加 /etc/kubernetes/encryption-provider.conf——provider 会删除该路径对应的条目。请参阅加密 Provider 配置的交付方式。

只有在你明确希望在不启用静态加密的情况下运行时,才移除该参数。对于 DR 对,双方的密钥必须完全一致,这意味着要按照可选灾难恢复部署中的说明,使用 DCSCluster.spec.encryptionProviderConfigRef 固定该密钥。