风铃草 中文站
持续迭代 2026.03 — 现在 · 主力项目

sentinel-go

从观测到维护

从一个基础的 Go HTTP 服务起步,逐步接进 Linux 内存指标、Prometheus、容器与 Kubernetes,最后长成一个受 PDB 约束的节点维护协调器。

  • Go
  • Prometheus
  • Docker
  • Kubernetes
  • controller-runtime
node-01 PDB 预算耗尽
429
驱逐请求暂缓

预算恢复后才可继续驱逐。

健康 Pod
2 / 3
最低可用
2
允许驱逐
0
维护场景示意。替代副本仍未就绪,两个健康 Pod 恰好满足最低可用数。

缘起

sentinel-go 最早只是一个很小的 Go HTTP 服务,后来顺着「怎么看清系统状态、怎么安全接管维护」一步步往下长:先接进 Linux 内存指标与 Prometheus,再装进容器与 Kubernetes,最后落成一个受限的节点维护协调器。

因此仓库里既有 /healthz 和内存表这种最基础的模块,也有 cordon 所有权注解、Eviction API、PDB 429 冷却和整轮 deadline 这类一不小心就会写成「表面能跑、遇到边界就坏」的控制逻辑。

问题

最开始去读 /proc 的时候,我发现自己以前对「系统在跑」的理解其实全靠现成命令的输出。真要自己把 MemAvailable 从内核文本变成 HTTP 指标,中间得挨个跨过字段缺失、单位换算、uint64 溢出保护、定时抓取和 PromQL 查询;哪一环偷了懒,监控面板上的数字就只是摆设。

等到往 Kubernetes 里走,节点维护也远不止敲一条 drain 命令那么简单。请求发出、API 受理、节点状态回读、Pod 真正退出、再到 PDB 预算恢复,这五个时刻在时间线上是彻底分开的。如果把它们混成一件事,写出来的代码就会在「刚发出请求」的那一刻草率宣布成功。

结构

整个项目由三条链路共用同一个 Go 二进制:一条负责把内核字段暴露成监控指标,一条负责在维护前只读地排查风险,最后一条才去集群里执行受限的节点维护。每条链路只管自己的输入、输出和失败退路,彼此之间不留隐式的黑盒。

观测

net/http /proc/meminfo /metrics Prometheus / PromQL

预检

client-go Reader Snapshot Pod → RS → Deployment Analyze → Report

维护

MaintenancePlan cordon(认领所有权) Eviction API 429 → 冷却 → 恢复
结构示意,非界面截图。预检与控制器共享 client-go 的读取路径,但预检永远不写集群。 FIG. 01

预检

真正动手封锁节点之前,先用只读客户端把现场摸清楚:节点上的 Pod 归属于哪个 ReplicaSet 和 Deployment、有没有挂载本地或非共享 PVC、同一批 Pod 会不会撞上多个重叠的 PDB。预检工具最后会给出一份三档风险报告和对应的退出码,既方便人直接看,也能塞进脚本里做门禁。

  1. 01

    Reader

    只读客户端,最小权限。

  2. 02

    Snapshot

    把 Pod、RS、Deployment、PDB 读成一份现场。

  3. 03

    Analyze

    归属链、PVC、重叠预算,逐项判断。

  4. 04

    Report

    三档风险与退出码,不写集群。

回归测试里专门放了两个反例:一个是挂载了 PVC 的 Pod,另一个是同一节点上互相重叠的 PDB。这两组用例必须稳定拦住预检——毕竟测试全绿本身不说明问题,能准确拦住坏情况才算有效。

控制器

下面的状态机可以直接点击推进。它把一次维护拆成了十一个状态:从发出 cordon 请求、等待回读确认,到撞上 PDB 429 进入冷却,再到预算恢复与最终解封。留意第一步的状态叫「已提交,等待观测」——因为请求发出去的那一瞬间,集群里的真实状态还没变呢。

一次维护的十一个状态

STEP 01 / 11

点击「开始维护」可单步推进状态机。任意步骤均可中断复位;当 PDB 预算耗尽时,Eviction API 会返回 429 并触发持久冷却。状态机不设自动播放,方便逐帧核对期望状态与观测回读的差异。

node-01 可调度
  • pod-a Running node-01
  • pod-b Running node-01
  • pod-c Running node-01
字段期望 desired观测 observed
spec.unschedulable false false
pods.ready 3 / 3 3 / 3
pdb.disruptionsAllowed 1 1

指标均针对 node-01 与该 PDB;差异出现时,观测格会被标出。

状态

节点可调度,3 个 Pod 就绪。PDB minAvailable = 2,当前允许 1 次中断。

示意基于真实控制器行为,但为便于阅读压缩了时间与次数;它不是线上集群的实时数据。

证据

  1. 01

    并发安全:互斥锁保护的 MemoryStore、依赖注入装配,以及 go test -race 下的隔离测试。

  2. 02

    输入校验:同时断言错误响应与「状态零副作用」,确保非法请求不会污染原有数据。

  3. 03

    /proc/meminfo:覆盖字段缺失、非法数字、单位换算,以及 MaxUint64 / 1024 的溢出边界。

  4. 04

    真实集群:在 kind 多节点集群中完成 readinessProbe 与 targetPort 错配排障,验证修复后流量恢复。

  5. 05

    控制器重启:从 Status 中的 nextActionAt 与 deadline 重读现场,决定继续协调或安全停止。

边界

它离生产可用的通用 Operator 还有很大距离,边界很明确:

  • 目前只覆盖单节点维护,没有经过多租户、大规模并发与长周期运行的验证。
  • 实验主要在 kind 与自有测试环境完成,没有在真实生产业务集群上跑过完整维护窗口。
  • 429 冷却与 deadline 仍有未覆盖的异常分支;「测试过重启恢复」不等于「所有中断场景都已穷尽」。
  • 项目在教学目标、骨架与审查支持下完成;核心切片由本人亲手实现,但并非从零独立设计整套系统。