Kubernetes Küme Yönetiminde GPU İsrafını Önleme ve Maliyet Optimizasyonu
Biz SRE’lerin kabusu genelde bellek sızıntıları ya da çöken DNS servisleridir sanıyorduk. Yanılmışız. Asıl kabus, şirketin yapay zeka trenine binmesiyle başlayan ve ay sonunda CFO’nun masasında patlayan o meşhur bulut faturasıymış. Kubernetes GPU yönetimi ve beraberinde gelen astronomik maliyetler, modern devops’un yeni canavarı. Veri bilimcilerin “Deney yapıyorum” diyerek rezerve ettiği ama günün %95’inde boş yatan A100’leri görünce saç baş yoluyorsanız, doğru yerdesiniz. Bu yazıda GPU monitoring ve k8s cost optimization tekniklerini kullanarak GPU israfını nasıl önleyeceğimizi inceliyoruz. Teoriyi geçip doğrudan üretim ortamında uygulayabileceğiniz konfigürasyonlara bakacağız.
Göremediğin şeyi optimize edemezsin: GPU izleme altyapısı
Kubernetes’in native metrik sunucusu (metrics-server) CPU ve bellekte iyi iş çıkarsa da GPU’dan tamamen habersizdir. Bir podun GPU talep etmesi ve Kubernetes scheduler’ın o poda bir GPU ataması, o GPU’nun gerçekten kullanıldığı anlamına gelmez. Bizim için kritik iki metrik var: GPU Duty Cycle (hesaplama gücü kullanımı) ve Framebuffer (VRAM) kullanımı.
Bu metrikleri toplamak için NVIDIA’nın DCGM Exporter aracını kümemize kurmamız gerekiyor. Prometheus ile entegre çalışan bu ajan, pod seviyesinde GPU kullanım detaylarını verir.
DCGM Exporter kurulumu ve Prometheus entegrasyonu
Aşağıdaki Helm komutuyla DCGM Exporter’ı kümenize hızlıca dahil edebilirsiniz. Burada kritik nokta, servis izleme (ServiceMonitor) özelliğini aktif etmektir:
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
helm install --namespace gpu-operator \
--create-namespace \
--set dcgmExporter.serviceMonitor.enabled=true \
gpu-operator nvidia/gpu-operator
Kurulum tamamlandıktan sonra Prometheus üzerinde şu sorguyu (PromQL) çalıştırarak rezerve edilmiş ama kullanılmayan (idle) GPU’ları anında tespit edebilirsiniz:
# Son 1 saat içinde ortalama GPU kullanımı %5'in altında olan ve pod tarafından rezerve edilmiş GPU'lar
avg_over_time(DCGM_FI_DEV_GPU_UTIL[1h]) < 5 and on(pod) kube_pod_container_resource_requests{resource="nvidia.com/gpu"} > 0
Yalnızca DCGM_FI_DEV_GPU_UTIL metriğine bakmak yanıltıcı olabilir, çünkü bu metrik sistemde başıboş duran, hiçbir podun istemediği GPU’ları da listeler. Bizim amacımız, bir geliştirici tarafından rezerve edilip kilitlenmiş ama kullanılmayan “israf” kaynakları yakalamak.
GPU bölümleme teknolojileri: MIG ve Time-Slicing
Her podun koca bir A100 veya L4 GPU’ya ihtiyacı yoktur. Basit bir model çıkarımı (inference) ya da hafif bir Jupyter Notebook hücresi için koca bir donanımı kapatmak tam bir kaynak israfıdır. Kubernetes’te GPU’ları bölmenin iki popüler yolu var: donanımsal MIG (Multi-Instance GPU) ve yazılımsal Time-Slicing.
1. Donanımsal bölümleme: NVIDIA MIG
MIG, fiziksel bir GPU’yu tamamen izole edilmiş mini GPU’lara (instance) böler. Bellek ve hata toleransı donanım seviyesinde izole edilir, yani bir podun çökmesi diğer “slice”ı etkilemez. A100 ve H100 gibi enterprise kartlarda desteklenir.
GPU Operator yüklü bir kümede MIG profilini aktif etmek için node üzerindeki etiketi değiştirmek yeterlidir:
kubectl label nodes <node-adi> nvidia.com/mig.config=all-1g.10gb --overwrite
Bu komut, uyumlu bir GPU’yu her biri 10GB VRAM’e sahip bağımsız parçalara böler. Podlarınız artık nvidia.com/gpu yerine doğrudan bu profilleri isteyebilir:
resources:
limits:
nvidia.com/mig-1g.10gb: 1
2. Yazılımsal bölümleme: GPU Time-Slicing
Elinizde MIG desteklemeyen (örneğin T4, L4 veya RTX serisi) kartlar varsa kurtarıcınız Time-Slicing. Bu yöntem GPU üzerinde zaman paylaşımlı (interleaved) çalışmayı sağlar. Bellek izolasyonu yoktur (biri hata yaparsa OOM olur), ama dev/staging ortamları için can kurtarır.
Time-slicing yapılandırması için aşağıdaki ConfigMap’i oluşturup GPU Operator’e bildirmemiz gerekir:
apiVersion: v1
kind: ConfigMap
metadata:
name: device-plugin-config
namespace: gpu-operator-resources
data:
any-name: |-
version: v1
sharing:
timeSlicing:
resources:
- name: nvidia.com/gpu
replicas: 4
Bu konfigürasyon, Kubernetes scheduler’a fiziksel olarak 1 adet olan GPU’yu sanal olarak 4 adetmiş gibi gösterir. Böylece 4 farklı pod aynı GPU’yu paylaşarak çalışabilir.
Karpenter ve dynamic autoscaling ile scale-to-zero
GPU makineleri pahalıdır. Gecenin bir yarısı çalışmayan bir eğitim (training) podu için AWS ya da GCP üzerinde bir GPU makinesinin açık kalması, sabaha kadar dolar yakmak demektir. Kubernetes Cluster Autoscaler yerine Karpenter kullanmak GPU maliyet optimizasyonunda çağ atlatır.
Karpenter, podun taints/tolerations ve kaynak isteklerine doğrudan bakarak milisaniyeler içinde doğru boyuttaki GPU makinesini (Spot veya On-Demand) ayağa kaldırabilir ve iş bittiğinde node’u anında yok edebilir (scale-to-zero).
Karpenter NodePool GPU konfigürasyonu
GPU iş yüklerinizi Spot instance’lar üzerinde koşturmak faturanızı %70’e varan oranda düşürür. İşte Karpenter için optimize edilmiş bir GPU NodePool tanımı:
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: gpu-spot-pool
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: karpenter.k8s.aws/instance-gpu-manufacturer
operator: In
values: ["nvidia"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["g4dn.xlarge", "g5.xlarge"]
taints:
- key: nvidia.com/gpu
value: "true"
effect: NoSchedule
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 30s
Buradaki ince ayar disruption.consolidationPolicy: WhenEmpty ve consolidateAfter: 30s parametreleridir. GPU üzerindeki iş bitip pod silindiği anda Karpenter 30 saniye içinde o pahalı makineyi kapatır. Geleneksel autoscaler’lardaki 10 dakikalık varsayılan bekleme süresini bypass etmiş oluruz.
Uygulama seviyesinde optimizasyon: MPS (Multi-Process Service)
Yoğun bir model çıkarım (inference) API’si çalıştırıyorsanız ve kuyrukta binlerce istek bekliyorsa, Time-Slicing’in yarattığı bağlam geçişi (context switching) yükü CPU’yu ve GPU’yu yorar. NVIDIA MPS, tek bir GPU üzerinde birden fazla CUDA işleminin eşzamanlı ve sıfıra yakın overhead ile çalışmasını sağlar.
Kubernetes üzerinde MPS kullanmak için pod konfigürasyonunuza NVIDIA’nın şu ortam değişkenini eklemeniz yeterlidir:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mps-inference-deployment
spec:
replicas: 3
template:
spec:
containers:
- name: model-server
image: my-inference-model:v1
env:
- name: CUDA_MPS_ACTIVE_THREAD_PERCENTAGE
value: "33" # Donanım gücünün %33'ünü sınırla
resources:
limits:
nvidia.com/gpu: 1
Bu sayede aynı GPU’yu kullanan 3 pod, donanım kaynaklarını birbirini bloklamadan, sanki paralel donanımları varmış gibi yüksek performansla kullanır.
Özet ve “ne yapmalı?” listesi
Kubernetes üzerinde GPU optimizasyonu tek hamleyle çözülecek bir iş değildir. Katmanlı bir yaklaşım gerektirir:
- İzleme: DCGM Exporter kurun ve boşta yatan (idle) GPU’ları alarm sisteminize (Alertmanager) bağlayın.
- Paylaşım: Geliştirme ortamlarında Time-Slicing, üretim ortamlarında ise izolasyon için MIG tercih edin.
- Autoscaling: Karpenter ile scale-to-zero uygulayın. İş bittiğinde sunucunun anında kapandığından emin olun.
- Satın alma: Model eğitimi gibi kesintiye dayanıklı işlerde kesinlikle Spot/Preemptible instance kullanın.
Doğru konfigüre edilmiş bir Kubernetes kümesi, AI projelerinizin hızından ödün vermeden bulut faturalarınızı yönetilebilir seviyelerde tutmanın tek yoludur.