-
KEDA 트러블슈팅: RabbitMQ 기반 이벤트 드리븐 오토스케일링이코에코(Eco²)/Troubleshooting 2025. 12. 26. 19:43

KEDA HTTP Add-on을 추가할 당시, KEDA 개발자가 포스팅한 디자인 아키텍처다. KEDA의 내부 통신(gRPC 파트)을 이해하는데 도움이 된다. 본 문서는 Kubernetes 환경에서 KEDA(Kubernetes Event-driven Autoscaling)를 활용하여 RabbitMQ 큐 길이 기반 Worker Pod 오토스케일링을 구현하는 과정에서 발생한 이슈와 해결 방법을 기술합니다.
환경 정보
구성 요소 버전/사양 Kubernetes v1.28.4 (kubeadm) KEDA v2.16.0 RabbitMQ v3.13.x (RabbitMQ Operator) ArgoCD v2.13.x CNI Calico (NetworkPolicy 적용) 목표
- CPU 기반 HPA의 한계를 극복하고 메시지 큐 길이 기반 오토스케일링 구현
- scan-worker의 동적 스케일링으로 부하 대응력 향상
- k6 부하 테스트 시 성공률 개선
1. 배경: CPU 기반 HPA의 한계
1.1 기존 구조
scan-worker는 Celery 기반 비동기 Worker로, RabbitMQ 큐에서 메시지를 소비하여 OpenAI API 호출 등 I/O 집약적 작업을 수행한다.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ scan-api │────►│ RabbitMQ │────►│ scan-worker │ │ (Producer) │ │ (Queue) │ │ (Consumer) │ └─────────────┘ └─────────────┘ └─────────────┘ │ scan.vision: 22 scan.answer: 16 scan.rule: 141.2 문제점
k6 부하 테스트(50 VUs, 3분) 결과:
지표 값 총 요청 643 성공 452 (70.3%) 실패 128 (19.9%) 부분 완료 63 (9.8%) CPU 기반 HPA가 효과적이지 않은 이유:
- OpenAI API 호출 대기 시간이 대부분 (30-50초)
- CPU 사용률은 낮게 유지 (10-15%)
- 큐에 메시지가 쌓여도 스케일업 트리거 안 됨
1.3 해결 방안: KEDA 도입
KEDA는 외부 이벤트 소스(RabbitMQ, Kafka, Prometheus 등)를 기반으로 HPA를 생성하여 이벤트 드리븐 스케일링을 지원한다.
2. 초기 구현
2.1 ScaledObject 정의
apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: scan-worker-scaledobject namespace: scan spec: scaleTargetRef: name: scan-worker kind: Deployment minReplicaCount: 1 maxReplicaCount: 5 cooldownPeriod: 120 pollingInterval: 15 triggers: - type: rabbitmq metadata: protocol: amqp queueName: scan.vision mode: QueueLength value: '10' authenticationRef: name: rabbitmq-trigger-auth2.2 TriggerAuthentication
apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: rabbitmq-trigger-auth namespace: scan spec: secretTargetRef: - parameter: host name: scan-secret key: CELERY_BROKER_URL
3. Issue #1: 메트릭 수집 실패 (ScalerNotActive)
3.1 현상
ScaledObject 배포 후 ACTIVE 상태가 False로 유지:
$ kubectl get scaledobject -n scan -o wide NAME SCALETARGETKIND SCALETARGETNAME MIN MAX READY ACTIVE scan-worker-scaledobject apps/v1.Deployment scan-worker 1 5 True FalseHPA 메트릭이 모두 0으로 표시:
$ kubectl describe hpa keda-hpa-scan-worker-scaledobject -n scan Metrics: "s0-rabbitmq-scan-vision" (target average value): 0 / 10 "s1-rabbitmq-scan-answer" (target average value): 0 / 10 "s2-rabbitmq-scan-rule" (target average value): 0 / 203.2 원인 분석
RabbitMQ 큐 상태를 직접 조회한 결과:
$ kubectl exec -n rabbitmq eco2-rabbitmq-server-0 -- \ rabbitmqctl list_queues name messages_ready messages_unacknowledged -p eco2 QUEUE READY UNACKED scan.vision 0 22 scan.answer 0 16 scan.rule 0 14 scan.reward 0 9핵심 발견:
messages_ready=0,messages_unacknowledged=NCelery Worker 설정:
# domains/_shared/celery/config.py worker_prefetch_multiplier = 1 # greenlet당 1개 prefetch task_acks_late = TrueWorker는 gevent pool(100 greenlets)을 사용하며, 메시지를 prefetch하여 처리한다. 이로 인해 모든 메시지가
unacked상태로 전환된다.3.3 KEDA RabbitMQ Scaler 동작 분석
KEDA의 AMQP 프로토콜 스케일러 동작:
// KEDA 소스 코드 참고 func (s *rabbitMQScaler) GetQueueInfo() (int, error) { // pika.BlockingConnection 사용 // channel.queue_declare(passive=True) 호출 // → messages_ready만 반환 }프로토콜 측정 대상 사용 API AMQP messages_readyqueue_declare (passive) HTTP messages(ready + unacked)Management API 3.4 해결책: HTTP 프로토콜 전환
triggers: - type: rabbitmq metadata: protocol: http host: http://admin:***@eco2-rabbitmq.rabbitmq.svc.cluster.local:15672 queueName: scan.vision vhostName: eco2 mode: QueueLength value: '10'HTTP Management API 응답 예시:
{ "name": "scan.vision", "messages": 22, "messages_ready": 0, "messages_unacknowledged": 22, "consumers": 1 }3.5 결과
$ kubectl describe hpa keda-hpa-scan-worker-scaledobject -n scan Metrics: "s0-rabbitmq-scan-vision" (target average value): 22 / 10 ✓ "s1-rabbitmq-scan-answer" (target average value): 16 / 10 ✓ "s2-rabbitmq-scan-rule" (target average value): 14 / 20커밋:
c6245e99 fix(keda): switch to HTTP protocol for RabbitMQ metrics
4. Issue #2: KEDA 내부 gRPC 통신 타임아웃
4.1 현상
HTTP 프로토콜 전환 후에도 메트릭 수집이 간헐적으로 실패:
$ kubectl logs -n keda deployment/keda-operator-metrics-apiserver --tail=20 E1226 10:15:00 provider.go:90] "timeout" "error"="timeout while waiting to establish gRPC connection to KEDA Metrics Service server" "server"="keda-operator.keda.svc.cluster.local:9666" W1226 10:15:02 logging.go:55] grpc: addrConn.createTransport failed to connect to {Addr: "10.103.57.99:9666"...} Err: dial tcp 10.103.57.99:9666: i/o timeout4.2 KEDA 아키텍처
KEDA는 2개의 주요 컴포넌트로 구성된다:
┌─────────────────────────────────────────────────────────────────┐ │ KEDA Namespace │ │ │ │ ┌────────────────────────┐ ┌────────────────────────┐ │ │ │ keda-operator │◄──gRPC──│ keda-metrics-apiserver │ │ │ │ │ :9666 │ │ │ │ │ - ScaledObject 감시 │ │ - External Metrics API │ │ │ │ - Scaler 실행 │ │ - HPA 메트릭 제공 │ │ │ │ - RabbitMQ HTTP 호출 │ │ │ │ │ └───────────┬────────────┘ └────────────────────────┘ │ │ │ │ │ │ HTTP :15672 │ │ ▼ │ └──────────────┼──────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ RabbitMQ Namespace │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ eco2-rabbitmq:15672 (Management API) │ │ │ │ eco2-rabbitmq:5672 (AMQP) │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘4.3 원인 분석
기존 NetworkPolicy 설정:
# allow-keda-egress.yaml (수정 전) spec: podSelector: {} policyTypes: - Egress egress: - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: rabbitmq ports: - port: 5672 # AMQP - port: 15672 # HTTP Management API누락된 설정: KEDA 내부 컴포넌트 간 gRPC 통신(port 9666)
4.4 해결책
# allow-keda-egress.yaml (수정 후) spec: podSelector: {} policyTypes: - Egress egress: # KEDA 내부 gRPC 통신 - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: keda ports: - protocol: TCP port: 9666 # gRPC metrics service - protocol: TCP port: 8080 # Prometheus metrics # RabbitMQ 접근 - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: rabbitmq ports: - protocol: TCP port: 5672 - protocol: TCP port: 156724.5 결과
$ kubectl get scaledobject -n scan -o wide NAME SCALETARGETKIND SCALETARGETNAME MIN MAX READY ACTIVE scan-worker-scaledobject apps/v1.Deployment scan-worker 1 5 True True ✓커밋:
81c0adde fix(network-policy): allow KEDA internal gRPC communication (9666)
5. Issue #3: ArgoCD selfHeal과 KEDA 스케일링 충돌
5.1 현상
메트릭 수집 성공 후 스케일업이 발생하지만, 즉시 스케일다운:
$ kubectl get events -n scan --sort-by=.lastTimestamp | grep -i scaled 28s Normal SuccessfulRescale New size: 3; reason: s0-rabbitmq-scan-vision above target 28s Normal ScalingReplicaSet Scaled up replica set scan-worker to 3 from 1 27s Normal ScalingReplicaSet Scaled down replica set scan-worker to 1 from 3스케일업 후 1초 만에 스케일다운 발생. HPA의
stabilizationWindowSeconds: 300설정이 무시됨.5.2 원인 분석
ArgoCD Application 설정 확인:
$ kubectl get application dev-scan-worker -n argocd -o jsonpath="{.spec.syncPolicy}" | jq { "automated": { "prune": true, "selfHeal": true } }ArgoCD 동작 시퀀스:
Timeline ──────────────────────────────────────────────────────────────────── T+0s KEDA HPA: replicas 1 → 3 (메트릭 기반 스케일업) T+1s ArgoCD: Drift 감지 (클러스터 상태 ≠ Git 상태) - 클러스터: replicas=3 - Git: replicas=1 (workloads/domains/scan-worker/base/deployment.yaml) T+1s ArgoCD: selfHeal=true → 자동 동기화 실행 T+2s Deployment: replicas 3 → 1 (Git 상태로 복원) ────────────────────────────────────────────────────────────────────5.3 해결책: ignoreDifferences 설정
ArgoCD ApplicationSet에
ignoreDifferences추가:# clusters/dev/apps/41-workers-appset.yaml apiVersion: argoproj.io/v1alpha1 kind: ApplicationSet spec: template: spec: syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=false ignoreDifferences: - group: apps kind: Deployment jsonPointers: - /spec/replicas5.4 주의사항
ApplicationSet은 기존 Application을 자동으로 업데이트하지 않는다. 기존 Application에 수동 패치 필요:
kubectl patch application dev-scan-worker -n argocd --type=json \ -p '[{ "op": "add", "path": "/spec/ignoreDifferences", "value": [{ "group": "apps", "kind": "Deployment", "jsonPointers": ["/spec/replicas"] }] }]'5.5 결과
$ kubectl get pods -n scan -l app=scan-worker NAME READY STATUS RESTARTS AGE scan-worker-69ff8ccc9d-djfps 2/2 Running 0 87s scan-worker-69ff8ccc9d-jnj9z 2/2 Running 0 34m scan-worker-69ff8ccc9d-mshx4 2/2 Running 0 87s $ kubectl exec -n rabbitmq eco2-rabbitmq-server-0 -- \ rabbitmqctl list_queues name consumers -p eco2 | grep scan scan.vision 3 scan.answer 3 scan.rule 3 scan.reward 33개의 Worker Pod가 안정적으로 유지되며, 각 큐에 3개의 Consumer가 연결됨.
커밋:
40824966 fix(argocd): ignore replicas field for KEDA/HPA management4f33e9ef fix(argocd): ignore replicas for worker deployments (KEDA/HPA)
6. 최종 구성
6.1 ScaledObject 최종 설정
apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: scan-worker-scaledobject namespace: scan spec: scaleTargetRef: name: scan-worker kind: Deployment minReplicaCount: 2 maxReplicaCount: 5 cooldownPeriod: 300 pollingInterval: 30 fallback: failureThreshold: 3 replicas: 2 advanced: horizontalPodAutoscalerConfig: behavior: scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 50 periodSeconds: 60 scaleUp: stabilizationWindowSeconds: 0 policies: - type: Pods value: 2 periodSeconds: 30 triggers: - type: rabbitmq metadata: protocol: http host: http://admin:***@eco2-rabbitmq.rabbitmq.svc.cluster.local:15672 queueName: scan.vision vhostName: eco2 mode: QueueLength value: '10'6.2 설정값 설명
파라미터 값 설명 minReplicaCount 2 최소 Worker 수 (baseline) maxReplicaCount 5 최대 Worker 수 cooldownPeriod 300s 스케일다운 전 대기 시간 pollingInterval 30s 메트릭 수집 주기 fallback.replicas 2 메트릭 수집 실패 시 유지할 replicas stabilizationWindowSeconds 300s 스케일다운 안정화 기간 value 10 Pod당 처리할 메시지 수 기준
7. 검증 결과
7.1 스케일링 동작 확인
부하 발생 시:
$ kubectl get hpa -n scan -o wide NAME REFERENCE TARGETS REPLICAS keda-hpa-scan-worker-scaledobject Deployment/scan-worker 22/10, 16/10, 14/20 37.2 개선 지표
항목 수정 전 수정 후 Worker replicas 1 (고정) 2-5 (동적) Consumer/queue 1 3+ 메트릭 수집 실패 정상 (실시간) 스케일업 유지 1초 만에 롤백 5분 안정화 HPA 트리거 CPU (비효율적) Queue Length (효율적)
8. 핵심 교훈
8.1 KEDA RabbitMQ Scaler
- AMQP 프로토콜은
messages_ready만 측정하므로 prefetch 환경에서는 HTTP 프로토콜 필수 mode: QueueLength는 프로토콜에 따라 측정 대상이 다름- TriggerAuthentication에서 vhost 설정 주의 (vhostName 파라미터)
8.2 NetworkPolicy
- KEDA는 operator와 metrics-apiserver 2개 컴포넌트로 구성
- 내부 gRPC 통신(port 9666)이 필수이며, 이에 대한 egress 정책 필요
- Egress 정책에서 자기 자신(namespace) 통신도 명시적으로 허용 필요
8.3 ArgoCD와 HPA/KEDA 공존
selfHeal: true는 HPA/KEDA의 replicas 변경을 되돌림ignoreDifferences로/spec/replicas필드를 동기화에서 제외- ApplicationSet 변경 시 기존 Application은 자동 업데이트되지 않음
8.4 디버깅 체크리스트
kubectl describe hpa- 메트릭 값 확인kubectl get scaledobject -o wide- ACTIVE 상태 확인kubectl logs -n keda deployment/keda-operator- Scaler 로그kubectl logs -n keda deployment/keda-operator-metrics-apiserver- 메트릭 서버 로그kubectl get events --sort-by=.lastTimestamp- 스케일링 이벤트 추적
9. 관련 리소스
9.1 변경 파일
파일 설명 workloads/scaling/base/scan-worker-scaledobject.yamlKEDA ScaledObject workloads/network-policies/base/allow-keda-egress.yamlNetworkPolicy clusters/dev/apps/41-workers-appset.yamlArgoCD ApplicationSet 9.2 관련 커밋
커밋 해시 설명 c6245e99AMQP → HTTP 프로토콜 전환 81c0addeKEDA 내부 gRPC 통신 허용 (NetworkPolicy) 227231f6KEDA 안정화 설정 (fallback, cooldown) 40824966ArgoCD ignoreDifferences (APIs) 4f33e9efArgoCD ignoreDifferences (Workers)
10. 참고 자료
- KEDA Documentation - RabbitMQ Scaler
- AWS Blog - Autoscaling Kubernetes workloads with KEDA
- 여기어때 기술블로그 - KEDA 도입기
- ArgoCD Documentation - Diffing Customization
- Kubernetes HPA Behavior
- https://hackmd.io/@arschles/kedahttp
Adding HTTP Scaling & Ingress Functionality to KEDA - HackMD
# Adding HTTP Scaling & Ingress Functionality to KEDA >This is a proposal to add HTTP functionalit
hackmd.io
'이코에코(Eco²) > Troubleshooting' 카테고리의 다른 글
Eventual Consistency 트러블슈팅: Character Rewards INSERT 멱등성 미보장 버그 픽스 (0) 2025.12.30 Streams & Scaling 트러블슈팅: SSE Gateway Sharding (0) 2025.12.27 Message Queue 트러블슈팅: Gevent Pool 마이그레이션 및 Stateless 체이닝의 한계 (0) 2025.12.25 Message Queue 트러블슈팅: Quorum Queue -> Classic Queue 마이그레이션 (0) 2025.12.24 Message Queue 트러블슈팅: RabbitMQ 구축 (0) 2025.12.22