EKS 노드 오토스케일링 — Karpenter vs Cluster Autoscaler

Kubernetes4분조회 –

EKS 에 파드를 띄우려면 그 파드가 올라갈 노드가 있어야 하고, 노드는 결국 EC2 인스턴스입니다. 트래픽이 늘어 파드를 늘리면 자리가 모자라 노드도 같이 늘어야 하고, 한가해지면 빈 노드를 내려야 요금이 안 나갑니다. 이걸 사람이 보고 있을 수는 없으니 노드를 자동으로 늘리고 줄이는 컴포넌트를 하나 붙입니다.

노드가 모자라면 파드는 Pending 에 멈춥니다. 스케줄러가 조건에 맞는 노드를 못 찾은 상태라 노드가 하나 더 생기기 전까지는 아무 일도 일어나지 않습니다. 오토스케일러가 보는 신호가 이 Pending 입니다.

여기에 Cluster Autoscaler 와 Karpenter 가 같이 거론됩니다. 둘 다 파드가 Pending 이면 노드를 늘린다는 목표는 같고, 갈리는 건 무엇을 늘리느냐입니다. 하나는 미리 만들어 둔 그룹을 늘리고, 다른 하나는 그때그때 EC2 를 골라서 띄웁니다.

Cluster Autoscaler — Node Group 증설

Cluster Autoscaler(CA)는 Node Group 단위로 스케일합니다. 인스턴스 타입별로 그룹을 미리 정의해두고 Pod 가 Pending 이면 “어느 그룹을 늘릴까” 를 판단합니다.

미리 정의된 Node Group:
  group-A: m5.large × 2~10
  group-B: c5.xlarge × 1~5

Pod Pending 감지
  → group-A로 가능? → 노드 +1

인스턴스 타입이 그룹마다 미리 고정돼 있으니 2 CPU 만 필요한 Pod 때문에 4 CPU 노드가 뜨면 나머지가 그대로 놀고, Node Group 을 추가하거나 바꾸려면 보통 Terraform 을 고쳐야 하는 수동 작업이 됩니다. Spot 을 다양하게 섞기도 어려운데, Node Group 별로 타입이 묶여 있어서 고를 수 있는 폭이 그룹 정의만큼으로 제한되기 때문입니다.

Karpenter — Pod 요구사항 기반 EC2 직접 선택

Karpenter 는 Node Group 이 아예 없고, Pod 가 Pending 이면 그 Pod 에 필요한 CPU·메모리를 보고 EC2 Fleet API 로 최적 인스턴스를 그 자리에서 고릅니다.

Pod Pending 감지
  → 이 Pod에 필요한 CPU/메모리는?
  → EC2에서 그에 맞는 인스턴스를 직접 선택
  → 노드 생성

설정은 NodePool CRD 로 하고, “어떤 계열, 몇 세대 이상, on-demand/spot” 같은 조건만 주면 그 범위 안에서 Karpenter 가 알아서 고릅니다.

kind: NodePool
spec:
  template:
    spec:
      requirements:
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]      # compute/general/memory 계열
        - key: karpenter.k8s.aws/instance-generation
          operator: Gt
          values: ["2"]                # 3세대 이상만
      expireAfter: 720h                # 노드 30일마다 교체
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 1m

인스턴스 타입을 하나로 고정하는 게 아니라 “이 조건에 맞는 것 아무거나” 라고 범위를 주는 거라, Spot 을 수십 종류 자동으로 섞을 수 있습니다.

consolidation — CA에 없는 것

여기까지는 노드를 늘리는 얘기라 둘이 방식만 다른 것처럼 보입니다. 그런데 Karpenter 에는 CA 에 없는 기능이 하나 있습니다 — 늘린 노드를 다시 합치는 겁니다. consolidationPolicy: WhenEmptyOrUnderutilized 를 켜면 이렇게 동작합니다.

노드 A (4 CPU): 1 CPU Pod만 실행 중
노드 B (4 CPU): 비어 있음

Karpenter: "A의 Pod를 더 작은 노드로 옮기고 A를 종료할 수 있나?"
  → 가능하면 Pod 이동 → 노드 A 삭제

쓰다 남은 노드를 더 작은 것으로 갈아 비용을 줄이고, consolidateAfter: 1m 은 노드에 파드가 붙거나 빠지지 않은 채 1분이 지나야 통합 후보로 본다는 뜻이라 뭔가 움직이면 타이머가 다시 시작합니다.

CA 도 사용률이 낮은 노드는 파드를 다른 데로 옮기고 내립니다. 기준은 requests 합계가 50% 미만으로 10분간 유지되는 것입니다. 못 하는 건 그 노드를 더 작은 타입으로 바꿔 끼우는 쪽이라, 크기가 안 맞는 자리가 계속 남습니다.

항목별 차이 — 선택 단위·속도·통합

항목 Cluster Autoscaler Karpenter
노드 선택 단위 Node Group 개별 EC2
인스턴스 타입 미리 고정 실시간 최적 선택
Spot 다양성 그룹별 제한 수십 종 자동 혼용
스케일 속도 수 분 수십 초
노드 통합 없음 consolidation
설정 방식 Node Group (Terraform) NodePool CRD (GitOps)

미리 만들어 둔 그룹 안에서 대수를 조절하느냐 그때그때 맞는 인스턴스를 새로 띄우느냐에서 갈리고, 나머지 차이는 대부분 여기서 따라옵니다.

expireAfter — 노드 주기 교체

expireAfter: 720h 는 노드를 30일마다 갈아치웁니다. 새 AMI 의 보안 패치가 자동으로 따라오고 오래 떠 있던 노드에 쌓인 변경도 같이 사라지지만, CA 를 쓰면 따로 절차를 만들어 돌려야 하는 일이 Karpenter 에서는 NodePool 에 한 줄입니다.

인스턴스 타입을 누가 정하느냐

표만 놓고 보면 Karpenter 로 갈아타면 되겠다고 생각하기 쉬운데, 두 도구의 차이는 결국 인스턴스 타입을 사람이 미리 정해 두느냐 컨트롤러가 그때 고르게 두느냐로 좁혀집니다. 워크로드 종류가 몇 안 되고 노드 구성이 거의 안 바뀌는 클러스터면 Node Group 을 미리 잡아 두는 쪽이 예측 가능해서 편하고, 파드 요구사항이 제각각이거나 Spot 을 넓게 쓰고 싶으면 Karpenter 쪽이 맞습니다.

이미 CA 를 쓰고 있다면 노드가 얼마나 놀고 있는지부터 보면 됩니다. 요청한 만큼과 실제 붙은 파드 사이 간격이 계속 벌어져 있으면 consolidation 하나만으로도 옮길 이유가 됩니다.

참고

  1. 불러오는 중