Overview
EKS 클러스터는 파드(Pod)가 생성되고 삭제될 때마다 노드를 확장하고 축소해야 한다.
펜딩(Pending) 상태의 파드가 생기면 노드를 추가하고, 유휴 노드가 발생하면 이를 제거하는 작업이다. AWS 환경에서 이 문제를 해결하는 대표적인 도구는 두 가지 — Cluster Autoscaler(CA)와 Karpenter가 있다.
둘 다 목표는 같다. 필요한 만큼만 노드를 배치하고, 불필요해지면 줄여 비용과 자원을 최적화하는 것이다. 하지만 그 일을 처리하는 방식은 근본부터 다르다. 핵심을 한 줄로 요약하면 다음과 같다.
- Cluster Autoscaler(2017년경, Kubernetes SIG-Autoscaling): AWS의 ASG(Auto Scaling Group) 위에서 동작한다. 직접 EC2를 생성하지 않고, ASG의 Desired Capacity(원하는 수량)를 조정하여 ASG가 대신 인스턴스를 기동하고 종료하도록 유도한다.
- Karpenter(2021년 AWS 출시 / 2024년 v1.0 전환): ASG 자체를 대체한다. EC2 Fleet API(CreateFleet / RunInstances)를 직접 호출하여 파드의 요구사항에 딱 맞는 노드를 그 자리에서 즉시 프로비저닝한다. 노드 그룹도, ASG도 필요하지 않는다.
본 글에서는 두 방식이 노드를 확장(Scale-out)하고 축소(Scale-in)하는 흐름의 차이, 설정 방식의 차이, 그리고 실제 운영 환경에서 겪었던 노드 종료 장애 사례를 통해 둘의 결정적인 차이점을 정리한다. 결론부터 말하면, 둘을 가르는 단 하나의 기준은 "노드 하위에 ASG가 존재하는가"이다. 이 한 가지 차이가 아키텍처와 운영 방식의 거의 모든 차이를 만들어낸다.

한눈에 보는 두 가지 흐름
핵심은 "노드를 누가, 무엇을 거쳐 생성하고 삭제하는가"이다. 오토스케일링의 두 축인 확장과 축소 흐름을 나란히 비교하면 그 차이가 분명하게 드러난다.
[ 스케일 업 / 확장 흐름 ]
1. Cluster Autoscaler — ASG Desired 조정 (간접 방식)
Pending Pod ──▶ CA ──▶ ASG Desired += 1
│
▼
ASG가 EC2 기동 (미리 정해진 인스턴스 타입)
│
▼
노드 클러스터 합류 ──▶ 파드 스케줄링
2. Karpenter — EC2 Fleet 직접 호출 (직접 방식)
Pending Pod ──▶ Karpenter
│ (파드의 리소스 요청 / 어피니티 / 툴러레이션 확인)
▼
EC2 Fleet (CreateFleet / RunInstances) 호출
│ (파드 스펙에 최적화된 타입을 그 자리에서 동적 선택)
▼
노드 클러스터 합류 ──▶ 파드 스케줄링
[ 스케일 다운 / 노드 종료 흐름 ]
1. Cluster Autoscaler — ASG 라이프사이클을 경유
CA: 저활용 노드 판단 ──▶ Cordon & Drain ──▶ ASG Desired -= 1
│
▼
ASG Terminate 라이프사이클 훅
(타임아웃 설정 시간 내에 drain이 완료되어야 함)
│
드레인 중단/지연 시 ──▶ 종료되지 못하는 좀비 노드 발생
2. Karpenter — Node에 Finalizer를 걸고 직접 제어
Karpenter: 통합/만료 판단 ──▶ Node에 Finalizer 설정
│
▼
Cordon & Drain 직접 수행
│
드레인 미완료 시 ──▶ 인스턴스를 삭제하지 않고 보류 및 재시도
│
terminationGracePeriod 도달 ──▶ 강제 컷오프 및 인스턴스 종료
CA는 노드 생성과 종료 모두 ASG라는 계층을 한 단계 거친다. 따라서 고정된 인스턴스 타입, 라이프사이클 훅 등 ASG가 가진 제약과 특성을 그대로 물려받는다.
반면 Karpenter는 EC2 API를 직접 다루기 때문에 중간 계층이 없다. 이 차이가 뒤따르는 모든 운영상의 차이를 가른다.
Cluster Autoscaler가 동작하는 방식
CA는 노드 그룹(Node Group) 단위로 동작한다. EKS 관리형 노드 그룹(Managed Node Group)은 내부적으로 ASG 하나와 1:1로 매핑되며, CA는 이 ASG의 desired count를 제어하는 컨트롤러 역할을 한다.
스케일 업 흐름은 다음과 같다. 파드가 스케줄링되지 못하고 Pending 상태에 빠지면, CA는 해당 파드를 수용할 수 있는 노드 그룹을 탐색한 뒤 그 ASG의 desired count를 1 올린다. 그러면 ASG가 미리 정의해 둔 인스턴스 타입 풀 안에서 EC2를 구동하고, 새 노드가 클러스터에 합류하면 파드가 그 위에 배치된다. CA 자체는 EC2 API를 직접 호출하지 않는다. 어디까지나 ASG에 수량 증가 신호만 보낼 뿐이다.
이 방식으로 인해 두 가지 운영 부담이 발생한다.
- 인스턴스 타입이 ASG에 종속된다: ASG는 정해진 타입(또는 제한된 타입 풀)으로만 노드를 띄운다. 따라서 GPU 워크로드, ARM 아키텍처, 메모리 집약적 워크로드 등 요구사항이 다른 파드들을 수용하려면 노드 그룹을 형태별로 잘게 쪼개야 한다. 테인트(Taint)나 가용 영역(AZ)까지 고려하면 관리해야 할 노드 그룹 수가 급격히 늘어난다.
- 노드 그룹 간의 균형을 직접 관리해야 한다: 가용 영역 간의 균형이나 타입별 용량 보장 등을 운영자가 직접 설계하고 관리해야 하는 번거로움이 있다.
스케일 다운은 반대로 진행된다. CA가 자원 사용률이 낮은(Underutilized) 노드를 선별하여 cordon 및 drain을 수행한 뒤, ASG의 desired count를 줄인다. 그러면 ASG가 해당 인스턴스를 종료한다. 여기서 중요한 점은 실제 인스턴스의 삭제가 ASG의 Terminate 라이프사이클을 통해 수행된다는 사실이다. 이 메커니즘이 아래에서 다룰 장애 사고의 원인이 된다.
Karpenter가 동작하는 방식
Karpenter는 접근 방식부터 다르다. ASG나 노드 그룹이라는 개념 자체를 사용하지 않으며, 클러스터 내부의 Pending 파드를 직접 모니터링한다.
스케일 업 흐름은 다음과 같다. 파드가 Pending 상태가 되면 Karpenter가 파드의 실제 리소스 요청량(Requests), 어피니티(Affinity), 툴러레이션(Toleration)을 분석한다. 그 후 EC2 Fleet API(CreateFleet / RunInstances)를 직접 호출하여, 해당 파드를 수용하기에 가장 효율적이고 비용이 저렴한 인스턴스 타입을 그 자리에서 동적으로 선택해 구동한다. 사전에 정의된 노드 그룹의 형태에 얽매이지 않으며, 인스턴스 타입을 고정하는 대신 NodePool에 허용 범위만 설정해 두고 실제 선택은 런타임에 Karpenter가 내린다.
설정 또한 ASG가 아닌 쿠버네티스 커스텀 리소스(CRD)로 관리한다.
- NodePool: 어떤 요구사항(Architecture, Capacity-type, Instance-family 등)을 가진 노드를 생성할지, 그리고 자발적 중단(Disruption) 정책은 어떻게 가져갈지 정의한다.
- EC2NodeClass: AMI, 서브넷, 보안 그룹, IAM 역할 등 AWS 레벨의 인프라 설정을 정의한다.
스케일 다운은 훨씬 더 적극적이고 스마트하다. CA가 단순히 사용량이 저조한 노드를 삭제하는 것에 그친다면, Karpenter는 통합(Consolidation) 기능까지 지원한다. 여러 노드에 파편화되어 실행 중인 파드들을 분석하여 더 적은 수의 노드로 빈 패킹(Bin-packing)하고 남는 노드를 정리하거나, 동일한 워크로드를 더 저렴한 인스턴스 타입으로 실시간으로 교체한다.
또한 노드 종료를 ASG에 위임하지 않는다. Karpenter는 Node 오브젝트에 자체 파이널라이저(karpenter.sh/termination)를 부여하고 종료 단계를 직접 주관한다. 직접 cordon과 drain을 수행하며, 드레인이 안전하게 끝나야 비로소 EC2 인스턴스를 삭제한다.
만약 특정 파드 때문에 드레인이 완료되지 않으면 무작정 인스턴스를 죽이지 않고 상태를 보류하며, NodePool에 정의한 terminationGracePeriod에 도달했을 때 비로소 파드를 강제로 정리하고 노드를 안전하게 컷오프한다. 종료의 모든 제어권을 Karpenter가 쥐고 있는 셈이다.
핵심 차이 비교
두 방식의 특징을 표로 요약하면 다음과 같다.
| 항목 | Cluster Autoscaler | Karpenter |
| 노드 프로비저닝 | ASG Desired 조정 $\rightarrow$ ASG가 EC2 기동/종료 | EC2 API 직접 호출 (CreateFleet), ASG 없음 |
| 노드 그룹 관리 | EKS Managed Node Group = ASG 1:1 매핑 | 노드 그룹 및 ASG 개념 없음 |
| 설정 주체 | ASG 설정(Min/Max) + CA 배포 플래그 | CRD (NodePool + EC2NodeClass) |
| 인스턴스 타입 | ASG 설정에 고정된 타입만 사용 가능 | 파드 요구사항에 맞춰 타입을 동적으로 최적화 |
| 스케일 다운 | 저활용 노드 Cordon/Drain 후 ASG 수량 감소 | 통합(Consolidation) 및 빈 노드 자율 회수 |
| 노드 종료 방식 | ASG Terminate 라이프사이클 훅을 경유 | Node 파이널라이저 기반 Karpenter 직접 제어 |
| 스팟 중단 처리 | 별도 도구(Node Termination Handler 등) 필요 | EventBridge + SQS 연동 기능 자체 내장 |
| 출시 시기 | 2017년경 (SIG Autoscaling) | 2021년 (AWS 공식 오픈소스), 2024년 v1.0 |
이 비교에서 가장 주목해야 할 축은 노드 프로비저닝과 종료 메커니즘이다.
CA의 한계와 운영 복잡성은 대부분 "노드 밑에 ASG가 존재한다"는 사실에서 비롯된다. 인스턴스 타입 고정이나 라이프사이클 훅 의존성 모두 ASG의 특성이다. Karpenter는 이 ASG 계층을 과감히 제거함으로써 기존의 제약 사항들을 한 번에 해결한다.
설정으로 보는 차이
실제 인프라 코드로 두 방식을 구현할 때 제어하는 대상은 확연히 다르다.
Cluster Autoscaler — ASG와 CA 플래그 중심 제어
# Managed Node Group 설정 예시 (Terraform)
# 인스턴스 타입을 그룹 내에 고정해야 하므로, 워크로드별로 노드 그룹을 쪼개야 한다.
eks_managed_node_groups = {
general = {
instance_types = ["m6i.xlarge"] # 해당 그룹은 이 타입으로만 생성된다
min_size = 2
desired_size = 2
max_size = 10
}
}
Karpenter — NodePool 중심 제어
# NodePool 설정 예시 (Kubernetes Manifest)
# 특정 타입을 고정하지 않고 유연한 허용 범위(Requirements)를 제공한다.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- { key: kubernetes.io/arch, operator: In, values: ["arm64"] }
- { key: karpenter.sh/capacity-type, operator: In, values: ["spot"] }
- { key: karpenter.k8s.aws/instance-family, operator: In, values: ["c7g","m7g"] }
terminationGracePeriod: 5m # 드레인 지연(Stuck) 시 강제 컷오프를 수행할 명시적 상한선
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
CA 환경에서는 새로운 아키텍처나 크기의 워크로드가 추가될 때마다 인프라 레벨에서 노드 그룹을 새로 정의해야 하지만, Karpenter 환경에서는 하나의 NodePool 내에 허용 조건만 명시해 두면 자율적으로 최적의 인스턴스를 선택한다.
노드 종료에서 갈리는 결정적 차이 — 좀비 노드 사건
표에 언급된 "노드 종료 방식"의 차이가 실제 운영 환경에서 얼마나 큰 리스크로 다가오는지, 직접 겪었던 장애 사례를 통해 설명한다.
당시 운영 중이던 클러스터는 CA와 매니지드 노드 그룹(ASG 기반) 조합이었다. 스케일 다운이 트리거되면 CA가 노드 드레인을 시작하고 ASG의 desired count를 줄인다. 이때 실제 인스턴스 종료는 ASG의 Terminate 라이프사이클 훅(기본 타임아웃 30분)의 제어를 받는다. 이는 곧 "30분이라는 제한 시간 안에 노드 드레인이 반드시 완료되어야 한다"는 전제가 깔려 있음을 의미한다.
사고는 특정 파드의 preStop 훅이 실패(FailedPreStopHook)하면서 발생했다. 파드가 정상적으로 종료되지 않자 노드 드레인 작업이 해당 단계에서 멈추어 버렸다. 드레인이 완료되지 않으니 인스턴스 종료 역시 진행되지 못했다. ASG 입장에서는 종료 명령은 내려졌으나 라이프사이클 훅 내부에서 드레인이 끝나지 않은 상태로 시간만 흐르게 되었고, 결과적으로 클러스터 내에서 새로운 파드를 받지도 못하고 완전히 정상 종료되지도 않는 '좀비 노드' 상태로 남게 되었다.
이 사건의 본질은 CA가 종료 프로세스의 완전한 제어권을 쥐고 있지 않다는 점에 있다. 드레인은 CA가 시작하지만, 인스턴스의 최종 삭제는 ASG의 타임라인 위에서 도는 별개의 작업이다. CA, ASG, 그리고 파드의 라이프사이클(preStop)이라는 세 컴포넌트의 타이밍이 어긋나면서 시스템이 불능 상태에 빠진 것이다.
반면 Karpenter는 이 상황을 훨씬 더 명확하고 결정론적으로 처리한다. Karpenter는 ASG나 라이프사이클 훅을 배제하고 Node 오브젝트의 파이널라이저를 통해 종료를 끝까지 직접 제어한다. 드레인이 막히면 무작정 인스턴스를 삭제하여 서비스 장애를 유발하지 않고 안전하게 대기하며 드레인을 재시도한다.
그러다 NodePool에 명시된 terminationGracePeriod에 도달하면, 타임아웃 규칙에 따라 해당 파드를 강제로 정리하고 노드를 확실하게 종료한다. 드레인이 중단되었을 때의 대책이 ASG의 모호한 타임아웃이 아니라, 쿠버네티스 리소스에 명시된 값에 의해 예측 가능한 방식으로 작동하는 것이다.
언제 무엇을 쓸까
새롭게 EKS 클러스터를 구축하거나 다양한 워크로드를 다루며 비용 최적화가 필수적인 환경이라면, Karpenter를 기본 선택지로 검토하는 것이 아키텍처 측면에서 유리하다. 인스턴스 타입을 동적으로 선택하는 능력, 적극적인 노드 통합 기능, 내장된 스팟 중단 처리 메커니즘, 독립적인 종료 제어권 등은 노드 그룹 관리에 들어가는 운영 공수를 획기적으로 줄여준다.
그러나 다음과 같은 환경에서는 Cluster Autoscaler의 활용 역시 여전히 유효하다.
- 기존 ASG 기반 구성으로 오랜 기간 안정적으로 운영 중인 경우: 무리하게 아키텍처를 변경하기보다 유지하는 것이 실익이 클 수 있다. 새로운 기술(CRD) 도입에 따른 학습 비용과 마이그레이션 리스크를 고려해야 한다.
- 노드 아키텍처 변화가 없고 워크로드가 고정적인 경우: 스케일링의 다변화가 필요 없고 단일 인스턴스 타입으로도 충분하다면, 단순하고 검증된 ASG+CA 조합이 관리 부담을 낮출 수 있다.
- 기존의 인프라 파이프라인이 ASG 기능에 깊게 결합되어 있는 경우: ASG 워밍 풀(Warming Pool)이나 커스텀 라이프사이클 훅을 필수로 활용하는 에코시스템을 구축해 두었다면 전환 비용을 면밀히 따져보아야 한다.
두 도구의 점진적인 혼용 및 전환도 가능하다. 상시 켜져 있어야 하는 코어 워크로드(Core Addons, Controller 등)는 고정된 매니지드 노드 그룹(CA 혹은 고정 크기)에 배치하고, 트래픽 변화가 심하거나 배치성 워크로드(CI/CD, Batch Job 등)는 Karpenter NodePool이 처리하도록 영역을 분리하여 안전하게 마이그레이션을 진행할 수 있다.
Karpenter 컨트롤러 자체의 안정적인 구동을 위해서도 코어 컨트롤러는 매니지드 노드 그룹에 띄우는 것이 권장된다.
운영 메모
CA 체제에서 Karpenter 체제로 전환하거나 실무에서 Karpenter를 운영할 때 반드시 체크해야 할 리스트다.
- 핵심 컨트롤러는 매니지드 노드 그룹에 격리한다: Karpenter 컨트롤러가 자기가 구동 중인 노드를 스스로 드레인하여 다운되는 데드락(Deadlock) 상황을 방지하기 위해, Karpenter 컨트롤러와 필수 코어 애드온(CoreDNS 등)은 상시 작동하는 별도의 매니지드 노드 그룹에 띄우는 것이 정석이다.
- `terminationGracePeriod` 를 반드시 명시한다: 앞서 언급한 드레인 중단(Stuck-drain) 리스크를 방지하기 위해, NodePool 설정 시 상한 타임아웃을 명시적으로 선언하여 종료 프로세스가 결정론적으로 마무리되도록 설정한다. 특히 `preStop` 훅의 실행 시간이 길거나 외부 연동이 포함된 워크로드일수록 이 설정이 중요하다.
- do-not-disrupt 어노테이션을 적극 활용한다: 비용 최적화를 위한 자발적 중단(Consolidation 등)으로 인해 서비스가 끊기면 안 되는 중요한 세션 파드나 상태 저장 파드에는 `karpenter.sh/do-not-disrupt: "true"` 어노테이션을 부여하여 보호한다.
- 트러블슈팅 시 kubectl get nodeclaim을 확인한다: 펜딩 파드 발생 시 디버깅 루틴이 달라진다. `NodeClaim` 오브젝트 자체가 생성되지 않는다면 파드의 요구사항과 일치하는 `NodePool` 설정이 없는 것이며, `NodeClaim` 은 생성되었으나 노드가 `Ready` 상태로 전환되지 않는다면 AWS 인프라 레이어(인스턴스 타입 용량 부족, 서브넷 설정 오류, IAM 권한 누락 등)의 문제다. CA 시절 ASG Activity History를 조회하던 방식을 `NodeClaim` 모니터링이 대체한다.
마무리
두 오토스케일링 솔루션을 실무에 적용하며 내린 최종 결론이다.
- 동일한 목표, 상이한 메커니즘: 두 도구 모두 파드의 수요에 연동하여 노드 자원을 유연하게 조절하지만, CA는 ASG의 수량을 조절하는 간접 방식을 취하고 Karpenter는 EC2 Fleet API를 직접 호출하는 직접 방식을 취한다.
- 운영 제약의 원인은 ASG 계층의 존재 유무: 인스턴스 타입의 고정, 복잡한 노드 그룹 파편화, 라이프사이클 훅 기반의 간접 종료 등 CA가 가진 구조적 제약은 "노드 하위에 ASG가 깔려 있다"는 사실 자체에서 기인한다. Karpenter는 이 중간 계층을 생략함으로써 제약 조건을 원천적으로 해결한다.
- 노드 종료 시점에서 드러나는 제어권의 차이: 상호 컴포넌트 간 타이밍 엇갈림으로 인해 드레인 지연 시 좀비 노드를 남길 위험이 있는 CA+ASG 조합과 달리, Karpenter는 파이널라이저를 통해 제어권을 일원화하고 `terminationGracePeriod` 로 데드라인을 명시하여 예외 상황을 훨씬 안전하고 결정론적으로 종결짓는다.
- 상황에 맞는 최적의 도구 선택: 신규 클러스터나 동적 워크로드가 중심이라면 Karpenter가 강력한 대안이 되며, 기존의 안정적인 인프라 환경이라면 CA 체제를 유지하는 것도 합리적인 선택이다. 두 도구의 점진적 공존 및 마이그레이션 전략을 통해 인프라를 안정적으로 고도화할 수 있다.
Reference
- Karpenter Documentation
- Cluster Autoscaler on AWS (GitHub)
- Karpenter Disruption & Termination (Docs)
- EC2 Auto Scaling lifecycle hooks (AWS Docs)
Somaz | DevOps Engineer | Kubernetes & Cloud Infrastructure Specialist
'AWS' 카테고리의 다른 글
| EKS + Karpenter 무중단 업그레이드 — 노드 잔존 문제부터 검증까지 (0) | 2026.07.16 |
|---|---|
| RDS IAM 인증으로 데이터베이스 비밀번호 관리에서 벗어나기 (1) | 2026.07.13 |
| EKS 무중단 배포 — lifecycle(preStop) · Pod Readiness Gatestrategy(RollingUpdate) · PodDisruptionBudget (1) | 2026.07.09 |
| EKS Pod Identity vs IRSA: 파드에 IAM 권한을 주는 두 가지 방식 (0) | 2026.06.30 |
| Karpenter on EKS: Terraform과 Helm으로 구성하는 2계층 설정 (0) | 2026.06.16 |