Provisionnement déclaratifDans le dialogue d’impression, choisissez « Enregistrer au format PDF » comme destination.
Retour

DevOps · C4 Infrastructure as Code · Chapitre 1 · 6 h

Provisionnement déclaratif

Infrastructure immuable ; Terraform : ressources, état, plan et application ; idempotence, modules et réutilisation, gestion de l'état partagé en équipe.

Il existe, dans presque toutes les organisations, un serveur que personne n'ose redémarrer. Il a été installé il y a quatre ans, configuré à la main, ajusté une dizaine de fois par des personnes qui ont depuis changé d'équipe. Il fonctionne. Nul ne sait exactement pourquoi, ni comment le reconstruire si le matériel lâche.

On l'appelle un serveur flocon : unique, et impossible à reproduire. Il est le symptôme d'un problème plus général, la dérive de configuration — l'écart qui se creuse entre ce qu'on croit avoir déployé et ce qui tourne réellement.

Ce chapitre supprime cette catégorie de problème en changeant l'objet qu'on manipule : on ne configure plus des machines, on décrit une infrastructure dans des fichiers versionnés.

Immuable plutôt que réparé

Deux façons de faire évoluer une machine.

La mutation : on se connecte, on met à jour un paquet, on modifie un fichier de configuration. Chaque intervention éloigne un peu plus la machine de son état d'origine, et deux serveurs installés le même jour finissent différents.

L'immuabilité : on ne modifie jamais une machine en service. On construit une nouvelle image, on déploie de nouvelles instances, on retire les anciennes. C'est exactement le conteneur du chapitre 3, à l'échelle de l'infrastructure — et c'est le même déploiement progressif qu'au chapitre 6.

La formule consacrée : on traite les serveurs comme du bétail, pas comme des animaux de compagnie. Un animal de compagnie a un nom, on le soigne quand il est malade. Un troupeau se compte : un animal malade est remplacé.

Trois bénéfices en découlent. Plus de dérive : l'état d'une machine est entièrement déterminé par l'image dont elle vient. Retour arrière trivial : redéployer l'image précédente. Et recette fidèle : le même code produit un environnement identique, ce qui est la condition posée au chapitre 6.

Déclaratif plutôt qu'impératif

C'est le second renversement, et il structure aussi le chapitre 9.

Un script impératif décrit les étapes : créer le réseau, puis la machine, puis la règle de pare-feu. Il suppose un état de départ connu, et devient faux dès que quelque chose existe déjà — il faut alors ajouter partout des « si cela n'existe pas ».

Une description déclarative énonce l'état voulu : il doit exister un réseau, deux machines et une règle. L'outil compare cette description au monde réel et calcule lui-même ce qu'il faut faire — créer, modifier, détruire, ou ne rien faire.

resource "aws_instance" "serveur_web" {  ami           = "ami-0c55b159"  instance_type = "t3.micro"  tags = { Name = "web-production" }} resource "aws_security_group" "web" {  ingress {    from_port   = 443    to_port     = 443    protocol    = "tcp"    cidr_blocks = ["0.0.0.0/0"]  }}

Ce fichier ne dit pas comment créer une machine : il dit qu'il doit en exister une, de ce type, avec ce nom. Le comment appartient à l'outil.

Plan, puis application

Terraform travaille en deux temps, et c'est ce qui le rend utilisable en équipe.

terraform plan     compare la description au monde réel                   et affiche CE QUI VA CHANGER, sans rien changer terraform apply    exécute ce plan

Le plan est l'artefact le plus important du chapitre. Il se lit avant d'agir, il se relit en demande de fusion, et il est le seul moyen de répondre à la question qui compte : « qu'est-ce que cette modification va détruire ? »

Terraform will perform the following actions:   + aws_instance.serveur_web_2      will be created  ~ aws_security_group.web          will be updated in-place  - aws_instance.ancien_serveur     will be destroyed-/+ aws_db_instance.principale      must be REPLACED

La dernière ligne est celle qu'il faut savoir repérer. Certaines modifications ne peuvent pas être appliquées sur place : changer la zone d'une machine, ou un paramètre immuable d'une base, implique de détruire puis recréer. Sur une base de production, c'est la perte des données — et le plan est le seul endroit où l'on peut s'en apercevoir avant.

D'où la règle de conduite : on lit le plan, ligne à ligne, et l'on cherche les - et les -/+. Un apply lancé sans avoir lu son plan est l'équivalent d'un rm -rf sans regarder le chemin.

L'état, et pourquoi c'est le point dur

Pour calculer un plan, Terraform a besoin de savoir ce qu'il a déjà créé. C'est le rôle du fichier d'état : la correspondance entre les ressources décrites dans le code et les objets réels chez le fournisseur.

   description (code)  ←──  état  ──→  monde réel

Trois écarts sont donc possibles, et ils ne se traitent pas de la même façon.

Le code a changé : c'est le cas normal, le plan propose les modifications.

Le monde réel a changé sans passer par Terraform — quelqu'un a modifié une règle depuis la console web. C'est la dérive, et le plan la révèle en proposant de revenir à ce qui est décrit. La bonne réaction n'est pas d'accepter la dérive mais de comprendre pourquoi quelqu'un a eu besoin de contourner le code.

L'état est perdu ou faux : Terraform ne sait plus ce qui lui appartient. Il proposera de recréer des ressources qui existent déjà, ce qui échouera — ou pire, réussira en double. C'est le scénario catastrophe du chapitre.

D'où trois règles, non négociables. L'état ne va jamais dans Git : il contient des valeurs sensibles en clair — mots de passe générés, clés — et deux personnes qui le versionnent produisent des conflits inextricables. Il vit dans un stockage distant partagé. Et ce stockage doit fournir un verrou : sans lui, deux apply simultanés écrivent chacun leur version de l'état, et la dernière écriture efface l'autre. On se retrouve avec des ressources créées que plus rien ne référence — invisibles, facturées, et impossibles à détruire proprement.

Idempotence et modules

L'idempotence est la propriété qui rend tout cela sûr : appliquer deux fois produit le même résultat qu'appliquer une fois. Puisque l'outil compare l'état voulu au réel, un second apply sans modification ne fait rien — No changes. C'est ce qui permet de relancer sans crainte après une interruption, et c'est ce que le chapitre 8 retrouvera avec Ansible.

Les modules évitent la duplication : un module « environnement applicatif » paramétré par le nom et la taille, instancié trois fois pour le développement, la recette et la production. C'est la même motivation que la fonction en programmation — et le même piège, un module trop paramétré devenant plus difficile à comprendre que le code qu'il factorise.

Quiz · 1 question

Un plan Terraform affiche « -/+ aws_db_instance.principale must be replaced ». Que signifie cette ligne ?

  • La base sera mise à jour sur place, avec un bref redémarragemise à jour sur place
  • La base sera DÉTRUITE puis RECRÉÉE, parce que la modification demandée porte sur un paramètre immuable — sur une base de production, cela signifie la perte des donnéesdestruction puis recréation
  • Terraform hésite entre deux actions et demandera confirmation pendant l'applicationdemande de confirmation

Réponse : Le symbole -/+ signale un remplacement : certains attributs ne peuvent pas être modifiés sur une ressource existante — la zone de disponibilité d'une machine, le moteur d'une base, parfois un simple nom — et l'outil n'a d'autre choix que de détruire puis recréer. Sur une base de données de production, cela veut dire perdre les données, et l'application se fera sans autre avertissement que cette ligne : il n'y a PAS de confirmation interactive par ressource. C'est précisément pourquoi le plan existe et pourquoi on le lit ligne à ligne, en cherchant les - et les -/+. Un apply lancé sans avoir lu son plan est l'équivalent d'un rm -rf sans regarder le chemin. Les parades : lire le plan en demande de fusion, et poser un verrou de protection contre la destruction sur les ressources critiques.

Quiz · 1 question

Deux ingénieurs lancent terraform apply en même temps sur un état stocké dans un simple fichier partagé, sans verrou. Que se passe-t-il ?

  • Le second attend automatiquement la fin du premier : Terraform sérialise les applicationssérialisation automatique
  • Chacun part du même état initial et écrit le sien à la fin : la dernière écriture écrase l'autre, et des ressources créées par le premier ne sont plus référencées — invisibles, facturées, impossibles à détruire proprementétat écrasé
  • Les deux applications échouent, ce qui est sans conséquencedouble échec

Réponse : Rien ne sérialise quoi que ce soit : la sérialisation vient du VERROU, et c'est le stockage distant qui le fournit, pas Terraform seul. Sans lui, les deux processus lisent le même état de départ, calculent chacun leur plan, créent chacun leurs ressources, puis écrivent chacun leur version finale de l'état. La seconde écriture écrase la première, et tout ce que le premier avait créé disparaît de l'état — mais pas du monde réel. On obtient des ressources orphelines : elles existent, elles sont facturées, aucun code ne les décrit, et un terraform destroy ne les touchera pas. Les retrouver demande de comparer à la main l'inventaire du fournisseur et l'état. D'où les trois règles : l'état ne va jamais dans Git (il contient des secrets en clair et produit des conflits inextricables), il vit dans un stockage distant partagé, et ce stockage fournit un verrou.

À vous

L'exercice construit un Terraform miniature, et c'est le seul moyen de voir ce que l'outil fait réellement.

Vous écrirez le calcul du plan : comparer la description, l'état et le monde réel, et en déduire les créations, modifications, remplacements et destructions. Le jeu de données contient un attribut immuable, pour que le -/+ apparaisse et qu'il faille le repérer.

Ensuite l'idempotence : appliquer, puis réappliquer, et vérifier qu'il ne se passe plus rien. Puis la dérive : quelqu'un modifie le monde réel sans passer par le code, et vous verrez le plan proposer de revenir en arrière.

La dernière partie simule deux applications concurrentes, avec et sans verrou. Vous compterez les ressources orphelines produites dans le premier cas.

Exercice de code

Calculez un plan avec remplacements et destructions, vérifiez l'idempotence, puis provoquez une dérive et une course.

Point de départ

// ── Trois sources de vérité ───────────────────────────────────────────────
const IMMUABLES = ["zone", "moteur"];   // modifier cela impose un remplacement

// Ce que le code décrit.
const CODE = {
  "instance.web":     { type: "t3.small", zone: "eu-west-1a" },
  "instance.web2":    { type: "t3.micro", zone: "eu-west-1b" },
  "securite.web":     { port: 443 },
  "base.principale":  { taille: 100, moteur: "postgres16" },
};

// Ce que l'outil croit avoir créé.
const ETAT = {
  "instance.web":     { type: "t3.micro", zone: "eu-west-1a" },
  "securite.web":     { port: 443 },
  "base.principale":  { taille: 100, moteur: "postgres15" },
  "instance.ancienne": { type: "t3.micro", zone: "eu-west-1a" },
};

// Ce qui existe réellement chez le fournisseur.
const REEL = { ...ETAT };

function plan(code, etat) {
  const actions = [];
  for (const [nom, voulu] of Object.entries(code)) {
    const connu = etat[nom];
    if (!connu) { actions.push({ signe: "+", nom, quoi: "sera créée" }); continue; }
    const differents = Object.keys(voulu).filter((k) => voulu[k] !== connu[k]);
    if (differents.length === 0) continue;
    // ← à écrire : si un attribut IMMUABLE change, c'est un remplacement
    actions.push({ signe: "~", nom, quoi: "modifiée sur place : " + differents.join(", ") });
  }
  // ← à écrire : ce qui est dans l'état mais plus dans le code sera détruit
  return actions;
}

function afficher(actions) {
  if (!actions.length) { console.log("      No changes. Infrastructure is up-to-date."); return; }
  for (const a of actions) {
    console.log("      " + a.signe.padStart(3) + " " + a.nom.padEnd(20) + a.quoi);
  }
}

// ── À VOUS ────────────────────────────────────────────────────────────────
// 1. Complétez plan() : remplacement sur attribut immuable, et destruction.
//    Repérez la ligne -/+ : que se passerait-il si l'on appliquait ?
// 2. Appliquez, puis replanifiez : vérifiez l'idempotence.
// 3. Simulez une DÉRIVE — quelqu'un modifie le monde réel depuis la console.
// 4. Simulez deux applications concurrentes sans verrou, puis avec.

afficher(plan(CODE, ETAT));

Solution

const IMMUABLES = ["zone", "moteur"];

const CODE = {
  "instance.web":    { type: "t3.small", zone: "eu-west-1a" },
  "instance.web2":   { type: "t3.micro", zone: "eu-west-1b" },
  "securite.web":    { port: 443 },
  "base.principale": { taille: 100, moteur: "postgres16" },
};
const ETAT_INITIAL = {
  "instance.web":      { type: "t3.micro", zone: "eu-west-1a" },
  "securite.web":      { port: 443 },
  "base.principale":   { taille: 100, moteur: "postgres15" },
  "instance.ancienne": { type: "t3.micro", zone: "eu-west-1a" },
};

function plan(code, etat) {
  const actions = [];
  for (const [nom, voulu] of Object.entries(code)) {
    const connu = etat[nom];
    if (!connu) { actions.push({ signe: "+", nom, quoi: "sera créée" }); continue; }
    const differents = Object.keys(voulu).filter((k) => voulu[k] !== connu[k]);
    if (differents.length === 0) continue;
    const immuablesTouches = differents.filter((k) => IMMUABLES.includes(k));
    if (immuablesTouches.length) {
      // Un attribut immuable ne se modifie pas : il faut détruire et recréer.
      actions.push({ signe: "-/+", nom,
        quoi: "DOIT ÊTRE REMPLACÉE (attribut immuable : " + immuablesTouches.join(", ") + ")" });
    } else {
      actions.push({ signe: "~", nom, quoi: "modifiée sur place : " + differents.join(", ") });
    }
  }
  for (const nom of Object.keys(etat)) {
    if (!code[nom]) actions.push({ signe: "-", nom, quoi: "sera détruite" });
  }
  return actions;
}

function afficher(actions) {
  if (!actions.length) { console.log("      No changes. Infrastructure is up-to-date."); return; }
  for (const a of actions) console.log("      " + a.signe.padStart(3) + " " + a.nom.padEnd(20) + a.quoi);
}

function appliquer(code, etat) {
  const nouveau = {};
  for (const [nom, voulu] of Object.entries(code)) nouveau[nom] = { ...voulu };
  return nouveau;   // après application, l'état reflète le code
}

console.log("— 1. le plan —");
afficher(plan(CODE, ETAT_INITIAL));
console.log("      ↑ La ligne -/+ est celle à repérer : appliquer ce plan DÉTRUIRAIT");
console.log("        la base de production et la recréerait vide. Il n'y a aucune");
console.log("        confirmation par ressource — la lecture du plan est la seule");
console.log("        protection, avec le verrou de prévention de destruction.");

console.log("");
console.log("— 2. idempotence —");
let etat = appliquer(CODE, ETAT_INITIAL);
console.log("   après application, second plan :");
afficher(plan(CODE, etat));
console.log("      Appliquer deux fois produit le même résultat qu'appliquer une");
console.log("      fois : c'est ce qui permet de relancer sans crainte après une");
console.log("      interruption.");

console.log("");
console.log("— 3. dérive : quelqu'un a modifié le monde réel à la main —");
const reel = { ...etat, "securite.web": { port: 22 } };   // un port SSH ouvert en console
console.log("   plan calculé contre le RÉEL observé :");
afficher(plan(CODE, reel));
console.log("      Le plan propose de revenir à ce que le code décrit. La bonne");
console.log("      réaction n'est pas d'accepter la dérive en modifiant le code,");
console.log("      mais de comprendre POURQUOI quelqu'un a eu besoin de contourner.");

console.log("");
console.log("— 4. deux applications concurrentes —");
function concurrent(avecVerrou) {
  const depart = { "securite.web": { port: 443 } };
  const codeA = { ...depart, "instance.a": { type: "t3.micro", zone: "eu-west-1a" } };
  const codeB = { ...depart, "instance.b": { type: "t3.micro", zone: "eu-west-1b" } };
  const reelFinal = { ...depart, "instance.a": codeA["instance.a"], "instance.b": codeB["instance.b"] };

  let etatFinal;
  if (avecVerrou) {
    // Sérialisé : B part de l'état laissé par A.
    const apresA = appliquer(codeA, depart);
    etatFinal = appliquer({ ...codeB, ...apresA }, apresA);
  } else {
    // Les deux partent du MÊME état ; la dernière écriture gagne.
    appliquer(codeA, depart);
    etatFinal = appliquer(codeB, depart);
  }
  const orphelines = Object.keys(reelFinal).filter((r) => !etatFinal[r]);
  return { etatFinal: Object.keys(etatFinal), orphelines };
}
for (const avec of [false, true]) {
  const r = concurrent(avec);
  console.log("   " + (avec ? "avec verrou  " : "sans verrou  ") +
    "état final : [" + r.etatFinal.join(", ") + "]");
  console.log("      ressources orphelines : " +
    (r.orphelines.length ? r.orphelines.join(", ") + "  ← existent, facturées, décrites par aucun code"
                         : "aucune"));
}
console.log("   Sans verrou, ce que le premier a créé disparaît de l'état mais pas");
console.log("   du monde réel. Un destroy ne les touchera pas : il faut comparer à");
console.log("   la main l'inventaire du fournisseur et l'état pour les retrouver.");

En travaux pratiques

Travaux pratiques 7 · 3 h

Décrire l'infrastructure, sans un centime d'hébergement

Écrire, planifier et appliquer une infrastructure réelle avec Terraform — en pilotant Docker en local, ce qui donne un vrai état, une vraie dérive et un vrai verrou.

Avant de commencer

  • Terraform ou OpenTofu installé
  • Docker en fonctionnement
  • Les images du TP 3 disponibles localement

Énoncé

  1. Décrire la pileAvec le fournisseur Docker, décrivez un réseau, un volume, un conteneur de base et un conteneur applicatif. Lancez un plan et LISEZ-LE avant d'appliquer. Indice : Le fournisseur kreuzwerker/docker expose docker_network, docker_volume, docker_image et docker_container.
  2. Éprouver l'idempotenceAppliquez, puis replanifiez sans rien modifier. Notez ce qu'affiche le plan, et expliquez pourquoi.
  3. Provoquer une dériveArrêtez un conteneur à la main avec Docker, sans passer par Terraform. Replanifiez. Que propose l'outil, et pourquoi ?
  4. Repérer un remplacementModifiez un attribut modifiable sur place, puis un attribut immuable. Comparez les deux plans et repérez le symbole qui distingue une modification d'un remplacement. Indice : Changer le nom d'un conteneur ne se fait pas sur place.
  5. Factoriser en moduleTransformez la description en module paramétré par le nom de l'environnement et le nombre d'exemplaires, puis instanciez-le deux fois : recette et production.
  6. Casser l'état, et le réparerFaites une copie de sauvegarde du fichier d'état, puis supprimez-le. Replanifiez. Que propose Terraform ? Restaurez ensuite la sauvegarde.
  7. La courseÀ deux, sur le même état partagé sans verrou, lancez une application simultanément. Comparez ensuite le contenu de l'état et la liste réelle des conteneurs.

C'est réussi quand

  • Un second plan sans modification affiche « no changes »
  • Vous savez repérer un remplacement dans un plan et dire ce qu'il détruirait
  • Le module s'instancie deux fois sans duplication de code
  • Vous pouvez nommer au moins une ressource orpheline produite par la course

Correction

La descriptionmain.tf
terraform {
required_providers {
  docker = { source = "kreuzwerker/docker", version = "~> 3.0" }
}
}

resource "docker_network" "interne" {
name = "tickets-interne"
}

resource "docker_volume" "donnees" {
name = "tickets-donnees"
}

resource "docker_container" "base" {
name  = "tickets-base"
image = "postgres:16-alpine"
env   = ["POSTGRES_USER=app", "POSTGRES_PASSWORD=secret"]
networks_advanced { name = docker_network.interne.name }
volumes {
  volume_name    = docker_volume.donnees.name
  container_path = "/var/lib/postgresql/data"
}
}

Aucune de ces lignes ne dit COMMENT créer quoi que ce soit : elles disent ce qui doit exister. L'outil compare cette description au monde réel et en déduit les actions — c'est tout le déclaratif.

Les quatre plans, et ce qu'ils apprennent
# après application, sans modification
No changes. Your infrastructure matches the configuration.

# après avoir arrêté un conteneur à la main
~ docker_container.base will be updated in-place
  ~ must_run: false -> true

# attribut modifiable
~ docker_container.appli will be updated in-place

# attribut immuable (le nom)
-/+ docker_container.appli must be replaced

Le second plan est la DÉRIVE : le monde réel a changé sans passer par le code, et l'outil propose de revenir à ce qui est décrit. La bonne réaction n'est pas de modifier le code pour entériner la dérive, mais de comprendre pourquoi quelqu'un a eu besoin de contourner.

Le modulemodules/pile/variables.tf
variable "environnement" { type = string }
variable "exemplaires"   { type = number, default = 1 }

# racine
module "recette" {
source        = "./modules/pile"
environnement = "recette"
}

module "production" {
source        = "./modules/pile"
environnement = "production"
exemplaires   = 3
}

Le même code décrit les deux environnements, et c'est ce qui rend la recette FIDÈLE à la production — condition posée au chapitre 6. Attention toutefois au module trop paramétré : au-delà d'une dizaine de variables, il devient plus difficile à comprendre que le code qu'il factorise.

L'état perdu
# après suppression de terraform.tfstate
Plan: 4 to add, 0 to change, 0 to destroy.

Error: container name "tickets-base" is already in use

Terraform ne sait plus ce qui lui appartient : il propose de tout recréer, et échoue parce que les objets existent déjà. C'est le scénario catastrophe, et il explique les trois règles — l'état ne va jamais dans Git (il contient des secrets en clair), il vit dans un stockage distant partagé, et ce stockage fournit un verrou.

Le résultat de la course
# A crée appli-1, B crée appli-2, les deux partis du même état
docker ps            # appli-1 ET appli-2 existent
terraform state list # seul appli-2 figure  <-- appli-1 est ORPHELIN

terraform destroy    # ne détruit pas appli-1 : il l'ignore

La dernière écriture de l'état a écrasé l'autre. Le conteneur créé par le premier existe, tourne, consomme — et aucun code ne le décrit. Sur un fournisseur de nuage, il serait facturé, et le retrouver demanderait de comparer à la main l'inventaire et l'état.

Ce que la suite en fait

Le chapitre 8 traite ce que celui-ci laisse de côté. Terraform crée une machine ; il ne dit pas ce qui tourne dessus. Ansible installe, configure, déploie — et l'on verra que la frontière entre les deux est moins nette qu'il n'y paraît, l'infrastructure immuable réduisant beaucoup le besoin de configuration.

Le chapitre 9 poussera le déclaratif jusqu'au bout : Kubernetes est une boucle de réconciliation permanente là où Terraform en exécute une à la demande. C'est la même idée, avec un contrôleur qui ne s'arrête jamais.

À retenir

Flashcards · 5 cartes

Qu'est-ce qu'un serveur flocon, et que propose l'infrastructure immuable ?
Une machine configurée à la main pendant des années, que plus personne ne sait reconstruire — symptôme de la DÉRIVE DE CONFIGURATION, l'écart entre ce qu'on croit avoir déployé et ce qui tourne. L'immuabilité y répond : on ne modifie jamais une machine en service, on construit une NOUVELLE image, on déploie, on retire l'ancienne. Formule : des serveurs traités comme du bétail, pas comme des animaux de compagnie. Bénéfices : plus de dérive, retour arrière trivial, recette fidèle.
Opposez impératif et déclaratif.
L'IMPÉRATIF décrit LES ÉTAPES — créer le réseau, puis la machine — ce qui suppose un état de départ connu et devient faux dès que quelque chose existe déjà. Le DÉCLARATIF énonce L'ÉTAT VOULU — il doit exister un réseau et deux machines — et l'outil compare au monde réel puis calcule lui-même quoi créer, modifier, détruire ou laisser. Le « comment » appartient à l'outil, et c'est aussi le modèle de Kubernetes.
Pourquoi le plan est-il l'artefact le plus important, et que faut-il y chercher ?
Parce qu'il montre CE QUI VA CHANGER sans rien changer : il se lit avant d'agir, se relit en demande de fusion, et répond à la seule question qui compte — qu'est-ce que cette modification va détruire ? On y cherche les « - » (destruction) et surtout les « -/+ » (REMPLACEMENT) : certains attributs ne se modifient pas sur place, et sur une base de production cela signifie perdre les données. Un apply lancé sans avoir lu son plan est un rm -rf sans regarder le chemin.
À quoi sert le fichier d'état, et quels écarts révèle-t-il ?
Il fait la correspondance entre les ressources DÉCRITES dans le code et les objets RÉELS chez le fournisseur — sans lui, aucun plan n'est calculable. Trois écarts : le CODE a changé (cas normal) ; le MONDE RÉEL a changé sans passer par l'outil, c'est la DÉRIVE, et le plan propose de revenir au code — la bonne réaction est de comprendre pourquoi quelqu'un a contourné ; l'ÉTAT est perdu ou faux, et l'outil ne sait plus ce qui lui appartient.
Quelles sont les trois règles sur l'état, et que risque-t-on sans verrou ?
1) IL NE VA JAMAIS DANS GIT : il contient des valeurs sensibles en clair et produit des conflits inextricables. 2) Il vit dans un STOCKAGE DISTANT PARTAGÉ. 3) Ce stockage fournit un VERROU. Sans verrou, deux apply simultanés partent du même état, créent chacun leurs ressources, et la dernière écriture écrase l'autre : les ressources du premier deviennent ORPHELINES — existantes, facturées, décrites par aucun code, et qu'un destroy ne touchera pas.