不能直接用 time.Ticker 做扩缩容决策,因其不感知指标延迟、不处理采样噪声、无滑动窗口平滑,易因瞬时异常(如 GC 暂停)误触发扩缩容;需引入指标缓冲、变化率抑制、最小稳定周期及带时间权重的滑动平均(如 EMA),并强制连续 n 周期达标才动作。
time.Ticker 做扩缩容决策很多初版实现会用定时器每 30 秒拉一次 CPU 使用率,再简单判断是否增减实例。这看似合理,但实际会导致抖动甚至雪崩:time.Ticker 不感知指标延迟、不处理采样噪声、也不做滑动窗口平滑——比如某次采集恰好卡在 GC 暂停瞬间,cpu_usage 突然飙到 95%,服务就误判为需扩容,而真实负载其实在下降。
真正可用的逻辑必须包含:指标缓冲、变化率抑制、最小稳定周期。建议用带时间权重的滑动平均(如 EMA),并强制要求连续 n 个周期满足阈值才触发动作。
github.com/beefsack/go-rate 或手写一个带时间衰减的 EMA 结构体,避免 raw 值跳变ema.Update(value, time.Now()),再判断 ema.Value() > 80
sync.Mutex)防止并发重复提交请求直接调用 Scale 子资源是最轻量的方式,比删/建 Deployment 更快且保留滚动更新历史。但要注意:Kubernetes 的 scale 接口默认只接受整数副本数,且对 minReplicas/maxReplicas 无感知——这些边界必须由你的控制器自己校验。
常见错误是未检查当前 replicas 是否已处于边界,导致反复提交相同请求,触发 apiserver 频繁更新 resourceVersion,引发冲突错误 409 Conflict。
/apis/apps/v1/namespaces/{ns}/deployments/{name}/scale 获取 status.replicas
math.Max(math.Min(target, max), min) 显式截断application/merge-patch+json 类型,内容为:{"spec":{"replicas":3}}errors.IsConflict(err) 要重试,errors.IsForbidden(err) 则说明 RBAC 权限不足(缺 scale verb)硬阈值(如 CPU > 80% 就扩容)在临界点附近极易来回触发,尤其当指标本身有小幅波动时。真实系统需要“迟滞区间”和“冷却期”。
典型做法是定义两个阈值:upThreshold = 75%(开始扩容),downThreshold = 45%(允许缩容)。只有从低于 45% 上升穿越 75% 时才扩容;从高于 75% 下降穿越 45% 时才缩容。中间区域(45%–75%)保持当前副本数不变。
currentStatus(ScalingUp/Stable/ScalingDown)、lastActionTime、pendingTarget
time.Since(lastActionTime) > cooldownDuration(如 5 分钟)replicas == minReplicas,即使指标持续低迷也禁止继续缩在没连上集群时,你依然要验证指标采集、EMA 计算、阈值判断、冷却控制等核心链路。关键在于把“执行扩缩容”抽象成可替换的接口。
定义一个 Scaler 接口:
type Scaler interface {
Scale(ctx context.Context, name string, target int32) error
},然后提供两个实现:KubeScaler(真集群)和 DummyScaler(仅打印日志 + 模拟延迟)。
--scaler=dummy 或 SCALER_TYPE=kube
DummyScaler.Scale 内部 sleep 100ms 并输出:INFO: would scale deployment "xxx" to 3 replicas
gomock 对单元测试中的 Scaler 打桩,验证连续 3 次高负载是否只触发 1 次扩容metric_timestamp 和 collector_time 两列,方便排查延迟来源。