7165 字
36 分钟

从 kubeasz 到 kubeadm:三节点存量集群的分批接管

2026-10-02,三台物理节点上的存量集群完成了从 kubeasz 到 kubeadm 的管理交接。 生产 Kubernetes 保持 1.35.9;三个控制面组件和 etcd 变成静态 Pod,kube-proxy 转为原生 DaemonSet,CoreDNS 纳入 kubeadm addon。当天傍晚,又清理了旧管理容器、镜像和工具目录。

这次没有重建生产集群,没有重新初始化 etcd,也没有为了换部署工具重新创建业务卷。结束时,原 94 对 PV/PVC 和 13 台 VMI 的身份保持,三节点 Ready,Ceph HEALTH_OK。新节点加入、证书续期、重建和重启则放在隔离实验环境里验证。

但过程并不顺滑:遇到了切换前的存储告警、API 停止等待超时、实验机上的旧域名依赖、DNS Service 地址冲突,以及验收脚本自身的误判。这篇按实际先后关系记录从规划到清理的完整过程。

文中时间均为北京时间,三台生产主机用 node-a、node-b、node-c 代称;省略真实内网地址、私仓地址、对象名称、身份标识和备份目录。状态截至 2026-10-02 18:10 的清理后复核,不是对之后现网状态的持续保证。这里只展示必要的机制与检查命令,不能当成任意 kubeasz 集群的直接执行脚本。

起点:版本已经升级,管理方式还没有变#

此前的虚机到物理机迁移完成后,三台机器同时承担控制面、etcd、worker 和 Ceph。之后先沿原路线完成了 Kubernetes 版本升级,正式接管时的主要版本已经是:

组件接管窗口使用的版本
Kubernetes / kubeadm1.35.9
etcd3.6.15
containerd / runc2.3.6 / 1.4.3
Calico / 通用 CNI3.31.7 / 1.8.0
Multus thick4.3.1
CoreDNS / NodeLocal DNSCache1.12.4 / 1.26.4
Rook / Ceph / Ceph CSI1.20.8 / 19.2.6 / 3.17.1

这些版本是这次的输入条件,没有借接管再做一轮生产版本升级。

原来的 kubeasz 负责准备主机、分发文件和执行部署。apiserver、controller-manager、scheduler、etcd、kube-proxy 在宿主机上由 systemd 启动。已有的本地 kube-lb、etcd TLS 端点、IPVS、NodeLocal DNS,以及 Calico 共用的 etcd 数据存储,都与一套全新 kubeadm 集群的默认布局存在差异。

因此,迁移要核对的是正在运行的参数、证书用途和数据身份,不能仅按旧 inventory 生成一套默认配置覆盖上去。

先把“接管哪些东西”说清楚#

我希望后续可以沿 kubeadm 的原生配置、证书、join 和 upgrade 流程维护核心集群。这不意味着把全部平台组件都放进 kubeadm addon。

最终确定的边界如下:

对象接管后的归属
apiserver、controller-manager、schedulerkubeadm 配置和补丁生成静态 Pod,由 kubelet 运行
etcdkubeadm local/stacked 布局,静态 Pod 使用原数据目录与成员身份
kubelet 配置、引导和客户端证书衔接使用 kubeadm 的配置、bootstrap 与 finalize 流程
kubelet 程序、systemd unit、主机参数主机维护流程独立负责
kube-proxykubeadm 原生 addon,DaemonSet + ConfigMap
CoreDNSkubeadm 原生 addon,保留审阅后的 Corefile 和资源补丁
containerd/runc、CNI 二进制、Keepalived/kube-lb独立主机组件
Calico、Multus、NodeLocal DNS各自维护配置和发布流程
Rook/Ceph/CSI、Ingress、监控、数据库与应用保持原 Helm、Operator 或应用发布流程

kubeadm 的职责集中在建立和维护核心集群;网络插件等附加能力需要其他管理流程配合。不能把别的发行版或部署工具里的“addons”列表,当成 kubeadm 原生支持的组件列表。kubeadm 的职责范围

对同一个资源只保留一个日常配置入口。完成原生 addon 接管后,再用旧 kubeasz role 覆盖它,会重新制造配置冲突。

数据保护:有备份文件,也有不能承诺的部分#

迁移最初的约束是对象存储数据不能丢,也可以为了数据保全暂停写入。完整规划因此先提出了独立对象副本、对象与历史版本清单、隔离恢复,以及冻结点核对。

现场没有可用的外部存储。这些条件没有因为写进方案就自动成立,最终也没有完成全部 RGW 对象的独立备份和恢复验证。

在明确接受这项剩余风险后,执行才按阶段推进。实际准备了新的 etcd 快照、节点配置/身份恢复材料和必要数据库备份,在不同主机保留副本,并核对大小、摘要和原生格式。几个关键切换点前又刷新了 etcd 快照。

这里必须区分三件事:

  • etcd 快照保护的是集群元数据,不是 Ceph 里的对象、块设备和文件内容。
  • 两台现有主机上的副本有助于单节点恢复,但仍处于同一集群故障域。
  • 在线数据库导出、etcd 快照和一个 S3 测试对象,不构成全部业务一致的停写冻结点。

因此,这次的结果不能写成“对象零丢失已经得到保证”。允许停写,也不等于实际完成了全量对象冻结、备份和恢复。

实验不是一次做完就永久有效#

最早在 09-28 建了三个 KubeVirt 实验 guest,使用独立 CA、机器身份、网络和合成数据,分别放到三台物理节点上。没有把生产 etcd 快照或生产私钥塞进实验集群。

那时实验 Kubernetes 还是 1.33.1。原生 addon 接管 CoreDNS 时出现了:

start version '1.12.1' not supported

改用 kubeadm 1.34.12 后,早期接管与回退流程才得以验证。但工具的不同子命令有不同版本约束:它能完成当时测试的 phase,不代表能用同一个工具正常执行任意 1.33 目标的 upgrade apply。当时也没有用 --force 把失败改成成功。

生产后来已经升级到 1.35.9,旧实验结果自然不能直接给新候选背书。正式接管前重新对齐工具、控制面镜像与本阶段相关组件,逐项复演正向切换和回退。实验 etcd 还经过了 3.5 补丁版本到 3.6 的必要对齐;生产 etcd 早已是 3.6.15,生产 C 阶段只是同版本运行方式交接。

实验也始终有覆盖边界:原三台 guest 的部分运行时和网络版本仍旧,不能称完全同构。后面新增的一次性节点使用了与生产一致的 containerd 2.3.6/runc 1.4.3;真实生产 Calico/Multus 的行为,则靠生产上的专用探针另外验证。

分批路线:先交接进程,再补齐管理能力#

实际阶段划分是:

阶段目标
B1–B3scheduler → controller-manager → apiserver,先完成一台,再推进其余两台
B4标准 PKI/kubeconfig、kubelet 衔接、管理元数据、引导能力和统一 API 入口
Cetcd 转静态 Pod,随后 API 改连本机 TLS etcd,管理元数据转 local
Dkube-proxy 从 systemd 逐台交给 DaemonSet
ECoreDNS 原生管理与 NodeLocal/Service 地址过渡
F后续维护能力复演、全栈验收、退役旧管理入口和资源清理

每个阶段都有自己的候选、备份、验证和停止条件。中间状态是有意保留的:例如 B4 结束时仍是 external etcd,DNS/proxy 还未接管,配置里就明确禁用对应 addon,避免提前执行后续步骤。

切换前先停在了存储告警上#

10-01 傍晚,生产交接前的检查发现 Ceph HEALTH_ERR / MDS_DAMAGE。当时首节点控制面尚未切换,不能把它归因于这次接管,也不能拿之前某次 HEALTH_OK 覆盖新的告警。

迁移暂停,先处理存储问题。恢复工作后,又重新检查 damage 表、相关文件读取和集群状态,才继续准备控制面切换。

暂停期间还发现一组游戏服务及其卷已经不在了。确认是主动卸载后,只更新这一项基线,后续保护其余 94 对原业务卷。如果不先核实,脚本既可能把正常卸载误报为迁移损失,也可能错误地把已删除业务重新创建回来。

每台只允许一个组件的新旧实例交接#

先给现有 kubelet 配置 staticPodPath,检查 systemd 停止语义,再让它读取候选。调整 kubelet 时核对原运行容器 ID,避免把准备静态 Pod 变成一次意外的业务重启。

每个组件的顺序是:

校验候选与回退文件
→ 停止旧 systemd 服务
→ 确认旧进程退出
→ 原子放入当前组件的静态清单
→ 检查唯一新实例、镜像、就绪与实际功能
→ 归档并 mask 旧 unit
→ 通过本组件验收后再继续

失败时只退回当前组件:先移出新清单,确认新容器退出,再恢复旧进程。不会让两个 etcd 进程同时打开同一数据目录,也不会用恢复整份旧快照代替普通的进程回退。

scheduler 和 controller-manager 还涉及选主。备用实例 Ready,并不能证明它已经实际完成领导和调谐;实验里验证领导行为,生产切换到后两台时也记录了 Lease 转移,并验证新 Pod 调度和 Deployment 调谐。

第一次 API 切换确实回退了#

首节点的 API 在 graceful shutdown 时耗时较长,HTTP shutdown 还出现了 deadline 日志。进程随后以 0 退出,但外层脚本也只等 60 秒,先触发了超时。

这时新 API 清单尚未激活,便恢复了原 systemd API。其余两台继续提供服务,前两批已验收的 scheduler/controller-manager 保持。

核对后,修正范围只有一个:API 外层等待从 60 秒提高到 120 秒,systemd 原有 90 秒限制不变。没有改 API 参数、放宽健康条件或同时切第二台。重试通过后,再按 node-b、node-c 推进,10-02 02:03 完成九个控制面静态 Pod 的交接。

采样期间确实有正在切换的单节点 API 请求失败,未观察到三个入口同时失败。采样不是无间隙覆盖,也不能把失败次数直接乘一个间隔当作精确中断秒数,更不能据此称业务零中断。

静态 Pod 有 YAML,但 kubeadm 不在每次开机时运行#

最终每台节点的活动目录是:

/etc/kubernetes/manifests/
├── kube-apiserver.yaml
├── kube-controller-manager.yaml
├── kube-scheduler.yaml
└── etcd.yaml

实际启动关系是:

systemd → containerd + kubelet
└→ 扫描 staticPodPath → 通过 CRI 创建静态 Pod

kubeadm 负责相应阶段的配置生成和维护操作,常驻运行者是 kubelet。API 中看到的是静态 Pod 的镜像对象,不能把删除镜像对象当成重载磁盘清单的方法。kubeadm 1.35 实现细节

活动目录只放有效清单,回退副本放到目录外;把备份也留在 kubelet 扫描目录里,可能让它被当成另一份配置读取。

CoreDNS 和 kube-proxy 又不同。它们的 Deployment/DaemonSet、ConfigMap 等运行对象保存在 API/etcd 中,不是每台机器上的静态 Pod YAML。仓库保留的是管理配置、补丁、Corefile 和配置基线,不能拿一份手工导出的 YAML 去和原生 addon 长期同时管理同一个对象。

B4:让证书、入口和新节点真正接上#

标准路径不等于换一套身份#

九个控制面 Pod 跑起来之后,仍然需要把管理关系补齐:标准 PKI 和 kubeconfig 路径、kubelet 配置与参数文件、kubeadm-config、kubelet-config、cluster-info,以及 bootstrap、CSR 和 RBAC。

这次保留原 CA 和 ServiceAccount 签名身份,对齐证书用途和引用;为 VIP 另行生成包含必要 SAN 的 API serving 证书。没有为了看起来像新集群而更换根 CA,也没有批量重签全部旧叶子证书。

这里有个容易误解的地方:kubeadm certs renew 以既有证书的属性作为续期依据。只在配置里加一个 SAN,再运行 renew,并不能按自己的想象扩展那张旧证书。证书续期的属性来源

kubelet 客户端则实际完成了旧身份引导、CSR 轮换和原生 finalize;Node UID 保持。现有节点的 serving pair 由本机补丁保留,新节点才使用相应的 serving bootstrap 流程。不能把一台旧节点的证书路径上传成所有新节点的通用配置。

统一 VIP 用 Keepalived 加现有 kube-lb#

原来的 kube-lb 只监听节点回环地址,新机器无法把另一个节点的 localhost 当作加入入口。于是增加统一 VIP,由宿主机上的 Keepalived 和已有 nginx kube-lb 承担,不依赖 Kubernetes 先启动。

原三个节点的本地 listener 保留,API 继续按各自节点地址绑定,避免端口冲突。VIP 是新增入口,不替换 Node InternalIP,也没有把所有组件的 kubeconfig 一律改成同一个地址。

最终日常管理员配置使用 VIP,各节点另留本机直连救援配置;controller-manager、scheduler 的本地访问方式和 kubelet 的客户端身份按各自用途处理。

05:06 生产 VIP 上线后,做了仅停止一台 Keepalived 的漂移试验,恢复后不抢占;Mac 在有限采样窗口里的 129 次 CA 验证请求全部成功。这个结果证明了所测的 daemon 停止场景,不能推导成主机断电或网络分区时也有同样的恢复时间。

新 worker 把旧 hosts 依赖暴露出来了#

B4 的一次性实验 worker 加入后,曾经仍是 NotReady。原因链很明确:

实验 kube-proxy 使用旧域名,新机无法解析
→ 读不到 Service / EndpointSlice
→ IPVS 没有建立 Service 转发表
→ Calico 初始化访问 Kubernetes Service 失败

修正实验 kube-proxy 的 API 入口后,转发表和 CNI 初始化恢复。接着又发现一台旧 API 优先使用新节点的 Hostname,但控制面并不能解析这个名称;调整为优先 InternalIP 后,API→kubelet 才通过。

serving CSR 也没有批量批准。逐项核对新 Node 身份、CN/O、IP/DNS SAN、signer、算法、usages 和签名后,只批准对应请求。客户端 bootstrap 成功和 serving TLS 通过,是两个不同的检查。

这些都是内层实验集群的结果。此时在生产 context 执行 kubectl get nodes,仍然只会看到三台物理机。

C:etcd 保留数据和身份,逐成员换运行方式#

这是最不能靠“启动了一个新容器”判断成功的一步。Calico 也使用原 etcd,删除原库或换一套空成员,影响会越过 Kubernetes 控制面本身。

生产依次处理当时的非 leader,必要时先转移领导权,实际顺序为 node-a → node-c → node-b。每次先确认旧进程彻底退出,再让新静态 Pod 打开原数据目录;不对原生产成员做 remove/add,不恢复旧快照,不创建空库。

清单还保留了一个数据目录保护:

# 已存在成员的数据目录保护示意,不是完整 Pod 清单。
volumes:
- name: etcd-data
hostPath:
path: /var/lib/etcd
type: Directory

目录必须已经存在,避免路径写错后自动创建空目录,把一次接管变成意外初始化。它只是一个防误操作检查,不能替代对目录内容、成员参数和恢复材料的核验。

每个成员完成后检查三端点健康、cluster/member ID、alarm、复制状态,以及同 revision 的 hash;同时实际创建和删除新的 Calico/Multus Pod。

三成员完成后,才逐台把 API 从原外部三端点切到本机 https://127.0.0.1:2379。旧回环 2379 原本是 HTTP,本次改为 TLS;指标/健康端点另放在回环 2381。Calico 仍使用原节点 TLS 端点,数据后端和地址合同保持。

实验中曾因某台 API 单独依赖即将停下的本机 etcd,加上 kubelet 又依赖这台 API,导致镜像 Pod 等待失败。修正复演起点,先保留外部三端点,并通过健康 peer 观察镜像对象与实际 CRI 容器,才继续。文件已落地、容器已启动、API 中镜像对象已收敛,是需要分别确认的状态。

10:14 左右,生产三个 API 全部使用本机 TLS etcd,管理元数据也改为 local/stacked。原生产 cluster ID 和三个 member ID 保持。

D:kube-proxy 逐节点交给 DaemonSet#

kube-proxy 没有直接从三个旧进程跳到三个新 Pod。

先建立原生 RBAC、ServiceAccount、ConfigMap 和 DaemonSet,用临时 nodeSelector 让期望实例数为零;随后一台一台停止旧进程,放行该节点上的新 Pod,验证单实例、IPVS、DNS、Service 和 NodePort,再处理下一台。

旧 IPVS、conntrack、CIDR、监听方式都保留,没有清空流量规则。共享配置中的节点名也不能写死为某一台,必须由每个 Pod 的节点信息提供。

全部节点通过后,再去掉临时限制,恢复最终策略并执行原生 addon phase,确认它能维护当前资源,而不会意外再次替换全部 Pod。10:35 完成配置与验收,旧三个 proxy unit 归档并 mask。

E:CoreDNS 最棘手的是 Service 地址#

这次保留 CoreDNS 1.12.4、Corefile、资源配置和原来的一个副本。NodeLocal 也保留原监听地址;变化是把 CoreDNS 的管理入口交给原生 addon。

问题在于,旧 kube-dns 使用 Service 网段中的 .2 地址,而本次 kubeadm 根据该网段计算出的 DNS 地址是 .10。原生实现会把这个地址写入 Service 清单,不能指望修改一个配置文件就把既有 Service 的不可变 ClusterIP 原地改掉。1.35.9 CoreDNS addon 实现

实验完成正向、回退和再次正向验证后,生产采用以下过渡:

  1. 临时把 CoreDNS 扩到三个副本,确认都可服务。
  2. 利用已有的稳定 upstream Service,逐节点把 NodeLocal 内部域的转发切过去。
  3. 在明确限定的 DNS Service 变更中,释放旧 kube-dns 地址,用 kube-dns-legacy 承接旧 .2;让原生 kube-dns 使用 .10。
  4. 逐节点将 NodeLocal 内部域上游切到新 kube-dns,等待 ConfigMap 投影生效后才重载。
  5. 验证旧地址、新地址、中间上游和 NodeLocal 的 UDP/TCP 查询,再恢复原来的一个 CoreDNS 副本。

这是一项明确批准的 Service 身份例外,不授权删除其他业务 Service、Namespace 或 PVC。CoreDNS Deployment 和 NodeLocal DaemonSet 的原 UID 保持,旧 .2 兼容入口也继续保留。

还有两个工具细节值得留下:本次 addon coredns phase 不接受 --patches CLI 参数,补丁目录通过 InitConfiguration.patches.directory 传入;带补丁的 --print-manifest 输出曾出现分隔符连在 apiVersion 上的情况,只规范化校验副本,再做 schema/server dry-run,没有跳过校验去“强行成功”。

11:59 恢复原副本数,12:01 完成最终配置上传。约十分钟的 DNS 采样中,三个节点的 NodeLocal 分别出现 4、6、2 个失败样本,旧 .2 和稳定 upstream 的探测失败为零。逐节点重载确实带来过短暂查询失败,不能包装成零 DNS 中断。

保留一个 CoreDNS 副本也是原有运行约束的延续。三个本地缓存不能替代多个 CoreDNS 后端,更不能据此宣称 DNS 已经没有单点。

F:必须拿一台新机器证明以后还能维护#

后面的验证没有停在“现有三台都是 Ready”。先让一次性实验 worker 从新系统加入,再删除这台一次性 VM/磁盘,观察它成为 NotReady,确认剩余三台和测试 Service 可用。之后用新的根盘和身份重建同名节点,执行原生 control-plane join,验证新增 etcd 成员进入 voter。

这里的替换场景是失去实验 worker 后,以新系统重建并作为新控制面加入。它没有验证丢失生产 Ceph 节点后的全部数据恢复,也不是把原生产 etcd 成员移除再加回。

这一轮又撞到了旧域名:生成 join 命令所用的实验 admin.conf 仍指向只在旧节点 hosts 中有效的名称。新节点能访问 VIP,却无法按生成的命令继续 discovery。暂停后,仅修正三台实验标准管理员配置的 server,再重新生成短期材料,原生 join 才成功。

controlPlaneEndpoint、实际管理员 kubeconfig、公开发现配置和组件客户端必须一起核对。 原生 cluster-info 的生成也会使用传入管理员配置里的集群信息,单改一个 ConfigMap 不会自动修正所有旧文件。1.35.9 cluster-info 实现

新增 control-plane 还要准备独立配置文件、镜像和主机依赖。默认 control-plane taint 与外部负载均衡排除标签,也要按节点实际角色处理;本环境是混合角色,不能不经判断就把默认标签套到所有生产节点上。

最终实验新控制面完成了:

  • 四 etcd 端点健康、原三个成员身份保持,同 revision hash 一致。
  • 包含 VIP 和新增直接 API 在内的 20 条 API→kubelet 路径。
  • 各 16 条 Pod HTTP、DNS 和 NodePort 路径,以及日志和 exec。
  • 原生证书到期检查、同版本升级候选检查。
  • 使用 containerd 2.3.6/runc 1.4.3 的 guest 重启,四静态 Pod、成员身份和访问恢复。

另在原实验节点实际做过 certs renew all,验证 CA/SA 身份保持,重载后 API 真正提供了新证书,续期后的管理员证书可以认证。新 guest 的重启验收从请求到检查通过约两分半,包含轮询与验证时间,不作为生产 RTO。

临时成员最后正常移除,仍只保留原三个实验成员。两次一次性 VM/磁盘、对应 Node、CSR、引导 Secret、证书共享材料和临时防火墙放行均清理;原三台实验 VM 保留。

验收脚本也要接受检查#

有一次实验候选清单与活动清单完全相同,kubelet 没有重启,脚本却一直等待新的容器 ID。另一次重启验证引用了 C 阶段结束时已主动删除的临时 etcd 键,导致明明已经恢复的集群仍被报超时。

两次都先核对实际状态,再修正检查:要验证重载时,明确在实验清单上制造一次受控变更;只验证终态时,就不要强求无变化配置产生新实例。需要持续存在的验证标记,也不能和阶段结束即清理的标记混用。

同样,工作负载数量或镜像变化不能一概忽略。维护期间有已确认的应用持续发布,也有 HPA 自动伸缩。接受这些变化的条件是明确对象、原 UID 和卷身份保持、新 generation 已被观察、全部新副本真正就绪;其他业务继续按原约束比较。

后续命令能运行,还要清楚它做了什么#

完成管理衔接后,生产和新的实验控制面都完成了证书与同版本候选检查。下面是生产侧的命令示例:

Terminal window
kubeadm certs check-expiration --config=/etc/kubernetes/kubeadm/kubeadm.yaml
kubeadm upgrade plan v1.35.9 --etcd-upgrade=false
kubeadm upgrade apply v1.35.9 --dry-run --etcd-upgrade=false \
--certificate-renewal=false --patches=/etc/kubernetes/kubeadm/patches --yes

这是本次既定版本和配置下的检查示例,执行后还比较了活动清单摘要。它不表示已经做过真实版本升级,也不能证明下一版本无需准备便可使用。

upgrade plan 还不是严格纯只读:该版本会创建短期健康检查 Job 来验证调度和启动。本次在验收范围内执行并清理,不能在一个仅允许只读的窗口中无提示地运行。1.35.9 升级健康检查实现

后续维护保存了每台节点的 kubeadm 配置和完整补丁:API 节点绑定、旧 kubelet serving 路径、etcd 既有目录保护、CoreDNS 资源/副本。节点专属配置不能照抄给新节点,共享 kubelet 配置与本机差异也需要分别维护。kubelet 配置集成

全栈验收具体覆盖了什么#

最后用生产上的专用资源完成验证,清理后再检查原基线:

范围本轮实际证据
核心组件三节点共 12 个静态 Pod;五类旧 unit 共 15 个 masked
API三直接入口、九条 API→kubelet、认证/聚合 API、跨 API 对象读写及 VIP TLS
网络Calico/Multus ADD 和 DEL、跨节点访问、DNS、Service、NodePort,六条 DF 大包路径
存储四类专用 1 GiB 卷跨三节点共 12 次重挂,旧标记、fsync 和读回通过
数据库7 套 PostgreSQL、5 套 MariaDB、9 套 Redis 的原生查询与复制关系
虚机原 13 台 VMI UID/MAC 保持、十个原可访问 SSH 端点;其中 10 台业务、3 台保留的实验 VM
应用入口35 条 Ingress 根路径探测无连接错误或 5xx;GitLab/Harbor 健康、监控无 down target
S3专用新对象 put/get/哈希比对/delete,确认测试对象和本地临时文件清理
恢复材料新 etcd 快照、40 文件数据库备份和节点配置在另一节点核对副本

API 根路径的 404、KAS 普通 HTTP 请求的 426 记录为实际响应,没有冒充已登录的业务操作。VPN 只验证了入口响应,没有建立新的客户端隧道;备份做了原生格式和完整性检查,没有全部导入隔离数据库做恢复。

13:27 清理生产专用 namespace、测试 Pod、Service 和四个测试 PV/PVC 后,原 94 对业务卷、13 台 VMI 仍符合身份检查,190 个 Deployment/StatefulSet/DaemonSet 通过就绪检查。这个数量比接管前的 189 多一个,是新增原生 kube-proxy DaemonSet。

Ceph 当时为 HEALTH_OK、18 OSD up/in、745 个 PG 处于 clean 状态(含正常 scrub)。13:28 的结束快照再次完成双份验证。生产物理机和业务 VM 在本次接管中没有重启;实验 guest 的重启不能替代生产整机断电演练。

最后才真正清掉 kubeasz#

13:20,先停止旧 kubeasz 管理容器、取消自动重启并禁用旧脚本入口。复核 API、网络、卷、应用和 S3 后,才在傍晚执行进一步清理。

清理前查了进程 exe/cwd/fd/mmap、符号链接、systemd/cron、Pod hostPath,以及 registry 的真实数据挂载。旧源码与双份归档做内容比较,历史 etcd 文件单独保留,再把工具目录临时改名;确认运行状态和镜像仓库未受影响后,才删除目录。

最终删除两个旧管理容器、三个 kubeasz 管理镜像、源码/下载缓存和旧下载入口,根文件系统空闲增加约 5.16 GiB。这是当时包含并发系统活动的观测差值,不是每个镜像标称体积的简单相加。

以下内容保留下来:

  • /opt/kube/bin 中仍在使用的程序,以及现用 PKI、kubeconfig 和数据目录。
  • 防止旧组件被重新拉起的 systemd mask。
  • Docker、本地 registry、其独立数据目录和在用组件镜像。
  • 已验证的恢复档案和迁移历史。

shell 里“generated by kubeasz”后面的 PATH 与补全代码仍然有用,因此只清掉旧生成注释和指向旧容器的快捷命令。看到旧工具名字就全盘删除,会误伤接管后继续使用的基础设施。

18:10 再次复核,集群仍通过原门禁;清理前后的关键服务 PID、组件容器 ID、活动二进制与清单摘要、凭据文件元数据保持,没有为清理而重启服务。

留下的终态和边界#

这次最终交付的是一套能由 kubeadm 原生配置和阶段命令维护核心组件的存量集群,以及仍然独立管理的网络、存储和业务层。日常入口使用 VIP,直连救援入口保留,节点差异有持久补丁,旧管理器不再参与日常部署。

以后增加节点,仍要先准备 OS、运行时、CNI 二进制、网络、镜像和预留地址。新增控制面涉及证书共享、etcd 成员及负载均衡后端;新增 Ceph 承载节点还要另做存储规划。一次 join 成功不会替代这些工作。

本轮没有进行生产物理机断电、实际 Kubernetes 版本升级、完整对象灾备恢复或 24 小时稳定性观察。尤其是独立对象备份仍然缺位,PV 身份保持与 S3 测试成功都不能证明全部对象零丢失。

真正留给下一次维护使用的,是经过验证的配置来源、单节点回退材料、明确的组件归属,以及能发现错误的验收流程。旧部署工具可以删除,这些材料需要继续维护。