Trois environnements et un frontal SNI au lieu de deux, certificats Let's Encrypt par DNS-01, sept DAGs, index des ADR complété jusqu'à 0020. Les exemples de l'ETL passent en bash et n'utilisent plus l'option --limit, retirée. L'adresse de la machine est masquée dans l'arbre, les ADR 0009 et 0014 portent une note datée sur l'approbation de la production.
7.9 KiB
EnerVision · procédure de déploiement (22/09/2026)
Terraform provisionne la machine, GitHub Actions déploie (ADR 0010). Deux environnements Compose
sur la VM ENI <IP-VM-G3> : rec sur la branche dev, prod sur main (ADR 0009).
| 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 |
| Postgres / Mailpit / Airflow (locaux) | 5434 / 8026 / 8082 |
5433 / 8025 / 8080 |
0. Avant toute commande
- Clé SSH déposée sur la VM :
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@<IP-VM-G3>. Terraform ne gère pas l'authentification par mot de passe (elle finirait dans le state). - L'utilisateur propriétaire existe déjà sur la VM (ex.
enervision) : il possède/srv/enervisionet fait tourner le runner. Terraform échoue tôt s'il manque, il ne le crée pas. - Jeton d'enregistrement du runner : Settings > Actions > Runners > New self-hosted runner. Valable 1 h, une seule inscription, créé par un administrateur du dépôt (ineszang).
mainest en retard de 64 commits et ne porte nideploy.yml, niprovision-host.sh, ni le Terraform, ni l'overlay paramétré (ports etPUBLIC_ORIGINen dur). Tant quedevn'est pas remonté dansmain, seule la recette est déployable : le cloneprodsera préparé mais sonmake stack-uppublierait 80/443 sans les variables, et aucun push surmainne déclencherait de déploiement (le workflow n'y existe pas). Remonterdev→mainavant de toucher à prod.
1. Provisionner la machine (depuis le poste)
cd infra/terraform/environments/vm-eni
cp terraform.tfvars.example terraform.tfvars
terraform init
terraform apply
terraform.tfvars, ignoré par git, trois valeurs à renseigner :
proprietaire = "enervision" # doit exister sur la VM
runner_version = "2.330.0" # épingler depuis github.com/actions/runner/releases
runner_token = "..." # jeton d'1 h, à retirer du fichier après l'apply
Défauts utiles : ssh_host = "<IP-VM-G3>", ssh_user = "root",
ssh_private_key_path = "~/.ssh/id_ed25519", racine = "/srv/enervision",
runner_labels = "eni-g3" (ciblé par deploy.yml), runner_dossier = "/opt/actions-runner".
L'apply fait trois choses, dans cet ordre : Docker + plugin Compose et usermod -aG docker,
puis scripts/provision-host.sh, puis l'installation et l'enregistrement du runner en service.
Il ne construit aucune image et ne démarre aucun conteneur : un apply n'interrompt pas la stack.
Rejouable : un clone existant est réaligné, un .env présent n'est jamais réécrit, un
certificat présent n'est jamais régénéré. Un nouvel apply de la ressource runner redemande un
jeton frais (il expire en 1 h).
2. Variables d'environnement
Un .env par dossier, en 600, généré sur la machine depuis .env.example. Aucun secret ne
passe par git ni par GitHub : le runner n'en reçoit aucun (seul SONAR_TOKEN existe côté CI).
Générés automatiquement : POSTGRES_PASSWORD, APP_SECRET_KEY, AIRFLOW_FERNET_KEY,
AIRFLOW_API_SECRET_KEY, AIRFLOW_JWT_SECRET, AIRFLOW_ADMIN_PASSWORD, AIRFLOW_APP_SECRET_KEY.
Fixés par environnement : COMPOSE_PROJECT_NAME, PUBLIC_HOST, PUBLIC_ORIGIN,
PROXY_HTTP_PORT, PROXY_HTTPS_PORT, POSTGRES_PORT, MAILPIT_UI_PORT, AIRFLOW_PORT.
À renseigner à la main, dans chaque .env, avant le premier démarrage :
APP_MOCK_API_USERNAME=...
APP_MOCK_API_PASSWORD=...
Garde-fou : le script refuse d'écrire un .env s'il reste un change_me hors APP_MOCK_API_*
(cas vécu d'une clé renommée en amont, AIRFLOW_WEBSERVER_SECRET_KEY sous Airflow 3).
APP_ENV=prod et APP_DEBUG=false sont en dur dans l'overlay, pas dans le .env : la valeur
local du poste reprendrait le dessus et rouvrirait /docs sans cookie __Secure-.
TS_TUNE_MEMORY=2GB et TS_TUNE_NUM_CPUS=2 sont obligatoires : deux TimescaleDB sur 8 Go se
réserveraient 25 % de la RAM chacune. La montée à 32 Go est à demander.
Certificats auto-signés générés par le script (infra/proxy/tls/), couvrant le nom d'hôte,
localhost et l'IP. Let's Encrypt (make tls-acme, ACME_EMAIL) reste hors d'atteinte sans
domaine public résolvable.
3. Premier démarrage (manuel, une seule fois, sur la VM)
cd /srv/enervision/rec && make stack-up # build + up + alembic upgrade head
cd /srv/enervision/prod && make stack-up # seulement après la remontée dev → main
stack-up refuse de démarrer si le certificat manque ou ne couvre pas PUBLIC_HOST, et applique
les migrations : sans elles la stack démarrerait verte sur une base sans schéma.
Premier administrateur, stack démarrée, dans chaque dossier :
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec backend \
python -m app.cli create-admin --email <adresse>
Données historiques : data/raw n'est pas dans git. Déposer les fichiers dans chaque dossier
avant de déclencher le DAG historical_import.
4. Réglages GitHub (administrateur du dépôt)
- Environnement
prod: branchemainseule autorisée, approbation d'un relecteur requise. - Environnement
rec: branchedevseule autorisée, sans approbation. - Settings > Actions : « Require approval for all outside collaborators ». Un runner
auto-hébergé sur un dépôt public exécute ce qu'on lui envoie ;
deploy.ymlne se déclenche jamais surpull_request, et le runner ne tourne jamais en root.
5. Déploiement continu, ensuite
Un push sur dev déploie la recette, un push sur main la production après approbation.
Le job (runner eni-g3) aligne le clone (fetch, checkout, reset --hard), lance
make stack-up, puis sonde /api/v1/health/ready derrière le proxy pendant 3 minutes ; en cas
d'échec il publie ps et les 50 dernières lignes de backend et proxy. Pas de checkout dans
l'espace du runner : .env, certificats et volumes doivent survivre d'un déploiement à l'autre.
Concurrence par branche, sans annulation.
Déclenchement manuel possible : workflow_dispatch.
6. Vérifier
curl -k https://localhost:8443/api/v1/health/ready # recette, sur la VM
curl -k https://localhost/api/v1/health/ready # production, sur la VM
Depuis un poste, ajouter à /etc/hosts :
<IP-VM-G3> enervision.local rec.enervision.local
Les deux noms sont obligatoires : le cookie __Secure-ev_refresh est posé par hôte et non par
port ; un seul nom déconnecterait la production à chaque connexion en recette.
Pièges à connaître
- Compose 2.24.4 minimum : l'overlay emploie
!overrideet!reset, sans quoi l'API resterait joignable en clair à côté du proxy. Le script le vérifie. - Le runner doit tourner sous le propriétaire de
/srv/enervision: sinon git refuse les clones (propriété douteuse) et le.enven600lui échappe. Correctif :PROPRIETAIRE=<utilisateur> bash scripts/provision-host.sh. - Chaque environnement reconstruit ses images à partir du même commit : la production n'exécute pas l'artefact validé en recette, mais un second build. Le passage à GHCR lèvera cette limite.
- Un
.envperdu se régénère, mais invalide les sessions et les connexions chiffrées par Airflow : ils ne sont sauvegardés nulle part ailleurs. - Retirer le runner se fait à la main, depuis les paramètres du dépôt :
terraform destroyne le désinscrit pas.
Références dans le dépôt
docs/adr/0009-deux-environnements-compose-sur-la-vm-eni.md,
docs/adr/0010-terraform-provisionne-github-actions-deploie.md,
docs/architecture/50-cicd.md, docs/architecture/10-infra.md, infra/README.md,
scripts/provision-host.sh, .github/workflows/deploy.yml, docker-compose.prod.yml.