在裸金属上创建集群

本文档说明如何使用 bare-metal provider 在物理服务器上创建 Kubernetes 集群。该工作流仅支持 YAML——目前 bare-metal 集群还没有 Fleet Essentials UI。

前提条件

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

1. 安装所需插件

global 集群上安装以下插件:

  • Alauda Container Platform Kubeadm Provider
  • Alauda Container Platform Bare Metal Infrastructure Provider(umbrella chart,同时安装 bare-metal manager 和 elemental-operator

详情请参见 安装指南

2. 导入 OS 镜像并确认 Image Catalog

在创建任何集群资源之前,请从 Alauda 技术支持获取匹配的 OS 镜像对,并将两个镜像都导入到目标平台 registry 中。仅导入一个镜像是不够的:

镜像使用者目的
OS 镜像 (base-image)elemental-image-catalog使用 Kubernetes 专用根文件系统重新配置已注册主机。
OS ISO 镜像 (base-image-iso)SeedImage.spec.baseImage构建用于首次主机安装和注册的 live ISO。

导入完成后,请使用目标平台 registry 中的镜像引用。不要将集群资源指向构建用 registry,也不要从另一个镜像引用派生出新的镜像引用,或替换为独立构建的镜像。继续之前,请在 registry 中或通过镜像导入结果确认这两个最终引用都存在。添加 image-catalog 条目或 SeedImage 引用不会导入对应镜像。

bare-metal provider chart 会附带一个 elemental-image-catalog ConfigMap,它将 Machine.spec.version 映射到用于(重新)配置节点的 elemental upgrade 镜像。你不需要单独创建此 ConfigMap——请确认目标 Kubernetes 版本已存在:

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

所有用作 Machine.spec.version 的值(包括控制平面和 worker MachineDeployment 资源)都必须作为该 ConfigMap 中的 key 出现,并保留前导 v。请确认该 key 在目标平台 registry 中解析到导入的 base-image。provider 会在重新配置时,通过将条目中的 registry 部分替换为平台 registry 地址来解析镜像。如果缺少目标版本,则不会写入 reprovision plan,并且 BaremetalMachine 会一直处于 Failed / Reason=ImageCatalogMiss,直到添加该条目。

3. 网络连通性

  • 每台物理主机都必须能够访问 global.platformUrlelemental-system-agent 注册、plan secret 轮询)。
  • 每台物理主机都必须能够从平台 registry(global.registry.address)拉取镜像,供 elemental install(首次启动期间)和 elemental upgrade(每次重新配置期间)使用。
  • 对于 Internal Self-built VIP,VIP 必须位于与控制平面节点 IP 相同的二层广播域中。vrid 在该域中必须唯一,网络必须允许 VRRP 和 gratuitous ARP 更新,并且节点镜像必须暴露 IPVS,且允许 Alive 将 net.ipv4.conf.all.arp_accept=1net.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. TPM 决策

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

  • emulate-tpm: false(或省略该字段)——仅当主机具有可用的硬件 TPM 时使用,这样 elemental-register 才能将其用于 auth: tpm
  • emulate-tpm: trueemulated-tpm-seed: -1——适用于任何没有硬件 TPM 的主机。这包括虚拟机以及未配备 TPM 模块的物理服务器(例如 Dell R620)。在此类主机上,如果 auth: tpmemulate-tpm: false 组合使用,elemental-register 会因 TPM 验证失败而无法发送注册请求,因此 elemental-operator 会记录为 0 次注册 POST。

5. 公共 Registry 凭证(仅在稍后安装平台组件时需要)

创建 bare-metal 集群不需要 public-registry-credential。只有当新 workload cluster 上后续的平台组件需要从受凭证保护的公共 registry 拉取镜像时,才需要它。如果你的测试范围只到集群和节点 Ready,则可以忽略此前提条件。

6. 主机磁盘和启动准备

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

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

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


集群创建工作流

使用 YAML 时,请先导入成对的 OS 镜像,然后按顺序执行五个必需的资源步骤,在加入 pool 之前还可选执行一个存储准备步骤。所有 Kubernetes 资源都必须应用在 cpaas-system 命名空间中。

步骤执行或应用内容结果
0将匹配的 base-imagebase-image-iso 导入目标平台 registrySeedImage 构建或主机重新配置开始之前,这两个 OS 镜像都已可用。
1MachineRegistrationSeedImageelemental-operator 构建可启动 ISO。
2在每台物理主机上启动 ISO主机执行 elemental install 并注册为 MachineInventory
2a(可选)声明并准备 MachineInventory.spec.storage在 inventory 尚未分配时,显式数据卷会被验证并准备。
3MachineInventoryPool 资源,每个角色一个在 CAPI 创建 Machines 之前,声明允许的控制平面和 worker inventory。
4BaremetalCluster、控制平面 BaremetalMachineTemplateKubeadmControlPlaneClusterCluster API 使用控制平面 pool 启动控制平面滚动。
5worker KubeadmConfigTemplate、worker BaremetalMachineTemplateMachineDeploymentCluster API 使用 worker pool 启动 worker 滚动。
WARNING

重要的命名空间要求

所有 bare-metal 资源都必须应用在 cpaas-system 命名空间中。provider 和 elemental-operator 只会对该命名空间中的对象进行调谐。

WARNING

Workload 集群命名

workload cluster-name 不能global。该名称保留给 global 集群使用,重复使用会导致 workload 集群的资源与 cpaas-system 中的 global 集群资源冲突。按照约定,CAPI ClusterBaremetalCluster 应完全命名为 <cluster-name>,而其依赖资源(KubeadmControlPlaneKubeadmConfigTemplateMachineDeployment、machine templates、pools、registrations)应以前缀 <cluster-name>- 命名。

解析占位符值

下面的示例清单使用 <placeholder> 语法表示环境相关的值:

占位符真实来源获取方式
<cluster-name>操作员提供的 workload 集群名称。不能是 global选择一次并保持一致,用于 ClusterBaremetalCluster、pools、templates 和 registration 资源。
<kubernetes-version>elemental-image-catalog ConfigMap key(带前导 v,例如 v1.33.7-2)。kubectl -n cpaas-system get cm elemental-image-catalog -o yaml
<base-image>导入的 OS 镜像,由 image catalog 用于主机重新配置。从 Alauda 技术支持获取受支持的镜像对,导入后使用其最终的平台 registry 引用。
<base-image-iso><base-image> 配对导入的 OS ISO 镜像。导入受支持的镜像并使用其最终的平台 registry 引用;不要从 <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>操作员决策。provider 托管的 Alive 使用 Internal,已有负载均衡器使用 External参考 规划控制平面端点
<control-plane-vip> / <control-plane-port>操作员提供的稳定端点。对于 Internal,使用控制平面二层域中未占用的 IPv4 VIP。对于 External,使用 load balancer 前端地址。不适用
<vrid>Internal 需要的操作员提供 VRID。它在控制平面二层域中必须唯一。External 时移除此字段。不适用
<install-device>elemental install 的整盘目标。只有在其身份稳定且明确时,直接内核路径才适用。对于复用一个 ISO 的多磁盘 bare-metal 主机,请使用 /dev/elemental-install-target在主机控制台通过 WWN 确认磁盘。参见 在多磁盘裸金属主机上选择固定系统磁盘
<dns-image-tag> / <etcd-image-tag>针对 <kubernetes-version> 烧录到 bare-metal 基础镜像中的组件版本。参见 OS 支持矩阵 中的 corednsetcd 列。
<kube-ovn-version>与所选 ACP 和 Kubernetes 发行版匹配的 acp/chart-cpaas-kube-ovn chart 版本。使用 OS 支持矩阵 中的 kube-ovn (chart) 值,而不是 Kube-OVN 组件版本。
<dns-server>当首次启动注册路径需要显式解析器时,由操作员提供。优先使用 MachineRegistration.config.cloud-config 或后续的 KubeadmControlPlane 启动数据(format: cloud-config)。不要从 SeedImage.cloud-config 写入 /etc/resolv.conf
<ssh-authorized-keys>操作员提供的 OpenSSH 公钥——在节点上进行任何交互式调试都需要它。不适用
<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 节点主机名。由操作员提供;如省略 hostname,则回退为 inventory 名称。
<pods-cidr> / <services-cidr> / <kube-ovn-join-cidr>由操作员提供。不能与主机网络、global 集群的 CIDR 或同一 global 上的任何其他 CAPI 集群重叠。不适用
<machine-name>用于定向删除或恢复的现有 CAPI Machine 名称。kubectl -n cpaas-system get machines.cluster.x-k8s.io
<new-replica-count>扩缩容后期望的 MachineDeployment.spec.replicas 值。操作员提供的目标 worker 数量。
<new-template-name>变更后的 BaremetalMachineTemplateKubeadmConfigTemplate 的新不可变模板名称。由操作员提供;应创建新名称,而不是就地编辑现有模板。
<new-kubernetes-version> / <new-tag> / <new-image>目标升级版本以及匹配的 bare-metal base-image tag 或已解析镜像。使用发布支持矩阵,并确认最终 key 在 elemental-image-catalog 中存在。

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

创建一个 MachineRegistration,用于描述注册 URL 和首次安装的 cloud-config,再创建一个指向匹配 ISO base image 的 SeedImage,让 elemental-operator 以该镜像为目标。

SeedImage.spec.baseImage 设置为导入的 base-image-iso 在目标平台 registry 中的最终引用。它必须与用于后续重新配置的 image catalog 中导入的 base-image 配对;不要自行更改 repository 名称或 tag 来构造 ISO 引用。

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

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

install.deviceinstall.eject-cdinstall.reboot 都是有意设置的。如果省略目标磁盘,elemental install 可能会选到意外的设备。在多磁盘物理主机上,应使用 固定系统磁盘工作流,而不是会变化的 /dev/sdX 名称。如果 eject-cdreboot 为 false,主机在首次安装后可能仍停留在 live 环境中,且永远无法成为 Cluster API 可用的 inventory。

应用清单并等待 SeedImage 构建完成:

kubectl apply -f 01-machineregistration-seedimage.yaml
kubectl -n cpaas-system get seedimage <cluster-name>-registration-iso -w

status.state 变为 Completed 时,获取下载 URL 和 ISO 校验和:

kubectl -n cpaas-system get seedimage <cluster-name>-registration-iso \
  -o jsonpath='{.status.downloadURL}{"\n"}'
kubectl -n cpaas-system get seedimage <cluster-name>-registration-iso \
  -o jsonpath='{.status.checksumURL}{"\n"}'

从该 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 镜像,生成新的 status.observedStorage,并在分配到 pool 之前达到 status.storage.phase=Prepared

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

可选:准备受管数据磁盘

受管存储属于长期存在的 MachineInventory,而不是 CAPI MachineBaremetalMachineTemplate。请在 inventory 尚未分配且加入生产 pool 之前进行配置。

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

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

如果没有磁盘需要管理,请让 spec.storage 保持缺失,或设置 volumes: []。storage controller 会将该 inventory 收敛为 Unmanaged,并标记 StoragePrepared=True/NoManagedVolumes,同时不执行任何磁盘操作。

步骤 2:创建 MachineInventoryPool 资源

每个角色创建一个 pool。pool reconciler 会验证每个成员是否存在,计算容量计数器,并将 baremetal.alauda.io/pool=<pool-name> 注解写入 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 所服务的 workload 集群名称。一个 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=Truetotal = len(spec.machineInventories),以及 available = total - allocated - preparing - reprovisioning - unavailablespec.machineInventories 中的 inventory 如果验证失败(缺失、缺少 plan secret、Ready=False,或存储未准备好),会增加 pool 的 unavailable 计数器,并在 MembersValid 条件中显现。非空的存储声明是一个分配门禁:在针对新的观测完成 Prepare 之前,该 inventory 对 CAPI 不可用(Available)。

控制平面 pool 的规模至少应达到 KubeadmControlPlane.spec.replicas。worker pool 的规模至少应达到 MachineDeployment.spec.replicas。对于滚动升级,pool 必须容纳完整的副本数——provider 使用的是同一 pool 上的先删后加语义,而不会同时执行两者。

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

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

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

  • Internal:provider 部署 Alive,并调谐 VIP 和控制平面后端成员关系。
  • External:provider 跳过 Alive;load balancer 拥有者负责维护 listener、健康检查和后端成员关系。

由于 Bare Metal 尚未有更早的 GA 版本,因此不存在传统的 provider-version 选项卡。请在下面的清单中使用 <control-plane-load-balancer-type> 选择模式。

WARNING

使用 OS 镜像中内置的 Kubernetes 镜像

受支持的 bare-metal OS 镜像会将 kubeadm control-plane、CoreDNS 和 etcd 镜像预加载到 cloud.alauda.io/alauda 下。请在 KCP 配置中保留该 repository,并使用 OS 支持矩阵中的组件 tag。不要将其替换为 <registry-address>/tkestack:那样会生成与 OS 预加载镜像不匹配的镜像引用。OS 通过 containerd 配置其内置 pause 镜像;不要在 KCP kubelet 参数中添加 pod-infra-container-image

TIP

完整配置参考

下面的示例使用的是最小化的 KubeadmControlPlane。如需生产环境建议采用的完整加固配置——包括 admission、audit、kubelet patch、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>

Cluster 注解。 bare-metal provider 在调谐期间依赖少量 Cluster 注解。操作员必须设置的权威注解如下:

注解必需值来源目的
capi.cpaas.io/resource-group-version字面值 infrastructure.cluster.x-k8s.io/v1beta1CAPI infrastructure 绑定。
capi.cpaas.io/resource-kind字面值 BaremetalClusterCAPI infrastructure 绑定。
cpaas.io/kube-ovn-join-cidr操作员选择的 /16 CIDR,不能与 pods/services 或任何其他集群重叠。Kube-OVN 节点间隧道。
cpaas.io/kube-ovn-version推荐来自 OS 支持矩阵kube-ovn (chart) 值。固定 provider 创建 cni-kube-ovn 时使用的 chart 版本。如果省略,provider 会回退到 kube-ovn ModulePlugin 版本。
cpaas.io/sentry-deploy-type字面值 Baremetal将集群标记为 bare-metal 部署配置。
cpaas.io/alb-address-type字面值 ClusterAddressbare-metal 集群使用的 ALB 地址模式。

BaremetalCluster 参数:

参数类型描述必填
.spec.controlPlaneLoadBalancer.typestring (Internal / External)Internal 部署 Alive static pod 并管理 VIP。External 跳过 Alive,并要求预先提供一个四层 TCP LoadBalancer。否(默认 Internal
.spec.controlPlaneLoadBalancer.hoststring控制平面端点。对于 Internal,使用 Self-built VIP。对于 External,使用 load balancer 前端地址。当端点留空时,会回填到 .spec.controlPlaneEndpoint.host
.spec.controlPlaneLoadBalancer.portint(1–65535)控制平面端口。通常为 6443
.spec.controlPlaneLoadBalancer.vridint(0–255)Keepalived virtual_router_id。在控制平面二层域中必须唯一。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 带有 baremetal.alauda.io/plan.type=reprovision
  • MachineInventory.status.plan.state —— 当主机完成 cloud-init cleanelemental upgrade、重启以及 kubeadm init/join 后,状态变为 Applied
  • BaremetalCluster.status.conditions[EndpointReady] —— 当配置的控制平面端点可达时,该值为 true。
WARNING

bare-metal provider 不支持单节点控制平面。请至少配置三个控制平面副本(KubeadmControlPlane.spec.replicas: 3),以确保 etcd 保持法定人数。在 Internal 模式下,这些节点还会参与 Alive VIP 仲裁。

步骤 4:部署 worker 节点

在控制平面 Ready 之后,创建 worker BaremetalMachineTemplate、worker KubeadmConfigTemplateMachineDeployment。完整的 worker YAML 和参数表见 Bare Metal 上管理节点 → 部署 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,请根据 External LoadBalancer 合同 验证 load balancer 前端以及每个后端。确认 load balancer 拥有者已注册当前所有控制平面节点,并且每个后端上的 HTTPS /healthz 都返回 HTTP 200

预期结果

成功创建的集群应显示:

  • Cluster.status.conditions[Ready]=True
  • KubeadmControlPlane 的所有副本均为 Ready
  • 每个 BaremetalMachine.status.phase=Runningstatus.ready=true
  • 每个已使用的 MachineInventory.status.plan.state=Applied,并且其 plan secret 上带有 baremetal.alauda.io/plan.type=reprovision
  • 每个已使用且 spec.storage.volumes[] 非空的 inventory 都会报告 status.storage.phase=ActiveStoragePrepared=TrueStorageActive=True;对应的 BaremetalMachine 会报告 StorageReady=True/AllVolumesReady
  • MachineInventoryPool.status 满足 available + allocated + preparing + reprovisioning + unavailable = total
  • Kubernetes 节点 Ready。
  • 对于 Internal,Alive Pod Ready,且可通过 Self-built VIP 访问 API。
  • 对于 External,load balancer 前端可访问,并且其后端列表与当前控制平面节点一致。

常见失败模式

症状可能原因检查位置
BaremetalMachine 卡在 PendingInventoryAllocated=False / Reason=PoolMissingBaremetalMachineTemplate.spec.template.spec.machineInventoryPoolRef.name 指向了不存在的 pool。pool 名称和命名空间。
BaremetalMachine 卡在 PendingInventoryAllocated=False / Reason=PoolExhaustedpool 中已没有可用的 Available inventory。MachineInventoryPool.status.available;每个成员上的分配注解。
BaremetalMachine 在分配后卡住,BootstrapReady=False / Reason=BootstrapWaitingKubeadmConfig 还没有生成 bootstrap data secret。Machine.spec.bootstrap.dataSecretName
BaremetalMachine 处于 FailedImageResolved=False / Reason=ImageCatalogMiss目标 Machine.spec.version 不是 elemental-image-catalog 中的 key。kubectl -n cpaas-system get cm elemental-image-catalog -o yaml
ImageResolved=False / Reason=ImageRegistryMissing既没有可选的 cpaas.io/registry-address 注解,也没有 platform registry-credential Secret 能解析出 registry 地址。Cluster 注解;platform registry-credential Secret。
inventory 已注册,但未计入 Available其受管存储声明尚未达到 StoragePrepared=True,或者主机存储观测缺失或过期。MachineInventory.status.storagestatus.observedStorageelemental-storage-observer.service,以及 storage-prepare plan 输出。
BaremetalMachine 等待且 StorageReady=False某个必需的受管卷未激活,或者没有新的重启后观测来确认其挂载。BaremetalMachine.status.conditions[StorageReady]MachineInventory.status.storage 以及主机挂载单元。
重新配置始终不完成;MachineInventory.status.plan.state=Failedelemental upgrade 失败(registry 不可达、TLS、磁盘已满)。plan secret 的 failed-output 字段;主机串口控制台。
BaremetalCluster 使用 Internal,但 VIP 不可达vrid 与其他集群冲突;VIP 不在控制平面 L2 域中;VRRP 或 gratuitous ARP 被阻止;IPVS 或所需 sysctl 不可用。Alive Pod、ip addr showipvsadm -Ln,以及控制平面主机上的两个必需 sysctl key。
BaremetalCluster 使用 External,但端点不可达listener、后端成员关系、TLS passthrough、健康检查、路由、DNS 或证书 SAN 配置不正确。load balancer 配置、每个后端的 HTTPS /healthz,以及 ACP workload 集群端口要求。

关于 operator 侧完整状态机参考(每个 condition reason 和恢复动作),请参见 Provider Overview → clean / reprovision plans


下一步

创建集群后:


附录

完整的 KubeadmControlPlane 配置

建议用于生产 bare-metal 集群的加固配置——包括 admission control、audit policy、kubelet patch、encryption provider 和 IPv6 绑定地址。请替换 解析占位符值 表中的占位符。

Kubernetes 1.35 kubelet settings

imagePullCredentialsVerificationPolicy: NeverVerify 仅从 Kubernetes 1.35 开始需要。在使用 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 是对称的——请参见 Bare Metal 上管理节点 → Bootstrap Template 中的 worker KubeadmConfigTemplate