管理裸金属主机上的数据磁盘

使用 MachineInventory.spec.storage 管理已连接到裸金属主机的数据磁盘。存储声明属于长期存在的 MachineInventory,而不属于临时的 Cluster API Machine,因此当节点被移除时,provider 可以保留文件系统和数据,并在之后重新使用相同的 inventory。

此功能不会创建、连接、分离、迁移、备份或复制物理磁盘或 SAN LUN。它只管理在 spec.storage.volumes[] 中明确列出的设备。所有未列出的设备仍会被观测,但不会受到管理。

DANGER

InitializeIfBlank 可以格式化磁盘

仅当你已确认目标 inventory 上准确的稳定设备 ID、确认该设备是可丢弃的空白数据磁盘,并且已独立备份任何重要数据后,才能使用 InitializeIfBlank。切勿从其他主机复制设备 ID,也不要将 /dev/sdX、/dev/vdX、/dev/nvmeXnY 或 /dev/mapper/mpathX 用作持久身份标识。

可用性和升级边界

受管存储需要以下全部三个组件:

  • Bare Metal provider,包括其存储控制器和失败即关闭的 MachineInventory 准入 webhook。
  • 匹配的 elemental-operator CRD 和 observer API。
  • 安装并启用 elemental-storage-observer.service 以及早期启动存储集成的主机 OS 镜像。
WARNING

在启用受管存储之前,请从 Alauda 技术支持获取匹配的 base-image 和 base-image-iso。将 base-image 用于主机重新置备,将 base-image-iso 用于 SeedImage / 实时安装。这些镜像是目标版本支持的配对;不要从一个镜像引用推导另一个镜像引用,也不要替换为独立构建的镜像。

仅在 global 集群上升级 provider 和 elemental-operator,不会在由旧版 OS 镜像创建的主机上安装 observer service。

  • 当 spec.storage 保持缺失或具有 volumes: [] 时,现有集群在管理侧升级后仍可继续使用非受管存储。此路径不会创建存储计划或修改磁盘。
  • 要在旧主机上启用受管存储,请先使用经过批准且支持 observer 的镜像重新安装、重置或替换该主机。在添加任何非空存储声明之前,确认 observer service 和最新的 status.observedStorage 报告均已就绪。
  • 不要将 spec.storage.volumes[] 添加到正在运行或已分配的 inventory 中。只有在 inventory 未分配且所有受管卷均处于非活动状态时,才能更改存储声明。

存储生命周期

阶段触发条件预期状态主机行为
Observe主机注册且 observer 报告设备。Unmanaged 或 Observed只读发现;不会自动选择任何设备。
Prepareoperator 在可用的 inventory 上保存非空存储声明。Prepared、StoragePrepared=True接管现有文件系统,或初始化明确授权的空白设备。业务路径保持未挂载状态。
ActivateCluster API 将已准备好的 inventory 分配给 BaremetalMachine。Active、StorageActive=True重新置备计划安装激活状态;所需的挂载单元会在 kubelet 启动前启动。
Deactivate删除所属的 Cluster API Machine。Prepared、StorageActive=False清理计划卸载受管卷。文件系统、存储所有权和数据均予以保留。
Releaseinventory 可用且处于非活动状态时,从 spec.storage 中移除卷。Unmanaged、StoragePrepared=True/NoManagedVolumes移除存储所有权和激活元数据,但不会擦除文件系统或数据。

删除 Machine 会执行 Deactivate,而不是 Release。因此,正常缩容会保留 inventory 的存储声明和数据,以便日后重用。

前提条件

  1. 按照安装 Bare Metal Provider 中的说明,在 global 集群上安装 Bare Metal Infrastructure Provider,并等待其 manager 部署和准入 webhook 变为 Ready。
  2. 从 Alauda 技术支持获取匹配的、支持 observer 的 base-image 和 base-image-iso。使用 ISO 镜像安装主机,并配置配套的基础镜像,以供后续重新置备使用。
  3. 在 Prepare 成功之前,将目标 MachineInventory 保持在生产池之外。它必须为 Ready=True、未分配,并且具有默认 plan Secret。
  4. 独立备份现有数据。保留存储不等同于应用一致性备份。
  5. 获取由完全相同的 provider 修订版本构建的 storagectl 二进制文件。storagectl 会复用 provider 的规范化、验证和哈希代码;请勿手动计算初始化授权哈希,也不要使用其他版本的二进制文件。
  6. 提交 InitializeIfBlank 的身份必须拥有虚拟 machineinventories/storageinitialize 子资源上的 update 权限。纯 Adopt 声明不需要此额外权限。

设置特定操作所需的变量:

export BM_NS=cpaas-system
export BM_INVENTORY='<machineinventory-name>'
export BM_STORAGECTL='<path-to-matching-storagectl>'
export BM_STORAGE_FILE=/tmp/baremetal-storage.yaml
export BM_INVENTORY_FILE=/tmp/baremetal-machineinventory.json
export BM_PATCH_FILE=/tmp/baremetal-storage.patch.json

验证管理组件和授权:

kubectl -n "$BM_NS" rollout status \
  deployment/cluster-api-provider-baremetal-manager --timeout=5m
kubectl get mutatingwebhookconfiguration \
  cluster-api-provider-baremetal-mutating-webhook-configuration
kubectl get validatingwebhookconfiguration \
  cluster-api-provider-baremetal-validating-webhook-configuration
kubectl auth can-i update machineinventories.elemental.cattle.io \
  --subresource=storageinitialize -n "$BM_NS"

身份在提交或更改初始化授权之前,最后一条命令必须返回 yes。仅向有权格式化磁盘的特定管理员用户或 ServiceAccount 授予此权限。

步骤 1:验证 Inventory 和 Observer

确认 inventory 可用且当前没有所有者:

kubectl -n "$BM_NS" get machineinventory.elemental.cattle.io "$BM_INVENTORY" \
  -o jsonpath='uid={.metadata.uid}{"\n"}allocation={.metadata.annotations.baremetal\.alauda\.io/allocation-state}{"\n"}baremetalMachine={.metadata.annotations.baremetal\.alauda\.io/owner-baremetalmachine}{"\n"}machine={.metadata.annotations.baremetal\.alauda\.io/owner-machine}{"\n"}cluster={.metadata.annotations.baremetal\.alauda\.io/owner-cluster}{"\n"}plan={.status.plan.secretRef.name}{"\n"}'

分配值必须为空或为 Available;所有所有者值都必须为空。如果 inventory 已分配,请删除所属的 CAPI Machine,并等待正常的清理和 Deactivate 流程。请勿手动移除所有者注解或 finalizer。

在主机上验证 observer:

sudo systemctl is-enabled elemental-storage-observer.service
sudo systemctl is-active elemental-storage-observer.service
sudo systemctl cat elemental-storage-observer.service

两条状态命令都必须成功。在 global 集群上,检查观察水位标记和设备信息:

kubectl -n "$BM_NS" get machineinventory.elemental.cattle.io "$BM_INVENTORY" \
  -o jsonpath='{.status.observedStorage.observedAt}{" epoch="}{.status.observedStorage.reportEpoch}{" sequence="}{.status.observedStorage.sequence}{" bootID="}{.status.observedStorage.bootID}{"\n"}'

kubectl -n "$BM_NS" get machineinventory.elemental.cattle.io "$BM_INVENTORY" -o json \
  | jq -r '.status.observedStorage.devices[] |
      [.id,.kind,.systemRole,(.sizeBytes|tostring),(.filesystem.type//"-"),
       (.filesystem.uuid//"-"),([.mounts[].path]|join(",")),
       ((.health.activePaths//0)|tostring),((.health.totalPaths//0)|tostring)] | @tsv'

仅使用报告最新且 systemRole 为 Data 的设备。

设备类型稳定 ID支持的策略
直连磁盘wwn:、nvme-eui:、nvme-nguid:,或 observer 批准的 serial: IDAdopt 或 InitializeIfBlank
现有分区partuuid:仅限 Adopt
多路径聚合映射wwid:Adopt;若整个映射为空白,则也可使用 InitializeIfBlank

请勿选择系统设备或 Unknown 设备、多路径成员、只读或可移动设备,或具有意外分区表、文件系统、LVM、RAID、LUKS、swap、使用者或挂载的设备。请勿同时选择整块磁盘及其分区。

步骤 2:定义受管卷

创建存储声明。此示例有意使用占位符;请将每个设备 ID、UUID、容量和挂载路径替换为同一目标清单中的实际信息。移除不适用于该主机的卷。

baremetal-storage.yaml
storage:
  retryNonce: 0
  volumes:
    - name: cpaas-data
      source:
        deviceID: <blank-direct-disk-id>
        minimumSize: 100Gi
      filesystem:
        policy: InitializeIfBlank
        type: xfs
      mount:
        path: /var/cpaas
        required: true
        options:
          - noatime

    - name: application-data
      source:
        deviceID: <existing-partition-id>
        minimumSize: 100Gi
      filesystem:
        policy: Adopt
        type: ext4
        expectedUUID: <existing-filesystem-uuid>
      mount:
        path: /data/application
        required: true

    - name: registry-data
      source:
        deviceID: <multipath-map-id>
        multipath:
          minimumActivePaths: 2
      filesystem:
        policy: Adopt
        type: xfs
        expectedUUID: <existing-filesystem-uuid>
      mount:
        path: /srv/registry
        required: true
        options:
          - noatime

Storage v2 每个清单最多支持 32 个卷。第一个版本支持整块设备上的 XFS 或 ext4、通过 Adopt 管理的现有 XFS/ext4 分区,以及整块多路径映射。它不会创建分区,也不管理 LVM、mdraid、LUKS、swap、共享集群文件系统或多主机读写访问。

required 默认为 true。可选卷仍会经过准备和激活;只有其失败时对 Ready 状态的影响会有所不同。/var/cpaas 必须始终为必需项。provider 会将 nodev,nosuid 添加到受管挂载中,并根据允许列表验证每个额外挂载选项。

在本地验证声明:

"$BM_STORAGECTL" hash --storage "$BM_STORAGE_FILE"

步骤 3:生成并应用原子补丁

始终从当前的实时对象开始,以确保补丁包含最新的 UID 和 resourceVersion:

kubectl -n "$BM_NS" get machineinventory.elemental.cattle.io "$BM_INVENTORY" -o json \
  >"$BM_INVENTORY_FILE"

对于包含 InitializeIfBlank 的声明,请选择一个大于 status.storage.lastConsumedInitializationApprovalCounter 的正计数器,并生成补丁:

jq '.status.storage.lastConsumedInitializationApprovalCounter // 0' \
  "$BM_INVENTORY_FILE"

"$BM_STORAGECTL" render-patch \
  --inventory "$BM_INVENTORY_FILE" \
  --storage "$BM_STORAGE_FILE" \
  --counter <next-initialization-counter> \
  >"$BM_PATCH_FILE"

生成的合并补丁会将批准绑定到当前 Inventory UID、规范化存储哈希、计数器和 resourceVersion。对于仅包含 Adopt 的声明,请省略 --counter。

运行服务器端 dry-run,再次检查生成的 spec.storage、设备 ID、路径和批准,然后再应用补丁:

kubectl -n "$BM_NS" patch machineinventory.elemental.cattle.io "$BM_INVENTORY" \
  --type=merge --patch-file="$BM_PATCH_FILE" --dry-run=server -o yaml

kubectl -n "$BM_NS" patch machineinventory.elemental.cattle.io "$BM_INVENTORY" \
  --type=merge --patch-file="$BM_PATCH_FILE"

如果 API 报告 resourceVersion 冲突,请重新获取清单、重新生成补丁、重复服务器端 dry-run,然后再应用。切勿从生成的补丁中移除 resourceVersion。

步骤 4:等待 Prepare 完成

监视存储状态:

kubectl -n "$BM_NS" get machineinventory.elemental.cattle.io "$BM_INVENTORY" -w

kubectl -n "$BM_NS" get machineinventory.elemental.cattle.io "$BM_INVENTORY" -o json \
  | jq '.status.storage | {
      phase,appliedSpecHash,pendingSpecHash,
      lastConsumedInitializationApprovalCounter,conditions,appliedVolumes,operation}'

在清单可以分配之前,预期结果如下:

phase=Prepared
StoragePrepared=True/AllRequiredVolumesPrepared
StorageActive=False/Inactive
operation=null

此时,已初始化的卷具有文件系统 UUID,而已接管的卷保留其原始 UUID,但业务挂载路径尚未激活。在 Prepare 完成之前,请勿将清单添加到生产 MachineInventoryPool。

步骤 5:分配并验证激活

将已准备的清单添加到目标 MachineInventoryPool,然后按照在裸金属上创建集群和在裸金属上管理节点中的说明创建或扩展 CAPI machine group。

重新置备完成后,验证 machine 和存储状态:

kubectl -n "$BM_NS" get baremetalmachines.infrastructure.cluster.x-k8s.io -o json \
  | jq -r --arg inventory "$BM_INVENTORY" \
      '.items[] | select(.status.machineInventoryRef.name==$inventory) |
       [.metadata.name,.status.phase,.status.ready,
        ([.status.conditions[]|select(.type=="StorageReady")|(.status+"/"+.reason)]|join(","))] | @tsv'

kubectl -n "$BM_NS" get machineinventory.elemental.cattle.io "$BM_INVENTORY" -o json \
  | jq '.status.storage | {phase,conditions,appliedVolumes,operation}'

预期结果为 BaremetalMachine.status.phase=Running、StorageReady=True/AllVolumesReady、MachineInventory.status.storage.phase=Active 和 StorageActive=True/AllRequiredVolumesActive。

在主机上,确认每个源 UUID、文件系统类型、挂载路径和挂载选项。所需的挂载单元必须在 kubelet 启动之前进入 active 状态:

sudo findmnt --mountpoint /var/cpaas
sudo systemctl is-active "$(systemd-escape --path --suffix=mount /var/cpaas)"
sudo systemctl show "$(systemd-escape --path --suffix=mount /var/cpaas)" \
  -p ActiveEnterTimestamp
sudo systemctl show kubelet.service -p ActiveEnterTimestamp

步骤 6:缩减并重新使用清单

通过正常的缩减或替换工作流删除拥有该资源的 CAPI Machine。不要删除 MachineInventory,也不要移除其存储 finalizer。

完成 drain、清理和 Deactivate 后,预期结果如下:

allocation-state=Available
status.storage.phase=Prepared
StoragePrepared=True
StorageActive=False/Inactive
business mount paths are inactive
filesystem UUIDs and data are retained

再次分配同一清单时,Activate 会重新使用现有的所有权记录和文件系统 UUID;不会再次格式化设备。通用池分配无法保证替换后的 Machine 获得相同的清单。如果应用程序必须返回到特定清单上的主机本地数据,请将该清单置于受控的有序池的首位,或使用专用池,并在依赖这些数据之前验证所选的 BaremetalMachine.status.machineInventoryRef。

步骤 7:释放存储所有权

只有在清单未分配且处于非活动状态时,才能释放卷。移除卷不会破坏数据:它会移除 Storage v2 所有权和激活元数据,但会保留文件系统 UUID 和数据。

要释放所有卷,请创建一个显式的空声明:

baremetal-storage-empty.yaml
storage:
  volumes: []

获取实时清单,并渲染、执行 dry-run,然后应用不带初始化计数器的补丁:

kubectl -n "$BM_NS" get machineinventory.elemental.cattle.io "$BM_INVENTORY" -o json \
  >"$BM_INVENTORY_FILE"

"$BM_STORAGECTL" render-patch \
  --inventory "$BM_INVENTORY_FILE" \
  --storage /tmp/baremetal-storage-empty.yaml \
  >"$BM_PATCH_FILE"

kubectl -n "$BM_NS" patch machineinventory.elemental.cattle.io "$BM_INVENTORY" \
  --type=merge --patch-file="$BM_PATCH_FILE" --dry-run=server -o yaml
kubectl -n "$BM_NS" patch machineinventory.elemental.cattle.io "$BM_INVENTORY" \
  --type=merge --patch-file="$BM_PATCH_FILE"

等待 phase=Unmanaged、StoragePrepared=True/NoManagedVolumes、空的 appliedVolumes[] 和 operation=null。如有需要,使用只读挂载验证保留的数据。实时清单上可能仍会保留存储 finalizer,以便后续对象删除继续遵循正确的清理顺序;不要手动移除它。

失败的操作与重试

存储操作会采取故障关闭策略。缺失、过期、忙碌、容量过小、降级、从未知来源挂载,或不再匹配其声明 UUID 的必需设备,会阻止分配或 Machine 就绪。controller 永远不会自动选择替代磁盘。

在修正 status.storage.operation.phase 为 Failed 的操作的外部原因后,递增 spec.storage.retryNonce,同时保持 volumes[] 不变,并使用 storagectl 渲染新的补丁。retryNonce 会为同一逻辑操作创建另一次执行尝试。它不会授权首次格式化、绕过预检或允许使用不同的存储声明。

不要将重试用作未知或部分变更声明的恢复机制。当 pendingSpecHash 非空,或失败操作报告 mutationState=DurableStateChanged 或 Unknown 时,保留原始声明,并允许同一操作日志逐步收敛。在考虑任何紧急 finalizer 或所有权操作之前,请联系技术支持。

限制

  • 当清单已分配或任何受管卷处于活动状态时,不能更改 volumes[]。
  • 已准备卷的 deviceID、文件系统策略/类型或名称不能就地更改。先释放旧卷,等待操作完成,然后添加替代声明。
  • Storage v2 不会在清单之间移动数据。清单所拥有的数据会保留在该清单标识的物理主机或 LUN 上。
  • Multipath 支持仅管理聚合的 wwid: 映射。它不会配置 multipath、SAN zoning、LUN masking、fencing 或多主机读写文件系统。
  • /var/lib/kubelet、/var/lib/containerd、/var/lib/etcd、/etc、/usr、/boot 以及其他系统路径均受保护。请使用不与受保护路径重叠的业务路径,例如 /var/cpaas、/data/*、/srv/* 或 /mnt/*。
  • 不要同时通过 MachineInventory.spec.storage 和 kubeadm/cloud-init disk_setup、fs_setup、mounts、自定义 systemd 单元或主机脚本管理同一设备或路径。

相关文档