GPU Üzerinde TensorFlow Serving'i Flux CD ile Dağıtmak

Selamlar, bu yazımda TensorFlow Serving'i GPU node'ları üzerinde Flux CD ile nasıl dağıttığımıza bakacağız. ML modellerini production'a almak başlı başına ayrı bir dert; bir de işin içine GPU, model versiyonu, kaynak ayarı, drift gibi şeyler girince elle yönetmek hızla kabusa dönüşüyor. Bence bu noktada GitOps şart, lafı çok uzatmadan girelim konuya.

Olay aslında basit. Eğitilmiş bir TensorFlow modeli var, bunu düşük gecikmeyle servis etmek istiyoruz. CPU ile mümkün ama gerçek üretim trafiğinde GPU'nun farkı çok bariz. TensorFlow Serving zaten bu iş için biçilmiş kaftan; üstüne Flux CD'yi koyduğumuzda her değişiklik bir pull request'e dönüyor, cluster da Git'teki gerçeğe göre kendini sürekli düzeltiyor.

Ön hazırlık

Başlamadan önce şunlara ihtiyacınız olacak:

  • NVIDIA device plugin yüklü, GPU node'lu bir Kubernetes cluster
  • Git deponuza bootstrap edilmiş Flux CD v2
  • Paylaşımlı bir volume veya object storage'da (GCS, S3) duran SavedModel
  • Cluster'dan erişilebilen bir container registry

Cluster'a kubectl get nodes -o json | jq '.items[].status.allocatable' | grep nvidia çekip GPU'ların gerçekten görünür olduğunu doğrulamadan devam etmeyin. Tecrübeyle sabittir, sonradan 'pod neden Pending'de takılı?' diye saç baş yolmak yerine en başta görmek daha iyi.

Namespace ve model config

Önce namespace'i ve TensorFlow Serving'in okuyacağı model konfigürasyonunu hazırlayalım. Burada model versiyon politikasını da belirliyoruz; biz son iki versiyonu canlı tutacağız ki rollback bir komut uzaklığında olsun.

# clusters/prod/tf-serving/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: tf-serving
  labels:
    app.kubernetes.io/managed-by: flux
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: tf-serving-models
  namespace: tf-serving
data:
  models.config: |
    model_config_list {
      config {
        name: 'oneri-modeli'
        base_path: '/models/oneri-modeli'
        model_platform: 'tensorflow'
        model_version_policy {
          latest { num_versions: 2 }
        }
      }
    }

Yeni bir model versiyonu yayınlamak istediğinizde bu ConfigMap'e değil, model store'a yeni klasörü atıyorsunuz; TensorFlow Serving en yenisini kendisi seçiyor.

GPU'lu deployment

Asıl iş burada. GPU kaynağını requests ve limits üzerinde aynı değere set ediyoruz çünkü nvidia.com/gpu paylaşılabilir bir kaynak değil, bütün veya hiç.

# clusters/prod/tf-serving/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tf-serving
  namespace: tf-serving
spec:
  replicas: 2
  selector:
    matchLabels:
      app: tf-serving
  template:
    metadata:
      labels:
        app: tf-serving
    spec:
      tolerations:
        - key: 'nvidia.com/gpu'
          operator: 'Exists'
          effect: 'NoSchedule'
      containers:
        - name: tf-serving
          # GPU destekli imajın spesifik etiketi - asla 'latest' kullanmayin
          image: tensorflow/serving:2.14.0-gpu
          args:
            - '--model_config_file=/etc/tf-serving/models.config'
            - '--rest_api_port=8501'
            - '--grpc_port=8500'
            - '--enable_batching=true'
          ports:
            - { containerPort: 8500, name: grpc }
            - { containerPort: 8501, name: rest }
          resources:
            requests:
              nvidia.com/gpu: 1
              memory: '8Gi'
              cpu: '4'
            limits:
              nvidia.com/gpu: 1
              memory: '8Gi'
              cpu: '4'
          env:
            - { name: TF_FORCE_GPU_ALLOW_GROWTH, value: 'true' }
          volumeMounts:
            - { name: model-config, mountPath: /etc/tf-serving }
            - { name: model-store, mountPath: /models }
      volumes:
        - name: model-config
          configMap: { name: tf-serving-models }
        - name: model-store
          persistentVolumeClaim: { claimName: model-store-pvc }

TF_FORCE_GPU_ALLOW_GROWTH çok kritik. Default'ta TensorFlow GPU belleğinin tamamını başta cımbızlıyor; bu sizi tek model + tek pod senaryosuna kilitler. Açtığınızda bellek gerektikçe büyüyor, aynı GPU'da farklı şeyler kohabit edebiliyor.

Flux Kustomization

Tüm bu dosyaları Flux'a teslim etmek için ufak bir Kustomization yetiyor. Sağlık kontrolünü ekliyoruz ki Flux pod gerçekten ayağa kalkmadan 'başarılı' demesin.

# clusters/prod/flux-kustomization-tf-serving.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: tf-serving
  namespace: flux-system
spec:
  interval: 5m
  path: ./clusters/prod/tf-serving
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: tf-serving
      namespace: tf-serving

prune: true deyince Git'ten silinen kaynaklar cluster'dan da gidiyor; manuel kubectl apply ile bıraktığınız hayalet kaynaklar varsa onlar da temizlenir, dikkatli olun.

Doğrulama

Deploy gittikten sonra basit bir kontrol:

flux get kustomizations tf-serving
kubectl rollout status deployment/tf-serving -n tf-serving

curl -X POST http://<svc-ip>:8501/v1/models/oneri-modeli:predict \
  -H 'Content-Type: application/json' \
  -d '{"instances": [[1.0, 2.0, 3.0, 4.0]]}'

Cevap geldiyse gerisi konfor.

Sık karşılaşılan tuzaklar

  • latest imaj etiketi kullanmak: Reprodüksiyon ölür, rollback belirsizleşir. Her zaman 2.14.0-gpu gibi sabit etiket.
  • Batching'i kapalı bırakmak: GPU saniyede tek istek için boşa beslenir. --enable_batching=true ve bir batching.config ile kuyruğu doldurun.
  • PodDisruptionBudget koymamak: Node drain sırasında iki replikanız da aynı anda inebilir. minAvailable: 1 zorunlu.
  • GPU'yu CPU node'una düşürmek: Tolerasyon var ama nodeSelector yoksa scheduler bazen şaşırır; nvidia.com/gpu.present: 'true' etiketiyle hedef node'u sabitleyin.

Kapanış

Bu yazıda TensorFlow Serving'i GPU node'larında Flux CD ile nasıl yönetilebilir bir hâle getirdiğimize baktık. Bana sorarsanız ML platformunun en az kod yazılan ama en çok kazandıran kısmı tam burası: model versiyonu artık 'birinin laptopundan deploy ettiği şey' olmaktan çıkıp sıradan bir PR'a dönüşüyor. Umarım faydalı olur, görüşmek üzere.