From 21818ab7e8f089955bd69a8f75008475e491457b Mon Sep 17 00:00:00 2001 From: Enskc05 Date: Fri, 7 Aug 2026 23:27:09 +0300 Subject: [PATCH] feat(infra): training image + GPU Job runbook for ML squad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Egitim kodu (services/ml/training) bir konteynere girmeden K8s Job olarak kosturulamiyordu; infra/docker/README'de listelenen training.Dockerfile mevcut degildi. - infra/docker/training.Dockerfile: stok pytorch CUDA imaji (torch+cuDNN hazir) uzerine requirements/ml.txt. Yalnizca services/ml kopyalanir, non-root calisir, Hydra ciktilari icin yazilabilir /workspace. - requirements/ml.txt: hydra-core, omegaconf, boto3, python-dotenv eklendi. Egitim giris noktasi bunlari import ediyordu ama listede yoktular; imaj build oluyor, ilk kosu ImportError ile duserdi. - docs/runbooks/training-job.example.yaml: GPU Job sablonu. /dev/shm icin emptyDir — varsayilan 64 MB PyTorch DataLoader worker'larini "Bus error" ile dusuruyor. - docs/runbooks/ML-TRAINING.md: DevOps'un bir kerelik kurulumu (imaj build, MinIO SealedSecret) + ML squad'in kosu dongusu + sorun giderme tablosu. Co-Authored-By: Claude Opus 5 --- docs/README.md | 1 + docs/runbooks/ML-TRAINING.md | 191 ++++++++++++++++++ docs/runbooks/training-job.example.yaml | 68 +++++++ infra/docker/README.md | 2 +- infra/docker/training.Dockerfile | 60 ++++++ infra/docker/training.Dockerfile.dockerignore | 6 + requirements/ml.txt | 9 + 7 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 docs/runbooks/ML-TRAINING.md create mode 100644 docs/runbooks/training-job.example.yaml create mode 100644 infra/docker/training.Dockerfile create mode 100644 infra/docker/training.Dockerfile.dockerignore diff --git a/docs/README.md b/docs/README.md index eb9d9f1..091f22d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,6 +12,7 @@ Project documentation that does not belong in source-level READMEs. | [`AIRFLOW.md`](AIRFLOW.md) | **Airflow rehberi (TR)** — veri pipeline orkestrasyonu: erişim, DAG teslimi (git-sync), çalışma ortamı kontratı (env, workspace, imaj), Data squad'dan kalanlar. **DAG yazacak kişinin adresi.** | | [`DEVOPS.md`](DEVOPS.md) | **DevOps kurulum günlüğü (TR)** — GPU sunucu bootstrap'ının tamamı: NVIDIA sürücü, MicroK8s + GPU addon, Sealed Secrets, Argo CD, NPM, UFW; karşılaşılan hatalar, sebepleri ve çözümleri. | | [`MONITORING.md`](MONITORING.md) | **Monitoring rehberi (TR)** — Prometheus + Grafana + exporter'lar + GPU (dcgm): erişim, ne izlenir, dashboard'lar, `grafana-admin` SealedSecret akışı, yeni scrape/dashboard ekleme. | +| [`runbooks/ML-TRAINING.md`](runbooks/ML-TRAINING.md) | **Eğitim runbook'u (TR)** — GPU sunucuda Kubernetes Job ile model eğitimi: imaj build'i, MinIO credential'ı, Job şablonu, izleme, sorun giderme. **Eğitim koşacak kişinin adresi.** | | `adr/` | Architecture Decision Records — one file per non-trivial decision | | `runbooks/` | On-call / incident playbooks (one per failure mode) | diff --git a/docs/runbooks/ML-TRAINING.md b/docs/runbooks/ML-TRAINING.md new file mode 100644 index 0000000..22fd688 --- /dev/null +++ b/docs/runbooks/ML-TRAINING.md @@ -0,0 +1,191 @@ +# Runbook — GPU Sunucuda Model Eğitimi + +ML ekibinin `deephorizon-ml` namespace'inde Kubernetes Job açarak L40S üzerinde +eğitim koşturmasının tam akışı: kimin neyi bir kez yaptığı, ML tarafının günlük +döngüsü, ve tıkandığı yerde nereye bakacağı. + +**Temel kural:** eğitim `python train.py` ile SSH oturumunda koşmaz. GPU'yu +Kubernetes dağıtır; işi isteyen bir **Job** açar, pod GPU'lu node'a yerleşir, +iş bitince GPU serbest kalır. Kazancı: SSH kopsa da eğitim devam eder, çöken +koşu yeniden denenir, GPU'yu iki kişi aynı anda kapamaz. + +## Sorumluluk sınırı + +| İş | Sahibi | Sıklık | +|:---|:---|:---| +| Eğitim imajını build + push | DevOps | Kod veya bağımlılık değişince | +| MinIO credential Secret'ı | DevOps | Bir kez (+ rotasyonda) | +| RBAC / kubeconfig | DevOps | Kişi eklendiğinde | +| Job manifest'i yazma, koşuyu başlatma/izleme | ML squad | Her koşuda | + +ML tarafının imaj build etmesi **gerekmez ve beklenmez** — Docker grubuna üyelik +host'ta root yetkisine eşdeğerdir, bu yüzden verilmez. + +--- + +## Bölüm A — Bir kerelik kurulum (DevOps) + +### A1. Eğitim imajını build et ve push'la + +Sunucuda, repo kökünden: + +```bash +TAG=$(date +%F) # ornek: 2026-08-07 +docker build -f infra/docker/training.Dockerfile \ + -t localhost:32000/deephorizon-training:$TAG . +docker push localhost:32000/deephorizon-training:$TAG +``` + +- `localhost:32000` MicroK8s'in yerleşik registry'si (`container-registry` + namespace'i). Cluster imajı oradan çeker; harici bir registry gerekmez. +- **`latest` etiketi kullanma.** Kubernetes aynı etiketi yeniden çekmez; + imajı güncellersin, Job eski katmanla koşar ve fark günlerce görülmez. +- Eğitim kodu şu an `ml/feature` dalında. Merge edilene kadar build o dalın + klonundan alınır: + ```bash + git fetch origin && git checkout ml/feature + ``` + +Doğrulama — imaj cluster'dan çekilebiliyor mu: + +```bash +kubectl -n deephorizon-ml run img-test --rm -it --restart=Never \ + --image=localhost:32000/deephorizon-training:$TAG -- python -c "import torch; print(torch.__version__)" +``` + +### A2. MinIO credential Secret'ı + +Eğitim kodu veriyi MinIO'dan `boto3` ile çeker ve üç env bekler: +`MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`. + +`ml-team` kullanıcısı kullanılır — `raw` + `datasets` bucket'larında yalnızca +okuma yetkisi var (bkz. `docs/DATA.md`). Root credential **kullanılmaz**. + +```bash +kubectl create secret generic minio-ml-credentials -n deephorizon-ml \ + --from-literal=access-key='ml-team' \ + --from-literal=secret-key='' \ + --dry-run=client -o yaml \ + | kubeseal -n deephorizon-ml -o yaml \ + | kubectl apply -f - +``` + +SealedSecret YAML'ı **Git'e girmez** (proje kuralı, `infra/k8s/secrets/` +klasörleri boş). Şifreleme namespace adına scope'ludur — `-n deephorizon-ml` +şart, başka namespace'te çözülmez. + +Doğrulama: +```bash +kubectl -n deephorizon-ml get secret minio-ml-credentials +``` + +### A3. Erişim (kişi eklendiğinde) + +Kişiye sunucuda yetkisiz bir Unix kullanıcısı + SSH key açılır ve +`~/.kube/config` olarak `ml-trainer` ServiceAccount token'ı kurulur. Bu SA +`deephorizon-ml` namespace'ine scoped: Job açabilir/silebilir, pod ve log +okuyabilir, PVC/ConfigMap/Secret yönetebilir; namespace dışına çıkamaz. + +> RBAC 2026-08-07'de etkinleştirildi. Öncesinde cluster `AlwaysAllow` ile +> çalışıyordu ve yazılı RBAC uygulanmıyordu — bkz. `docs/DEVOPS.md`. + +--- + +## Bölüm B — Eğitim koşusu (ML squad) + +### B1. Job manifest'ini hazırla + +Şablon: [`training-job.example.yaml`](training-job.example.yaml) + +```bash +cp docs/runbooks/training-job.example.yaml ~/unet-01.yaml +nano ~/unet-01.yaml +``` + +Her koşuda değiştirilecek iki yer: + +| Alan | Ne yazılır | +|:---|:---| +| `metadata.name` | Benzersiz ad (`unet-baseline-01`, `unet-lr1e4-02`…). Aynı adla ikinci Job açılamaz. | +| `args` | Hydra override'ları: `training.epochs=50`, `training.learning_rate=0.0001`, `loss.name=l1` … | + +Hyperparametrelerin varsayılanları `services/ml/conf/` altında. `args` boş +bırakılırsa varsayılanlarla koşar. + +### B2. Başlat + +```bash +kubectl apply -f ~/unet-01.yaml +``` + +### B3. İzle + +```bash +kubectl get jobs +kubectl get pods -w # Pending -> ContainerCreating -> Running +kubectl logs -f job/unet-baseline-01 # canli log +``` + +`kubectl logs -f` kesilirse eğitim etkilenmez, tekrar bağlanılır. SSH oturumu +kapansa da Job cluster'da koşmaya devam eder. + +### B4. Sonuçlar + +Her koşu MLflow'a loglanır: tüm config parametre olarak, epoch metrikleri, +`best_model.pt` ve örnek PNG'ler artifact olarak. + +- MLflow UI (LAN): `http://10.10.1.132:30500` +- Cluster içi: `http://mlflow.deephorizon-ml.svc:5000` + +### B5. Bitir / iptal et + +```bash +kubectl delete job unet-baseline-01 +``` + +Job `ttlSecondsAfterFinished: 86400` ile 24 saat sonra kendini siler. Metrikler +ve model MLflow'da kalır — pod'un silinmesi sonuçları kaybettirmez. + +--- + +## Sorun giderme + +| Belirti | Sebep | Ne yapmalı | +|:---|:---|:---| +| Pod `Pending`, uzun süre başlamıyor | GPU meşgul — sunucuda **tek L40S** var, başka bir Job onu tutuyor | `kubectl describe pod ` → `Insufficient nvidia.com/gpu`. Diğer koşunun bitmesini bekle ya da sahibiyle konuş | +| `ImagePullBackOff` | Etiket yanlış ya da imaj push'lanmamış | Manifest'teki etiketi DevOps'un push'ladığıyla karşılaştır | +| `CreateContainerConfigError` | `minio-ml-credentials` Secret'ı yok | DevOps → A2 | +| `ModuleNotFoundError` | İmaj eski, yeni bağımlılık eklenmiş | DevOps yeni etiketle build+push eder | +| `Bus error` / DataLoader worker çöküyor | `/dev/shm` varsayılanı 64 MB | Şablondaki `dshm` volume'u manifest'te duruyor mu kontrol et; yoksa `data.num_workers=0` ile geç | +| `CUDA out of memory` | Batch büyük | `training.batch_size` düşür, `data.crop_size` küçült ya da `training.amp=true` (L40S bf16 destekler) | +| MinIO'dan dosya listelenmiyor, 0 örnek | Prefix yanlış (aşağıdaki nota bak) | `mc ls` ile gerçek yolu doğrula, `data.minio_prefix` override'la | +| `Forbidden` | Namespace dışına çıkılmaya çalışıldı | Yetki `deephorizon-ml` ile sınırlı; ihtiyaç varsa DevOps'a yaz | + +Pod'un neden başlamadığını anlamanın tek adresi: +```bash +kubectl describe pod # en alttaki Events bolumu +``` + +--- + +## Bilinen açıklar / dikkat + +- **MinIO prefix'i doğrulanmalı.** `services/ml/conf/data/default.yaml` şu an + `bucket_name: datasets` **ve** `minio_prefix: datasets/training-512/v1` + diyor. `docs/DATA.md`'deki düzen `datasets` bucket'ı + `training-512/v1/` + prefix'i. İkisi doğruysa boto3 `datasets/datasets/training-512/v1` arar ve + hiçbir şey bulamaz. İlk koşudan önce `mc ls dh/datasets/` ile gerçek yolu + teyit edin. +- **Tek GPU.** İkinci Job `Pending` bekler; bu doğru davranış. Inference + deploy edildiğinde kök README'deki "training öncesi inference `replicas: 0`" + politikası devreye girecek. +- **`lpips` ilk kullanımda ağdan ağırlık indirir.** Perceptual loss'a geçen + koşularda pod'un dışarı erişimi olmalı; kapalı ortamda ağırlıklar imaja + gömülmeli. +- **Python sürümü.** İmaj Python 3.11 (stok PyTorch CUDA imajı); repo kökü + 3.13 istiyor. Fark bilinçli — 3.13 alt sınırı `ehtim` için, eğitim kodu onu + kullanmıyor. Gerekçe `infra/docker/training.Dockerfile` başlığında. +- **numpy sürümü.** İmaj `requirements/base.txt` uyarınca numpy 2.x kurar; + `ml/feature` dalının `pyproject.toml`'u Intel Mac uyumluluğu için + `numpy<2.0` pinliyor. İkisi farklı ortamlar (konteyner vs. lokal venv) ama + ilk koşuda numpy kaynaklı bir hata çıkarsa ilk bakılacak yer burası. diff --git a/docs/runbooks/training-job.example.yaml b/docs/runbooks/training-job.example.yaml new file mode 100644 index 0000000..c2a890d --- /dev/null +++ b/docs/runbooks/training-job.example.yaml @@ -0,0 +1,68 @@ +# Ornek egitim Job'i — kopyala, adini ve args'ini degistir, uygula. +# +# kubectl apply -f training-job.example.yaml +# +# Bu dosya Argo CD tarafindan izlenmez (docs/ hicbir Application'in path'i degil); +# ad-hoc egitim kosulari GitOps'a girmez, elle uygulanir. +# +# Rehber: docs/runbooks/ML-TRAINING.md +apiVersion: batch/v1 +kind: Job +metadata: + # Her kosuya BENZERSIZ ad ver — ayni adla ikinci Job acilamaz. + name: unet-baseline-01 + namespace: deephorizon-ml +spec: + # Egitim cokerse 1 kez daha dener. 0 yaparsan hic denemez. + backoffLimit: 1 + # Biten Job 24 saat sonra kendini siler (pod ve loglar da gider). + # Loglari saklamak istiyorsan MLflow'a bak, orada kalici. + ttlSecondsAfterFinished: 86400 + template: + spec: + restartPolicy: Never + containers: + - name: train + # Etiket her build'de degisir — `latest` kullanma. + image: localhost:32000/deephorizon-training:2026-08-07 + # Hyperparametre override'lari (Hydra). Bos birakirsan + # services/ml/conf/ altindaki varsayilanlar gecerli olur. + args: + - "training.epochs=50" + - "training.batch_size=16" + env: + - name: MLFLOW_TRACKING_URI + value: "http://mlflow.deephorizon-ml.svc:5000" + - name: MINIO_ENDPOINT + value: "http://minio.deephorizon-data.svc:9000" + - name: MINIO_ACCESS_KEY + valueFrom: + secretKeyRef: + name: minio-ml-credentials + key: access-key + - name: MINIO_SECRET_KEY + valueFrom: + secretKeyRef: + name: minio-ml-credentials + key: secret-key + resources: + requests: + nvidia.com/gpu: 1 + memory: "32Gi" + cpu: "8" + limits: + # GPU'da requests ve limits AYNI olmak zorunda (Kubernetes kurali). + nvidia.com/gpu: 1 + memory: "48Gi" + cpu: "16" + volumeMounts: + # Hydra cikti klasoru ve DataLoader'in shared memory ihtiyaci. + - name: dshm + mountPath: /dev/shm + volumes: + # Varsayilan /dev/shm 64 MB'tir; num_workers>0 olan PyTorch DataLoader + # bunu doldurur ve worker'lar "Bus error" ile duser. + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 8Gi diff --git a/infra/docker/README.md b/infra/docker/README.md index 39962f5..7f3757e 100644 --- a/infra/docker/README.md +++ b/infra/docker/README.md @@ -5,7 +5,7 @@ Multi-stage Dockerfiles for each service. | File | Service | |:---|:---| | `ml.Dockerfile` | Inference server (slim runtime, `requirements/serving.txt`) | -| `training.Dockerfile` | Training image (full `requirements/ml.txt`, CUDA base) | +| `training.Dockerfile` | Training image (`requirements/ml.txt` on the stock PyTorch CUDA base) — run as a GPU `Job`, see [`docs/runbooks/ML-TRAINING.md`](../../docs/runbooks/ML-TRAINING.md) | | `api.Dockerfile` | Go API gateway (static binary) | | `frontend.Dockerfile` | Next.js frontend (standalone Node.js runtime) | diff --git a/infra/docker/training.Dockerfile b/infra/docker/training.Dockerfile new file mode 100644 index 0000000..50d9198 --- /dev/null +++ b/infra/docker/training.Dockerfile @@ -0,0 +1,60 @@ +# Egitim imaji — services/ml/training/*.py'yi GPU'lu bir Kubernetes Job icinde calistirir. +# +# NEDEN AYRI IMAJ: egitim kodu veriyi MinIO'dan cekiyor (boto3), Hydra ile +# konfigure ediliyor ve her kosuyu MLflow'a logluyor. Bunlarin hicbiri stok +# pytorch imajinda yok. +# +# BUILD (sunucuda, repo kokunden): +# docker build -f infra/docker/training.Dockerfile -t localhost:32000/deephorizon-training: . +# docker push localhost:32000/deephorizon-training: +# +# Etiket olarak `latest` KULLANMA — Kubernetes ayni etiketi yeniden cekmez, +# guncelleme sessizce uygulanmaz. Tarih ver: 2026-08-07 gibi. +# +# Kullanim rehberi: docs/runbooks/ML-TRAINING.md + +# Torch + CUDA + cuDNN hazir gelir. Torch'u pip ile kurmak GB'larca indirme ve +# CUDA surum eslesmesi riski demek. L40S (Ada Lovelace, sm_89) cu124 ile desteklenir. +# +# Bu imajda Python 3.11 var, repo koku 3.13 istiyor (pyproject.toml). Fark +# bilincli: 3.13 alt siniri ehtim icin konuldu (veri squad'i) ve egitim kodu +# ehtim kullanmiyor. Egitim tarafinda 3.13'e ihtiyac duyan bir sey cikarsa +# nvidia/cuda taban imajina gecilir ve torch cu124 wheel'i elle kurulur. +FROM pytorch/pytorch:2.6.0-cuda12.4-cudnn9-runtime + +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONPATH=/app + +WORKDIR /app + +# --- Python bagimliliklari --- +# torch/torchvision zaten imajda ve requirements'taki alt sinirlari karsiliyor; +# pip onlari yeniden indirmez. Geri kalan (mlflow, hydra, boto3, metrikler) gelir. +COPY requirements/ /app/requirements/ +RUN pip install --no-cache-dir -r requirements/ml.txt \ + && rm -rf /app/requirements + +# --- Dogrulama: eksik bir sey varsa build burada dussun, egitimin 3. saatinde degil --- +RUN python -c "\ +import torch, torchvision, torchmetrics, mlflow, hydra, omegaconf, boto3, lpips, skimage, cv2, numpy; \ +print('torch', torch.__version__, '| cuda', torch.version.cuda, '| numpy', numpy.__version__); \ +assert torch.version.cuda, 'CUDA destegi olmayan torch kuruldu'" + +# --- Uygulama kodu --- +# Yalnizca services/ml kopyalanir; services/api (Go) ve services/frontend (Node) +# egitimle ilgisiz ve imaji buyutur. Build context filtresi: +# infra/docker/training.Dockerfile.dockerignore +COPY services/ml/ /app/services/ml/ + +# --- Calisma kullanicisi --- +# GPU erisimi root gerektirmez. Hydra cikti klasorunu (outputs/) calisma +# dizinine yazar; bu yuzden WORKDIR yazilabilir ve trainer'a ait olmali. +RUN useradd --create-home trainer \ + && install -d -o trainer -g trainer /workspace +USER trainer +WORKDIR /workspace + +# Hyperparametreler Job manifest'indeki `args` ile gecilir: +# args: ["training.epochs=50", "training.batch_size=16"] +CMD ["python", "-m", "services.ml.training.train"] diff --git a/infra/docker/training.Dockerfile.dockerignore b/infra/docker/training.Dockerfile.dockerignore new file mode 100644 index 0000000..4fc5b8f --- /dev/null +++ b/infra/docker/training.Dockerfile.dockerignore @@ -0,0 +1,6 @@ +** +!requirements/ +!requirements/** +!services/ +!services/ml/ +!services/ml/** diff --git a/requirements/ml.txt b/requirements/ml.txt index 3a75f3b..dc0787b 100644 --- a/requirements/ml.txt +++ b/requirements/ml.txt @@ -9,3 +9,12 @@ torchmetrics>=1.6.0 lpips>=0.1.4 mlflow>=2.21.0 optuna>=4.3.0 + +# Egitim giris noktasi (services/ml/training/train.py) Hydra ile konfigure edilir +# ve veriyi MinIO'dan boto3 ile ceker. Bunlar olmadan `python -m +# services.ml.training.train` ImportError ile duser — imaj build'i degil, ilk +# kosu kirilir. Bkz. infra/docker/training.Dockerfile +hydra-core>=1.3.2 +omegaconf>=2.3.0 +boto3>=1.35.0 +python-dotenv>=1.0.0