更名说明:本项目原名 KubeSentry,自 2026-11-03 起更名 KubeStasis(避免与既有项目重名);CRD group 也于 11-04 由
apps.sentry.io改为kubestasis.io。前几篇文章里的旧名指的都是同一个项目,代码仓库现为 github.com/weirong-ui/…。
这是系列文章的第一篇。这个系列我会从零手写一个 Kubernetes Operator——KubeStasis,它能让集群里的应用"自己变强、自己疗伤"。本篇讲清楚:为什么做、解决什么问题、怎么设计。后续文章按模块拆解实现。
一、从一个凌晨三点的故障说起
假设你有这样一个服务部署在 K8s 上:
新版本上线十分钟后,5xx 错误率从 0.1% 飙到 15%。这时候会发生什么?
- kubelet 说:Pod 没挂啊,进程活着,探针也过,不归我管。
- HPA 说:错误率不是我的指标,我只看 CPU,现在才 30%,不扩容。
- Deployment 说:回滚?需要人来执行
kubectl rollout undo。
发现了吗?K8s 的每一个组件都在自己的职责边界内"正确地"工作,但没有任何一个组件对"应用整体健康"负责。凌晨三点,需要一个人爬起来,看 Grafana,判断,然后敲回滚命令。
KubeStasis 想做的就是把这个"人"的职责自动化:声明式地告诉集群"什么状态算健康、出问题了按什么策略处理",剩下的交给 Operator。
二、现有方案的短板
调研了一圈主流方案,各有明确的能力边界:
HPA(原生)
- 只支持 CPU/内存等资源指标(自定义指标要装 Prometheus Adapter,配置繁琐)
- 纯比例控制,没有冷却窗口意识,流量尖峰下副本数反复横跳(震荡是经典问题)
- 对故障零响应
KEDA
- 指标源丰富(包括 Prometheus),解决了"用什么指标扩缩容"
- 但依然只有"伸缩"一只手,不管自愈
Argo Rollouts / Flagger
- 解决"发布过程的安全"(金丝雀、渐进式交付),但运行期故障自愈不在射程内
也就是说:伸缩(KEDA)、发布安全(Flagger)、运行期自愈——第三块是空白。KubeStasis 主攻第三块,同时用更好的伸缩算法补齐第二块短板。
三、KubeStasis 是什么
一句话:一个基于 controller-runtime 的 Kubernetes Operator,通过自定义 CRD App 管理应用,提供多信号智能伸缩 + 分级故障自愈 + 金丝雀自动回滚。
整体架构:
开发者 ──kubectl apply App CR──▶ API Server ──▶ Admission Webhook(校验+安全默认值)
│
▼
┌────────── KubeStasis Operator ──────────┐
│ App Controller(Reconcile 主循环) │
│ ├── Scaler:多信号伸缩决策 │
│ ├── Healer:分级故障自愈引擎 │
│ └── 读写 Deployment / Service / Status │
└──────────────────────────────────────────┘
▲
Prometheus(QPS / P99 / 错误率)
用户写一个 App CR,就能获得:
apiVersion: kubestasis.io/v1alpha1
kind: App
metadata:
name: order-service
spec:
image: registry.example.com/order-service:v1.2.0
replicas: 3
scaling:
minReplicas: 2
maxReplicas: 20
cooldownSeconds: 120 # 冷却窗口,防震荡
metrics:
- type: QPS
target: "1000" # 每 Pod 目标 QPS
- type: P99Latency
target: "200ms"
health:
strategy: graduated # 分级自愈:扩容 → 降级 → 回滚
canary:
enabled: true
observationSeconds: 300 # 新版本观察窗口
errorRateThreshold: "0.01" # 错误率超 1% 自动回滚
四、三个核心设计决策
决策 1:为什么是 Operator,而不是一个外部轮询服务?
完全可以写个 cron job 定期查 Prometheus 然后改 Deployment——但那只是"脚本的堆砌"。选 Operator 的理由:
- 声明式:用户描述期望状态(健康策略),而不是过程。这是 K8s 的灵魂,也是面试必考点。
- 事件驱动:Informer 机制让 Reconcile 只在资源变化时触发,比轮询优雅且省资源。
- 原生集成:
kubectl get app就能看到 Phase 和 Conditions,自愈动作直接以 Event 留痕,可审计、可追踪。
做这个项目的过程,就是吃透"控制器模式"的过程——ListWatch、DeltaFIFO、工作队列,这些面试高频词全都会在代码里亲手摸一遍。
决策 2:伸缩算法怎么防震荡?
HPA 震荡的根源是无记忆的比例控制。KubeStasis 加了三层防线:
- 冷却窗口:
now - lastScalingAt < cooldownSeconds时,拒绝一切缩容(扩容可以紧急放行——宁可多花钱,不可少容量)。 - 迟滞带:目标副本数与当前差值在 ±10% 内时不动作,避免"加一个又减一个"的抖动。
- 缩容渐进:每次最多缩 20%,扩容一步到位。不对称策略,因为扩太慢伤用户,缩太快伤稳定性。
多信号决策取"最紧急者":desired = max(ceil(replicas × current/target) for each metric),再被 min/max 和迟滞带约束。
决策 3:自愈为什么要"分级"?
最直觉的写法是:发现异常 → 立刻执行所有能做的动作(扩容 + 重启 + 回滚一起上)。这是大忌:动作互相干扰会破坏事故现场,让你永远不知道根因是什么。
KubeStasis 的分级自愈状态机:
L1 扩容(缓解流量型问题)──观察一个窗口──▶ 无效?
L2 降级(摘除不健康副本,切断对坏实例的流量)──观察──▶ 无效?
L3 回滚(回退到上一个健康 revision)
每次 Reconcile 只执行一级动作,动作之间必须有观察期。所有动作 Emit Kubernetes Event(reason 统一 KubeStasis* 前缀)并写回 Status——自愈系统自己必须是可观测的,否则它做出的错误决策你都没法审计。
五、安全也是一等公民(CKS 的落地点)
作为持有 CKA + CKS 的项目,安全设计不能只是嘴上说说:
- Defaulting Webhook 自动注入安全上下文:
runAsNonRoot、readOnlyRootFilesystem、drop ALL capabilities、seccomp RuntimeDefault——受管应用默认就是 CKS 及格线以上的安全姿态 - Validating Webhook:禁止
latest标签进生产、镜像必须显式 tag、伸缩范围合法性校验 - Operator 自身:distroless 镜像、非 root、RBAC 最小权限(拒绝通配符)
- Webhook 证书由 cert-manager 自动管理轮换
六、Roadmap 与系列安排
| 阶段 | 内容 | 对应文章 |
|---|---|---|
| P0 | CRD + 第一个 Reconcile + envtest | 02:《从零写一个 K8s Operator》 |
| P1 | Prometheus 多信号伸缩 + 防震荡 | 03:《为什么 HPA 会震荡》 |
| P2 | 分级自愈引擎 + 金丝雀回滚 | 04:《自动值班员》 |
| P3 | Admission Webhook | 06:《准入控制实战》 |
| P4 | 手写 mini-Informer | 05:《图解 client-go Informer》 |
仓库地址:github.com/weirong-ui/kubestasis(系列更新中,欢迎 star 追更)
写在最后
这个项目的定位很清楚:它不是又一个 TodoApp 级别的 Go 练手项目,而是一个把 Kubernetes 控制器模式、Informer 机制、准入控制、可观测性全部亲手实现一遍的"面试级"开源项目。每一行核心代码都会在系列文章里拆开讲透。
如果这个思路对你有帮助,点个赞关注不迷路,系列持续更新中。下一篇:《从零写一个 K8s Operator:kubebuilder 初始化到第一个 Reconcile》。
觉得哪里设计得不合理?评论区欢迎拍砖,Issues 区见。