事故中反思

总结/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.bakmv /var/lib/kubelet /var/lib/kubelet.bak
  • 创建软连接: ln -s /data900/containerd /var/lib/containerdln -s /data900/kubelet /var/lib/kubelet
  • 重启containerd 和 kubelet 服务

但是, 启动就发现各项应用异常, 排查后发现是原本哪些使用了local-storage 挂载本地磁盘路径的PV, 仍然在, 但磁盘下的数据全清空, 各项服务均初始化了.

并且在部署 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 目录移动失败. 对于实操
  • 基础服务要遵循标准. 最初安装k8s , 亦或者docker 时, 就应该区分数据和软件, 让数据直接放到数据盘, 此处就不会触发磁盘占用超85% 的问题, 更不会出现数据永久丢失的问题 ———— 多年前的子弹正中眉心.
  • 个人/团队对钱的态度. 其实想过用快照, 但当时没有想到过数据会丢失, 想着最多也就是服务启动异常, 只要数据在,怎么都可以恢复. 另一方面就是, 手里并没有一笔来自团队的公款, 供这些日常操作(比如快照这种), 还是有点舍不得花钱. 如果自己在钱这块的态度更随意, 或者团队能允许一些杂项的报销, 那这里实操的时候, 下意识选择会多一些,发生问题后, 也能多次回滚试错, 直到成功.

回过头来, 思考一下, 这次迁移出现问题, 是为什么呢?很难回溯了, 结合AI 问答, 我能理解并接受的理由如下:

  1. /var/lib/kubelet 和 /var/lib/containerd 目录下大量包含软连接,硬链接和挂载点.
  2. rsync 在跨磁盘(系统盘和数据盘, 数据盘挂载在/data下)复制时, 硬链接,挂载点是不能被复制的,且实操时传递给rsync 的参数, 也很少, 不能复制全部数据(元数据).
  3. kubelet 和containerd 重启后,containerd 会触发POD 重建(因为数据缺失), kubelete 在元数据丢失且POD 重建时, 会进入”宁可错删,不可错用” 的机制, 这不同于K8S 教程中关于PV local-storage 不会清理数据的机制.
  4. 数据被强制初始化丢失.

而端口没被占用却一直提示占用, 可能就是因为数据丢失, 导致数据不一致, 记录中显示有80 端口被占用.

那为什么查到的攻略是可以这么做, 而自己踩坑了, 出现元数据丢失呢?

因为, 没有通过 drain 标记节点不可用, 来驱逐POD.

为什么又没有选择标记不可用, 并驱逐POD 呢?

因为一开始想着是单机版K8S, 不应该驱逐. 现在回想起来, 还是应该先驱逐.

我不应该既把它当作一个集群来使用, 又把它当作一个单机版软件来维护, 这是不对的.

那如果驱逐了, 我再迁移数据, 就不会出问题了么?

实际情况: k8s apiserver 这类的控制面板, 当初是通过kubeadm 来安装到宿主机中的单机k8s 的 ———— 他在本地中以静态POD 的形式存在.

驱逐节点时, 这类POD 不会被驱逐, 仍然会运行 ———— 也就是说, 现实情况是, 如果驱逐了, /var/lib/kubelet 目录下仍然有POD的记录, POD 如果有挂载点、文件映射, 该有的软硬链接mount等,也都会有, 迁移之后还是会存在元数据不一致.

那以当前的情况, 如果再给我一次实操的机会, 我该怎么做?

  • 标记节点不可用,驱逐POD

  • 检查是否还有POD 在运行

    这个时候应该是没有, 或者只有几个关于k8s 控制面板的静态 POD 在运行.

  • 通过移除 manifest 中yaml 来移除静态POD(控制面板)

    关于集群的状态, 是存放在etcd中, etcd 在通过kubeadm 部署时有指定路径.POD 结束不会清空磁盘(不会像本次, 因为数据在迁移后前对不上,而触发异常流程,强制清空)

  • 检查containerd 和 k8s 是否有容器运行

    此时是希望能看到, 既没有POD 在k8s中运行,也没有容器在containerd 中运行(不然containerd 在迁移时也会出现类似于跨磁盘移动, 挂载无法保留的问题)————甚至, k8s 的客户端已经无法链接k8s 了, 因为控制面板移除, 这个集群, 或者这个节点已经没有了.

  • 停止kubelet 服务

  • 停止containerd 服务

  • 转移 containerd 和 kubelet 目录

  • 软连接 或者 mount 新路径到原本路径

  • 启动containerd

  • 检查contaienrd 运行的容器

  • 启动kubelet

  • 恢复 manifest 下的文件

  • 尝试检查k8s 中运行的POD

    这里是希望manifest 下的静态POD 能恢复, 即, 能通过kubectl 客户端查询到集群状态.

  • 标记节点可用

  • 等待服务启动