본문으로 건너뛰기

험난한 Kubernetes 전환기 (5) - KEDA 적용하기

· 약 6분
Austin Lee
DevOps Engineer @ Allganize

오랜 기간 동안 진행한 Kubernetes 전환 과정 이후에도, 팀 내에서 더 나은 환경을 제공하기 위해 여러 계획을 진행하고 있습니다. 최근에는 이벤트와 트래픽에 따른 자원 관리를 본격적으로 시작할 필요성이 생겼고, 이를 위해 KEDA를 도입하게 되었습니다.
이를 구성한 과정과 느낀 점을 간단히 정리해 보았습니다.

KEDA

KEDA는 다양한 이벤트를 기반으로 컨테이너를 동적으로 확장하거나 축소할 수 있도록 지원하는 Kubernetes 컴포넌트로, CNCF Graduated Project이기도 합니다.
예를 들어, 메시지 큐나 DB에 쌓인 데이터의 양을 보고 트래픽을 조정하거나, CronJob처럼 특정 시간대를 기준으로 Pod 수를 조절할 수 있습니다.

KEDA Diagram

KEDA의 원리를 간단히 설명하면, ScaledObject라는 리소스를 정의하여 HPA(Horizontal Pod Autoscaler)의 상태를 지속적으로 업데이트하는 것입니다. 외부 이벤트가 발생하면 ScaledObject가 Scaling 규칙을 변경하고, 이를 HPA에 전달하여 Pod 수를 조절하도록 합니다.
실제로 ScaledObject를 생성해 보면 자동으로 HPA가 생성되는 것을 확인할 수 있고, Pod scaling 이벤트 자체는 HPA에 위임됩니다.

기존의 HPA는 보통 CPU 또는 메모리 사용률에 따라 Pod의 수를 조절하는 방식이었습니다. KEDA는 이를 확장하여 이벤트 소스를 중심으로 더 유연하고 확장된 조건에서의 리소스 조정을 가능하도록 해 줍니다.

저희 회사의 경우에는 문서 처리나 각종 배치 처리를 RabbitMQ 이벤트를 보고 동적으로 처리하고자 하는 요구가 있었습니다. 기존에는 코드로 이 로직을 직접 구현했었는데, Kubernetes 환경으로 전환하면서 실제 KEDA를 사용하도록 변경이 필요했습니다.
또한 B2B 서비스가 메인이기 때문에 회사 서비스의 트래픽은 특정 시간대에 몰리는 경향이 있었고, 이에 따른 트래픽 대응과 리소스 효율화에도 KEDA가 좋은 해결책이 될 수 있었습니다.

RabbitMQ 연동하기

저희가 KEDA를 적용하고자 한 앱들의 이벤트 소스는 RabbitMQ였고, 따라서 이를 집중적으로 구현했습니다. RabbitMQ 이벤트 소스를 어떻게 연동하는지는 이 문서에 사례별로 자세히 나와 있습니다.

기본 구현

KEDA 설치는 Helm chart를 통해 쉽게 설치할 수 있습니다.
KEDA를 사용할 모든 클러스터에 설치해야 합니다.

KEDA를 구현할 때는 다음 리소스들을 구현해야 합니다.

  • ScaledObject: 이벤트 소스를 모니터링하고, HPA가 작동하기 위한 Scaling 규칙을 정의합니다. ScaledObject가 정의되면 자동으로 HPA가 생성됩니다.
  • TriggerAuthentication: 이벤트 소스에 대한 인증 정보를 저장합니다. 여기서는 RabbitMQ의 URL이나 인증 정보가 해당됩니다.
  • Secret: 당연히 리소스에 직접 비밀 정보를 입력하는 것은 좋지 않습니다. 따라서 이를 Secret으로 관리합니다.
    • 저희는 External Secret Operator를 사용하여 외부 Secret Manager에서 비밀 정보를 가져오도록 설정했습니다.

관련된 구현체는 공식 문서에 매우 잘 설명되어 있습니다. 저는 이 리소스들을 기존 서비스 Helm chart 템플릿에 통합하여 values.yaml에서 쉽게 적용할 수 있도록 했습니다.

특정 큐 지정하기

같은 RabbitMQ를 사용하더라도 앱마다 조금씩 요구사항이 달랐습니다.
우선 특정 스케줄링 작업을 처리하는 Batch 앱의 경우 정해진 큐 2~3개만 바라보면 되었습니다. 이 경우에는 triggers 항목에 모니터링할 큐 이름을 직접 명시하면 됩니다.

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: batch-worker-scaledobject
namespace: default
spec:
scaleTargetRef:
name: batch-worker-deployment
triggers:
- type: rabbitmq
metadata:
protocol: auto
queueName: foo
mode: QueueLength
value: "50"
activationValue: "1"
- type: rabbitmq
metadata:
protocol: auto
queueName: bar
mode: QueueLength
value: "50"
activationValue: "1"

정규식 필터 사용하기

문서 파싱을 담당하는 앱들은 예를 들면 document- 라는 Prefix로 시작하는 모든 큐를 사용하고 있었습니다.
KEDA에서는 정규식을 이용한 큐 필터링 기능을 제공하기 때문에, 요구사항을 만족시킬 수 있었습니다. 예를 들어, 다음과 같이 구현하면 됩니다.

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: document-worker-scaledobject
namespace: default
spec:
scaleTargetRef:
name: document-worker-deployment
triggers:
- type: rabbitmq
metadata:
protocol: http # Required for regex usage
useRegex: "true" # Enable regex pattern matching
queueName: "document-.*" # Or other regex pattern
mode: QueueLength
value: "5"
activationValue: "1"

다만, 이 기능을 사용하기 위해서는 RabbitMQ Management API를 통한 상태 조회 등이 필요하기 때문에 반드시 프로토콜을 http로 설정해야 합니다.

피크 타임 트래픽 대응하기

앞의 2가지 케이스는 계획된 내용이었지만, 그 외에도 KEDA를 활용할 일이 생겼습니다.

최근에 주 사용 시간대(평일 오전 8시 ~ 오후 7시)에 서비스 응답 속도가 느려진다는 불만이 제기되었습니다.
물론 애플리케이션 로직을 개선하는 것도 필요하지만 당장은 진행하기 어려웠고 인프라 레벨에서 선제 대응이 필요했습니다.

처음에는 CronJob 등으로 명시적 Scaling을 할지 고민했지만, 조사하면서 KEDA의 Cron Scaler를 사용해 특정 시간대에 Pod 수를 유지하는 방법을 찾게 되었습니다.

Cron Scaler를 사용하면 정해진 시간대에 원하는 Pod 수를 보장하도록 설정할 수 있습니다. 저희는 피크 타임 이전에 리소스가 올라오는 시간까지 감안하여, 전후 15분 정도 여유를 두었습니다. 또한, 기존의 HPA처럼 CPU 사용률 기준 Scaling도 같이 적용할 수 있다고 하여 해당 옵션도 추가했습니다.

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: web-cron-scaledobject
namespace: default
spec:
scaleTargetRef:
name: web-deployment
triggers:
- type: cron
metadata:
timezone: Asia/Tokyo
start: "45 7 * * 1-5"
end: "15 19 * * 1-5"
desiredReplicas: "30"
- type: cpu
metadata:
type: Utilization
value: "85"

여기서 desiredReplicas의 수치는 계속 응답 속도를 보면서 조정하였습니다. 이 경우 2~3번 정도의 조정을 거쳐 최적의 수치를 찾을 수 있었습니다.

사용하는 리소스를 더 늘리는 행위였기 때문에 추가적인 비용 발생은 어쩔 수 없었지만, 확실히 성능이 개선되었다는 피드백을 받을 수 있었습니다.

Cron Scaler에 대한 추가 정보는 이 문서를 참고해 주세요.

주의사항

  1. Argo CD로 KEDA를 설치할 때 몇 가지 이슈가 있었습니다.
    • CRD 문제로 Server-side Apply를 사용해야 합니다.
    • 일부 OutOfSync 상태 해결을 위해 ignoreDifferences 처리가 필요했습니다.
  2. KEDA를 활성화하기 전에 반드시 HPA를 비활성화해야 합니다.
    • KEDA가 자체적으로 HPA를 관리하기 때문에 예기치 않은 충돌이나 오류가 발생할 수 있습니다.
  3. 이는 HPA와 동일한데, KEDA를 활성화했다면 Deployment의 replicas 값을 비워 두어야 합니다.
    • HPA가 조정하려는 값과 replicas 값이 충돌할 수 있기 때문입니다.
    • 이를 가장 쉽게 확인하려면 replicas를 설정해 두고, KEDA에서 Zero scaling, 즉 최소를 0으로 설정해 보면 됩니다.
      이렇게 하면 KEDA가 계속 Pod를 종료시키고 Deployment는 계속 복구시키는 무한 루프를 확인할 수 있습니다.

마치며

이렇게 KEDA를 도입하여 이벤트 기반의 동적 스케일링 환경을 구축하고, 추가로 서비스 안정성도 해결할 수 있었습니다.
KEDA는 위에서 설명한 2개의 기능 외에도 많은 기능과 이벤트 소스를 지원하기 때문에 더욱 다양한 상황에서 활용할 수 있습니다. 또한, 추후에는 Cold start를 고려하지 않아도 되는 앱에 대해 Scale-to-zero 개념을 적용하여 더욱 효율적인 자원 관리가 가능할 것이라고 기대하고 있습니다.

개인적으로 한 번 써 보고 싶었던 도구라 만족도가 살짝 더 높았던 것은 덤입니다 😆

다만 앞으로도 Scaling 옵션을 계속 조정해 나가며 최적의 설정을 찾고, 또한 KEDA Scaling 이벤트를 수집하여 모니터링하는 작업도 필요하다고 생각하고 있습니다.
(현시점 가장 확실한 것은 Deployment의 이벤트를 수집하는 것이라고 보고 있습니다.)

앞으로도 KEDA 외에 적용해 보고 싶은 도구가 많습니다. 안정성을 생각하면서 점점 발전된 환경을 구축해 나가려고 합니다.

참고 자료