HPA 只会伸缩不会自愈?我设计了一个 K8s 应用自愈 Operator:KubeStasis

23 阅读6分钟

更名说明:本项目原名 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 的理由:

  1. 声明式:用户描述期望状态(健康策略),而不是过程。这是 K8s 的灵魂,也是面试必考点。
  2. 事件驱动:Informer 机制让 Reconcile 只在资源变化时触发,比轮询优雅且省资源。
  3. 原生集成:kubectl get app 就能看到 Phase 和 Conditions,自愈动作直接以 Event 留痕,可审计、可追踪。

做这个项目的过程,就是吃透"控制器模式"的过程——ListWatch、DeltaFIFO、工作队列,这些面试高频词全都会在代码里亲手摸一遍。

决策 2:伸缩算法怎么防震荡?

HPA 震荡的根源是无记忆的比例控制。KubeStasis 加了三层防线:

  1. 冷却窗口:now - lastScalingAt < cooldownSeconds 时,拒绝一切缩容(扩容可以紧急放行——宁可多花钱,不可少容量)。
  2. 迟滞带:目标副本数与当前差值在 ±10% 内时不动作,避免"加一个又减一个"的抖动。
  3. 缩容渐进:每次最多缩 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 与系列安排

阶段内容对应文章
P0CRD + 第一个 Reconcile + envtest02:《从零写一个 K8s Operator》
P1Prometheus 多信号伸缩 + 防震荡03:《为什么 HPA 会震荡》
P2分级自愈引擎 + 金丝雀回滚04:《自动值班员》
P3Admission Webhook06:《准入控制实战》
P4手写 mini-Informer05:《图解 client-go Informer》

仓库地址:github.com/weirong-ui/kubestasis(系列更新中,欢迎 star 追更)

写在最后

这个项目的定位很清楚:它不是又一个 TodoApp 级别的 Go 练手项目,而是一个把 Kubernetes 控制器模式、Informer 机制、准入控制、可观测性全部亲手实现一遍的"面试级"开源项目。每一行核心代码都会在系列文章里拆开讲透。

如果这个思路对你有帮助,点个赞关注不迷路,系列持续更新中。下一篇:《从零写一个 K8s Operator:kubebuilder 初始化到第一个 Reconcile》。


觉得哪里设计得不合理?评论区欢迎拍砖,Issues 区见。