Files
Johan LEROY a577356198 feat(infra): fait tourner Airflow 3 dans la stack Compose
Image apache/airflow:3.3.2-python3.12. Le webserver devient l'api-server
(healthcheck /api/v2/monitor/health) et le dag-processor est un service à
part : depuis Airflow 3 le scheduler ne parse plus les DAGs.

Les tâches passent par l'Execution API de l'api-server avec un jeton signé
par AIRFLOW_JWT_SECRET, nouveau secret du .env partagé entre conteneurs.
AIRFLOW_WEBSERVER_SECRET_KEY devient AIRFLOW_API_SECRET_KEY. airflow-init
refuse toujours de démarrer si l'un des secrets manque.

FabAuthManager explicite : le SimpleAuthManager par défaut ne sait pas créer
le compte admin que airflow-init pose via _AIRFLOW_WWW_USER_*. Pas de
triggerer, aucun opérateur déférable dans les DAGs.
2026-09-22 09:01:07 +02:00

51 lines
2.5 KiB
Docker

# Image Airflow EnerVision : ajoute ml/ et apps/backend/ dans leurs propres environnements Python
# 3.14, distincts du Python 3.12 de l'image de base qui fait tourner Airflow lui-meme, pour que
# les DAGs puissent lancer `uv run python -m enervision_ml.train`/`.score`,
# `app.detection.internal_alerts` et `app.cli` en sous-processus. Airflow ne devient jamais un
# consommateur direct de LightGBM, de MLflow ou du SQLAlchemy du backend. Cf. ADR 0008.
FROM apache/airflow:3.3.2-python3.12
# LightGBM est compile contre libgomp (OpenMP), absent de l'image de base (minimale, sans
# toolchain de compilation). Sans lui : `OSError: libgomp.so.1: cannot open shared object file`
# au premier `import lightgbm`, seulement au moment ou une tache tourne reellement.
USER root
RUN apt-get update \
&& apt-get install --no-install-recommends -y libgomp1 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Pre-cree, appartenant a `airflow` : docker-compose y monte un volume nomme partage entre
# `ml_train` et `ml_score` (le modele ecrit par l'un, lu par l'autre). Un volume nomme herite des
# permissions du repertoire qu'il recouvre a son premier montage ; sans ce chown prealable, il
# serait cree root:root et illisible par le conteneur, qui tourne en `airflow` (uid 50000).
# `/opt/backend` ne porte aucun volume, mais `WORKDIR` le creerait root meme sous `USER airflow`.
RUN mkdir -p /opt/ml/state /opt/backend && chown -R airflow:root /opt/ml /opt/backend
USER airflow
# L'image de base embarque deja un `uv`, mais pas celui que le depot epingle par ailleurs
# (apps/backend/Dockerfile) : on aligne, pour que le format de verrou lu soit le meme partout.
COPY --from=ghcr.io/astral-sh/uv:0.11.26 /uv /home/airflow/.local/bin/uv
# Piege : pas de `UV_PROJECT_ENVIRONMENT` global. Il vaudrait pour les deux projets, et `uv run`
# dans l'un resoudrait le venv de l'autre. Par defaut, uv prend `<projet>/.venv`, donc le bon.
ENV UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy
WORKDIR /opt/ml
COPY --chown=airflow:root ml/pyproject.toml ml/uv.lock ./
RUN uv sync --locked --no-install-project --no-dev
COPY --chown=airflow:root ml/enervision_ml ./enervision_ml
RUN uv sync --locked --no-dev
WORKDIR /opt/backend
# `packages = ["app"]` : le reste de apps/backend (alembic, tests) n'a rien a faire dans l'image.
COPY --chown=airflow:root apps/backend/pyproject.toml apps/backend/uv.lock ./
RUN uv sync --locked --no-install-project --no-dev
COPY --chown=airflow:root apps/backend/app ./app
RUN uv sync --locked --no-dev
WORKDIR /opt/airflow