Worker может почти не расходовать CPU и одновременно проигрывать нагрузке: тысячи сообщений ждут в SQS, а Horizontal Pod Autoscaler видит спокойный контейнер. Для асинхронной системы давление измеряется не загрузкой процессора, а объёмом и возрастом невыполненной работы. KEDA превращает backlog очереди во внешнюю метрику HPA и умеет возвращать Deployment в ноль, когда очередь опустела.
31 июля Альбена Галабова из Itgix описала настройку KEDA для Amazon SQS в блоге CNCF. Материал Linux Foundation доступен по CC BY 3.0; эта русская адаптация добавляет эксплуатационные ограничения и сверяет manifest с KEDA 2.20. На 3 августа последний опубликованный patch-релиз — v2.20.2.
Почему CPU не описывает очередь
CPU показывает, насколько занят запущенный процесс, но не отвечает на вопрос, сколько работы ждёт снаружи. Один worker может быстро забрать batch из SQS и долго ждать внешнюю базу. CPU останется низким, хотя новые сообщения продолжат поступать. Обратная ситуация тоже возможна: тяжёлая локальная обработка загрузит CPU, но backlog уже исчез.
Для queue-driven workload полезный сигнал ближе к бизнес-очереди:
ApproximateNumberOfMessages— сообщения, доступные для получения;ApproximateNumberOfMessagesNotVisible— сообщения, которые consumer уже взял, но ещё не удалил;ApproximateNumberOfMessagesDelayed— отложенные сообщения, которые пока нельзя получить.
По умолчанию SQS-scaler KEDA складывает visible и in-flight сообщения. Delayed добавляются только при scaleOnDelayed: "true". Это не универсально правильная формула: она должна совпадать с семантикой consumer и visibility timeout.
Как KEDA делит ответственность с HPA
KEDA опрашивает SQS, публикует external metric и создаёт HPA для целевого Deployment. При положительном числе replicas масштабирование выполняет обычный HPA. KEDA отдельно управляет активацией из нуля и возвратом в ноль.
Эта граница объясняет два параметра, которые часто путают:
cooldownPeriodждёт после последнего активного trigger перед переходом в 0;horizontalPodAutoscalerConfig.behaviorуправляет изменениями в диапазоне 1→N и N→1.
Спецификация ScaledObject прямо указывает: cooldownPeriod не заменяет HPA stabilization window. Если настроить только cooldown, резкие колебания между 2 и 20 replicas останутся без контроля.
Базовый ScaledObject без ловушки одного сообщения
Ниже пример для Deployment orders-worker. Он использует очередь из переменной QUEUE_URL, опрашивает её раз в 10 секунд и целится в 10 сообщений на Pod:
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: sqs-auth
namespace: workers
spec:
podIdentity:
provider: aws
identityOwner: workload
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: orders-worker
namespace: workers
spec:
scaleTargetRef:
name: orders-worker
pollingInterval: 10
cooldownPeriod: 120
minReplicaCount: 0
maxReplicaCount: 30
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 60
triggers:
- type: aws-sqs-queue
authenticationRef:
name: sqs-auth
metadata:
queueURLFromEnv: QUEUE_URL
awsRegion: eu-west-1
queueLength: "10"
activationQueueLength: "0"
scaleOnInFlight: "true"
scaleOnDelayed: "false"
activationQueueLength — порог активности, а не число сообщений для первой replica. Scaler активен, когда metric выше порога. Значение 1 оставит Deployment в нуле при ровно одном сообщении; если любое сообщение должно разбудить worker, используйте 0 или не задавайте поле — это default KEDA 2.20.
queueURLFromEnv читается из контейнера scale target. Если переменная находится не в первом контейнере Pod, задайте scaleTargetRef.envSourceContainerName; иначе KEDA не найдёт URL, хотя Deployment работает.
queueLength нужно получить из нагрузочного теста
KEDA вычисляет желаемое число replicas примерно так:
desired replicas = ceil(actual messages / queueLength)
При queueLength: 10 backlog 25 даёт три replicas, но число 10 ничего не говорит о реальной производительности. Один Pod может обрабатывать десять коротких задач в секунду или одну внешнюю транзакцию пять минут.
Настройте target по результатам теста consumer:
- измерьте устойчивую скорость обработки одной replica;
- задайте допустимое время очистки burst;
- проверьте разные размеры сообщения и latency зависимостей;
- ограничьте
maxReplicaCountпропускной способностью базы, API и других downstream; - повторите тест с ошибками и retry, а не только с happy path.
Autoscaler способен быстро создать 30 Pod и перенести bottleneck в PostgreSQL. Backlog-aware scaling согласует replicas с очередью, но не знает capacity всей системы.
In-flight сообщения могут помогать или мешать
Default scaleOnInFlight: "true" учитывает сообщения внутри visibility window. Это удерживает capacity, пока работа ещё выполняется, и защищает от раннего scale-in. Для длинных jobs такое поведение обычно разумно.
Но in-flight backlog может быть ложным сигналом. Consumer завершился после ReceiveMessage, сообщение остаётся невидимым до timeout, а KEDA продолжает считать его работой. Слишком длинный visibility timeout удерживает replicas; слишком короткий создаёт повторную доставку ещё работающей задачи.
Решение зависит от модели обработки:
- оставьте in-flight в формуле, если оно отражает занятые worker slots;
- отключите через
scaleOnInFlight: "false", если масштабировать нужно только ожидающие сообщения; - продлевайте visibility для долгих tasks и делайте обработчик идемпотентным;
- согласуйте
terminationGracePeriodSecondsсо временем безопасного завершения или возврата сообщения.
Метрика приблизительная — SLO стройте по возрасту
AWS предупреждает, что значения ApproximateNumberOfMessages* eventually consistent и могут сходиться как минимум минуту после остановки producers. Поэтому короткий polling interval не превращает SQS в точный счётчик. Небольшие колебания около порога неизбежны.
Depth удобен для управления capacity, но пользовательское ожидание лучше отражает возраст. CloudWatch-метрика ApproximateAgeOfOldestMessage показывает возраст старейшего необработанного сообщения. Дашборд должен связывать минимум четыре сигнала:
visible backlog → in-flight backlog → age of oldest → processing/error rate
Если depth стабилен, а возраст растёт, workers не успевают. Если replicas растут, но throughput нет, причина находится в consumer или downstream, а не в autoscaler.
Разделяйте IAM scaler и worker
KEDA нужно читать свойства очереди. Для полного queue URL его минимальная задача — sqs:GetQueueAttributes на конкретный ARN. Сам consumer отдельно вызывает ReceiveMessage, DeleteMessage, ChangeMessageVisibility и, при необходимости, GetQueueUrl.
Не объединяйте роли автоматически. Компрометация KEDA operator не должна давать право получать или удалять сообщения, а компрометация worker — читать другие очереди. В примере используется podIdentity.provider: aws; старый provider aws-eks и поле scaler metadata identityOwner помечены deprecated и будут удалены в KEDA 3.
Выбор identityOwner: workload означает, что KEDA использует identity ServiceAccount целевого workload. Альтернатива — роль самого KEDA operator. Решение нужно закрепить в threat model и проверить trust policy, а не выбирать по тому, какой manifest первым заработал.
Как проверить rollout
Перед production создайте тестовую очередь и пройдите наблюдаемый сценарий:
- при пустой очереди Deployment устойчиво остаётся в 0;
- одно сообщение активирует минимум одну replica;
- burst создаёт ожидаемое число Pod без превышения downstream capacity;
- после очистки HPA снижает replicas, затем KEDA переводит 1→0;
- ошибка IAM или SQS API видна в
ScaledObjectconditions и логах operator; - недоступная external metric приводит к заранее выбранному fallback, а не случайному состоянию;
- завершение Pod не теряет сообщение и не запускает неограниченные дубликаты.
Проверьте kubectl describe scaledobject, созданный HPA, operator logs, SQS attributes и фактический throughput. Успех — не «Pod стало больше», а возраст очереди вернулся к SLO без перегрузки зависимостей.
KEDA решает правильную задачу только с правильным сигналом. Для SQS этим сигналом обычно служит backlog, но production-настройка начинается не с копирования YAML. Она начинается с пропускной способности worker, семантики visibility timeout, IAM-границы и допустимого возраста сообщения.