diff --git a/content/posts/config-driven-ops.md b/content/posts/config-driven-ops.md new file mode 100644 index 0000000..d8d6a88 --- /dev/null +++ b/content/posts/config-driven-ops.md @@ -0,0 +1,133 @@ +--- +title: "配置化运维:让变化不再改代码" +description: "一天的真实记录:派单路由要加城市、安全防护要封网段、插件要更新配置——三件看起来无关的事,最后都收敛到同一个答案:把变化变成配置。" +date: 2026-08-03T19:20:00+08:00 +draft: false +tags: + - 运维 + - 自动化 + - 配置化 + - 架构 +--- + +## 背景 + +这是一个普通的工作日,却让我对"运维自动化"有了新的理解。回顾这一天做的事,三件看似无关的任务,最后都收敛到了同一个答案:**把变化变成配置,而不是代码**。 + +## 一、早上:派单路由要加城市 + +业务提需求:"给 4 个城市加专属通知群。" + +放在半年前,这意味着改代码:找到派单函数,加 if-else,测试,上线。但今天,这套逻辑已经重构为**配置驱动**: + +```json +{ + "city_dingtalk_groups": { + "重庆": {"token": "...", "at_mobiles": ["..."]}, + "昆山": {"token": "...", "at_mobiles": ["..."]} + } +} +``` + +加一个城市 = 配置文件加几行。**零代码扩展**。 + +但这背后藏着一个真实的事故:早上一开始,代码里有个隐患——通知块的 if 条件**顶格**(不区分租约类型),而变量只在某个分支里定义。短租工单派单成功 → 进入通知块 → 变量未定义 → **NameError 崩溃**。 + +这个 bug 教会我两件事: + +1. **顶格块引用分支内局部变量 = 高危模式**。条件与变量定义必须同作用域,这是代码审查的检查点。 +2. **配置化的前提是逻辑先稳**。配置只是把"变化"外置,如果底层逻辑有作用域地雷,配置化只会让地雷更容易被踩。 + +## 二、上午:巡检工单找不到维修站 + +定时任务每小时的巡检建单持续报错:某公司的地址在四川宜宾(非核心城市),没有维修站可匹配。 + +排查发现:巡检单没有 SN、租约类型未知,走不进"长租"分支的兜底逻辑,四条候选路径全部失败,返回空。 + +修复方案不是加一条 if-else,而是**在函数末尾加第 5 条兜底路径**: + +``` +① 核心城市站匹配 → ② 长租默认站 → ③ 城市匹配 → ④ SN历史 → ⑤ 全局兜底 +``` + +非长租工单(巡检等)在前 4 条全失败后,兜底到总仓。**短租不兜底长租站**(业务约束),长租逻辑完全不变。 + +7 个场景回归全通过。这个修复的本质是:**给"找不到"留一条明确的后路**,而不是让错误在午夜静默发生。 + +## 三、下午:文件服务要支持插件更新配置 + +另一个需求:文件上传 API 要支持"固定文件名",给插件做配置更新用。 + +原设计是安全优先的:存储名 UUID 化(不可预测),每次上传 URL 都变。但插件需要一个**固定 URL** 拉取最新配置。 + +解决:上传接口加一个 `name` 参数。 + +```bash +curl -X POST https://file.example.com/api/upload \ + -H "X-Admin-Key: xxx" \ + -F "file=@plugin-config.json" \ + -F "name=plugin-config.json" +# → URL 永久固定,同名覆盖更新 +``` + +关键在安全设计,三个防线缺一不可: + +| 防线 | 作用 | +|------|------| +| 正则白名单 `[A-Za-z0-9._-]+` | 防路径穿越(`../../etc/passwd`) | +| 扩展名白名单 | 防 `evil.php` 上传 | +| 原子替换(先 .tmp 再 rename) | 防插件读到半写配置 | + +测试 8 项全过:上传、覆盖、下载、路径穿越拒绝、危险扩展名拒绝、无密钥拒绝、UUID 默认模式兼容、特殊字符拒绝。 + +**设计原则**:固定名 = 公开文件,配置文件里绝不放敏感信息;要保护的文件继续用 UUID 模式。 + +## 四、贯穿全天:自动封禁 + 文档同步 + +### auto-ban:让封禁自动化 + +三台服务器每天被 SSH 爆破数百次。fail2ban 实时封单 IP,但攻击者换 IP 就绕过。写了 auto-ban 脚本部署到 cron: + +``` +每小时执行 → 提取爆破/扫描 IP → 转 /24 网段 → 白名单过滤 → ufw 永久封禁 +``` + +首次执行封了 169 个攻击网段。从此**封禁不再需要人**。 + +### 文档技能同步:让"记住"变成制度 + +今天最大的架构决策不是技术,而是流程:把"改动即同步文档"固化为**三层铁律**(行为准则 → 工作流 → 项目开局),以后任何改动都必须同步 SKILL.md/README/CRON 等文档。 + +为什么?因为血的教训:改完代码忘更新文档 → 下次按旧文档操作 → 踩坑 → 再花时间排查。**文档不是可选项,是交付物的一部分**。 + +## 五、方法论沉淀 + +这一天做的事,可以抽象成一个公式: + +``` +运维自动化 = 配置化(变化外置) + 自动化(重复交给机器) + 文档化(经验可传承) +``` + +| 任务 | 变化是什么 | 配置化 | 自动化 | +|------|-----------|:--:|:--:| +| 派单通知加城市 | 城市列表 | ✅ JSON | ✅ 定时任务 | +| 巡检找维修站 | 兜底规则 | ✅ 配置表 | ✅ 定时建单 | +| 插件更新配置 | 文件内容 | ✅ 固定名参数 | ✅ 固定 URL 拉取 | +| 封禁攻击者 | 攻击 IP | ✅ 白名单配置 | ✅ cron 脚本 | + +三个案例的共同点:**人只需要声明"要什么",机器负责"怎么做"**。配置承载业务决策,代码承载执行逻辑,文档承载经验传承——三者解耦,各自演进。 + +## 总结 + +运维自动化的高级形态,不是写更多的脚本,而是**让变化发生在配置层**。 + +- 加一个城市 → 改 JSON,不改代码 +- 封一个网段 → 等 cron 自动执行,不手动操作 +- 更新一个配置 → 上传同名文件,URL 不变 +- 记住一个教训 → 固化进文档铁律,人人遵守 + +当变化不再需要改代码,系统就真正"长出了骨骼"。 + +--- + +*本文已脱敏,不含真实域名、IP、UUID 或凭证。*