From 154deca51196fe5a2220d036a5b938ebaa30c938 Mon Sep 17 00:00:00 2001 From: Johan LEROY Date: Tue, 22 Sep 2026 08:35:42 +0200 Subject: [PATCH] =?UTF-8?q?docs(architecture):=20d=C3=A9crit=20les=20compo?= =?UTF-8?q?sants=20Airflow=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quatre services au lieu de trois (api-server, dag-processor), le secret JWT partagé et le choix du FabAuthManager. Le Python 3.12 d'etl/airflow est celui de l'image retenue, plus une limite d'Airflow. --- docs/architecture/10-infra.md | 24 ++++++++++++++++-------- docs/architecture/50-cicd.md | 7 ++++--- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/architecture/10-infra.md b/docs/architecture/10-infra.md index c6121e7..f1338f0 100644 --- a/docs/architecture/10-infra.md +++ b/docs/architecture/10-infra.md @@ -48,20 +48,28 @@ Trois pièges sont documentés en tête du `docker-compose.yml`, ils ne se devin - `db/init` est monté **fichier par fichier**. Monter le dossier masquerait les scripts d'init de l'image, dont `timescaledb-tune`. Ajouter un fichier dans `db/init/` impose donc une ligne dans le compose. Voir [`db/README.md`](../../db/README.md). -- `LocalExecutor` exécute les tâches comme sous-processus du **scheduler**, jamais du webserver : +- `LocalExecutor` exécute les tâches comme sous-processus du **scheduler**, jamais de l'api-server : c'est le scheduler qui a besoin du volume `airflow_ml_state` (modèle, magasin MLflow). ### Airflow (issues #115 et #116) -Trois services, `docker compose profiles` non utilisés (démarrage explicite via `make +Quatre services (Airflow 3.3), `docker compose profiles` non utilisés (démarrage explicite via `make airflow-up`, pas dans `make dev`) : | Service | Rôle | Points notables | |---|---|---| -| `airflow-init` | Migre la base de métadonnées, crée le compte admin | Conteneur jetable (`restart: "no"`), ne redémarre jamais. `webserver`/`scheduler` attendent qu'il se termine avec succès | -| `airflow-webserver` | UI, port `8080` | `LocalExecutor` : n'exécute aucune tâche lui-même | +| `airflow-init` | Migre la base de métadonnées, crée le compte admin | Conteneur jetable (`restart: "no"`), ne redémarre jamais. `api-server`, `dag-processor` et `scheduler` attendent qu'il se termine avec succès | +| `airflow-apiserver` | UI et API REST (`/api/v2`), port `8080` | `LocalExecutor` : n'exécute aucune tâche lui-même. Sert aussi l'Execution API que les tâches appellent, d'où le secret JWT partagé | +| `airflow-dag-processor` | Parse `dags/` et publie les DAGs sérialisés | Composant à part entière depuis Airflow 3 : le scheduler ne lit plus les fichiers de DAG | | `airflow-scheduler` | Planifie et **exécute** les tâches (`LocalExecutor`) | Les DAGs y tournent en sous-processus (`uv run --no-sync python -m ...`), c'est lui qui a besoin du volume `airflow_ml_state` | +Airflow 3 impose deux choses que le compose reflète : les tâches ne touchent plus la base de +métadonnées et passent par l'Execution API de l'`api-server`, avec un jeton signé par +`AIRFLOW_JWT_SECRET` (secret partagé entre conteneurs, jamais celui généré au démarrage) ; et +l'authentification par défaut (`SimpleAuthManager`) ne sait pas créer de compte, d'où le +`FabAuthManager` qui garde le compte admin posé par `airflow-init`. Pas de `triggerer` : aucun +opérateur déférable dans les DAGs. + Construits depuis `etl/airflow/Dockerfile`, contexte `.` (racine du repo, pas `etl/airflow/`) : l'image doit pouvoir `COPY` les sources de `ml/` **et** de `apps/backend/` pour se synchroniser deux environnements Python **3.14** (`/opt/ml/.venv` et `/opt/backend/.venv`, `uv sync --locked` à @@ -95,14 +103,14 @@ rend contraignant. `airflow-init` s'appuie sur l'entrypoint de l'image (`_AIRFLOW_DB_MIGRATE`, `_AIRFLOW_WWW_USER_*`) plutôt que sur un script maison : l'entrypoint porte le code de sortie, une migration ratée (typiquement la base `airflow` absente, cf. ci-dessous) fait échouer le service et -`webserver`/`scheduler` ne démarrent pas sur une base non migrée. Le mot de passe du compte admin +`api-server`, `dag-processor` et `scheduler` ne démarrent pas sur une base non migrée. Le mot de passe du compte admin passe par l'environnement, jamais par `argv` (ni `ps`, ni `docker compose config`). Les variables `AIRFLOW_*` ne sont volontairement pas en `${VAR:?}` : Compose interpole le fichier entier avant de filtrer les services, une variable requise manquante casserait `make db-up`, `make dev`... pour tout poste dont le `.env` est antérieur. Elles valent `${VAR:-}` et c'est -`airflow-init` qui refuse de démarrer (clé Fernet, clé Flask, mot de passe ou -`AIRFLOW_APP_SECRET_KEY` vides). +`airflow-init` qui refuse de démarrer (clé Fernet, clé de session de l'API, secret JWT, mot de +passe ou `AIRFLOW_APP_SECRET_KEY` vides). Le conteneur reçoit deux variables du backend en plus de `ML_DATABASE_URL` : `DATABASE_URL`, en dialecte asyncpg, et `APP_SECRET_KEY`, alimentée par `AIRFLOW_APP_SECRET_KEY`. Cette dernière est @@ -242,7 +250,7 @@ Ces arbitrages sont pris. Ils ne vivaient jusqu'ici que dans des commentaires de | Base applicative | `enervision` | Variable `POSTGRES_DB` | | Base de test | `enervision_test` | Créée par `db/init/110-test-database.sql`, nom attendu en dur par `apps/backend/tests/conftest.py` | | Base de métadonnées Airflow | `airflow` | Créée par `db/init/120-airflow-database.sql`, même conteneur `db` | -| Webserver Airflow | `8080` | `make airflow-up`. Scheduler et webserver ne publient que ce port ; les tâches (`LocalExecutor`) tournent côté scheduler, sans port propre | +| API server Airflow | `8080` | `make airflow-up`. Api-server, scheduler et dag-processor ne publient que ce port ; les tâches (`LocalExecutor`) tournent côté scheduler, sans port propre | ## Le trou vers k3s diff --git a/docs/architecture/50-cicd.md b/docs/architecture/50-cicd.md index 4fcd851..11fc9df 100644 --- a/docs/architecture/50-cicd.md +++ b/docs/architecture/50-cicd.md @@ -81,9 +81,10 @@ rien changer), mais ce serait à borner sur un dépôt à forte fréquence de pu `backend.yml`, `ml.yml` et `airflow.yml` déclarent en plus un groupe de concurrence par référence git avec `cancel-in-progress`, ce qui annule un run devenu obsolète par un push plus récent. -**Piège de version** : `etl/airflow` tourne en **Python 3.12** et non 3.14, parce qu'Airflow 2.10 -ne supporte pas encore 3.14. Le 3.14 du module ML ne vit, dans ce contexte, que dans l'image -Docker et son propre environnement. +**Piège de version** : `etl/airflow` tourne en **Python 3.12** et non 3.14 : c'est l'interpréteur +de l'image `apache/airflow:3.3.2-python3.12` retenue, et les tests d'intégrité doivent tourner sur +le même. Le 3.14 du module ML ne vit, dans ce contexte, que dans l'image Docker et son propre +environnement. ## Ce qui bloque un merge