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 planLe 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 REPLACEDLa 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éelTrois é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émarrage — mise à 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ées — destruction puis recréation
- Terraform hésite entre deux actions et demandera confirmation pendant l'application — demande 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 applications — sé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équence — double é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é
- Décrire la pile — Avec 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.
- Éprouver l'idempotence — Appliquez, puis replanifiez sans rien modifier. Notez ce qu'affiche le plan, et expliquez pourquoi.
- Provoquer une dérive — Arrêtez un conteneur à la main avec Docker, sans passer par Terraform. Replanifiez. Que propose l'outil, et pourquoi ?
- Repérer un remplacement — Modifiez 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.
- Factoriser en module — Transformez 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.
- Casser l'état, et le réparer — Faites une copie de sauvegarde du fichier d'état, puis supprimez-le. Replanifiez. Que propose Terraform ? Restaurez ensuite la sauvegarde.
- 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
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.
# 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.
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.
# 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.
# 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.