当前位置: 首页 > news >正文

一行命令毁掉整个 Kubernetes 集群,然后我花了一天时间把它找回来

本文是对 Impromptu disaster recovery 的整理与翻译背景2025 年 3 月 18 日作者 Amos 本来只是想找一个自托管的看板工具顺便把 Kubernetes 配置文件的缩进从 2 个空格改成 4 个空格。结果这一天变成了一场长达数小时的灾难恢复演练。这不是一篇无责任事后分析报告blameless postmortem。文章里有锅有甩锅有反思有干货也有对当下 LLM 辅助编程现状的冷静审视。自建基础设施我的服务器是怎么跑起来的Amos 是个有多年自托管经验的老玩家。从最早的 PHP 共享主机到便宜的 VPS再到今天在 Hetzner 上跑的一套 k3s 集群。集群的主节点是德国 Hetzner 的一台专用服务器边缘节点分布在全球各地也全在 Hetzner他们现在有东南亚节点了。这个集群上跑着他博客的自制 CDN、一个私有的 Forgejo 实例类似 GitHub 的代码托管平台以及其他内部服务。当天他想部署一个叫 Taiga 的项目管理工具在 ArtifactHub 上找到了几个非官方的 Helm Chart于是打开了自己的基础设施仓库开始写部署 manifest。他是怎么往 k3s 上部署东西的最直接的方式是用kubectl apply但 Amos 不喜欢这种方式因为磁盘上的 YAML 文件和集群里实际存在的资源之间容易出现状态漂移——你可能忘了 apply 某个文件也可能 apply 顺序不对导致资源被覆盖。他没有去用 Spinnaker 这种复杂的 CD 工具而是选择了一个更简单但有代价的方案rsync。他写了一个deploy-manifests脚本#!/bin/bash -euxsshrootbrattouch /var/lib/rancher/k3s/server/manifests/traefik.yaml.skiprsync-av--delete./manifests/ rootbrat:/var/lib/rancher/k3s/server/manifests/custom/sshrootbratjournalctl -fxu k3s逻辑很简单把本地manifests/目录 rsync 到服务器上的特定目录k3s 的 reconciler 会监听这个目录的变化一旦有文件写入就去比对现有资源并执行增删改操作。为什么不从 CI 里跑这个 rsync因为 Forgejo 本身就是跑在 k3s 上的用 Forgejo Actions 会有鸡和蛋的循环依赖问题用 GitHub Actions 又要把生产服务器的私钥交给它Amos 不放心。所以就这样手动跑着用了。k3s reconciler比你想象的更简单也更危险k3s 的 reconciler 有一个关键特性启动时读取目录下所有.yaml文件对比整个资源集合然后执行必要的 CRUD 操作。它没有先预览再确认的步骤不像 OpenTofu 有 plan也没有 dry-run 选项不像 Ansible出错了也不会主动报错到终端——你只能盯着journalctl的日志。唯一比直接用kubectl apply差的地方kubectl apply会阻塞直到操作完成并把错误打印出来而 reconciler 是异步的你只能靠日志来感知。# journalctl -u k3s | grep -F custom/ | ccze -A | tail -4用久了之后Amos 已经能预判 reconciler 的行为不会每次都去查日志。只要 30 秒内服务没起来才会 SSH 进去看一眼。直到 2025 年 3 月 18 日事情彻底失控。引发灾难的命令格式化的代价Amos 最近把代码编辑器改成了用 4 个空格缩进所有文件包括 YAML。打开旧的 manifest 发现还是 2 个空格于是他向编辑器里内置的 AI 助手Zed 编辑器的Claude 3.5 Sonnet Fast Edit模式请教了一条批量重格式化的命令。AI 给出了这条命令yq-i-P.manifests/**/*.yaml看起来很合理-i原地修改-P美化输出.表示处理整个文档。Amos 想反正这是个 git 仓库出了问题还能 revert就直接跑了。运行之后他查看了git statusChanges not staged for commit: modified: manifests/cert-manager/300-cert-manager-chart.yaml只有一个文件被修改了他有点失望觉得命令只处理了第一个文件然后……就把它 commit 了并跑了./deploy-manifests。同时他在第二块屏幕上挂着 k3s 的日志。日志开始疯狂滚动。他发现 k3s 在不断尝试创建……已经存在的资源的重复副本仔细一看300-cert-manager-chart.yaml被频繁提到。等等这个文件为什么有 1.75MByq 的惊喜多文件传入 -i 的实际行为这里是事故的根本原因。yq-i-P.manifests/**/*.yaml这条命令并不是对每个文件分别格式化。当yq收到多个文件参数时它会把所有文件的内容合并成一个 YAML 流然后因为有-i标志把合并后的结果写回第一个文件。其他文件则完全不动。所以300-cert-manager-chart.yaml变成了 1.75MB——它把所有 manifest 文件的内容都吃进去了。而git status之所以只显示这一个文件被修改是因为其他文件确实没有变化它们本来的内容没被修改只是第一个文件被撑爆了。如果 Amos 当时看的不是git status而是git diff哪怕扫一眼也会立刻发现异常。Cool Bear 的责任清单非无责任复盘版Amos 在运行命令之前没有读 yq 的 man pageAmos 只看了git status没看git diffAmos 当天压力很大睡眠不足本来就不应该去动基础设施LLM 们的表现集体翻车实录Amos 后来把这条命令的问题拿去问了几个主流 LLM看看他们是否能主动发现这个陷阱。结果是没有一个模型在第一次能主动指出问题。GPT-4oGPT-4o 给了一个教科书般的解析把每个参数的含义都说得头头是道但对多文件会被合并进第一个文件这个关键行为只字未提。Amos 主动告知这个行为之后GPT-4o 认可了但给出的修复方案是……把manifests/**/*.yaml改成manifests/**/*去掉了.yaml后缀这和原命令有完全一样的问题。再次指出之后GPT-4o 才给出了两个真正有效的解法# 方案一用 find xargsfindmanifests-typef-name*.yaml-print0|xargs-0-I{}yq-i-P.{}# 方案二用 for 循环forfileinmanifests/**/*.yaml;doyq-i-P.$filedoneClaude 3.7 Sonnet同样第一次的解析完全没提到多文件合并的问题。被纠正后Claude 给出的方案更简洁findmanifests-name*.yaml-execyq-i-P.{}\;Amos 评价说Claude 的解法比 GPT-4o 的更好不需要绕 xargs也不用担心 IFS 的问题。DeepSeek R1同样先翻车被纠正后承认其实知道这个行为但没在初次回答里提到。Mistral Le Chat引用了多个来源yq 官方文档、第三方博客但同样没能在首次回答时发现问题。结论关于 LLM 的公平判断所有模型都在被纠正后承认了错误并感谢了纠正。但这并不意味着任何事情得到了改变——这些模型不会从对话中学习也许只有当这篇文章进了下一代的训练集未来的模型才会知道 yq 的这个行为。Amos 说他对 LLM 的判断是偏袒的怪 LLM 有点甩锅的嫌疑。毕竟是他自己按下了回车键。他引用了一张 IBM 1979 年的内部培训图“计算机永远不应该被追责因此计算机永远不应该做出管理决策。”话虽如此他也认为yq对多文件的处理方式是一个糟糕的 API 设计。这种静默合并行为极具破坏性而且没有任何警告。只是此时改动会是 breaking change所以可能就这样了。雪上加霜删掉重复资源触发大规模删除发现问题之后Amos 想到的第一个操作是删掉那个 1.75MB 的合并文件里多余的重复资源。然后 k3s reconciler 看到这个文件之后发现大量资源消失了于是立刻开始删除它们。而且删得非常快。Deployment 被删 → Pod 被删 → 容器停止证书和 Secret 被删 → Traefik 开始用默认证书Service 和 IngressRoute 被删 → Traefik 返回 404最终Traefik 本身也没了整个 Kubernetes 集群的资源几乎被清空。Amos 本想研究一下怎么让 k3s 停止删除操作但很快决定算了我有备份有些服务本来就快废弃了干脆趁此机会做一次提前的春季大扫除。决定抹掉服务器从零开始。灾难恢复全程记录第一步先用 rsync 把数据捞出来在抹机器之前Amos 先把/var/lib/rancher/k3s整个目录 rsync 出来作为保底rsync-avz--progress\rootbrat:/var/lib/rancher/k3s\./var-lib-rancher-k3s很快发现两个问题agent/containerd下藏了 60 万个小文件rsync 是单线程的带宽足够时默认压缩反而成了瓶颈。他切换到了排除 containerd 目录的版本很快就搞定了尽管有 30GB。Amos 说非常庆幸这件事发生在家里家里有 2.5Gbps 的宽带。第二步通过 Hetzner Robot 重装 Debian 12Hetzner 提供了一个叫Hetzner Robot的管理面板可以进入救援模式。重启后 SSH 进去运行installimage然后就会看到一个 ncurses 的 TUI 安装界面前提是你用的不是 Ghostty 终端——Ghostty 用的 TERM 值是xterm-ghostty很多地方没有对应的 terminfo会直接跳进 nano。解决 Ghostty 的 terminfo 问题infocmp-x|sshrootbrat -- tic-x-installimage很小但会帮你配置好 RAID 1Amos 表示满意。第三步搭建新的 k3s 集群Amos 用 Ansible 管理节点的配置他写了一套 playbook./ansible-playbook playbooks/k3s.yaml-lbrat他的 Ansible inventory 是通过一个 Rust 脚本从 OpenTofuTerraform 的开源 fork的 state 文件自动生成的用了 cargo 的实验性-Zscript特性类似于在 Rust 文件里直接写 Cargo.toml 依赖他非常喜欢这个特性。顺便提一句Ansible 有整整22 个变量优先级层级这是个令人叹为观止的设计。第四步CA 证书哈希不匹配节点连接主节点时报错token CA hash does not match the Cluster CA certificate hash: de13... ! d262...k3s 集群有自己的证书颁发机构CA重装后新的 CA 密钥对和旧的不同但 Ansible 变量里存的还是旧的哈希值而且这个旧值通过git-crypt加密后提交在了 infra 仓库里。更麻烦的是k3s-leader 的 Ansible 角色还会把旧的node-token文件写回服务器所以直接读文件也没用。node-token的结构是K10token-ca-hash::server:random-token只需要把K10和::之间的 CA 哈希替换成新的就行了。Amos 自己通过比对日志里的哈希值搞定了这个问题GPT-4o 其实也解释了这个结构只是他后来才注意到。最终边缘节点成功连上了主节点。恢复关键服务Traefik v3k3s 自带 Traefik v2但 Amos 要用非实验性的 HTTP/3 支持所以用的是自己部署的 Traefik v3。首先在部署脚本里禁掉 k3s 内置的 Traefiktouch/var/lib/rancher/k3s/server/manifests/traefik.yaml.skip然后通过 HelmChart 资源部署 v3---apiVersion:helm.cattle.io/v1kind:HelmChartmetadata:name:traefiknamespace:traefikspec:repo:https://traefik.github.io/chartschart:traefikversion:34.4.1valuesContent:|image: repository: traefik tag: v3.3.4 deployment: kind: DaemonSet logs: general: level: INFO hostNetwork: truecert-manager用于自动申请 Let’s Encrypt 证书同样是一个 HelmChart 资源---apiVersion:helm.cattle.io/v1kind:HelmChartmetadata:name:cert-managernamespace:kube-systemspec:repo:https://charts.jetstack.iochart:cert-managerversion:1.17.1valuesContent:|crds: enabled: trueMinio专用服务器有 2×512GB SSDAmos 用 Minio 做本地对象存储物尽其用。总结与反思这次事故是多个因素叠加的结果工具层面yq在多文件传入-i时静默合并到第一个文件这是个反直觉的 API 设计没有任何警告k3s reconciler 没有 dry-run 或 plan 功能操作是不可逆的且异步执行操作层面没有在运行命令前读 man page只看了git status而没看git diff疲劳状态下操作生产基础设施关于 LLMAI 给出的命令看起来很合理但有一个非显而易见的致命副作用所有被测试的主流模型GPT-4o、Claude 3.7 Sonnet、DeepSeek R1、Mistral Le Chat在首次回答时都没能主动指出这个问题但最终责任在人计算机不应该被追责所以不能让计算机做管理决策在用 LLM 生成的命令操作生产环境之前务必自己理解每一个参数的含义正确的 yq 批量格式化写法三选一# 方法一find exec推荐最简洁findmanifests-name*.yaml-execyq-i-P.{}\;# 方法二find xargsfindmanifests-typef-name*.yaml-print0|xargs-0-I{}yq-i-P.{}# 方法三shell for 循环forfileinmanifests/**/*.yaml;doyq-i-P.$filedone这次灾难最终花了 Amos 一整天时间来恢复但他的服务器在文章发布时已经完全恢复正常运行。他的博客、CDN、Forgejo 实例全部重新上线。有时候备份和快速恢复能力比避免事故更重要——当然两者都有最好。
http://www.rkmt.cn/news/1414899.html

相关文章:

  • 如是心商业模式开发概述
  • Oracle、海量数据库、达梦数据库 技术对比迁移避坑指南
  • 新手必看:第一把吉他到底该花多少钱?
  • 隐形车衣到底能不能保车漆?实测结果告诉你真相
  • 2026年AI写作辅助软件实测精选:5款神器从大纲到答辩全链路通关攻略
  • 5分钟掌握QuickRecorder:macOS屏幕录制的终极免费解决方案
  • CISSP备考避坑指南:从零到持证,我的150小时高效复习路线图(含独家笔记模板)
  • ChemCrow:实用高效的化学AI助手完整使用教程
  • 2026年智能语音机器人厂商推荐:全场景适配技术深度拆解 - 品牌2025
  • 终极Gaggiuino改造指南:3步打造智能咖啡机
  • RPG Maker解密神器:一键提取加密游戏资源,开启二次创作新篇章
  • 基于BJT的AB类音频功放设计与制作:从原理到实践
  • 终极指南:使用applera1n工具解锁iOS 15-16设备激活锁的完整教程
  • DeepSeek API 5M 免费 token 实战教程 + TokenMix.ai 无缝切换
  • 终极Windows系统优化指南:用Dism++彻底解决电脑卡顿问题
  • OCAuxiliaryTools终极指南:跨平台OpenCore配置工具深度解析
  • 2026年5月27日江诗丹顿官方保养价目表|避坑指南+日常养护全攻略 - 资讯速览
  • 基于ESP32与超声波传感器的自动道闸系统设计与实现
  • 深度解析G-Helper:华硕笔记本开源性能控制工具完全指南
  • 3个关键步骤彻底解决Switch手柄问题:Joy-Con Toolkit完全指南
  • Code Coverage系列(三)gcov 是什么?做什么?两个参数?检测原理?gcno文件内容?gcda文件内容?
  • 2026北京白银定制加工技术推荐:北京黄金0元体验检测纯度/北京黄金个性化定制服务/避坑要点与靠谱选择 - 优质品牌商家
  • MP3音频太大!怎么压缩,三种白嫖方式
  • WorkBuddy 好用的十个 Skills,让你的 AI 助手效率翻倍
  • Veo 2输出模糊?5步精准定位编码链路瓶颈:从帧率抖动、量化矩阵到光流补偿全诊断流程
  • 基于树莓派Pico W的蓝牙陀螺仪遥控车:从硬件到软件的完整实现
  • 微软封号、拒付赏金,被激怒的研究员把零日漏洞丢给了全世界
  • 哪个开源商城系统更适合二次开发?2026年很多企业开始重视“长期维护成本”——很多系统前期开发很快,但真正决定企业未来成本的,其实是“后期还能不能继续改”
  • 百度网盘解析工具终极指南:如何免费突破下载限速
  • 双轴晶体中的锥形折射