让积压量决定副本数:用 KEDA 按 Amazon SQS 队列深度扩缩 Kubernetes Worker

2026-07-31 33 预计阅读时间: 1 分钟
来源: cncf.io AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

在事件驱动系统里,CPU 和内存并不总能反映真实压力。一个消费者 Pod 可能只占用很少的 CPU,但 Amazon SQS 中已经堆积了数千条消息。此时如果只依赖 Kubernetes HPA 的资源指标,扩容往往发生得太晚。

KEDA 可以把 SQS 队列深度转换成 Kubernetes 扩缩容信号,让 Worker 副本数跟随待处理消息数量变化。系统关心的不再只是“Pod 有多忙”,而是“还有多少工作没完成”。

为什么 CPU 指标会误判事件处理压力

队列消费者经常包含大量 I/O 操作,例如调用外部 API、查询数据库或等待对象存储响应。在等待期间,CPU 使用率可能很低,但消息仍会持续进入队列。

只看 CPU 会带来两个典型问题:

  • 消息快速积压时,Pod 的 CPU 尚未达到阈值,HPA 不会及时扩容。
  • 队列已经清空后,某些批处理或运行时行为仍可能维持较高的资源占用,缩容出现延迟。

SQS 队列深度更接近业务压力,但它也不是吞吐量本身。扩缩容配置还需要考虑单条消息的处理时间、每个 Pod 的并发度、可见性超时,以及下游服务能承受的请求速率。

一个简单的容量估算是:

目标副本数 ≈ 待处理消息数 / 每个 Pod 期望承担的消息数

例如,将 KEDA 的目标队列长度设置为 20,队列中有约 200 条待处理消息时,扩缩容器会倾向于请求约 10 个副本。实际结果仍受 minReplicaCountmaxReplicaCount、指标采样和 Kubernetes HPA 行为影响。

KEDA 如何把 SQS 接入 Kubernetes 扩缩容

KEDA 通过 ScaledObject 描述扩缩容目标和外部事件源。对于 SQS,配置通常包含:

  • 要扩缩的 Deployment
  • 队列 URL 和 AWS 区域。
  • 每个副本期望承担的队列长度。
  • 轮询间隔、冷却时间以及副本上下限。
  • KEDA 查询 SQS 指标所需的 AWS 身份与 IAM 权限。

KEDA 负责轮询队列并向 Kubernetes 暴露外部指标,真正调整 Deployment 副本数的仍是 HPA。排障时因此需要同时检查 ScaledObject、KEDA Operator 日志和生成的 HPA。

SQS 返回的是近似消息数量,而且“可见消息”与“正在处理但尚未删除的消息”代表不同状态。KEDA 版本和配置可能决定是否把 in-flight 消息计入计算。上线前应根据所用版本核对 scaleOnInFlight 等选项,并观察长耗时任务是否造成副本数持续偏高。

可以这样实践:部署 Worker 和 ScaledObject

下面是一套可改造的最小配置。运行前需要完成三项修改:替换镜像地址、SQS 队列 URL 和 AWS 区域;同时确保集群已安装 KEDA,并为它配置能够读取该队列属性的 AWS 身份。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: orders-worker
  namespace: workers
spec:
  replicas: 0
  selector:
    matchLabels:
      app: orders-worker
  template:
    metadata:
      labels:
        app: orders-worker
    spec:
      containers:
        - name: worker
          image: ghcr.io/example/orders-worker:1.0.0
          env:
            - name: SQS_QUEUE_URL
              value: https://sqs.us-east-1.amazonaws.com/123456789012/orders
            - name: AWS_REGION
              value: us-east-1
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              memory: 256Mi
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: sqs-auth
  namespace: workers
spec:
  podIdentity:
    provider: aws-eks
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: orders-worker-sqs
  namespace: workers
spec:
  scaleTargetRef:
    name: orders-worker
  pollingInterval: 15
  cooldownPeriod: 120
  minReplicaCount: 0
  maxReplicaCount: 30
  fallback:
    failureThreshold: 3
    replicas: 2
  triggers:
    - type: aws-sqs-queue
      authenticationRef:
        name: sqs-auth
      metadata:
        queueURL: https://sqs.us-east-1.amazonaws.com/123456789012/orders
        awsRegion: us-east-1
        queueLength: "20"
        activationQueueLength: "1"
        scaleOnInFlight: "true"

将文件保存为 sqs-worker.yaml 后,可以执行:

kubectl create namespace workers
kubectl apply -f sqs-worker.yaml
kubectl get scaledobject,hpa,deployment -n workers
kubectl describe scaledobject orders-worker-sqs -n workers

这里假设使用 EKS Pod Identity 或 IRSA 一类的无静态密钥身份方案。具体绑定方式取决于集群和 KEDA 版本。KEDA 查询队列至少需要读取 SQS 队列属性;可以从以下最小策略开始,再按实际错误补充权限:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "sqs:GetQueueAttributes"
      ],
      "Resource": "arn:aws:sqs:us-east-1:123456789012:orders"
    }
  ]
}

不要把 AWS Access Key 和 Secret Key 直接写进 YAML。优先使用工作负载身份,并将权限限制到目标队列。

用测试消息验证扩容链路

可以向队列发送一批测试消息,再观察副本数变化。下面的命令要求本机已经配置 AWS CLI 凭证;请替换队列 URL:

QUEUE_URL="https://sqs.us-east-1.amazonaws.com/123456789012/orders"

for i in $(seq 1 100); do
  aws sqs send-message \
    --region us-east-1 \
    --queue-url "$QUEUE_URL" \
    --message-body "{\"order_id\":\"test-$i\"}" >/dev/null
done

kubectl get deployment orders-worker -n workers -w

同时查看队列属性,有助于区分可见消息和正在处理的消息:

aws sqs get-queue-attributes \
  --region us-east-1 \
  --queue-url "$QUEUE_URL" \
  --attribute-names \
    ApproximateNumberOfMessages \
    ApproximateNumberOfMessagesNotVisible

如果没有扩容,可按这个顺序检查:

  1. kubectl describe scaledobject 中是否存在认证或指标错误。
  2. KEDA Operator 是否有 sqs:GetQueueAttributes 权限。
  3. 队列 URL、区域和 IAM Resource ARN 是否一致。
  4. KEDA Metrics Server 和生成的 HPA 是否正常。
  5. activationQueueLength 是否高于当前积压量。

参数不要只按“越快越好”调整

queueLength 决定扩容敏感度。设置太大,消息等待时间会上升;设置太小,则可能产生大量 Pod、拉高成本,并冲击数据库或第三方 API。

pollingInterval 越短,KEDA 越快看到积压变化,但查询频率也会增加。cooldownPeriod 太短会导致负载波动时频繁缩容和重新拉起 Pod。对于启动慢的 Worker,还应考虑镜像拉取、节点扩容和应用预热耗时。

maxReplicaCount 应当由下游容量决定,而不是由队列可能达到的最大深度决定。若数据库最多承受 200 个并发连接,而每个 Worker 会占用 10 个连接,那么将上限直接设为 100 很可能把队列问题转化为数据库故障。

投入生产前,至少确认以下事项:

  • 使用真实处理时长估算 queueLength 和最大副本数。
  • 校准 SQS visibility timeout,避免长任务被重复投递。
  • Worker 能够处理重复消息,并在收到终止信号时停止领取新任务。
  • 配置死信队列、失败重试和消息年龄告警。
  • 同时监控可见消息数、in-flight 消息数、最老消息年龄和处理失败率。
  • 验证 KEDA 或 AWS API 暂时不可用时的 fallback 行为。

KEDA 解决的是“根据工作量分配消费者”这一层问题。它不能自动保证消息幂等、下游容量或任务正确终止。把队列深度扩缩容与这些工程约束一起设计,才能真正缩短消息等待时间,而不是单纯增加 Pod 数量。


相关推荐