kubernetes
쿠버네티스 클러스터를 운영하다 보면, CPU 워크로드와 GPU 워크로드가 뒤섞여 배포되는 경우가 많습니다. 특히 ML/딥러닝 모델 학습이나 추론 파이프라인에서는 GPU 리소스가 안정적으로 할당되어야 성능과 비용을 모두 잡을 수 있습니다. 아래의 궁금증을 고민해 봅시다.
Karpenter를 활용해 이러한 문제를 해결하는 방법을 단계별로 다뤄봅니다. 정리해보면, 이번글에서는 아래의 내용을 다루게 됩니다.
GPU 전용 NodePool을 만들기 전에 몇 가지 준비가 필요합니다. 우선, Karpenter가 이미 클러스터에 설치되어 있어야 하고, 이전 단계에서 설정했던 것처럼 기본적인 EC2NodeClass(default)와 클러스터 태깅 작업도 완료되어 있어야 합니다.
또한 GPU를 활용하려면 GPU 드라이버와 NVIDIA Device Plugin이 반드시 필요합니다. 가장 쉬운 방법은 AWS에서 제공하는 EKS Optimized GPU AMI를 사용하는 것으로, 이러한 GPU 전용 AMI에는 NVIDIA 드라이버와 CUDA 라이브러리가 이미 내장돼 있습니다. 따라서 노드 생성 시 별도 드라이버를 설치 할 필요가 없습니다. 만약 일반 AMI를 사용한다면, GPU 드라이버와 CUDA가 포함되어 있지 않게 때문에 GPU 인스턴스를 띄우더라도, 드라이버가 없어서 쿠버네티스 Pod에서 nvidia.com/gpu 리소스를 인식하지 못합니다. 따라서 인스턴스가 부팅될 때 스크립트(부팅 시 NVIDIA 드라이서 다운/설치)나 DaemonSet을 통해 드라이버를 직접 설치해야 합니다.
이번 예시에서는 GPU 전용 NodePool을 따로 구성하기 위해 gpu-class라는 이름의 전용 NodeClass를 만들고, 이후 GPU NodePool이 이 NodeClass를 참조하는 방식으로 설계합니다.
┌──────────────────────────────────┐
│ EC2NodeClass (gpu-class) │ ← GPU AMI/보안그룹/서브넷
└───────────────┬──────────────────┘
│ nodeClassRef
┌─────────▼─────────┐
│ NodePool: gpu │ ← p/g 계열 + Spot/OD 허용 + limits
└─────────┬─────────┘
│
GPU 워크로드 (toleration, nvidia.com/gpu 요청)
EC2NodeClass는 어떤 EC2 인스턴스를 Karpenter가 생성할 수 있는지를 정의하는 객체입니다. 여기서는 GPU 전용 AMI를 지정하고, 서브넷/보안그룹을 클러스터 태그 기반으로 선택합니다.
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: gpu-class
spec:
# GPU AMI 권장: EKS Optimized GPU AMI (리전별 AMI ID 삽입)
# 일반 AMI 사용 시, 반드시 NVIDIA 드라이버를 설치해야 합니다.
amiFamily: AL2
amiSelectorTerms:
- id: "ami-xxxxxxxxxxxxxxxxx" # ← 리전별 EKS GPU AMI ID로 교체
role: "KarpenterNodeRole-<YOUR-CLUSTER-NAME>" # ← 교체
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: "<YOUR-CLUSTER-NAME>" # ← 교체
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: "<YOUR-CLUSTER-NAME>"
NodePool은 어떤 조건에서 노드를 스케줄할지, 어떤 제약을 둘지 설정하는 리소스입니다. 여기서는 GPU 워크로드 전용 풀을 만들고, Spot/On-Demand 혼합 사용과 전체 자원 상한을 지정했습니다.
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: gpu-pool
spec:
template:
metadata:
labels:
nodeclass: gpu
workload: ml
accelerator: nvidia
spec:
nodeClassRef:
name: gpu-class
# ML/GPU 전용으로 고립
taints:
- key: workload
value: ml
effect: NoSchedule
requirements:
# GPU 계열만 허용 (p/g)
- key: karpenter.k8s.aws/instance-family
operator: In
values: ["p", "g"]
# 필요 시 세대 제한(예: 4세대 초과만)
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["4"]
# Spot과 On-Demand 혼합 허용
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
# 아키텍처 (대부분 amd64)
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
disruption:
consolidationPolicy: WhenUnderutilized
consolidateAfter: 30s
# 풀 전체 자원 상한: 과다 확장 방지
limits:
resources:
nvidia.com/gpu: "8" # 풀 전체 GPU 합계 상한(예: 8개)
cpu: "256" # 선택: CPU 상한 (vCPU 합)
batch/v1
는 쿠버네티스의 Batch API 그룹이고, Job·CronJob 같은 배치 워크로드를 다룹니다. Job은 쿠버네티스에서 “정해진 작업을 완료할 때까지 Pod을 실행하는 컨트롤러”입니다. 아래는 Job 형태로 GPU 리소스를 요청하는 간단한 예시입니다.
apiVersion: batch/v1
kind: Job
metadata:
name: gpu-render
spec:
template:
spec:
# GPU 전용 풀에만 스케줄되도록
tolerations:
- key: "workload"
operator: "Equal"
value: "ml"
effect: "NoSchedule"
containers:
- name: nvidia-smi
image: nvidia/cuda:12.3.1-base-ubuntu22.04 # 멀티-아키 확인 필요(amd64)
command: ["bash", "-lc", "nvidia-smi && sleep 60"]
resources:
limits:
nvidia.com/gpu: 1
restartPolicy: Never
드라이버가 설치된 상태라고 해도, 쿠버네티스 스케줄러가 GPU 리소스를 인식하려면 Device Plugin이 필요합니다. NVIDIA가 제공하는 공식 DaemonSet을 배포하면 자동으로 GPU를 kubelet에 등록합니다. kubectl apply 한 번으로 설치 가능하며, GPU 노드마다 자동으로 Pod이 배포됩니다.
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.5/nvidia-device-plugin.yml
순서를 정리하면 아래와 같습니다.
# (사전) NVIDIA Device Plugin 설치
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.5/nvidia-device-plugin.yml
kubectl -n kube-system rollout status ds/nvidia-device-plugin-daemonset --timeout=2m
# 1) GPU 전용 NodeClass
kubectl apply -f ec2nodeclass-gpu.yaml
# 2) GPU 전용 NodePool
kubectl apply -f nodepool-gpu.yaml
kubectl get nodepool gpu-pool -o wide
# 3) GPU 워크로드(Job) 배포 → Pending이 스케일업 트리거
kubectl apply -f job-gpu.yaml
먼저, 쿠버네티스가 GPU 리소스를 인식하려면 NVIDIA Device Plugin이 꼭 필요합니다. 이 플러그인은 GPU가 있는 노드마다 자동으로 뜨면서 쿠버네티스에 nvidia.com/gpu라는 리소스를 등록해줍니다. 그래서 제일 먼저 플러그인을 설치하고, DaemonSet이 정상적으로 배포됐는지 확인합니다.
그다음 단계는 GPU 전용 NodeClass를 만드는 것입니다. 여기에는 어떤 AMI를 쓸지, 어떤 서브넷과 보안그룹에 노드를 띄울지를 정의합니다. 준비된 YAML(ec2nodeclass-gpu.yaml)을 적용하면 됩니다.
NodeClass를 기반으로 실제 스케일링 단위가 되는 GPU NodePool을 생성합니다. NodePool에는 GPU 계열 인스턴스만 쓰도록 제한, Spot/OnDemand 혼합 사용 여부, 풀 전체 리소스 상한 같은 정책이 들어 있습니다. nodepool-gpu.yaml을 적용한 뒤에는 kubectl get nodepool로 상태를 확인할 수 있습니다.
마지막으로, **GPU 워크로드(Job)**을 배포합니다. Job은 처음에는 Pending 상태로 대기하다가, Karpenter가 이를 감지하고 GPU 전용 노드를 자동으로 띄웁니다. 노드가 준비되면 Job이 해당 노드에서 실행되며 실제로 GPU가 정상적으로 잡히는지 확인할 수 있습니다.
# (1) Karpenter가 새 노드를 만들었는지
kubectl get nodes -l nodeclass=gpu -o wide
# (2) Taint/Label이 맞는지 (GPU 전용 풀 강제)
kubectl describe node <gpu-node> | grep -i -E 'Taints|accelerator|workload'
# (3) GPU 리소스가 노드에 보이는지
kubectl describe node <gpu-node> | grep -i 'nvidia.com/gpu'
# (4) Device Plugin 정상 동작
kubectl -n kube-system get ds | grep -i nvidia
# (5) Job이 GPU에서 돌고 nvidia-smi 출력했는지
kubectl get pods -l job-name=gpu-render -o wide
kubectl logs job/gpu-render
먼저, GPU 워크로드를 배포한 뒤에는 Karpenter가 실제로 새로운 GPU 노드를 띄웠는지 확인해야 합니다. 이를 위해 kubectl get nodes 명령으로 nodeclass=gpu 라벨이 붙은 노드가 있는지 조회합니다.
노드가 뜬 게 보이면, 그 노드에 GPU 전용 풀 설정이 제대로 적용됐는지 점검합니다. kubectl describe node로 해당 노드를 살펴보면 Taints와 Labels가 나오는데, 여기서 workload=ml 같은 테인트가 걸려 있는지, accelerator=nvidia 같은 라벨이 붙어 있는지를 확인합니다. 이게 맞아야 GPU 전용 노드로 강제할 수 있습니다.
다음은 실제 GPU 리소스가 잡혔는지 보는 단계입니다. 같은 describe 결과 안에 nvidia.com/gpu 항목이 Capacity/Allocatable 섹션에 표시돼야 합니다. 표시되지 않는다면 드라이버나 플러그인이 문제일 수 있습니다.
이어서 Device Plugin이 정상 동작 중인지 확인합니다. kubectl get ds -n kube-system으로 DaemonSet 목록을 조회하면 nvidia-device-plugin-daemonset이 떠 있어야 하고, 상태가 Running이어야 합니다.
마지막으로, 내가 실행한 Job이 정말 GPU 노드에서 돌고 있는지, 그리고 nvidia-smi 출력이 로그에 찍혔는지를 확인합니다. Pod가 어떤 노드에 배치됐는지 확인한 뒤, kub
이번 글에서는 Karpenter를 활용해 GPU 전용 NodePool을 구축하는 과정을 살펴봤습니다. GPU 전용 AMI와 Device Plugin을 준비하고, NodeClass와 NodePool을 정의한 뒤, 실제 GPU Job을 배포해 동작을 검증하는 단계까지 진행했습니다. 이를 통해 CPU 워크로드와 GPU 워크로드를 명확하게 분리할 수 있고, Spot과 On-Demand 인스턴스를 혼합해 비용 효율성과 안정성을 동시에 확보할 수 있습니다. 또한 NodePool 전체 자원에 상한선을 걸어 두면, 과도한 확장을 방지하면서도 필요한 시점에만 GPU 자원이 자동으로 할당됩니다.
결국 이 방식은 ML/딥러닝 워크로드를 운영할 때 성능·비용·안정성을 모두 고려할 수 있는 실용적인 전략이 됩니다. GPU가 필요한 시점에만 노드가 생성되고, 사용하지 않을 때는 자동으로 정리되므로, 관리자의 부담을 크게 줄일 수 있습니다. 앞으로 GPU 워크로드를 클라우드 환경에서 안정적으로 운영하고자 한다면, Karpenter 기반의 GPU 전용 NodePool 설계가 좋은 출발점이 될 것입니다.