From 268496a8c436e3b9a44cd3df130a6c1a40963f0d Mon Sep 17 00:00:00 2001 From: Valentin Date: Mon, 21 Sep 2026 12:04:49 +0200 Subject: [PATCH 1/6] =?UTF-8?q?feat(ml):=20enregistrer=20le=20mod=C3=A8le?= =?UTF-8?q?=20dans=20le=20MLflow=20Model=20Registry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ml/enervision_ml/train.py | 6 +++++- ml/tests/test_train.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/ml/enervision_ml/train.py b/ml/enervision_ml/train.py index fad5b89..2d4e25c 100644 --- a/ml/enervision_ml/train.py +++ b/ml/enervision_ml/train.py @@ -181,7 +181,11 @@ def _log_to_mlflow( ) mlflow.log_metrics({f"model_{cle}": valeur for cle, valeur in model_metrics.items()}) mlflow.log_metrics({f"baseline_{cle}": valeur for cle, valeur in baseline_metrics.items()}) - mlflow.lightgbm.log_model(booster, name="model") + mlflow.lightgbm.log_model( + booster, + name="model", + registered_model_name="consumption-forecast-lightgbm", + ) mlflow.log_artifact(str(model_output)) diff --git a/ml/tests/test_train.py b/ml/tests/test_train.py index 3084983..047e069 100644 --- a/ml/tests/test_train.py +++ b/ml/tests/test_train.py @@ -3,6 +3,7 @@ from pathlib import Path import numpy as np import pandas as pd +import pytest from enervision_ml.features import TARGET_COLUMN, build_features, feature_columns from enervision_ml.train import chronological_split, prepare_dataset, train @@ -74,3 +75,18 @@ def test_train_runs_end_to_end_on_synthetic_data_and_beats_a_dummy_baseline( assert model_metrics["n_observations"] > 0 assert model_metrics["mae"] >= 0 assert baseline_metrics["n_observations"] == model_metrics["n_observations"] + assert model_metrics["mae"] < baseline_metrics["mae"] + +def test_train_raises_when_the_validation_window_is_empty(tmp_path: Path) -> None: + depart = datetime(2026, 1, 1, tzinfo=UTC) + frame = make_frame("site-a", heures=50, depart=depart) # trop court pour un lag de 168h + csv_path = tmp_path / "trop_court.csv" + frame.to_csv(csv_path, index=False) + + with pytest.raises(ValueError, match="Fenetre d'entrainement ou de validation vide"): + train( + csv_path=csv_path, + model_output=tmp_path / "model.txt", + test_fraction=0.2, + tracking_uri=f"sqlite:///{tmp_path / 'mlflow.db'}", + ) From 9312d3b60f6f25247f3092d34b360e21177761c1 Mon Sep 17 00:00:00 2001 From: Valentin Date: Mon, 21 Sep 2026 14:52:00 +0200 Subject: [PATCH 2/6] =?UTF-8?q?feat(ml):=20enregistrer=20le=20mod=C3=A8le?= =?UTF-8?q?=20dans=20le=20MLflow=20Model=20Registry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- ml/.dockerignore | 5 +++++ ml/Dockerfile | 3 +++ ml/README.md | 31 +++++++++++++++++++++++++++++++ ml/docker-compose.mlflow.yml | 33 +++++++++++++++++++++++++++++++++ ml/tests/test_train.py | 1 + 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 ml/.dockerignore create mode 100644 ml/Dockerfile create mode 100644 ml/docker-compose.mlflow.yml diff --git a/.gitignore b/.gitignore index 47574d1..81c0338 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,7 @@ ml/models/* !ml/models/.gitkeep ml/mlruns/ ml/mlartifacts/ -ml/mlflow.db +ml/mlflow.db* # IDE et OS .idea/ diff --git a/ml/.dockerignore b/ml/.dockerignore new file mode 100644 index 0000000..456e579 --- /dev/null +++ b/ml/.dockerignore @@ -0,0 +1,5 @@ +.venv +data +mlruns +mlflow.db* +models diff --git a/ml/Dockerfile b/ml/Dockerfile new file mode 100644 index 0000000..77b75ed --- /dev/null +++ b/ml/Dockerfile @@ -0,0 +1,3 @@ +FROM python:3.12-slim +RUN pip install --no-cache-dir mlflow==3.16.1 psycopg2-binary +EXPOSE 5000 diff --git a/ml/README.md b/ml/README.md index 4d69363..b11a273 100644 --- a/ml/README.md +++ b/ml/README.md @@ -57,6 +57,37 @@ validation. La coupure est **chronologique**, jamais un tirage aleatoire de lign aleatoire laisserait des lignes de validation "voir" des lignes d'entrainement via leurs lags/moyennes glissantes, une fuite qui masquerait un surapprentissage. +## Serveur MLflow (conteneur) + +Un serveur MLflow (PostgreSQL pour les metadonnees, volume pour les artefacts) se lance avec +Docker. Prerequis : Docker Desktop demarre. + +``` +cd ml +docker compose -f docker-compose.mlflow.yml up -d --build +``` + +Interface : http://localhost:5000. Entrainer vers ce serveur : + +``` +uv run python -m enervision_ml.train --csv data/all_sites_combined.csv --mlflow-tracking-uri http://localhost:5000 +``` + +Arreter : `docker compose -f docker-compose.mlflow.yml down` (ajouter `-v` pour effacer aussi les +runs et les modeles). + +Pour voir les runs dans l'interface (MLflow 3.x) : + +- Passer le selecteur en haut a gauche sur **Model training**. Le mode **GenAI** affiche des + traces LLM et reste vide pour un entrainement LightGBM. +- **Runs** liste les entrainements, **Models** les artefacts de modele de chaque run (tous nommes + `model`), et **Model registry** les versions numerotees de `consumption-forecast-lightgbm`. + +Limites : les identifiants PostgreSQL (`mlflow` / `mlflow`) du compose ne conviennent qu'au +developpement local. Un deploiement partage demandera des secrets, de l'authentification et un +stockage d'artefacts dedie (S3/MinIO). Le port 5000 doit etre libre : arreter `mlflow ui` avant, +ou changer le mapping (`"5001:5000"`) dans le compose. + ## Commandes ```bash diff --git a/ml/docker-compose.mlflow.yml b/ml/docker-compose.mlflow.yml new file mode 100644 index 0000000..981a2f9 --- /dev/null +++ b/ml/docker-compose.mlflow.yml @@ -0,0 +1,33 @@ +services: + mlflow-db: + image: postgres:16 + environment: + POSTGRES_USER: mlflow + POSTGRES_PASSWORD: mlflow + POSTGRES_DB: mlflow + volumes: + - mlflow-db-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U mlflow"] + interval: 5s + retries: 10 + + mlflow: + build: . + depends_on: + mlflow-db: + condition: service_healthy + ports: + - "5000:5000" + volumes: + - mlflow-artifacts:/mlartifacts + command: > + mlflow server + --host 0.0.0.0 --port 5000 + --backend-store-uri postgresql://mlflow:mlflow@mlflow-db:5432/mlflow + --artifacts-destination /mlartifacts + --serve-artifacts + +volumes: + mlflow-db-data: + mlflow-artifacts: diff --git a/ml/tests/test_train.py b/ml/tests/test_train.py index 047e069..7f22d5c 100644 --- a/ml/tests/test_train.py +++ b/ml/tests/test_train.py @@ -77,6 +77,7 @@ def test_train_runs_end_to_end_on_synthetic_data_and_beats_a_dummy_baseline( assert baseline_metrics["n_observations"] == model_metrics["n_observations"] assert model_metrics["mae"] < baseline_metrics["mae"] + def test_train_raises_when_the_validation_window_is_empty(tmp_path: Path) -> None: depart = datetime(2026, 1, 1, tzinfo=UTC) frame = make_frame("site-a", heures=50, depart=depart) # trop court pour un lag de 168h From 33aeea835bcbbd1adb4b049bdf207239f61a8cd1 Mon Sep 17 00:00:00 2001 From: Valentin Date: Mon, 21 Sep 2026 15:06:05 +0200 Subject: [PATCH 3/6] fix(ml): retire le mot de passe PostgreSQL du compose --- .gitignore | 1 + ml/.dockerignore | 1 + ml/.env.example | 1 + ml/README.md | 7 +++++++ ml/docker-compose.mlflow.yml | 4 ++-- 5 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 ml/.env.example diff --git a/.gitignore b/.gitignore index 81c0338..2e04f8a 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ ml/models/* ml/mlruns/ ml/mlartifacts/ ml/mlflow.db* +ml/.env # IDE et OS .idea/ diff --git a/ml/.dockerignore b/ml/.dockerignore index 456e579..5784a2c 100644 --- a/ml/.dockerignore +++ b/ml/.dockerignore @@ -3,3 +3,4 @@ data mlruns mlflow.db* models +.env diff --git a/ml/.env.example b/ml/.env.example new file mode 100644 index 0000000..65e2ef5 --- /dev/null +++ b/ml/.env.example @@ -0,0 +1 @@ +MLFLOW_DB_PASSWORD=change-me diff --git a/ml/README.md b/ml/README.md index b11a273..2847555 100644 --- a/ml/README.md +++ b/ml/README.md @@ -59,6 +59,13 @@ lags/moyennes glissantes, une fuite qui masquerait un surapprentissage. ## Serveur MLflow (conteneur) +Premiere utilisation : copier `.env.example` en `.env` et y choisir un mot de passe PostgreSQL +(lettres et chiffres uniquement). Le fichier `.env` est ignore par git. + +``` +Copy-Item .env.example .env +``` + Un serveur MLflow (PostgreSQL pour les metadonnees, volume pour les artefacts) se lance avec Docker. Prerequis : Docker Desktop demarre. diff --git a/ml/docker-compose.mlflow.yml b/ml/docker-compose.mlflow.yml index 981a2f9..0d889cb 100644 --- a/ml/docker-compose.mlflow.yml +++ b/ml/docker-compose.mlflow.yml @@ -3,7 +3,7 @@ services: image: postgres:16 environment: POSTGRES_USER: mlflow - POSTGRES_PASSWORD: mlflow + POSTGRES_PASSWORD: ${MLFLOW_DB_PASSWORD:?definir MLFLOW_DB_PASSWORD dans ml/.env} POSTGRES_DB: mlflow volumes: - mlflow-db-data:/var/lib/postgresql/data @@ -24,7 +24,7 @@ services: command: > mlflow server --host 0.0.0.0 --port 5000 - --backend-store-uri postgresql://mlflow:mlflow@mlflow-db:5432/mlflow + --backend-store-uri postgresql://mlflow:${MLFLOW_DB_PASSWORD}@mlflow-db:5432/mlflow --artifacts-destination /mlartifacts --serve-artifacts From 5cc99178c2411b626b60b8133a26f50346d083b7 Mon Sep 17 00:00:00 2001 From: Valentin Date: Mon, 21 Sep 2026 15:41:02 +0200 Subject: [PATCH 4/6] fix(ml): execute MLflow en non-root et installe uniquement des wheels --- ml/Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ml/Dockerfile b/ml/Dockerfile index 77b75ed..e82eadd 100644 --- a/ml/Dockerfile +++ b/ml/Dockerfile @@ -1,3 +1,7 @@ FROM python:3.12-slim -RUN pip install --no-cache-dir mlflow==3.16.1 psycopg2-binary +RUN pip install --no-cache-dir --only-binary :all: mlflow==3.16.1 psycopg2-binary==2.9.10 +RUN useradd --create-home --uid 1000 mlflow \ + && mkdir /mlartifacts \ + && chown mlflow /mlartifacts +USER mlflow EXPOSE 5000 From 8760ebc70146c9b3ae4d54b177df95f6f18aac84 Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 22 Sep 2026 10:41:02 +0200 Subject: [PATCH 5/6] fix(ml): applique les corrections de la review MLflow (securite, documentation, robustesse) --- Makefile | 10 +++++++++- docs/architecture/10-infra.md | 29 +++++++++++++++++++++++++++++ docs/architecture/40-data.md | 4 ++++ ml/Dockerfile | 2 +- ml/README.md | 29 +++++++++++++++++++++-------- ml/docker-compose.mlflow.yml | 4 ++-- 6 files changed, 66 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index b3d3c7b..fe60e50 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ DEMO_NOW ?= 2024-12-31T00:00:00Z lint format typecheck test test-cov test-integration check \ openapi docker-build db-up db-down db-reset db-logs db-psql db-wait db-ensure-airflow \ migrate bootstrap-admin services-up demo-data demo-data-force \ - ml-lint ml-typecheck ml-test ml-check ml-train ml-score detect-alerts recommendations \ + ml-lint ml-typecheck ml-test ml-check ml-train ml-score mlflow-up detect-alerts recommendations \ airflow-lint airflow-test airflow-check airflow-up airflow-down airflow-logs \ tls-selfsigned tls-acme tls-renew stack-up stack-down stack-logs @@ -118,6 +118,14 @@ ml-train: ## Entraine le modele LightGBM. CSV=chemin optionnel, sinon lit ML_DAT ml-score: ## Score le prochain pas horaire et l'ecrit dans `prediction`. CSV= et NOW= optionnels cd $(ML) && uv run python -m enervision_ml.score $(if $(CSV),--csv $(CSV),) $(if $(NOW),--now $(NOW),) +mlflow-up: ## Démarre le serveur MLflow (tracking + registry) en conteneur. ml/.env requis + @test -n "$(strip $(ML_ENV_DB_PASSWORD))" \ + || { echo "MLFLOW_DB_PASSWORD absente de ml/.env (copier ml/.env.example)"; exit 1; } + @echo "$(ML_ENV_DB_PASSWORD)" | grep -qE '^[A-Za-z0-9]+$$' \ + || { echo "MLFLOW_DB_PASSWORD doit contenir uniquement lettres et chiffres (interpolee dans l'URI postgresql://)"; exit 1; } + cd $(ML) && docker compose -f docker-compose.mlflow.yml up -d --build + @echo "mlflow -> http://localhost:5000" + detect-alerts: ## Détecte les alertes internes depuis les lectures en base. SITE= et NOW= optionnels cd $(BACKEND) && uv run python -m app.detection.internal_alerts $(if $(SITE),--site-id $(SITE),) $(if $(NOW),--now $(NOW),) diff --git a/docs/architecture/10-infra.md b/docs/architecture/10-infra.md index 2bde800..a9d9c0e 100644 --- a/docs/architecture/10-infra.md +++ b/docs/architecture/10-infra.md @@ -9,6 +9,7 @@ quel contexte, quelles décisions sont arrêtées, et ce qui manque encore entre | Docker Compose plus reverse proxy | Déployer sur la machine on-premise | `Fait` | | Deux projets Compose sur la VM ENI, recette et production | Déploiement continu depuis GitHub | `En cours` | | k3s single-node | Cible à terme | `En cours` | +| MLflow (`ml/`) | Tracker les expériences et le registre de modèles en local | `Fait`, non relié aux autres topologies | ## Poste de développement @@ -147,6 +148,34 @@ est minimale et n'embarque pas la runtime OpenMP dont LightGBM a besoin, sans qu (`OSError: libgomp.so.1`) n'apparaît qu'à la première tâche réellement exécutée, pas à la construction de l'image. +### MLflow (`ml/`) + +Statut : `Fait`, en local uniquement. Défini par `ml/docker-compose.mlflow.yml`, indépendant +du `docker-compose.yml` principal (réseau, volumes et démarrage séparés). + +| Service | Image | Points notables | +|---|---|---| +| `mlflow-db` | `postgres:16` | Stocke le tracking store MLflow. Mot de passe obligatoire via `MLFLOW_DB_PASSWORD` | +| `mlflow` | Construite depuis `ml/` | Expose l'UI et l'API MLflow sur `127.0.0.1:5000`. Artefacts sur volume `mlflow-artifacts`, tracking store sur `mlflow-db` | + +Portée actuelle : environnement de tracking et de registre de modèles pour le développement +local uniquement. Ce compose n'est relié ni à `docker-compose.prod.yml`, ni aux deux +environnements Compose de la VM ENI, ni à la cible k3s. Le magasin utilisé par Airflow pour +`ml_train`/`ml_score` (SQLite, volume `airflow_ml_state`) en est distinct — les deux MLflow ne +se voient pas tant que `MLFLOW_TRACKING_URI` n'est pas posé côté Airflow. + +Limite connue : le DAG Airflow `ml_train` enregistre lui aussi une version a chaque execution +via `registered_model_name` (magasin SQLite du volume `airflow_ml_state`, distinct de ce +serveur). Versions et artefacts s'y accumulent sans politique de nettoyage -- fonctionne en +l'etat, mais a surveiller si les entrainements deviennent frequents. + +Pour relier les runs Airflow (`ml_train`, magasin SQLite local) a ce serveur MLflow, positionner +`MLFLOW_TRACKING_URI=http://mlflow:5000` dans l'environnement du service `airflow-scheduler` (ou +`http://host.docker.internal:5000` si le serveur MLflow tourne hors du reseau Compose principal), +et s'assurer que le conteneur Airflow peut joindre le service `mlflow` -- ce qui suppose de les +rapprocher sur le meme reseau Docker ou d'exposer MLflow autrement qu'en `127.0.0.1` uniquement +(cf. point 1 sur l'exposition du port). Non fait a ce jour : aucun besoin de centraliser les runs +d'entrainement Airflow et locaux n'a encore ete identifie. ## Machine cible, exécution Docker Statut : `Fait`. Défini par l'overlay `docker-compose.prod.yml`, appliqué par-dessus le diff --git a/docs/architecture/40-data.md b/docs/architecture/40-data.md index 82f769b..011d91f 100644 --- a/docs/architecture/40-data.md +++ b/docs/architecture/40-data.md @@ -287,6 +287,10 @@ Chaque table remplit un rôle précis dans le traitement et l'exploitation des d | `alert` | Enregistrer les alertes, leur type, leur gravité et leur message | API Mock `/alerts` et détections EnerVision | | `recommendation` | Proposer des actions et expliquer la règle qui les motive | Règles métier d'EnerVision | +Le scoring (`ml_score`) charge le modèle depuis un fichier local (`models/lightgbm-consumption.txt`) +et trace son empreinte SHA-256 dans `prediction.model_reference`. Il ne lit aucune version depuis +le Model Registry MLflow (`ml/`) : ce registre sert aujourd'hui à la traçabilité des +entraînements, pas au déploiement du modèle de scoring. Les anomalies historiques décrites dans les JSON sont conservées dans `dataset.metadata`. Elles servent à l'analyse des données et ne sont pas considérées comme des alertes actuelles. diff --git a/ml/Dockerfile b/ml/Dockerfile index e82eadd..bcd1706 100644 --- a/ml/Dockerfile +++ b/ml/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12-slim +FROM python:3.14-slim RUN pip install --no-cache-dir --only-binary :all: mlflow==3.16.1 psycopg2-binary==2.9.10 RUN useradd --create-home --uid 1000 mlflow \ && mkdir /mlartifacts \ diff --git a/ml/README.md b/ml/README.md index 447eef8..5585c54 100644 --- a/ml/README.md +++ b/ml/README.md @@ -62,17 +62,21 @@ lags/moyennes glissantes, une fuite qui masquerait un surapprentissage. Premiere utilisation : copier `.env.example` en `.env` et y choisir un mot de passe PostgreSQL (lettres et chiffres uniquement). Le fichier `.env` est ignore par git. -``` -Copy-Item .env.example .env +```bash +cp .env.example .env ``` Un serveur MLflow (PostgreSQL pour les metadonnees, volume pour les artefacts) se lance avec Docker. Prerequis : Docker Desktop demarre. +```bash +make mlflow-up ``` -cd ml -docker compose -f docker-compose.mlflow.yml up -d --build -``` + +La cible vérifie que `MLFLOW_DB_PASSWORD` (définie dans `ml/.env`) ne contient que des lettres et +des chiffres avant de démarrer le serveur : ce mot de passe est interpolé directement dans l'URI +PostgreSQL (`postgresql://mlflow:${MLFLOW_DB_PASSWORD}@...`), un caractère spécial la rendrait +invalide sans message d'erreur clair. Interface : http://localhost:5000. Entrainer vers ce serveur : @@ -90,9 +94,11 @@ Pour voir les runs dans l'interface (MLflow 3.x) : - **Runs** liste les entrainements, **Models** les artefacts de modele de chaque run (tous nommes `model`), et **Model registry** les versions numerotees de `consumption-forecast-lightgbm`. -Limites : les identifiants PostgreSQL (`mlflow` / `mlflow`) du compose ne conviennent qu'au -developpement local. Un deploiement partage demandera des secrets, de l'authentification et un -stockage d'artefacts dedie (S3/MinIO). Le port 5000 doit etre libre : arreter `mlflow ui` avant, +Limites : l'identifiant PostgreSQL du compose est fixe a `mlflow`, le mot de passe vient de la +variable obligatoire `MLFLOW_DB_PASSWORD` (aucune valeur par defaut, le compose refuse de +demarrer sans elle) -- ce mot de passe est choisi lors de la copie de `.env.example`, il ne +convient donc qu'au developpement local tel quel. Un deploiement partage demandera des secrets, +de l'authentification et un stockage d'artefacts dedie (S3/MinIO). Le port 5000 doit etre libre : arreter `mlflow ui` avant, ou changer le mapping (`"5001:5000"`) dans le compose. ## Scoring @@ -121,6 +127,13 @@ section 2 : fichier : `train.py` reecrit toujours le meme chemin a chaque entrainement, donc le nom seul ne distinguerait pas deux versions du modele. +**Le scoring ne lit pas le Model Registry.** Le fichier charge par `--model` est local +(`models/lightgbm-consumption.txt`), independant des versions enregistrees dans le +**Model registry** MLflow (`consumption-forecast-lightgbm`). `train.py` enregistre bien une +version a chaque entrainement (tracabilite), mais aucun alias (`champion` par exemple) n'est +pose, et `enervision_ml.score` ne les lit pas. Le registre sert aujourd'hui a la tracabilite des +entrainements, pas au deploiement du modele utilise en scoring. + En mode `--csv`, rien n'est ecrit en base : c'est un instantane historique fige (l'heure "future" calculee a partir de la fin du CSV n'existe dans aucune base reelle), utile pour valider le pipeline sans base joignable. diff --git a/ml/docker-compose.mlflow.yml b/ml/docker-compose.mlflow.yml index 0d889cb..78cc5de 100644 --- a/ml/docker-compose.mlflow.yml +++ b/ml/docker-compose.mlflow.yml @@ -1,6 +1,6 @@ services: mlflow-db: - image: postgres:16 + image: postgres:17 environment: POSTGRES_USER: mlflow POSTGRES_PASSWORD: ${MLFLOW_DB_PASSWORD:?definir MLFLOW_DB_PASSWORD dans ml/.env} @@ -18,7 +18,7 @@ services: mlflow-db: condition: service_healthy ports: - - "5000:5000" + - "127.0.0.1:5000:5000" volumes: - mlflow-artifacts:/mlartifacts command: > From 44163bfb983164775ea1aa8f7090084f57314c12 Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 22 Sep 2026 15:04:21 +0200 Subject: [PATCH 6/6] fix(ml): corrige le build MLflow (psycopg2), le garde-fou Makefile, la doc et la fuite de mot de passe --- Makefile | 2 ++ docs/architecture/10-infra.md | 2 +- ml/Dockerfile | 2 +- ml/docker-compose.mlflow.yml | 11 +++++------ 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index fe60e50..63d0ea5 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,8 @@ PG_USER := $(or $(strip $(call env-val,POSTGRES_USER)),enervision) PG_PASSWORD := $(or $(strip $(call env-val,POSTGRES_PASSWORD)),change_me) PG_DB := $(or $(strip $(call env-val,POSTGRES_DB)),enervision) PG_PORT := $(or $(strip $(call env-val,POSTGRES_PORT)),5433) +ml-env-val = $(shell sed -n 's/^$(1)=//p' ml/.env 2>/dev/null | tail -1) +ML_ENV_DB_PASSWORD := $(call ml-env-val,MLFLOW_DB_PASSWORD) AIRFLOW_PORT := $(or $(strip $(call env-val,AIRFLOW_PORT)),8080) MAILPIT_UI_PORT := $(or $(strip $(call env-val,MAILPIT_UI_PORT)),8025) ML_DATABASE_URL ?= postgresql+psycopg://$(PG_USER):$(PG_PASSWORD)@localhost:$(PG_PORT)/$(PG_DB) diff --git a/docs/architecture/10-infra.md b/docs/architecture/10-infra.md index a9d9c0e..a0e977f 100644 --- a/docs/architecture/10-infra.md +++ b/docs/architecture/10-infra.md @@ -155,7 +155,7 @@ du `docker-compose.yml` principal (réseau, volumes et démarrage séparés). | Service | Image | Points notables | |---|---|---| -| `mlflow-db` | `postgres:16` | Stocke le tracking store MLflow. Mot de passe obligatoire via `MLFLOW_DB_PASSWORD` | +| `mlflow-db` | `postgres:17` | Stocke le tracking store MLflow. Mot de passe obligatoire via `MLFLOW_DB_PASSWORD` | | `mlflow` | Construite depuis `ml/` | Expose l'UI et l'API MLflow sur `127.0.0.1:5000`. Artefacts sur volume `mlflow-artifacts`, tracking store sur `mlflow-db` | Portée actuelle : environnement de tracking et de registre de modèles pour le développement diff --git a/ml/Dockerfile b/ml/Dockerfile index bcd1706..070b752 100644 --- a/ml/Dockerfile +++ b/ml/Dockerfile @@ -1,5 +1,5 @@ FROM python:3.14-slim -RUN pip install --no-cache-dir --only-binary :all: mlflow==3.16.1 psycopg2-binary==2.9.10 +RUN pip install --no-cache-dir --only-binary :all: mlflow==3.16.1 psycopg2-binary==2.9.13 RUN useradd --create-home --uid 1000 mlflow \ && mkdir /mlartifacts \ && chown mlflow /mlartifacts diff --git a/ml/docker-compose.mlflow.yml b/ml/docker-compose.mlflow.yml index 78cc5de..92fafbf 100644 --- a/ml/docker-compose.mlflow.yml +++ b/ml/docker-compose.mlflow.yml @@ -21,12 +21,11 @@ services: - "127.0.0.1:5000:5000" volumes: - mlflow-artifacts:/mlartifacts - command: > - mlflow server - --host 0.0.0.0 --port 5000 - --backend-store-uri postgresql://mlflow:${MLFLOW_DB_PASSWORD}@mlflow-db:5432/mlflow - --artifacts-destination /mlartifacts - --serve-artifacts + environment: + MLFLOW_DB_PASSWORD: ${MLFLOW_DB_PASSWORD} + entrypoint: [ "/bin/sh", "-c" ] + command: + - exec mlflow server --host 0.0.0.0 --port 5000 --backend-store-uri "postgresql://mlflow:$$MLFLOW_DB_PASSWORD@mlflow-db:5432/mlflow" --artifacts-destination /mlartifacts --serve-artifacts volumes: mlflow-db-data: