在裸金属上创建集群

本文介绍如何使用裸金属 provider 在物理服务器上创建 Kubernetes 集群。目前,整个工作流仅使用 YAML,Fleet Essentials UI 尚不支持裸金属集群。

前提条件

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

1. 安装必需的插件

在 global 集群上安装以下插件:

  • Alauda Container Platform Kubeadm Provider
  • Alauda Container Platform Bare Metal Infrastructure Provider(umbrella chart,会同时安装裸金属管理器和 elemental-operator)

详情请参阅安装指南。

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 镜像布局归档,其中两个镜像使用同一标签;请参阅各 Provider 的镜像格式。在具有 containerd 且能够访问平台 registry 的主机上以 root 身份运行导入操作,例如在 global 控制平面节点上;Alauda OS 附带 ctr,但不附带 skopeo。/var/lib/containerd 所需的可用空间至少应与归档大小相同。将归档导入专用的 containerd 命名空间,然后列出创建的镜像名称:

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 中都列出了相应标签。--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 不包含任何条目,因此在添加目标版本之前,无法配置任何节点。请对 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

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

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 轮询其计划 Secret。这是平台入口路径,而非直接连接 kube-apiserver
集群加入新集群中的每个节点此集群的控制平面端点TCP 6443,或 BaremetalCluster.spec.controlPlaneLoadBalancer.port 中设置的端口kubeadm join 和 kubelet 访问 API server
集群加入新集群中的每个节点根据 cpaas.io/registry-address 注解解析出的平台 registryTCP 11443节点拉取平台镜像,包括 CoreDNS、kube-proxy 和 kube-ovn
集群管理global 集群节点此集群的控制平面端点TCP 6443,或配置的控制平面端口Cluster API 控制器在 global 上运行,并通过此集群的 API server 协调集群
集群运行时新集群中的每个节点同一集群中的其他所有节点控制平面节点之间的 etcd 2379 和 2380;默认 Geneve 封装使用的 Kube-OVN 覆盖网络隧道 UDP 6081;kubelet;以及未按端口过滤的 Pod 和 Service 流量集群内部流量。完整的组件端口清单请参阅 Communication Matrix。如果您更改了 Kube-OVN 隧道类型或连接了辅助网络,请根据自身配置确认隧道端口
WARNING

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

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

表格适用范围。 这些路径适用于业务集群主机,也就是本页创建的集群。以下两个相邻场景在其他文档中介绍,不包含在上表中:

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

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

控制平面端点。

  • 对于 Internal Self-built VIP,VIP 必须位于与控制平面节点 IP 相同的 Layer-2 广播域中。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. 主机时间同步

所有将成为集群节点的主机(包括控制平面和工作节点)在启动 SeedImage ISO 时都必须已经设置正确时间。平台对跨节点的要求是:每个集群节点使用统一时区,且节点间的时间同步误差不超过 10 秒;请参阅 Node Checks。该要求针对集群节点,因此也适用于此处,但此流程中没有任何步骤会替您验证这一点:该页面上的快速配置脚本和节点检查针对的是平台通过 SSH 加入的传统操作系统主机,不会在由 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,其他节点和客户端会将其视为尚未生效,直到自身时钟追上;时钟落后的主机则会签发有效期短于配置值所表示期限的证书。硬件时钟从未同步过的服务器通常会偏差数分钟或数小时,而非数秒,导致控制平面完全无法启动——并且这一问题会在控制平面启动阶段才暴露,远晚于引入错误时钟的注册步骤。

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。
  • 对于任何不具有硬件 TPM 的主机,使用 emulate-tpm: true 和 emulated-tpm-seed: -1。这包括虚拟机,也包括未配备 TPM 模块的物理服务器(例如 Dell R620)。在此类主机上,auth: tpm 与 emulate-tpm: false 配合使用会导致 elemental-register 的 TPM 证明失败,且不会发送注册请求,因此 elemental-operator 记录的注册 POST 请求数为零。

6. 业务集群镜像 Registry

启动集群不需要 registry 凭证。裸金属主机仅使用平台 registry 地址安装操作系统并加入集群,因此无需配置凭证即可访问其 Node 全部处于 Ready 状态的集群。

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

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

WARNING

凭证可以稍后配置,但必须提前选择 registry。集群在创建时即与其 registry 绑定,现有集群无法迁移到其他 registry。请在应用以下清单之前做出决定,即使您要稍后才安装平台组件也不例外。

无论选择哪种方案,都不会影响上述操作系统镜像。主机在 elemental install 期间以及每次重新预配时,都会从平台 registry 拉取 base-image 和 base-image-iso;此时尚未建立任何集群级绑定。

7. 主机磁盘和启动准备

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

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

仅使用已注册 inventory 报告的稳定存储标识。/dev/sdX、/dev/vdX、/dev/nvmeXnY 和 /dev/mapper/mpathX 等 Linux 路径是运行时路径,不得放入 MachineInventory.spec.storage。

8. 节点标签和污点

若要为某个池中的节点设置专属标签、注解或污点(例如为监控、日志记录或入口流量预留专用池),请按照在节点上配置注解、标签和污点中的说明,在该池的资源上声明这些设置。请在创建池之前进行规划:只有在 capi-controller-manager 上启用标签同步后,Machine 标签才会传递到其 Node;该页面也介绍了此内容。


集群创建工作流

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

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

重要的命名空间要求

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

WARNING

业务集群命名

业务 cluster-name 不得为 global。该名称保留给 global 集群,重复使用会导致业务集群的资源与 global 集群在 cpaas-system 中的资源发生冲突。作为约定,请将 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 的整盘目标。只有在其标识稳定且明确时,直接使用 kernel 路径才合适。对于复用同一 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 提供的目标 worker 数量。
<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 不足:同一镜像的快照会共享扩展区,但升级到不同镜像时,必须同时容纳两个大小约为 3.5 GiB 且互不相关的系统,并会在中途因 no space left on device 失败,导致每次重试都会留下一个不完整的快照。每次集群升级都会经过 elemental upgrade,而分区大小在安装时固定,因此该问题只会在之后暴露——此时集群已经投入生产,若不重新安装就无法重新分区。

请在安装时将其大小调整为至少 20480 MiB(20 GiB)。

位置和阶段都很重要

该片段必须位于 SeedImage.spec.cloud-config 中的 stages.boot 下。将其放在 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 通用;仅当首次启动注册路径需要时,才将站点特定的 resolver 配置放入 MachineRegistration.spec.config.cloud-config。在经过测试的 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,并在分配到池之前达到 status.storage.phase=Prepared。

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

可选:准备托管数据磁盘

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

按照管理裸金属主机上的数据磁盘执行以下操作:

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

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

第 2 步:创建 MachineInventoryPool 资源

每个角色创建一个池。池 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此池服务的业务集群名称。一个 MachineInventory 不得出现在两个活动池中。是
.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

健康的池会报告 Ready=True、total = len(spec.machineInventories) 和 available = total - allocated - preparing - reprovisioning - unavailable。在 spec.machineInventories 中列出但未通过验证的 inventory(缺失、plan secret 缺失、Ready=False 或存储未准备就绪)会增加池的 unavailable 计数器,并显示在 MembersValid 条件中。非空存储声明是分配门控条件:只有在基于最新 observation 成功完成 Prepare 后,inventory 才会被视为 Available 到 CAPI。

将控制平面池的大小设置为至少 KubeadmControlPlane.spec.replicas。将 worker 池的大小设置为至少 MachineDeployment.spec.replicas。对于滚动升级,池必须容纳完整的副本数——provider 使用同一池中的先删除后添加语义,绝不会同时容纳两者。

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

创建 BaremetalCluster(声明所选的控制平面端点模式)、控制平面 BaremetalMachineTemplate(指向控制平面池)、KubeadmControlPlane(副本数 + kubeadm 配置)以及 CAPI Cluster。

Bare Metal API 在同一工作流中支持两种端点模式:

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

不存在旧版 provider 版本选项卡,因为 Bare Metal 没有需要保留的早期 provider 版本。在下面的 manifest 中使用 <control-plane-load-balancer-type> 选择模式。

WARNING

使用 OS image 中内置的 Kubernetes images

受支持的裸金属 OS image 会在 cloud.alauda.io/alauda 下预加载 kubeadm 控制平面、CoreDNS 和 etcd images。在 KCP 配置中保留该 repository,并使用 OS Support Matrix 中的组件标签。不要将其替换为 <registry-address>/tkestack:这会生成与 OS 中预加载 images 不匹配的 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>

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

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,不得与 pods/services 或任何其他集群重叠。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。标记集群使用裸金属部署配置。
cpaas.io/alb-address-type是字面值 ClusterAddress。裸金属集群使用的 ALB 地址模式。

BaremetalCluster 参数:

参数类型描述必填
.spec.controlPlaneLoadBalancer.typestring(Internal / External)Internal 部署 Alive static pods 并管理 VIP。External 跳过 Alive,并要求预先置备 Layer 4 TCP LoadBalancer。否(默认为 Internal)
.spec.controlPlaneLoadBalancer.hoststring控制平面端点。对于 Internal,使用 Self-built VIP。对于 External,使用负载均衡器前端地址。端点留空时,会回填到 .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 端点。设置后不得更改。留空可由 reconciler 从 controlPlaneLoadBalancer 回填。否
.spec.networkTypestring由 provider 管理的 CNI 实现。将其设置为 kube-ovn;任何其他值都会导致 provider 跳过 Kube-OVN AppRelease 协调和节点移除集成。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] — 配置的控制平面端点可访问后变为 true。
WARNING

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

第 4 步:部署 Worker 节点

控制平面 Ready 后,创建 worker BaremetalMachineTemplate、worker KubeadmConfigTemplate 和 MachineDeployment。完整的 worker YAML 和参数表请参阅在裸金属上管理节点 → Worker 节点部署。

集群验证

使用 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,根据外部 LoadBalancer 合约验证负载均衡器前端和每个后端。确认负载均衡器所有者已注册当前所有控制平面节点,并且对每个后端执行 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 都带有 annotation baremetal.alauda.io/plan.type=reprovision(这是 annotation,而不是 label — -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,并且可通过 Self-built VIP 访问 API。
  • 对于 External,负载均衡器前端可访问,并且其后端列表与当前控制平面节点匹配。

常见故障模式

症状可能原因检查位置
BaremetalMachine 卡在 Pending,InventoryAllocated=False / Reason=PoolMissingBaremetalMachineTemplate.spec.template.spec.machineInventoryPoolRef.name 指向不存在的 pool。pool 名称和 namespace。
BaremetalMachine 卡在 Pending,InventoryAllocated=False / Reason=PoolExhaustedpool 中已没有 Available inventory。MachineInventoryPool.status.available;每个成员上的分配 annotation。
BaremetalMachine 在分配后卡住,BootstrapReady=False / Reason=BootstrapWaitingKubeadmConfig 尚未生成 bootstrap data secret。Machine.spec.bootstrap.dataSecretName。
BaremetalMachine 位于 Failed 中,ImageResolved=False / Reason=ImageCatalogMiss目标 Machine.spec.version 不是 elemental-image-catalog 中的 key。该 chart 创建的 catalog 没有条目。kubectl -n cpaas-system get cm elemental-image-catalog -o yaml。按照导入 OS 镜像并填充镜像目录中的说明添加该 key,然后删除受影响的 Machine;BaremetalMachine 不会自行从 Failed 中移除。
ImageResolved=False / Reason=ImageRegistryMissing可选的 cpaas.io/registry-address annotation 和平台 registry-credential Secret 均无法解析 registry 地址。集群 annotation;平台 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 以及主机挂载单元。
Reprovision 永不完成;MachineInventory.status.plan.state=Failedelemental upgrade 失败(registry 无法访问、TLS、磁盘已满)。plan secret 的 failed-output 字段;主机串行控制台。
BaremetalCluster 使用 Internal,但 VIP 无法访问vrid 与其他集群冲突;VIP 不在控制平面 L2 域中;VRRP 或 gratuitous ARP 被阻止;IPVS 或所需的 sysctl 不可用。Alive Pods、ip addr show、ipvsadm -Ln 以及控制平面主机上所需的两个 sysctl key。
BaremetalCluster 使用 External,但端点无法访问Listener、后端成员关系、TLS passthrough、健康检查、路由、DNS 或证书 SAN 配置不正确。负载均衡器配置、每个后端的 HTTPS /healthz 以及 ACP 业务集群端口要求。

如需查看完整的 operator 侧状态机参考(每个 condition reason 和恢复操作),请参阅Provider 概览 → clean / reprovision plans。


后续步骤

创建集群后:


附录

完整的 KubeadmControlPlane 配置

适用于生产裸金属集群的强化配置,包含准入控制、审计策略、kubelet 补丁、加密提供程序和 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 模板。