
인공지능(AI)과 딥러닝 모델의 규모가 거대해짐에 따라, 클라우드 네이티브 환경인 Kubernetes(K8s)에서 고가의 자원인 GPU를 얼마나 효율적으로 관리하느냐가 운영 비용 절감과 성능 최적화의 핵심이 되었습니다. 단순히 GPU가 장착된 노드를 클러스터에 추가하는 것만으로는 부족합니다. 특정 워크로드가 고성능 GPU(예: H100, A100)를 점유하도록 유도하거나, 일반 CPU 워크로드가 GPU 노드의 자원을 낭비하지 않도록 격리하는 정교한 스케줄링 전략이 필요합니다. 본 가이드에서는 파이썬 기반의 머신러닝 파이프라인을 운영하는 엔지니어를 위해 Node Affinity, Taints, 그리고 Tolerations를 조합하여 GPU 리소스를 완벽하게 제어하는 실무적인 해결 방법을 제시합니다.
1. GPU 스케줄링의 핵심 개념 및 매커니즘 차이
Kubernetes에서 워크로드를 특정 노드에 배치하거나 배제하기 위해 사용하는 핵심 메커니즘은 크게 세 가지입니다. 각 기능의 목적과 차이점을 이해하는 것이 첫 번째 단계입니다.
| 구분 | Node Affinity (노드 어피니티) | Taints & Tolerations (테인트와 톨러레이션) | Node Selector (노드 셀렉터) |
|---|---|---|---|
| 주요 목적 | 특정 노드로 팟(Pod)을 끌어당김 (Attraction) | 특정 노드에서 팟을 밀어냄 (Repulsion) | 가장 단순한 레이블 기반 매칭 |
| 설정 대상 | Pod 스펙에 정의 | Node에 Taint 설정, Pod에 Toleration 설정 | Pod 스펙에 정의 |
| 유연성 | Soft(Preferred) 및 Hard(Required) 설정 가능 | 강력한 격리 및 배제 기능 제공 | 매우 낮음 (엄격한 일치만 허용) |
| GPU 활용 시나리오 | A100 GPU가 있는 노드에 우선 배정 시 활용 | 일반 웹 서비스가 GPU 노드에 뜨지 못하게 방어 | 단순 테스트용 GPU 할당 |
2. 실무 적용을 위한 7가지 Python 및 YAML 구현 예제
실제 프로덕션 환경에서 바로 적용할 수 있는 구성 예시입니다. NVIDIA GPU Operator가 설치되어 있다고 가정하며, 파이썬 클라이언트 라이브러리(kubernetes-python)를 통한 자동화 스크립트도 포함합니다.
예제 1: Required Node Affinity를 이용한 특정 GPU 모델(A100) 강제 할당
반드시 특정 GPU 모델에서만 실행되어야 하는 학습 작업의 경우 'requiredDuringSchedulingIgnoredDuringExecution'을 사용합니다.
apiVersion: v1
kind: Pod
metadata:
name: gpu-training-a100
spec:
containers:
- name: training-container
image: nvidia/cuda:12.0-base
resources:
limits:
nvidia.com/gpu: 1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
Kam-
- key: nvidia.com/gpu.product
operator: In
values:
- NVIDIA-A100-SXM4-40GB
예제 2: Taints와 Tolerations를 이용한 GPU 노드 전 전용 격리 방법
GPU 노드에 테인트를 설정하여, 명시적으로 허용(Toleration)하지 않은 일반 팟들이 GPU 자원을 점유하지 못하도록 차단합니다.
# 노드에 테인트 설정 명령
# kubectl taint nodes gpu-node-01 gpu=true:NoSchedule
apiVersion: v1
kind: Pod
metadata:
name: isolated-gpu-pod
spec:
containers:
- name: ml-app
image: my-ml-repo:latest
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
예제 3: Python 클라이언트를 이용한 동적 GPU 워크로드 생성
파이썬 코드로 API를 호출하여 상황에 따라 Affinity가 설정된 팟을 배포하는 방법입니다.
from kubernetes import client, config
def create_gpu_pod():
config.load_kube_config()
api = client.CoreV1Api()
pod = client.V1Pod(
metadata=client.V1ObjectMeta(name="dynamic-gpu-pod"),
spec=client.V1PodSpec(
containers=[client.V1Container(
name="cuda-container",
image="nvidia/cuda:11.0-base",
resources=client.V1ResourceRequirements(limits={"nvidia.com/gpu": "1"})
)],
affinity=client.V1Affinity(
node_affinity=client.V1NodeAffinity(
required_during_scheduling_ignored_during_execution=client.V1NodeSelector(
node_selector_terms=[client.V1NodeSelectorTerm(
match_expressions=[client.V1NodeSelectorRequirement(
key="nvidia.com/gpu.family",
operator="In",
values=["ampere"]
)]
)]
)
)
)
)
)
api.create_namespaced_pod(namespace="default", body=pod)
create_gpu_pod()
예제 4: Preferred Affinity를 활용한 비용 최적화(Spot 인스턴스 유도)
가급적이면 저렴한 Spot 인스턴스 노드에 배치하되, 없으면 일반 노드에 배치하는 유연한 설정입니다.
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: capability/instance-type
operator: In
values:
- spot
예제 5: 다중 GPU 제조사 환경에서의 가용 영역(AZ) 고려 스케줄링
특정 가용 영역(AZ)에 있는 노드 중 GPU가 있는 곳을 선택하는 복합 설정입니다.
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1a
- key: nvidia.com/gpu.present
operator: In
values:
- "true"
예제 6: Python을 활용한 노드 Labeling 및 Tainting 자동화 스크립트
클러스터 관리 자동화를 위해 특정 조건의 노드에 태그를 입히는 관리자용 스크립트 예시입니다.
from kubernetes import client, config
def label_gpu_nodes():
config.load_kube_config()
v1 = client.CoreV1Api()
nodes = v1.list_node().items
for node in nodes:
if 'nvidia.com/gpu.count' in node.metadata.labels:
body = {
"metadata": {"labels": {"workload-type": "heavy-gpu"}},
"spec": {"taints": [{"effect": "NoSchedule", "key": "dedicated", "value": "gpu"}]}
}
v1.patch_node(node.metadata.name, body)
print(f"Node {node.metadata.name} has been hardened for GPU tasks.")
label_gpu_nodes()
예제 7: Anti-Affinity를 이용한 고가용성(HA) GPU 서비스 구성
동일한 GPU 모델을 사용하는 팟들이 서로 다른 노드에 분산 배치되도록 하여 장애 내성을 높이는 설정입니다.
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- inference-server
topologyKey: "kubernetes.io/hostname"
3. 성능 최적화 및 문제 해결 전략
GPU 스케줄링 시 흔히 발생하는 문제는 'Pending' 상태의 지속입니다. 이는 대개 Toleration 누락이나 자원 부족 때문입니다. 이를 해결하기 위해 다음의 3가지 체크리스트를 점검하십시오.
- Describe Pod 확인:
kubectl describe pod [NAME]명령어를 통해 'Events' 섹션에서 Affinity 거부 사유를 확인합니다. - Resource Quota 점검: 네임스페이스에 할당된
requests.nvidia.com/gpu한도가 초과되지 않았는지 확인합니다. - NVIDIA Device Plugin 상태: 노드에서 GPU 장치를 인식하지 못할 경우 Affinity 설정과 상관없이 배치가 불가능하므로 데몬셋(DaemonSet) 상태를 확인합니다.
4. 결론: 효율적인 GPU 인프라 운영을 위한 제언
Kubernetes 환경에서 GPU는 단순한 하드웨어가 아니라, 소프트웨어적으로 세밀하게 관리되어야 하는 핵심 자산입니다. Node Affinity를 통해 '선택'하고, Taints/Tolerations를 통해 '보호'하는 전략을 병행할 때 비로소 인프라 비용 효율성을 극대화할 수 있습니다. 특히 Python 기반의 자동화 도구를 활용하여 클러스터 상태에 따라 동적으로 워크로드를 배치하는 방식은 대규모 AI 서비스 운영에서 필수적인 역량입니다.
이 가이드에서 소개한 7가지 패턴을 바탕으로 귀사의 클러스터 성격에 맞는 최적의 스케줄링 정책을 수립해 보시기 바랍니다.
출처 및 참고문헌:
1. Kubernetes Documentation: Assigning Pods to Nodes (2024)
2. NVIDIA Cloud Native Documentation: GPU Operator Guide (2024)
3. Google Cloud Architecture Framework: Cost Optimization for GKE (2023)
4. Python Kubernetes Client Official Repository API Reference
Python, Kubernetes, GPU Scheduling, Node Affinity, Taints and Tolerations, MLOps, Resource Management```