Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |

Expand Down
191 changes: 191 additions & 0 deletions docs/runbooks/ML-TRAINING.md
Original file line number Diff line number Diff line change
@@ -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='<ml-team-parolasi>' \
--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 <ad>` → `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 <pod-adi> # 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ı.
68 changes: 68 additions & 0 deletions docs/runbooks/training-job.example.yaml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion infra/docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |

Expand Down
60 changes: 60 additions & 0 deletions infra/docker/training.Dockerfile
Original file line number Diff line number Diff line change
@@ -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:<etiket> .
# docker push localhost:32000/deephorizon-training:<etiket>
#
# 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/<tarih>) 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"]
6 changes: 6 additions & 0 deletions infra/docker/training.Dockerfile.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
**
!requirements/
!requirements/**
!services/
!services/ml/
!services/ml/**
9 changes: 9 additions & 0 deletions requirements/ml.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading