Published on

EKS 노드 비용 40%↓ - Karpenter Spot+중단대응

Authors

EKS에서 노드 비용은 대개 전체 인프라 비용의 상당 부분을 차지합니다. 특히 워커 노드를 온디맨드(OD)로만 운영하면, 트래픽 변동이 큰 서비스에서 유휴 용량이 바로 비용 낭비로 이어집니다. 반대로 Spot을 쓰면 비용은 크게 내려가지만, 언제든 회수(interruption) 될 수 있어 가용성/지연/배포 안정성에 대한 불안이 생깁니다.

이 글은 Karpenter로 Spot 중심 오토스케일링을 구성해 노드 비용을 40% 이상 절감하면서도, Spot 중단 이벤트에 예측 가능하게 대응하는 실전 운영 패턴을 다룹니다. (중요: 본문에 >/< 같은 부등호는 MDX 빌드 이슈가 있어 코드 블록/인라인 코드로만 표기합니다.)

왜 Cluster Autoscaler 대신 Karpenter인가

Cluster Autoscaler(CA)도 노드 오토스케일링이 가능하지만, 다음 제약이 자주 발목을 잡습니다.

  • 노드그룹(Managed Node Group, ASG) 단위로 스케일링: 인스턴스 타입/용량을 세밀하게 바꾸기 어렵습니다.
  • Spot 다변화(여러 타입, 여러 AZ, 여러 용량 전략) 최적화가 제한적입니다.
  • 파드 스케줄 요구사항(아키텍처, 용량, 디스크, 네트워크)을 만족하는 “최적” 노드를 즉시 만들기보다, 미리 정의한 그룹에서 증감하는 방식입니다.

Karpenter는 미리 정해진 노드그룹에 덜 의존하고, 파드 요구사항을 보고 그때그때 최적 인스턴스를 선택해 띄웁니다.

  • 다양한 인스턴스 타입을 한 번에 후보로 두고, 가격/가용성에 따라 유연하게 선택
  • 필요하면 더 작은 노드 여러 개로 쪼개거나, 반대로 큰 노드로 합쳐서(binpacking) 비용 최적화
  • TTL/Consolidation로 유휴 노드를 적극적으로 정리

비용 40% 절감이 나오는 전제 조건

비용 절감 폭은 워크로드 성격에 따라 달라지지만, 아래 조건을 만족하면 40%는 현실적인 수치입니다.

  • 워크로드의 상당 부분이 무상태(stateless) 이거나, 상태가 있어도 재시작/재스케줄에 강함
  • HPA/큐 기반 소비자/배치처럼 스케일 아웃이 자연스러운 구조
  • requests/limits를 대충 잡지 않고, 최소한 requests는 현실적으로 설정
  • Spot 중단을 “장애”가 아니라 “정상 이벤트”로 보고, 중단 대응(Drain, PDB, graceful shutdown) 을 갖춤

참고로 파드가 자주 재시작되거나 노드 교체가 잦아지면, CrashLoopBackOff 같은 증상으로 비용 최적화 이전에 안정성 문제가 먼저 터집니다. 관련 진단은 Kubernetes CrashLoopBackOff 10가지 원인과 15분 진단도 함께 보시면 좋습니다.

아키텍처 개요: Spot은 기본, On-Demand는 안전망

권장하는 기본 구조는 이렇습니다.

  • 기본 노드풀: Spot (대부분의 워크로드)
  • 안전망 노드풀: On-Demand (핵심 시스템 파드, 스케줄러/컨트롤플레인 주변, 혹은 Spot 회수 폭주 시)
  • 중요한 파드: PDB + 우선순위(PriorityClass) + graceful termination
  • Spot 중단 이벤트: Node Termination Handler(NTH) 또는 이벤트 기반 드레이닝

Karpenter만으로도 노드 생성/정리는 되지만, Spot 회수 이벤트를 감지하고 빠르게 드레인하는 컴포넌트가 있으면 “중단이 와도 서비스 영향이 작아지는” 운영이 됩니다.

Karpenter 설치 전 체크리스트

1) EKS 버전/권한/IRSA

  • Karpenter Controller는 AWS API(EC2, Pricing 등) 호출이 필요합니다.
  • IRSA(IAM Roles for Service Accounts)로 최소 권한을 부여하세요.

EKS에서 IAM/SDK 인증이 꼬이면 403 계열로 고생합니다. 특히 노드/파드 역할 분리가 불명확하면 디버깅이 길어집니다. 필요하면 EKS에서 AWS SDK 403 MissingAuthenticationToken 해결도 참고하세요.

2) 서브넷/보안그룹 태그

Karpenter가 노드를 띄울 서브넷과 보안그룹을 찾을 수 있도록 태그가 필요합니다. (태그 키는 Karpenter 버전/가이드에 따라 달라질 수 있으니 공식 문서를 기준으로 하되, 원리는 동일합니다.)

  • 클러스터 식별 태그
  • 노드 디스커버리 태그

3) 인스턴스 타입 후보군 전략

Spot 안정성을 높이는 핵심은 “한 타입만 고집하지 않는 것”입니다.

  • m/c/r 계열을 섞고
  • 세대도 2~3개로 넓히고
  • AZ도 2개 이상

후보군을 넓히면 단가 최저는 조금 올라가도, 스케일 실패/용량 부족으로 인한 장애 확률이 내려갑니다.

Karpenter NodePool 설계: Spot 중심 구성 예시

아래 예시는 개념을 보여주는 예시입니다. 실제 필드명은 사용 중인 Karpenter API 버전에 맞춰 조정하세요.

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: spot-general
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values:
            - m6i.large
            - m6i.xlarge
            - c6i.large
            - c6i.xlarge
            - r6i.large
      taints:
        - key: spot
          value: "true"
          effect: NoSchedule
  limits:
    cpu: "500"
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    expireAfter: 720h

핵심 포인트:

  • karpenter.sh/capacity-typespot으로 제한
  • 후보 인스턴스 타입을 여러 개로 넓힘
  • taints로 Spot 노드에 기본적으로 NoSchedule을 걸고, Spot 허용 워크로드만 toleration으로 올라오게 설계
  • Consolidation/expire로 유휴/비효율 노드를 정리

On-Demand 안전망 NodePool

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: ondemand-core
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand"]
      taints:
        - key: core
          value: "true"
          effect: NoSchedule
  limits:
    cpu: "100"
  • 핵심 파드만 tolerationscore 노드에 올라가게 하면, Spot 변동이 커도 최소 기능은 지킬 수 있습니다.

워크로드 쪽 “Spot 중단 내성” 체크리스트

Spot을 쓰는 순간, 노드는 “언젠가 사라지는 리소스”가 됩니다. 따라서 파드가 다음을 만족해야 합니다.

1) PDB로 동시 축출 한도 제한

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: api
  • 노드 드레인/축출 시 한 번에 너무 많이 빠지지 않도록 제한
  • 단, minAvailable을 과도하게 높이면 드레인이 막혀서 오히려 복구가 늦어질 수 있습니다.

2) graceful shutdown: preStop + terminationGracePeriodSeconds

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 6
  template:
    spec:
      terminationGracePeriodSeconds: 40
      containers:
        - name: api
          image: your-api:latest
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 10"]
  • 로드밸런서/서비스 디스커버리에서 트래픽이 빠질 시간을 확보
  • 앱이 SIGTERM을 제대로 처리하도록 구현(커넥션 드레인, 작업 중단/재시도)

3) PriorityClass로 “살아남아야 하는 파드” 우선 배치

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: critical
value: 100000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
description: "Critical workloads"
  • Spot 대규모 회수로 용량이 급감할 때, 중요 파드가 우선 스케줄되도록 유도

4) Spot 노드에만 올라갈 워크로드를 명시

spec:
  tolerations:
    - key: spot
      operator: Equal
      value: "true"
      effect: NoSchedule
  • “기본은 OD, 일부만 Spot”도 가능하지만, 비용 최적화 목표가 크다면 “기본은 Spot, 핵심만 OD”가 더 효과적입니다.

Spot 중단 대응: 이벤트 감지 후 빠른 드레인

Spot은 보통 중단 2분 전후의 사전 통지를 제공합니다. 이 시간 안에 해야 할 일은 단순합니다.

  • 해당 노드에 cordon
  • 파드 evict (PDB 준수)
  • 새 노드를 빠르게 확보(Karpenter)

이를 자동화하는 대표 옵션이 AWS Node Termination Handler(NTH) 입니다. NTH는 EC2 메타데이터/이벤트를 감지해 노드를 드레인합니다.

NTH 설치(Helm) 예시

helm repo add eks https://aws.github.io/eks-charts
helm upgrade --install aws-node-termination-handler eks/aws-node-termination-handler \
  --namespace kube-system \
  --set enableSpotInterruptionDraining=true \
  --set enableRebalanceMonitoring=true \
  --set enableScheduledEventDraining=true

운영 팁:

  • 드레인 속도가 느리면 PDB/terminationGracePeriodSeconds/애플리케이션 종료 로직을 먼저 점검
  • 중단 이벤트가 자주 오는데 서비스 영향이 크다면, 인스턴스 타입 후보군이 너무 좁은지 확인

Karpenter Consolidation(통합)로 “유휴 비용” 더 깎기

Spot을 쓰더라도, 파드 배치가 비효율적이면 노드가 남아서 비용이 새어 나갑니다. Karpenter의 consolidation은 다음 상황에서 효과가 큽니다.

  • 트래픽 피크가 지나간 뒤 노드가 많이 남는 구조
  • requests가 과대 설정되어 binpacking이 안 되는 구조

권장 흐름:

  1. 먼저 requests를 현실화
  2. consolidation을 WhenEmptyOrUnderutilized로 켬
  3. 관측 지표(노드 수, 파드 재스케줄 횟수, P95 지연)를 보며 점진적으로 튜닝

비용 절감 효과를 “숫자”로 검증하는 방법

1) Cost Explorer: On-Demand 대비 Spot 비중

  • EC2 비용을 인스턴스 타입/구매 옵션(Spot/OD)로 분해
  • EKS 노드가 쓰는 ASG/태그 기준으로 필터링

2) Kubernetes 레벨: 노드/파드 효율

  • 노드 CPU/메모리 사용률 평균, P95
  • 파드 pending 시간(스케줄 지연)
  • eviction 횟수, 노드 교체 빈도

3) 장애/품질 지표

  • 배포 중 오류율 증가 여부
  • P95/P99 지연, 타임아웃 비율
  • 워커 재시작 증가(CrashLoopBackOff)

자주 겪는 장애 패턴과 해결책

스케일 아웃이 간헐적으로 실패한다

원인 후보:

  • 인스턴스 타입 후보군이 너무 좁음
  • 특정 AZ에만 서브넷이 열려 있음
  • 보안그룹/서브넷 태그 누락
  • EBS/ENI 제한으로 파드 밀집이 안 됨

대응:

  • 타입/세대/AZ 후보군 확장
  • requests 조정으로 파드 밀도를 개선
  • 노드 부트스트랩/네트워크 설정 점검

드레인이 느려서 중단 시 영향이 크다

원인 후보:

  • terminationGracePeriodSeconds가 너무 큼
  • preStop에서 불필요하게 오래 대기
  • PDB가 너무 엄격해서 eviction이 막힘

대응:

  • 종료 시간을 “필요한 만큼만” 확보
  • PDB를 minAvailable에서 maxUnavailable로 바꾸는 것도 고려

로그 폭주로 노드 디스크가 꽉 차서 축출이 연쇄 발생한다

Spot 전환과 별개로, 노드가 자주 교체되면 로그/디스크 이슈가 더 빨리 드러납니다. 특히 journalctl 로그가 쌓이면 노드 안정성이 급격히 떨어질 수 있습니다. journalctl 로그 폭주로 디스크 찰 때 10분 해결처럼 노드 레벨 로그 정책도 함께 정비하세요.

운영에서 통하는 권장 조합(요약)

  • Spot NodePool: 다양한 인스턴스 타입 + 여러 AZ + taints로 대상 워크로드 제한
  • On-Demand NodePool: 최소 안전망(핵심 파드만 toleration)
  • 워크로드: PDB + graceful shutdown + PriorityClass
  • 중단 대응: NTH로 interruption 감지 후 자동 드레인
  • 비용 최적화: consolidation + requests 현실화

이 조합을 제대로 적용하면, “Spot이라 불안하다”가 아니라 “중단은 자주 오지만 영향은 작다”로 운영 감각이 바뀌고, 그 결과 EKS 노드 비용을 안정적으로 40% 이상 낮출 수 있습니다.

다음 단계 체크리스트

  • 현재 워크로드를 core(OD)와 spot-ok로 분류했는가
  • requests/limits가 실측 기반으로 정리되어 있는가
  • PDB가 드레인을 막지 않으면서도 가용성을 지키는 수준인가
  • NTH 이벤트가 실제로 감지되고, 드레인 로그가 남는가
  • consolidation 이후 지연/오류율이 악화되지 않는가

위 항목을 순서대로 점검하면, 비용 절감과 안정성을 동시에 잡는 Karpenter Spot 운영을 빠르게 정착시킬 수 있습니다.