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.
4.8 KiB
0010 - Terraform provisionne la machine, GitHub Actions déploie l'application
- Statut : accepté
- Date : 2026-09-22
Contexte
L'ADR 0009 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
applyexige 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'applyn'est donc pas rejouable sans intervention humaine, ce qui est acceptable : il ne se joue qu'à l'installation. - L'utilisateur propriétaire de
/srv/enervisiondoit 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 planne dira jamais que la recette est tombée ; c'est la sonde dedeploy.ymlqui le dit. - Pas de provisioner
destroysur le runner : il imposerait de mettre le chemin de la clé SSH dans le state, etsvc.sh uninstallne 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.ymlseul. C23 reste porté par les scripts, que Terraform appelle désormais au lieu de les remplacer.