← Tous les articles

Des modules Terraform qui passent à l'échelle avec votre équipe, pas contre elle

Un module Terraform, c'est ce qui permet à une équipe d'arrêter de copier-coller de l'infrastructure. Mais un module qui n'est ni versionné, ni revu, ni testé ne fait pas passer votre équipe à l'échelle - il fait passer à l'échelle votre rayon de dégâts. Une modification négligente d'un module 'vpc' partagé, et chaque environnement qui le référence hérite du bug au prochain apply. La solution : cesser de traiter les modules comme des dossiers partagés et commencer à les traiter comme des produits versionnés.

Voici la version pratique : ce qui rend un module fiable, comment le publier via un vrai pipeline, et comment les équipes le consomment - en local et en CI - directement depuis GitHub, épinglé à un tag ou, quand la sécurité l'exige, à un commit hash immuable. Sans registry privé.

Ce qui rend un module fiable

La fiabilité commence avant tout pipeline. Un module auquel d'autres équipes peuvent se fier est petit et fait une seule chose - un réseau, une base de données, un service - et non une 'plateforme' qui fait tout. Il expose un ensemble clair et documenté d'inputs et d'outputs, embarque des valeurs par défaut raisonnables, et épingle ses propres versions de provider pour se comporter pareil aujourd'hui et dans six mois.

Rien de tout cela ne survit à une équipe qui grandit sans que deux choses soient non négociables : chaque changement est revu, et chaque changement est testé contre une infrastructure réelle et jetable. Un module est le seul morceau de code dont les bugs sont multipliés par chaque consommateur - il mérite la barre la plus haute que vous ayez.

La version, c'est le contrat

À mesure que l'organisation grandit et que de nouveaux environnements apparaissent - un sandbox ici, une seconde région là - la version cesse d'être un luxe et devient le contrat entre le module et tous ceux qui l'utilisent. Chaque consommateur épingle une version explicite ; personne ne suit main. Le versionnage sémantique dit alors au consommateur ce que signifie un saut de version : un patch est sûr, un minor ajoute, un major l'obligera à changer du code.

Épingler, c'est aussi ce qui permet d'avancer et de reculer en confiance. Passer en production est un saut de version ; une mauvaise release est un retour à la précédente. Sans versions, 'faire un rollback du module' veut en fait dire 'retrouver à quel commit il en était mardi dernier'.

Consommez-le depuis GitHub - sans registry

Vous n'avez besoin ni d'un registry privé ni d'Artifactory pour partager des modules correctement. Référencez le module directement par sa source Git et un ref. La même chaîne de source fonctionne sur un portable et en CI ; l'authentification se résume à un token GitHub - un PAT en local, le token du runner en CI - branché une fois pour que les clones HTTPS portent l'identifiant.

# main.tf - épingler à un tag publié
module "vpc" {
  source = "git::https://github.com/acme/tf-modules.git//modules/vpc?ref=v1.4.0"

  cidr_block = "10.20.0.0/16"
  azs        = ["eu-west-1a", "eu-west-1b"]
}

L'identifiant se définit une fois via le insteadOf de git, donc ni votre code ni votre state ne contiennent jamais de secret - Terraform se contente de cloner en HTTPS sous votre identité :

# local + CI : laisser git utiliser un token pour github.com en HTTPS
git config --global url."https://oauth2:${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"

Immutabilité : épinglez un commit hash quand il doit rester verrouillé

Un tag est commode, mais il est mobile : quiconque a les droits d'écriture peut le repointer ou faire un force-push de v1.4.0 vers un autre code, et votre prochain apply le récupère. Pour la plupart des modules, ce compromis convient. Pour ceux qui ne doivent pas bouger sous vos pieds - tout ce qui est sensible côté sécurité ou conformité - épinglez plutôt le commit hash complet. Un commit ne peut pas être substitué : ref=<sha> résout toujours exactement vers les octets que vous avez revus.

# verrouillé à un commit exact, impossible à substituer
module "kms" {
  source = "git::https://github.com/acme/tf-modules.git//modules/kms?ref=3f9a1c4e8b7d6c5a4f3e2d1c0b9a8f7e6d5c4b3a"
}
Épingler un module : tag (mobile) vs commit hash (immuable) source = "git::https://github.com/acme/tf-modules.git//vpc?ref=v1.4.0" tag - commode, mais mobile : il peut être repointé ou forcé source = "git::https://github.com/acme/tf-modules.git//vpc?ref=3f9a1c4e8b7d" commit hash - immuable : personne ne peut échanger le code La même source fonctionne en local et en CI - authentifiez-vous avec un token GitHub. Ni registry ni Artifactory.
La même source Git fonctionne en local et en CI derrière un token. Un tag est commode mais peut être déplacé ; un commit hash est immuable - utilisez-le quand le module ne doit pas bouger sous vos pieds.

Publiez le module comme un produit

Si les consommateurs épinglent des versions, quelque chose doit produire ces versions de façon fiable - un pipeline dans le repo du module. Et soyons précis : un module ne se 'déploie' pas. Il est validé, testé et publié. Le déploiement, c'est le travail du consommateur.

  1. À chaque pull request, lancez les vérifications statiques : terraform fmt -check, terraform validate et tflint.
  2. Puis testez pour de vrai - montez l'exemple du module dans un compte sandbox jetable, vérifiez-le (terraform test, ou Terratest pour des vérifications plus poussées) et détruisez-le. Un module qui n'a jamais été appliqué est un module non testé.
  3. Au merge sur main, laissez GitVersion calculer la prochaine version sémantique à partir de l'historique des commits, créer le tag Git correspondant, et publier - annoncez la release dans le canal Slack ou Teams de l'équipe (et faites-en un miroir vers un registry, si d'aventure vous en avez un).
Construisez le module comme un produit : du PR à une version épinglable SUR LA PULL REQUEST validerfmt · validate · tflint testerapplique dans un sandbox · vérifie · détruit merge sur main SUR MAIN GitVersioncalcule la prochaine semver tagv1.4.0 publierannonce sur Slack / Teams · (miroir vers un registry)
Une version n'existe qu'une fois le changement revu, validé et réellement appliqué. Au merge, GitVersion dérive la prochaine semver, pose le tag et l'annonce.
# .github/workflows/release.yml (esquisse)
on: { pull_request: {}, push: { branches: [main] } }

jobs:
  check:
    steps:
      - run: terraform fmt -check -recursive
      - run: terraform validate
      - run: tflint
      - run: terraform test          # applique les exemples dans un sandbox et detruit

  release:
    if: github.ref == 'refs/heads/main'
    needs: check
    steps:
      - uses: gittools/actions/gitversion/execute@v3   # -> prochaine semver
      - run: gh release create "v${VERSION}"
      - run: ./notify.sh "tf-modules ${VERSION} publie"  # Slack / Teams

L'outil exact importe peu - GitVersion, release-please et semantic-release font tous l'affaire. Ce qui compte, c'est qu'une version ne voie le jour qu'une fois le changement revu, validé et réellement appliqué.

Maintenant, utilisez-le : de Terraform à Terragrunt

Avec un module testé et taggé, un projet Terraform classique se contente de l'épingler - le bloc module ci-dessus, c'est toute l'histoire. Ça suffit pour un ou deux environnements. Mais dès que vous faites tourner la même stack en dev, staging et production, sur une ou plusieurs régions, copier-coller cette plomberie par environnement, c'est exactement la duplication que les modules étaient censés éliminer.

Terragrunt garde les environnements DRY

Terragrunt enveloppe Terraform pour supprimer cette répétition : il génère la config de backend et de provider, garde la version du module à un seul endroit par stack, et laisse chaque environnement n'être qu'un fichier léger qui dit 'ce module, cette version, ces inputs'. Promouvoir le long de la chaîne devient un saut de version d'une ligne.

# live/prod/eu-west-1/vpc/terragrunt.hcl
terraform {
  source = "git::https://github.com/acme/tf-modules.git//modules/vpc?ref=v1.4.0"
}
include "root" { path = find_in_parent_folders() }

inputs = {
  cidr_block = "10.30.0.0/16"
  azs        = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
}

Une arborescence qui passe à l'échelle : projet / environnement / région

Organisez le repo live pour que le chemin soit l'adresse de l'infrastructure. Une forme volontairement ennuyeuse : environnement -> région -> composant - adaptez les niveaux à la façon dont votre organisation raisonne réellement (certains séparent d'abord par projet, compte ou équipe) :

live/
├── root.hcl                    # state distant, provider, tags communs
├── dev/
│   └── eu-west-1/
│       ├── vpc/terragrunt.hcl
│       └── eks/terragrunt.hcl
├── staging/
│   └── eu-west-1/ ...
└── prod/
    ├── eu-west-1/
    │   ├── vpc/terragrunt.hcl
    │   └── eks/terragrunt.hcl
    └── eu-central-1/ ...

Chaque feuille épingle une version de module, et le dossier vous dit exactement ce qui tourne où. Mettre la production à jour devient alors une PR revue qui change un ref= de v1.4.0 à v1.5.0 - petit, évident et trivial à annuler.

Des modules fiables et à but unique ; revus et testés à chaque changement ; versionnés et publiés par un pipeline ; consommés depuis GitHub par tag - ou par commit hash immuable quand ça compte - et assemblés avec une arborescence Terragrunt qui reflète votre parc. Voilà ce qui sépare les modules qui passent à l'échelle avec votre équipe de ceux qui, en douce, passent à l'échelle contre elle.

Tous les articles

Vous avez un système comme celui-ci ?

Contactez-nous

Retrouvez-moi sur les réseaux sociaux