在裸金属上创建集群

本文介绍如何使用裸金属 provider 在物理服务器上创建 Kubernetes 集群。整个工作流仅使用 YAML——目前没有适用于裸金属集群的 Fleet Essentials UI。

先决条件

创建集群之前,请确保满足以下所有先决条件。

1. 安装所需插件

在裸金属上创建第一个业务集群之前,请在 global 集群上上传并安装以下插件。无论 global 集群本身运行在哪种 IaaS 平台上,都适用:

  • Alauda Container Platform Cluster Bootstrap Provider
  • Alauda Container Platform Infrastructure Provider for Bare Metal(umbrella chart,会同时安装裸金属 manager 和 elemental-operator)

操作步骤请参阅安装 Provider。如果 global 集群安装在裸金属上,还需完成将 global 集群准备为管理集群。

2. 导入 OS 镜像并填充镜像目录

创建任何集群资源之前,请将两种裸金属 OS 镜像都导入目标平台 registry,然后将目标 Kubernetes 版本添加到 elemental-image-catalog。这两个步骤都不会自动完成:provider 软件包不包含 OS 镜像,provider chart 创建的镜像目录也没有任何条目。

镜像用途说明
OS 镜像(base-image),仓库 baremetal-base-imageelemental-image-catalog使用 Kubernetes 专用根文件系统重新配置已注册的主机。
OS ISO 镜像(base-image-iso),仓库 baremetal-base-image-isoSeedImage.spec.baseImage构建用于初始主机安装和注册的 live ISO。

只导入一种镜像是不够的。添加镜像目录条目或 SeedImage 引用,并不会导入对应的镜像。

导入镜像。 适用于你的 ACP 版本的 Bare Metal OS 下载文件是一个归档,其中包含一个 OCI 镜像布局,两个镜像使用同一个 tag;请参阅各 Provider 的镜像格式。在安装了 containerd 且能够访问平台 registry 的主机上以 root 身份运行导入操作,例如 global 控制平面节点;Alauda OS 自带 ctr,但不包含 skopeo。/var/lib/containerd 需要至少与归档大小相等的可用空间。将归档导入专用的 containerd namespace,并列出创建的镜像名称:

ctr -n bare-metal-os images import --all-platforms --no-unpack <bare-metal-os-archive>
ctr -n bare-metal-os images ls -q

输出会列出 baremetal-base-image:<os-image-tag> 和 baremetal-base-image-iso:<os-image-tag>。推送这两个镜像,并确认 registry 中各自都列出了该 tag。--skip-verify 需要 --local:

for repo in baremetal-base-image baremetal-base-image-iso; do
  ctr -n bare-metal-os images push --local --skip-verify \
    -u "<registry-username>:<registry-password>" \
    "<registry-address>/tkestack/${repo}:<os-image-tag>" "${repo}:<os-image-tag>"
  curl -sk -u "<registry-username>:<registry-password>" \
    "https://<registry-address>/v2/tkestack/${repo}/tags/list"
done

ctr -n bare-metal-os images rm \
  "baremetal-base-image:<os-image-tag>" "baremetal-base-image-iso:<os-image-tag>"

最后一条命令会在两个镜像均成功推送后删除本地副本。对于 global 集群的 registry,admin 密码存储在 cpaas-system/registry-admin Secret 中。在灾难恢复对中,请将镜像导入两个 global 集群的 registry;请参阅将 global 集群准备为管理集群。

导入后,<base-image> 为 <registry-address>/tkestack/baremetal-base-image:<os-image-tag>,而 <base-image-iso> 为 <registry-address>/tkestack/baremetal-base-image-iso:<os-image-tag>。两个镜像必须来自同一个归档。不要将集群资源指向构建用 registry,不要混用来自不同归档的镜像,也不要替换为独立构建的镜像。

填充镜像目录。 cpaas-system 中的 elemental-image-catalog ConfigMap 将 Machine.spec.version 映射到用于(重新)配置节点的 elemental upgrade 镜像。provider chart 创建的该 ConfigMap 不包含任何条目,因此在添加目标版本之前,无法配置任何节点。请 patch 该 ConfigMap,而不是替换它,以保留现有条目,然后检查结果:

kubectl -n cpaas-system patch configmap elemental-image-catalog --type merge \
  -p '{"data":{"<kubernetes-version>":"<base-image>"}}'

kubectl -n cpaas-system get configmap elemental-image-catalog -o yaml

控制平面和 worker MachineDeployment 资源中使用的每个 Machine.spec.version 值,都必须作为键出现在此 ConfigMap 中,并保留开头的 v,且解析到已导入的 base-image。provider 会在重新配置时,通过将条目中的 registry 部分替换为平台 registry 地址来解析镜像。如果缺少目标版本,则不会写入 reprovision plan,BaremetalMachine 最终会进入 Failed / Reason=ImageCatalogMiss。之后再添加条目也不会清除此状态;请删除受影响的 Machine,使其 owner 重新创建该对象。

3. 网络连接

裸金属主机的注册、安装和加入集群都完全通过网络进行,因此在首台主机启动 SeedImage ISO 之前,必须开放下表中的所有网络路径。

Alauda OS 节点上的主机防火墙已预先配置并由平台维护——你无需管理它,而且下表也不是节点防火墙规则集。请参阅管理防火墙端口。你需要规划的是主机与其访问端点之间的网络:主机子网、平台和 registry 之间的站点防火墙、安全组及路由器 ACL。

阶段源目标协议 / 端口用途
首次启动每台从 SeedImage ISO 启动的主机站点 DNS 服务器UDP 和 TCP 53主机必须先按名称解析平台访问地址和 registry 主机,之后才能访问其中任一端点
首次启动每台主机站点 NTP 服务器UDP 123主机时间同步中的时钟检查
首次启动每台从 SeedImage ISO 启动的主机平台访问地址(global.platformUrl)TCP 443,或自定义的平台 HTTPS 端口elemental-register 注册主机,并创建其 MachineInventory
首次启动每台主机平台 registry(global.registry.address)TCP 11443elemental install 拉取 Alauda OS 镜像。之后每次 elemental upgrade 重新配置时,都会再次拉取该镜像
配置和稳定运行每台业务集群主机平台访问地址,路径 /kubernetes/globalTCP 443,或自定义的平台 HTTPS 端口elemental-system-agent 轮询其 plan Secret。这是平台入口路径,而非直接连接 kube-apiserver
加入集群新集群的每个节点此集群的控制平面端点TCP 6443,或 BaremetalCluster.spec.controlPlaneLoadBalancer.port 中设置的端口kubeadm join 和 kubelet 访问 API server
加入集群新集群的每个节点根据 cpaas.io/registry-address annotation 解析出的平台 registryTCP 11443节点拉取平台镜像——CoreDNS、kube-proxy 和 kube-ovn
集群管理global 集群节点此集群的控制平面端点TCP 6443,或配置的控制平面端口Cluster API controllers 在 global 上运行,并通过此集群的 API server 调和该集群
集群运行时新集群的每个节点同一集群的所有其他节点控制平面节点之间的 etcd 2379 和 2380;Kube-OVN overlay 隧道,默认 Geneve 封装使用 UDP 6081;kubelet;以及不按端口过滤的 Pod 和 Service 流量集群内部流量。完整的组件端口清单请参阅通信矩阵。如果更改了 Kube-OVN 隧道类型或附加了辅助网络,请根据你自己的配置确认隧道端口
WARNING

仅开放 443 不足以满足业务主机的要求

elemental-register 和 elemental-system-agent 都通过平台访问地址访问平台,因此注册和轮询 plan 只需要开放平台 HTTPS 端口。但这并非全部要求。同一主机仍需通过 DNS 解析名称、从 11443 上的 registry 拉取操作系统和平台镜像、通过控制平面端点端口加入 API server,并与其他节点交换集群流量。仅允许 443 的防火墙审批,会导致主机成功注册,却在安装或加入集群时失败,而且届时更难查明原因。

表格范围。 这些路径适用于业务集群主机,也就是本文创建的主机。另外两种相邻场景在其他文档中说明,未包含在上表中:

  • Bootstrap。 在 global 本身仍在安装期间,其主机会向临时 bootstrap 端点注册,适用的端口也不同——请参阅Bootstrap 节点网络。
  • 移交后的 global 主机。 global 主机的系统智能体使用 Global 控制平面端点,而不是平台入口路径;在 DR 对中,它会直接使用 6443 上的本地控制平面 VIP。请参阅端点与身份。

在 bootstrap 阶段创建的 SeedImage 指向 bootstrap 端点,移交后不得重复使用;请在活动的 global 集群上创建新的 MachineRegistration 和 SeedImage 对象。在 global 集群上创建的业务 SeedImage 在移交后仍然有效,因为其注册 URL 指向平台域名,而不是 bootstrap 地址。

控制平面端点。

  • 对于 Internal Self-built VIP,该 VIP 必须与控制平面节点 IP 位于同一个二层广播域中。vrid 在该域中必须唯一,网络必须允许 VRRP 和 gratuitous ARP 更新,并且节点镜像必须公开 IPVS,且允许 Alive 设置 net.ipv4.conf.all.arp_accept=1 和 net.ipv4.vs.conntrack=1。
  • 对于 External LoadBalancer,请在创建集群之前配置 listener 和控制平面后端。请遵循规划控制平面端点。

首次启动时的寻址。

如果目标 VM 或物理主机启动进入 live ISO 后未从 DHCP 获取地址,请先在主机控制台手动配置网络,然后再等待注册。先检查 NetworkManager 连接名称,再应用站点专用的地址、网关和 DNS 值:

nmcli connection show
nmcli connection modify "Wired connection 1" \
  ipv4.method manual \
  ipv4.addresses 192.168.254.73/24 \
  ipv4.gateway 192.168.254.1 \
  ipv4.dns 192.168.16.19 \
  ipv4.ignore-auto-dns yes
nmcli connection up "Wired connection 1"

4. 主机时间同步

每台将成为集群节点的主机——无论是控制平面节点还是 worker 节点——在启动 SeedImage ISO 时都必须已经具有正确的时间。平台对跨节点的要求是:每个集群节点使用统一的时区,且节点之间的时间同步误差不超过 10 秒;请参阅节点检查。该要求描述的是集群节点,因此同样适用于此处,但此路径不会替你验证这一点:该页面中的快速配置脚本和节点检查面向的是平台通过 SSH 加入的非 Alauda OS 机器,不会在通过 elemental-operator 安装的 Alauda OS 主机上运行。

将时间同步作为步骤 1之前的必要条件:

  • 确保主机子网可以访问站点 NTP 服务器,并按照网络连接中的说明开放 UDP 123。生产集群不应依赖公共 NTP。
  • 从固件设置时间,而不是从操作系统设置。此时安装磁盘已按照主机磁盘和启动准备中的说明被擦除,因此主机上没有可用于运行命令的操作系统。请在 BIOS/UEFI 设置中或通过 BMC 校正时间,并将硬件时钟保持为 UTC,以便安装 Alauda OS 后统一应用时区。
  • 相互比较各主机,而不仅仅是与某个参考时间比较。对于所有计划加入同一集群的主机,在其中任何主机启动 ISO 之前,时间差都必须保持在 10 秒要求以内。
  • 节点达到 Ready 后,在每个节点上使用 timedatectl 确认结果——该命令会同时报告时区以及时钟是否已同步——并使用 chronyc tracking 查看时间源和当前偏移量。

整个集群 PKI 都继承运行 kubeadm init 的主机时钟。kubeadm 根据该主机的当前时间设置证书颁发机构的 NotBefore,每个叶证书都会继承该 NotBefore,并据此计算每个证书的 NotAfter。因此,时钟超前的主机会生成一套 PKI,其他节点和任何客户端都会将其视为尚未生效,直到自身时钟追上;而时钟落后的主机会生成一套比配置的有效期所暗示的时间更早过期的 PKI。硬件时钟从未同步过的服务器通常会相差数分钟或数小时,而不是几秒,因此后果是控制平面完全无法启动——而且问题会在控制平面启动阶段才暴露,此时距离引入错误时钟的注册步骤已经很久。

WARNING

Day-2 chrony MachineConfig 无法替代此要求

配置 Chrony 时间服务会将 /etc/chrony.conf 写入已经运行并加入集群的节点。这是使集群在持续运行期间使用站点 NTP 服务器的正确方式,但它与此处的要求不同:它无法校正仍在安装 Alauda OS 或运行 kubeadm 的主机时钟。将时间同步推迟到该步骤,会使安装和加入集群的时间窗口得不到保护。

5. TPM 决策

根据主机是否提供真实硬件 TPM(/dev/tpm0)设置 MachineRegistration.spec.config.elemental.registration.emulate-tpm,而不是根据主机是物理机还是虚拟机来设置:

  • emulate-tpm: false(或省略该字段)——仅适用于具有可用硬件 TPM 的主机,以便 elemental-register 使用它执行 auth: tpm。
  • emulate-tpm: true 和 emulated-tpm-seed: -1——适用于任何没有硬件 TPM 的主机。这既包括虚拟机,也包括出厂时未配备 TPM 模块的物理服务器(例如 Dell R620)。在此类主机上,auth: tpm 与 emulate-tpm: false 结合使用会使 elemental-register 的 TPM 证明失败,并且永远不会发送注册请求,因此 elemental-operator 会记录 0 次注册 POST。

6. 业务集群镜像 Registry

启动集群不需要 Registry 凭证。裸金属主机仅使用平台 Registry 地址安装操作系统并加入集群,因此无需配置凭证即可访问一个所有节点均为 Ready 的集群。

下一步——在新集群上安装平台组件——才需要凭证。这些组件会从 Registry 拉取镜像,而要求身份验证的 Registry 需要集群可使用的凭证。

默认情况下,集群使用 global 集群的 Registry;当该 Registry 需要身份验证时,其凭证由 public-registry-credential Secret 提供。如果希望从专用 Registry 拉取平台组件镜像,请遵循为业务集群选择镜像 Registry。

WARNING

凭证可以稍后配置,但 Registry 的选择不能推迟。集群创建时即会绑定其 Registry,现有集群无法迁移到其他 Registry。请在应用以下 manifest 之前完成决定,即使你要到之后才安装平台组件。

上述两种选择都不会影响前文所述的操作系统镜像。主机会在 elemental install 期间以及每次重新置备时,从平台 Registry 拉取 base-image 和 base-image-iso,此时尚不存在任何集群级绑定。

7. 主机磁盘和启动准备

在启动 SeedImage ISO 之前,先对每台主机上的所有磁盘和虚拟磁盘(VD)进行分类。记录哪个设备是 OS 安装目标,以及哪些设备(如有)必须保留应用数据:

  • 擦除选定的 OS 安装磁盘和其他已废弃的启动磁盘,确保不再保留可启动的旧操作系统。主机必须启动 ISO,而不是磁盘上旧的操作系统。将 MachineRegistration.spec.config.elemental.install.device 设置为准确的 OS 磁盘。
  • 如果计划使用 Adopt 策略管理数据磁盘,不要擦除该磁盘。请独立备份它,记录其稳定 ID 和文件系统 UUID,并确保不会将其选为安装设备。
  • 用于 InitializeIfBlank 的磁盘必须可丢弃且确实为空。格式化仍需在主机注册后单独获得初始化批准;注册 manifest 不会授权执行该操作。
  • 从所有磁盘中移除残留的 Elemental 分区标签——COS_STATE、COS_PERSISTENT、COS_OEM、COS_RECOVERY,而不仅是目标安装磁盘。Elemental 会根据标签(blkid -L COS_STATE)解析分区;第二块磁盘上的残留标签(例如来自之前的安装或遗留的 multipath 成员)会导致它解析到错误的设备,从而使重新置备 snapshotter 失败。
  • 对于同一磁盘可能通过多个路径显示的主机(multipath),请确保这些路径中没有任何路径暴露仍带有 COS_* 标签的磁盘;任一路径上的残留标签都可能先于目标安装磁盘被解析。请清理磁盘,而不是禁用 multipath——该镜像会保持 multipath 启用,以支持网络连接存储启动。
  • 对于具有多个磁盘的主机,不要将 install.device 留空,也不要将 /dev/sda 用作持久身份标识。请遵循在多磁盘裸金属主机上选择固定系统磁盘,以便重复使用同一个 ISO,同时通过 WWN 选择每台主机的系统磁盘。
  • 设置启动顺序,使主机优先启动虚拟 CD / ISO。

只能使用已注册清单报告的稳定存储身份。/dev/sdX、/dev/vdX、/dev/nvmeXnY 和 /dev/mapper/mpathX 等 Linux 路径属于运行时路径,不得放入 MachineInventory.spec.storage。

8. 节点标签和污点

如果要为池中的节点设置专属角色标签和污点——例如为监控、日志记录或 ingress 预留专用池——请按照基础设施节点中的说明,在池的资源上声明这些配置。请在创建池之前完成规划。


集群创建工作流

使用 YAML 时,先导入配套的 OS 镜像,然后执行五个必需的资源步骤;在将节点加入池之前,还可选择执行存储准备步骤。每个 Kubernetes 资源都必须应用到 cpaas-system 命名空间中。

步骤应用或执行结果
0将 base-image 和 base-image-iso 导入目标平台注册表,并将目标 Kubernetes 版本添加到 elemental-image-catalog 中——请参阅已导入 OS 镜像并填充镜像目录两个 OS 镜像均可用,并且在开始构建 SeedImage 或重新置备主机之前,目录已解析每个 Machine.spec.version。
1MachineRegistration 和 SeedImageelemental-operator 会构建可启动 ISO。
2在每台物理主机上启动 ISO主机运行 elemental install,并注册为 MachineInventory。
2a(可选)声明并准备 MachineInventory.spec.storage清单未分配时,会验证并准备显式数据卷。
3MachineInventoryPool 资源,每个角色一个在 CAPI 创建 Machine 之前,声明允许使用的控制平面和工作节点清单。
4BaremetalCluster、控制平面 BaremetalMachineTemplate、KubeadmControlPlane 和 ClusterCluster API 使用控制平面池启动控制平面推出。
5工作节点 KubeadmConfigTemplate、工作节点 BaremetalMachineTemplate 和 MachineDeploymentCluster API 使用工作节点池启动工作节点推出。
WARNING

重要的命名空间要求

所有裸金属资源都必须应用到 cpaas-system 命名空间中。provider 和 elemental-operator 只会协调该命名空间中的对象。

WARNING

业务集群命名

业务 cluster-name 不得命名为 global。该名称保留给 global 集群;重复使用会导致业务集群的资源与 cpaas-system 中 global 集群的资源发生冲突。按照惯例,CAPI Cluster 和 BaremetalCluster 应保持名称恰好为 <cluster-name>,而依赖资源(KubeadmControlPlane、KubeadmConfigTemplate、MachineDeployment、machine 模板、池、注册资源)则应以 <cluster-name>- 为前缀。

解析占位符值

以下示例清单使用 <placeholder> 语法表示特定环境的值:

占位符真实来源获取方式
<cluster-name>operator 提供的业务集群名称。不得为 global。一次确定,并在 Cluster、BaremetalCluster、池、模板和注册资源中保持一致。
<kubernetes-version>导入的 OS 镜像的 Kubernetes 版本,作为 elemental-image-catalog ConfigMap 键添加(带前导 v,例如 v1.33.7-2)。从 OS Support Matrix 获取值;使用 kubectl -n cpaas-system get cm elemental-image-catalog -o yaml 确认键。
<bare-metal-os-archive>目标 ACP 版本的 Bare Metal OS 归档文件。OS Support Matrix 中列出的下载页面。
<os-image-tag>归档文件中两个镜像共用的标签。导入后执行 ctr -n bare-metal-os images ls -q。
<registry-username> / <registry-password>可向 <registry-address> 推送的账户。对于 global 集群的 registry,使用用户 admin,密码取自 kubectl -n cpaas-system get secret registry-admin -o jsonpath='{.data.password}' | base64 -d。
<base-image>image catalog 用于主机重新置备的导入 OS 镜像。在 OS Images Imported and Image Catalog Populated 中导入后执行 <registry-address>/tkestack/baremetal-base-image:<os-image-tag>。
<base-image-iso>与 <base-image> 配对的导入 OS ISO 镜像。执行 <registry-address>/tkestack/baremetal-base-image-iso:<os-image-tag>,从与 <base-image> 相同的归档文件导入。
<registry-address>导入两个 OS 镜像的目标平台 registry(与安装 chart 上的 global.registry.address 取值相同)。执行 kubectl get cluster global -n cpaas-system -o jsonpath='{.metadata.annotations.cpaas\.io/registry-address}'(如果存在)。
<control-plane-load-balancer-type>operator 的决定。对于 provider 管理的 Alive,使用 Internal;对于现有负载均衡器,使用 External。查看 Plan the Control Plane Endpoint。
<control-plane-vip> / <control-plane-port>operator 提供的稳定端点。对于 Internal,使用控制平面 Layer-2 域中未使用的 IPv4 VIP。对于 External,使用负载均衡器前端地址。n/a
<vrid>仅供 Internal 使用的 operator 提供的 VRID。它在控制平面 Layer-2 域中必须唯一。对于 External,移除该字段。n/a
<install-device>elemental install 的整盘目标。仅当直接内核路径的标识稳定且明确时,才适合使用该路径。对于复用同一个 ISO 的多磁盘裸金属主机,请使用 /dev/elemental-install-target。从主机控制台通过 WWN 确认磁盘。请参阅 Select a Fixed System Disk on Multi-Disk Bare-Metal Hosts。
<dns-image-tag> / <etcd-image-tag>为 <kubernetes-version> 构建到裸金属基础镜像中的组件版本。请参阅 OS Support Matrix 中的 coredns 和 etcd 列。
<kube-ovn-version>与所选 ACP 和 Kubernetes 版本匹配的 Kube-OVN chart 版本。使用 OS Support Matrix 中的 kube-ovn (chart) 值,而不是 Kube-OVN 组件版本,并通过已安装的插件确认:kubectl get moduleplugins.cluster.alauda.io kube-ovn -o jsonpath='{.spec.mainChart}{" "}{.status.latestVersion}'。
<dns-server>当首次启动注册路径需要显式 resolver 时由 operator 提供。优先使用 MachineRegistration.config.cloud-config 或更高版本的 KubeadmControlPlane bootstrap 数据(format: cloud-config)。不要从 SeedImage.cloud-config 写入 /etc/resolv.conf。
<ssh-authorized-keys>operator 提供的 OpenSSH 公钥——节点上的任何交互式调试都需要该密钥。n/a
<inventory-name>主机启动 ISO 并注册后由 elemental-operator 分配。注册前无法预测。kubectl -n cpaas-system get machineinventories.elemental.cattle.io
<pool-name>声明 inventory 的 MachineInventoryPool 的名称。在下面的示例中使用 <cluster-name>-control-plane-pool 或 <cluster-name>-worker-pool。
<control-plane-inventory-1/2/3> / <worker-inventory-1/2/3>为每个角色选定的确切 MachineInventory 名称。使用 kubectl -n cpaas-system get machineinventories.elemental.cattle.io 中的名称。
<control-plane-host-1/2/3> / <worker-host-1>在重新置备期间应用的可选 Kubernetes 节点主机名。由 operator 提供;省略 hostname 以回退到 inventory 名称。
<pods-cidr> / <services-cidr> / <kube-ovn-join-cidr>由 operator 提供。不得与主机网络、global 集群的 CIDR 或同一 global 上的任何其他 CAPI 集群重叠。n/a
<workload-kubeconfig>业务集群的 Kubeconfig。控制平面初始化后,Cluster API 会将其发布为 Secret。kubectl -n cpaas-system get secret <cluster-name>-kubeconfig -o jsonpath='{.data.value}' | base64 -d
<machine-name>用于定向移除或恢复的现有 CAPI Machine 名称。kubectl -n cpaas-system get machines.cluster.x-k8s.io
<new-replica-count>扩缩容后的目标 MachineDeployment.spec.replicas 值。operator 提供的目标工作节点数量。
<new-template-name>用于更改后的 BaremetalMachineTemplate 或 KubeadmConfigTemplate 的新模板名称。由 operator 提供;使用新名称。
<new-kubernetes-version> / <new-tag> / <new-image>目标升级版本以及匹配的裸金属基础镜像标签或已解析镜像。使用版本支持矩阵,并确认最终键存在于 elemental-image-catalog 中。

步骤 1:构建 SeedImage 并注册主机

创建一个描述注册 URL 和首次安装 cloud-config 的 MachineRegistration,以及一个将 elemental-operator 指向匹配 ISO 基础镜像的 SeedImage。

将 SeedImage.spec.baseImage 设置为导入的 base-image-iso 的最终目标平台 registry 引用。它必须与 image catalog 用于后续重新置备的导入 base-image 配对:两个镜像都必须来自同一个 OS 归档文件,绝不能来自不同的归档文件。

01-machineregistration-seedimage.yaml
apiVersion: elemental.cattle.io/v1beta1
kind: MachineRegistration
metadata:
  name: <cluster-name>-registration
  namespace: cpaas-system
  labels:
    app.kubernetes.io/part-of: cluster-api-provider-baremetal
spec:
  # ${...} placeholders are expanded by elemental-register from SMBIOS data.
  # System UUID is preferred over Serial Number because it is more reliable
  # across virtualised test environments.
  machineName: "<cluster-name>-${System Information/UUID}"
  machineInventoryLabels:
    app.kubernetes.io/part-of: cluster-api-provider-baremetal
    pool.baremetal.alauda.io/eligible: "true"
    elemental.cattle.io/serial-number: "${System Information/Serial Number}"
  machineInventoryAnnotations:
    elemental.cattle.io/machine-uuid: "${System Information/UUID}"
  config:
    elemental:
      install:
        device: <install-device>
        eject-cd: true
        reboot: true
        snapshotter:
          type: btrfs
          maxSnaps: 4
      registration:
        # false only when the host has a real hardware TPM (/dev/tpm0). Set true
        # (with emulated-tpm-seed: -1) on any host without one, including physical
        # servers that ship without a TPM module.
        emulate-tpm: false
---
apiVersion: elemental.cattle.io/v1beta1
kind: SeedImage
metadata:
  name: <cluster-name>-registration-iso
  namespace: cpaas-system
  labels:
    app.kubernetes.io/part-of: cluster-api-provider-baremetal
spec:
  type: iso
  baseImage: <base-image-iso>          # Use the imported target-platform registry reference.
  registrationRef:
    apiVersion: elemental.cattle.io/v1beta1
    kind: MachineRegistration
    name: <cluster-name>-registration
    namespace: cpaas-system
  targetPlatform: linux/amd64
  size: 20Gi
  cleanupAfterMinutes: 120
  cloud-config:
    stages:
      boot:
        - name: "Size COS_STATE for reprovisioning"
          files:
            - path: /etc/elemental/config.d/partitions.yaml
              permissions: 0644
              content: |
                install:
                  partitions:
                    state:
                      size: 20480

首次安装前调整 COS_STATE 大小

state 分区用于存放运行中的系统以及所有保留的快照。Elemental 默认的 8192 MiB 不够用:一个镜像的快照会共享 extent,但升级到不同镜像时,必须同时容纳两个大小约为 3.5 GiB 且互不相关的系统,并会在中途因 no space left on device 失败,导致每次重试都在其上留下一个不完整的快照。每次集群升级都会经过 elemental upgrade,而分区大小在安装时固定,因此该问题只会在之后才暴露——此时集群已经投入生产,若不重新安装就无法再重新分区。

在安装时将其大小设置为至少 20480 MiB(20 GiB)。

位置和阶段都很重要

该片段必须位于 stages.boot 下的 SeedImage.spec.cloud-config 中。将其放入 MachineRegistration.spec.config.cloud-config 或任何其他阶段都会留下 8 GiB 分区,并且不会报告错误。

在向其余主机群推出之前,先在第一台已安装的主机上验证结果:

# Expect ~20G, not 8G
lsblk -o NAME,SIZE,LABEL | grep COS_STATE
df -h /

不要将 /etc/resolv.conf 添加到 SeedImage.spec.cloud-config。保持 ISO 通用;仅当首次启动注册路径需要时,才在 MachineRegistration.spec.config.cloud-config 中配置站点特定的 resolver。在经过测试的 Global 部署流程中,ISO 不包含 resolver 文件;节点 DNS 随后由 kubeadm bootstrap 数据进行配置。

install.device、install.eject-cd 和 install.reboot 是有意设置的。如果省略目标磁盘,elemental install 可能会选择非预期设备。在多磁盘物理主机上,请使用固定系统磁盘操作流程,而不是使用会变化的 /dev/sdX 名称。如果 eject-cd 或 reboot 为 false,主机可能会在首次安装后仍停留在 live 环境中,永远无法成为 Cluster API 可用的 inventory。

应用 manifest,并等待 SeedImage 构建完成:

kubectl apply -f 01-machineregistration-seedimage.yaml
kubectl -n cpaas-system wait \
  --for=condition=SeedImageReady=True \
  seedimage/<cluster-name>-registration-iso \
  --timeout=30m

SEEDIMAGE_REASON="$(kubectl -n cpaas-system \
  get seedimage <cluster-name>-registration-iso \
  -o jsonpath='{.status.conditions[?(@.type=="SeedImageReady")].reason}')"
SEEDIMAGE_DOWNLOAD_URL="$(kubectl -n cpaas-system \
  get seedimage <cluster-name>-registration-iso \
  -o jsonpath='{.status.downloadURL}')"
SEEDIMAGE_CHECKSUM_URL="$(kubectl -n cpaas-system \
  get seedimage <cluster-name>-registration-iso \
  -o jsonpath='{.status.checksumURL}')"

if [ "${SEEDIMAGE_REASON}" != "SeedImageBuildSuccess" ] || \
   [ -z "${SEEDIMAGE_DOWNLOAD_URL}" ] || \
   [ -z "${SEEDIMAGE_CHECKSUM_URL}" ]; then
  echo "SeedImage did not produce a downloadable ISO and checksum" >&2
  exit 1
fi

printf '%s\n%s\n' "${SEEDIMAGE_DOWNLOAD_URL}" "${SEEDIMAGE_CHECKSUM_URL}"

不要将 status.state 用作成功判定条件;当前 controller 不会填充该字段。只有当 SeedImageReady=True 的 reason 为 SeedImageBuildSuccess,且 status.downloadURL 和 status.checksumURL 均非空时,才能继续操作。下载生命周期已过期的 SeedImage 仍可能报告 SeedImageReady=True,但其 reason 会发生变化,且其 URL 会被清除。

从此 ISO 启动每台目标主机。elemental-register 首先运行(创建 MachineInventory 并上传 observedNetwork),随后 elemental install 将 OS 写入磁盘。安装完成后,主机仍可用于执行 plan。如果 live ISO 环境没有 DHCP 地址,请按照网络连接中的说明,在主机控制台上手动配置 NetworkManager,然后再等待 MachineInventory。

确认注册状态:

kubectl -n cpaas-system get machineinventories.elemental.cattle.io

每个计划使用的 inventory 都必须:

  • 显示 Ready=True。
  • 具有非空的 status.plan.secretRef.name。
  • 具有与主机预期 NIC 匹配的 spec.observedNetwork(仅当希望安装时 IP 在重新置备期间保持不变时才需要)。
  • 当需要托管数据磁盘时,运行支持 observer 的 OS image,报告新的 status.observedStorage,并在分配到 pool 之前达到 status.storage.phase=Prepared。

记录确切的 MachineInventory 名称——下一步会按名称引用它们。

可选:准备托管数据磁盘

托管存储属于长期存在的 MachineInventory,而不属于 CAPI Machine 或 BaremetalMachineTemplate。请在 inventory 尚未分配、且尚未将其添加到生产 pool 之前进行配置。

按照管理裸金属主机上的数据磁盘中的说明:

  1. 验证 elemental-storage-observer.service 并检查 status.observedStorage。
  2. 使用稳定 ID 选择每个设备,并选择 Adopt 或 InitializeIfBlank。
  3. 使用匹配的 storagectl binary 进行验证,并以原子方式修补声明;在需要时,同时修补其初始化批准。
  4. 等待 StoragePrepared=True/AllRequiredVolumesPrepared 和 status.storage.phase=Prepared。

如果不应管理任何磁盘,请省略 spec.storage,或将其设置为 volumes: []。存储 controller 会将该 inventory 收敛到 Unmanaged,并设置 StoragePrepared=True/NoManagedVolumes,且不会执行任何磁盘操作。

步骤 2:创建 MachineInventoryPool 资源

每个角色创建一个 pool。pool reconciler 会验证每个成员是否存在,计算容量计数器,并将 baremetal.alauda.io/pool=<pool-name> annotation 写入 inventory。

02-machineinventorypool.yaml
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: MachineInventoryPool
metadata:
  name: <cluster-name>-control-plane-pool
  namespace: cpaas-system
spec:
  clusterName: <cluster-name>
  machineInventories:
    - name: <control-plane-inventory-1>
      hostname: <control-plane-host-1>    # Optional; falls back to the inventory name.
    - name: <control-plane-inventory-2>
      hostname: <control-plane-host-2>
    - name: <control-plane-inventory-3>
      hostname: <control-plane-host-3>
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: MachineInventoryPool
metadata:
  name: <cluster-name>-worker-pool
  namespace: cpaas-system
spec:
  clusterName: <cluster-name>
  machineInventories:
    - name: <worker-inventory-1>
      hostname: <worker-host-1>
    - name: <worker-inventory-2>
    - name: <worker-inventory-3>

关键参数:

参数类型描述必需
.spec.clusterNamestring此 pool 服务的业务集群名称。一个 MachineInventory 不能出现在两个活动 pool 中。是
.spec.machineInventories[].namestring已注册 MachineInventory 的确切名称。是
.spec.machineInventories[].hostnamestring重新置备期间应用的主机名。省略时默认为 inventory 名称。否

应用并验证:

kubectl apply -f 02-machineinventorypool.yaml
kubectl -n cpaas-system get machineinventorypools.infrastructure.cluster.x-k8s.io

健康的 pool 会报告 Ready=True、total = len(spec.machineInventories) 和 available = total - allocated - preparing - reprovisioning - unavailable。spec.machineInventories 中列出的 inventory 如果未通过验证(缺失、plan secret 缺失、Ready=False 或存储未准备好),会增加 pool 的 unavailable 计数器,并显示在 MembersValid condition 中。非空的存储声明是分配门槛:只有在基于最新 observation 成功完成 Prepare 后,该 inventory 才会对 CAPI 处于 Available 状态。

将 control-plane pool 的大小设置为至少 KubeadmControlPlane.spec.replicas。将 worker pool 的大小设置为至少 MachineDeployment.spec.replicas。对于滚动升级,pool 必须容纳完整的 replica 数量——provider 会在同一个 pool 中采用先删除后添加的语义,绝不会同时执行两者。

步骤 3:创建控制平面集群资源

创建 BaremetalCluster(声明所选的控制平面 endpoint 模式)、控制平面 BaremetalMachineTemplate(指向控制平面 pool)、KubeadmControlPlane(replicas + kubeadm 配置)以及 CAPI Cluster。

Bare Metal API 在同一操作流程中支持两种 endpoint 模式:

  • Internal:provider 部署 Alive,并协调 VIP 和控制平面后端成员。
  • External:provider 跳过 Alive;负载均衡器所有者维护 listener、健康检查和后端成员。

在下面的 manifest 中使用 <control-plane-load-balancer-type> 选择模式。

WARNING

使用 OS image 中内置的 Kubernetes image

受支持的裸金属 OS image 会在 cloud.alauda.io/alauda 下预加载 kubeadm 控制平面、CoreDNS 和 etcd image。在 KCP 配置中保留该 repository,并使用 OS Support Matrix 中的组件 tag。不要将其替换为 <registry-address>/tkestack:这会生成与 OS 中预加载的 image 不匹配的 image 引用。OS 会通过 containerd 配置其内置的 pause image;不要将 pod-infra-container-image 添加到 KCP kubelet 参数中。

TIP

完整配置参考

下面的示例使用最小化的 KubeadmControlPlane。如需生产环境推荐的完整加固配置文件——包括 admission、audit、kubelet 补丁和 encryption provider——请参阅附录中的完整 KubeadmControlPlane 配置。

03-cluster.yaml
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: BaremetalCluster
metadata:
  name: <cluster-name>
  namespace: cpaas-system
spec:
  controlPlaneLoadBalancer:
    type: <control-plane-load-balancer-type>
    host: <control-plane-vip>
    port: <control-plane-port>
    # Required only for Internal. Remove this field for External.
    vrid: <vrid>
  networkType: kube-ovn      # Enables provider-managed Kube-OVN AppRelease reconciliation.
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: BaremetalMachineTemplate
metadata:
  name: <cluster-name>-control-plane-template
  namespace: cpaas-system
spec:
  template:
    spec:
      machineInventoryPoolRef:
        name: <cluster-name>-control-plane-pool
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
  name: <cluster-name>-control-plane
  namespace: cpaas-system
spec:
  replicas: 3
  version: <kubernetes-version>
  rolloutStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 0            # Bare-metal does not over-provision; replace one at a time.
  machineTemplate:
    nodeDrainTimeout: 5m
    nodeVolumeDetachTimeout: 5m
    infrastructureRef:
      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
      kind: BaremetalMachineTemplate
      name: <cluster-name>-control-plane-template
  kubeadmConfigSpec:
    users:
      - name: boot
        sudo: ALL=(ALL) NOPASSWD:ALL
        shell: /bin/bash
        sshAuthorizedKeys:
          - "<ssh-authorized-keys>"
    clusterConfiguration:
      imageRepository: cloud.alauda.io/alauda
      dns:
        imageTag: <dns-image-tag>
      etcd:
        local:
          imageTag: <etcd-image-tag>
      apiServer:
        extraArgs:
          profiling: "false"
          tls-min-version: VersionTLS12
      controllerManager:
        extraArgs:
          profiling: "false"
          tls-min-version: VersionTLS12
      scheduler:
        extraArgs:
          profiling: "false"
          tls-min-version: VersionTLS12
    initConfiguration:
      nodeRegistration:
        kubeletExtraArgs:
          node-labels: "kube-ovn/role=master"
    joinConfiguration:
      nodeRegistration:
        kubeletExtraArgs:
          node-labels: "kube-ovn/role=master"
---
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
  name: <cluster-name>
  namespace: cpaas-system
  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/kube-ovn-version: <kube-ovn-version>
    cpaas.io/sentry-deploy-type: Baremetal
    cpaas.io/alb-address-type: ClusterAddress
  labels:
    cluster-type: ProviderBaremetal
spec:
  clusterNetwork:
    pods:
      cidrBlocks:
        - <pods-cidr>
    services:
      cidrBlocks:
        - <services-cidr>
  controlPlaneRef:
    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
    kind: KubeadmControlPlane
    name: <cluster-name>-control-plane
  infrastructureRef:
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
    kind: BaremetalCluster
    name: <cluster-name>

集群 annotation。 裸金属 provider 在 reconcile 期间依赖一组 Cluster annotation。operator 必须设置的权威 annotation 如下:

Annotation必需值来源用途
capi.cpaas.io/resource-group-version是字面值 infrastructure.cluster.x-k8s.io/v1beta1CAPI 基础设施绑定。
capi.cpaas.io/resource-kind是字面值 BaremetalClusterCAPI 基础设施绑定。
cpaas.io/kube-ovn-join-cidr是operator 选择的 /16 CIDR,不得与 pod/service 或任何其他集群重叠。Kube-OVN 节点间隧道。
cpaas.io/kube-ovn-version建议OS Support Matrix 中的 kube-ovn(chart) 值。固定 provider 创建 cni-kube-ovn 时使用的 chart 版本。省略时,provider 回退到 kube-ovn ModulePlugin 版本。
cpaas.io/sentry-deploy-type是字面值 Baremetal。将集群标记为裸金属部署 profile。
cpaas.io/alb-address-type是字面值 ClusterAddress。裸金属集群使用的 ALB 地址模式。

BaremetalCluster 参数:

参数类型描述必需
.spec.controlPlaneLoadBalancer.typestring(Internal / External)Internal 会部署 Alive static pod 并管理 VIP。External 会跳过 Alive,并要求预先置备 Layer 4 TCP LoadBalancer。否(默认为 Internal)
.spec.controlPlaneLoadBalancer.hoststring控制平面 endpoint。对于 Internal,使用 Self-built VIP。对于 External,使用负载均衡器前端地址。当 endpoint 留空时,会回填到 .spec.controlPlaneEndpoint.host。是
.spec.controlPlaneLoadBalancer.portint(1–65535)控制平面端口。通常为 6443。是
.spec.controlPlaneLoadBalancer.vridint(0–255)Keepalived virtual_router_id。在控制平面 Layer-2 域中必须唯一。对于 External,移除此字段。当 type=Internal
.spec.controlPlaneEndpointobject向 CAPI 暴露的 API server endpoint。设置后不得更改。留空则由 reconciler 从 controlPlaneLoadBalancer 回填。否
.spec.networkTypestring由 provider 管理的 CNI 实现。将其设置为 kube-ovn;任何其他值都会导致 provider 跳过 Kube-OVN AppRelease reconcile 和节点移除集成。Kube-OVN 集群必需

应用并观察:

kubectl apply -f 03-cluster.yaml
kubectl -n cpaas-system get clusters.cluster.x-k8s.io
kubectl -n cpaas-system get kubeadmcontrolplanes.controlplane.cluster.x-k8s.io
kubectl -n cpaas-system get baremetalmachines.infrastructure.cluster.x-k8s.io -w

每个新的控制平面 BaremetalMachine 都会推进 Pending → Allocated → Reprovisioning → Running。观察:

  • BaremetalMachine.status.machineInventoryRef.name —— 被选中的 inventory。
  • BaremetalMachine.status.planSecretRef.name —— 正在驱动的 plan secret。该 secret 携带 annotation baremetal.alauda.io/plan.type=reprovision。
  • MachineInventory.status.plan.state —— 主机完成 cloud-init clean、elemental upgrade、重启以及 kubeadm init/join 后变为 Applied。
  • BaremetalCluster.status.conditions[EndpointReady] —— 配置的控制平面 endpoint 可访问后变为 true。
WARNING

裸金属 provider 不支持单节点控制平面。至少置备三个控制平面 replica(KubeadmControlPlane.spec.replicas: 3),以确保 etcd 保持仲裁。在 Internal 模式下,相同节点还会参与 Alive VIP 仲裁。

步骤 4:部署工作节点

控制平面 Ready 后,创建 worker BaremetalMachineTemplate、worker KubeadmConfigTemplate 和 MachineDeployment。

确认 worker pool 容量

kubectl -n cpaas-system get machineinventorypools.infrastructure.cluster.x-k8s.io <cluster-name>-worker-pool

status.available 必须至少等于目标 replica 数量。如果需要额外容量,请注册更多主机(在这些主机上启动 SeedImage ISO——参见步骤 1),在每个 inventory 仍未分配时准备任何托管存储,然后将其名称添加到 spec.machineInventories。

以此方式添加的主机会经历与原始节点相同的首次启动流程,因此必须满足相同的先决条件:网络连接中所述的网络路径,以及主机时间同步中的时钟门槛。请为新主机确认这两项,不要假定交付时获得的批准涵盖其子网和硬件时钟。

配置 worker BaremetalMachineTemplate

apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: BaremetalMachineTemplate
metadata:
  name: <cluster-name>-worker-template
  namespace: cpaas-system
spec:
  template:
    spec:
      machineInventoryPoolRef:
        name: <cluster-name>-worker-pool

关键参数:

参数类型描述必需
.spec.template.spec.machineInventoryPoolRef.namestringworker pool 名称(同一 namespace)。如需将 worker 移动到其他 pool,请参阅将 worker 移动到其他 pool。是

配置 bootstrap template

imagePullCredentialsVerificationPolicy: NeverVerify 仅从 Kubernetes 1.35 开始需要。使用 Kubernetes 1.34 或更早版本创建 worker 时,请省略此参数。

apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
metadata:
  name: <cluster-name>-worker-bootstrap
  namespace: cpaas-system
spec:
  template:
    spec:
      users:
        - name: boot
          sudo: ALL=(ALL) NOPASSWD:ALL
          shell: /bin/bash
          sshAuthorizedKeys:
            - "<ssh-authorized-keys>"
      files:
        - path: /etc/kubernetes/patches/kubeletconfiguration0+strategic.json
          owner: "root:root"
          permissions: "0644"
          content: |
            {
              "apiVersion": "kubelet.config.k8s.io/v1beta1",
              "kind": "KubeletConfiguration",
              "imagePullCredentialsVerificationPolicy": "NeverVerify",
              "protectKernelDefaults": true,
              "staticPodPath": null,
              "tlsCertFile": "/etc/kubernetes/pki/kubelet.crt",
              "tlsPrivateKeyFile": "/etc/kubernetes/pki/kubelet.key",
              "streamingConnectionIdleTimeout": "5m",
              "clientCAFile": "/etc/kubernetes/pki/ca.crt"
            }
      joinConfiguration:
        patches:
          directory: /etc/kubernetes/patches

provider 会在将 bootstrap user-data 写入 reprovision plan 之前进行最小化规范化,因此请将以下字段从 template 中省略:

  • Hostname / FQDN —— 自动根据 MachineInventoryPool.spec.machineInventories[].hostname 设置(省略时使用 inventory 名称)。
  • kubeletExtraArgs.provider-id —— 自动设置为 baremetal:///<inventory-name>。
  • nodeRegistration.criSocket —— 未设置时自动设置为 unix:///var/run/containerd/containerd.sock。

规范化后的 user-data 也会写回 bootstrap secret 中的 data["resolved-value"],以便调试;原始 data["value"] 保持不变。

配置 MachineDeployment

apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
  name: <cluster-name>-workers
  namespace: cpaas-system
spec:
  clusterName: <cluster-name>
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 0        # Bare-metal pools cannot over-provision.
      maxUnavailable: 1
  selector:
    matchLabels: {}
  template:
    metadata:
      labels:
        cluster.x-k8s.io/cluster-name: <cluster-name>
        pool.name: <cluster-name>-workers
    spec:
      clusterName: <cluster-name>
      version: <kubernetes-version>
      nodeDrainTimeout: 5m
      nodeVolumeDetachTimeout: 5m
      bootstrap:
        configRef:
          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
          kind: KubeadmConfigTemplate
          name: <cluster-name>-worker-bootstrap
          namespace: cpaas-system
      infrastructureRef:
        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
        kind: BaremetalMachineTemplate
        name: <cluster-name>-worker-template
        namespace: cpaas-system

关键参数:

参数类型描述必填
.spec.clusterNamestring目标集群名称。是
.spec.replicasintworker 节点数量。必须满足 replicas ≤ MachineInventoryPool.status.available + status.allocated(针对 worker pool)。是
.spec.template.spec.versionstringworker Kubernetes 版本。必须是 elemental-image-catalog 中的键。可以与控制平面版本不同,但必须遵循标准的 kubelet 版本偏差策略。是
.spec.strategy.rollingUpdate.maxSurgeint裸金属不会超额配置——保持 0。provider 没有可用的备用物理主机来先启动额外节点。是
.spec.strategy.rollingUpdate.maxUnavailableint当 maxSurge=0 时,必须为 > 0。worker 会在此预算内逐个回收。是

应用并观察:

kubectl apply -f workers.yaml
kubectl -n cpaas-system get machinedeployments.cluster.x-k8s.io
kubectl -n cpaas-system get baremetalmachines.infrastructure.cluster.x-k8s.io -w
kubectl get nodes -o wide                     # workload cluster

集群验证

使用 kubectl

# Cluster status
kubectl -n cpaas-system get cluster <cluster-name>

# Control plane progress
kubectl -n cpaas-system get kubeadmcontrolplane <cluster-name>-control-plane

# Per-machine state
kubectl -n cpaas-system get machines.cluster.x-k8s.io
kubectl -n cpaas-system get baremetalmachines.infrastructure.cluster.x-k8s.io

# Pool counters
kubectl -n cpaas-system get machineinventorypools.infrastructure.cluster.x-k8s.io

# Storage lifecycle for inventories that declare managed volumes
kubectl -n cpaas-system get machineinventories.elemental.cattle.io \
  -o custom-columns='NAME:.metadata.name,PHASE:.status.storage.phase,PREPARED:.status.storage.conditions[?(@.type=="StoragePrepared")].status,ACTIVE:.status.storage.conditions[?(@.type=="StorageActive")].status'

# Workload cluster Nodes (use the workload kubeconfig)
kubectl get nodes -o wide

验证控制平面端点

确认配置的模式和端点:

kubectl -n cpaas-system get baremetalcluster <cluster-name> \
  -o jsonpath='{.spec.controlPlaneLoadBalancer.type}{" "}{.spec.controlPlaneEndpoint.host}{":"}{.spec.controlPlaneEndpoint.port}{"\n"}'

对于 Internal,验证 Alive 状态和节点前置条件:

kubectl --kubeconfig <workload-kubeconfig> -n kube-system get pods -l app=alive -o wide
kubectl --kubeconfig <workload-kubeconfig> get --raw=/version

在每个控制平面节点上运行以下命令,而不是在管理集群上运行:

sysctl net.ipv4.conf.all.arp_accept net.ipv4.vs.conntrack

对于 External,根据External LoadBalancer contract 验证负载均衡器前端和每个后端。确认负载均衡器所有者已注册当前所有控制平面节点,并确认每个后端的 HTTPS /healthz 返回 HTTP 200。

预期结果

成功创建的集群应显示:

  • Cluster.status.conditions[Ready]=True。
  • KubeadmControlPlane 个副本全部为 Ready。
  • 每个 BaremetalMachine.status.phase=Running 和 status.ready=true。
  • 每个使用的 MachineInventory.status.plan.state=Applied 都在其 plan secret 上带有注解 baremetal.alauda.io/plan.type=reprovision(这是注解而不是标签——-o jsonpath='{.metadata.annotations}')。
  • 每个使用的、spec.storage.volumes[] 非空的 inventory 都报告 status.storage.phase=Active、StoragePrepared=True 和 StorageActive=True;对应的 BaremetalMachine 报告 StorageReady=True/AllVolumesReady。
  • MachineInventoryPool.status 满足 available + allocated + preparing + reprovisioning + unavailable = total。
  • Kubernetes Nodes 均为 Ready。
  • 对于 Internal,Alive Pods 均为 Ready,并且可以通过自建 VIP 访问 API。
  • 对于 External,负载均衡器前端可访问,并且其后端列表与当前控制平面节点匹配。

常见故障模式

症状可能原因检查位置
BaremetalMachine 停留在 Pending、InventoryAllocated=False / Reason=PoolMissingBaremetalMachineTemplate.spec.template.spec.machineInventoryPoolRef.name 指向不存在的池。池名称和命名空间。
BaremetalMachine 停留在 Pending、InventoryAllocated=False / Reason=PoolExhausted池中没有剩余的 Available inventory。MachineInventoryPool.status.available;每个成员上的分配注解。
BaremetalMachine 在分配后停滞,BootstrapReady=False / Reason=BootstrapWaitingKubeadmConfig 尚未生成 bootstrap data secret。Machine.spec.bootstrap.dataSecretName。
BaremetalMachine 位于 Failed、ImageResolved=False / Reason=ImageCatalogMiss目标 Machine.spec.version 不是 elemental-image-catalog 中的键。该 chart 创建的 catalog 不包含任何条目。kubectl -n cpaas-system get cm elemental-image-catalog -o yaml。按照OS Images Imported and Image Catalog Populated 中的说明添加该键,然后删除受影响的 Machine;BaremetalMachine 不会自行离开 Failed。
ImageResolved=False / Reason=ImageRegistryMissing可选的 cpaas.io/registry-address 注解和平台 registry-credential Secret 均无法解析 registry 地址。集群注解;平台 registry-credential Secret。
inventory 已注册,但未计为 Available其 managed storage 声明尚未达到 StoragePrepared=True,或者主机存储观测缺失或已过期。MachineInventory.status.storage、status.observedStorage、elemental-storage-observer.service,以及 storage-prepare plan 输出。
BaremetalMachine 等待并显示 StorageReady=False所需的 managed volume 未激活,或没有新的重启后观测结果确认其挂载。BaremetalMachine.status.conditions[StorageReady]、MachineInventory.status.storage,以及主机挂载单元。
重新配置始终无法完成;MachineInventory.status.plan.state=Failedelemental upgrade 失败(registry 无法访问、TLS 错误、磁盘已满)。plan secret 的 failed-output 字段;主机串行控制台。
BaremetalCluster 使用 Internal,但无法访问 VIPvrid 与另一个集群冲突;VIP 不在控制平面 L2 域中;VRRP 或 gratuitous ARP 被阻止;IPVS 或所需的 sysctl 不可用。Alive Pods、ip addr show、ipvsadm -Ln,以及控制平面主机上两个必需的 sysctl 键。
BaremetalCluster 使用 External,但无法访问端点Listener、后端成员关系、TLS 透传、健康检查、路由、DNS 或证书 SAN 不正确。负载均衡器配置、每个后端的 HTTPS /healthz,以及 ACP 业务集群端口要求。

有关完整的 operator 侧状态机参考(每个 condition reason 和恢复操作),请参阅 Provider Overview → clean / reprovision plans。

后续步骤

创建集群后:


附录

完整的 KubeadmControlPlane 配置

生产环境裸金属集群推荐使用的加固配置——包括准入控制、审计策略、kubelet 补丁、加密 provider 和 IPv6 绑定地址。请替换解析占位符值表中的占位符。

Kubernetes 1.35 kubelet 设置

仅从 Kubernetes 1.35 开始才需要 imagePullCredentialsVerificationPolicy: NeverVerify。使用 Kubernetes 1.34 或更早版本创建集群时,请省略此参数。

apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
  name: <cluster-name>-control-plane
  namespace: cpaas-system
spec:
  replicas: 3
  version: <kubernetes-version>
  rolloutStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 0
  machineTemplate:
    nodeDrainTimeout: 5m
    nodeVolumeDetachTimeout: 5m
    infrastructureRef:
      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
      kind: BaremetalMachineTemplate
      name: <cluster-name>-control-plane-template
  kubeadmConfigSpec:
    users:
      - name: boot
        sudo: ALL=(ALL) NOPASSWD:ALL
        shell: /bin/bash
        sshAuthorizedKeys:
          - "<ssh-authorized-keys>"
    files:
      - path: /etc/kubernetes/admission/psa-config.yaml
        owner: "root:root"
        permissions: "0644"
        content: |
          apiVersion: apiserver.config.k8s.io/v1
          kind: AdmissionConfiguration
          plugins:
          - name: PodSecurity
            configuration:
              apiVersion: pod-security.admission.config.k8s.io/v1
              kind: PodSecurityConfiguration
              defaults:
                enforce: "privileged"
                enforce-version: "latest"
                audit: "baseline"
                audit-version: "latest"
                warn: "baseline"
                warn-version: "latest"
              exemptions:
                usernames: []
                runtimeClasses: []
                namespaces:
                - kube-system
                - cpaas-system
      - path: /etc/kubernetes/patches/kubeletconfiguration0+strategic.json
        owner: "root:root"
        permissions: "0644"
        content: |
          {
            "apiVersion": "kubelet.config.k8s.io/v1beta1",
            "kind": "KubeletConfiguration",
            "imagePullCredentialsVerificationPolicy": "NeverVerify",
            "protectKernelDefaults": true,
            "tlsCertFile": "/etc/kubernetes/pki/kubelet.crt",
            "tlsPrivateKeyFile": "/etc/kubernetes/pki/kubelet.key",
            "streamingConnectionIdleTimeout": "5m",
            "clientCAFile": "/etc/kubernetes/pki/ca.crt"
          }
      - path: /etc/kubernetes/audit/policy.yaml
        owner: "root:root"
        permissions: "0644"
        content: |
          apiVersion: audit.k8s.io/v1
          kind: Policy
          omitStages:
          - "RequestReceived"
          rules:
          - level: None
            users:
            - system:kube-controller-manager
            - system:kube-scheduler
            - system:serviceaccount:kube-system:endpoint-controller
            verbs: ["get", "update"]
            namespaces: ["kube-system"]
            resources:
            - group: ""
              resources: ["endpoints"]
          - level: None
            nonResourceURLs:
            - /healthz*
            - /version
            - /swagger*
          - level: None
            resources:
            - group: ""
              resources: ["events"]
          - level: None
            verbs: ["get", "list", "watch"]
          - level: None
            resources:
            - group: "coordination.k8s.io"
              resources: ["leases"]
          - level: None
            resources:
            - group: "authorization.k8s.io"
              resources: ["subjectaccessreviews", "selfsubjectaccessreviews"]
            - group: "authentication.k8s.io"
              resources: ["tokenreviews"]
          - level: Metadata
            resources:
            - group: ""
              resources: ["secrets", "configmaps"]
          - level: RequestResponse
            resources:
            - group: ""
            - group: "apps"
            - group: "rbac.authorization.k8s.io"
            - group: "storage.k8s.io"
            - group: "networking.k8s.io"
          - level: Metadata
    postKubeadmCommands:
      - chmod 600 /var/lib/kubelet/config.yaml
    clusterConfiguration:
      imageRepository: cloud.alauda.io/alauda
      dns:
        imageTag: <dns-image-tag>
      etcd:
        local:
          imageTag: <etcd-image-tag>
      apiServer:
        extraArgs:
          audit-log-format: json
          audit-log-maxage: "30"
          audit-log-maxbackup: "10"
          audit-log-maxsize: "200"
          profiling: "false"
          audit-log-mode: batch
          audit-log-path: /etc/kubernetes/audit/audit.log
          audit-policy-file: /etc/kubernetes/audit/policy.yaml
          tls-cipher-suites: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
          admission-control-config-file: /etc/kubernetes/admission/psa-config.yaml
          tls-min-version: VersionTLS12
          kubelet-certificate-authority: /etc/kubernetes/pki/ca.crt
        extraVolumes:
        - name: vol-dir-0
          hostPath: /etc/kubernetes
          mountPath: /etc/kubernetes
          pathType: Directory
      controllerManager:
        extraArgs:
          bind-address: "::"
          profiling: "false"
          tls-min-version: VersionTLS12
      scheduler:
        extraArgs:
          bind-address: "::"
          tls-min-version: VersionTLS12
          profiling: "false"
    initConfiguration:
      patches:
        directory: /etc/kubernetes/patches
      nodeRegistration:
        kubeletExtraArgs:
          node-labels: "kube-ovn/role=master"
    joinConfiguration:
      patches:
        directory: /etc/kubernetes/patches
      nodeRegistration:
        kubeletExtraArgs:
          node-labels: "kube-ovn/role=master"

Worker bootstrap 采用对称配置——有关 worker KubeadmConfigTemplate,请参阅配置 Bootstrap 模板。