쿠버네티스 오토스케일링 계층 — HPA · KEDA · Karpenter

Kubernetes24분조회 –

트래픽이 몰릴 때마다 사람이 kubectl scale 을 치고 있을 수는 없으니 부하에 맞춰 알아서 늘고 한가해지면 줄어야 합니다. 그런데 쿠버네티스에서 이걸 붙이려고 찾아보면 HPA 와 KEDA, Karpenter 가 같이 나옵니다.

셋 다 뭔가를 자동으로 늘려 준다고 소개돼 있어서 문서만 봐서는 HPA 를 쓸지 KEDA 를 쓸지 안 갈립니다. Karpenter 는 그 둘의 대안인지 같이 쓰는 건지도 모르겠고, 셋을 한꺼번에 켜 두면 서로 반대로 움직여서 파드가 늘었다 줄었다 하지 않을지도 걸렸습니다.

셋은 경쟁 관계가 아니라 층이 다릅니다. HPA 와 KEDA 가 파드 개수를 조절하고 Karpenter 는 그 파드가 올라갈 노드를 늘리니, 하나가 파드를 늘리면 다음이 노드를 띄우는 식으로 이어집니다.

Pod를 늘리는 층 — HPA와 KEDA

HPA 는 CPU 나 메모리 사용률을 보고 Pod replicas 를 조절합니다. 그 사용률을 읽어 오려면 metrics-server 가 클러스터에 깔려 있어야 합니다. KEDA 도 같은 일을 하지만 볼 수 있는 대상이 훨씬 넓습니다. SQS 큐 깊이, Kafka lag, Prometheus 쿼리 결과처럼 CPU·메모리가 아닌 값을 기준으로 Pod 를 늘립니다.

설정을 나란히 놓으면 차이가 한눈에 들어옵니다.

# HPA — CPU 사용률을 보고 늘린다
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
  scaleTargetRef: { kind: Deployment, name: <앱> }
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target: { type: Utilization, averageUtilization: 70 }
# KEDA — 큐에 쌓인 개수를 보고 늘린다
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
spec:
  scaleTargetRef: { name: <앱> }
  minReplicaCount: 0          # 큐가 비면 0 까지 내린다
  maxReplicaCount: 10
  triggers:
    - type: aws-sqs-queue
      metadata:
        queueURL: <큐 URL>
        queueLength: "5"      # 메시지 5개당 파드 1개

갈리는 지점은 metrics 와 triggers, 무엇을 보고 늘리느냐 하나입니다. 큐가 쌓이면 워커를 늘리고 다 처리하면 0 으로 줄이는 배치성 워크로드라면 CPU 사용률로는 큐가 쌓였다는 걸 표현할 방법이 없으니, 이런 자리에 KEDA 와 큐 조합이 맞습니다. HPA 는 기본 설정으로는 minReplicas 를 0 으로 못 내리는 것도 차이입니다.

노드를 늘리는 층 — Karpenter

Pod 를 늘려 봐야 노드에 자리가 없으면 그대로 Pending 이고, 자리를 만드는 건 Karpenter 의 몫입니다.

트래픽 급증KEDA — Pod 층큐 깊이 보고 replicas 증가새 Pod가 PendingKarpenter — 노드 층requests 합산 → EC2 provisioningPod 스케줄 완료노드 부족
Karpenter 는 Pending 을 보고 나서 노드를 붙임

KEDA 가 하는 건 “Pod 를 더 만들어라” 까지고 그 Pod 가 뜰 자리(노드)는 Karpenter 가 만드는 것이라, Pod 층과 노드 층 사이에서는 둘이 부딪힐 일이 없습니다.

부딪히는 건 같은 Pod 층에 둘을 같이 올렸을 때고, KEDA 는 ScaledObject 마다 HPA 를 자기가 만들어 관리하기 때문에 이미 HPA 가 붙은 Deployment 에 ScaledObject 를 만들면 already managed by the hpa 로 거부합니다. 그래도 넘기려면 소유권을 넘기는 어노테이션을 따로 붙여야 하니, 같은 층에서는 둘 중 하나만 쓰는 게 맞습니다.

Pod 층에서는 KEDA 나 HPA 중 하나만 쓰고, 노드 층은 Karpenter 가 받습니다.

scale-to-zero의 cold start

KEDA 는 Pod 를 0 까지 줄일 수 있습니다(scale-to-zero). 비용은 좋지만 큐가 비었다가 첫 요청이 올 때 Pod 가 없어서 지연(cold start)이 생깁니다.

완화하는 방법은 몇 가지고, minReplicaCount: 1 로 최소 하나는 항상 띄워 두거나 cooldownPeriod 를 조정해 너무 빨리 0 으로 안 내려가게 하거나, ScaledJob 으로 요청이 있을 때만 Pod 를 Job 형태로 만들게 할 수 있습니다.

0 까지 줄여 비용을 아끼는 것과 첫 요청 지연은 맞바꾸는 관계라 요청이 얼마나 자주 오는지와 그 지연을 얼마나 견딜 수 있는지를 보고 값을 정합니다.

층이 나뉘어 있으면 볼 곳도 나뉨

스케일이 안 되는 것 같을 때는 파드가 안 늘어나는 건지 노드가 안 늘어나는 건지부터 가릅니다. replicas 가 그대로면 HPA 나 KEDA 쪽이고 replicas 는 늘었는데 새 파드가 Pending 에 쌓여 있으면 노드 층이라 Karpenter 를 봅니다. 셋이 이름만 보면 다 스케일링 같아서 어디를 손댈지 헷갈리지만 이 한 줄로 갈립니다.

참고

  1. 불러오는 중