Kubernetes for ML¶
개요¶
Kubernetes란?¶
Kubernetes(K8s)는 컨테이너화된 애플리케이션의 배포, 스케일링, 관리를 자동화하는 오픈소스 오케스트레이션 플랫폼이다. Google이 내부에서 사용하던 Borg 시스템을 기반으로 2014년 오픈소스로 공개했으며, 현재 CNCF(Cloud Native Computing Foundation)에서 관리한다.
왜 ML 워크로드에 Kubernetes를 사용하는가?¶
ML/AI 워크로드는 다음과 같은 특성 때문에 Kubernetes가 적합하다:
| 특성 | 설명 | K8s 해결책 |
|---|---|---|
| 리소스 집약적 | GPU, 대용량 메모리 필요 | 동적 리소스 할당, GPU 스케줄링 |
| 가변적 워크로드 | 학습(일시적) vs 추론(상시) | Job, Deployment 리소스 분리 |
| 스케일링 필요 | 트래픽/데이터 증가에 대응 | HPA, Karpenter로 자동 스케일링 |
| 재현성 요구 | 동일 환경에서 동일 결과 | 컨테이너화로 환경 일관성 보장 |
| 비용 최적화 | 고가 GPU 효율적 활용 | Spot 인스턴스, 리소스 공유 |
핵심 특징¶
- 선언적 구성: YAML로 원하는 상태를 정의하면 K8s가 자동으로 맞춤
- 자가 치유: Pod 장애 시 자동 재시작/재배포
- 서비스 디스커버리: 내부 DNS로 서비스 간 통신 자동화
- 롤링 업데이트: 무중단 배포 지원
- 시크릿 관리: API 키, 모델 경로 등 민감 정보 안전 관리
1. 핵심 개념¶
1.1 K8s 리소스 계층¶
계층 설명:
- Cluster: 노드들의 집합. 마스터 노드(컨트롤 플레인)와 워커 노드로 구성
- Namespace: 리소스의 논리적 분리. 팀별, 환경별(dev/staging/prod) 구분에 사용
- Workload Resources: 실제 컨테이너를 실행하는 리소스들
- Deployment: 상시 실행 (웹서버, API, 추론 서비스)
- Job/CronJob: 일회성/정기 작업 (학습, 배치 처리)
- StatefulSet: 상태 유지 필요 (분산 학습, 데이터베이스)
- Pod: 컨테이너 실행의 최소 단위. 하나 이상의 컨테이너가 네트워크/스토리지 공유
1.2 ML 워크로드 유형¶
| 리소스 | 용도 | 특징 |
|---|---|---|
| Job | 학습 작업 | 완료 후 종료 |
| Deployment | 추론 서비스 | 상시 실행, 오토스케일링 |
| CronJob | 정기 학습/평가 | 스케줄 기반 |
| StatefulSet | 분산 학습 | 안정적인 네트워크 ID |
2. Pod 기본¶
2.1 ML 추론 Pod¶
apiVersion: v1
kind: Pod
metadata:
name: ml-inference
labels:
app: ml-model
spec:
containers:
- name: inference
image: ml-model:v1.0
ports:
- containerPort: 8000
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
env:
- name: MODEL_PATH
value: "/models/model.pkl"
- name: MLFLOW_TRACKING_URI
valueFrom:
secretKeyRef:
name: ml-secrets
key: mlflow-uri
volumeMounts:
- name: model-storage
mountPath: /models
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: model-storage
persistentVolumeClaim:
claimName: model-pvc
2.2 GPU Pod¶
apiVersion: v1
kind: Pod
metadata:
name: gpu-training
spec:
containers:
- name: training
image: pytorch/pytorch:2.0.1-cuda11.7-cudnn8-runtime
command: ["python", "train.py"]
resources:
limits:
nvidia.com/gpu: 1 # GPU 1개 요청
memory: "32Gi"
cpu: "8"
volumeMounts:
- name: data
mountPath: /data
- name: shm # PyTorch DataLoader를 위한 공유 메모리
mountPath: /dev/shm
volumes:
- name: data
persistentVolumeClaim:
claimName: training-data-pvc
- name: shm
emptyDir:
medium: Memory
sizeLimit: "16Gi"
nodeSelector:
nvidia.com/gpu.product: NVIDIA-A100-SXM4-80GB
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
3. Deployment (모델 서빙)¶
3.1 기본 Deployment¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: ml-model
labels:
app: ml-model
spec:
replicas: 3
selector:
matchLabels:
app: ml-model
template:
metadata:
labels:
app: ml-model
spec:
containers:
- name: model
image: ml-model:v1.0
ports:
- containerPort: 8000
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
env:
- name: MODEL_VERSION
value: "v1.0"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
3.2 Service¶
apiVersion: v1
kind: Service
metadata:
name: ml-model-service
spec:
selector:
app: ml-model
ports:
- port: 80
targetPort: 8000
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ml-model-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
rules:
- host: ml.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ml-model-service
port:
number: 80
3.3 HorizontalPodAutoscaler¶
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ml-model-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ml-model
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
# 커스텀 메트릭 (Prometheus)
- type: Pods
pods:
metric:
name: requests_per_second
target:
type: AverageValue
averageValue: 100
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
4. Job (학습 작업)¶
4.1 기본 학습 Job¶
apiVersion: batch/v1
kind: Job
metadata:
name: training-job
spec:
backoffLimit: 3 # 실패 시 재시도 횟수
activeDeadlineSeconds: 86400 # 24시간 타임아웃
ttlSecondsAfterFinished: 3600 # 완료 후 1시간 뒤 삭제
template:
spec:
restartPolicy: Never
containers:
- name: training
image: ml-training:v1.0
command: ["python", "train.py"]
args:
- "--epochs=100"
- "--batch-size=32"
resources:
limits:
nvidia.com/gpu: 1
memory: "32Gi"
cpu: "8"
volumeMounts:
- name: data
mountPath: /data
- name: output
mountPath: /output
env:
- name: MLFLOW_TRACKING_URI
valueFrom:
secretKeyRef:
name: ml-secrets
key: mlflow-uri
volumes:
- name: data
persistentVolumeClaim:
claimName: training-data
- name: output
persistentVolumeClaim:
claimName: training-output
nodeSelector:
node-type: gpu
4.2 CronJob (정기 재학습)¶
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-retrain
spec:
schedule: "0 2 * * *" # 매일 새벽 2시
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: retrain
image: ml-training:v1.0
command: ["python", "retrain.py"]
env:
- name: DATE
value: "$(date +%Y-%m-%d)"
5. 분산 학습¶
5.1 PyTorch Distributed (StatefulSet)¶
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: pytorch-distributed
spec:
serviceName: pytorch-distributed
replicas: 4 # 워커 수
selector:
matchLabels:
app: pytorch-distributed
template:
metadata:
labels:
app: pytorch-distributed
spec:
containers:
- name: worker
image: pytorch-training:v1.0
command:
- "torchrun"
- "--nnodes=4"
- "--nproc_per_node=1"
- "--rdzv_id=job1"
- "--rdzv_backend=c10d"
- "--rdzv_endpoint=pytorch-distributed-0.pytorch-distributed:29500"
- "train.py"
ports:
- containerPort: 29500
name: rdzv
resources:
limits:
nvidia.com/gpu: 1
memory: "32Gi"
env:
- name: MASTER_ADDR
value: "pytorch-distributed-0.pytorch-distributed"
- name: MASTER_PORT
value: "29500"
---
apiVersion: v1
kind: Service
metadata:
name: pytorch-distributed
spec:
clusterIP: None # Headless Service
selector:
app: pytorch-distributed
ports:
- port: 29500
name: rdzv
5.2 Kubeflow Training Operator¶
apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
name: pytorch-distributed-job
spec:
pytorchReplicaSpecs:
Master:
replicas: 1
restartPolicy: OnFailure
template:
spec:
containers:
- name: pytorch
image: pytorch-training:v1.0
command:
- "python"
- "train.py"
- "--backend=nccl"
resources:
limits:
nvidia.com/gpu: 1
Worker:
replicas: 3
restartPolicy: OnFailure
template:
spec:
containers:
- name: pytorch
image: pytorch-training:v1.0
command:
- "python"
- "train.py"
- "--backend=nccl"
resources:
limits:
nvidia.com/gpu: 1
6. GPU 관리¶
6.1 NVIDIA Device Plugin¶
# 설치
kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.0/nvidia-device-plugin.yml
# 확인
kubectl get nodes -o jsonpath='{.items[*].status.allocatable.nvidia\.com/gpu}'
6.2 GPU 공유 (Time-Slicing)¶
# ConfigMap for time-slicing
apiVersion: v1
kind: ConfigMap
metadata:
name: nvidia-device-plugin-config
namespace: kube-system
data:
config.yaml: |
version: v1
sharing:
timeSlicing:
renameByDefault: false
failRequestsGreaterThanOne: false
resources:
- name: nvidia.com/gpu
replicas: 4 # GPU를 4개로 분할
6.3 GPU 노드 선택¶
# 특정 GPU 모델 선택
nodeSelector:
nvidia.com/gpu.product: NVIDIA-A100-SXM4-80GB
# 또는 Node Affinity
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: nvidia.com/gpu.memory
operator: Gt
values: ["40000"] # 40GB 이상
7. 모니터링¶
7.1 Prometheus + Grafana¶
# ServiceMonitor for ML metrics
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: ml-model-monitor
spec:
selector:
matchLabels:
app: ml-model
endpoints:
- port: metrics
interval: 15s
path: /metrics
7.2 GPU 메트릭 (DCGM Exporter)¶
# DCGM Exporter 설치
helm repo add gpu-helm-charts https://nvidia.github.io/dcgm-exporter/helm-charts
helm install dcgm-exporter gpu-helm-charts/dcgm-exporter
# 주요 메트릭
# DCGM_FI_DEV_GPU_UTIL: GPU 사용률
# DCGM_FI_DEV_MEM_COPY_UTIL: 메모리 대역폭 사용률
# DCGM_FI_DEV_FB_USED: 사용 중인 메모리
# DCGM_FI_DEV_POWER_USAGE: 전력 사용량
8. Karpenter (오토스케일링)¶
8.1 GPU NodePool¶
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: gpu-pool
spec:
template:
spec:
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values: ["g5.xlarge", "g5.2xlarge", "g5.4xlarge"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
nodeClassRef:
name: gpu-node-class
taints:
- key: nvidia.com/gpu
value: "true"
effect: NoSchedule
limits:
cpu: 1000
memory: 4000Gi
nvidia.com/gpu: 100
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 5m
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: gpu-node-class
spec:
amiFamily: AL2
role: KarpenterNodeRole
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: my-cluster
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 200Gi
volumeType: gp3
9. 비용 최적화¶
9.1 노드 비용 구조¶
| 클러스터 구성요소 | 비용 | 최적화 방법 |
|---|---|---|
| EKS Control Plane | \(0.10/시간 (~\)72/월) | 불가 (고정) |
| Worker 노드 | EC2 비용 | Spot, RI, Karpenter |
| NAT Gateway | $0.045/시간 + 데이터 | VPC Endpoint 활용 |
| Load Balancer | $0.025/시간~ | 통합, Ingress 활용 |
| EBS Volumes | $0.10/GB/월 (gp3) | 적절한 크기 설정 |
9.2 Spot 인스턴스 활용¶
# Karpenter로 Spot 우선 사용
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: spot-pool
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"] # Spot 우선 시도
- key: node.kubernetes.io/instance-type
operator: In
values: ["m5.xlarge", "m5.2xlarge", "m6i.xlarge"] # 다양한 타입
limits:
cpu: 1000
disruption:
consolidationPolicy: WhenUnderutilized
consolidateAfter: 30s
9.3 리소스 최적화¶
# 적절한 requests/limits 설정
resources:
requests:
memory: "2Gi" # 실제 사용량 기반
cpu: "500m"
limits:
memory: "4Gi" # requests의 1.5-2배
cpu: "1"
# VPA (Vertical Pod Autoscaler) 사용
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: ml-model-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: ml-model
updatePolicy:
updateMode: "Auto" # 자동으로 리소스 조정
9.4 비용 모니터링¶
# Kubecost 설치
helm install kubecost kubecost/cost-analyzer \
--namespace kubecost \
--create-namespace
# 네임스페이스별 비용 확인
kubectl cost namespace --show-all-resources
# Pod별 비용 확인
kubectl cost pod -n ml-workloads
10. 실무 팁¶
10.1 GPU 워크로드 체크리스트¶
배포 전:
[ ] GPU 드라이버 DaemonSet 확인 (nvidia-device-plugin)
[ ] 노드에 GPU 할당 가능 확인 (nvidia.com/gpu)
[ ] 적절한 nodeSelector/tolerations 설정
[ ] /dev/shm 볼륨 마운트 (PyTorch DataLoader용)
배포 후:
[ ] GPU 사용률 모니터링 (DCGM Exporter)
[ ] OOM 발생 시 batch size 조정
[ ] 분산 학습 시 NCCL 통신 확인
10.2 문제 해결¶
# Pod이 Pending 상태일 때
kubectl describe pod <pod-name>
# Events에서 원인 확인: 리소스 부족, nodeSelector 불일치 등
# GPU가 인식 안 될 때
kubectl logs -n kube-system -l name=nvidia-device-plugin-ds
# 드라이버 버전, CUDA 버전 확인
# OOMKilled 발생 시
kubectl describe pod <pod-name> | grep -A5 "Last State"
# limits.memory 증가 또는 batch size 감소
# 이미지 풀 실패 시
kubectl describe pod <pod-name> | grep -A10 "Events"
# ECR 인증, 이미지 태그, 네트워크 확인
10.3 보안 Best Practices¶
# 1. 비root 사용자
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
# 2. 읽기 전용 파일시스템
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
# 3. 네트워크 정책
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ml-model-policy
spec:
podSelector:
matchLabels:
app: ml-model
ingress:
- from:
- podSelector:
matchLabels:
role: api-gateway
ports:
- port: 8000
10.4 프로덕션 배포 체크리스트¶
[ ] Liveness/Readiness Probe 설정
[ ] Resource requests/limits 설정
[ ] PodDisruptionBudget 설정
[ ] HorizontalPodAutoscaler 설정
[ ] 로그 수집 (Fluentd/Fluent Bit)
[ ] 메트릭 수집 (Prometheus)
[ ] 알람 설정 (에러율, 레이턴시)
[ ] 롤링 업데이트 전략 설정
[ ] Secrets 암호화 (External Secrets Operator)