安装 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 节点都会从此主机拉取平台镜像集。
硬件
CPU 和内存的最低配置依据此安装路径的标准交付实践制定。在传统安装路径中,运行 setup.sh 的机器会成为第一个控制平面节点,因此必须满足 先决条件中的控制平面最低要求;而此处的引导节点不会加入集群——它只负责提取 Core Package、运行临时的基于 KIND 的引导集群,并在交接前提供平台 Registry。这种负载较轻且只是临时的,标准交付所采用并验证的配置为 4 核 / 8 GB。
对于 arm64,请遵循同一页面中的平台 ARM 约定:至少为 x86_64 配置的 1.5 倍,最好为 2 倍,因此最低配置为 6 核 / 12 GB,推荐配置为 8 核 / 16 GB。
磁盘空间是最常导致引导过程停滞的资源。请分三部分规划:
操作系统
引导节点运行传统操作系统,而非 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。安装过程中途更改地址会导致节点置备失败。
兼容性和版本输入
安装前,记录交付软件包支持的版本集:
安装生命周期

使用以下阶段图确定安装各部分的输入、预期结果以及首先进行诊断的位置。
操作步骤
步骤 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 格式:
/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。
设置 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
设置 provider 软件包路径和 chart 版本。
export VSPHERE_PROVIDER_PACK="/root/cluster-api-provider-vsphere.amd64.<version>.tgz"
export KUBEADM_PROVIDER_PACK="/root/cluster-api-provider-kubeadm.amd64.<version>.tgz"
export VSPHERE_PROVIDER_VERSION="<vsphere-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 "${VSPHERE_PROVIDER_PACK}"
/root/cpaas-install/installer/res/amd64/packtool pack push \
-r "${LOCAL_REGISTRY_ADDRESS}" -c "${KUBEADM_PROVIDER_PACK}"
创建并应用 Kubeadm provider 和 VMware vSphere provider 的 AppRelease 资源。
mkdir -p /root/yamls
export VSPHERE_PROVIDER_APPRELEASES="/root/yamls/vsphere-provider-appreleases.yaml"
cat > "${VSPHERE_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-vsphere
namespace: cpaas-system
spec:
destination:
cluster: ""
namespace: ""
source:
charts:
- name: ait/chart-cluster-api-provider-vsphere
releaseName: cluster-api-provider-vsphere
targetRevision: ${VSPHERE_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 "${VSPHERE_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 vsphereclusters.infrastructure.cluster.x-k8s.io --ignore-not-found 2>/dev/null | grep -q vsphereclusters.infrastructure.cluster.x-k8s.io; do
sleep 10
done
设置 provider 软件包路径和 chart 版本。
export HCS_PROVIDER_PACK="/root/cluster-api-provider-hcs.amd64.<version>.tgz"
export KUBEADM_PROVIDER_PACK="/root/cluster-api-provider-kubeadm.amd64.<version>.tgz"
export HCS_PROVIDER_VERSION="<hcs-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 "${HCS_PROVIDER_PACK}"
/root/cpaas-install/installer/res/amd64/packtool pack push \
-r "${LOCAL_REGISTRY_ADDRESS}" -c "${KUBEADM_PROVIDER_PACK}"
创建并应用 Kubeadm provider 和 HCS provider 的 AppRelease 资源。
mkdir -p /root/yamls
export HCS_PROVIDER_APPRELEASES="/root/yamls/hcs-provider-appreleases.yaml"
cat > "${HCS_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-hcs
namespace: cpaas-system
spec:
destination:
cluster: ""
namespace: ""
source:
charts:
- name: ait/chart-cluster-api-provider-hcs
releaseName: cluster-api-provider-hcs
targetRevision: ${HCS_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 "${HCS_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 hcsclusters.infrastructure.cluster.x-k8s.io --ignore-not-found 2>/dev/null | grep -q hcsclusters.infrastructure.cluster.x-k8s.io; do
sleep 10
done
设置 provider 软件包路径和 chart 版本。
export BAREMETAL_PROVIDER_PACK="/root/cluster-api-provider-baremetal.amd64.<version>.tgz"
export KUBEADM_PROVIDER_PACK="/root/cluster-api-provider-kubeadm.amd64.<version>.tgz"
export BAREMETAL_PROVIDER_VERSION="<baremetal-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 "${BAREMETAL_PROVIDER_PACK}"
/root/cpaas-install/installer/res/amd64/packtool pack push \
-r "${LOCAL_REGISTRY_ADDRESS}" -c "${KUBEADM_PROVIDER_PACK}"
将 Bare Metal Alauda OS 镜像导入同一本地 registry。provider 软件包不包含这些镜像。Bare Metal OS 下载文件是一个 OCI 镜像布局归档,其中包含两个使用相同标签的镜像,且两个镜像都是必需的——请参阅按 Provider 划分的镜像格式:
使用 ctr 将归档导入 bootstrap 节点上的专用 containerd 命名空间,并列出该命令创建的镜像名称。/var/lib/containerd 需要至少与归档大小相同的可用空间:
export OS_IMAGE_ARCHIVE="/root/<bare-metal-os-archive>.tar"
ctr -n bare-metal-os images import --all-platforms --no-unpack "${OS_IMAGE_ARCHIVE}"
ctr -n bare-metal-os images ls -q
输出会列出使用相同标签的两个镜像:
baremetal-base-image-iso:<os-image-tag>
baremetal-base-image:<os-image-tag>
将 OS_IMAGE_TAG 设置为该标签,然后推送两个镜像,并确认 registry 为每个镜像列出了该标签:
export OS_IMAGE_TAG="<os-image-tag>"
for repo in baremetal-base-image baremetal-base-image-iso; do
ctr -n bare-metal-os images push --plain-http \
"${LOCAL_REGISTRY_ADDRESS}/tkestack/${repo}:${OS_IMAGE_TAG}" "${repo}:${OS_IMAGE_TAG}"
curl -s "http://${LOCAL_REGISTRY_ADDRESS}/v2/tkestack/${repo}/tags/list"
done
如果 bootstrap Registry 需要身份验证,请使用 -u <username>:<password> 将凭证传递给这两个命令。将镜像保留在 bare-metal-os 命名空间中:将基础镜像上传到 global Registry会在安装后再次推送这些镜像。
创建并应用 Kubeadm provider 和 Bare Metal provider 的 AppRelease 资源。
Bare Metal provider AppRelease 还会填充镜像目录。chart 会创建一个不包含条目的 elemental-image-catalog ConfigMap,因此 provider.imageCatalog.images 必须将 ${K8S_VERSION} 映射到上面导入的 baremetal-base-image;chart 会为仓库添加 global.registry.address 前缀。没有该条目,每个 global BaremetalMachine 都会以 ImageResolved=False / Reason=ImageCatalogMiss 进入 Failed,且不会置备任何节点。
Bare Metal Bootstrap 端点
在 bootstrap 期间,global 集群尚未将控制权交给最终 VIP。请将裸金属注册路径保留在 bootstrap 节点上:global.platformUrl 指向 bootstrap 节点,elemental.server.url 指向 https://<bootstrap-host-ip>:12443。此阶段不要在用于 global 机器的 MachineRegistration 上设置 baremetal.cluster.io/system-agent-server-url。对于 Bare Metal DR,请将 baremetal.cluster.io/system-agent-auth-scope: global 添加到该 bootstrap MachineRegistration;这是交接前唯一需要的 DR 专用 system-agent 注解。之后,DR 交接 Job 会将这些机器迁移到位于 https://<CONTROL_PLANE_VIP>:6443 的本地直连 API 端点。
安装 Bare Metal provider 之前,请准备好供 global-alb2 使用的 bootstrap HTTPS 证书。elemental-operator 会从 cpaas-system/dex.tls 挂载 CA;当 elemental.tls.agentTLSMode 为 strict 时,elemental-system-agent 会使用该 CA。Secret 必须包含 tls.crt、tls.key 以及在 AppRelease 中配置的 CA bundle key(下方示例为 ca.crt)。由于 bootstrap 端点为 https://${HOST_IP}:12443,服务证书必须将 ${HOST_IP} 包含为 IP SAN。
如果 bootstrap 集群中有 cert-manager,请使用 bootstrap 本地 CA 创建或刷新 dex.tls:
kubectl get crd certificates.cert-manager.io issuers.cert-manager.io
kubectl -n cpaas-system apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: baremetal-bootstrap-selfsigned
namespace: cpaas-system
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: baremetal-bootstrap-ca
namespace: cpaas-system
spec:
secretName: baremetal-bootstrap-ca
commonName: baremetal-bootstrap-ca
duration: 87600h
renewBefore: 720h
isCA: true
privateKey:
algorithm: RSA
size: 2048
usages:
- cert sign
- crl sign
issuerRef:
name: baremetal-bootstrap-selfsigned
kind: Issuer
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: baremetal-bootstrap-ca
namespace: cpaas-system
spec:
ca:
secretName: baremetal-bootstrap-ca
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: dex-tls-bootstrap
namespace: cpaas-system
spec:
secretName: dex.tls
commonName: ${HOST_IP}
duration: 87600h
renewBefore: 720h
privateKey:
algorithm: RSA
size: 2048
usages:
- digital signature
- key encipherment
- server auth
ipAddresses:
- "${HOST_IP}"
- "127.0.0.1"
dnsNames:
- global-alb2
- global-alb2.cpaas-system.svc
- global-alb2.cpaas-system.svc.cluster.local
issuerRef:
name: baremetal-bootstrap-ca
kind: Issuer
EOF
kubectl -n cpaas-system wait certificate/baremetal-bootstrap-ca \
--for=condition=Ready \
--timeout=120s
kubectl -n cpaas-system wait certificate/dex-tls-bootstrap \
--for=condition=Ready \
--timeout=120s
验证 Secret 是否包含预期的 key,并确认该证书对 bootstrap 端点有效:
kubectl -n cpaas-system get secret dex.tls \
-o jsonpath='{.data.tls\.crt}{" "}{.data.tls\.key}{" "}{.data.ca\.crt}{"\n"}'
tmp_dir=$(mktemp -d)
trap 'rm -rf "${tmp_dir}"' EXIT
kubectl -n cpaas-system get secret dex.tls \
-o jsonpath='{.data.ca\.crt}' | base64 -d > "${tmp_dir}/ca.crt"
kubectl -n cpaas-system get secret dex.tls \
-o jsonpath='{.data.tls\.crt}' | base64 -d > "${tmp_dir}/tls.crt"
openssl verify -CAfile "${tmp_dir}/ca.crt" "${tmp_dir}/tls.crt"
openssl x509 -in "${tmp_dir}/tls.crt" -noout -text | grep "IP Address:${HOST_IP}"
echo | openssl s_client \
-connect "${HOST_IP}:12443" \
-servername "${HOST_IP}" \
-CAfile "${tmp_dir}/ca.crt" \
-verify_return_error 2>&1 | grep "Verify return code: 0 (ok)"
如果 bootstrap ALB 仍在提供旧证书,请重启它并再次验证:
kubectl -n cpaas-system rollout restart deploy/global-alb2
kubectl -n cpaas-system rollout status deploy/global-alb2
不要将此 bootstrap dex.tls 包含在 dcs-import-extra-resources 中。它仅用于临时 bootstrap 端点。最终 global 集群的 dex.tls 由安装程序的平台证书流程创建或维护。对于 DR,请按照步骤 8 中的 thirdParty 控制台证书指南操作,确保两端都提供针对稳定平台域名的证书。只有在客户端需要通过控制平面 VIP 直接访问平台 HTTPS 时,才需要将该 VIP 包含在平台证书中。端口 11443 上的 Global Registry 提供其自身的 global-registry-server 证书链,不由 console.cert 或 dex.tls 配置。
mkdir -p /root/yamls
export BAREMETAL_PROVIDER_APPRELEASES="/root/yamls/baremetal-provider-appreleases.yaml"
cat > "${BAREMETAL_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-baremetal
namespace: cpaas-system
spec:
destination:
cluster: ""
namespace: ""
source:
charts:
- name: ait/chart-cluster-api-provider-baremetal
releaseName: cluster-api-provider-baremetal
targetRevision: ${BAREMETAL_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}
tls:
secretName: dex.tls
labelBaseDomain: cpaas.io
namespace: cpaas-system
platformUrl: https://${HOST_IP}
protectSecretFiles:
enabled: false
region: global
registry:
address: ${BOOTSTRAP_REGISTRY_ADDRESS}
replicas: 1
scheme: https
provider:
imageCatalog:
images:
${K8S_VERSION}:
repository: tkestack/baremetal-base-image
tag: ${OS_IMAGE_TAG}
handoffHook:
# Normal non-DR bootstrap baseline. Bare Metal DR must patch this to true
# before calling the installer API, as shown in Optional Disaster Recovery Deployment.
directAPIServer: false
controlPlaneVIP: ${CONTROL_PLANE_VIP}
delivery:
enabled: true
mode: always
elemental:
server:
url: https://${HOST_IP}:12443
systemAgent:
authMode: shared
# Normal non-DR bootstrap baseline. Bare Metal DR must enable the split
# and set sharedAuthReadOnly according to this side's active/standby role.
splitAuthEnabled: false
serviceAccountName: baremetal-system-agent
globalServiceAccountName: baremetal-global-system-agent
sharedAuthReadOnly: false
tls:
agentTLSMode: strict
caCertSecretName: dex.tls
caCertSecretKey: ca.crt
EOF
kubectl apply -f "${BAREMETAL_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 baremetalclusters.infrastructure.cluster.x-k8s.io --ignore-not-found 2>/dev/null | grep -q baremetalclusters.infrastructure.cluster.x-k8s.io; do
sleep 10
done
until kubectl get crd machineinventories.elemental.cattle.io --ignore-not-found 2>/dev/null | grep -q machineinventories.elemental.cattle.io; do
sleep 10
done
上面的完整 AppRelease 有意展示了常规的非 DR 基线。对于 Bare Metal DR 安装,请勿在这三个基线值保持不变的情况下调用安装程序 API。先应用可选灾难恢复部署中的特定角色 DR 补丁,并验证该 bootstrap 集群上实际生效的 AppRelease 值。
provider 启动后,验证 chart 值是否已被接受。出现带有 --system-agent-auth-mode 等未知标志的 CrashLoopBackOff,表示 AppRelease chart 与 elemental-operator 镜像不匹配;继续操作前,请安装来自同一发布负载的 chart 和镜像。
kubectl -n cpaas-system get pods | grep -E 'cluster-api-provider-baremetal|elemental'
kubectl -n cpaas-system logs deploy/elemental-operator --tail=100
kubectl -n cpaas-system get configmap elemental-image-catalog -o jsonpath='{.data}'
目录输出必须包含 ${K8S_VERSION} 这一 key。输出为空或出现 {},表示 provider.imageCatalog.images 值未传递到 release;请在应用 global manifest 之前修复 AppRelease。
步骤 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 控制器会强制执行该字段值:
在渲染 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 命名空间中包含以下资源:
使用在 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}"
在渲染 VMware vSphere global manifest 之前,先设置其输出路径。
export GLOBAL_VSPHERE_YAML="/root/yamls/new-global.yaml"
在组装 manifest 之前创建两个凭证 Secret,并确保不将它们包含在 manifest 中。第 5 步使用 kubectl apply 应用 manifest 时,会将它们的内容记录在各自 Secret 的 kubectl.kubernetes.io/last-applied-configuration 注解中。
- vCenter 凭证必须命名为
global-vsphere-credentials——第 7 步的导入 ConfigMap 和 DR 部分都将此名称硬编码。按照在 VMware vSphere 上创建集群中的说明,使用该固定名称创建凭证,并在 VSphereCluster.spec.identityRef.name 中引用它。
- vSphere CPI 凭证由
ClusterResourceSet 引用。按照同一指南中的说明创建该凭证。
然后,VMware vSphere global manifest 必须在 cpaas-system 命名空间中包含以下资源:
使用VMware vSphere 基础设施准备准备 vSphere 输入值。以在 VMware vSphere 上创建集群和VMware vSphere Provider为基础参考,准备 global 集群 manifest。创建集群指南通过将 manifest 应用到现有的 global 管理集群来创建业务集群;该指南本身不会创建 global 集群。只有应用以下 global 特定要求后,才能复用其中的 vSphere 资源定义:
-
将 Cluster.metadata.name 和 VSphereCluster.metadata.name 设置为 global(基础设施集群与 CAPI 共享 Cluster 名称)。为其他每个 CAPI 资源和 provider 资源添加 global- 前缀;下方的连接片段使用 KubeadmControlPlane.metadata.name: global-kcp。
-
添加 Cluster.metadata.labels.is-global: "true" 和 Cluster.metadata.labels.cluster-type: VSphere。
-
添加包含 ${NODE_REGISTRY_ADDRESS} 的 Cluster.metadata.annotations["cpaas.io/registry-address"]。
-
保留平台控制器所需的 VMware vSphere 注解,包括 VMware vSphere 创建集群指南中的网络和 CPI 注解。
-
将 VSphereMachineTemplate.spec.template.spec.folder 设置为 /<datacenter>/vm/global,以便 operator 能够在 vCenter 中识别 global 集群 VM。在 DR 部署中,为主集群和备用集群使用不同的子文件夹,例如 /<datacenter>/vm/global/primary 和 /<datacenter>/vm/global/standby。
-
将 VSphereCluster.spec.identityRef.name 设置为 global-vsphere-credentials。此固定 Secret 名称仅适用于 VMware vSphere global 安装路径;非 global VMware vSphere 集群遵循通用创建集群指南。
-
在应用 manifest 之前选择控制平面端点模式;之后无法更改。对于外部 LoadBalancer,请先按照规划控制平面端点中的约定完成配置并验证。对于 VMware vSphere Provider v1.0.17 或更高版本以及 ACP v4.4 或更高版本,global 集群也可以使用 internal Self-built VIP:完成Self-built VIP 先决条件后,将启用 Self-built VIP中的 spec.controlPlaneLoadBalancer 块添加到 global VSphereCluster 中。使用 v1.0.16 或更早版本时,仅可使用外部 LoadBalancer。
Self-built VIP 先决条件命令会从正在运行的 global 集群读取 ModulePlugin/alive,但 bootstrap 安装期间该集群尚不存在。请改为从安装软件包中确认其包含 alive 插件。
-
设置 KubeadmControlPlane.spec.kubeadmConfigSpec.format: cloud-config;如果 provider 将此 API 字段默认为 cloud-config,也可以不设置该字段。cloud-init 是处理数据的来宾软件,不是此字段的有效值。
-
保留 release manifest 中的 kubeadm 文件,包括 VMware vSphere 的 /etc/kubernetes/encryption-provider.conf 文件条目、kubelet 补丁、审计策略和安装程序 RBAC 条目。VMware vSphere 通过 KubeadmControlPlane.spec.kubeadmConfigSpec.files 交付此文件;不要采用 DCS 的 DCSCluster.spec.encryptionProviderConfigRef 模式。
片段范围
以下 YAML 是差异片段,而不是可直接应用的完整 manifest。将这些 global 特定更改合并到根据 VMware vSphere 创建集群指南准备的 manifest 中,然后应用完整的 manifest 文件。
以下片段展示了 global 特定的 Cluster API 连接配置。请使用上面的 VMware vSphere 创建集群参考资料填写 provider 资源字段。
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: global
namespace: cpaas-system
labels:
cluster-type: VSphere
is-global: "true"
addons.cluster.x-k8s.io/vsphere-cpi: "enabled"
annotations:
capi.cpaas.io/resource-group-version: infrastructure.cluster.x-k8s.io/v1beta1
capi.cpaas.io/resource-kind: VSphereCluster
cpaas.io/alb-address-type: ClusterAddress
cpaas.io/network-type: kube-ovn
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: VSphereCluster
name: global
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
name: global-kcp
namespace: cpaas-system
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: VSphereMachineTemplate
name: global-cp-machine-template
kubeadmConfigSpec:
format: cloud-config
clusterConfiguration:
etcd:
local:
serverCertSANs:
- "${CONTROL_PLANE_VIP}"
- "${PLATFORM_HOST}"
在渲染 HCS global manifest 之前,设置其输出路径。
export GLOBAL_HCS_YAML="/root/yamls/new-global.yaml"
HCS global manifest 必须在 cpaas-system 命名空间中包含以下资源:
使用在 Huawei Cloud Stack 上创建集群和Huawei Cloud Stack 的基础设施资源中的 HCS 资源字段。对于 global 集群,还需满足以下要求:
- 将
Cluster.metadata.name 和 HCSCluster.metadata.name 设置为 global(基础设施集群共享 CAPI Cluster 名称)。为其他每个 CAPI 资源和 provider 资源添加 global- 前缀;下面的连接片段使用 KubeadmControlPlane.metadata.name: global-kcp。
- 将
HCSCluster.spec.identityRef.name 设置为 ${PROVIDER_SECRET_NAME}。Step 7 会将此 Secret 导入最终的 global 集群。
- 添加
Cluster.metadata.labels.is-global: "true" 和 Cluster.metadata.labels.cluster-type: HCS。
- 添加带有
${NODE_REGISTRY_ADDRESS} 的 Cluster.metadata.annotations["cpaas.io/registry-address"]。
- 设置
KubeadmControlPlane.spec.kubeadmConfigSpec.format: cloud-config;如果 provider 将此 API 字段默认为 cloud-config,也可以不设置该字段。cloud-init 是来宾软件名称,而不是字段值。
- 保留 release manifest 中的非加密 kubeadm 文件、kubelet 补丁、审计策略和安装程序 RBAC 条目。
- 对于正常的非 DR 部署,不要将
/etc/kubernetes/encryption-provider.conf 添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.files。
- 将
/var/cpaas 保留为平台状态。当其必须在节点替换后保留时,在 HCSMachineConfigPool.spec.configs[].persistentDisks[] 中声明;不要依赖 HCSMachineTemplate.spec.template.spec.dataVolumes[] 作为保留状态。
- 为
global 集群使用高可用控制平面。单控制平面 HCS 集群仅适用于创建场景,不是推荐的 global 升级路径。
片段范围
以下 YAML 是差异片段,而不是可直接应用的完整 manifest。将这些 global 特定更改合并到根据 HCS 创建集群参考资料准备的 manifest 中,然后应用完整的 manifest 文件。
以下片段展示了 global 特定的 Cluster API 连接配置。请使用上面的 HCS 创建集群参考资料填写 provider 资源字段。
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: global
namespace: cpaas-system
labels:
cluster-type: HCS
is-global: "true"
annotations:
capi.cpaas.io/resource-group-version: infrastructure.cluster.x-k8s.io/v1beta1
capi.cpaas.io/resource-kind: HCSCluster
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: HCSCluster
name: global
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
name: global-kcp
namespace: cpaas-system
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: HCSMachineTemplate
name: global-cp-machine-template
kubeadmConfigSpec:
format: cloud-config
clusterConfiguration:
etcd:
local:
serverCertSANs:
- "${CONTROL_PLANE_VIP}"
- "${PLATFORM_HOST}"
在渲染裸金属 global manifest 之前,设置其输出路径。
export GLOBAL_BAREMETAL_YAML="/root/yamls/new-global.yaml"
裸金属 global manifest 必须在 cpaas-system 命名空间中包含以下资源:
使用在裸金属上创建集群、管理裸金属上的节点和裸金属 Provider作为资源参考。对于 global 集群,还需满足以下要求:
- 将
Cluster.metadata.name 和 BaremetalCluster.metadata.name 设置为 global。为其他每个 CAPI、裸金属和 elemental 资源添加 global- 前缀。
- 添加
Cluster.metadata.labels.cluster-type: ProviderBaremetal。
- 添加带有
${NODE_REGISTRY_ADDRESS} 的 Cluster.metadata.annotations["cpaas.io/registry-address"]。
- 将
SeedImage.spec.baseImage 设置为 ${BOOTSTRAP_REGISTRY_ADDRESS}/tkestack/baremetal-base-image-iso:${OS_IMAGE_TAG},即 Step 3 中导入的 ISO 镜像。重新部署镜像来自 Step 3 中设置的 elemental-image-catalog 条目;provider 会使用 cpaas.io/registry-address 注解替换该条目的 registry 部分,因此主机会从 ${NODE_REGISTRY_ADDRESS} 拉取该镜像。
- 添加
Cluster.metadata.annotations["cpaas.io/kube-ovn-join-cidr"]、Cluster.metadata.annotations["cpaas.io/sentry-deploy-type"]: Baremetal 和 Cluster.metadata.annotations["cpaas.io/alb-address-type"]: ClusterAddress。
- 设置
KubeadmControlPlane.spec.kubeadmConfigSpec.format: cloud-config;如果 provider 将此 API 字段默认为 cloud-config,也可以不设置该字段。生成的数据由节点上的 cloud-init 处理。
- 设置
KubeadmControlPlane.spec.rolloutStrategy.rollingUpdate.maxSurge: 0。裸金属池无法对物理主机进行超额分配。
- 当 release manifest 使用 kube-ovn 时,在
KubeadmControlPlane 上保留 controlplane.cluster.x-k8s.io/skip-kube-proxy: ""。
- 将
${CONTROL_PLANE_VIP} 和 ${PLATFORM_HOST} 放入 KubeadmControlPlane.spec.kubeadmConfigSpec.clusterConfiguration.etcd.local.serverCertSANs。
- 在规划控制平面端点中选择裸金属端点模式。对于
Internal,设置 VIP、端口 6443 和唯一的 vrid。对于 External,设置外部负载均衡器前端地址和端口,并省略 vrid。
- 对于正常的非 DR 部署,可以省略
BaremetalCluster.spec.encryptionProviderConfigRef。对于 DR 部署,按照可选灾难恢复部署中的说明设置该字段;不要通过将 /etc/kubernetes/encryption-provider.conf 添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.files 来交付它。
- 对于裸金属 DR,将
baremetal.cluster.io/system-agent-auth-scope: global 添加到用于 bootstrap global 主机的 MachineRegistration 中。bootstrap 期间不要设置 baremetal.cluster.io/system-agent-server-url。bootstrap ISO 必须通过 bootstrap 节点注册;之后交接作业会将 global 机器迁移到本地直接 API 端点。
- 如果
global VM 或物理主机在 live-ISO 启动期间没有 DHCP,请在等待 MachineInventory 注册之前,通过主机控制台手动配置 NIC。使用在裸金属上创建集群中所述的相同 NetworkManager 操作步骤,将示例地址、网关、DNS 和连接名称替换为该主机的值。
- 在 live-ISO 启动之前,从每个
global 主机的固件设置时钟,并确保各主机之间的时间偏差保持在平台要求的 10 秒以内。请参阅主机时间同步。
- 不要依赖操作系统主机名的副作用。裸金属 provider 会根据 CAPI 和清单对象规范化 kubeadm 节点名称和 provider ID。
- 在 bootstrap 集群中创建的
SeedImage 是 bootstrap 工件。交接后,请在活动的 global 集群上创建任何新的 MachineRegistration 或 SeedImage。
片段范围
以下 YAML 是差异片段,而不是可直接应用的完整 manifest。将这些 global 特定更改合并到根据裸金属创建集群参考资料准备的 manifest 中,然后应用完整的 manifest 文件。
以下片段展示了 global 特定的 Cluster API 连接配置。请使用裸金属创建集群参考资料填写清单名称、注册配置、镜像引用和可选的工作节点资源。
将 <control-plane-load-balancer-type> 设置为 provider 管理的 Alive 对应的 Internal,或设置为用户配置的 LoadBalancer 对应的 External。使用 External 时,请移除 vrid。有关每种模式的要求,请参阅规划控制平面端点。
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: global
namespace: cpaas-system
labels:
cluster-type: ProviderBaremetal
annotations:
capi.cpaas.io/resource-group-version: infrastructure.cluster.x-k8s.io/v1beta1
capi.cpaas.io/resource-kind: BaremetalCluster
cpaas.io/kube-ovn-join-cidr: "${KUBE_OVN_JOIN_CIDR}"
cpaas.io/registry-address: "${NODE_REGISTRY_ADDRESS}"
cpaas.io/sentry-deploy-type: Baremetal
cpaas.io/alb-address-type: ClusterAddress
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: BaremetalCluster
name: global
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: BaremetalCluster
metadata:
name: global
namespace: cpaas-system
spec:
controlPlaneLoadBalancer:
type: <control-plane-load-balancer-type>
host: ${CONTROL_PLANE_VIP}
port: 6443
# Required only for Internal. Remove this field for External.
vrid: <unique-vrid>
# vipMode defaults to nic. Set it explicitly only when the environment
# requires another supported mode, such as arp or policy_route.
# vipMode: nic
# Required for DR. Omit this field for a normal non-DR deployment unless
# you need to provide a pre-existing encryption-provider.conf.
# encryptionProviderConfigRef:
# name: global-encryption-provider-config
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: MachineInventoryPool
metadata:
name: global-control-plane-pool
namespace: cpaas-system
spec:
clusterName: global
machineInventories:
- global-cp-1
- global-cp-2
- global-cp-3
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: BaremetalMachineTemplate
metadata:
name: global-control-plane-template
namespace: cpaas-system
spec:
template:
spec:
machineInventoryPoolRef:
name: global-control-plane-pool
---
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: BaremetalMachineTemplate
name: global-control-plane-template
kubeadmConfigSpec:
format: cloud-config
clusterConfiguration:
etcd:
local:
serverCertSANs:
- "${CONTROL_PLANE_VIP}"
- "${PLATFORM_HOST}"
步骤 5 — 应用 global 清单
将 provider 特定的清单应用到 bootstrap 集群。
kubectl apply -f "${GLOBAL_DCS_YAML}"
kubectl apply -f "${GLOBAL_VSPHERE_YAML}"
kubectl apply -f "${GLOBAL_HCS_YAML}"
kubectl apply -f "${GLOBAL_BAREMETAL_YAML}"
在预期 Cluster API 协调继续进行之前,请等待 bootstrap 注册生成预期的清单。
kubectl -n cpaas-system get machineinventory.elemental.cattle.io
kubectl -n cpaas-system get machineinventorypool
kubectl -n cpaas-system get baremetalcluster,baremetalmachine
步骤 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。
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"}'
在触发安装程序之前,创建并应用 VMware vSphere 导入 ConfigMap。正常和灾难恢复 global 安装都需要此 ConfigMap。global-vsphere-credentials Secret 存储 vCenter 用户名和密码,且必须与 VMware vSphere global 清单中 VSphereCluster.spec.identityRef.name 所引用的 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: "vsphereclusters.infrastructure.cluster.x-k8s.io"
names: ["global"]
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/vsphereclusters/cpaas-system/"
method: etcdctl
- resource: "vspheremachinetemplates.infrastructure.cluster.x-k8s.io"
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/vspheremachinetemplates/cpaas-system/"
method: etcdctl
- resource: "vspheremachines.infrastructure.cluster.x-k8s.io"
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/vspheremachines/cpaas-system/"
method: etcdctl
- resource: "vspherevms.infrastructure.cluster.x-k8s.io"
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/vspherevms/cpaas-system/"
method: etcdctl
- resource: "vspheremachineconfigpools.infrastructure.cluster.x-k8s.io"
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/vspheremachineconfigpools/cpaas-system/"
method: etcdctl
- resource: "secrets"
names: ["global-vsphere-credentials"]
method: kubectl
EOF
kubectl apply -f /root/yamls/dcs-import-extra-resources.yaml
在触发安装程序之前,创建并应用 HCS 导入 ConfigMap。正常和灾难恢复 global 安装都需要此 ConfigMap。将 PROVIDER_SECRET_NAME 设置为 HCSCluster.spec.identityRef.name 使用的相同 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
- resource: "hcsclusters.infrastructure.cluster.x-k8s.io"
names: ["global"]
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/hcsclusters/cpaas-system/"
method: etcdctl
- resource: "hcsmachinetemplates.infrastructure.cluster.x-k8s.io"
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/hcsmachinetemplates/cpaas-system/"
method: etcdctl
- resource: "hcsmachineconfigpools.infrastructure.cluster.x-k8s.io"
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/hcsmachineconfigpools/cpaas-system/"
method: etcdctl
- resource: "hcsmachines.infrastructure.cluster.x-k8s.io"
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/hcsmachines/cpaas-system/"
method: etcdctl
EOF
kubectl apply -f /root/yamls/dcs-import-extra-resources.yaml
在触发安装程序之前,创建并应用裸金属导入 ConfigMap。此 ConfigMap 会导入全新安装交接流程用于枚举已安装 Global 机器的持久裸金属和 elemental 所有者资源。不得导入 Elemental 计划 Secret 或 kubeadm bootstrap 数据 Secret。
在 DCS API 之前导入
在调用 POST /cpaas-installer/api/config/dcs 之前创建 dcs-import-extra-resources。如果缺少该对象,交接作业可能会使用空的目标列表运行,因为新的 global 集群不包含描述 bootstrap global 机器的 BaremetalMachine、MachineInventory 或 MachineRegistration 对象。不要通过将其计划 Secret 或 kubeadm bootstrap 数据 Secret 添加到 ConfigMap 来补救。
从已协调的 BaremetalMachine 对象中收集确切的 Global MachineInventory 名称,并确定这些主机所使用的 bootstrap MachineRegistration。对于 DR,还要收集 BaremetalCluster.spec.encryptionProviderConfigRef 引用的 Secret 名称,以便最终的 global 集群包含相同的加密 provider 配置。不要导入任意平台凭证 Secret 或 MachineRegistration 令牌 Secret。
kubectl -n cpaas-system get baremetalmachine \
-l cluster.x-k8s.io/cluster-name=global \
-o custom-columns='NAME:.metadata.name,INVENTORY:.status.machineInventoryRef.name'
kubectl -n cpaas-system get machineregistration.elemental.cattle.io
kubectl -n cpaas-system get baremetalcluster global \
-o jsonpath='{.spec.encryptionProviderConfigRef.name}{"\n"}'
创建 ConfigMap。将清单和 MachineRegistration 占位符替换为上述命令输出的值。对于 DR,还要替换加密 provider Secret 占位符;对于未设置 BaremetalCluster.spec.encryptionProviderConfigRef 的非 DR 安装,请移除该 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: "customresourcedefinitions.apiextensions.k8s.io"
names:
- baremetalclusters.infrastructure.cluster.x-k8s.io
- baremetalmachines.infrastructure.cluster.x-k8s.io
- baremetalmachinetemplates.infrastructure.cluster.x-k8s.io
- machineinventorypools.infrastructure.cluster.x-k8s.io
- machineinventories.elemental.cattle.io
- machineregistrations.elemental.cattle.io
- seedimages.elemental.cattle.io
method: kubectl
- resource: "baremetalclusters.infrastructure.cluster.x-k8s.io"
names: ["global"]
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/baremetalclusters/cpaas-system/"
method: etcdctl
- resource: "baremetalmachinetemplates.infrastructure.cluster.x-k8s.io"
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/baremetalmachinetemplates/cpaas-system/"
method: etcdctl
- resource: "baremetalmachines.infrastructure.cluster.x-k8s.io"
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/baremetalmachines/cpaas-system/"
method: etcdctl
- resource: "machineinventorypools.infrastructure.cluster.x-k8s.io"
etcdKeyBase: "/registry/infrastructure.cluster.x-k8s.io/machineinventorypools/cpaas-system/"
method: etcdctl
- resource: "machineinventories.elemental.cattle.io"
names:
- "<global-machine-inventory-1>"
- "<global-machine-inventory-2>"
- "<global-machine-inventory-3>"
etcdKeyBase: "/registry/elemental.cattle.io/machineinventories/cpaas-system/"
method: etcdctl
- resource: "machineregistrations.elemental.cattle.io"
names: ["<global-machine-registration>"]
etcdKeyBase: "/registry/elemental.cattle.io/machineregistrations/cpaas-system/"
method: etcdctl
# Required for DR when BaremetalCluster.spec.encryptionProviderConfigRef is set.
# The name must match the Secret referenced by BaremetalCluster.
- resource: "secrets"
names:
- "<global-encryption-provider-config-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
不要导入计划或 Kubeadm Bootstrap Secret
安装程序会在其批量 etcd 导入完成创建所有者对象之前应用 method: kubectl 额外资源。此时导入的 Elemental 计划 Secret 仍引用 bootstrap MachineInventory UID;该所有者 UID 不标识之后导入的 Global MachineInventory,因此 Kubernetes 垃圾回收可能会删除该 Secret。不要将 Elemental 计划 Secret、KubeadmConfig 对象或 Machine.spec.bootstrap.dataSecretName Secret 添加到此 ConfigMap。
第一版本的全新安装交接流程会显式处理此顺序。它会验证所有目标 Global MachineInventory 对象是否存在,从仍在运行的 bootstrap 集群读取每个源计划 Secret,并使用活动 Global MachineInventory UID 作为控制器所有者来创建 Global 计划 Secret。
KubeadmConfig 对象和 kubeadm bootstrap 数据 Secret 不会被第一版本的全新安装交接流程使用,也不得添加到此 ConfigMap。
继续执行步骤 8 之前,请检查渲染后的 ConfigMap:
- 它包含裸金属 CRD、
BaremetalCluster/global、裸金属机器资源、确切的 Global MachineInventory 对象以及 Global MachineRegistration。
- 它不包含
SeedImage 对象、MachineRegistration 令牌 Secret、Elemental 计划 Secret、KubeadmConfig 对象或 kubeadm bootstrap 数据 Secret。
- 对于 DR,它仍包含加密 provider Secret,且其名称与
BaremetalCluster.spec.encryptionProviderConfigRef.name 完全匹配。
bootstrap SeedImage 会生成指向 bootstrap 环境的 ISO;在 global 集群完成交接后,它不再是正确的生命周期对象。导入 seedimages.elemental.cattle.io CRD 只是为了让新的 global 集群理解该 API 类型。
对于 DR,请在安装后确认最终的 global 集群包含导入的加密 provider Secret。
kubectl --kubeconfig <global-kubeconfig> -n cpaas-system \
get secret <global-encryption-provider-config-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 都使用相同的端点路径,但请求正文不同。
每个 console.host 条目都是裸地址:不带 https:// 前缀,也不带端口。端口来自 console.httpPort 和 console.httpsPort。
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 使用相同的地址。
VMware vSphere 使用与 DCS 相同的安装程序端点路径。请求正文在 console.host 和 cluster.features.ha 中携带控制平面 VIP;无论使用外部 LoadBalancer 还是 Self-built VIP,方式都相同;相同的地址也在 VSphereCluster.spec.controlPlaneEndpoint.host 中声明。集群 CIDR 不在此处发送,而是在 VMware vSphere Cluster 清单中声明。
mkdir -p /root/yamls
export INSTALLER_CONFIG_JSON="/root/yamls/installer-config-vsphere.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": {
"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}"
无论该地址由外部 LoadBalancer 还是 Self-built VIP 提供,都将 console.host 和 cluster.features.ha.vip 设置为本地 global 控制平面 VIP,即 ${CONTROL_PLANE_VIP}。不要在 console.host 中使用平台域名;请使用 console.globalHost 作为平台访问地址。VSphereCluster.spec.controlPlaneEndpoint.host 使用相同的地址。
HCS 使用与 DCS 相同的安装程序端点路径,但其请求正文不包含 cluster.features.ha。控制平面 VIP 由 HCSCluster.spec.controlPlaneLoadBalancer 中声明的 HCS ELB 所拥有。
mkdir -p /root/yamls
export INSTALLER_CONFIG_JSON="/root/yamls/installer-config-hcs.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": {}
}
},
"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 作为平台访问地址。HCSCluster.spec.controlPlaneLoadBalancer.vipAddress 使用相同的地址。
Bare Metal 安装程序请求包含切换后使用的控制平面端点。在 Internal 模式下,Alive 暴露 Self-built VIP。在 External 模式下,User-Provisioned 负载均衡器暴露相同的端点并维护控制平面后端。将 REGISTRY_DOMAIN 设置为 ${PLATFORM_HOST}:11443。保持 registry.externalAddress 未设置,以便安装程序部署并填充本地平台 Registry。不要在此字段中使用 bootstrap Registry 地址。
在 DR 对中,两个 global 集群使用相同的 ${PLATFORM_HOST}:11443 Registry 地址。这样可以使两侧的 ProductBase.spec.registry.address 和两个 cpaas.io/registry-address Cluster 注解保持一致,从而 etcd Synchronizer 将其作为无操作进行复制,而不会使用主集群的值覆盖备用集群的值。每个 global 集群仍运行自己的 Registry;平台域名只需解析到当前处于活动状态的集群。
Registry 凭证遵循平台域名
每个 global 集群都有自己的 cpaas-system/registry-admin 凭证,但平台域名一次只解析到其中一个集群。当你将构件推送到 ${PLATFORM_HOST}:11443 时——例如 provider package 或 Global Cluster Disaster Recovery(以前称为 etcd Synchronizer)package——请使用域名当前指向的集群的 Registry 凭证进行身份验证。故障转移后,域名会指向另一个集群,因此要使用的凭证也会随之改变。凭证错误会导致 unable to retrieve auth token: invalid username/password,但这并不表示你访问了错误的集群。跨站点的匿名拉取不受影响。
mkdir -p /root/yamls
export INSTALLER_CONFIG_JSON="/root/yamls/installer-config-baremetal.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 置备速度、镜像拉取时间以及所选插件的数量。
你将观察到的阶段
安装期间的信号
同时查看安装程序进度 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
常见停滞情况及查看位置
此处未列出的问题通常指向特定于环境的原因。收集安装程序日志、进度 API 响应以及相关的 kubectl describe 输出,然后升级处理。
可选灾难恢复部署
部署用于灾难恢复的主集群和备用 global 集群时,请使用本节。在为每个 global 集群应用特定于 provider 的清单之前,先完成这些附加步骤。
开始之前,请完成双向 DR 网络要求。在两个集群 VIP 都配置了所需的负载均衡器侦听器,且集群间网络规则允许所需流量双向传输之前,不要启动任何一侧的安装。正常运行期间使用的方向是从备用集群到主集群;故障转移后方向会反转。
Bare Metal Fresh-install Scope
本节中的 Bare Metal 拆分认证操作步骤仅适用于全新安装主集群和备用 global 集群。本节不定义或验证现有 Bare Metal DR 集群对的原地升级或迁移,也不涵盖故障环境的修复或恢复。对于这些生命周期任务,请使用经过单独验证的运维手册。
以下小节并非适用于所有 provider。请根据你的基础设施完成相应小节:
将仅适用于 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 字段
在两个安装环境的 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 数据。
无需引用 VSphereCluster 加密 Secret。对于 VMware vSphere,请在主、备用安装环境的 KubeadmControlPlane.spec.kubeadmConfigSpec.files 中都保留此文件条目。两侧渲染出的 /etc/kubernetes/encryption-provider.conf 内容必须完全相同,包括 provider 顺序、键名和 base64 密钥值。此外,还要在两个安装环境中创建第 7 步中的 VMware vSphere dcs-import-extra-resources ConfigMap,以便安装程序导入 vSphere 基础架构资源和 global-vsphere-credentials Secret。
- path: /etc/kubernetes/encryption-provider.conf
owner: "root:root"
append: false
permissions: "0644"
content: |
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: ${ENCRYPTION_PROVIDER_SECRET_B64}
主、备用安装环境中的 DR serverCertSANs 列表必须保持一致。
无需引用 HCSCluster 加密 Secret。对于 HCS,请在主、备用安装环境的 KubeadmControlPlane.spec.kubeadmConfigSpec.files 中追加此文件条目。两侧渲染出的 /etc/kubernetes/encryption-provider.conf 内容必须完全相同,包括 provider 顺序、键名和 base64 密钥值。
- path: /etc/kubernetes/encryption-provider.conf
owner: "root:root"
append: false
permissions: "0644"
content: |
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: ${ENCRYPTION_PROVIDER_SECRET_B64}
主、备用安装环境中的 DR serverCertSANs 列表必须保持一致。
在两个安装环境中创建第 7 步中的 HCS dcs-import-extra-resources ConfigMap。将 PROVIDER_SECRET_NAME 设置为 HCSCluster.spec.identityRef.name 使用的同一 Secret 名称。
在主、备用安装环境的 minialauda 中创建加密 provider Secret。该 Secret 必须与 BaremetalCluster 位于同一命名空间,并且必须包含名为 encryption-provider.conf 的键。
kubectl create secret generic "${BAREMETAL_ENCRYPTION_PROVIDER_SECRET}" \
--from-file=encryption-provider.conf="${ENCRYPTION_PROVIDER_CONF}" \
-n cpaas-system \
--dry-run=client -o yaml | kubectl apply -f -
在 BaremetalCluster.spec.encryptionProviderConfigRef 中引用该 Secret。
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: BaremetalCluster
metadata:
name: global
namespace: cpaas-system
spec:
encryptionProviderConfigRef:
name: global-encryption-provider-config
裸金属 provider 会读取此 Secret,并将 /etc/kubernetes/encryption-provider.conf 注入生成的控制平面 bootstrap 数据中。对于 Bare Metal DR,不要再手动将该文件添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.files;BaremetalCluster 引用才是唯一可信来源。
此 Secret 还必须包含在第 7 步中的 Bare Metal dcs-import-extra-resources ConfigMap 中。它不能只保留在 bootstrap 集群中,因为移交后的 global 集群会保留导入的 BaremetalCluster 对象,并且还必须拥有被引用的 Secret,以供后续 provider 调谐使用。
主、备用安装环境中的 DR serverCertSANs 列表必须保持一致。
还要保留准备共享 ServiceAccount 签名密钥中的共享 ServiceAccount 签名密钥配置。没有此密钥,备用 API 服务器就无法验证现有主机在故障转移前获得的 baremetal-system-agent 令牌。
在两个安装环境中创建第 7 步中的 Bare Metal dcs-import-extra-resources ConfigMap。该 ConfigMap 必须导入移交所需的持久化裸金属和 elemental owner 资源,包括 BaremetalCluster.spec.encryptionProviderConfigRef 引用的加密 provider Secret。不得导入 SeedImage、Elemental plan Secrets、KubeadmConfig 对象或 kubeadm bootstrap data Secrets;全新安装的移交流程只有在所有目标 Global MachineInventory 对象都存在后,才会创建初始 Global plan Secrets。
两侧的 Bare Metal provider AppRelease 都必须为 Global 机器启用 Global 本地身份、共享 workload 身份和直接 kube-apiserver 移交。在创建 AppRelease 后、调用安装程序 API 前,分别在两侧的 bootstrap 集群上应用以下补丁之一。
在主 bootstrap 集群上:
kubectl -n cpaas-system patch apprelease cluster-api-provider-baremetal \
--type=merge \
-p '{"spec":{"values":{"handoffHook":{"directAPIServer":true},"elemental":{"systemAgent":{"authMode":"shared","splitAuthEnabled":true,"serviceAccountName":"baremetal-system-agent","globalServiceAccountName":"baremetal-global-system-agent","sharedAuthReadOnly":false}}}}}'
在备用 bootstrap 集群上:
kubectl -n cpaas-system patch apprelease cluster-api-provider-baremetal \
--type=merge \
-p '{"spec":{"values":{"handoffHook":{"directAPIServer":true},"elemental":{"systemAgent":{"authMode":"shared","splitAuthEnabled":true,"serviceAccountName":"baremetal-system-agent","globalServiceAccountName":"baremetal-global-system-agent","sharedAuthReadOnly":true}}}}}'
在每个 bootstrap 集群上验证生效的值:
export EXPECTED_SHARED_AUTH_READ_ONLY=false # Use true on the standby bootstrap cluster.
kubectl -n cpaas-system get apprelease cluster-api-provider-baremetal -o json | \
jq -e --argjson expected_read_only "${EXPECTED_SHARED_AUTH_READ_ONLY}" '
.spec.values.handoffHook.directAPIServer == true and
.spec.values.elemental.systemAgent.splitAuthEnabled == true and
.spec.values.elemental.systemAgent.sharedAuthReadOnly == $expected_read_only
'
在此命令成功之前,请勿继续。主集群必须使用 sharedAuthReadOnly: false;在主到备用的同步处于活动状态时,备用集群必须使用 sharedAuthReadOnly: true。
最终的 bootstrap AppRelease 值如下:
handoffHook:
controlPlaneVIP: <current-side-control-plane-vip>
directAPIServer: true
delivery:
enabled: true
mode: always
elemental:
systemAgent:
authMode: shared
splitAuthEnabled: true
serviceAccountName: baremetal-system-agent
globalServiceAccountName: baremetal-global-system-agent
tls:
agentTLSMode: strict
caCertSecretName: dex.tls
caCertSecretKey: ca.crt
请将 bootstrap 和最终证书输入分开。第 3 步中创建的 bootstrap dex.tls 有专用的 ca.crt 密钥,仅用于 https://<bootstrap-host-ip>:12443。不要将其导入最终集群。在每个最终 Global 集群上,由安装程序管理的 dex.tls 会在 tls.crt 中包含平台完整证书链;因此,最终集群上的 Bare Metal provider 必须使用 elemental.tls.caCertSecretKey: tls.crt。在每个最终 global 集群上安装 provider 时,按照在 global 集群上安装 Cluster API providers中的说明,在 provider 的 ModuleInfo 的 spec.valuesOverride 中设置此值。此处无需覆盖 agentTLSMode 和 caCertSecretName:其默认值 strict 和 dex.tls 与上述 bootstrap 值相符。
Final Global provider values
elemental:
tls:
caCertSecretKey: tls.crt
最终 operator 将此证书链用于其注册端点。直接 API 移交仍会从 Secret/cpaas-system/baremetal-global-system-agent-token 的 ca.crt 密钥获取 kube-apiserver CA;不要在 Global 主机的直接 kubeconfig 中用 dex.tls/tls.crt 替换此 CA。
elemental.systemAgent.splitAuthEnabled 是 elemental-operator 和移交作业共用的唯一开关。不要单独配置移交身份验证启用开关。采用单一可信来源,可避免 operator 已将共享 Role 限定为 workload plans 后,Global 主机仍保留共享令牌。
分别在两侧设置共享 bundle 所有权:在 bootstrap release 上应用上述补丁,并按照在 global 集群上安装 Cluster API providers中的说明,在每个最终 global 集群的 provider ModuleInfo 中设置。活动主集群负责调谐 workload 权限,而非活动备用集群则将同步的共享 bundle 保持为只读:
Primary active Global
elemental:
systemAgent:
sharedAuthReadOnly: false
Standby inactive Global
elemental:
systemAgent:
sharedAuthReadOnly: true
安装使用两组互不重叠的 system-agent 权限:
Role/cpaas-system/baremetal-global-system-agent 仅包含属于本地 Cluster/global 的机器的 plan Secret 名称。其 ServiceAccount 和 token Secret 仅存在于本地 global 集群中,不得同步到对端。
Role/cpaas-system/baremetal-system-agent 仅包含非 global workload plan Secret 名称。其 ServiceAccount、token Secret、Role 和 RoleBinding 会从主集群同步到备用集群。
使用带命名空间的 Role 和 RoleBinding 对象,不要使用 ClusterRoleBinding。不要授予命名空间范围的 Secret 访问权限,也不要包含 registry、bootstrap、集群访问凭证或平台凭证 Secret。
使用标准 system-agent 名称
将 Global 本地对象命名为 baremetal-global-system-agent 和 baremetal-global-system-agent-token,并将 workload 共享对象命名为 baremetal-system-agent 和 baremetal-system-agent-token。etcd-sync 精确同步和忽略规则使用这些固定名称。除非在同步开始前同时更新并验证每一条对应的 etcd-sync 规则,否则不要自定义这些名称。
对于此全新的 DR 安装,每个最终 Global 集群都必须在 ConfigMap/cpaas-system/baremetal-system-agent-handoff 中报告 data.ready: "true"。这是清理 bootstrap 的信号,而非完整的验收结果:还必须验证下面的运行时端点、令牌、CA、plan 反馈和 RBAC 边界。全新安装不使用 RoleBinding/cpaas-system/baremetal-global-system-agent-handoff-bridge;该项必须不存在。不要手动修补完成信号。
安装主集群和备用集群
对主、备用 global 集群分别执行第 1 至第 9 步。
使用两个独立的 bootstrap 节点
对于每种 provider,都要使用两个独立的 bootstrap 节点,分别用于主、备用安装。不要让两侧复用同一个 bootstrap 集群:在移交完成之前,bootstrap 环境会承载安装程序、内置 Registry 以及其所安装的 global 集群的 provider 资源;对于 Bare Metal,还包括 AppRelease 对象、Registry Secrets、MachineRegistration、SeedImage 和移交状态。共用一个 bootstrap 环境会污染两个 global 安装,并可能导致移交或清理操作针对错误的一侧。
两侧请使用对应 provider 的安装程序配置差异:
对于主集群,请确保平台域名解析到主控制平面 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
从此处到本小节末尾的所有内容仅适用于 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
只有 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。
配置参数如下:
安装前,在备用集群上创建令牌 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 后,插件安装才会继续。
打开同步路径前,确认规则源报告 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
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 会引用以下内容,但不会创建它们。请先按顺序准备:
-
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
-
VM 模板——上传 Alauda OS 镜像并据此创建 VM 模板;记录其名称,以便填入 vmTemplateName。请使用 4.2.1+ 模板,以便在节点替换期间分离并重新附加 /var/cpaas 持久磁盘。请参阅机器模板。
-
计算集群、分布式虚拟交换机、端口组和数据存储——选择目标 DCS 计算集群、分布式虚拟交换机、端口组,以及具有足够可用容量的数据存储。它们分别填入 resource、dvSwitchName、portGroupName 和 datastoreName。请参阅DCS 平台容量和放置。
-
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 上创建集群。
-
需要读取的版本和 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}
要替换的值
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 固定该密钥。