多机运维的事件管道与安全边界:从主机自记账到统一通知与审计
机器一多,运维的难点就从"怎么连上去"变成了三件事:机器上发生的事能不能都被看到、该通知的人有没有被通知到、以及出事之后能不能回答"当时谁做了什么"。
这三条对应一个系统的三条主线:事件怎么进来、怎么出去、以及如何留下记录。下面按这三条主线拆开讲,最后给一份可直接抄的排障速查表。
一、事件模型:先把 schema 和类别定死
统一事件 schema,occurred_at 用事件发生时间而非入库时间:
id / source_id / category / severity / kind / title / body / occurred_at
类别用代码内的注册表定义,而不是靠大家手写字符串。好处是新类别改一处常量即可,前端渲染与推送策略自动识别:
| 类别 | 内容 | 推送 |
|---|---|---|
| health | 主机掉线 / 恢复迁变 | 是 |
| security | 爆破尝试 / 锁定 / 防火墙变更 | 是 |
| route | 出口切换 / 探测结果 | 是 |
| lease | 租约到期提醒 | 是 |
| operations | 链路巡检 / 运维动作 | 是 |
| daily_report | 每日汇总 | 是 |
| jobs | 控制台任务 | 仅记录 |
| *(通配默认) | 未归类兜底 | 兜底策略 |
推送内容的排版也有讲究:先给结论再给细节。第一行是状态灯 + 标题(🔴严重 / 🟠错误 / 🟡警告 / 🟢信息,恢复类一律 ✅),第二段起才是紧凑元数据。这样值班的人三秒能抓到重点,不用读完一整屏才知道要不要起床。
二、上行通道:每主机一钥的 HMAC
主机侧不持有任何下游凭据——推送通道(即时通讯机器人、邮件 SMTP)的 token 只在服务端。每台机器发一个 HMAC 密钥,用 SDK 封装签名与重试,调用方几乎无感:
from ops_notify import notify
notify('backup-job', '备份完成', body='...', kind='routine', severity='info')
三个设计要点:
- 服务端只认签名:上行接口校验 per-host HMAC,某台机器的密钥泄露可以单独吊销,不影响其他机器;
- 本地 spool:网络不通时事件先落盘,由定时任务周期性 flush,恢复后补投——做到不丢事件;
- 服务端按
id去重,投递语义是"至少一次"。
三、出口:队列、策略与投递保证
各类事件源 ──▶ notifications 统一队列 ──▶ 通道策略 ──▶ 即时通讯 / 邮件
│
├─ 去重(事件 id)
├─ 静默时段
├─ 指数退避重试(超过上限标记 failed,可手工重投)
└─ 每次投递落库(delivery audit)
这里有一条形而下的教训值得单独标注:队列必须是常驻 worker 内的显式循环,不能靠裸 asyncio task。 我们曾经因为 fire-and-forget 任务随 worker 静默死亡而丢过整批通知——没有 supervisor 兜底的异步任务是不可靠的,症状是"看起来一切正常但就是没消息"。
投递结果用三态表达,全部入库:
sent:已发出blocked:被策略拦截(静默时段、类别策略)failed:可重投
还有一个容易搞反的点:控制值班噪音靠类别策略与静默时段,不要靠"少接事件"。 把事件源砍掉,等于把可观测性也一起砍了——现在不推的那些事件,恰恰是三个月后排查时要翻的记录。
四、运维日志:两层自记账
要回答"谁在任何机器上做了什么",光靠系统日志不够,得做两层自记账:
- agent 侧:每次受限调用(无论成功失败)追加一行 JSON(时间 / agent / 动作 / 结果 / 摘要)到本机日志,控制台查询时随包带回尾部 N 条;
- 服务端:所有管理动作(重启、发布、策略变更、纳管)进入审计表,与控制台事件、通知投递记录合并成统一的系统时间线,支持按时间 / 来源 / 类别检索。
原则是:事件先落账再执行,失败也要记账。 日志面向的是"三个月后来查案的人",写清 who / what / outcome 就够,不必追求堆栈完整。
五、特权收口:非 root 应用 + root 代理协议
Web 应用进程一律以非 root 用户运行,所有特权动作走一个 root 常驻代理(Unix socket,0600/0660 权限隔离):
应用 ──预览请求──▶ 代理生成 diff / 影响面 + 单次令牌
用户确认 ──▶ 应用持令牌请求执行 ──▶ 代理校验令牌(一次性、短时效)
──▶ 执行(前置自动备份)──▶ 审计落库 ──▶ 可回滚动作登记回滚方式
这套协议的收益是结构性的:应用层被攻破也拿不到 root,而且每个危险动作天然获得"预览—确认—审计—回滚"四件套,不需要靠人记得做。
六、入口安全(登录面)
登录是第一道也是最容易被自动化的面,几条实践下来性价比很高:
- 密码存储用 PBKDF2-HMAC-SHA256,迭代次数开到 60 万并加盐;
- 会话用 HttpOnly / SameSite cookie,服务端维护会话表以便随时吊销(改角色、改密码自动失效该用户全部会话);
- 登录加固三件套:
- 首次失败后出现算术挑战(单次有效、短时效),拦掉绝大多数脚本;
- 蜜罐字段(给机器填的隐藏输入框,一旦有值即判定为机器人);
- N 次失败按 IP 锁定,触发即推送一条安全事件;
- CSRF 令牌 + 同源校验;API 响应
no-store;CSP 白名单; - 登录尝试保留最近窗口的记录,安全面板可见,并可一键吊销会话。
七、部署与变更验收
每次上线固定流水线,不要凭记忆:
全量测试(含浏览器级渲染断言) → 构建 → 版本号唯一化
→ 上传(校验和比对)→ 服务重启(确认自愈依赖)→ 双端回归(桌面 + 移动视口)→ 提交变更摘要
对应的检查清单:
- [ ] 测试全绿
- [ ] 静态资源版本号新鲜且唯一
- [ ] 上传后校验和与本地一致
- [ ] 依赖服务重启后健康(包括被连带停止的单元)
- [ ] 手机视口 + 桌面视口各过一遍核心页面
- [ ] 变更摘要进运维日志 / 提交信息
八、排障速查
| 症状 | 先查 | 提示 |
|---|---|---|
| 通知没发出来 | 队列 worker 是否存活 → 投递审计表 | worker 死亡最常见;failed 状态可手工重投 |
| 事件重复 | 去重键 | 上行重试导致,属"至少一次"的正常语义 |
| 面板显示异常但机器正常 | 观察表残留僵尸行 | 不再上报的观察对象会永远冻结在最后状态,需清理 |
| 改了推送策略不生效 | 策略加载时机 | 注册表常量化后需要随代码重新部署 |
| 登录一直要求挑战 | 挑战窗口与失败计数 | 挑战单次有效;成功登录后计数清零 |
小结
一套能长期用下去的事件系统,靠的不是告警接得多,而是四个约定:事件进得来(HMAC + 本地 spool 不丢)、出得去(常驻队列 + 三态投递有据可查)、留得下(统一时间线可回溯)、以及权限收得住(非 root + 单次令牌代理)。
写 web 应用时容易把精力全放在功能上,但这几处"不起眼的管道工程",恰恰决定了系统是"能用"还是"敢在半夜放心让它自己跑"。
- 点赞
- 收藏
- 关注作者
评论(0)