☁️ 클라우드

KEDA

Kubernetes Event-driven Autoscaling

이벤트 기반 오토스케일링을 위한 Kubernetes 컴포넌트

📖 상세 설명

KEDA(Kubernetes Event-driven Autoscaling)는 외부 이벤트 소스를 기반으로 Kubernetes 워크로드를 스케일링하는 오픈소스 컴포넌트입니다. Microsoft와 Red Hat이 공동 개발했으며, CNCF 프로젝트로 Graduated 상태입니다. 기본 HPA가 CPU/메모리만 지원하는 것과 달리, KEDA는 60개 이상의 이벤트 소스(Scaler)를 지원합니다.

주요 기능으로 Scale to Zero가 있습니다. 이벤트가 없으면 Pod를 0개로 줄여 비용을 절감합니다. 메시지 큐에 메시지가 들어오면 Pod를 생성하고, 처리가 끝나면 다시 0으로 줄입니다. 이는 서버리스와 유사한 운영 방식을 Kubernetes에서 구현할 수 있게 합니다.

지원 이벤트 소스는 매우 다양합니다. 메시지 큐: Kafka, RabbitMQ, AWS SQS, Azure Service Bus, Google Pub/Sub. 데이터베이스: PostgreSQL, MySQL, MongoDB, Redis. 메트릭: Prometheus, Datadog, New Relic. 클라우드 서비스: AWS CloudWatch, Azure Monitor, GCP Stackdriver. 스케줄: Cron 기반 스케일링도 가능합니다.

아키텍처는 크게 세 가지 컴포넌트로 구성됩니다. keda-operator는 ScaledObject와 ScaledJob CRD를 감시하고 HPA를 생성/관리합니다. keda-metrics-apiserver는 외부 메트릭을 Kubernetes 메트릭 API로 노출합니다. Scaler는 각 이벤트 소스에서 메트릭을 수집합니다.

💻 코드 예제

# KEDA 설치 (Helm)
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda \
    --namespace keda \
    --create-namespace \
    --set prometheus.metricServer.enabled=true

# Kafka Consumer ScaledObject
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: kafka-consumer-scaler
  namespace: production
spec:
  scaleTargetRef:
    name: order-processor          # Deployment 이름
  pollingInterval: 15              # 메트릭 확인 주기 (초)
  cooldownPeriod: 300              # 스케일 다운 대기 시간
  minReplicaCount: 0               # Scale to Zero 활성화
  maxReplicaCount: 50              # 최대 Pod 수
  fallback:
    failureThreshold: 3            # 스케일러 오류 시
    replicas: 2                    # 폴백 replica 수
  triggers:
    - type: kafka
      metadata:
        bootstrapServers: kafka-cluster:9092
        consumerGroup: order-consumer-group
        topic: orders
        lagThreshold: "100"        # Consumer Lag 임계값
        offsetResetPolicy: earliest
      authenticationRef:
        name: kafka-credentials

---
# Kafka 인증 정보
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: kafka-credentials
  namespace: production
spec:
  secretTargetRef:
    - parameter: sasl
      name: kafka-secrets
      key: sasl-mechanism
    - parameter: username
      name: kafka-secrets
      key: username
    - parameter: password
      name: kafka-secrets
      key: password

---
# Consumer Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-processor
  namespace: production
spec:
  replicas: 0                      # KEDA가 관리하므로 0으로 시작
  selector:
    matchLabels:
      app: order-processor
  template:
    metadata:
      labels:
        app: order-processor
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: processor
          image: myapp/order-processor:v1.2.0
          resources:
            requests:
              cpu: "200m"
              memory: "256Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          env:
            - name: KAFKA_BROKERS
              value: "kafka-cluster:9092"
            - name: CONSUMER_GROUP
              value: "order-consumer-group"
# AWS SQS ScaledObject
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: sqs-processor-scaler
  namespace: production
spec:
  scaleTargetRef:
    name: sqs-processor
  minReplicaCount: 0
  maxReplicaCount: 30
  triggers:
    - type: aws-sqs-queue
      metadata:
        queueURL: https://sqs.ap-northeast-2.amazonaws.com/123456789/order-queue
        queueLength: "5"           # 메시지 5개당 Pod 1개
        awsRegion: ap-northeast-2
        scaleOnInFlight: "true"    # 처리 중 메시지도 카운트
      authenticationRef:
        name: aws-credentials

---
# IRSA (IAM Roles for Service Accounts) 인증
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: aws-credentials
  namespace: production
spec:
  podIdentity:
    provider: aws-eks              # EKS Pod Identity 사용

---
# ScaledJob: 일회성 작업에 적합
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
  name: image-processor-job
  namespace: batch
spec:
  jobTargetRef:
    parallelism: 1
    completions: 1
    backoffLimit: 3
    template:
      spec:
        restartPolicy: Never
        containers:
          - name: processor
            image: myapp/image-processor:v2.0
            resources:
              requests:
                cpu: "1"
                memory: "2Gi"
  pollingInterval: 10
  minReplicaCount: 0
  maxReplicaCount: 100
  successfulJobsHistoryLimit: 5
  failedJobsHistoryLimit: 5
  triggers:
    - type: aws-sqs-queue
      metadata:
        queueURL: https://sqs.ap-northeast-2.amazonaws.com/123456789/image-queue
        queueLength: "1"           # 메시지 1개당 Job 1개
        awsRegion: ap-northeast-2
      authenticationRef:
        name: aws-credentials

---
# 멀티 트리거 예제 (SQS + Cron)
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: hybrid-scaler
spec:
  scaleTargetRef:
    name: hybrid-processor
  minReplicaCount: 0
  maxReplicaCount: 20
  triggers:
    # SQS 기반 스케일링
    - type: aws-sqs-queue
      metadata:
        queueURL: https://sqs.ap-northeast-2.amazonaws.com/123456789/tasks
        queueLength: "10"
      authenticationRef:
        name: aws-credentials
    # 업무 시간 최소 유지 (9시~18시)
    - type: cron
      metadata:
        timezone: Asia/Seoul
        start: 0 9 * * 1-5         # 월-금 09:00
        end: 0 18 * * 1-5          # 월-금 18:00
        desiredReplicas: "3"       # 최소 3개 유지
# Prometheus 메트릭 기반 스케일링
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: prometheus-scaler
  namespace: production
spec:
  scaleTargetRef:
    name: api-gateway
  minReplicaCount: 2
  maxReplicaCount: 50
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring.svc:9090
        metricName: http_requests_per_second
        threshold: "100"           # Pod당 100 RPS
        query: |
          sum(rate(http_requests_total{
            namespace="production",
            service="api-gateway"
          }[2m]))
        ignoreNullValues: "false"

---
# HTTP Request 수 기반 (Prometheus + Ingress 메트릭)
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: ingress-based-scaler
spec:
  scaleTargetRef:
    name: web-frontend
  minReplicaCount: 3
  maxReplicaCount: 100
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleDown:
          stabilizationWindowSeconds: 300
          policies:
            - type: Percent
              value: 10
              periodSeconds: 60
        scaleUp:
          stabilizationWindowSeconds: 0
          policies:
            - type: Pods
              value: 4
              periodSeconds: 15
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring:9090
        threshold: "50"
        query: |
          sum(rate(nginx_ingress_controller_requests{
            namespace="production",
            ingress="web-frontend"
          }[1m])) /
          count(kube_pod_status_ready{
            namespace="production",
            pod=~"web-frontend.*"
          } == 1)

---
# Redis List 기반 스케일링
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: redis-worker-scaler
spec:
  scaleTargetRef:
    name: background-worker
  minReplicaCount: 0
  maxReplicaCount: 20
  triggers:
    - type: redis
      metadata:
        address: redis-master.cache:6379
        listName: job_queue
        listLength: "10"           # 10개 항목당 Pod 1개
        activationListLength: "1"  # 1개 이상이면 스케일업 시작
        databaseIndex: "0"
      authenticationRef:
        name: redis-auth

---
# 스케일링 상태 확인 명령어
# kubectl get scaledobject -A
# kubectl describe scaledobject kafka-consumer-scaler -n production
# kubectl get hpa -n production  # KEDA가 생성한 HPA 확인
# kubectl logs -n keda -l app=keda-operator  # KEDA 로그

💬 실무에서 이렇게 씁니다

아키텍처 설계 회의

PM: "주문 처리 시스템이 피크 시간에 Kafka lag이 계속 쌓여요. HPA를 쓰고 있는데 CPU 기반이라 반응이 느린 것 같아요."

DevOps: "KEDA로 전환하면 Kafka consumer lag을 직접 트리거로 사용할 수 있어요. lag이 100 이상이면 Pod를 늘리고, 0이 되면 Scale to Zero도 가능합니다. 야간에는 자동으로 0으로 줄어서 비용도 절감되고요. 현재 시스템에서 consumer group lag을 KEDA가 직접 모니터링하게 설정하면, CPU 기반보다 훨씬 빠르게 반응합니다."

면접 질문

면접관: "KEDA와 기본 HPA의 차이점을 설명해주시고, 언제 KEDA를 선택하시나요?"

지원자: "기본 HPA는 CPU, 메모리 같은 리소스 메트릭이나 커스텀 메트릭 서버가 필요합니다. KEDA는 60개 이상의 외부 이벤트 소스를 네이티브로 지원하고, Scale to Zero가 가능한 게 큰 차이점입니다. 메시지 큐 기반 워커나 배치 처리처럼 이벤트가 없을 때 리소스를 완전히 회수해야 하는 경우, 또는 외부 서비스의 메트릭을 기반으로 스케일링해야 할 때 KEDA를 선택합니다. 실제로 SQS consumer에 KEDA를 적용해서 야간 비용을 80% 줄인 경험이 있습니다."

트러블슈팅

개발자: "KEDA ScaledObject를 배포했는데 Pod가 스케일업이 안 돼요. HPA도 생성이 안 됐어요."

SRE: "먼저 kubectl describe scaledobject로 Status 확인해보세요. Scaler 연결 실패일 가능성이 높아요. TriggerAuthentication이 제대로 설정됐는지, Secret이 같은 네임스페이스에 있는지 확인하세요. KEDA operator 로그에서 'unable to get metrics' 에러가 있으면 인증 문제입니다. AWS SQS면 IRSA 설정 확인하고, Kafka면 SASL 인증 정보 확인해보세요."

⚠️ 주의사항

Scale to Zero에서 Cold Start 무시

minReplicaCount: 0 설정 시 첫 이벤트 처리에 지연이 발생합니다. Pod 시작, 컨테이너 초기화, 연결 수립까지 수십 초 걸릴 수 있어요. 실시간성이 중요하면 minReplicaCount를 1 이상으로 설정하거나 activationValue를 조정하세요.

HPA와 KEDA 동시 적용

같은 Deployment에 HPA와 KEDA ScaledObject를 모두 적용하면 충돌이 발생합니다. KEDA는 내부적으로 HPA를 생성하므로, 기존 HPA가 있다면 삭제하고 KEDA만 사용하세요. advanced.horizontalPodAutoscalerConfig로 HPA 동작을 커스터마이징할 수 있습니다.

Fallback 설정으로 장애 대비

외부 이벤트 소스 장애 시 스케일링이 멈출 수 있습니다. fallback 설정으로 스케일러 오류 시 유지할 replica 수를 지정하세요. failureThreshold와 replicas를 설정하면 메트릭 수집 실패 시에도 최소 가용성을 보장합니다.

🔗 관련 용어

📚 더 배우기