IT/AWS

Karpenter NodeOverlay로 GPU spot 가격 흔들림 잡아보다 (alpha 도입 보류한 이야기)

gfrog 2026. 5. 29. 18:43
반응형

Karpenter NodeOverlay로 GPU spot 가격 흔들림 잡아보다 (alpha 도입 보류한 이야기)

지난주에 GPU 학습 워크로드 비용 그래프를 보다가 멘탈이 좀 나갔다. 분명 spot으로 돌리고 있는데 월 비용이 예상의 1.6배. 클러스터에 들어가서 노드 목록을 찍어보니, Karpenter가 골라준 인스턴스 타입이 죄다 g5.12xlarge, g6e.12xlarge 같은 큰 놈들이었다. 우리가 돌리는 잡 사이즈 보면 g5.2xlarge 두세 대면 충분한데.

원인을 찾는 데 한참 걸렸다. 결론부터 말하면 Karpenter의 spot 할당 전략 — price-capacity-optimized — 가 큰 인스턴스를 "더 싸다"고 판단한 결과였다. 단위 vCPU당 가격이 큰 인스턴스가 낮긴 한데, 우리 워크로드 입장에서는 vCPU/GPU 비율이 맞지 않아서 그냥 비용만 더 나가는 상황.

처음엔 NodePool requirements로 풀어보려 했다

가장 먼저 떠올린 건 NodePool에서 인스턴스 타입을 못 박는 거였다. 이렇게:

requirements:
  - key: node.kubernetes.io/instance-type
    operator: In
    values: ["g5.2xlarge", "g5.4xlarge", "g6.2xlarge"]

근데 이렇게 좁히면 spot 가용성이 박살난다. 어느 AZ에서 g5.2xlarge spot이 안 잡히면 그냥 펜딩 상태로 누워있는다. 새벽 3시에 학습 잡 펜딩 알람 보고 싶지 않다. 한 시간 정도 g5/g6 패밀리 전부 풀어주고 가중치만 줄 방법을 찾다가 NodeOverlay라는 게 있다는 걸 알게 됐다.

NodeOverlay가 정확히 풀어주는 문제

Karpenter v1.1부터 alpha로 들어온 기능이다. 핵심은 "특정 인스턴스 타입에 대해 가격 시그널을 임의로 조정"할 수 있다는 점이다. 예를 들면 g5.12xlarge에 가격 가중치를 1.3배로 주면, Karpenter가 견적을 낼 때 그 타입을 30% 더 비싸다고 인식해서 우선순위를 낮춘다. 실제 청구되는 가격은 그대로지만, 스케줄링 결정에는 영향을 주는 식이다.

apiVersion: karpenter.sh/v1alpha1
kind: NodeOverlay
metadata:
  name: discourage-big-gpu
spec:
  requirements:
    - key: node.kubernetes.io/instance-type
      operator: In
      values: ["g5.12xlarge", "g5.24xlarge", "g6e.12xlarge", "g6e.24xlarge"]
  priceAdjustment: "+30%"

여기서 한 가지 알아야 할 게 있다. NodeOverlay를 spot에 적용하면 Karpenter 내부 할당 전략이 price-capacity-optimized에서 capacity-optimized-prioritized로 바뀐다. 가격 조정값이 EC2 Fleet API에 priority로 전달되고, EC2는 그 priority를 보고 골라준다. 가용성보다 우리가 준 우선순위가 더 세지는 건 아니고, capacity가 충분한 것 중에서 priority 높은 걸 고르는 식이다.

이게 사실 우리한테는 더 잘 맞았다. price-capacity-optimized는 "interruption 최소화"가 1순위인데, 학습 워크로드는 어차피 checkpoint 잘 찍고 있어서 interruption 좀 일어나도 괜찮다. 비용이 더 중요했다.

그래서 도입했냐 — 안 했다

테스트 클러스터에서 일주일 돌려봤다. 결과는 좋았다. g5.2xlarge / g5.4xlarge 비율이 80% 넘게 잡혔고, 비용 시뮬레이션 상 월 24% 절감 예상. 근데 production 적용 직전에 팀 내부 논의에서 막혔다.

쟁점은 v1alpha1이라는 점이었다. CRD schema가 다음 마이너 업그레이드에서 바뀔 수 있고, 실제로 Karpenter 이슈 트래커 보면 NodeOverlay provisioning 동작에 대한 토론이 한창이다. 우리 팀 EKS 업그레이드 주기가 분기마다 한 번인데, 매번 NodeOverlay manifest 재검증을 routine에 넣어야 한다는 게 부담이었다.

또 하나, NodeOverlay alpha 기간 동안 발견된 엣지 케이스 — pricing override가 multi-AZ에서 일관되지 않게 동작하는 경우가 있다는 보고가 GitHub에 올라와 있다. 우리 클러스터는 3-AZ인데 이 부분이 영향이 있을지 추가 테스트가 필요했다.

결국 결정은 "도입 보류, 다음 분기 재검토"였다. 대신 임시방편으로 이렇게 갔다:

# 큰 GPU 인스턴스를 아예 후보에서 제외
requirements:
  - key: karpenter.k8s.aws/instance-gpu-count
    operator: In
    values: ["1", "4"]   # 단일 GPU 또는 4-GPU만
  - key: node.kubernetes.io/instance-type
    operator: NotIn
    values: ["g5.12xlarge", "g5.24xlarge", "g5.48xlarge"]

GPU 개수로 한 번 거르고, 그래도 끼어드는 큰 놈들은 NotIn으로 명시적으로 빼는 식. spot 가용성은 약간 손해 보긴 하는데, 그래도 NodeOverlay alpha 들이는 것보단 운영 부담이 적다.

정리

  • NodeOverlay는 spot price-capacity-optimized 전략이 워크로드와 안 맞을 때 진짜 좋은 도구다
  • 다만 v1alpha1이라는 점은 가볍게 볼 게 아니다. CRD schema 변경, 엣지 케이스 보고를 매 업그레이드마다 확인할 여유가 있는 팀에만 권한다
  • 우리처럼 분기 업그레이드 + 학습 워크로드 같은 케이스라면, GPU 개수 기반 필터링 + NotIn 조합이 당장은 안전한 절충안

다음 분기에 v1beta1 승격되면 그때 다시 들여다볼 예정이다. 혹시 production에서 NodeOverlay alpha 돌리고 계신 분 있으면 어떻게 운영 부담 줄였는지 댓글로 좀 알려주세요. 진심 궁금합니다.

반응형