安装 global 集群
本文介绍如何在 Alauda OS 上安装 global 集群。global 集群是平台控制平面,通过 Cluster API 进行置备。当平台控制平面必须运行在 Alauda OS 等不可变操作系统上时,请使用此路径。
对于 Huawei DCS 上的 global 集群,请使用为此 版本发布的 Alauda OS 镜像创建 VM 模板。请参阅下载 Alauda OS。
适用场景
在满足以下所有条件时选择此安装路径:
- 您希望
global 集群运行在不可变操作系统上。目前支持的镜像是 Alauda OS。
- 您的基础设施属于有文档说明的 provider:Huawei DCS、VMware vSphere、Huawei Cloud Stack 或裸金属。
- 您可以运行一个临时 bootstrap 节点,该节点能够访问目标 IaaS 平台。
对于 Ubuntu 或 RHEL 等非 Alauda OS,请改用在非 Alauda OS 上安装。
通用先决条件
以下先决条件适用于每个 provider:
- 一个满足Bootstrap 节点要求的 bootstrap 节点。灾难恢复部署需要两个节点,分别用于主集群和备用
global 集群;请参阅安装主集群和备用集群。
- 来自 Customer Portal 的 Core Package。
- 来自 Common ACP Extensions 的八个必需 Aligned Extension 软件包,其 版本和架构必须与 Core Package 相同。请参阅必需的 Aligned Extension。
- Alauda Container Platform Cluster Bootstrap Provider 软件包。
- 适用于目标平台的基础设施 provider 软件包。
- bootstrap 节点与目标 IaaS 平台 API 端点之间的网络可达性。请参阅网络。
- 为
global 控制平面和工作节点规划 IP 和主机名。有关各 provider 使用的资源模型,请参阅基础设施资源。
global 集群的稳定 Kubernetes API 端点。创建集群前,请在规划控制平面端点中选择并验证特定于 provider 的端点模式。
- 平台访问地址、registry 地址以及 Pod 和 Service CIDR 范围。如果要使用客户管理的外部平台镜像 registry,请先完成准备;请参阅准备外部平台镜像 registry。
- 对于使用 ACP 提供的 Alauda OS 镜像的 x86_64 节点,其底层 CPU 必须支持
x86-64-v2 ISA 基线。请参阅操作系统支持矩阵。
命名约定(必需)
此规则适用于此安装路径支持的每个基础设施 provider,包括 Huawei DCS、Huawei Cloud Stack、VMware vSphere 以及未来添加的任何 provider。您在步骤 4 中编写的每个 manifest 都必须遵循此规则。这些资源命名错误会导致两种不同的故障模式,具体如下;一种会破坏初始置备,另一种只会在灾难恢复期间出现。
- CAPI
Cluster 和 provider 的基础设施集群资源(例如 Huawei DCS 的 DCSCluster 或 Huawei Cloud Stack 的 HCSCluster;每个 provider 都有其对应资源)必须严格命名为 global。cpaas-installer 会按字面名称查找这些资源;只有在基础设施集群命名为 global 时,Huawei Cloud Stack provider 才会分配全局 ELB 监听器端口(registry 和控制台使用 11443,DR etcd 同步使用 2379,Web 访问使用 443)。使用其他名称会静默破坏 registry 拉取、DR etcd 同步和 Web 控制台。
- 每个其他 CAPI 资源(
KubeadmControlPlane、KubeadmConfigTemplate、MachineDeployment)以及每个其他 provider 基础设施资源(machine template、IP/主机名池、machine config pool 和其他任何特定于 provider 的资源)都必须使用带有 global- 前缀的名称。DR(故障转移)机制使用此前缀来识别由 global 集群拥有的资源。不带 global- 前缀的 global 集群资源对 DR 不可见,并会导致备用集群的机器在故障转移时被删除——集群会正常完成置备并运行,但首次执行 DR 时会丢失节点。这是硬性要求,而非样式约定。
Cluster.spec.controlPlaneRef.name 及其他所有交叉引用都必须与带前缀的名称完全匹配。
Bootstrap 节点要求
bootstrap 节点是一台运行 Core Package 中 setup.sh 的临时机器。在安装期间,它承载基于 KIND 的 minialauda 管理集群、内置平台 registry、Cluster API bootstrap 和基础设施 provider,以及 cpaas-installer。它不会加入 global 集群,交接完成后即会移除——请参阅停用 Bootstrap 集群。
请根据其提供的镜像负载而非稳定运行时负载来规划规格:在集群置备期间,每个 global 节点都会从此主机拉取平台镜像集。
硬件
CPU 和内存最低要求来自此安装路径的标准交付实践。在非 Alauda OS 安装路径中,运行 setup.sh 的机器会成为第一个控制平面节点,因此必须满足前提条件中的控制平面最低要求;此处的 bootstrap 节点不会加入集群——它只提取 Core Package、运行临时的基于 KIND 的 bootstrap 集群,并在交接前提供平台 registry。这是较轻的临时负载,4 个核心 / 8 GB 是标准交付所采用并验证的规格。
在 arm64 上,请遵循同一页面中的平台 ARM 约定:至少使用 x86_64 规格的 1.5 倍,最好使用 2 倍,因此最低要求为 6 个核心 / 12 GB,推荐配置为 8 个核心 / 16 GB。
磁盘是最常导致 bootstrap 停滞的资源。请从以下三个方面规划磁盘空间:
操作系统
bootstrap 节点不运行 Alauda OS。它需要一个带有 Bash 和 root 权限的 64 位 Linux 发行版,以便 setup.sh 能够启动步骤 2 中所述的 bootstrap 集群。
节点预处理中的要求——经过验证的 OS 和内核列表、SSH 用户以及 sshd_config 设置——不适用于此安装路径中的 bootstrap 节点。这些要求针对的是平台通过 SSH 加入集群的非 Alauda OS 机器,而 bootstrap 节点不会加入 global 集群。这与非 Alauda OS 安装路径不同,在该路径中,运行 setup.sh 的机器会成为第一个控制平面节点,因此必须满足这些要求。
在没有其他约束时,从该经过验证的列表中选择一个发行版仍然是安全的默认选择。
x86-64-v2 基线适用于集群节点,不适用于 bootstrap 节点
OS 支持矩阵中的 x86-64-v2 ISA 基线适用于从 ACP 提供的 Alauda OS 镜像创建的集群节点。平台不会对运行用户提供的非 Alauda OS 的 x86_64 机器强制实施该基线,因此它不是 bootstrap 节点本身的要求。请参阅节点预处理。
如果 bootstrap 节点是运行集群节点的同一基础设施上的虚拟机,检查其 CPU 标志可以及早发现缺少基线的问题。但这不能替代在实际运行 Alauda OS 节点的主机上验证 x86-64-v2,因为来宾机可见的标志取决于 hypervisor 的 CPU 模型。
网络
bootstrap 节点需要一个在整个安装生命周期内保持不变的静态 IP 地址。HOST_IP 会写入置备节点用于拉取镜像的 cpaas.io/registry-address annotation;对于裸金属,还会写入注册端点和其服务证书的 SAN。安装过程中途更改地址会导致节点置备失败。
兼容性和版本输入
安装前,记录交付包支持的版本集:
安装生命周期

使用以下阶段图确定安装每个部分的输入、预期结果和首个诊断位置。
操作步骤
步骤 1 — 准备通用变量
在引导节点上设置通用变量。
export HOST_IP="<bootstrap-host-ip>"
export LOCAL_REGISTRY_ADDRESS="127.0.0.1:11443"
export BOOTSTRAP_REGISTRY_ADDRESS="172.18.0.1:11443"
export NODE_REGISTRY_ADDRESS="${HOST_IP}:11443"
export CONTROL_PLANE_VIP="<global-control-plane-vip>"
export PLATFORM_HOST="<platform-access-domain-or-vip>"
export REGISTRY_DOMAIN="<platform-registry-domain-or-vip>:11443"
export CLUSTER_CIDR="100.3.0.0/16"
export SERVICE_CIDR="100.4.0.0/16"
export KUBE_OVN_JOIN_CIDR="<kube-ovn-join-cidr>"
export K8S_VERSION="<target-kubernetes-version>"
export INGRESS_CLASS_NAME="global-alb2"
export PROVIDER_SECRET_NAME="global-secret"
# Use v-prefixed semver that matches the target Alauda OS image.
从引导节点推送软件包时使用 LOCAL_REGISTRY_ADDRESS。在 AppRelease chart 仓库值中使用 BOOTSTRAP_REGISTRY_ADDRESS,因为 provider Pod 会从引导集群的网络内部读取 chart 仓库。由于置备的 global 节点在置备期间必须通过其子网可访问的地址拉取镜像,因此在 Cluster API registry annotation 中使用 NODE_REGISTRY_ADDRESS(引导节点的 registry,即 <bootstrap-host-ip>:11443)。这是一个临时值:global 集群自身的 registry 启动后,安装程序会自动将 Cluster 和 DCSCluster 上的 cpaas.io/registry-address annotation 重写为永久平台 registry,因此后续调谐会从 global 集群而不是引导节点拉取镜像。
请区分 Registry 的格式:
/v2/ 片段仅属于 Registry HTTP API 请求。不要将其添加到镜像引用、cpaas.io/registry-address annotation 或 AppRelease repoURL 中。
步骤 2 — 创建引导集群
使用 Bash 运行 Core Package 提供的引导脚本。该脚本会在引导节点上创建一个名为 minialauda 的临时、基于 KIND 的引导集群——这是仅用于置备 global 集群的临时 Cluster API 管理集群。完成后,将引导控制平面容器中的匹配 kubectl 客户端提供给引导节点,并配置导出的 kubeconfig。
mkdir -p /root/cpaas-install
tar -xvf <core-package> -C /root/cpaas-install
cd /root/cpaas-install/installer
Core Package 不包含所需的 Aligned Extension 软件包。在运行 setup.sh 之前,将 Required Aligned Extensions 中列出的八个软件包分别复制到解压后的 Core Package 的 plugins/ 目录中。如果缺少这些软件包,则无法安装所需的 Aligned Extensions。每个软件包运行一次以下命令:
cp <path-to-extension-package> /root/cpaas-install/installer/plugins/
下载的文件名为 <package-name>.<arch>.<version>.tgz。请保持文件名不变。这八个软件包名称为 console、console-central、console-marketplace、application-catalog、aiops-base、container-platform、acp-business 和 gatewayapi-plugin。保留 plugins/ 中已有的文件,并确认八个软件包均已存在:
ls -1 /root/cpaas-install/installer/plugins/
不要将 Common ACP Extensions 中的其他软件包复制到此目录中。Cluster Bootstrap Provider 和基础设施 provider 软件包也不复制到此处;步骤 3 会将它们上传到引导 Registry。
然后运行引导脚本。要使用外部平台镜像 registry,请使用 bash setup.sh --registry "<registry-address>" 替代 bash setup.sh。Registry 地址采用 <host-or-IP>[:port] 格式,不包含 http:// 或 https://。如果 registry 需要身份验证,还要同时提供拉取 --username 和 --password;有关如何在不暴露密码的情况下输入这些值,请参阅 Start the Installer。按照 Prepare an External Platform Image Registry 中的说明,在执行此步骤前准备好 registry。
bash setup.sh
mkdir -p "${HOME}/.local/bin" ~/.kube
if ! command -v kubectl >/dev/null 2>&1; then
nerdctl cp minialauda-control-plane:/usr/bin/kubectl \
"${HOME}/.local/bin/kubectl"
chmod 0755 "${HOME}/.local/bin/kubectl"
export PATH="${HOME}/.local/bin:${PATH}"
fi
cp /var/cpaas/data/alauda.kubeconfig ~/.kube/config
kubectl get nodes
引导脚本会置备内置 registry、Cluster API 控制平面以及驱动 global 集群安装的安装程序组件。
默认情况下,内置引导 Registry 为匿名访问,不会创建 global-registry-auth Secret。如果在引导设置期间同时配置了 Registry 用户名和密码,setup.sh 会在 cpaas-system 中创建该 Secret。
步骤 3 — 上传并安装 Provider 软件包
将 Cluster Bootstrap Provider 软件包和基础设施 provider 软件包上传到本地 registry。
下面的 AppRelease 示例使用默认的匿名引导 Registry,因此不引用 global-registry-auth。
如果引导 Registry 需要身份验证,请在创建任何 provider AppRelease 之前确认 Secret 存在:
kubectl -n cpaas-system get secret global-registry-auth -o name
然后在应用每个 provider AppRelease 之前添加这两个身份验证字段:
spec:
source:
chartPullSecret: global-registry-auth
values:
global:
registry:
imagePullSecrets:
- global-registry-auth
如果 Registry 为匿名访问,则不要创建占位 Secret,也不要添加这些字段。引导 Secret 也不是凭证传输机制:默认情况下,业务集群通过 global 集群的 public-registry-credential 流程接收其 Registry 拉取 Secret;也可以按照 Choose the Image Registry for a Workload Cluster 中的说明,将业务集群绑定到专用 registry。不要将引导集群的 global-registry-auth Secret 复制到业务集群中。
为什么每个 provider 的 cluster.type 都是 Baremetal
下面选项卡中的 AppRelease 值均设置了 global.cluster.type: Baremetal。这是 chart 内部的分类器,而不是 IaaS provider 名称。在 Huawei DCS、VMware vSphere、Huawei Cloud Stack 和 Bare Metal 的 global 安装中,都应保留 Baremetal。该值决定平台如何配置节点级组件;它不会选择基础设施 provider。
设置 provider 软件包路径和 chart 版本。
export DCS_PROVIDER_PACK="/root/cluster-api-provider-dcs.amd64.<version>.tgz"
export KUBEADM_PROVIDER_PACK="/root/cluster-api-provider-kubeadm.amd64.<version>.tgz"
export DCS_PROVIDER_VERSION="<dcs-provider-chart-version>"
export KUBEADM_PROVIDER_VERSION="<kubeadm-provider-chart-version>"
上传软件包。
/root/cpaas-install/installer/res/amd64/packtool pack push \
-r "${LOCAL_REGISTRY_ADDRESS}" -c "${DCS_PROVIDER_PACK}"
/root/cpaas-install/installer/res/amd64/packtool pack push \
-r "${LOCAL_REGISTRY_ADDRESS}" -c "${KUBEADM_PROVIDER_PACK}"
创建并应用 Cluster Bootstrap Provider 和 DCS provider 的 AppRelease 资源。
mkdir -p /root/yamls
export DCS_PROVIDER_APPRELEASES="/root/yamls/dcs-provider-appreleases.yaml"
cat > "${DCS_PROVIDER_APPRELEASES}" <<EOF
---
apiVersion: operator.alauda.io/v1alpha1
kind: AppRelease
metadata:
annotations:
auto-recycle: "true"
interval-sync: "true"
name: cluster-api-provider-kubeadm
namespace: cpaas-system
spec:
destination:
cluster: ""
namespace: ""
source:
charts:
- name: acp/chart-cluster-api-provider-kubeadm
releaseName: cluster-api-provider-kubeadm
targetRevision: ${KUBEADM_PROVIDER_VERSION}
repoURL: ${BOOTSTRAP_REGISTRY_ADDRESS}
timeout: 120
values:
global:
albName: ${INGRESS_CLASS_NAME}
auth:
default_admin: admin@cpaas.io
cluster:
isGlobal: true
name: global
networkType: kube-ovn
type: Baremetal
host: ${PLATFORM_HOST}
ingress:
ingressClassName: ${INGRESS_CLASS_NAME}
labelBaseDomain: cpaas.io
namespace: cpaas-system
platformUrl: https://${PLATFORM_HOST}
protectSecretFiles:
enabled: false
region: global
registry:
address: ${BOOTSTRAP_REGISTRY_ADDRESS}
replicas: 1
scheme: https
---
apiVersion: operator.alauda.io/v1alpha1
kind: AppRelease
metadata:
annotations:
auto-recycle: "true"
interval-sync: "true"
name: cluster-api-provider-dcs
namespace: cpaas-system
spec:
destination:
cluster: ""
namespace: ""
source:
charts:
- name: acp/chart-cluster-api-provider-dcs
releaseName: cluster-api-provider-dcs
targetRevision: ${DCS_PROVIDER_VERSION}
repoURL: ${BOOTSTRAP_REGISTRY_ADDRESS}
timeout: 120
values:
global:
albName: ${INGRESS_CLASS_NAME}
auth:
default_admin: admin@cpaas.io
cluster:
isGlobal: true
name: global
networkType: kube-ovn
type: Baremetal
host: ${PLATFORM_HOST}
ingress:
ingressClassName: ${INGRESS_CLASS_NAME}
labelBaseDomain: cpaas.io
namespace: cpaas-system
platformUrl: https://${PLATFORM_HOST}
protectSecretFiles:
enabled: false
region: global
registry:
address: ${BOOTSTRAP_REGISTRY_ADDRESS}
replicas: 1
scheme: https
EOF
kubectl apply -f "${DCS_PROVIDER_APPRELEASES}"
until kubectl get crd kubeadmcontrolplanes.controlplane.cluster.x-k8s.io --ignore-not-found 2>/dev/null | grep -q kubeadmcontrolplanes.controlplane.cluster.x-k8s.io; do
sleep 10
done
until kubectl get crd dcsclusters.infrastructure.cluster.x-k8s.io --ignore-not-found 2>/dev/null | grep -q dcsclusters.infrastructure.cluster.x-k8s.io; do
sleep 10
done
设置 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}"
创建并应用 Cluster Bootstrap 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: acp/chart-cluster-api-provider-kubeadm
releaseName: cluster-api-provider-kubeadm
targetRevision: ${KUBEADM_PROVIDER_VERSION}
repoURL: ${BOOTSTRAP_REGISTRY_ADDRESS}
timeout: 120
values:
global:
albName: ${INGRESS_CLASS_NAME}
auth:
default_admin: admin@cpaas.io
cluster:
isGlobal: true
name: global
networkType: kube-ovn
type: Baremetal
host: ${PLATFORM_HOST}
ingress:
ingressClassName: ${INGRESS_CLASS_NAME}
labelBaseDomain: cpaas.io
namespace: cpaas-system
platformUrl: https://${PLATFORM_HOST}
protectSecretFiles:
enabled: false
region: global
registry:
address: ${BOOTSTRAP_REGISTRY_ADDRESS}
replicas: 1
scheme: https
---
apiVersion: operator.alauda.io/v1alpha1
kind: AppRelease
metadata:
annotations:
auto-recycle: "true"
interval-sync: "true"
name: cluster-api-provider-vsphere
namespace: cpaas-system
spec:
destination:
cluster: ""
namespace: ""
source:
charts:
- name: acp/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}"
创建并应用 Cluster Bootstrap 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: acp/chart-cluster-api-provider-kubeadm
releaseName: cluster-api-provider-kubeadm
targetRevision: ${KUBEADM_PROVIDER_VERSION}
repoURL: ${BOOTSTRAP_REGISTRY_ADDRESS}
timeout: 120
values:
global:
albName: ${INGRESS_CLASS_NAME}
auth:
default_admin: admin@cpaas.io
cluster:
isGlobal: true
name: global
networkType: kube-ovn
type: Baremetal
host: ${PLATFORM_HOST}
ingress:
ingressClassName: ${INGRESS_CLASS_NAME}
labelBaseDomain: cpaas.io
namespace: cpaas-system
platformUrl: https://${PLATFORM_HOST}
protectSecretFiles:
enabled: false
region: global
registry:
address: ${BOOTSTRAP_REGISTRY_ADDRESS}
replicas: 1
scheme: https
---
apiVersion: operator.alauda.io/v1alpha1
kind: AppRelease
metadata:
annotations:
auto-recycle: "true"
interval-sync: "true"
name: cluster-api-provider-hcs
namespace: cpaas-system
spec:
destination:
cluster: ""
namespace: ""
source:
charts:
- name: acp/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 镜像布局归档,其中包含两个具有相同 tag 的镜像,两个镜像都不可缺少——请参阅 Image Format by Provider:
使用 ctr 将归档导入引导节点上的专用 containerd namespace,并列出它创建的镜像名称。/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
输出会列出两个具有相同 tag 的镜像:
baremetal-base-image-iso:<os-image-tag>
baremetal-base-image:<os-image-tag>
将 OS_IMAGE_TAG 设置为该 tag,然后推送两个镜像,并确认 registry 为每个镜像列出了该 tag:
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
如果引导 Registry 需要身份验证,请使用 -u <username>:<password> 将凭证传递给两个命令。将镜像保留在 bare-metal-os namespace 中:Upload the base image to the global Registry 会在安装后再次推送这些镜像。
创建并应用 Cluster Bootstrap 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 引导端点
在引导期间,global 集群尚未将控制权移交给最终 VIP。将裸金属注册路径保留在引导节点上:global.platformUrl 指向引导节点,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 添加到该引导 MachineRegistration;这是移交前唯一需要的 DR 特定 system-agent annotation。DR 移交 Job 稍后会将这些机器迁移到 https://<CONTROL_PLANE_VIP>:6443 的本地直接 API 端点。
在安装 Bare Metal provider 之前,准备 global-alb2 使用的引导 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。由于引导端点为 https://${HOST_IP}:12443,服务证书必须将 ${HOST_IP} 作为 IP SAN。
如果引导集群中有 cert-manager,请使用引导本地 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,并确认该证书对引导端点有效:
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)"
如果引导 ALB 仍提供旧证书,请重启它并再次验证:
kubectl -n cpaas-system rollout restart deploy/global-alb2
kubectl -n cpaas-system rollout status deploy/global-alb2
不要将此引导 dex.tls 包含在 dcs-import-extra-resources 中。它仅用于临时引导端点。最终的 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: acp/chart-cluster-api-provider-kubeadm
releaseName: cluster-api-provider-kubeadm
targetRevision: ${KUBEADM_PROVIDER_VERSION}
repoURL: ${BOOTSTRAP_REGISTRY_ADDRESS}
timeout: 120
values:
global:
albName: ${INGRESS_CLASS_NAME}
auth:
default_admin: admin@cpaas.io
cluster:
isGlobal: true
name: global
networkType: kube-ovn
type: Baremetal
host: ${PLATFORM_HOST}
ingress:
ingressClassName: ${INGRESS_CLASS_NAME}
labelBaseDomain: cpaas.io
namespace: cpaas-system
platformUrl: https://${PLATFORM_HOST}
protectSecretFiles:
enabled: false
region: global
registry:
address: ${BOOTSTRAP_REGISTRY_ADDRESS}
replicas: 1
scheme: https
---
apiVersion: operator.alauda.io/v1alpha1
kind: AppRelease
metadata:
annotations:
auto-recycle: "true"
interval-sync: "true"
name: cluster-api-provider-baremetal
namespace: cpaas-system
spec:
destination:
cluster: ""
namespace: ""
source:
charts:
- name: acp/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。请先应用 Optional Disaster Recovery Deployment 中按角色划分的 DR patch,然后在该引导集群上验证生效的 AppRelease 值。
provider 启动后,验证 chart 值是否已被接受。如果出现带有未知 flag(例如 --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;请修复 AppRelease,然后再应用 global manifest。
步骤 4 — 配置 Provider 特定的全局 Manifest
为 global 集群创建一个 provider 特定的 manifest。该 manifest 使用与业务集群相同的 provider 资源,但还必须包含平台控制平面所需的全局专用标签、annotation、registry 值、与安装程序兼容的 kubeadm 设置以及持久化数据路径。
使用 provider 创建指南作为详细资源参考:
将 Common Prerequisites 中的命名约定 应用于你在下面编写的 manifest 中的每个资源。
将 KubeadmControlPlane.spec.kubeadmConfigSpec.format 设置为目标 provider 接受的值。这是一个 API 字段:输入 cloud-config,而不是 cloud-init。对于下面列出的 provider,cloud-init 是接收并应用生成的 cloud-config 数据的客户机操作系统软件。Provider controller 会强制执行该字段值:
在渲染 DCS global manifest 之前,设置其输出路径。
export GLOBAL_DCS_YAML="/root/yamls/new-global.yaml"
按照Cloud Credentials中的说明,在组装 manifest 之前创建 DCS API 凭证 Secret。不要将其作为 YAML 写入 manifest:第 5 步使用 kubectl apply 应用 manifest,这会将凭证记录在 Secret 的 kubectl.kubernetes.io/last-applied-configuration annotation 中。manifest 通过 DCSCluster.spec.credentialSecretRef 引用该凭证。
然后,DCS global manifest 必须在 cpaas-system namespace 中包含以下资源:
使用在 Huawei DCS 上创建集群和Huawei DCS 基础设施资源中的 DCS 资源字段。对于 global 集群,还需满足以下要求:
- 将
Cluster.metadata.name 和 DCSCluster.metadata.name 设置为 global(基础设施集群与 CAPI 共享 Cluster 名称)。为其他每个 CAPI 资源和 provider 资源添加 global- 前缀;下面的连接片段使用 KubeadmControlPlane.metadata.name: global-kcp。
- 将
DCSCluster.spec.credentialSecretRef.name 设置为 ${PROVIDER_SECRET_NAME}。第 7 步会将此 Secret 导入最终的 global 集群。
- 添加
Cluster.metadata.labels.is-global: "true" 和 Cluster.metadata.labels.cluster-type: DCS。
- 添加包含
${NODE_REGISTRY_ADDRESS} 的 Cluster.metadata.annotations["cpaas.io/registry-address"]。
- 为 Alauda OS 设置
KubeadmControlPlane.spec.kubeadmConfigSpec.format: ignition。
- 使用外部 LoadBalancer 并设置
DCSCluster.spec.controlPlaneLoadBalancer.type: external,或使用 type: internal Self-built VIP。对于内部模式,在控制平面 Layer 2 网络中预留 IPv4 VIP,并使用在 Huawei DCS 上创建集群中的 YAML 工作流。对于外部模式,按照规划控制平面端点中的说明,验证 listener、后端、运行状况检查和可达性。
- 保留
KubeadmControlPlane.spec.kubeadmConfigSpec.users 条目,并确保其 sshAuthorizedKeys 列表非空(boot 用户)。DCS ignition 格式会拒绝空的 SSH 密钥列表,因此,即使你不计划通过 SSH 访问 global 集群,也必须设置此字段。如果不需要交互式密钥,请参阅解析占位符值了解应提供的值。
- 保留非加密 kubeadm 文件、kubelet 补丁、审计策略和安装程序 RBAC 条目。文件内容(PodSecurity admission 配置、kubelet 补丁和审计策略)以及完整的
clusterConfiguration、preKubeadmCommands、postKubeadmCommands、init 和 join 节点注册补丁,都与业务集群相同。请从完整的 KubeadmControlPlane 配置附录中复制这些内容,或引用其中说明的 dcs-kubernetes-<major.minor>-files Secret。下面的连接片段仅展示叠加在该基础配置之上的 global 特定字段。
- 对于正常的非 DR 部署,不要设置
DCSCluster.spec.encryptionProviderConfigRef。按照附录中的配置保留 clusterConfiguration.apiServer.extraArgs.encryption-provider-config:DCS provider 会自行将 /etc/kubernetes/encryption-provider.conf 写入每个控制平面节点——第一个机器使用随机密钥,其余机器使用正在运行的 API server 文件——因此该参数可以解析,并且 global 集群会启用 etcd 静态数据加密。不要将该文件添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.files;provider 会丢弃该路径的任何条目,因此添加在其中的条目会被静默忽略。请参阅如何交付加密 provider 配置。
- 将
/var/cpaas 保留为平台状态。如果需要在滚动替换期间保留磁盘,请在 DCSIpHostnamePool.spec.pool[].persistentDisk 中声明它;不要依赖 DCSMachineTemplate 模板磁盘来保留状态。
- 对 DCS 本地存储使用具体的
datastoreName 值,除非你已确认所选 datastore 集群可以将卷放置在能够运行目标 VM 的主机上。
片段范围
以下 YAML 是差异片段,不是可以直接应用的完整 manifest。将这些 global 特定变更合并到根据 DCS 创建集群参考资料准备的 manifest 中,然后应用完整的 manifest 文件。如果希望从完整文件开始,而不是从片段组装,请在本页末尾调整示例:Huawei DCS 的完整 global Manifest。
以下片段展示了 global 特定的 Cluster API 连接配置。使用上面的 DCS 创建集群参考资料填写 provider 资源字段。
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: global
namespace: cpaas-system
labels:
cluster-type: DCS
is-global: "true"
annotations:
capi.cpaas.io/resource-group-version: infrastructure.cluster.x-k8s.io/v1beta1
capi.cpaas.io/resource-kind: DCSCluster
cpaas.io/registry-address: "${NODE_REGISTRY_ADDRESS}"
spec:
clusterNetwork:
pods:
cidrBlocks:
- ${CLUSTER_CIDR}
services:
cidrBlocks:
- ${SERVICE_CIDR}
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
name: global-kcp
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DCSCluster
name: global
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
name: global-kcp
namespace: cpaas-system
annotations:
controlplane.cluster.x-k8s.io/skip-kube-proxy: ""
spec:
replicas: 3
version: ${K8S_VERSION}
rolloutStrategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
machineTemplate:
nodeDrainTimeout: 1m
nodeDeletionTimeout: 5m
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DCSMachineTemplate
name: global-cp-template
kubeadmConfigSpec:
format: ignition
clusterConfiguration:
etcd:
local:
serverCertSANs:
- "${CONTROL_PLANE_VIP}"
- "${PLATFORM_HOST}"
在渲染 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 annotation 中。
- vCenter 凭证,其名称必须为
global-vsphere-credentials——第 7 步的导入 ConfigMap 和 DR 部分都硬编码了这一确切名称。按照在 VMware vSphere 上创建集群中的说明,使用该固定名称创建凭证,并从 VSphereCluster.spec.identityRef.name 引用它。
- vSphere CPI 凭证,由
ClusterResourceSet 引用。按照同一指南中的说明创建该凭证。
然后,VMware vSphere global manifest 必须在 cpaas-system namespace 中包含以下资源:
使用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 annotations,包括 VMware vSphere 创建集群指南中的网络和 CPI annotations。
-
将 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,按照规划控制平面端点中的约定先完成配置和验证。global 集群也可以使用 internal Self-built VIP:完成Self-built VIP 先决条件,然后将启用 Self-built VIP中的 spec.controlPlaneLoadBalancer 块添加到 global VSphereCluster 中。
Self-built VIP 先决条件命令会从正在运行的 global 集群读取 ModulePlugin/alive,但在 bootstrap 安装期间该集群尚不存在。请改为从安装软件包中确认其包含 alive plugin。
-
设置 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}。步骤 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 集群仅支持创建,无法升级。
片段范围
以下 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}"
在渲染 Bare Metal global manifest 之前,设置其输出路径。
export GLOBAL_BAREMETAL_YAML="/root/yamls/new-global.yaml"
Bare Metal global manifest 必须在 cpaas-system 命名空间中包含以下资源:
将在裸金属上创建集群、管理裸金属节点和 Bare Metal 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},即步骤 3 中导入的 ISO 镜像。重新置备镜像来自步骤 3 中设置的 elemental-image-catalog 条目;provider 会将该条目的 registry 部分替换为 cpaas.io/registry-address annotation,因此主机会从 ${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。
- 在规划控制平面端点中选择 Bare Metal 端点模式。对于
Internal,设置 VIP、端口 6443 和唯一的 vrid。对于 External,设置外部负载均衡器前端地址和端口,并省略 vrid。
- 对于正常的非 DR 部署,可以省略
BaremetalCluster.spec.encryptionProviderConfigRef。对于 DR 部署,按照可选的灾难恢复部署中的说明进行设置;不要通过将 /etc/kubernetes/encryption-provider.conf 添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.files 来交付它。
- 对于 Bare Metal 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 秒以内。请参阅主机时间同步。
- 不要依赖 OS 主机名的副作用。裸金属 provider 会根据 CAPI 和清单对象规范化 kubeadm 节点名称和 provider ID。
- 在 bootstrap 集群中创建的
SeedImage 是 bootstrap 工件。交接后,在活动的 global 集群上创建任何新的 MachineRegistration 或 SeedImage。
片段范围
以下 YAML 是差异片段,不是可以直接应用的完整 manifest。将这些 global 特定变更合并到根据 Bare Metal 创建集群参考资料准备的 manifest 中,然后应用完整的 manifest 文件。
以下片段展示了 global 特定的 Cluster API 连接配置。使用 Bare Metal 创建集群参考资料填写清单名称、注册配置、镜像引用和可选的工作节点资源。
对于 provider 管理的 Alive,将 <control-plane-load-balancer-type> 设置为 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}"
等待 bootstrap 注册生成预期的资源清单,然后再期待 Cluster API 协调取得进展。
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 和 Bare Metal,它还会导入 provider 的 Cluster API 资源;对于 Huawei DCS,这些资源由内置流程迁移,因此正常安装时,DCS ConfigMap 只需包含凭证 Secret 条目。正常安装和灾难恢复 global 安装都需要 VMware vSphere、Huawei Cloud Stack 和 Bare Metal 的 ConfigMap。
对于灾难恢复安装,通过 Secret 引用提供加密 provider 配置的两个 provider——Huawei DCS(DCSCluster.spec.encryptionProviderConfigRef)和 Bare Metal(BaremetalCluster.spec.encryptionProviderConfigRef)——还必须通过此 ConfigMap 导入该 Secret。内置的 DCS 迁移流程只迁移 Cluster API 资源,不会复制所引用的 Secret。
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
在触发安装程序之前,创建并应用 Bare Metal 导入 ConfigMap。此 ConfigMap 会导入持久化的裸金属和 elemental owner 资源,全新安装交接流程将使用这些资源枚举已安装的 Global 机器。不得导入 Elemental plan Secret 或 kubeadm bootstrap data Secret。
在 DCS API 之前导入
在调用 POST /cpaas-installer/api/config/dcs 之前创建 dcs-import-extra-resources。如果缺少该对象,交接作业可能会使用空的目标列表运行,因为新的 global 集群中不包含用于描述 bootstrap global 机器的 BaremetalMachine、MachineInventory 或 MachineRegistration 对象。不要通过将这些机器的 plan Secret 或 kubeadm bootstrap data Secret 添加到 ConfigMap 来弥补。
从已协调的 BaremetalMachine 对象中收集确切的 Global MachineInventory 名称,并确定这些主机使用的 bootstrap MachineRegistration。对于 DR,还要收集 BaremetalCluster.spec.encryptionProviderConfigRef 引用的 Secret 名称,以便最终的 global 集群包含相同的加密 provider 配置。不要导入任意 platform 凭证 Secret 或 MachineRegistration token 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。将 inventory 和 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
不要导入 Plan 或 Kubeadm Bootstrap Secret
安装程序会在批量 etcd 导入完成并创建其 owner 对象之前应用 method: kubectl extras。此时导入的 Elemental plan Secret 仍引用 bootstrap MachineInventory UID;该 owner UID 并不对应之后导入的 Global MachineInventory,因此 Kubernetes 垃圾回收可能会删除该 Secret。不要将 Elemental plan Secret、KubeadmConfig 对象或 Machine.spec.bootstrap.dataSecretName Secret 添加到此 ConfigMap。
第一版全新安装交接流程会显式处理此顺序。它会验证所有目标 Global MachineInventory 对象均已存在,从仍在运行的 bootstrap 集群读取每个源 plan Secret,并使用活动 Global MachineInventory UID 作为其控制器 owner 来创建 Global plan Secret。
第一版全新安装交接流程不会使用 KubeadmConfig 对象和 kubeadm bootstrap data Secret,不得将其添加到此 ConfigMap。
继续执行步骤 8 之前,请检查渲染后的 ConfigMap:
- 包含 Bare Metal CRD、
BaremetalCluster/global、Bare Metal 机器资源、确切的 Global MachineInventory 对象以及 Global MachineRegistration。
- 不包含
SeedImage 对象、MachineRegistration token Secret、Elemental plan Secret、KubeadmConfig 对象或 kubeadm bootstrap data 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"}'
向嵌入式安装程序 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 指定。
如果在步骤 2 中启动 bootstrap 脚本时使用了外部 platform 镜像 registry,请在请求的 registry 对象中,将 registry.externalAddress 设置为 registry 地址,以替代 registry.domain;不要同时设置两者。保留示例中的 registry.username 和 registry.password:它们是用于从 registry 拉取镜像的凭证。如果 registry 允许匿名拉取,则省略这两个字段。安装程序不会将镜像或软件包复制到外部 registry,因此目标版本的负载必须已上传;请参阅准备外部平台镜像 Registry。
DCS 安装程序请求包含 global 控制平面 VIP。使用外部负载均衡器时,这是外部预配的 VIP。使用 Self-built VIP 时,请使用 provider 管理的 alive 运行时所拥有的保留 VIP。
mkdir -p /root/yamls
export INSTALLER_CONFIG_JSON="/root/yamls/installer-config-dcs.json"
cat > "${INSTALLER_CONFIG_JSON}" <<EOF
{
"basic": {
"username": "admin@cpaas.io",
"password": "<base64-platform-admin-password>"
},
"registry": {
"domain": "${REGISTRY_DOMAIN}",
"username": "<registry-username>",
"password": "<base64-registry-password>"
},
"console": {
"host": [
"${CONTROL_PLANE_VIP}"
],
"globalHost": "${PLATFORM_HOST}",
"httpPort": 80,
"httpsPort": 443,
"cert": {
"selfSigned": {}
}
},
"cluster": {
"clusterCIDR": "${CLUSTER_CIDR}",
"serviceCIDR": "${SERVICE_CIDR}",
"features": {
"ha": {
"vip": "${CONTROL_PLANE_VIP}",
"vport": 6443,
"isThirdParty": true
}
}
},
"product": [
"base",
"acp"
],
"deployMode": "normal",
"hostIP": "${HOST_IP}"
}
EOF
curl -k -X POST "http://${INSTALLER_IP}:8080/cpaas-installer/api/config/dcs" \
-H 'Content-Type: application/json' \
-d @"${INSTALLER_CONFIG_JSON}"
将 console.host 设置为本地 global 控制平面 VIP,即 ${CONTROL_PLANE_VIP}。不要在 console.host 中使用 platform 域名;请使用 console.globalHost 作为 platform 访问地址。cluster.features.ha.vip 使用相同的地址。
VMware vSphere 使用与 DCS 相同的安装程序端点路径。请求正文在 console.host 和 cluster.features.ha 中携带控制平面 VIP;对于外部负载均衡器和 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}"
无论该地址由外部负载均衡器还是 Self-built VIP 提供,都将 console.host 和 cluster.features.ha.vip 设置为本地 global 控制平面 VIP,即 ${CONTROL_PLANE_VIP}。不要在 console.host 中使用 platform 域名;请使用 console.globalHost 作为 platform 访问地址。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 中使用 platform 域名;请使用 console.globalHost 作为 platform 访问地址。HCSCluster.spec.controlPlaneLoadBalancer.vipAddress 使用相同的地址。
Bare Metal 安装程序请求包含交接后使用的控制平面端点。在 Internal 模式下,Alive 提供 Self-built VIP。在 External 模式下,User-Provisioned 负载均衡器提供相同的端点,并维护控制平面后端。使用 platform 内置 Registry 时,将 REGISTRY_DOMAIN 设置为 ${PLATFORM_HOST}:11443。保持 registry.externalAddress 未设置,以便安装程序部署并填充本地 platform Registry。不要在此字段中使用 bootstrap Registry 地址。
在使用 platform 内置 Registry 的 DR 对中,两个 global 集群使用相同的 ${PLATFORM_HOST}:11443 Registry 地址。这样可使两侧的 ProductBase.spec.registry.address 和两个 cpaas.io/registry-address Cluster 注解保持一致,因此 Global Cluster Disaster Recovery 会将其作为无操作进行复制,而不会使用主集群的值覆盖备用集群的值。每个 global 集群仍运行自己的 Registry;platform 域名只需解析到当前处于活动状态的集群。
Registry 凭证跟随 platform 域名
每个 global 集群都有自己的 cpaas-system/registry-admin 凭证,但 platform 域名一次只解析到其中一个。将制品推送到 ${PLATFORM_HOST}:11443 时(例如 provider 软件包或 Global Cluster Disaster Recovery 软件包),请使用当前域名所指向集群的 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 中使用 platform 域名;请使用 console.globalHost 作为 platform 访问地址。cluster.features.ha.vip 使用相同的地址。
第三方 Console 证书
示例使用自签名 Console 证书。如果环境要求使用第三方证书,请在提交安装程序请求前,将 console.cert 替换为 thirdParty 块,其中包含以 base64 编码的完整证书链、私钥以及可选的 PKCS#12 值。此证书仅用于 platform HTTPS 入口;不会配置由 KubeadmControlPlane 生成的 kube-apiserver 或 etcd 证书。端口 11443 上的 Global registry 使用独立管理的 global-registry-server 证书链,而不是 console.cert 或 dex.tls。
DR 证书要求
对于主/备用 Bare Metal global DR 部署,不要让两侧分别生成互不相关的自签名证书。请在两侧配置相同的受信任 thirdParty platform 证书。其必需的 SAN 是稳定的 ${PLATFORM_HOST} 域名。默认情况下,不要添加主集群或备用集群控制平面 VIP,也不要添加内部 Service 名称。只有当客户端有意通过该 VIP 直接访问 platform HTTPS 时,才添加 VIP SAN。通过端口 11443 访问 Registry 不使用此证书;Registry 证书管理是独立的,不属于此证书步骤。交接给 https://<control-plane-vip>:6443 的 Global 主机将验证独立的 kube-apiserver 证书和 CA,而不是 console.cert 或 dex.tls。
步骤 9 — 监控安装
安装程序接受请求后,安装会经过多个阶段;这些阶段可从 bootstrap 节点观察。Alauda OS 上一个典型的 global 集群需要 30–60 分钟;总耗时取决于 IaaS 预配速度、镜像拉取时间以及所选插件数量。
可观察到的阶段
安装期间的信号
同时查看安装程序进度 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 server 接受。组成每个 global 集群的机器仍使用各自集群本地的身份。
准备共享 DR 变量
在主集群和备用集群的安装环境中设置相同的加密密钥值。
export ENCRYPTION_PROVIDER_CONF="/root/yamls/encryption-provider.conf"
export ENCRYPTION_PROVIDER_SECRET_B64="<base64-shared-etcd-encryption-key>"
export PRIMARY_CLUSTER_VIP="<primary-ha-vip>"
export STANDBY_CLUSTER_VIP="<standby-ha-vip>"
export DCS_ENCRYPTION_PROVIDER_SECRET="encryption-provider-config"
export BAREMETAL_ENCRYPTION_PROVIDER_SECRET="global-encryption-provider-config"
export SERVICE_ACCOUNT_ISSUER="https://kubernetes.default.svc.cluster.local"
在两个安装环境中创建加密 provider 配置文件。
mkdir -p "$(dirname "${ENCRYPTION_PROVIDER_CONF}")"
cat > "${ENCRYPTION_PROVIDER_CONF}" <<EOF_CONF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: ${ENCRYPTION_PROVIDER_SECRET_B64}
EOF_CONF
Bare Metal:准备共享 ServiceAccount 签名密钥
Bare Metal only
只有 Bare Metal 需要共享 ServiceAccount 签名密钥。Huawei DCS、VMware vSphere 和 Huawei
Cloud Stack 部署请跳过本节——它们共享加密 provider 配置,但两侧各自保留自己的签名密钥。不要将这些 files 条目添加到非 Bare Metal 的
KubeadmControlPlane 中。
仅生成一次 ServiceAccount 签名密钥,并在主集群和备用 KubeadmControlPlane 清单中使用相同的文件。只有两侧使用相同密钥签名时,备用 API server 才会接受在主集群上创建的固定 baremetal-system-agent 令牌。
mkdir -p /root/global-dr-sa
openssl genrsa -out /root/global-dr-sa/sa.key 2048
openssl rsa -in /root/global-dr-sa/sa.key -pubout -out /root/global-dr-sa/sa.pub
chmod 0600 /root/global-dr-sa/sa.key
chmod 0644 /root/global-dr-sa/sa.pub
kubectl -n cpaas-system create secret generic global-sa-signing-key \
--from-file=sa.key=/root/global-dr-sa/sa.key \
--from-file=sa.pub=/root/global-dr-sa/sa.pub \
--dry-run=client -o yaml | kubectl apply -f -
将以下条目添加到主集群和备用集群的 KubeadmControlPlane.spec.kubeadmConfigSpec 中。两侧的文件内容以及 issuer/audience 值必须完全一致。
files:
- path: /etc/kubernetes/pki/sa.key
owner: root:root
permissions: "0600"
contentFrom:
secret:
name: global-sa-signing-key
key: sa.key
- path: /etc/kubernetes/pki/sa.pub
owner: root:root
permissions: "0644"
contentFrom:
secret:
name: global-sa-signing-key
key: sa.pub
clusterConfiguration:
apiServer:
extraArgs:
service-account-key-file: /etc/kubernetes/pki/sa.pub
service-account-signing-key-file: /etc/kubernetes/pki/sa.key
service-account-issuer: https://kubernetes.default.svc.cluster.local
api-audiences: https://kubernetes.default.svc.cluster.local
controllerManager:
extraArgs:
service-account-private-key-file: /etc/kubernetes/pki/sa.key
集群安装完成后,在两侧各选取一个控制平面节点,验证文件和 kubeadm 静态 Pod 参数。
sha256sum /etc/kubernetes/pki/sa.key /etc/kubernetes/pki/sa.pub
grep -E 'service-account-issuer|api-audiences|service-account-key-file|service-account-signing-key-file' \
/etc/kubernetes/manifests/kube-apiserver.yaml
grep -E 'service-account-private-key-file' \
/etc/kubernetes/manifests/kube-controller-manager.yaml
将 DR etcd Server 证书 SAN 添加到 KubeadmControlPlane
在第 4 步生成的清单中,将您正在安装的集群的控制平面 VIP、平台访问地址和 etcd.kube-system 添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.clusterConfiguration.etcd.local.serverCertSANs 中。每个安装环境使用自己的列表:主集群仅列出 ${PRIMARY_CLUSTER_VIP},备用集群仅列出 ${STANDBY_CLUSTER_VIP}。这些值用于配置 kubeadm 生成的 etcd server 证书;它们独立于平台 console.cert,不得复制到其 thirdParty SAN 列表中。
Primary installation environment
serverCertSANs:
- "${PRIMARY_CLUSTER_VIP}"
- "${PLATFORM_HOST}"
- "etcd.kube-system"
Standby installation environment
serverCertSANs:
- "${STANDBY_CLUSTER_VIP}"
- "${PLATFORM_HOST}"
- "etcd.kube-system"
添加 provider 特定的 DR 字段
在两个安装环境中,分别在 bootstrap 集群中创建加密 provider Secret。该 Secret 必须与 DCSCluster 位于同一命名空间,并且必须包含一个名为 encryption-provider.conf 的键。
kubectl create secret generic "${DCS_ENCRYPTION_PROVIDER_SECRET}" \
--from-file=encryption-provider.conf="${ENCRYPTION_PROVIDER_CONF}" \
-n cpaas-system \
--dry-run=client -o yaml | kubectl apply -f -
在 DCSCluster.spec 中添加对该 Secret 的引用。
encryptionProviderConfigRef:
name: encryption-provider-config
DCS 使用 DCSCluster.spec.encryptionProviderConfigRef 来传递灾难恢复加密 provider 配置。每次构建控制平面 bootstrap 数据时,DCS provider 都会读取此 Secret。对于 DCS DR 路径,不要将 /etc/kubernetes/encryption-provider.conf 添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.files 中。
还必须将此 Secret 包含在第 7 步的 DCS dcs-import-extra-resources ConfigMap 中。它不能只留在 bootstrap 集群中,因为移交后的 global 集群会保留 DCSCluster 对象及其 encryptionProviderConfigRef,并且还必须拥有被引用的 Secret,以供后续 provider 调和使用。内置的 DCS 迁移流程会迁移 Cluster API 资源,但不会复制此 Secret。
在两个安装环境中创建 DCS dcs-import-extra-resources ConfigMap,并使用第 7 步中 ConfigMap 的 DR 形式,而不是常规形式。将 PROVIDER_SECRET_NAME 设置为 DCSCluster.spec.credentialSecretRef.name 使用的同一 Secret 名称,并确保 DCS_ENCRYPTION_PROVIDER_SECRET 与 DCSCluster.spec.encryptionProviderConfigRef.name 保持一致。
缺少 Secret 不会立即报错,而是在替换控制平面机器时才会失败
设置 DCSCluster.spec.encryptionProviderConfigRef 后,DCS provider 会要求存在被引用的 Secret,不会回退到生成或读取配置。不会创建控制平面机器的调和不受影响,因此,如果 Secret 留在 bootstrap 集群中,安装看起来会正常运行,直到需要新的控制平面机器时才会出问题——例如 Kubernetes 升级、KubeadmControlPlane 滚动更新或控制平面节点替换。随后,新建的 Machine 会停留在 Provisioning 中,且不会创建 VM;provider 日志会记录 failed to create vm: failed to resolve user data: get encryption provider config secret。
检查现有 DR 对的两侧;如果缺少 Secret,请补充创建:
kubectl --kubeconfig <global-kubeconfig> -n cpaas-system get dcscluster global \
-o jsonpath='{.spec.encryptionProviderConfigRef.name}{"\n"}'
kubectl --kubeconfig <global-kubeconfig> -n cpaas-system get secret <name>
使用安装时所用的同一 encryption-provider.conf 重新创建该 Secret,以确保新控制平面机器继续使用运行中的 API 服务器已在使用的密钥。如果该文件已不在 bootstrap 节点上,请从同一集群中正在运行的控制平面节点复制它(/etc/kubernetes/encryption-provider.conf)。不同的密钥会导致无法解密 DR 对已有的 etcd 数据。
无需引用 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 etcd Server 证书 SAN 添加到 KubeadmControlPlane 中的说明,使用各安装环境自己的 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 etcd Server 证书 SAN 添加到 KubeadmControlPlane 中的说明,使用各安装环境自己的 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 etcd Server 证书 SAN 添加到 KubeadmControlPlane 中的说明,使用各安装环境自己的 DR serverCertSANs 列表。
还要保留准备共享 ServiceAccount 签名密钥中的共享 ServiceAccount 签名密钥配置。缺少此密钥时,备用 API 服务器无法验证现有主机在故障转移前收到的 baremetal-system-agent 令牌。
在两个安装环境中创建第 7 步的 Bare Metal dcs-import-extra-resources ConfigMap。该 ConfigMap 必须导入移交所需的持久化裸金属和 Elemental 所有者资源,包括 BaremetalCluster.spec.encryptionProviderConfigRef 所引用的加密 provider Secret。不得导入 SeedImage、Elemental plan Secret、KubeadmConfig 对象或 kubeadm bootstrap 数据 Secret;只有在目标 Global 的所有 MachineInventory 对象都存在后,全新安装的移交流程才会创建初始 Global plan Secret。
两侧的 Bare Metal provider AppRelease 都必须启用 Global 本地身份、共享 workload 身份,以及 Global 机器的直接 kube-apiserver 移交。创建 AppRelease 后、调用安装程序 API 前,在各侧的 bootstrap 集群上应用以下某个 patch。
在主 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 中包含平台完整证书链;因此,最终 Global 集群上的 Bare Metal provider 必须使用 elemental.tls.caCertSecretKey: tls.crt。
最终 Global 集群不会继承 bootstrap release 值。在每个最终 Global 集群上安装 Bare Metal provider 时,请按安装集群插件中的说明,将以下值及下文的 sharedAuthReadOnly 一并设置在 acp/chart-cluster-api-provider-baremetal 下该 provider 的 ModuleInfo 中的 spec.valuesOverride。将 agentTLSMode 和 caCertSecretName 保持为默认值(strict 和 dex.tls),不要复制其他 bootstrap 值,例如 handoffHook.*:
Final Global provider values
elemental:
tls:
caCertSecretKey: tls.crt
systemAgent:
splitAuthEnabled: true
最终 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 plan 后,Global 主机仍保留共享令牌。
分别在两侧设置共享 bundle 所有权:在 bootstrap release 中应用上述 patch,并在每个最终 Global 集群的 provider ModuleInfo 中设置(请参阅安装集群插件)。每个最终 Global 集群保留自己的值;DR 同步不会复制 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;该对象必须不存在。不要手动 patch 完成信号。
安装主集群和备用集群
对主、备用 global 集群分别执行第 1 步至第 9 步。
使用两个独立的 bootstrap 节点
对每个 provider 使用两个独立的 bootstrap 节点,一个用于主集群安装,一个用于备用集群安装。不要在两侧重复使用同一个 bootstrap 集群:在移交之前,bootstrap 环境承载着安装程序、内嵌 Registry,以及其所安装的 global 集群的 provider 资源;对于 Bare Metal,还包括 AppRelease 对象、Registry Secret、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 值;只有该值一致, Global Cluster Disaster Recovery 才能复制这些键而不覆盖任何内容。
verify_baremetal_global_registry() {
kubeconfig=$1
expected_registry=$2
test "$(kubectl --kubeconfig "${kubeconfig}" \
get productbase.product.alauda.io base \
-o jsonpath='{.spec.registry.address}')" = "${expected_registry}"
test "$(kubectl --kubeconfig "${kubeconfig}" \
get cluster.platform.tkestack.io global \
-o jsonpath='{.metadata.annotations.cpaas\.io/registry-address}')" = \
"${expected_registry}"
test "$(kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get cluster.cluster.x-k8s.io global \
-o jsonpath='{.metadata.annotations.cpaas\.io/registry-address}')" = \
"${expected_registry}"
test "$(kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get secret public-registry-credential -o jsonpath='{.data.registry}' | \
base64 -d)" = "${expected_registry}"
# Only deployments that enabled Registry authentication have this Secret. On an
# anonymous Registry it does not exist, and checking it unconditionally aborts
# the whole function under `set -e` before the remaining assertions run.
if kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get secret global-registry-auth >/dev/null 2>&1; then
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get secret global-registry-auth -o jsonpath='{.data.\.dockerconfigjson}' | \
base64 -d | jq -e --arg registry "${expected_registry}" \
'.auths[$registry] != null' >/dev/null
fi
kubectl --kubeconfig "${kubeconfig}" get apprelease -A -o json | \
jq -e --arg registry "${expected_registry}" '
all(.items[];
.spec.source.repoURL == $registry and
.spec.values.global.registry.address == $registry)
' >/dev/null
}
export GLOBAL_REGISTRY_ADDRESS="${PLATFORM_HOST}:11443"
verify_baremetal_global_registry \
"${PRIMARY_GLOBAL_KUBECONFIG}" "${GLOBAL_REGISTRY_ADDRESS}"
verify_baremetal_global_registry \
"${STANDBY_GLOBAL_KUBECONFIG}" "${GLOBAL_REGISTRY_ADDRESS}"
在移除任一 bootstrap KIND 集群前,先在两侧完成完整的移交检查。在该侧已安装的 Global 机器中,GLOBAL_HOSTS 是以空格分隔的管理地址列表。请在能够使用两个 Global kubeconfig 并通过 SSH 连接到这些机器的安全主机上运行此操作。由于运行时连接文件包含 bearer token,请保持 shell 跟踪禁用。
set -euo pipefail
set +x
export PRIMARY_GLOBAL_KUBECONFIG="<path-to-primary-global-kubeconfig>"
export STANDBY_GLOBAL_KUBECONFIG="<path-to-standby-global-kubeconfig>"
export PRIMARY_BOOTSTRAP_KUBECONFIG="<path-to-primary-bootstrap-kubeconfig>"
export STANDBY_BOOTSTRAP_KUBECONFIG="<path-to-standby-bootstrap-kubeconfig>"
export PRIMARY_GLOBAL_HOSTS="<primary-global-host-1> <primary-global-host-2> <primary-global-host-3>"
export STANDBY_GLOBAL_HOSTS="<standby-global-host-1> <standby-global-host-2> <standby-global-host-3>"
export PRIMARY_GLOBAL_REGISTRATION_NAME="<primary-global-machine-registration-name>"
export STANDBY_GLOBAL_REGISTRATION_NAME="<standby-global-machine-registration-name>"
verify_global_auth_scope() {
kubeconfig=$1
registration_name=$2
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get machineregistration.elemental.cattle.io "${registration_name}" -o json | \
jq -e '
.metadata.annotations["baremetal.cluster.io/system-agent-auth-scope"] ==
"global"
' >/dev/null
global_inventory_names="$({
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get baremetalmachines.infrastructure.cluster.x-k8s.io -o json | \
jq -c '[
.items[]
| select(
.metadata.labels["cluster.x-k8s.io/cluster-name"] == "global"
)
| .status.machineInventoryRef.name? // empty
] | unique | sort'
})"
printf '%s\n' "${global_inventory_names}" | \
jq -e 'length > 0' >/dev/null
while IFS= read -r inventory_name; do
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get machineinventory.elemental.cattle.io "${inventory_name}" -o json | \
jq -e '
.metadata.annotations["baremetal.cluster.io/system-agent-auth-scope"] ==
"global"
' >/dev/null
done < <(printf '%s\n' "${global_inventory_names}" | jq -r '.[]')
}
verify_baremetal_handoff() {
bootstrap_kubeconfig=$1
kubeconfig=$2
control_plane_vip=$3
global_hosts=$4
registration_name=$5
expected_endpoint="https://${control_plane_vip}:6443"
kubectl --kubeconfig "${bootstrap_kubeconfig}" -n cpaas-system \
wait --for=condition=complete \
job/baremetal-system-agent-handoff --timeout=30m
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get configmap baremetal-system-agent-handoff -o json | \
jq -e '.data.ready == "true"' >/dev/null
verify_global_auth_scope "${kubeconfig}" "${registration_name}"
if kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get rolebinding baremetal-global-system-agent-handoff-bridge \
>/dev/null 2>&1; then
echo "temporary handoff bridge still exists" >&2
return 1
fi
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system get \
serviceaccount/baremetal-global-system-agent \
secret/baremetal-global-system-agent-token \
role/baremetal-global-system-agent \
rolebinding/baremetal-global-system-agent >/dev/null
global_plan_names="$({
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get role baremetal-global-system-agent -o json | \
jq -c '[
.rules[]?
| select(any(.apiGroups[]?; . == ""))
| select(any(.resources[]?; . == "secrets"))
| .resourceNames[]?
] | unique | sort'
})"
printf '%s\n' "${global_plan_names}" | jq -e 'length > 0' >/dev/null
while IFS= read -r plan_secret; do
test "$({
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get secret "${plan_secret}" -o jsonpath='{.type}'
})" = "elemental.cattle.io/plan"
done < <(printf '%s\n' "${global_plan_names}" | jq -r '.[]')
expected_token_sha="$({
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get secret baremetal-global-system-agent-token \
-o jsonpath='{.data.token}' | base64 -d | \
sha256sum | awk '{print $1}'
})"
expected_ca_sha="$({
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get secret baremetal-global-system-agent-token \
-o jsonpath='{.data.ca\.crt}' | base64 -d | \
sha256sum | awk '{print $1}'
})"
for host in ${global_hosts}; do
actual_endpoint="$({
ssh "root@${host}" \
'cat /var/lib/elemental/agent/elemental_connection.json' | \
jq -r '.kubeConfig' | \
awk '$1 == "server:" {print $2; exit}'
})"
test "${actual_endpoint}" = "${expected_endpoint}"
actual_namespace="$({
ssh "root@${host}" \
'cat /var/lib/elemental/agent/elemental_connection.json' | \
jq -r '.namespace'
})"
test "${actual_namespace}" = "cpaas-system"
actual_plan_secret="$({
ssh "root@${host}" \
'cat /var/lib/elemental/agent/elemental_connection.json' | \
jq -r '.secretName'
})"
printf '%s\n' "${global_plan_names}" | \
jq -e --arg name "${actual_plan_secret}" 'index($name) != null' \
>/dev/null
actual_token_sha="$({
ssh "root@${host}" \
'cat /var/lib/elemental/agent/elemental_connection.json' | \
jq -r '.kubeConfig' | \
awk '$1 == "token:" {print $2; exit}' | \
tr -d '\r\n' | sha256sum | awk '{print $1}'
})"
test "${actual_token_sha}" = "${expected_token_sha}"
actual_ca_sha="$({
ssh "root@${host}" \
'cat /var/lib/elemental/agent/elemental_connection.json' | \
jq -r '.kubeConfig' | \
awk '$1 == "certificate-authority-data:" {print $2; exit}' | \
base64 -d | sha256sum | awk '{print $1}'
})"
test "${actual_ca_sha}" = "${expected_ca_sha}"
ssh "root@${host}" \
"grep -Fq -- '${expected_endpoint}' /oem/elemental-system-agent.yaml"
ssh "root@${host}" \
'test "$(stat -c %a /oem/elemental-system-agent.yaml)" = 600'
ssh "root@${host}" \
'grep -Fqx -- "CATTLE_AGENT_STRICT_VERIFY=\"true\"" /etc/rancher/elemental/agent/envs'
ssh "root@${host}" \
'systemctl is-active --quiet elemental-system-agent.service'
done
}
verify_baremetal_handoff \
"${PRIMARY_BOOTSTRAP_KUBECONFIG}" "${PRIMARY_GLOBAL_KUBECONFIG}" \
"${PRIMARY_CLUSTER_VIP}" \
"${PRIMARY_GLOBAL_HOSTS}" "${PRIMARY_GLOBAL_REGISTRATION_NAME}"
verify_baremetal_handoff \
"${STANDBY_BOOTSTRAP_KUBECONFIG}" "${STANDBY_GLOBAL_KUBECONFIG}" \
"${STANDBY_CLUSTER_VIP}" \
"${STANDBY_GLOBAL_HOSTS}" "${STANDBY_GLOBAL_REGISTRATION_NAME}"
还要验证两侧 Global 本地的权限边界。在首个非 global workload plan 存在之前,共享的 baremetal-system-agent ServiceAccount、token Secret、Role 和 RoleBinding 在两个集群上都可能不存在。不要仅为满足 Global 安装检查而手动创建或复制该资源集;当首个共享范围的注册被协调时,活动 operator 会创建该资源集,随后由 etcd-sync 将其复制到备用集群。
verify_global_local_system_agent_rbac() {
kubeconfig=$1
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system get \
serviceaccount/baremetal-global-system-agent \
secret/baremetal-global-system-agent-token \
role/baremetal-global-system-agent \
rolebinding/baremetal-global-system-agent >/dev/null
local_plan_names="$({
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get role baremetal-global-system-agent -o json | \
jq -c '[
.rules[]?
| select(any(.apiGroups[]?; . == ""))
| select(any(.resources[]?; . == "secrets"))
| .resourceNames[]?
] | unique | sort'
})"
printf '%s\n' "${local_plan_names}" | jq -e 'length > 0' >/dev/null
expected_local_plan_names="$({
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get machineinventories.elemental.cattle.io -o json | \
jq -c '[
.items[]
| select(
.metadata.annotations["baremetal.cluster.io/system-agent-auth-scope"] ==
"global"
)
| .status.plan.secretRef.name? // empty
] | unique | sort'
})"
test "${local_plan_names}" = "${expected_local_plan_names}"
while IFS= read -r plan_secret; do
test "$({
kubectl --kubeconfig "${kubeconfig}" -n cpaas-system \
get secret "${plan_secret}" -o jsonpath='{.type}'
})" = "elemental.cattle.io/plan"
test "$({
kubectl --kubeconfig "${kubeconfig}" \
--as=system:serviceaccount:cpaas-system:baremetal-global-system-agent \
-n cpaas-system auth can-i get "secret/${plan_secret}"
})" = yes
test "$({
kubectl --kubeconfig "${kubeconfig}" \
--as=system:serviceaccount:cpaas-system:baremetal-global-system-agent \
-n cpaas-system auth can-i patch "secret/${plan_secret}"
})" = yes
test "$({
kubectl --kubeconfig "${kubeconfig}" \
--as=system:serviceaccount:cpaas-system:baremetal-system-agent \
-n cpaas-system auth can-i get "secret/${plan_secret}"
})" = no
done < <(printf '%s\n' "${local_plan_names}" | jq -r '.[]')
for verb in list create delete; do
test "$({
kubectl --kubeconfig "${kubeconfig}" \
--as=system:serviceaccount:cpaas-system:baremetal-global-system-agent \
-n cpaas-system auth can-i "${verb}" secrets
})" = no
done
test "$({
kubectl --kubeconfig "${kubeconfig}" \
--as=system:serviceaccount:cpaas-system:baremetal-global-system-agent \
-n cpaas-system auth can-i get secret/global-registry-auth
})" = no
}
verify_global_local_system_agent_rbac "${PRIMARY_GLOBAL_KUBECONFIG}"
verify_global_local_system_agent_rbac "${STANDBY_GLOBAL_KUBECONFIG}"
不要仅凭 ready 信号判定验收通过。如果运行时 token 或 CA 不匹配、持久化 OEM 文件中的 bootstrap 端点过期、plan 探测失败或权限边界不正确,则即使 data.ready 为 true,全新移交也尚未完成。
备用集群安装成功后,将平台域名切回主入口。首次同步期间,主集群是活动源。
仅适用于 Bare Metal
只有 Bare Metal 需要自定义 etcd-sync 规则源。Huawei DCS、VMware vSphere 和 Huawei
Cloud Stack 部署可跳过本节,直接继续执行
安装 etcd-sync;其安装顺序不影响结果。
在安装 etcd-sync 之前,先在主集群和备用集群上应用此 ConfigMap。
将相同的 baremetal-dr-rules ConfigMap 应用到主集群和备用集群。使用 Bare Metal etcd-sync Rules 中的模板。将 plan 占位符替换为两个集群中当前所有 Global plan Secret 名称。两侧的移交都完成后,从 MachineInventory.status.plan.secretRef.name 读取这些名称;全新移交会创建 Global plan Secret,因此提前收集的名称已过期。插件 chart 会提供其余 Bare Metal 规则。每当添加或替换 Global plan Secret 时,都要更新此 ConfigMap。
在镜像启动前创建规则源
如果安装 etcd-sync 时已设置 active_cluster_vip 和活动集群 token,同步路径会立即开启。如果 Bare Metal 规则源尚不存在,首次同步将不会应用 Global plan Secret 排除规则,并会用主集群的 Secret 覆盖备用集群的 plan Secret——这是 故障排查 中列出的 A Global plan is overwritten 故障模式。随后,备用 Global 主机将收到属于主集群的 plans。
先创建 ConfigMap 不会产生副作用:插件安装之前,它只是一个带标签的普通 ConfigMap,不会被任何组件读取。如果必须先安装插件,请将 active_cluster_vip 留空,仅在规则源报告 accepted 后再设置。
安装 etcd-sync (ACP 4.4.0+)
仅在当前备用 Global 上安装 v4.4.0 或更高版本的插件。不要在两侧同时安装或运行镜像。
安装前,将平台域名切回主控制平面 VIP——安装备用集群时,域名曾指向备用集群。主集群是首次同步的活动源;插件启动时会解析平台域名,如果域名指向的集群本身运行着 etcd-sync,插件将拒绝启动。若域名仍指向备用集群,即将执行的安装会触发此检查。
插件包发布为 etcd-sync,而不是 global-etcd-sync:从软件包服务器下载 packages/etcd-sync/<minor>/etcd-sync.amd64.<version>.tgz。它不包含在安装程序 bundle 中,也不在 bootstrap Registry 内,因此应像其他插件包一样将其上传到目标集群。
配置如下:
安装前,在备用集群上创建 token Secret。其值为活动集群 cpaas-system/k8sadmin ServiceAccount 的 bearer token:
# On the active cluster:
kubectl -n cpaas-system get secret k8sadmin -o jsonpath='{.data.token}' | base64 -d
# On the standby cluster, with the value copied from above:
ACTIVE_CLUSTER_TOKEN='<paste-the-token-from-the-active-cluster>'
kubectl -n cpaas-system create secret generic etcd-sync-active-cluster-token \
--from-literal=token="${ACTIVE_CLUSTER_TOKEN}" \
--dry-run=client -o yaml | kubectl apply -f -
安装期间,etcd-sync-bootstrap Job 会在 etcd-sync Deployment 启动之前运行。只有在 Job 准备好 remote-etcd-ca、remote-etcd-issuer 和 remote-etcd-client 后,插件安装才会继续。
开启同步路径前,确认规则源报告 accepted,且出现在 ConfigMap/etcd-sync-rule-snapshot 中:
kubectl --kubeconfig <standby-kubeconfig> -n cpaas-system \
get configmap baremetal-dr-rules \
-o jsonpath='{.metadata.annotations.etcd-sync\.cpaas\.io/status}{"\n"}'
kubectl --kubeconfig <standby-kubeconfig> -n cpaas-system \
get configmap etcd-sync-rule-snapshot \
-o jsonpath='{.data.snapshot\.json}'
被拒绝的规则源不会处于活动状态。
启动并验证同步
触发一次 etcd-sync 监控检查。收到 HTTP 425 时重试,直到检查获准执行,然后要求遗漏键和多余键均为零。确认共享 workload system-agent 资源集要么在两侧都不存在,要么在两侧都完整存在;只在一侧存在或部分存在均视为失败。
不要仅凭 AppRelease 就绪状态作为 DR 验收结果。有关故障切换顺序和故障切换后的测试,请继续阅读 Global Cluster Disaster Recovery。
重启需要重新加载 DR 和端点配置的 Pod。在主控制平面节点和备用控制平面节点上运行相同的命令。
sudo kubectl delete po -n cpaas-system -l 'service_name in (alertmanager,vmselect,vminsert)'
sudo kubectl delete po -n cpaas-system -l service_name=cpaas-elasticsearch
sudo kubectl delete po -n cpaas-system -l service_name=cluster-transformer
有关安装后的 DR 生命周期,请参阅 Global Cluster Disaster Recovery。
验证
安装程序报告完成后,请按照 验证 中的说明,确认 global 集群运行正常,其中包括 Alauda OS 检查。
如果 global 集群安装在裸金属上,请接下来完成将 global 集群准备为管理集群。这是弃用 bootstrap 集群之前的必要步骤;交接完成后重新配置 global 节点时,会从 global Registry 拉取该节点上传的基础映像。
弃用 Bootstrap 集群
安装程序报告成功后,并且在裸金属环境中完成将 global 集群准备为管理集群后,global 集群便不再依赖 bootstrap 集群。常规验证以及下面针对特定 provider 的交接门槛均通过后,从 bootstrap 节点移除临时 bootstrap 集群——只删除本地 bootstrap 集群(minialauda)及其 KIND 容器网络。
裸金属:必须通过最终交接门槛
不要仅因为 minialauda 处于 Ready 状态、安装程序报告成功,或 baremetal-system-agent-handoff Job 处于 Complete 状态,就移除 KubeadmControlPlane。每台已导入的 Global 主机都必须先通过最终 global 集群执行的探测,之后才能停用 bootstrap 端点。
从新建的 global 集群读取最终交接记录:
kubectl --kubeconfig <global-kubeconfig> -n cpaas-system \
get configmap baremetal-system-agent-handoff \
-o jsonpath='{.data.ready}{"\n"}'
完成状态 ConfigMap 恰好包含一个键:ready。它仅用作安装程序清理门槛:记录交接和 global 侧探测已完成,不代表端点、TLS 输入或各主机最终使用的身份状态。不要手动修改 data.ready,也不要单独将其视为身份验证分离正确的证明——以下检查才能确认这一点。
只有满足以下所有条件后,才能继续:
- 当前安装程序交接钩子使用已部署的 provider 配置运行了该 Job,且 Job 成功完成。
- 在最终的
global 集群中,data.ready 为 true。
- 在最终的
global 集群中,每个已导入的目标 MachineInventory 均报告 status.plan.state: Applied。这是 global 侧探测结果,也是证明每台主机现在都向新集群报告的检查项。
- 最终集群中存在每个已导入的目标对象,以及该对象所引用的计划 Secret。
- 最终集群中的 system-agent 身份与已部署的模式一致,并且临时跨身份 RoleBinding 已被移除。
使用以下命令检查最后三项条件:
kubectl --kubeconfig <global-kubeconfig> -n cpaas-system \
get machineinventories.elemental.cattle.io \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.plan.state}{"\n"}{end}'
kubectl --kubeconfig <global-kubeconfig> -n cpaas-system \
get serviceaccount,secret,role,rolebinding \
| grep -E 'baremetal-(global-)?system-agent'
kubectl --kubeconfig <global-kubeconfig> -n cpaas-system \
get rolebinding baremetal-global-system-agent-handoff-bridge
对于 elemental.systemAgent.splitAuthEnabled: false,最终集群仅包含共享的 baremetal-system-agent 身份。对于 true,集群还包含 Global-local baremetal-global-system-agent 身份,并且两个 Role 不得指定同一个计划 Secret。最后一条命令必须报告 NotFound。
如果当前 Job 失败或超时、目标集不完整、缺少被引用的计划 Secret,或探测计划不是 Applied,请保持 minialauda 运行,并检查交接 Job 和 provider 日志。
确认 DCS Secret 已到达 global 集群
安装期间,你在步骤 7 中创建的 dcs-import-extra-resources ConfigMap 会将 DCS API 凭证 Secret 复制到 global 集群——它会导入 DCSCluster.spec.credentialSecretRef 中指定的 Secret。移除 bootstrap 节点之前,请确认该 Secret 已存在:kubectl --kubeconfig <global-kubeconfig> get secret <name> -n cpaas-system。如果缺少,请先将其复制过去——否则,你在 global 集群上安装的 DCS provider 将没有 DCS API 凭证,无法协调资源(例如,之后扩容会失败)。
对于 DR 安装,请对 DCSCluster.spec.encryptionProviderConfigRef 中指定的 Secret 执行相同检查。DCSCluster 对象会在交接后保留该引用,因此 bootstrap 集群移除后,如果缺少该 Secret,之后每次创建控制平面机器都会失败——请参阅添加特定于 provider 的 DR 字段。
不要通过删除 Cluster API 对象来清理
不要运行 kubectl delete cluster global,也不要将删除 Cluster、KubeadmControlPlane 或 provider 基础设施对象作为清理步骤。安装完成后,这些对象会管理正在运行的 global 控制平面机器;删除它们会连带删除控制平面 VM,并破坏刚安装的集群。弃用操作仅限于在 bootstrap 节点上移除本地 bootstrap 集群(其 KIND 容器);请保留 Cluster API 对象。
后续步骤
- 在创建业务集群之前,请先在
global 集群上上传并安装目标 IaaS 平台的基础设施 provider 和 Cluster Bootstrap Provider,无论 global 集群运行在哪个 IaaS 平台上:请参阅准备。对于 DR 集群对,请在两个 global 集群上都执行此操作。
- 配置业务集群的基础设施资源:请参阅基础设施资源。
- 创建第一个业务集群:请参阅在 Alauda OS 上创建。
- 规划灾难恢复:请参阅Global 集群灾难恢复。
示例:Huawei DCS global 完整清单
这是一个完整的单文件清单,用于在 Huawei DCS 上创建具有三个副本的控制平面 global 集群。它包含步骤 4 中所述的同一组资源,已整合在一起,因此你无需从不同页面合并片段。示例中使用的值仅用于文档演示:请替换每个 <placeholder>,并复用在步骤 1 中导出的 ${...} 变量。在步骤 5 中应用该清单。
此示例适用于非 DR 集群。为避免维护两份副本,这里不再重复 KubeadmControlPlane kubeadmConfigSpec 主体——它与业务集群相同,取自完整的 KubeadmControlPlane 配置附录,资源 3 中内联注明了两个 global / 非 DR 差异。
该清单不会创建 DCS API 凭证 Secret。该 Secret 已在上述先决条件中创建,并在此处通过 DCSCluster.spec.credentialSecretRef 按名称引用。步骤 7 会通过 dcs-import-extra-resources ConfigMap 将其复制到 global 集群;你必须确保其中的 names 列表与同一 Secret 名称保持一致。
应用前:在 DCS 上准备以下内容
清单会引用以下内容,但不会创建它们。请先按顺序准备好:
-
DCS API 访问权限及据此创建的凭证 Secret——端点(https://<host>:7443)、一个 administrator 用户及其密码,以及站点 ID。四项信息都需写入凭证 Secret;如果该字段留空,provider 会从中读取站点 ID 并将其写入 DCSCluster.spec.site,因此下面的清单不会设置该字段。
按照云凭证中的说明,在 cpaas-system 中创建名为 ${PROVIDER_SECRET_NAME} 的 Secret。不要将其编写为 YAML 后再应用:kubectl apply 会将密码记录在 Secret 的 kubectl.kubernetes.io/last-applied-configuration 注解中。
global 安装还要求添加清单中所有其他资源都带有的集群名称标签:
kubectl label secret "${PROVIDER_SECRET_NAME}" --namespace cpaas-system \
--overwrite cpaas.io/cluster-name=global
-
VM 模板——上传 Alauda OS 镜像并据此创建 VM 模板;记录其名称,以供 vmTemplateName 使用。请使用 4.2.1+ 模板,以便在替换节点期间可以分离并重新挂载 /var/cpaas 持久磁盘。请参阅机器模板。
-
计算集群、分布式虚拟交换机、端口组和数据存储——选择目标 DCS 计算集群、一个分布式虚拟交换机、一个端口组,以及具有足够可用容量的数据存储。它们分别对应 resource、dvSwitchName、portGroupName 和 datastoreName。请参阅DCS 平台容量与放置。
-
IP 和 API 端点——为控制平面准备三个可用的节点 IP(包含网关和 DNS);它们对应 DCSIpHostnamePool。然后选择一个符合控制平面端点约定的外部 LoadBalancer,并将 TCP 6443 转发到这三个节点;或者为 Self-built VIP 选择同一 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 值。
准备好这些内容后,即可应用下面的清单。示例使用外部 LoadBalancer。如果选择 Self-built VIP,请在创建 global 集群之前,先应用清单之后所示的内部模式替换项。
---
# 1. Control-plane IP / hostname pool (one entry per control-plane replica).
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DCSIpHostnamePool
metadata:
name: global-cp-pool
namespace: cpaas-system
labels:
cpaas.io/cluster-name: "global"
spec:
pool:
# /var/cpaas holds platform state and must survive node replacement, so it is
# declared here as a persistentDisk bound to the IP slot (not as a
# DCSMachineTemplate disk). Requires a DCS VM template 4.2.1+ and maxSurge: 0.
- ip: "192.0.2.11"
mask: "24"
gateway: "192.0.2.1"
dns: "192.0.2.2"
hostname: "global-cp-1"
machineName: "global-cp-1"
persistentDisk:
- {slot: 0, quantityGB: 100, datastoreName: <datastore-name>, path: /var/cpaas, format: xfs, mountOptions: [defaults]}
- ip: "192.0.2.12"
mask: "24"
gateway: "192.0.2.1"
dns: "192.0.2.2"
hostname: "global-cp-2"
machineName: "global-cp-2"
persistentDisk:
- {slot: 0, quantityGB: 100, datastoreName: <datastore-name>, path: /var/cpaas, format: xfs, mountOptions: [defaults]}
- ip: "192.0.2.13"
mask: "24"
gateway: "192.0.2.1"
dns: "192.0.2.2"
hostname: "global-cp-3"
machineName: "global-cp-3"
persistentDisk:
- {slot: 0, quantityGB: 100, datastoreName: <datastore-name>, path: /var/cpaas, format: xfs, mountOptions: [defaults]}
---
# 2. Control-plane VM spec.
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DCSMachineTemplate
metadata:
name: global-cp-template
namespace: cpaas-system
labels:
cpaas.io/cluster-name: "global"
spec:
template:
spec:
vmTemplateName: <vm-template-name>
# Places the cloned VMs in a DCS compute cluster.
resource:
type: cluster
name: <dcs-cluster-name>
vmConfig:
dvSwitchName: <dvswitch-name>
portGroupName: <port-group-name>
dcsMachineCpuSpec: {quantity: 16}
dcsMachineMemorySpec: {quantity: 32768} # MB
dcsMachineDiskSpec:
- {quantity: 0, datastoreName: <datastore-name>, systemVolume: true}
- {quantity: 10, datastoreName: <datastore-name>, path: /var/lib/etcd, format: xfs}
- {quantity: 100, datastoreName: <datastore-name>, path: /var/lib/kubelet, format: xfs}
- {quantity: 100, datastoreName: <datastore-name>, path: /var/lib/containerd, format: xfs}
# /var/cpaas is intentionally NOT a template disk — it is declared as a
# persistentDisk on the IP pool above so it survives node replacement.
ipHostPoolRef:
name: global-cp-pool
---
# 3. Control plane.
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
name: global-kcp
namespace: cpaas-system
labels:
cpaas.io/cluster-name: "global"
annotations:
controlplane.cluster.x-k8s.io/skip-kube-proxy: ""
spec:
replicas: 3
version: ${K8S_VERSION}
rolloutStrategy:
type: RollingUpdate
rollingUpdate: {maxSurge: 0}
machineTemplate:
nodeDrainTimeout: 1m
nodeDeletionTimeout: 5m
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DCSMachineTemplate
name: global-cp-template
kubeadmConfigSpec:
format: ignition
users:
- name: boot
sshAuthorizedKeys:
- "ssh-ed25519 AAAA...replace-with-your-public-key... global-boot"
# The rest of kubeadmConfigSpec — files, clusterConfiguration,
# preKubeadmCommands, postKubeadmCommands, initConfiguration,
# joinConfiguration — is identical to a workload cluster, so it is not
# duplicated here. Take it verbatim from the Complete KubeadmControlPlane
# Configuration appendix in the DCS create-cluster guide
# (#complete-kubeadmcontrolplane-configuration). The three large files
# (psa-config.yaml, control-plane-kubelet-patch.json, audit-policy.yaml) may
# use contentFrom the dcs-kubernetes-<major.minor>-files Secret. Apply these
# global / non-DR deltas to that body:
# 1. Add clusterConfiguration.etcd.local.serverCertSANs:
# ["${CONTROL_PLANE_VIP}", "${PLATFORM_HOST}"]
# A DR pair uses a different list — see Optional Disaster Recovery
# Deployment. etcd.kube-system belongs to that list, not this one.
# 2. Omit the /etc/kubernetes/encryption-provider.conf files entry: the
# provider writes that file on control-plane nodes and drops any
# files entry for it. Keep apiServer.extraArgs.encryption-provider-
# config — see the note after this example.
---
# 4. DCS infrastructure cluster.
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DCSCluster
metadata:
name: "global"
namespace: cpaas-system
labels:
cpaas.io/cluster-name: "global"
annotations:
cpaas.io/registry-address: "${NODE_REGISTRY_ADDRESS}"
spec:
controlPlaneLoadBalancer: {host: "${CONTROL_PLANE_VIP}", port: 6443, type: external}
controlPlaneEndpoint: {host: "${CONTROL_PLANE_VIP}", port: 6443}
credentialSecretRef: {name: "${PROVIDER_SECRET_NAME}"} # created in the prerequisites above
networkType: kube-ovn
site: <dcs-site-id>
---
# 5. Top-level CAPI Cluster: global wiring, labels, and annotations.
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: global
namespace: cpaas-system
labels:
cpaas.io/cluster-name: "global"
cluster-type: DCS
is-global: "true"
annotations:
capi.cpaas.io/resource-group-version: infrastructure.cluster.x-k8s.io/v1beta1
capi.cpaas.io/resource-kind: DCSCluster
capi.cpaas.io/kubernetes: ${K8S_VERSION} # same value as KubeadmControlPlane.spec.version
cpaas.io/registry-address: "${NODE_REGISTRY_ADDRESS}"
cpaas.io/nodes-mode: self-managed # node lifecycle managed by CAPI + the DCS provider
cpaas.io/kube-ovn-join-cidr: <kube-ovn-join-cidr> # a /16 you choose; must not overlap the pod / service CIDRs or another cluster's join CIDR
cpaas.io/kube-ovn-version: <kube-ovn-chart-version>
spec:
clusterNetwork:
pods: {cidrBlocks: ["${CLUSTER_CIDR}"]}
services: {cidrBlocks: ["${SERVICE_CIDR}"]}
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
name: global-kcp
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DCSCluster
name: global
内部 Self-built VIP 变体
上述示例使用 type: external。应用前,可以将资源 4 的端点字段替换为以下内部配置。VIP 必须预留在控制平面 Layer 2 网络中,并且 vrid 在该网络中必须唯一:
spec:
controlPlaneLoadBalancer:
host: "${CONTROL_PLANE_VIP}"
port: 6443
type: internal
vrid: <unique-vrid-1-255>
# interface: eth0 # optional; omit for provider/alive auto-detection
controlPlaneEndpoint: {host: "${CONTROL_PLANE_VIP}", port: 6443}
需要替换的值
Secret 加密与灾难恢复
此示例启用了 etcd 静态 Secret 加密。apiServer.extraArgs.encryption-provider-config 保留在你从附录复制的 clusterConfiguration 中,DCS provider 会提供它所指向的文件:为第一台控制平面机器生成一个随机密钥,之后的每台机器则使用正在运行的 API server 自己的文件。不要将 /etc/kubernetes/encryption-provider.conf 添加到 KubeadmControlPlane.spec.kubeadmConfigSpec.files——provider 会丢弃指向该路径的任何条目。请参阅如何交付加密 provider 配置。
只有在你明确希望停用静态加密时,才移除此参数。对于 DR 集群对,两侧的密钥必须相同,因此必须按照可选的灾难恢复部署中的说明,使用 DCSCluster.spec.encryptionProviderConfigRef 固定该密钥。