在裸金属上升级 Kubernetes

本指南介绍如何完成裸金属集群升级工作流的第 2 阶段。在升级 Kubernetes 之前,请先完成升级 Distribution Version中所述的 Distribution Version 升级。

INFO

本页面在完整 ACP 升级流程中的位置

本页面仅介绍升级中的 Kubernetes 步骤。完整的 ACP 升级流程(包括升级构件同步、通过 CVO 升级 ACP Core、升级 Aligned 插件,以及从 Marketplace 升级 Agnostic 插件)记录在以下页面中。开始本页面中的 Kubernetes 步骤之前,请先完成这些步骤:

当集群在裸金属主机上运行 Alauda OS 时,请使用本页面,因为裸金属上的 Kubernetes 步骤会使用新的 elemental upgrade 镜像替换每个节点,而不是在原位置升级二进制文件。

关键注意事项

裸金属升级会替换每个节点,不会在现有 OS 上运行 kubeadm upgrade。其机制如下:

  1. CAPI 根据滚动发布策略删除一个 Machine。
  2. provider 将 clean 计划写入 inventory 的 plan secret。
  3. 主机停止 kubelet,停用所有受管数据挂载,清除 CRI 工作负载,停止 containerd,然后返回同一池中的 Available。
  4. CAPI 创建替代的 Machine。
  5. provider 从同一池中选择一个 Available inventory,根据 elemental-image-catalog 解析新的 Kubernetes 版本,并写入 reprovision 计划。
  6. 主机准备受管卷激活,运行 cloud-init clean → elemental upgrade --system <new-image> → 重启。initramfs 会清除 Kubernetes 持久化状态;受管挂载单元会在 kubelet 之前启动,cloud-init 会重新执行,并针对新的控制平面运行 kubeadm init / kubeadm join。

开始之前,operator 必须牢记以下两个结构性影响:

  • Kubernetes 管理的系统状态不会保留。 /var/lib/kubelet、/var/lib/containerd、/var/lib/etcd 和 /etc/kubernetes 会在每次重新置备的 initramfs 清理步骤中被清除。Storage v2 不允许将这些受保护路径作为受管数据挂载点。
  • 已声明的数据卷可以保留在 inventory 上。 清理计划会停用这些卷,但不会清除其文件系统;重新置备会在 kubelet 启动之前激活所选 inventory 中已准备好的卷。这是主机本地保留机制,不是备份或数据迁移机制。
  • 同一个 MachineInventory 可能不会被再次选中。 provider 不保证由 clean 计划释放的 inventory 会被重新分配给替代的 Machine;数据不会自动跟随替代节点。由于替换操作会在 maxSurge=0 时按先删除后添加的方式执行(旧节点和新节点不重叠),因此池容量只需满足期望的副本数,发布过程中旧节点和新节点不会共存。

升级顺序

按以下顺序升级裸金属集群:

  1. 前提条件:在 global 集群上升级 ACP 平台。这会将裸金属 provider、elemental-operator 以及相关 CAPI 组件升级到能够理解新模式的版本。仅在管理侧控制器完成推出并变为 Ready 后,才触发业务集群升级。
  2. 在业务集群上升级 Distribution Version(Aligned Extensions)。请参阅升级 Distribution Version。
  3. 将目标 Alauda OS 镜像对导入平台注册表,并将目标 Kubernetes 版本添加到 elemental-image-catalog。请参阅更新镜像目录。
  4. 将 Kube-OVN 升级到目标 ACP 版本所需的 chart 版本,并等待 AppRelease 达到 Success。
  5. 升级控制平面的 Kubernetes 版本(一次替换一个控制平面节点)。
  6. 将 worker 节点升级到目标 Kubernetes 版本(在 maxUnavailable 预算内替换所有 worker 节点)。

Cluster API 负责编排滚动替换。

WARNING

跳过步骤 1 可能导致两种故障:旧 provider 会静默忽略新的模式字段;或者控制器镜像在推出过程中发生替换,导致计划 secret 状态机中断。在开始业务集群推出之前,务必先完成管理侧升级。

provider 升级期间的存储兼容性

在引入受管理存储之前创建的现有集群仍保持兼容,只要 MachineInventory.spec.storage 保持缺失或设置为 volumes: []。在这种非受管理模式下,存储控制器不会执行磁盘操作,也不会仅仅因为管理侧 provider 已升级而重写运行中 Machine 的计划。

这种兼容性并不意味着可以在每台旧主机上原地启用受管理存储:

  • 仅在 global 集群上升级 provider 和 elemental-operator,不会向旧版主机 OS 安装 elemental-storage-observer.service,也不会安装早期启动阶段的存储集成。
  • 在此类主机上保持 spec.storage 为空。若要稍后启用受管理存储,请使用已批准且支持 observer 的镜像重新安装、重置或替换主机,然后在 inventory 未分配时,验证 observer 服务和新的 status.observedStorage 报告。
  • 仅在满足上述主机镜像前提条件后,才配置并准备受管理卷。请勿向正在运行或已分配的 inventory 添加非空的存储声明。

有关准备和验证操作步骤,请参阅管理裸金属主机上的数据磁盘。

前提条件

开始之前,请确保满足以下所有前提条件:

  • Distribution Version 升级已完成。
  • 已安装的裸金属 provider AppRelease 报告的请求修订版本与已安装修订版本相同,且状态为 phase=Success。
  • 可通过现有的 <control-plane-vip>:<control-plane-port> 访问控制平面。
  • 所有当前节点均运行正常,且状态为 Ready。
  • 在更改 Kube-OVN 或重新置备任何节点之前,请使用 ACP Cluster Enhancement 创建最新的按需 etcd 备份。确认最新的 status.records 条目报告状态为 result: Success,记录其 backupTimestamp 和 fileName,并确认相应的备份归档文件存在;即使任何控制平面节点被重新置备或变得不可用,仍能检索该文件。
  • 目标 Kubernetes 版本是 elemental-image-catalog 中的一个键。如果不是,请在开始之前添加该版本(参阅更新镜像目录)。
  • 已从 OS Support Matrix 中匹配的 ACP 行读取目标 kube-ovn (chart) 版本。
  • BaremetalCluster.spec.networkType 为 kube-ovn;对于其他任何值,provider 都会跳过 Kube-OVN 协调。
  • 集群中的每台主机都可以访问平台 registry。
  • 推出过程中可能会被重新置备的每台主机,其 state 分区至少有 4 GiB 可用空间,且没有先前升级失败留下的部分快照(参阅检查 COS_STATE 可用空间)。
  • KubeadmControlPlane.spec.rolloutStrategy.rollingUpdate.maxSurge = 0 和 MachineDeployment.spec.strategy.rollingUpdate.maxSurge = 0 均已设置;裸金属不会对物理主机进行超额配置。
  • 相关池有足够的容量,能够一次替换一个节点,同时不低于所需的副本数。
  • 每个存储声明非空的 inventory 均报告状态为 StoragePrepared=True,且每个当前已分配的 inventory 均报告状态为 StorageActive=True。在推出之前,记录每个 BaremetalMachine.status.machineInventoryRef 以及受管理文件系统 UUID,以便检测是否选择了不同的 inventory,而不是假定数据随 Machine 一同迁移。
INFO

可使用 /var/cpaas/backup 下的默认本地归档进行恢复;不需要 S3 兼容存储。但是,裸金属替换可能导致原节点本地副本不可用,而且无法保证替换后的 Machine 使用相同的 MachineInventory。如果恢复设计无法保证在节点替换或故障后访问本地归档,请在开始第 2 阶段之前,将副本保存在节点之外的 S3 兼容存储或其他安全位置。

检查 COS_STATE 可用空间

每次重新置备都会在 COS_STATE 上创建新的快照,并将完整的目标系统写入其中。由于升级目标是不同的镜像,因此该分区必须同时容纳两个彼此无关、大小约为 3.5 GiB 的系统。空间不足的主机会在执行到一半时因 no space left on device 而失败,而失败的尝试会留下部分快照——因此每次自动重试开始时的可用空间都会比上一次更少。在 8 GiB 分区上重试三次就足以将其完全填满。

在开始推出之前检查这一点。检查受影响池中的每台主机,而不只是预计会被替换的主机:clean 计划释放的 inventory 不保证会再次被选中,因此任何 Available inventory 都可能接收新镜像。

在每台主机上:

# Free space on COS_STATE. Use btrfs, not df — df does not account for snapshot extents.
btrfs filesystem usage -h /

# Retained snapshots
snapper --no-dbus list

Free (estimated) 必须至少为 4 GiB——目标系统约为 3.5 GiB,此外还需要为复制期间写入的 btrfs 元数据预留空间。如果低于此值,请在开始推出之前释放空间。

删除升级失败后留下的部分快照

Elemental 在向快照写入数据时会将其标记为 update-in-progress=yes,并且仅在升级完成后清除该标记。仍带有此标记、且既不是默认快照也不是正在运行的快照的快照,就是失败尝试的残留,可以安全删除。

列出包含以下三个判定列的快照:

snapper --no-dbus --csvout list --columns number,default,active,userdata

仅当以下三个条件全部满足时,才删除快照——default 为 no,active 为 no,且 userdata 列包含 update-in-progress=yes:

snapper --no-dbus delete --sync <number>
WARNING

绝不要删除 default 或 active 列为 yes 的快照。前者是主机下一次将要引导的快照,后者是当前正在运行的快照;删除其中任何一个都会导致主机无法引导。

随后重新运行 btrfs filesystem usage -h /,确认空间确实已回收。在已达到 100% 使用率的分区上,snapper delete 本身可能会因缺少元数据空间而失败——这正是最初导致部分快照累积的相同情况。如果删除失败,请将该主机视为需要重新安装,而不要针对它重试推出。

如果删除所有部分快照后,主机的可用空间仍少于 4 GiB,则分区本身过小。在应用 COS_STATE sizing 之前安装的主机使用 8 GiB 的 COS_STATE,而分区大小在安装时即已固定——扩大分区需要重新安装。请单独规划此操作;不要针对这类主机开始推出并期待其成功。

更新镜像目录

elemental-image-catalog 是用于向裸金属 provider 引入 Kubernetes 版本的资源。

INFO

裸金属 provider 插件不包含任何 Kubernetes 版本或 OS 镜像。其 chart 创建的 elemental-image-catalog 不包含任何条目;升级插件或 Distribution Version 都不会添加条目。每个新的 Kubernetes 版本都必须显式添加,且需先将对应的 OS 镜像导入平台 registry。

首先,按照导入 OS 镜像并填充镜像目录中的说明,将目标版本的 baremetal-base-image 和 baremetal-base-image-iso 从 Bare Metal OS 归档导入平台 registry。然后使用以下任一选项添加版本。

选项 A — 添加 chart override。 在 global 集群上 provider 的 ModuleInfo 中,将新版本追加到 spec.valuesOverride 内的 provider.imageCatalog.images 下(使用 global.registry.address 作为 registry),与已在此处设置的值并列。不要替换整个 valuesOverride:这会删除 caCertSecretKey,导致 elemental-operator 无法启动。若要查找并编辑 ModuleInfo,请参阅安装集群插件:

spec:
  valuesOverride:
    acp/chart-cluster-api-provider-baremetal:
      provider:
        imageCatalog:
          images:
            <new-kubernetes-version>:
              repository: tkestack/baremetal-base-image
              tag: <official-image-tag>

平台会重新渲染 ConfigMap;provider 进程内的 watch 会热加载缓存,无需重启。在 DR 配对中,请在两个 global 集群上都执行此操作。

选项 B — 直接修补 ConfigMap。 如果安装插件时未使用 chart override,或者需要将已批准的镜像引用直接固定在目录中,请使用此选项:

kubectl -n cpaas-system patch configmap elemental-image-catalog \
  --type='json' \
  -p='[{"op":"add","path":"/data/<new-kubernetes-version>","value":"<registry-address>/tkestack/baremetal-base-image:<new-tag>"}]'

无论使用哪种选项,继续之前都要进行验证:

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

键必须包含前导 v(例如 v1.34.5)。如果 CAPI 创建替代的 Machine 时缺少该键,则生成的 BaremetalMachine 会进入 Failed,并带有 ImageResolved=False / Reason=ImageCatalogMiss,且不会写入 reprovision 计划。之后再添加该键不会重启它:BaremetalMachine 会一直处于 Failed,直到你删除该 Machine,并由其所有者创建新的实例。使用 maxSurge: 0 时,此时待替换的节点已经消失,因此必须在更改任何版本之前添加该键。

裸金属 provider 使用 base-image 进行重新置备,不会自动重建 SeedImages。在后续主机入驻之前,请使用从同一归档导入的 base-image-iso 刷新 MachineRegistration / SeedImage。不要混用来自不同归档的镜像,也不要替换为独立构建的镜像。

在升级控制平面前升级 Kube-OVN

按照在升级控制平面前升级 Kube-OVN中的裸金属流程操作。

共享操作步骤会验证 BaremetalCluster.spec.networkType,更新 CAPI Cluster 注释,让 provider 协调完整的 chart 源和规范,并检查 chart 名称、请求的修订版本、已安装的修订版本、阶段和条件。不要手动修补 AppRelease 源。

升级控制平面

继续之前,请完成共享的在升级控制平面前升级 Kube-OVN操作步骤,并确认 cni-kube-ovn 已安装目标修订版本,且状态为 phase=Success。

将 KubeadmControlPlane.spec.version 修补为新的 Kubernetes 版本。如果组件镜像标签(DNS、etcd)已固定,请在同一次编辑中更新这些标签:

kubectl -n cpaas-system edit kubeadmcontrolplane <cluster-name>-control-plane
  • spec.version ← 目标 Kubernetes 版本(必须与某个 elemental-image-catalog 键匹配)。
  • spec.kubeadmConfigSpec.clusterConfiguration.dns.imageTag ← 与新版本匹配的 CoreDNS 镜像标签。
  • spec.kubeadmConfigSpec.clusterConfiguration.etcd.local.imageTag ← 与新版本匹配的 etcd 镜像标签。
  • 目标版本为 Kubernetes 1.35 或更高版本时,请按照适用于 Kubernetes 1.35 的必需 kubelet 补丁中的说明,在同一次编辑中更新 spec.kubeadmConfigSpec.files 中的 /etc/kubernetes/patches/kubeletconfiguration0+strategic.json。

仅升级 Kubernetes 时,裸金属 provider 不需要新的 BaremetalMachineTemplate:模板只包含池引用,升级镜像会通过目录从 Machine.spec.version 解析。只有需要将控制平面迁移到其他池时,才需要新模板。

Cluster API 会逐个滚动升级控制平面节点(因为 maxSurge=0):

kubectl -n cpaas-system get kubeadmcontrolplane <cluster-name>-control-plane -w
kubectl -n cpaas-system get baremetalmachines.infrastructure.cluster.x-k8s.io
kubectl -n cpaas-system get machineinventorypools.infrastructure.cluster.x-k8s.io <cluster-name>-control-plane-pool
kubectl get nodes -o wide                                                    # workload cluster

每个节点的预期操作顺序:

  1. CAPI 将一个旧的 Machine 标记为待删除;对应的 BaremetalMachine 转为 Preparing。
  2. provider 写入 clean 计划;托管卷会停用但不会擦除,最终清单状态为 Available,所有者注释已清除,存储已返回到 Prepared。
  3. CAPI 创建新的 Machine(使用目标版本);随后创建新的 BaremetalMachine。
  4. provider 从控制平面池分配一个 Available 清单,解析新镜像,并写入 reprovision 计划。
  5. 主机为所选清单中已准备好的卷暂存激活操作,运行 elemental upgrade --system <new-image>,重启,在 kubelet 启动前启动所需的挂载单元,并对存续的控制平面执行 kubeadm join。只有在所需存储已激活后,BaremetalMachine.status.phase 才会变为 Running。

重复步骤 1–5,直到所有控制平面节点都已替换。

在整个滚动升级期间,alive 会继续管理 VIP。随着控制平面成员发生变化,裸金属 provider 会重新渲染 alive chart 的值(peer 列表和 ipvs.ips),并滚动更新静态 Pod 清单。

升级工作节点

将每个 MachineDeployment.spec.template.spec.version 修补到新的 Kubernetes 版本。CAPI 会在 maxUnavailable 预算内替换工作节点——裸金属 provider 会复用与控制平面相同的工作节点池和镜像目录解析逻辑。

对于 Kubernetes 1.35 或更高版本,首先创建一个包含所需 kubelet 补丁的新工作节点 KubeadmConfigTemplate,然后在与版本升级相同的 MachineDeployment 编辑中更新 spec.template.spec.bootstrap.configRef.name。下面的命令包含该引用。对于不需要 bootstrap 更改的早期目标版本,请省略 bootstrap 对象。按照更新 Bootstrap 模板创建新模板。

kubectl -n cpaas-system patch machinedeployment <cluster-name>-workers \
  --type='merge' \
  -p='{"spec":{"template":{"spec":{"version":"<new-kubernetes-version>","bootstrap":{"configRef":{"name":"<new-bootstrap-template>"}}}}}}'

查看:

kubectl -n cpaas-system get machinedeployments.cluster.x-k8s.io -w
kubectl -n cpaas-system get baremetalmachines.infrastructure.cluster.x-k8s.io
kubectl get nodes -o wide

工作节点升级所需调整项少于控制平面:MachineDeployment 上没有 clusterConfiguration 块,因此无需更新 DNS / etcd 标签。工作节点上的 Kubernetes 组件版本会跟随新的基础镜像。

对于更早的版本,仅当发行版本更改了 cloud-init 或 kubeletExtraArgs 等设置时,才需要更改 bootstrap 模板。

跨版本升级

Kubernetes 控制平面必须经过受支持的次要版本跃迁。使用准备跨版本升级来识别并预先准备每个中间 ACP 行。

对于每个中间行以及最终的目标行:

  1. 导入该行的 OS 镜像对,并将其 Kubernetes 版本添加到 elemental-image-catalog,如更新镜像目录中所述。
  2. 将 Kube-OVN 升级到该行的 kube-ovn (chart) 版本,并等待 phase=Success。
  3. 将控制平面升级到该行的 Kubernetes 版本,并等待每个控制平面节点变为 Ready。
  4. 将每个工作节点 MachineDeployment 升级到相同的 Kubernetes 版本,并等待滚动升级完成后再开始下一次跃迁。

版本值始终来自 OS Support Matrix,而不是本指南中的固定示例。裸金属滚动升级策略已经将节点替换串行化,因此跨版本升级所需的额外池容量不会超过同一 minor 版本升级所需的容量。

从失败的阶段 2 升级中恢复

不要将 Kubernetes minor 版本降级视为普通回滚:

  1. 如果尚未创建目标版本控制平面 Machine,请使用阶段 1 恢复期间恢复 Kube-OVN恢复 Kube-OVN,然后恢复之前的 KubeadmControlPlane 和 MachineDeployment manifest 值。
  2. 如果目标 minor 版本的控制平面机器已加入,请停止进一步发布,并在目标 minor 版本上向前修复。如果向前修复无法恢复集群健康状态,请按照 ACP etcd 恢复操作步骤,从经过验证的升级前备份恢复集群。不要将 Kubernetes、CoreDNS 或 etcd 修补回之前的 minor 版本。

恢复 etcd 属于灾难恢复操作,而不是升级前验证步骤。不要仅为验证备份而在健康集群上运行恢复操作步骤。

每个完成 reprovision 计划的主机都已替换其 OS 镜像并清除 Kubernetes 管理的状态。更改版本字段不会重建之前的节点状态。

验证

发布完成后:

kubectl -n cpaas-system get baremetalmachines.infrastructure.cluster.x-k8s.io \
  -l cluster.x-k8s.io/cluster-name=<cluster-name> \
  -o custom-columns='NAME:.metadata.name,PHASE:.status.phase,INVENTORY:.status.machineInventoryRef.name,PLAN_SECRET:.status.planSecretRef.name'
kubectl -n cpaas-system get machines.cluster.x-k8s.io \
  -l cluster.x-k8s.io/cluster-name=<cluster-name>
kubectl -n cpaas-system get apprelease cni-kube-ovn
kubectl get nodes -o wide

cni-kube-ovn AppRelease 应报告目标已安装修订版本,并显示 phase=Success。对于目标集群,每个当前的 BaremetalMachine 都应为 Running,每个 CAPI Machine 都应报告新的 spec.version,并且每个业务 Node 都应为 Ready,且使用新的 kubelet 版本。

对于每个当前的 BaremetalMachine,使用 INVENTORY 和 PLAN_SECRET 列验证支撑运行中节点的清单和计划:

kubectl -n cpaas-system get machineinventories.elemental.cattle.io <inventory-name> \
  -o jsonpath='{.status.plan.state}{" storage="}{.status.storage.phase}{"\n"}'
kubectl -n cpaas-system get secret <plan-secret-name> \
  -o jsonpath='{.metadata.annotations.baremetal\.alauda\.io/plan\.type}{"\n"}'

每个被引用的 MachineInventory 都应报告 Applied,并且每个被引用的计划 Secret 都应报告 reprovision。仅对当前 BaremetalMachine 对象所引用的清单执行此检查。未参与发布的备用清单可能没有 reprovision 计划。已完成 clean 计划但未被选中用于替换 Machine 的清单可能仍报告 clean;这两种情况都不表示发布未完成。

对于包含受管卷的清单,还应验证 status.storage.phase=Active、StoragePrepared=True、StorageActive=True 和 BaremetalMachine.status.conditions[StorageReady]=True/AllVolumesReady。将其当前文件系统 UUID 与升级前记录进行比较。不同的 UUID 通常表示选择了不同的清单;不得将其解释为原始数据已迁移。

MachineInventoryPool.status 应使用 preparing = reprovisioning = 0 满足 available + allocated + preparing + reprovisioning + unavailable = total。当池中包含备用清单时,available 可能仍大于零;这些清单不在上述计划验证范围内。

故障排查

问题检查项
Kube-OVN 注解已更改,但 chart 名称、目标修订版本或已安装修订版本未收敛检查 AppRelease 条件和 provider 日志。按照共享的 Kube-OVN 验证 操作;不要手动修补源。
发布卡在第一个新节点上BaremetalMachine.status.conditions[ImageResolved] — 确认新密钥位于 elemental-image-catalog 中,并且 Cluster 携带 cpaas.io/registry-address。
清理后清单未变为可分配检查 MachineInventory.status.storage 和 status.observedStorage。在清单可以返回分配集合之前,必须根据主机的新观察结果确认受管卷处于非活动状态且已 Prepared。
替换 Machine 等待 StorageReady验证所选清单是否为预期清单,所需设备 ID 和文件系统 UUID 是否仍然匹配,所需挂载单元是否处于活动状态,以及主机是否生成了重启后的最新观察结果。
主机重启进入一个始终无法加入集群的状态MachineInventory.status.plan.state(Failed 为终态);检查主机的串行控制台以查找 elemental upgrade 错误。验证主机是否可以访问平台注册表。
elemental upgrade 因 no space left on device 失败state 分区太小 — 8 GiB 的 COS_STATE 无法同时容纳正在运行的系统和升级到不同的镜像。每次失败尝试还会留下一个部分快照,因此可用空间会持续减少。按照移除失败升级留下的部分快照中的说明移除这些快照,然后让计划重试以恢复。应用 COS_STATE 大小调整 之前安装的主机需要重新安装才能扩展分区。
新控制平面节点的 kubeadm join 失败确认 VIP 仍可访问;alive 可能已丢失锁。检查静态 pod 中的 kubectl -n kube-system get lease、ipvsadm -Ln、keepalived 日志。
Worker 发布因设置了 MachineDeployment.spec.strategy.rollingUpdate.maxSurge > 0 而停滞裸金属池无法实现超额配置。设置 maxSurge=0,并提升 maxUnavailable,以便在池中为更多并行替换分配预算。
发布过程中池容量耗尽MachineInventoryPool.status.available=0 和 allocated 仍包含尚未替换的节点。向池中添加另一个 MachineInventory 以释放队列,或等待正在进行的 Preparing 节点完成排空。

有关完整的 operator 侧状态机参考(包括每个条件原因和恢复操作),请参阅 Provider 概述。


其他资源