使用 KEDA 根据 NATS 队列长度伸缩 Docker 容器

解读

在国内云原生落地场景中,**“消息积压触发弹性”**是微服务削峰填谷的刚需。面试官想验证四点:

  1. 你是否理解 KEDA 作为事件驱动弹性控制器 的定位,与 HPA 的差异;
  2. 能否把 NATS 队列长度指标 准确暴露给 KEDA;
  3. 是否熟悉 Docker 镜像与 Deployment 的配套改造(非 root、最小镜像、快速启动);
  4. 是否具备 全链路可观测与故障兜底 意识(灰度、冷却窗口、兜底副本数)。
    回答时要体现“指标来源→KEDA 配置→镜像优化→CI/CD 串联→生产级细节”的闭环,避免只背 YAML。

知识点

  • KEDA CRD:ScaledObject、TriggerAuthentication、ClusterTriggerAuthentication
  • NATS JetStream 消费模型:stream、consumer、ack policy、pending.msgs 指标
  • Docker 多阶段构建:distroless 或 alpine 最小镜像,非 root 用户,<100 MB
  • 水平伸缩边界:minReplicaCount/maxReplicaCount、cooldownPeriod、advanced pollingInterval
  • 国产云兼容:ACK/TKE/华为 CCE 均已内置 KEDA 插件,但需确认 RBAC 与监控存储(thanos/云监控)
  • 灰度发布:argo-rollouts 结合 ScaledObject 的 rollout 策略,防止瞬间缩容到零
  • 可观测:KEDA 暴露的 keda_metrics_adapter_prom_metrics 需接入阿里云 Prometheus 或夜莺,配置 Grafana 大盘:keda_scaler_metrics_value、keda_scaler_errors
  • 安全:KEDA 使用 pod identity 绑定云账号,避免在镜像里写 NATS 密钥;Dockerfile 里通过 --mount=type=secret 构建时导入证书

答案

步骤 1:NATS JetStream 侧准备

  1. 创建 stream:nats stream add orders --subjects="orders.>" --storage=file --retention=limits --max-msgs=-1 --max-age=24h
  2. 创建 pull consumer:nats consumer add orders order-worker --ack=explicit --max-deliver=3 --replay=instant
  3. 确认指标端点:JetStream 内置 /jsz?consumers=true 接口返回 pending.msgs,KEDA 的 NATS scaler 会调用该接口,需保证 cluster 内网 DNS 可达

步骤 2:Docker 镜像优化
Dockerfile 示例:

FROM golang:1.22-alpine AS builder
RUN apk add --no-cache ca-certificates git
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app cmd/worker/main.go

FROM alpine:3.19
RUN apk add --no-cache nats-cli ca-certificates tzdata && \
    adduser -D -u 1000 appuser
USER 1000
COPY --from=builder /src/app /usr/local/bin/app
ENTRYPOINT ["app"]

构建后镜像 42 MB,启动时间 <300 ms,适合缩容到零后快速冷启动。

步骤 3:KEDA 配置

apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: nats-jetstream-auth
  namespace: orders
spec:
  secretTargetRef:
  - parameter: username
    name: nats-user-creds
    key: username
  - parameter: password
    name: nats-user-creds
    key: password
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-worker-scaler
  namespace: orders
spec:
  scaleTargetRef:
    name: order-worker-deploy  # 对应 Deployment 名称
  pollingInterval: 10          # 每 10 秒拉一次队列长度
  cooldownPeriod: 60           # 连续 60 秒无消息才缩容
  minReplicaCount: 0           # 国产云夜间低峰缩容到零,节省 30% 成本
  maxReplicaCount: 50
  triggers:
  - type: nats-jetstream
    metadata:
      server: "nats://nats.jetstream.svc.cluster.local:4222"
      stream: "orders"
      consumer: "order-worker"
      account: "$G"
      activationLagThreshold: "10"  # 队列积压 ≥10 条即触发扩容
    authenticationRef:
      name: nats-jetstream-auth

关键点

  • 使用 零副本 时,需把 terminationGracePeriodSeconds=30 调小,避免缩容到零时 NATS ack 超时;
  • 若集群版本 <1.24,需手动给 KEDA metrics-server 打开 Aggregated API 开关

步骤 4:CI/CD 串联
在 GitLab CI 中增加 job:

keda-validate:
  stage: deploy
  image: bitnami/kubectl:1.29
  script:
    - kubectl apply --dry-run=server -f keda/ && echo "KEDA 语法通过"
    - kubectl wait --for=condition=Ready scaledobject/order-worker-scaler -n orders --timeout=60s

灰度策略:先设置 maxReplicaCount=5 试运行一晚,观察阿里云 Prometheus 中 keda_scaler_metrics_value{scaledObject="order-worker-scaler"} 曲线,确认与 NATS 控制台一致后再放大上限。

步骤 5:故障兜底

  • 兜底副本数:通过 KEDA + VerticalPodAutoscaler 保活插件,在零副本场景下若 Scaler 失效,VPA 强制拉起 1 副本;
  • 告警:夜莺规则 keda_scaler_errors > 0 持续 2 分钟即 @值班,防止因 JetStream 宕机导致弹性失效;
  • 镜像回滚:Docker 镜像 tag 固定为 git-commit-sha,配合 kubectl rollout undo,回滚时间 <30 秒。

拓展思考

  1. 多集群消息漂移:若生产环境采用 阿里云 MSE + NATS 双活,KEDA 需配置 ClusterTriggerAuthentication 并区分 stream 名称,防止南北集群同时扩容造成重复消费。
  2. 成本优化:结合 阿里云 ECI 弹性实例,在零副本扩容时拉起 Spot + 节省停机不收费 实例,单位成本再降 70%,但需给 Dockerfile 加入 优雅退出信号处理signal.NotifyContext),防止 Spot 回收时消息未 ack。
  3. Serverless 化:将 Worker 容器改造为 Knative Service,KEDA 的 ScaledObject 指向 Knative Revision,实现 消息驱动 + 自动缩容到零 + 蓝绿发布 三位一体,适合大促场景。