Trois phrases du depot annonçaient une surveillance de derive inexistante, et une quatrieme disait qu'aucune base PostgreSQL n'etait joignable pour tester le chargement ML. Les quatre sont maintenant fausses, donc reecrites plutot que laissees en dette. - ADR 0011 : ou vit le calcul et pourquoi pas dans `ml/`, les deux dedoublonnages qu'impose la jointure, et quatre alternatives ecartees avec la contrainte qui les interdit (la metrique MLflow n'est pas la meme grandeur, `alert` borne ses valeurs et refuse un site nul, Prometheus n'a pas de collecteur, ne rien persister ne repond pas a la question du jury). - DAG `derive` quotidien, hors du DAG `alertes` : un echec de derive y ferait croire que la detection a echoue, et la fenetre de 168 h ne se recalcule pas toutes les heures. - ML-START : la limite de `--now` est dite au lieu d'etre decouverte en demonstration. Elle ne decale que l'instant de reference, pas la fenetre de lecture, donc aucun rattrapage ne peut fabriquer de paires prevu/realise sur un jeu fige. - 50-cicd : pourquoi le job ML installe aussi le backend (le schema n'a qu'une source), et ce que coute le filtre de chemins qui l'accompagne.
339 lines
21 KiB
Markdown
339 lines
21 KiB
Markdown
# Infrastructure
|
|
|
|
Plusieurs topologies coexistent et ne servent pas la même chose. Ce document dit laquelle vaut
|
|
dans quel contexte, quelles décisions sont arrêtées, et ce qui manque encore entre elles.
|
|
|
|
| Topologie | Sert à | Statut |
|
|
|---|---|---|
|
|
| Docker Compose | Développer et recetter sur le poste | `Fait` |
|
|
| 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` |
|
|
| Provisionnement Terraform de la VM | Préparer la machine et enregistrer le runner | `En cours` |
|
|
| k3s single-node | Cible à terme | `En cours` |
|
|
|
|
## Poste de développement
|
|
|
|
Statut : `Fait`. Défini par `docker-compose.yml`, projet `enervision`.
|
|
|
|
```mermaid
|
|
flowchart TB
|
|
subgraph poste["Poste de développement"]
|
|
ng["ng serve<br/>:4200"]
|
|
api["uvicorn --reload<br/>:8000"]
|
|
end
|
|
|
|
subgraph compose["docker compose"]
|
|
back["service backend<br/>image construite depuis apps/backend"]
|
|
db[("service db<br/>timescale/timescaledb-ha:pg17")]
|
|
end
|
|
|
|
ng -.->|"proxy /api"| api
|
|
api -->|"hôte :5433 vers conteneur :5432"| db
|
|
back -->|"réseau interne, db:5432"| db
|
|
```
|
|
|
|
| Service | Image | Points notables |
|
|
|---|---|---|
|
|
| `db` | `timescale/timescaledb-ha:pg17` | Publié sur **5433** côté hôte, 5432 souvent déjà pris. `healthcheck` `pg_isready`, 12 tentatives, `start_period` 40s |
|
|
| `backend` | Construite depuis `apps/backend` | `depends_on: db, condition: service_healthy`. **N'embarque pas le source** : toute modification impose `docker compose up -d --build backend` |
|
|
|
|
**La boucle de développement n'utilise pas le service `backend`.** `make db-up` puis `make dev` :
|
|
seule la base tourne en conteneur, l'API et `ng serve` tournent sur le poste avec le rechargement
|
|
à chaud, lancés ensemble par `make dev` (`make dev-backend`/`make dev-frontend` pour lancer l'un
|
|
des deux seul). Le service `backend` sert la stack complète et la recette. Les deux occupent le
|
|
port 8000, ils ne se lancent donc pas ensemble.
|
|
|
|
Trois pièges sont documentés en tête du `docker-compose.yml`, ils ne se devinent pas :
|
|
|
|
- `PGDATA` vaut `/home/postgres/pgdata/data` pour l'image `-ha`, et non le chemin habituel de
|
|
l'image `postgres`. Monté ailleurs, le volume ne retient rien, sans le moindre message.
|
|
- `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 de l'api-server :
|
|
c'est le scheduler qui a besoin du volume `airflow_ml_state` (modèle, magasin MLflow).
|
|
|
|
### Airflow (issues #115, #116 et #119)
|
|
|
|
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. `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` à
|
|
la construction), distincts du Python 3.12 qui fait tourner Airflow lui-même. Les DAGs shellent
|
|
vers ces venvs plutôt que d'importer LightGBM, MLflow ou SQLAlchemy dans le process Airflow.
|
|
Le choix et ses contreparties sont dans
|
|
l'[ADR 0008](../adr/0008-airflow-execute-le-code-du-backend.md).
|
|
|
|
| DAG | Planification | Ce qu'il lance, et où |
|
|
|---|---|---|
|
|
| `ml_train` | manuelle | `enervision_ml.train`, dans `/opt/ml/.venv` |
|
|
| `ml_score` | `0 * * * *` | `enervision_ml.score`, dans `/opt/ml/.venv` |
|
|
| `alertes` | `15 * * * *` | `app.detection.internal_alerts` puis `app.cli generate-recommendations`, dans `/opt/backend/.venv` |
|
|
| `historical_import` | manuelle | `app.etl.historical_import`, dans `/opt/backend/.venv` ; les fichiers de `data/raw` sont montés en lecture seule dans `/opt/data/raw` |
|
|
| `derive` | `30 5 * * *` | `app.monitoring.drift`, dans `/opt/backend/.venv` ; quotidien parce que sa fenêtre couvre 168 h, et sans reprise parce qu'une dérive n'est pas une panne passagère |
|
|
|
|
Le DAG `historical_import` réutilise le pipeline historique existant sans dupliquer sa logique.
|
|
Il reste manuel, car le dataset sert à initialiser l'environnement. Le montage
|
|
`./data/raw:/opt/data/raw:ro` permet au scheduler de lire les fichiers CSV/JSON sans pouvoir les
|
|
modifier.
|
|
|
|
**Pourquoi `alertes` tourne à la quinzième minute.** Sa règle `anomaly` compare une lecture à la
|
|
`prediction` du même instant, que `ml_score` écrit à l'heure pile. Le décalage laisse le scoring
|
|
finir. Aucune dépendance n'est déclarée entre les deux DAGs pour autant, ni `ExternalTaskSensor` ni
|
|
tâche greffée : quatre règles de détection sur cinq ne touchent pas au modèle, et un modèle jamais
|
|
entraîné ne doit pas priver le parc de ses alertes. Le décalage est donc une convention et non une
|
|
garantie : le plafond de `ml_score` est de 30 minutes, et un scoring qui déborde de `:15` prive
|
|
`anomaly` de la `prediction` de l'heure, qu'elle ne retrouvera au passage suivant que si sa fenêtre
|
|
la couvre encore. Les quatre autres règles ne s'en aperçoivent pas.
|
|
|
|
Ses deux tâches s'enchaînent en revanche (`recommendation.alert_id` est une clé étrangère `NOT
|
|
NULL`), et toutes deux sont rejouables sans risque : l'idempotence est portée par la base,
|
|
`uq_alert_source_reference` et `uq_recommendation_alert_rule`. Chacune a 2 tentatives, 2 minutes
|
|
d'attente entre elles et un plafond de 5 minutes **par tentative** : au pire, reprises comprises,
|
|
l'enchaînement occupe 38 minutes, ce qui le garde sous le pas horaire qu'un `max_active_runs=1`
|
|
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
|
|
`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é 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
|
|
**délibérément différente** de celle de l'API. La configuration du backend refuse de se construire
|
|
sans clé, mais la détection ne signe ni ne vérifie aucun jeton : un Airflow compromis, qui permet
|
|
déjà d'exécuter du code depuis son interface, ne doit pas livrer par-dessus la clé de signature
|
|
des JWT.
|
|
|
|
**Pourquoi `ml_train` est manuel.** Réentraîner est coûteux et sa cadence n'est pas une décision
|
|
prise. Surtout, `train.py` écrase le modèle sans comparer ses métriques à celles de l'ancien : un
|
|
cron déploierait silencieusement un modèle dégradé. Tant que ce garde-fou n'existe pas, le
|
|
déclenchement reste humain. `ml_score`, lui, est planifié à l'heure, avec `max_active_runs=1`
|
|
(pas deux scorings simultanés dans `prediction`), 2 tentatives et un plafond de 30 minutes.
|
|
|
|
CI : `.github/workflows/airflow.yml` (Python 3.12 via `etl/airflow/.python-version`) lance lint et
|
|
tests d'intégrité des DAGs, et construit l'image (elle `COPY` `ml/` et `apps/backend/`, une
|
|
modification de l'un ou de l'autre peut donc la casser, d'où leurs chemins dans les déclencheurs)
|
|
avant de vérifier que les deux environnements s'y importent sans réseau.
|
|
|
|
Piège à connaître : sur un volume `pgdata` déjà peuplé (poste de dev existant plutôt que premier
|
|
`make db-up`), `db/init/120-airflow-database.sql` ne se rejoue pas (PostgreSQL n'exécute
|
|
`docker-entrypoint-initdb.d/` que sur un volume vide). Créer la base `airflow` à la main une fois :
|
|
`docker compose exec db psql -U $POSTGRES_USER -d $POSTGRES_DB -c "CREATE DATABASE airflow;"`.
|
|
|
|
`libgomp1` est installé explicitement dans l'image (`apt-get`, en root) : l'image Airflow de base
|
|
est minimale et n'embarque pas la runtime OpenMP dont LightGBM a besoin, sans quoi l'erreur
|
|
(`OSError: libgomp.so.1`) n'apparaît qu'à la première tâche réellement exécutée, pas à la
|
|
construction de l'image.
|
|
|
|
## Machine cible, exécution Docker
|
|
|
|
Statut : `Fait`. Défini par l'overlay `docker-compose.prod.yml`, appliqué par-dessus le
|
|
`docker-compose.yml`. Écrit et validé sur le poste, **jamais encore lancé sur le serveur de
|
|
l'école**. Décision et motifs dans l'[ADR 0007](../adr/0007-terminaison-tls-et-reverse-proxy-nginx.md).
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
navigateur["Navigateur"]
|
|
|
|
subgraph machine["Machine on-premise"]
|
|
proxy["service proxy<br/>nginx:1.28-alpine<br/>:80 et :443"]
|
|
front["service frontend<br/>nginx statique :3000"]
|
|
api["service backend<br/>uvicorn :8000"]
|
|
db[("service db<br/>:5432")]
|
|
mail["service mailpit"]
|
|
end
|
|
|
|
navigateur -->|"HTTPS"| proxy
|
|
proxy -->|"/"| front
|
|
proxy -->|"/api/"| api
|
|
api --> db
|
|
api --> mail
|
|
```
|
|
|
|
Le proxy est **le seul service à publier des ports** sur le réseau. Backend et frontend ne sont
|
|
plus publiés du tout, la base et l'interface Mailpit sont ramenées sur `127.0.0.1`, donc joignables
|
|
par tunnel SSH et pas autrement. Le détail du routage, les deux modes d'obtention du certificat et
|
|
la commande de validation hors exécution sont dans [`infra/proxy/README.md`](../../infra/proxy/README.md).
|
|
|
|
Deux conséquences se propagent jusqu'à l'application, et elles ne se devinent pas :
|
|
|
|
- Servir le SPA et l'API sous la même origine est ce qui rend le cookie `__Secure-ev_refresh`
|
|
utilisable. Sans cela, `apiUrl: '/api/v1'` ne mène nulle part une fois en conteneur.
|
|
- `APP_TRUST_PROXY_HEADERS` passe à vrai en même temps, sinon la limitation de débit par IP
|
|
compte sur l'IP du proxy et devient globale.
|
|
|
|
### Deux environnements sur la même machine
|
|
|
|
Statut : `En cours`, la machine n'étant pas encore provisionnée. Décision et motifs dans
|
|
l'[ADR 0009](../adr/0009-deux-environnements-compose-sur-la-vm-eni.md).
|
|
La VM `eadl-2025-nantes-g3` portera la recette et la production, chacune dans son clone du dépôt,
|
|
son `.env` et son projet Compose. Le nom de projet préfixe volumes, réseau et conteneurs : rien
|
|
n'est partagé. `scripts/provision-host.sh` prépare les deux dossiers, génère les secrets et les
|
|
certificats, et ne démarre rien.
|
|
|
|
| | Recette | Production |
|
|
|---|---|---|
|
|
| Branche, environnement GitHub | `dev`, `rec` | `main`, `prod` |
|
|
| Dossier, projet Compose | `/srv/enervision/rec`, `enervision-rec` | `/srv/enervision/prod`, `enervision-prod` |
|
|
| URL | `https://rec.enervision.local:8443` | `https://enervision.local` |
|
|
| Proxy HTTP, HTTPS | `127.0.0.1:8081`, `8443` | `80`, `443` |
|
|
| PostgreSQL, Mailpit, Airflow, sur `127.0.0.1` | `5434`, `8026`, `8082` | `5433`, `8025`, `8080` |
|
|
|
|
Les deux noms d'hôte visent la même IP, à déclarer dans le `/etc/hosts` des postes. Deux noms
|
|
distincts sont nécessaires : le cookie `__Secure-ev_refresh` est posé par hôte, pas par port.
|
|
La redirection HTTP de la recette est ramenée sur la boucle locale parce que la configuration
|
|
Nginx renvoie vers `https://$host` sans port, c'est-à-dire vers la production.
|
|
|
|
Le déploiement est décrit dans [50-cicd.md](50-cicd.md) : un runner GitHub Actions installé sur
|
|
la VM aligne le dossier sur la branche poussée et lance `make stack-up`.
|
|
|
|
### Provisionnement de la machine
|
|
|
|
Statut : `En cours`. Décision et frontière dans
|
|
l'[ADR 0010](../adr/0010-terraform-provisionne-github-actions-deploie.md) : **Terraform
|
|
provisionne la machine, GitHub Actions déploie l'application**. La racine
|
|
`infra/terraform/environments/vm-eni/` fait trois choses, et rien d'autre.
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant TF as terraform apply
|
|
participant VM as VM eadl-2025-nantes-g3
|
|
participant GH as GitHub
|
|
|
|
TF->>VM: SSH, get.docker.com puis docker compose version
|
|
TF->>VM: copie et exécute scripts/provision-host.sh
|
|
VM->>VM: deux clones, deux .env, deux certificats
|
|
TF->>VM: installe actions-runner, config.sh, svc.sh
|
|
VM->>GH: le runner s'enregistre avec le label eni-g3
|
|
```
|
|
|
|
Aucune image n'y est construite, aucun conteneur lancé : un `apply` n'interrompt pas la stack qui
|
|
tourne. Le premier démarrage reste manuel, `make stack-up` dans chaque dossier ; les suivants
|
|
sont joués par le runner à chaque push. Terraform ne sait rien de l'état de la stack, c'est la
|
|
sonde de `deploy.yml` qui le dit.
|
|
|
|
Le jeton d'enregistrement du runner est valable une heure et ne vaut que pour une inscription :
|
|
l'`apply` n'est pas rejouable sans qu'un administrateur du dépôt en crée un nouveau.
|
|
|
|
## Cible à terme, k3s
|
|
|
|
Statut : `En cours`. Le module `infra/terraform/modules/k3s/` installe le cluster, depuis la
|
|
racine `infra/terraform/environments/k3s-cible/`. Il n'a jamais été appliqué.
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
poste["Poste<br/>terraform apply"]
|
|
kube["kubeconfig local"]
|
|
|
|
subgraph serveur["Serveur on-premise"]
|
|
k3s["k3s server single-node<br/>Traefik désactivé"]
|
|
charges["Charges de travail<br/>aucune déclarée"]
|
|
end
|
|
|
|
poste -->|"SSH, get.k3s.io"| k3s
|
|
k3s -->|"cat /etc/rancher/k3s/k3s.yaml"| kube
|
|
k3s -.-> charges
|
|
```
|
|
|
|
### Ce que le Terraform fait
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant TF as terraform apply
|
|
participant SRV as Serveur on-premise
|
|
participant L as Poste local
|
|
|
|
TF->>SRV: SSH, curl get.k3s.io puis install server
|
|
TF->>SRV: attend /etc/rancher/k3s/k3s.yaml
|
|
TF->>SRV: ssh cat k3s.yaml
|
|
SRV-->>L: kubeconfig, 127.0.0.1 réécrit en ssh_host
|
|
```
|
|
|
|
### Ce que le Terraform ne fait pas
|
|
|
|
Il déclare le provider `null` et **lui seul** : ni `kubernetes`, ni `helm`. Aucun namespace,
|
|
aucun déploiement, aucun service, aucun ingress. À l'issue d'un `apply`, on dispose d'un cluster
|
|
vide et d'un kubeconfig, rien de plus.
|
|
|
|
## Décisions figées
|
|
|
|
Ces arbitrages sont pris. Ils ne vivaient jusqu'ici que dans des commentaires de code et des
|
|
`description` de variables, c'est-à-dire qu'ils ne survivaient pas au premier remaniement.
|
|
|
|
| Décision | Raison | Où elle est appliquée |
|
|
|---|---|---|
|
|
| k3s single-node plutôt que Kubernetes complet | Une seule machine on-premise, pas de plan de contrôle à répartir | `modules/k3s/main.tf` |
|
|
| `k3s_version` obligatoire, valeur vide refusée | Sans épinglage, `get.k3s.io` installe la dernière version à chaque exécution : le déploiement cesse d'être reproductible | `validation` dans `modules/k3s/variables.tf` |
|
|
| Traefik désactivé | Le choix d'ingress reste ouvert, on ne veut pas en subir un par défaut | `k3s_disable_components`, défaut `["traefik"]` |
|
|
| Kubeconfig laissé en `600/root`, lu par `sudo` | `--write-kubeconfig-mode 644` exposerait `cluster-admin` à tout utilisateur local de la machine | Commentaire et `fetch_kubeconfig` dans `modules/k3s/main.tf` |
|
|
| State Terraform en backend `local` | Un seul opérateur, pas d'exécution concurrente, pas de dépendance à un stockage distant | `versions.tf` de chaque racine |
|
|
| `.terraform.lock.hcl` versionné | Fige les versions de provider entre contributeurs et future CI | Commentaire dans `.gitignore` |
|
|
| `*.tfvars` ignoré, `*.tfvars.example` versionné | Les tfvars portent l'adresse du serveur et le chemin de la clé | `.gitignore` |
|
|
| Désinstallation gérée au `destroy` | `k3s-uninstall.sh` en `on_failure = continue` : un serveur injoignable ne bloque pas le `destroy` | `modules/k3s/main.tf` |
|
|
| Une racine Terraform par machine provisionnée, nommée d'après elle | `environments/dev` laissait croire à un environnement applicatif, alors que `rec` et `prod` vivent sur la même machine et ne sont pas provisionnés par Terraform | `environments/vm-eni`, `environments/k3s-cible` |
|
|
| Terraform provisionne, GitHub Actions déploie | Deux chemins pour le même acte de livraison, c'est ce que la revue de #141 relève sur la VM | [ADR 0010](../adr/0010-terraform-provisionne-github-actions-deploie.md) |
|
|
| Connexion SSH par clé, jamais par mot de passe | Une variable de mot de passe finit en clair dans le state, ou dans les `triggers` qui y sont persistés | `environments/vm-eni/variables.tf`, `modules/k3s/main.tf` |
|
|
| Terminaison TLS par un reverse proxy Nginx en Compose | L'ingress k3s supposait un registre et des manifestes qui n'existent pas, à quatre jours du rendu | `docker-compose.prod.yml`, [ADR 0007](../adr/0007-terminaison-tls-et-reverse-proxy-nginx.md) |
|
|
| Certificat auto-signé par défaut, chemin ACME câblé | Aucun domaine public ne résout vers la machine : le défi HTTP-01 ne peut pas aboutir | `scripts/tls-selfsigned.sh`, `infra/proxy/acme-deploy-hook.sh` |
|
|
| Un projet Compose par environnement, sur la même machine | Une seule VM, et l'isolation par nom de projet ne demande ni cluster ni registre | `.env` de chaque dossier, [ADR 0009](../adr/0009-deux-environnements-compose-sur-la-vm-eni.md) |
|
|
| Runner GitHub Actions auto-hébergé sur la VM | Les runners hébergés par GitHub ne joignent pas une adresse privée d'école | `.github/workflows/deploy.yml` |
|
|
| Secrets dans le `.env` de chaque environnement, sur la machine | Ni dans git, ni dans GitHub : le runner n'a rien à recevoir | `scripts/provision-host.sh` |
|
|
|
|
## Ports et noms
|
|
|
|
| Quoi | Valeur | Remarque |
|
|
|---|---|---|
|
|
| PostgreSQL, côté hôte | `5433` | Redirigé vers 5432 dans le conteneur. 5432 est souvent déjà pris |
|
|
| PostgreSQL, côté réseau Compose | `db:5432` | Nom de service, utilisé par `DATABASE_URL` du service `backend` |
|
|
| API | `8000` | Identique en conteneur et hors conteneur |
|
|
| Frontend, `ng serve` | `4200` | Boucle de développement. Valeur par défaut d'`APP_CORS_ORIGINS` |
|
|
| Frontend en conteneur | `3000` | Ce qu'écoute le nginx de l'image, en conteneur comme côté hôte |
|
|
| Reverse proxy | `80` et `443` | Les seuls ports publiés par `docker-compose.prod.yml`, via `PROXY_HTTP_PORT` et `PROXY_HTTPS_PORT`. 80 ne sert que la redirection et le défi ACME. La recette publie `8443` et `127.0.0.1:8081` |
|
|
| SSH du serveur | `22` par défaut | `ssh_port`, redéfinissable |
|
|
| 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` |
|
|
| 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
|
|
|
|
Rien ne relie aujourd'hui ce qui est construit par Compose et ce qui tournerait sur k3s. Compose
|
|
construit une image backend localement ; k3s ne saurait pas où la trouver. C'est la première
|
|
question à trancher, avant toute ressource Kubernetes.
|
|
|
|
## Questions ouvertes
|
|
|
|
- **Quel ingress** remplace Traefik le jour de la bascule k3s. Qui termine le TLS est tranché par
|
|
l'[ADR 0007](../adr/0007-terminaison-tls-et-reverse-proxy-nginx.md), mais la réponse vaut pour la
|
|
topologie Compose, pas pour Kubernetes.
|
|
- **Quel nom de domaine public**, sans lequel Let's Encrypt reste hors d'atteinte et le certificat
|
|
reste auto-signé.
|
|
- **Quel registre d'images**, et comment il est alimenté sans CI.
|
|
- **Quel stockage persistant** côté Kubernetes pour PostgreSQL, et si la base tourne dans le
|
|
cluster ou à côté.
|
|
- **Quelle stratégie de sauvegarde et de restauration** des données de mesure.
|