Infrastructure as code à dix équipes
Où couper l'état, à qui appartient un module et comment traiter la dérive quand dix équipes écrivent la même infrastructure : les critères pour décider, et le coût de chaque découpage.
Par Elias VarenMis à jour le 7 min de lecture
Un plan qui dure onze minutes, qui affiche quatre cents ressources dont trois vous concernent, et qu'une autre équipe a verrouillé depuis vingt minutes parce que son apply attend une validation. À ce stade, plus personne ne lit le plan. On cherche son nom de ressource dans la sortie, on vérifie que rien n'est marqué destroy, on approuve. L'outil qui devait rendre les changements d'infrastructure relisibles produit exactement l'inverse.
L'infrastructure as code tient très bien à une équipe avec un seul état. Elle casse quand l'organisation grandit, sur la taille de l'état, la propriété des modules et la dérive. Ce sont des décisions d'organisation, qu'aucun outil supplémentaire ne prendra à votre place.
L'état comme frontière de panne
Un fichier d'état Terraform ou OpenTofu définit plusieurs choses à la fois : ce qu'un apply peut détruire, ce qu'un verrou bloque, et ce qu'un plan doit rafraîchir. Choisir où couper l'état revient donc à choisir le rayon d'explosion d'une erreur, la file d'attente entre équipes et la durée de la boucle de retour.
L'état unique maximise les trois. Une faute de frappe dans le module réseau peut proposer de recréer la base de données d'une équipe qui n'a rien demandé. Le verrou est global, et dix équipes font la queue derrière un seul apply. Le rafraîchissement interroge l'API du fournisseur pour chaque ressource, si bien que la durée du plan croît avec la somme du travail de tout le monde.
L'erreur inverse existe, avec un état par ressource ou par micro-composant. Chaque changement réel traverse alors cinq états, dans un ordre que personne n'a écrit, et l'on a reconstruit à la main le graphe de dépendances que l'outil calculait tout seul.
Je coupe selon deux axes, dans cet ordre.
La vitesse de changement. Le réseau, les zones DNS, les comptes et les rôles changent quelques fois par an. Les services applicatifs changent chaque semaine. Les mettre dans le même état expose ce qui bouge rarement au risque de ce qui bouge chaque semaine.
Le propriétaire. Un état a une équipe propriétaire, et une seule. Si deux équipes doivent approuver chaque apply, la coupe est au mauvais endroit.
En pratique, cela donne trois couches, chacune dupliquée par environnement :
foundations/ réseau, DNS, identités, journalisation équipe plateforme ~ mensuel
shared/ cluster de bases, stockage objet, registre équipe plateforme ~ hebdo
services/<name>/ ce qu'un service possède en propre équipe du service ~ quotidienLe critère de contrôle est simple : un plan sur l'état d'un service doit se lire en entier, par un humain, en moins de deux minutes. S'il ne se lit plus, l'état est trop gros.
Les dépendances entre états sont un contrat
Dès qu'on coupe, un service a besoin de l'identifiant du réseau ou de l'adresse du cluster de bases. La première solution qui vient consiste à lire l'état distant de la couche du dessous. Elle marche, au prix d'un couplage invisible : le consommateur a accès en lecture à tout l'état du producteur, secrets compris. Il dépend en plus des noms de sorties comme d'une API que personne n'a déclarée publique.
Je préfère une publication explicite. La couche productrice écrit ses sorties utiles dans un registre de paramètres, ou dans un simple objet JSON versionné sur le stockage objet, et la couche consommatrice lit ces valeurs par une source de données.
# foundations/outputs.tf — what the layer commits to provide
resource "aws_ssm_parameter" "private_subnets" {
name = "/infra/prod/network/private_subnet_ids"
type = "StringList"
value = join(",", module.network.private_subnet_ids)
}
# services/billing/main.tf — what the service consumes
data "aws_ssm_parameter" "private_subnets" {
name = "/infra/prod/network/private_subnet_ids"
}L'exemple emploie la syntaxe d'un fournisseur donné ; le principe se transpose à n'importe quel magasin clé-valeur. La liste des paramètres publiés devient le contrat de la couche. Elle se relit, et le producteur peut refondre son état interne sans casser personne tant que les clés restent stables.
Un module sans propriétaire est une dette à dix signatures
Le module partagé porte la promesse de l'IaC à plusieurs, une façon de créer une base, un bucket, un service, avec les bons réglages par défaut. C'est aussi là que l'organisation se bloque.
Soit un module « base de données » né dans une équipe. Trois autres le copient, chacune ajoute une variable pour son cas. Au bout d'un an, il peut compter quarante variables, dont douze booléens qui s'excluent mutuellement, et plus personne n'ose changer une valeur par défaut parce qu'aucun test ne dit qui en dépend.
Les modules partagés tiennent mieux avec quelques règles.
Un module a une équipe propriétaire nommée dans le repo, qui relit toutes les modifications et qui peut dire non. Sans droit de refus, on fait de la maintenance, sans propriété.
Un module est versionné et consommé par version épinglée. Aucune référence à la branche main. Le propriétaire publie, les consommateurs montent quand ils le décident, et un changement cassant est une version majeure avec une note de migration.
module "database" {
source = "git::ssh://git@forge.internal/infra/modules.git//postgres?ref=postgres-v3.2.0"
name = "billing"
size = "medium"
}Un module expose peu. Au-delà d'une dizaine de variables, le module n'encode plus une décision ; il rejoue l'API du fournisseur avec une couche d'indirection en plus. Je préfère deux modules opinionnés (« base transactionnelle », « base analytique ») à un module générique.
Le coût est réel, car l'épinglage crée une traîne de versions. Dix équipes, ce sont dix dates de montée différentes, et le jour où un correctif de sécurité doit passer partout, on paie la liberté qu'on a donnée. Il faut un inventaire des versions consommées (un script qui parcourt les repos suffit) et une règle de fin de support : au-delà de deux versions majeures de retard, la montée devient prioritaire.
La dérive se mesure, puis se tranche
La dérive désigne l'écart entre l'état déclaré et la réalité. Ses sources sont à distinguer : la modification manuelle pendant un incident, la ressource créée hors code « pour tester », et le fournisseur qui change une valeur par défaut ou ajoute un attribut.
La première est légitime. Interdire la console pendant une panne revient à choisir la pureté contre le rétablissement. Ce qui ne l'est pas, c'est de ne pas la rapatrier dans le code ensuite. Une pratique qui tient : toute modification manuelle ouvre un ticket dans la journée, et la tâche planifiée ci-dessous sert de filet.
# run every night, per state
tofu plan -detailed-exitcode -lock=false -input=false
# 0: no difference 1: error 2: difference between code and reality
case $? in
2) notify "difference detected on ${STATE_NAME}" ;;
1) notify "plan failed on ${STATE_NAME}" ;;
esacLe code de sortie 2 donne le signal, sans dire d'où vient l'écart : une dérive, ou un changement mergé que personne n'a encore appliqué. Les deux méritent l'alerte, et le second se reconnaît vite dans l'historique du repo. Le traitement compte davantage. Un écart détecté a un propriétaire (celui de l'état) et deux issues possibles. Soit on ré-applique le code, soit on modifie le code pour refléter la réalité. Un écart qu'on laisse « pour voir » pendant trois semaines devient du bruit, et un plan bruité redevient un plan que plus personne ne lit, comme au premier paragraphe.
L'apply automatique de correction de dérive est tentant, et dangereux. Si l'écart vient d'un geste d'incident encore nécessaire, comme une capacité augmentée à la main à trois heures du matin, la remise en conformité automatique relance la panne. Je détecte automatiquement, et je corrige après lecture humaine.
Le seuil où ce découpage se justifie
Ce découpage a un prix. Plus d'états, cela fait plus de pipelines, plus de droits à gérer, et un ordre de déploiement à connaître pour monter un environnement de zéro. Les contrats entre couches demandent la discipline d'une API. Les modules versionnés demandent une équipe qui les entretient, soit une part significative du temps de la plateforme.
En dessous de trois équipes, je ne vois rien dans ce découpage qui rembourse son prix. Un repo, un état par environnement, des modules locaux dans le même repo suffisent, le plan reste lisible et la coordination se fait à l'oral. Sur une infrastructure simple (quelques VPS, un fichier Compose, un proxy), l'état Terraform lui-même est discutable au-delà du DNS et des machines.
Pour savoir si le seuil est passé, je regarde quelques signaux :
- un
plande routine dépasse cinq minutes ou ne se lit plus en entier ; - des équipes attendent le verrou d'une autre plus d'une fois par semaine ;
- un
applya déjà proposé de modifier une ressource que son auteur ne connaissait pas ; - personne ne sait répondre à « qui valide un changement sur ce module ? ».
Ces signaux appellent des remèdes différents, et les compter ne dit pas lequel. Les deux premiers mesurent la taille de l'état, et couper selon la vitesse de changement les règle. Les deux derniers relèvent de la propriété, que le découpage seul ne crée pas. Quand ils apparaissent, je nomme les propriétaires avant de toucher aux états, faute de quoi la nouvelle coupe reproduit l'ancienne confusion. Un signal isolé dans un contexte qui l'explique (un plan lent sur un environnement de test peuplé de ressources oubliées, par exemple) appelle d'abord un ménage dans cet état.
Sources et historique
Vérification technique : 10 octobre 2026. Une erreur ? Voici comment elle est corrigée.
À lire ensuite
Toute la rubrique InfraOrganisation
Les équipes « deux pizzas » sans la plateforme qui va avec
Pourquoi la petite équipe autonome n'est pas une règle de taille mais la conséquence d'une plateforme en libre-service, et comment décider ce que vous pouvez en reprendre quand vous n'avez pas cette plateforme.
7 minLecture libre
Infra
Kubernetes : en avez-vous besoin ?
Le coût total de Kubernetes face à un PaaS ou à des VM pour une équipe de 5 à 30 ingénieurs, compté en temps de personnes, et les besoins précis qui justifient de le payer.
7 minLecture libre
Infra
Un changement de configuration est un déploiement
Pourquoi une poussée de configuration casse plus large et plus vite qu'une livraison de code, et quelles protections lui appliquer selon sa portée et sa réversibilité.
7 minLecture libre