- Published on
EKS Karpenter로 노드 자동확장·비용 30% 절감
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
EKS에서 비용이 새는 가장 흔한 지점은 노드가 워크로드보다 먼저 고정되어 있고, 스케일링 단위가 노드 그룹에 묶여 있다는 점입니다. 특히 트래픽 변동이 큰 서비스나 배치 잡이 섞인 클러스터에서는, 피크를 대비해 노드를 넉넉히 잡아두는 순간부터 유휴 리소스가 비용으로 직결됩니다.
Karpenter는 이 문제를 “파드 요구사항 기반”으로 뒤집습니다. 파드가 스케줄되지 못해 Pending이 되면, 그 파드의 requests/limits, nodeSelector, affinity, taints/tolerations 등을 읽고 최적의 인스턴스를 즉시 생성합니다. 그리고 유휴 노드는 빠르게 정리합니다. 결과적으로 다음 3가지가 동시에 일어납니다.
- 필요한 순간에만 노드를 띄워 유휴 비용을 줄임
- 파드 요구사항에 맞는 인스턴스를 선택해 과대 스펙을 줄임
- 스팟과 온디맨드를 섞고 빈패킹을 강화해 단가를 줄임
이 글에서는 EKS에서 Karpenter를 도입해 “노드 자동확장 + 비용 30% 절감”을 목표로 할 때, 실제로 효과가 나는 설정 포인트와 운영 체크리스트를 정리합니다.
왜 Cluster Autoscaler 대신 Karpenter인가
Cluster Autoscaler(CA)는 Managed Node Group 또는 ASG 단위로 스케일링합니다. 즉 “이 노드 그룹의 템플릿(AMI, 인스턴스 타입, 디스크 등)”이 먼저 정해져 있어야 하고, 파드가 원하는 형태와 맞지 않으면 불필요하게 큰 인스턴스를 띄우거나 스케일링이 지연될 수 있습니다.
Karpenter는 다음이 강점입니다.
- 인스턴스 타입을 동적으로 선택:
c,m,r계열을 섞어 최저가/가용성 기준으로 선택 - 초 단위 프로비저닝: 파드 스케줄 실패를 트리거로 빠르게 노드 생성
- 빈패킹 효율: 가능한 한 기존 노드에 조밀하게 스케줄하고, 남는 노드는 축출 후 종료
- 스팟 전략 최적화: 여러 인스턴스 풀을 열어 두고 중단 리스크를 분산
다만, “아무 설정 없이” 비용이 줄어들지는 않습니다. Karpenter는 강력한 만큼, 제약 조건과 방어 장치를 설계해야 비용과 안정성이 같이 올라갑니다.
비용 30% 절감이 나오는 전형적인 패턴
경험적으로 30% 내외 절감이 잘 나오는 조합은 다음 중 2개 이상을 동시에 달성할 때입니다.
- 스팟 비중 확대
- stateless API, 워커, 배치 잡, 큐 컨슈머 등
on-demand는 최소 베이스만 유지하고 나머지를 스팟으로
- 과대 스펙 제거
- 기존 노드 그룹이
m5.2xlarge고정인데 실제 파드는 CPU만 필요하거나 메모리만 필요 - Karpenter가
c또는r계열로 자동 선택해 단가 절감
- 유휴 노드 제거(Consolidation)
- 트래픽이 빠지는 시간대에 노드가 남아 있는 상태를 줄임
- 축출 가능한 파드를 잘 설계하면 유휴 노드가 빠르게 정리됨
- 디스크/네트워크 비용 최적화
- 기본 EBS가 과하게 크거나, 불필요한
gp3IOPS 설정 - 노드 템플릿을 최소화해 낭비 제거
사전 점검: Pending 원인을 먼저 정리해야 한다
Karpenter는 Pending을 해결해 주지만, Pending의 원인이 리소스 부족이 아니라 taint, affinity, PDB, maxPods 같은 제약이라면 오히려 “노드를 더 띄워도 해결이 안 되는 상황”이 생깁니다.
특히 다음 케이스는 먼저 정리하는 편이 좋습니다.
Insufficient cpu또는Insufficient memory가 아닌데 계속Pending- 특정 노드 그룹에만 스케줄되도록 하드하게 묶여 있음
- 과도한
nodeSelector로 인스턴스 선택지가 좁음
관련해서 파드가 Pending인 원인을 빠르게 분해하는 방법은 아래 글도 같이 보면 좋습니다.
아키텍처 개요: Karpenter의 핵심 리소스
Karpenter는 버전에 따라 CRD 명칭이 다를 수 있지만, 실무에서 핵심은 다음 2가지입니다.
NodePool: 어떤 종류의 노드를 어떤 정책으로 운영할지(요구사항, 한도, 축출/통합 정책)EC2NodeClass(또는 유사 리소스): AMI, 서브넷/보안그룹, EBS 설정 등 AWS 인프라 템플릿
그리고 IAM은 기본적으로 IRSA로 붙이는 것을 권장합니다.
설치: Helm + IRSA 기본 뼈대
아래는 “개념을 보여주기 위한” 예시입니다. 실제 적용 시에는 사용 중인 Karpenter 버전의 공식 문서를 기준으로 CRD/API 버전을 맞추세요.
1) IRSA용 IAM Role 준비(개념)
Karpenter 컨트롤러가 EC2를 생성/종료하고, 인스턴스 프로파일을 붙일 수 있어야 합니다. Terraform 또는 CloudFormation으로 구성하는 것이 일반적입니다.
정책에는 보통 다음 권한이 포함됩니다.
ec2:RunInstances,ec2:TerminateInstances,ec2:CreateFleetec2:Describe*iam:PassRole(노드 인스턴스 역할 전달)ssm:GetParameter(AMI 조회 방식에 따라)
2) Helm 설치 예시
helm repo add karpenter https://charts.karpenter.sh
helm repo update
# values는 환경에 맞게 조정
helm upgrade --install karpenter karpenter/karpenter \
--namespace karpenter --create-namespace \
--set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"="arn:aws:iam::123456789012:role/KarpenterControllerRole" \
--set settings.clusterName="my-eks" \
--set settings.interruptionQueueName="my-eks-karpenter-interruptions"
여기서 interruptionQueueName은 스팟 중단 알림을 SQS로 받아서 드레이닝을 빠르게 하기 위한 구성입니다.
NodePool 설계: 비용 절감의 80%는 여기서 결정된다
“30% 절감”을 만들려면 NodePool을 워크로드 성격별로 최소 2개로 나누는 게 좋습니다.
on-demand기반의 안정 풀(베이스 용량)spot기반의 탄력 풀(대부분의 변동 트래픽)
1) 스팟 NodePool 예시
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: spot-general
spec:
template:
spec:
nodeClassRef:
name: general-ec2
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: kubernetes.io/os
operator: In
values: ["linux"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["c6a.large", "c6a.xlarge", "m6a.large", "m6a.xlarge", "r6a.large"]
taints:
- key: workload
value: spot
effect: NoSchedule
limits:
cpu: "500"
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
expireAfter: 168h
포인트는 다음과 같습니다.
instance-type을 1~2개로 고정하지 말고 풀을 넓게 잡아 스팟 가용성을 높입니다.- 스팟 노드에는
taint를 걸고, 스팟 허용 워크로드만tolerations로 들어오게 합니다. consolidationPolicy로 유휴/저활용 노드를 정리해 비용을 줄입니다.
2) 온디맨드 NodePool 예시(베이스)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: ondemand-base
spec:
template:
spec:
nodeClassRef:
name: general-ec2
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m6a.large", "m6a.xlarge"]
limits:
cpu: "200"
disruption:
consolidationPolicy: WhenEmpty
온디맨드는 “최소 안정 용량”만 담당하게 두고, 나머지는 스팟이 먹도록 설계하면 절감 폭이 커집니다.
EC2NodeClass: 서브넷/보안그룹/디스크를 표준화
노드 템플릿이 제각각이면 운영이 복잡해지고, 디스크 기본값이 과해져 비용이 누적됩니다.
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: general-ec2
spec:
amiFamily: AL2023
role: "KarpenterNodeRole-my-eks"
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: "my-eks"
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: "my-eks"
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 50Gi
volumeType: gp3
deleteOnTermination: true
비용 관점 체크리스트:
- 루트 볼륨을 필요 이상으로 크게 잡지 않기
gp3로 표준화하고 IOPS/throughput을 과도하게 올리지 않기- 서브넷은 멀티 AZ로 열어 두되, 라우팅/보안그룹 태그를 표준화
워크로드 쪽 설정: 스팟 친화적으로 만들기
Karpenter가 노드를 잘 만들어도, 파드가 “축출 불가”하면 통합(Consolidation)이 막혀 비용이 내려가지 않습니다.
1) 스팟 허용 워크로드에 toleration 추가
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
spec:
replicas: 6
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
spec:
tolerations:
- key: workload
operator: Equal
value: spot
effect: NoSchedule
containers:
- name: worker
image: public.ecr.aws/nginx/nginx:stable
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
2) PDB를 “안전하지만 과하지 않게”
PDB가 너무 빡빡하면 노드 축출이 막혀서 비용 최적화가 깨집니다.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: worker-pdb
spec:
minAvailable: 70%
selector:
matchLabels:
app: worker
트래픽이 낮은데도 minAvailable이 절대값으로 높게 잡혀 있으면, Consolidation이 사실상 불가능해집니다.
운영에서 자주 터지는 이슈와 해결 포인트
1) 파드는 Pending인데 노드가 안 뜬다
대부분 다음 중 하나입니다.
- 파드가 요구하는
nodeSelector/affinity가 NodePool requirements와 충돌 taint/toleration불일치- 서브넷/보안그룹 셀렉터 태그가 잘못됨
이때는 이벤트와 컨트롤러 로그를 같이 봐야 합니다. EKS에서 디버깅이 막히는 경우도 종종 있는데, 아래 글이 도움이 됩니다.
2) 스팟 중단으로 서비스가 흔들린다
- 스팟 풀을 넓혀서(인스턴스 타입 다양화) 중단 리스크를 분산
- 온디맨드 베이스 풀을 너무 얇게 두지 않기
topologySpreadConstraints로 AZ 분산- SQS interruption handling을 구성해 빠른 드레이닝
3) 비용이 생각만큼 안 줄어든다
- Consolidation이 막혀 있는지 확인(PDB, local PV, daemonset, pod anti-affinity)
- 파드
requests가 과하게 잡혀 있는지 확인 - 노드 TTL(
expireAfter)로 장기 방치 노드를 정리 - 인스턴스 타입을 너무 비싼 쪽으로 제한했는지 확인
30% 절감을 위한 실전 체크리스트
아래 항목을 순서대로 맞추면 “체감 가능한 절감”이 나오는 경우가 많습니다.
- 워크로드를
spot 가능과spot 불가로 분류 - 스팟 NodePool에
taint를 걸고, 허용 파드에만tolerations부여 - 스팟 인스턴스 타입 풀을 최소 5개 이상으로 확장
requests/limits를 현실화해 빈패킹 효율을 끌어올림- PDB를 퍼센트 기반으로 재설계해 축출 가능성을 확보
- Consolidation을
WhenEmptyOrUnderutilized로 켜고, 관측 후 점진 조정 - 온디맨드 베이스 풀은 “최소 생존 용량”만 유지
마무리
EKS에서 Karpenter는 단순한 오토스케일러가 아니라, “파드 요구사항 기반의 동적 노드 프로비저너”에 가깝습니다. 비용 30% 절감은 과장이 아니라, 스팟 비중 확대와 빈패킹, 유휴 노드 정리를 함께 달성하면 충분히 현실적인 수치입니다.
핵심은 도구 자체가 아니라 설계입니다. NodePool을 워크로드 성격별로 분리하고, 스팟 친화적인 파드 정책(PDB, toleration, requests)을 갖추면 안정성과 비용을 동시에 가져갈 수 있습니다.