KEDA로 RabbitMQ Consumer 오토스케일링 - 운영 가이드

큐가 갑자기 쌓일 때 HPA(CPU 기반)는 거의 도움이 안 된다. 워커 파드의 CPU는 정작 한가한데 메시지는 계속 쌓이는 상황, 한 번쯤 겪어봤을 것이다. 이런 경우엔 큐 길이 자체를 메트릭으로 써야 한다. KEDA가 그걸 표준화해준다.
이 글은 RabbitMQ 컨슈머를 KEDA로 스케일링할 때 실제로 챙겨야 하는 설정들을 정리한 가이드다. KEDA 2.17 기준이고, 운영하면서 한 번씩 발 걸렸던 항목 위주로 적었다.
왜 HPA만으로는 부족한가
전형적인 큐 워커는 메시지 하나 처리에 I/O 대기가 많다. 외부 API 콜, DB 조회, S3 업로드 같은 것들. 이런 워커는 CPU가 30%만 찍혀도 큐는 만 건씩 쌓일 수 있다. CPU 50%를 임계로 HPA를 걸어두면 영원히 안 늘어난다.
큐 길이를 직접 보는 게 자연스럽다. KEDA의 ScaledObject는 RabbitMQ broker에 주기적으로 polling해서 queue length를 읽어오고, 그 값으로 워커 deployment의 replica를 조정한다. CPU/메모리 같은 hardware 지표가 아니라 비즈니스 지표로 스케일링하는 셈이다.
기본 ScaledObject 구성
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: rabbitmq-auth
namespace: workers
spec:
secretTargetRef:
- parameter: host
name: rabbitmq-conn
key: amqp-url
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-worker
namespace: workers
spec:
scaleTargetRef:
name: order-worker
pollingInterval: 15 # 초 단위, 너무 짧게 잡으면 broker 부하
cooldownPeriod: 120 # scale-to-zero 직전 대기
minReplicaCount: 0
maxReplicaCount: 30
triggers:
- type: rabbitmq
metadata:
protocol: amqp
queueName: order.process
mode: QueueLength
value: "20" # pod 1개당 처리 가능 메시지 수
authenticationRef:
name: rabbitmq-auth
핵심은 value: 20이다. "파드 한 대가 동시에 처리해 줬으면 하는 메시지 수"라고 읽으면 된다. 큐에 200건 있으면 파드 10개를 띄운다. 처리 속도가 안 받쳐주면 이 값을 낮추고, 파드 기동 비용이 크면 올린다.
운영하면서 깨진 곳들
protocol: http vs amqp
protocol: amqp는 정확하지만 connection이 매번 새로 맺어진다 (caching이 잘 안 됨). 워커가 수십 개 깜빡일 때 broker 쪽 connection 수가 출렁댄다. 프로덕션에서는 protocol: http로 management API를 쓰는 편이 점잖다. 15015 포트로 GET /api/queues/{vhost}/{queue} 한 번 치는 거라 가볍다.
metadata:
protocol: http
host: http://rabbitmq.svc:15672
queueName: order.process
mode: QueueLength
value: "20"
대신 management plugin이 켜져 있어야 하고, RBAC도 monitoring 권한이 필요하다.
mode: MessageRate가 더 맞을 때
QueueLength는 백로그 기반이라 "지금 막 들어오는 트래픽"에 반응이 느릴 수 있다. 큐가 거의 비어있다가 갑자기 spike가 들어오면 KEDA는 polling 한 번이 지나가야 깨닫는다.
mode: MessageRate는 publish rate를 본다. "초당 100건 들어오는데 파드 하나가 20건/초 처리 가능하면 → 파드 5개" 식으로 계산한다. 트래픽 패턴이 burst-y하면 이 모드가 더 빠르게 반응한다. 단점은 publish rate가 0이 되는 순간 빠르게 0으로 내려간다는 것. 마지막 메시지 처리 중인 파드까지 같이 죽으면 안 되니까 cooldownPeriod를 넉넉히 둬야 한다.
Graceful shutdown은 별도 작업
KEDA가 replica를 줄이라고 명령해도 in-flight 메시지를 마저 처리하는 건 워커 본인의 책임이다. 우리 팀은 처음에 이걸 놓쳐서 메시지가 ack 되기 전에 SIGKILL 받는 케이스가 있었다.
terminationGracePeriodSeconds를 60~120초로 늘리고, 워커 코드에서 SIGTERM 받으면 consumer 채널 close → 진행 중인 메시지 처리 끝까지 ack → connection close → exit 순서로 흐름을 만들어둬야 한다. KEDA는 안 도와준다.
Activation threshold로 깜빡임 방지
minReplicaCount: 0으로 두고 큐가 비어있을 때 파드를 다 죽이는 운영을 한다면, activationValue를 같이 써야 한다.
triggers:
- type: rabbitmq
metadata:
mode: QueueLength
value: "20"
activationQueueLength: "5" # 5건 이상 쌓여야 0→1 trigger
이게 없으면 메시지 1건만 들어와도 즉시 파드를 띄우다가, 처리 끝나면 cooldown 후 죽이고, 또 1건 들어오면 띄우고를 반복한다. 메시지가 드문드문 오는 큐에서 자주 본다.
모니터링 포인트
KEDA 자체도 메트릭을 노출한다. keda_scaler_metrics_value로 trigger가 본 현재 메시지 수, keda_scaler_errors로 broker 통신 실패를 추적할 수 있다. 알람은 최소 두 개 걸어두는 편이 좋다.
하나는 keda_scaler_errors_total 증가 — broker 접근 실패. 인증 토큰 만료나 management plugin 다운일 가능성이 크다. 다른 하나는 큐 길이가 maxReplicaCount * value를 일정 시간 초과 — 즉, KEDA가 띄울 만큼 다 띄웠는데 backlog가 안 줄어드는 상황. 워커 처리 속도 문제거나 다운스트림 병목이다.
마무리
KEDA 자체 설정은 단순한데, 실제로 운영해보면 mode 선택, graceful shutdown, activation threshold 같은 항목에서 한 번씩 깨진다. 처음 도입할 때는 protocol: http + QueueLength + activationQueueLength 조합으로 시작하길 권한다. 트래픽 패턴이 burst-y해서 반응성이 부족하면 그때 MessageRate로 옮겨가도 늦지 않다.
다음에는 같은 KEDA로 SQS 스케일링했을 때의 차이점도 정리해보려고 한다. RabbitMQ랑 비슷할 줄 알았는데, IAM 설정에서 한참 헤맸다.