feat(infra): Terraform provisionne la VM, GitHub Actions continue de déployer

Le seul Terraform du dépôt installait un cluster k3s que rien ne consomme, et
sa racine ne passait même pas `terraform init` : depuis Terraform 1.x, un
provisioner `destroy` impose que tout le bloc `connection` ne lise que `self`,
et celui du module k3s lisait `var.*`. Il est corrigé en batissant la connexion
sur `triggers`, où ne figurent que l'adresse, le port, l'utilisateur et le
chemin de la clé : jamais la clé ni un mot de passe.

`environments/vm-eni` provisionne la machine qui porte la recette et la
production : Docker et le plugin Compose, `scripts/provision-host.sh`, puis
l'enregistrement du runner. Rien n'y construit d'image ni ne lance de
conteneur, et un `apply` n'interrompt pas la stack qui tourne. Le déploiement
continu ne bouge pas : `deploy.yml` reste le seul chemin de livraison.

`environments/dev` devient `k3s-cible`, parce qu'il provisionnait un cluster et
pas un environnement applicatif, et que `rec` et `prod` vivent sur la même
machine. `environments/prod`, dossier vide, disparaît.

`infra.yml` formate et valide chaque racine à chaque push : c'est l'absence
d'un tel job qui a laissé ce Terraform cassé sans que rien ne le dise. La
boucle parcourt `environments/*`, une racine ajoutée est couverte sans y
toucher.

ADR 0010 pour la frontière entre provisionnement et livraison, et la doc
d'architecture alignée dessus.
This commit is contained in:
Johan LEROY
2026-09-22 11:27:21 +02:00
parent be6be42947
commit 0db69fd023
22 changed files with 543 additions and 30 deletions
@@ -76,6 +76,7 @@ secret.
à chaque push : plusieurs minutes par déploiement, acceptable pour la cadence du projet.
- `environments/prod` de Terraform reste vide. Le provisionnement de la machine est porté par
`scripts/provision-host.sh`, que Terraform pourra appeler par `remote-exec` le jour où une
racine visant la VM existera.
racine visant la VM existera. Cette racine existe depuis l'[ADR 0010](0010-terraform-provisionne-github-actions-deploie.md),
sous le nom `environments/vm-eni`, et `environments/prod` a disparu avec elle.
- L'image frontend quitte `dhi.io/nginx`, registre authentifié dont personne n'a l'accès, pour
`nginx:1.28-alpine`, la même image que le proxy. Elle n'avait jamais été construite.
@@ -0,0 +1,77 @@
# 0010 - Terraform provisionne la machine, GitHub Actions déploie l'application
- Statut : accepté
- Date : 2026-09-22
## Contexte
L'[ADR 0009](0009-deux-environnements-compose-sur-la-vm-eni.md) a posé la livraison : deux
projets Compose sur la VM ENI, alignés sur `dev` et sur `main` par un runner auto-hébergé. Elle
ne dit pas qui prépare la machine. C'est `scripts/provision-host.sh`, lancé à la main en SSH.
La grille d'évaluation attend en C22 que l'infrastructure soit « provisionnée via du code
(Terraform, Ansible…) ». Le seul Terraform du dépôt installe un cluster k3s que rien ne
consomme, qui n'a jamais été appliqué, et dont la racine ne passait même pas `terraform init`
depuis que Terraform refuse les provisioners `destroy` dont la connexion lit autre chose que
`self`. Sa racine s'appelait `environments/dev`, nom qui laissait croire à un environnement
applicatif alors que les deux environnements réels sont `rec` et `prod`, sur la même machine.
La branche `feat/deploy` (PR #141) proposait la réponse inverse : Terraform construit les
images, lance les conteneurs et copie les sources par SSH. La revue a relevé deux racines sur
trois qui ne passent pas `terraform validate`, le mot de passe SSH écrit en clair dans le state,
un backend lancé sans base ni variables d'environnement, et trois architectures différentes pour
trois environnements.
## Décision
**Terraform provisionne la machine, GitHub Actions déploie l'application.** La frontière est
nette et vérifiable : `infra/terraform/environments/vm-eni` installe Docker et le plugin
Compose, exécute `scripts/provision-host.sh`, enregistre le runner. Il ne construit aucune
image, ne lance aucun conteneur, et un `apply` n'interrompt pas la stack qui tourne.
**Le déploiement continu ne change pas.** `deploy.yml` reste le seul chemin de livraison : push
sur `dev` ou `main`, alignement du clone, `make stack-up`, sonde `/api/v1/health/ready`.
**Le Bash reste la mécanique, Terraform devient le point d'entrée.** `provision-host.sh` connaît
les deux environnements, leurs ports décalés, leurs secrets et leurs certificats. Le réécrire en
HCL créerait une seconde source de vérité qui divergerait au premier changement de port.
**Aucun secret dans le state.** Authentification SSH par clé seulement, pas de variable de mot
de passe. Le jeton d'enregistrement du runner est une variable `sensitive` fournie à l'`apply`,
jamais un `trigger` : les `triggers` sont la seule partie d'un `null_resource` que Terraform
persiste.
**Les racines portent le nom de ce qu'elles provisionnent**, pas d'un environnement applicatif :
`vm-eni` pour la machine, `k3s-cible` pour le cluster resté en cible. `environments/prod`,
dossier vide, disparaît.
## Alternatives écartées
- **Ansible à la place du Bash** : plus idiomatique pour de la configuration de machine, et le
jury le reconnaîtrait immédiatement comme de l'IaC. Mais c'est un outil de plus à installer et
à faire tourner, pour réécrire un script qui fonctionne, à trois jours du gel technique.
- **Provisioners applicatifs de `feat/deploy`** : voir la revue sur #141. Terraform y devenait un
orchestrateur concurrent de Compose, sans base de données ni migrations.
- **Terraform appelle aussi `make stack-up`** : le premier démarrage serait plus court d'une
commande, mais Terraform se mettrait à porter la livraison, que le runner rejoue à chaque
push. Deux chemins pour le même acte, c'est précisément ce que #141 montre qu'il ne faut pas.
- **k3s tout de suite** : le cluster serait vide, sans manifeste, sans registre et sans stockage
persistant. Le module reste, documenté comme cible.
- **State Terraform distant** : un seul opérateur, pas d'exécution concurrente. Le backend local
suffit, comme pour `k3s-cible`.
## Conséquences
- Le premier `apply` exige un jeton d'enregistrement du runner, valable une heure et pour une
seule inscription, que seul un administrateur du dépôt peut créer. L'`apply` n'est donc pas
rejouable sans intervention humaine, ce qui est acceptable : il ne se joue qu'à l'installation.
- L'utilisateur propriétaire de `/srv/enervision` doit exister sur la machine avant l'`apply`.
Terraform vérifie et échoue tôt plutôt que de le créer : décider d'un compte système est une
décision d'administration, pas un effet de bord de déploiement.
- Terraform ne sait rien de l'état de la stack. `terraform plan` ne dira jamais que la recette
est tombée ; c'est la sonde de `deploy.yml` qui le dit.
- Pas de provisioner `destroy` sur le runner : il imposerait de mettre le chemin de la clé SSH
dans le state, et `svc.sh uninstall` ne désinscrit pas le runner côté GitHub. Le retrait reste
manuel, depuis les paramètres du dépôt.
- C22 cesse de reposer sur `docker-compose.prod.yml` seul. C23 reste porté par les scripts, que
Terraform appelle désormais au lieu de les remplacer.