总结/var/lib/containerd 和 /var/lib/kubelet 目录迁移后服务异常, 数据丢失.
迁移数据期间, k8s 重启异常:
- local-storage 类型的PV 全部被清空
- nginx ingress 无法重启
…
导火索是, 需要在现有的k8s 中部署一个MinerU 的服务, 官方提供的镜像打包出来后是40G, 而containerd 和kubelete 数据都在系统盘中, 系统盘总共100G, 在尝试部署期间触发了磁盘盘占用超过85%的警告而终止.
而我们的数据盘有1T容量, 并且余量很富足.
思考后决定: 迁移 /var/lib/containerd 和 /var/lib/kubelet 目录到数据盘.
在查阅资料后,实操步骤是:
- 停止 kukelet 服务:
systemctl stop kubelet - 停止 containerd 服务:
systemctl stop containerd - 复制数据:
rsync -av --progress /var/lib/containerd/ /data900/containerd/和rsync -av --progress /var/lib/kubelet/ /data900/kubelet/ - 修改原文件夹到新路径:
mv /var/lib/containerd /var/lib/containerd.bak和mv /var/lib/kubelet /var/lib/kubelet.bak - 创建软连接:
ln -s /data900/containerd /var/lib/containerd和ln -s /data900/kubelet /var/lib/kubelet - 重启containerd 和 kubelet 服务
但是, 启动就发现各项应用异常, 服务均初始化了,原有的数据都没了.
并且在部署 MinerU 后, 试图通过Ingress 向外发布时, 始终无法更新ingress, 在尝试主动结束 nginx-ingress 对应的pod后, ingress 再也无法启动, 提示80 端口占用————但本机确实端口未占用.
从某种程度上来讲, 这是不幸的, 自己亲自触发了一个顶级事故, 服务异常, 数据永久丢失.
但是呢, 又是幸运的, 和MinerU 相关的任务, 在交代的任务时间点前, 做完了(停掉containerd 和kubelet,用docker run 人工配置启动每个基础服务+应用), 同时这台服务器上没有生产数据.
但, 警钟长鸣, 实际作业中的标准/细节, 都需要改善:
快照. 在对应用软件下层基础设施改动时, 在已经有很多软件运行的前提下, 还是在阿里云上租的云服务器, 都值得用快照临时备份. 这里低估了迁移数据重启带来的风险, 没有想到连PV 挂载的本地路径下的数据都会被清空.
local-storage. k8s 官方其实一直强调, local-storage 仅用于开发、测试环境. 但是我一直在云端的环境中使用local-storage. 从成本的角度来说, 非结构化文件, 放到对象存储中挂载到k8s 中, 更合适, 而且也比阿里云的云盘便宜.
单节点k8s. k8s 推荐的, 能用于生产的部署结构应该是3 + N , 即最少3个节点且为奇数个的k8s 控制节点, 和多个工作节点. 我们一直只有一台云服务器, 但却一直以自建的k8s 来部署服务. 此前选择自建单机k8s 更多是为了学习, 而且当时团队更多是部署一些算法原型, 今年才开始试着往企业方向做一些软件/服务. 但没有想到 4-5 年过去, 单机 的 k8s 逐渐成为了日常的核心, 但它其实不具备高可用, 而且从目前团队的规模, 服务器规模来看, 当初使用 k8s 是不合适的————后续计划的是给团队建议, 对于有稳定要求的应用(有可能发生实际生产数据的应用),使用阿里云的ACS, 测试环境用自建的单机k8s, 或者扩大服务器规模或者准备运维人员.
过分相信AI. 对于这次这种相对紧急的开发+实施任务, 在实操过程中省去了很多原本人去核实的流程. 比如实操步骤是AI 问答 + 查看原文结合, 快速推进的.(任务指标需要在2天内完成开发+实施).AI 是对回答不负责的, 而实际使用AI 的人, 也就是我, 是需要对他的结果做核实的.
异常未及时终止. 在停掉服务后, rsync 复制完文件后, 执行MV 操作时, 期间出现过异常, 导致kubelet 目录移动失败. 回忆起来, 可能自己也混用了mv 命令和sync命令, 甚至AI 给出的 rsync 参数不够正确——因为跨磁盘了.
基础服务要遵循标准. 最初安装k8s , 亦或者docker 时, 就应该区分数据和软件, 让数据直接放到数据盘, 此处就不会触发磁盘占用超85% 的问题, 更不会出现数据永久丢失的问题 ———— 多年前的子弹正中眉心.
回过头来, 思考一下, 这次迁移出现问题, 是为什么呢?很难回溯了, 结合AI 问答, 我能理解并接受的理由如下:
- /var/lib/kubelet 和 /var/lib/containerd 目录下大量包含软连接,硬链接和挂载点, 特别是我没有先驱逐节点, 这类东西格外的多.
- rsync 在跨磁盘(系统盘和数据盘, 数据盘挂载在/data下)复制时, 软连接、硬链接、挂载点是不能完全相同的复制出来的, 且实操时传递给rsync 的参数, 应该是应用在同磁盘的迁移,没有对应实际的场景, 两块磁盘, 导致部分数据丢失 —— 这个时候应该还可以通过对比工具检测文件一致性, 包括文件名、权限、内容等,试着通过重新建立软硬链接或者挂载点,做修复, 但因为节点没有驱逐, 所以这样的差异文件将会非常多, 难以实操.
- 丢失元数据(挂载点和软硬链接这类), 表现出数据丢失,可能是POD 重启发现对应文件目录结构有, 就不会触发重新挂载,导致对应目录读取都是空的。但可能只是挂载点丢失(跨磁盘了),原有数据可能还在磁盘中, 当时应该认真检查数据盘中对应路径下的数据.
- 之前的部分 POD 数据没有正确重回 kubelet 的管理, 导致为他生成的一些网络规则(iptables)残留, 导致新的 ingress 在尝试启动时, kubelet 检测端口冲突 —— 这个时候我应该尝试通过查看 iptables 来尝试修正启动异常, 或者应该让AI 辅助, 尝试查看kubelet 源码中, 启动pod时的端口检测逻辑, 是否存在iptabels的检测.
那, 此处该怎么正确迁移呢?
首先是需要关注关键点:
- sync 涉及到跨磁盘同步
- 这是基于kubeadm 安装的单节点 k8s, api-server 和调度工具, 全部以static pod 的形式存在于manifest 下, 且这个单机集群中还有DaemonSet, 也无法通过节点驱逐, 让这类pod 停止 ——— 也就是说, 肯定会存在无法被驱逐的pod 存在于集群.
那该如何做呢?
设想的,可能的方案有三个(请谨慎参考):
- 第一个,原地迁移
- 服务器快照备份
- 节点驱逐, 留下尽量少的文件, 让迁移后的对比、人工修复,变得可行
- 迁移数据盘中被k8s local-storage 或者hostpath 使用的目录,做备份, 丢失后以重新覆盖, 恢复数据
- 然后结束服务,并检测containerd 和contaienrd 中运行的容器,彻底结束, 再迁移数据。 并通过对比工具, 检查原有目录和新目录的差异,权限,内容,软硬链接,对差异项尝试修复。
- 通过sync 将原有数据迁移到原有磁盘的别的位置, 腾出挂在目录
- 然后数据盘对应目录挂载到原有的位置.
- 重启, 检测k8s 运行状况 —— 节点健康检测,deployment 缩放尝试,ingress pod 杀掉检测是否重启
- 如若没问题, 将挂载配置到开机自动挂载
- 第二个,扩展集群后迁移
- 服务器快照备份
- 尝试临时租用一台低配置linux 服务器, 搭建 k8s 并以控制节点角色加入到原有集群.
- 等待etcd 同步
- 驱逐原节点,此时新节点上可以查看集群状态和处于未调度的deployment
- 停止原节点kubelet 和 contaienrd 相关服务和程序
- 备份数据磁盘中, 关于local-host 和 local storage 部分的文件
- 原节点尝试通过kubeadm reset, 并检查、修复iptabels中的残留
- 数据磁盘中的目录挂载到kubelet 和containerd 的默认数据目录(此时文件夹中为空)
- kubeadm init 重新安装单机版k8s 集群
- 控制节点+worker 的身份加入新服务器中的k8s 集群
- 等待etcd 同步
- 检查集群中 deployment 等资源的调度状态
- 前序正常的话, 检查各项应用和服务, 是否正常
- 如果一切正常, 将挂载配置到开机启动挂载
- 第三个
- 服务器快照
- 利用工具, 备份集群配置数据(各种deployment 等资源)
- 停止kubelet 和 containerd
- 备份local-host 和 local-storage 对应的数据
- 通过kubeadm reset 卸载这个k8s 集群
- 清理残留
- 挂载数据目录到默认目录
- 通过kubeadm init 重新初始化集群
- 通过工具, 导入配置
- 检测集群中pod 调度状态
- 检测各项软件、数据是否正常
- 设置开机自动挂载