KEDA 도입 가이드 - HPA만으로는 부족했던 순간들
HPA(Horizontal Pod Autoscaler)를 쓰다 보면 어느 순간 벽에 부딪히는 순간이 온다. CPU가 안 튀는데 큐가 밀리거나, 새벽에는 파드가 0개여도 되는데 굳이 1개는 떠 있어야 한다거나, 아예 커스텀 메트릭 어댑터를 붙이자니 설정이 지저분해지는 그런 순간들.
우리 팀도 6개월쯤 CPU 기반 HPA로 버티다가 결국 KEDA로 넘어왔다. 이 글은 그 과정에서 정리한 실무 가이드다. 최근 0.35 코어 의존성으로 올라오면서 events.k8s.io API로 이벤트를 남기는 방식이 바뀌었는데, 이런 세부 변화들도 같이 짚고 넘어가겠다.
HPA로 왜 부족한가
HPA는 리소스(CPU/메모리)나 커스텀 메트릭을 기반으로 파드 수를 조정한다. 문제는 이게 "지금 파드가 얼마나 힘든가"를 기반으로 한다는 점이다. 실제 워크로드는 "얼마나 밀렸는가"인 경우가 많다.
우리 팀의 대표적인 케이스는 이랬다.
- SQS 컨슈머: 큐에 메시지가 5만 건 쌓여도 파드 CPU는 30%. 왜냐면 파드 하나가 처리할 수 있는 최대 스루풋에 이미 걸려 있어서, CPU가 튀지 않고 그냥 열심히 일만 한다. HPA는 스케일 아웃을 안 한다.
- 배치성 Cron 워커: 하루에 20분만 일하는 프로세스. 24시간 파드 1개를 띄워놓는 게 아까웠다.
- Kafka 컨슈머 그룹: 파티션 lag이 튀면 스케일 아웃하고 싶은데, HPA로 하려면 prometheus-adapter를 붙여야 하고, 규칙이 어댑터 ConfigMap과 HPA YAML 두 군데에 분산된다.
KEDA는 이걸 ScaledObject 하나로 처리한다. 그리고 내부적으로는 KEDA가 HPA 오브젝트를 생성해 대신 관리하는 구조라, 결국 HPA 위에 얹은 얇은 레이어에 가깝다.
설치와 기본 구조
Helm으로 설치가 제일 편하다.
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --create-namespace
설치되면 세 개의 파드가 뜬다: keda-operator, keda-operator-metrics-apiserver, keda-admission-webhooks. 여기서 metrics-apiserver는 커스텀 external metrics API를 노출해서 HPA가 그걸 긁어가는 구조다.
핵심 CRD는 두 개다.
ScaledObject: Deployment/StatefulSet에 붙이는 스케일러ScaledJob: Job에 붙이는 스케일러 (배치용)
첫 번째 예제: SQS 기반 스케일링
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-worker
namespace: default
spec:
scaleTargetRef:
name: order-worker
minReplicaCount: 0
maxReplicaCount: 30
pollingInterval: 15
cooldownPeriod: 120
triggers:
- type: aws-sqs-queue
authenticationRef:
name: aws-sqs-auth
metadata:
queueURL: https://sqs.ap-northeast-2.amazonaws.com/1234/order-queue
queueLength: "50"
awsRegion: "ap-northeast-2"
identityOwner: operator
여기서 queueLength: "50"은 "파드 하나가 담당할 메시지 수"다. 즉 큐에 500개 쌓이면 파드는 10개까지 뜬다. 이 값이 스루풋과 지연 목표의 트레이드오프인데, 처음엔 안전하게 크게 잡고 점진적으로 낮추는 걸 추천한다. 너무 작게 잡으면 파드가 미친듯이 뜨락내리락 한다.
minReplicaCount: 0이 KEDA의 킬러 피처다. HPA는 최소 1이 마지노선인데, KEDA는 트리거가 idle하면 파드를 0으로 만든다. 새벽에 트래픽 없는 워커가 이걸로 사라진다.
인증은 TriggerAuthentication으로
큐 URL은 ScaledObject에 쓰지만, IAM 자격증명은 별도로 관리한다.
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: aws-sqs-auth
spec:
podIdentity:
provider: aws
IRSA를 이미 쓰고 있다면 provider: aws만 넣으면 워커 파드의 서비스어카운트 IAM 롤을 그대로 승계한다. 아주 깔끔하다. 예전엔 KEDA 오퍼레이터에 별도 IAM 롤을 붙여야 했는데, identityOwner: workload로 지정하면 워크로드 자체 롤을 쓴다.
두 번째 예제: Kafka lag 기반 스케일링
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: event-consumer
spec:
scaleTargetRef:
name: event-consumer
minReplicaCount: 2
maxReplicaCount: 20
triggers:
- type: kafka
metadata:
bootstrapServers: kafka-broker.kafka:9092
consumerGroup: event-consumer-group
topic: user-events
lagThreshold: "1000"
offsetResetPolicy: latest
activationLagThreshold: "500"
Kafka에서 조심할 게 있다. 파티션 수보다 파드 수를 많이 늘려도 소용없다. 컨슈머 그룹 특성상 파티션 수가 병렬성의 상한이다. KEDA는 이걸 알고 있어서, 최근 버전에서는 scaleToZeroOnInvalidOffset 같은 옵션도 제공한다. 파티션 재할당 중 offset이 무효화되는 순간에도 안전하게 동작하도록.
activationLagThreshold는 "0에서 1로 뜨는 임계값"이고 lagThreshold는 "1 이후 파드당 담당량"이다. 이 둘을 헷갈리면 스케일 아웃이 안 되거나 항상 파드가 떠 있는 사태가 벌어진다. 우리도 여기서 한 번 실수했다.
세 번째 예제: Cron으로 시간대 기반 스케일링
triggers:
- type: cron
metadata:
timezone: Asia/Seoul
start: "0 9 * * 1-5"
end: "0 19 * * 1-5"
desiredReplicas: "5"
업무 시간에만 특정 개수를 보장하고 싶을 때 편하다. 다른 트리거와 조합하면 "업무 시간에는 최소 5개, 큐가 밀리면 더 늘어남"도 가능하다. 여러 트리거를 동시에 넣으면 KEDA는 각 트리거가 계산한 desired replica 중 최댓값을 취한다.
도입 후 마주친 함정들
첫째, Prometheus 트리거의 쿼리 부하. 파드마다 쿼리하는 게 아니라 오퍼레이터 하나가 쿼리하지만, pollingInterval을 너무 짧게 잡으면 프로메테우스가 힘들어한다. 우리는 30초로 시작했다가 15초로 조정했다. 5초는 하지 마라.
둘째, cooldownPeriod와 HPA behavior의 상호작용. KEDA는 HPA를 생성하는데, 이 HPA의 scaleDown.stabilizationWindowSeconds가 별개로 존재한다. cooldownPeriod는 "0으로 내리기까지의 시간"이고, HPA behavior는 "일반 스케일다운 창"이다. 둘 다 신경써야 한다.
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
셋째, minReplicaCount: 0의 초기 지연. 0에서 1로 파드가 뜨는 데 시간이 걸린다. 이미지 풀, 부팅, 헬스체크까지 다 지나야 큐를 소비하기 시작하니, 지연에 민감한 워크로드는 minReplicaCount: 1 로 두는 게 낫다. 진짜 야간에 완전히 잠재워도 되는 배치성 워크로드에서만 0으로 내려라.
넷째, 관측성. KEDA 자체가 Prometheus 메트릭을 낸다. keda_scaler_active, keda_scaler_errors, keda_scaled_object_paused 같은 지표를 대시보드에 꽂아둬야 뭔가 이상할 때 알 수 있다. 특히 errors가 슬금슬금 올라오는 케이스가 있는데, 대개 IAM 권한이 만료됐거나 큐가 삭제된 경우다.
마이그레이션 팁
기존 HPA에서 KEDA로 넘어올 때, HPA 오브젝트를 먼저 지우고 ScaledObject를 만들어야 한다. 두 개가 같은 Deployment를 대상으로 하면 서로 충돌한다. KEDA v2.10 이후로는 admission webhook이 이걸 잡아주지만, 그전 버전이면 조용히 이상하게 동작할 수 있다.
한 번에 다 옮기지 말고, 워크로드 하나씩 옮기면서 24시간 관찰하는 걸 추천한다. 특히 새벽 트래픽 저점에서 파드가 어떻게 움직이는지 봐야 한다.
정리하며
KEDA는 HPA를 대체한다기보다는 "HPA를 훨씬 편하게 쓰는 레이어"에 가깝다. 60개가 넘는 스케일러를 표준 방식으로 제공한다는 게 진짜 강점이다. 팀 안에서 "이 워크로드는 이 지표로 스케일해요"가 일관된 YAML로 표현되는 것만으로도 운영 부담이 훨씬 줄었다.
다음에는 KEDA의 HTTP Add-on을 다뤄볼까 한다. HTTP 트래픽으로 서빙 워크로드까지 스케일 투 제로로 만드는 실험을 진행 중이다. 아직 검증 단계라 조금 더 굴려보고 정리하겠다.
혹시 KEDA 다른 트리거로 재밌게 쓰는 사례 있으시면 댓글로 공유 부탁드립니다.
추가 리소스
태그: KEDA, Kubernetes, 오토스케일링, HPA, DevOps, autoscaling