DevOps · C4 Infrastructure as Code · Chapitre 2 · 4 h
Configuration
Ansible : inventaire, playbooks, rôles ; gestion de configuration contre provisionnement ; secrets et coffres-forts.
Le chapitre 7 s'arrête sur une machine vide. Elle existe, elle a une adresse, un disque et un groupe de sécurité — et rien ne tourne dessus.
Reste à installer les paquets, déposer les fichiers de configuration, créer les comptes de service, démarrer les processus. C'est la gestion de configuration, et elle répond à une question que le provisionnement ne pose pas : non pas « quelles machines existent ? » mais « qu'y a-t-il dessus ? ».
Deux outils, une frontière mobile
| Provisionnement (Terraform) | Configuration (Ansible) | |
|---|---|---|
| Objet | machines, réseaux, bases, DNS | paquets, fichiers, services, comptes |
| Question | qu'est-ce qui existe ? | qu'y a-t-il dessus ? |
| Modèle | état désiré, comparé à un état enregistré | état désiré, vérifié à chaque exécution |
| Fréquence | à chaque changement d'infrastructure | à chaque changement de configuration |
La frontière est moins nette qu'elle n'en a l'air, et elle s'est déplacée ces dix dernières années.
Dans le modèle historique, on créait des serveurs durables et on les configurait à répétition, pour rattraper la dérive du chapitre 7. Dans le modèle immuable, la configuration est cuite dans l'image au moment de la construction, et la machine en service n'est plus jamais touchée. Le rôle d'Ansible se déplace alors : il sert à construire l'image plutôt qu'à entretenir des serveurs.
Il reste largement employé, pour trois usages qui ne disparaissent pas. Le matériel physique et les environnements sur site, qu'on ne remplace pas d'un clic. Les tâches ponctuelles d'exploitation — appliquer un correctif de sécurité sur trente machines, faire tourner une migration. Et la configuration d'appareils qui ne sont pas des serveurs : équipements réseau, systèmes embarqués.
Ansible en quatre notions
Ansible se distingue par une absence : il n'y a rien à installer sur les machines cibles. Il se connecte en SSH, y dépose un module, l'exécute, récupère le résultat, et repart. Pas d'agent, pas de service à maintenir, pas de port supplémentaire à ouvrir — c'est la raison principale de son adoption.
L'inventaire liste les machines et les regroupe.
web: hosts: web1.example.net: web2.example.net:base: hosts: db1.example.net:Le playbook associe des groupes à des tâches.
- hosts: web become: true tasks: - name: installer nginx ansible.builtin.package: name: nginx state: present - name: déposer la configuration ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf mode: "0644" notify: recharger nginx - name: activer le service ansible.builtin.service: name: nginx state: started enabled: true handlers: - name: recharger nginx ansible.builtin.service: name: nginx state: reloadedLes tâches déclarent un état, pas une commande : state: present signifie « il doit être
installé », et non « installe-le ». La différence est celle du chapitre 7, et elle porte tout ce
qui suit.
Les rôles regroupent tâches, fichiers, gabarits et variables en une unité réutilisable —
l'équivalent du module Terraform. Un rôle nginx s'applique à plusieurs projets ; ses variables
en font l'ajustement.
À noter enfin les gestionnaires (handlers) : une tâche notifie un gestionnaire, qui ne s'exécute qu'une fois, à la fin, et seulement si quelque chose a réellement changé. C'est ce qui évite de recharger nginx trois fois — ou de le recharger alors que rien n'a bougé.
L'idempotence, en pratique
C'est le point du chapitre, et il se mesure.
Une exécution Ansible se termine par un décompte : ok pour les tâches qui n'ont rien eu à
faire, changed pour celles qui ont modifié quelque chose.
PLAY RECAPweb1 : ok=12 changed=3 unreachable=0 failed=0La deuxième exécution du même playbook doit afficher changed=0. C'est le test
d'idempotence, et il devrait faire partie du pipeline : un playbook qui rapporte des
modifications alors que rien n'a bougé ne permet plus de distinguer « la configuration a
dérivé » de « ce playbook ment ».
Or il existe une porte de sortie qui casse cette propriété : les modules command et shell,
qui exécutent une commande arbitraire. Ansible ne peut pas savoir si elle a changé quelque
chose, alors il déclare changed à chaque fois.
- name: télécharger l'outil # NON idempotent ansible.builtin.shell: curl -o /opt/outil https://example.net/outilTrois façons de rétablir la propriété, par ordre de préférence. Employer un module dédié —
ici get_url, qui ne retélécharge pas si le fichier est déjà là. À défaut, déclarer une
condition de création : creates: /opt/outil fait sauter la tâche si le fichier existe. En
dernier recours, dire soi-même quand il y a eu changement, avec changed_when sur la sortie
de la commande.
La règle d'usage : shell est l'aveu qu'aucun module ne convient. C'est parfois vrai, et cela
doit rester rare — chaque occurrence est une tâche dont on ne sait plus si elle est sûre à
relancer.
Les secrets
Un playbook a besoin de mots de passe de base, de clés d'interface, de certificats. Ils ne peuvent aller ni dans le dépôt, ni dans l'inventaire, ni dans les variables ordinaires.
La règle est simple à énoncer : un secret ne va jamais dans Git en clair. Et elle est plus
difficile à tenir qu'il n'y paraît, parce que Git n'oublie pas : un secret commité puis
retiré au commit suivant reste dans l'historique, accessible à quiconque clone le dépôt. Le
retirer vraiment demande de réécrire l'historique — le rebase du chapitre 2, avec toutes ses
conséquences sur les collègues — et, surtout, le secret doit être considéré comme compromis
et donc changé.
Trois mécanismes, du plus simple au plus solide.
Les fichiers chiffrés dans le dépôt : ansible-vault chiffre un fichier de variables avec un
mot de passe. Le fichier est versionné, illisible sans la clé, et la clé est fournie à
l'exécution. C'est suffisant pour un projet d'étudiants, et cela déplace le problème sur une
seule clé à protéger.
Les variables d'environnement injectées par le pipeline : les secrets sont stockés dans le gestionnaire de l'outil d'intégration continue, jamais dans le dépôt.
Les coffres-forts — HashiCorp Vault, gestionnaires de secrets des fournisseurs. Ils ajoutent ce que les deux premiers n'ont pas : journalisation des accès, rotation automatique, et secrets à durée de vie limitée. Un identifiant de base valable une heure et renouvelé automatiquement vaut mieux qu'un mot de passe permanent bien chiffré.
Le chapitre 11 reprendra ce sujet du côté de la détection : comment s'assurer qu'aucun secret n'entre dans le dépôt, plutôt que de le retirer après coup.
Quiz · 1 question
Un playbook exécuté deux fois de suite affiche changed=7 la seconde fois. Que faut-il en conclure ?
- C'est normal : Ansible réapplique la configuration à chaque exécution — comportement normal
- Le playbook n'est pas idempotent — probablement des tâches shell ou command, qui déclarent changed à chaque fois. On ne peut plus distinguer « la configuration a dérivé » de « ce playbook ment » — playbook non idempotent
- Les machines cibles ont redémarré entre les deux exécutions — redémarrage
Réponse : L'idempotence est la propriété centrale de la gestion de configuration : les tâches déclarent un ÉTAT — « nginx doit être installé » — et un module bien écrit vérifie d'abord si l'état est déjà atteint, auquel cas il rapporte ok. Une seconde exécution sans modification doit donc afficher changed=0, et ce test devrait faire partie du pipeline. Sept modifications signalées alors que rien n'a bougé viennent presque toujours des modules command et shell : Ansible ne peut pas savoir si la commande a changé quelque chose, donc il déclare changed systématiquement. Le coût n'est pas cosmétique — le décompte est le seul indicateur de dérive dont on dispose, et un playbook bruyant le rend inutilisable. Trois remèdes : employer un module dédié (get_url plutôt que curl), déclarer creates: pour faire sauter la tâche, ou préciser changed_when.
Quiz · 1 question
Un mot de passe a été commité par erreur, puis retiré au commit suivant. Est-ce réglé ?
- Oui : le fichier actuel ne le contient plus, donc il n'est plus exposé — réglé
- Non : Git n'oublie pas, le secret reste dans l'historique et quiconque clone le dépôt peut le lire. Il faut le considérer comme COMPROMIS et le changer — réécrire l'historique ne suffit pas — compromis, à changer
- Non, mais il suffit de réécrire l'historique avec un rebase pour effacer toute trace — rebase suffisant
Réponse : Un dépôt conserve tous les états successifs : le commit qui contenait le secret existe toujours, et il suffit de remonter l'historique pour le lire. Cela vaut pour tout clone déjà fait, pour les copies chez les collègues, pour les caches des serveurs d'intégration continue et pour les miroirs. Réécrire l'historique — avec les conséquences du chapitre 2 sur les branches partagées — retire le secret du dépôt d'origine, mais ne le retire de nulle part ailleurs, et il a pu être aspiré entre-temps : les dépôts publics sont scrutés en permanence par des robots, et une clé publiée y est utilisée en quelques minutes. La seule action qui règle réellement le problème est la ROTATION : considérer le secret comme compromis et le remplacer. Le nettoyage de l'historique vient après, et le chapitre 11 traitera de l'empêcher d'entrer.
À vous
L'exercice construit un exécuteur de playbook et mesure l'idempotence.
Vous écrirez le moteur : pour chaque tâche, comparer l'état voulu à l'état de la machine
simulée, agir si nécessaire, et compter ok ou changed. Puis vous exécuterez deux fois le
même playbook — la seconde doit afficher changed=0.
Le jeu de tâches contient délibérément une tâche shell qui rapporte une modification à chaque
exécution. Vous la corrigerez de deux façons — module dédié, puis condition creates — et vous
vérifierez le décompte.
La dernière partie est un détecteur de secrets, appliqué à une série de modifications : clé d'interface, mot de passe de connexion, jeton dans une chaîne. Vous mesurerez aussi les faux positifs, parce qu'un détecteur qui crie trop est désactivé — c'est le bruit d'alertes du chapitre 10, par anticipation.
Exercice de code
Écrivez le moteur qui produit le décompte ok/changed, réparez une tâche non idempotente, puis détectez des secrets.
Point de départ
// ── Une machine simulée ───────────────────────────────────────────────────
function creerMachine() {
return { paquets: new Set(), fichiers: new Map(), services: new Map() };
}
// ── Les modules : chacun COMPARE avant d'agir ─────────────────────────────
const MODULES = {
package: (m, a) => {
if (m.paquets.has(a.name)) return false; // déjà là : rien à faire
m.paquets.add(a.name);
return true; // changed
},
copy: (m, a) => {
if (m.fichiers.get(a.dest) === a.contenu) return false;
m.fichiers.set(a.dest, a.contenu);
return true;
},
service: (m, a) => {
if (m.services.get(a.name) === a.state) return false;
m.services.set(a.name, a.state);
return true;
},
get_url: (m, a) => {
return false; // ← à écrire : ne retélécharge pas si le fichier est là
},
// La porte de sortie : Ansible ne peut pas savoir si la commande a agi.
shell: (m, a) => {
if (a.creates && m.fichiers.has(a.creates)) return false; // ← à honorer
return true; // sinon : TOUJOURS changed
},
};
function executer(playbook, machine, tracer) {
let ok = 0, changed = 0;
const gestionnaires = new Set();
for (const t of playbook) {
const aChange = MODULES[t.module](machine, t);
if (aChange) { changed++; if (t.notify) gestionnaires.add(t.notify); } else ok++;
if (tracer) console.log(" " + (aChange ? "changed" : "ok ") + " " + t.name);
}
// Un gestionnaire ne part QUE si quelque chose a changé, et une seule fois.
for (const g of gestionnaires) { if (tracer) console.log(" changed [handler] " + g); changed++; }
return { ok, changed };
}
const PLAYBOOK = [
{ module: "package", name: "nginx", /* … */ },
{ module: "copy", dest: "/etc/nginx/nginx.conf", contenu: "server{}", notify: "recharger nginx" },
{ module: "service", name: "nginx", state: "started" },
{ module: "shell", cmd: "curl -o /opt/outil https://exemple.net/outil" },
];
PLAYBOOK.forEach((t, i) => { t.name = t.name ?? t.dest ?? t.cmd; });
// ── Détection de secrets ──────────────────────────────────────────────────
const MODIFICATIONS = [
'const cle = "sk_live_4eC39HqLyjWDarjtT1zdp7dc";',
'DATABASE_URL=postgres://app:Tr0ub4dor@db:5432/app',
'const cle = process.env.STRIPE_KEY;',
'// exemple : token = "xxxxxxxxxxxx"',
'password_hash = "$2b$12$KIXQ2ei0Rl0Cq0Bl6Xa2yO"',
];
function detecter(ligne) {
return false; // ← à écrire, puis à mesurer sur les faux positifs
}
// ── À VOUS ────────────────────────────────────────────────────────────────
// 1. Exécutez le playbook DEUX fois. Que vaut changed la seconde fois ?
// 2. Écrivez get_url et honorez creates dans shell. Corrigez la tâche
// fautive de deux façons, et vérifiez changed=0.
// 3. Écrivez detecter(), puis comptez vrais et faux positifs.
const m = creerMachine();
console.log(" première exécution :");
console.log(" " + JSON.stringify(executer(PLAYBOOK, m, true)));
Solution
function creerMachine() {
return { paquets: new Set(), fichiers: new Map(), services: new Map() };
}
const MODULES = {
package: (m, a) => { if (m.paquets.has(a.name)) return false; m.paquets.add(a.name); return true; },
copy: (m, a) => {
if (m.fichiers.get(a.dest) === a.contenu) return false;
m.fichiers.set(a.dest, a.contenu); return true;
},
service: (m, a) => {
if (m.services.get(a.name) === a.state) return false;
m.services.set(a.name, a.state); return true;
},
// Un module dédié COMPARE avant d'agir : c'est toute la différence.
get_url: (m, a) => {
if (m.fichiers.has(a.dest)) return false;
m.fichiers.set(a.dest, "téléchargé"); return true;
},
shell: (m, a) => {
if (a.creates && m.fichiers.has(a.creates)) return false;
if (a.creates) m.fichiers.set(a.creates, "créé par shell");
return true;
},
};
function executer(playbook, machine, tracer) {
let ok = 0, changed = 0;
const gestionnaires = new Set();
for (const t of playbook) {
const aChange = MODULES[t.module](machine, t);
if (aChange) { changed++; if (t.notify) gestionnaires.add(t.notify); } else ok++;
if (tracer) console.log(" " + (aChange ? "changed" : "ok ") + " " + t.name);
}
for (const g of gestionnaires) { if (tracer) console.log(" changed [handler] " + g); changed++; }
return { ok, changed };
}
const nommer = (p) => p.map((t) => ({ ...t, name: t.name ?? t.dest ?? t.cmd }));
const FAUTIF = nommer([
{ module: "package", name: "nginx" },
{ module: "copy", dest: "/etc/nginx/nginx.conf", contenu: "server{}", notify: "recharger nginx" },
{ module: "service", name: "nginx", state: "started" },
{ module: "shell", cmd: "curl -o /opt/outil https://exemple.net/outil" },
]);
const CORRIGE_CREATES = nommer([
...FAUTIF.slice(0, 3),
{ module: "shell", cmd: "curl -o /opt/outil …", creates: "/opt/outil" },
]);
const CORRIGE_MODULE = nommer([
...FAUTIF.slice(0, 3),
{ module: "get_url", dest: "/opt/outil", name: "télécharger l'outil (get_url)" },
]);
console.log("— 1 et 2. idempotence —");
for (const [nom, pb] of [["playbook fautif (shell)", FAUTIF],
["corrigé par creates:", CORRIGE_CREATES],
["corrigé par get_url", CORRIGE_MODULE]]) {
const m = creerMachine();
const un = executer(pb, m, false);
const deux = executer(pb, m, false);
console.log(" " + nom.padEnd(26) +
"1re : ok=" + un.ok + " changed=" + un.changed +
" | 2e : ok=" + deux.ok + " changed=" + deux.changed +
(deux.changed === 0 ? " idempotent" : " NON IDEMPOTENT"));
}
console.log("");
console.log(" Détail de la seconde exécution du playbook corrigé :");
const m2 = creerMachine();
executer(CORRIGE_MODULE, m2, false);
executer(CORRIGE_MODULE, m2, true);
console.log(" Le gestionnaire n'est pas parti : rien n'avait changé. C'est ce qui");
console.log(" évite de recharger nginx à chaque exécution — et ce qui rend le");
console.log(" décompte utilisable comme indicateur de dérive.");
console.log("");
console.log("— 3. détection de secrets —");
const MODIFICATIONS = [
{ ligne: 'const cle = "sk_live_4eC39HqLyjWDarjtT1zdp7dc";', secret: true },
{ ligne: 'DATABASE_URL=postgres://app:Tr0ub4dor@db:5432/app', secret: true },
{ ligne: 'const cle = process.env.STRIPE_KEY;', secret: false },
{ ligne: '// exemple : token = "xxxxxxxxxxxx"', secret: false },
{ ligne: 'password_hash = "$2b$12$KIXQ2ei0Rl0Cq0Bl6Xa2yO"', secret: false },
{ ligne: 'AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCY', secret: true },
];
function detecter(ligne) {
// Préfixes connus de fournisseurs : très fiables, peu de faux positifs.
if (/\b(sk_live|ghp_|AKIA)[A-Za-z0-9_]{10,}/.test(ligne)) return true;
// Mot de passe dans une chaîne de connexion.
if (/:\/\/[^:\s]+:[^@\s]{6,}@/.test(ligne)) return true;
// Affectation d'une variable au nom sensible avec une valeur longue…
if (/(SECRET|PASSWORD|TOKEN|API_KEY)[A-Z_]*\s*[=:]\s*["']?[A-Za-z0-9\/+_-]{16,}/i.test(ligne)) {
// …sauf si c'est une référence à une variable d'environnement, un
// exemple manifeste, ou une empreinte déjà hachée.
if (/process\.env|os\.environ|\bxxxx|\$2[aby]\$/.test(ligne)) return false;
return true;
}
return false;
}
let vp = 0, fp = 0, fn = 0;
for (const m of MODIFICATIONS) {
const d = detecter(m.ligne);
if (d && m.secret) vp++;
if (d && !m.secret) fp++;
if (!d && m.secret) fn++;
console.log(" " + (d ? "ALERTE " : " ") + (m.secret ? "[vrai secret] " : "[inoffensif] ") +
m.ligne.slice(0, 52));
}
console.log(" vrais positifs " + vp + " | faux positifs " + fp + " | manqués " + fn);
console.log(" Les faux positifs comptent autant que les manqués : un détecteur");
console.log(" qui crie sur chaque hachage et chaque exemple est désactivé au bout");
console.log(" d'une semaine, et ne protège plus de rien. C'est déjà le bruit");
console.log(" d'alertes du chapitre 10.");
En travaux pratiques
Travaux pratiques 8 · 2 h
Configurer, et mesurer l'idempotence
Écrire un playbook qui configure une machine, prouver son idempotence par le décompte, puis réparer une tâche qui la casse.
Avant de commencer
- Ansible installé
- Deux conteneurs cibles avec Python, servant de machines de laboratoire
Énoncé
- Monter l'inventaire — Déclarez vos deux conteneurs comme cibles, en connexion locale par Docker plutôt que par SSH, et vérifiez la connectivité avec le module ping. Indice : ansible_connection: docker évite d'installer un serveur SSH dans les cibles.
- Écrire le playbook — Installez nginx, déposez une page à partir d'un gabarit contenant le nom de la machine, et démarrez le service. Ajoutez un gestionnaire qui recharge nginx quand la configuration change.
- Le test qui compte — Exécutez le playbook deux fois de suite. Relevez le décompte final des deux exécutions. Que doit valoir « changed » la seconde fois ?
- Casser l'idempotence — Ajoutez une tâche qui télécharge un fichier avec une commande shell. Réexécutez deux fois et observez le décompte. Expliquez pourquoi Ansible ne peut pas faire mieux.
- La réparer, de deux façons — Corrigez d'abord avec une condition de création, puis en remplaçant la commande par un module dédié. Vérifiez le décompte après chaque correction, et dites laquelle vous préférez.
- Chiffrer un secret — Placez le mot de passe de la base dans un fichier de variables chiffré, versionnez-le, et vérifiez qu'il est illisible sans la clé. Le playbook doit continuer de fonctionner.
- Vérifier le gestionnaire — Modifiez le gabarit et relancez : le gestionnaire doit partir. Relancez sans rien modifier : il ne doit pas partir. Expliquez l'intérêt de ce comportement.
C'est réussi quand
- La seconde exécution affiche changed=0
- Le fichier de variables est illisible dans le dépôt
- Le gestionnaire ne se déclenche que lorsque quelque chose a réellement changé
Correction
# inventaire.yml
web:
hosts:
labo1: { ansible_connection: docker }
labo2: { ansible_connection: docker }
# site.yml
- hosts: web
tasks:
- name: installer nginx
ansible.builtin.package: { name: nginx, state: present }
- name: déposer la page
ansible.builtin.template:
src: index.html.j2
dest: /usr/share/nginx/html/index.html
mode: "0644"
notify: recharger nginx
- name: démarrer le service
ansible.builtin.service: { name: nginx, state: started }
handlers:
- name: recharger nginx
ansible.builtin.service: { name: nginx, state: reloaded }Chaque tâche déclare un ÉTAT — « nginx doit être installé » — et non une commande. Un module bien écrit vérifie d'abord si l'état est atteint, et ne rapporte changed que s'il a agi.
PLAY RECAP (1re exécution) labo1 : ok=3 changed=3 labo2 : ok=3 changed=3 PLAY RECAP (2e exécution) labo1 : ok=3 changed=0 labo2 : ok=3 changed=0
Ce zéro est le test d'idempotence, et il devrait figurer dans le pipeline du TP 5. Sans lui, on ne peut plus distinguer « la configuration a dérivé » de « ce playbook ment ».
# NON idempotent : changed à chaque exécution
- ansible.builtin.shell: curl -o /opt/outil https://exemple.net/outil
# correction 1 : condition de création
- ansible.builtin.shell: curl -o /opt/outil https://exemple.net/outil
args: { creates: /opt/outil }
# correction 2, préférable : un module dédié
- ansible.builtin.get_url:
url: https://exemple.net/outil
dest: /opt/outil
mode: "0755"Ansible ne peut pas savoir si une commande arbitraire a changé quelque chose : il déclare donc changed systématiquement. La seconde correction est meilleure — get_url gère aussi les droits, la somme de contrôle et le remplacement partiel. shell est l'aveu qu'aucun module ne convient, et chaque occurrence est une tâche dont on ne sait plus si elle est sûre à relancer.
ansible-vault create group_vars/web/secrets.yml # on y écrit : mot_de_passe_base: secret git add group_vars/web/secrets.yml # versionné, illisible cat group_vars/web/secrets.yml # $ANSIBLE_VAULT;1.1;AES256 … ansible-playbook site.yml --ask-vault-pass
Le problème est déplacé sur une seule clé à protéger, ce qui est déjà beaucoup mieux qu'un mot de passe en clair. Un coffre-fort ajouterait ce qui manque encore : journalisation des accès, rotation, et surtout secrets à durée de vie limitée.
Il ne se déclenche QUE si une tâche a réellement changé quelque chose, et une seule fois même si trois tâches le notifient. Sans ce mécanisme, on rechargerait nginx à chaque exécution — inutile, et sur un service à fort trafic, ce n'est pas gratuit. C'est aussi ce qui rend le décompte lisible.
Ce que la suite en fait
Le bloc V change d'échelle. Ansible configure des machines qu'on connaît par leur nom ; Kubernetes reçoit une description et décide lui-même où placer les charges, en les déplaçant quand une machine tombe. Il n'y a plus de machine à configurer, seulement un état souhaité à déclarer — la boucle de réconciliation du chapitre suivant est ce chapitre poussé à sa limite.
Les secrets, eux, reviendront deux fois : comme objet Kubernetes au chapitre 9, et comme sujet de détection au chapitre 11.
À retenir
Flashcards · 5 cartes
- Qu'est-ce qui distingue provisionnement et gestion de configuration, et comment la frontière a-t-elle bougé ?
- Le PROVISIONNEMENT répond à « qu'est-ce qui EXISTE ? » — machines, réseaux, bases. La CONFIGURATION répond à « qu'y a-t-il DESSUS ? » — paquets, fichiers, services, comptes. La frontière s'est déplacée avec l'infrastructure immuable : la configuration est désormais CUITE DANS L'IMAGE à la construction, et Ansible sert à construire l'image plutôt qu'à entretenir des serveurs. Il reste employé pour le matériel physique, les tâches ponctuelles d'exploitation, et les appareils qui ne sont pas des serveurs.
- Quelles sont les quatre notions d'Ansible, et quelle absence le caractérise ?
- L'INVENTAIRE liste et regroupe les machines. Le PLAYBOOK associe des groupes à des tâches. Les TÂCHES déclarent un ÉTAT (state: present = « il doit être installé »), pas une commande. Les RÔLES regroupent tâches, fichiers et variables en unité réutilisable. L'ABSENCE : rien à installer sur les cibles — connexion SSH, dépôt d'un module, exécution, retour. Pas d'agent, pas de service à maintenir : c'est la raison principale de son adoption. À noter aussi les GESTIONNAIRES, exécutés une seule fois à la fin et seulement si quelque chose a changé.
- Comment mesure-t-on l'idempotence, et qu'est-ce qui la casse ?
- Par le décompte de fin : ok pour les tâches qui n'ont rien eu à faire, changed pour les autres. LA SECONDE EXÉCUTION DU MÊME PLAYBOOK DOIT AFFICHER changed=0, et ce test devrait être dans le pipeline. Ce qui la casse : les modules command et shell, dont Ansible ne peut pas savoir s'ils ont changé quelque chose — ils déclarent donc changed à chaque fois. Le coût n'est pas cosmétique : le décompte est le seul indicateur de dérive dont on dispose.
- Comment rétablir l'idempotence d'une tâche shell ?
- Par ordre de préférence : 1) EMPLOYER UN MODULE DÉDIÉ — get_url plutôt que curl, il ne retéléchargera pas si le fichier est là. 2) DÉCLARER creates: — la tâche saute si le fichier existe. 3) En dernier recours, PRÉCISER changed_when à partir de la sortie de la commande. Règle : shell est l'aveu qu'aucun module ne convient ; parfois vrai, cela doit rester rare — chaque occurrence est une tâche dont on ne sait plus si elle est sûre à relancer.
- Un secret a été commité puis retiré : que faut-il faire, et pourquoi ?
- LE CONSIDÉRER COMME COMPROMIS ET LE CHANGER. Git conserve tous les états : le commit fautif existe toujours, lisible par quiconque a cloné — collègues, caches d'intégration continue, miroirs. Réécrire l'historique le retire du dépôt d'origine et de nulle part ailleurs, et il a pu être aspiré entre-temps : les dépôts publics sont scrutés en permanence, une clé publiée est utilisée en quelques minutes. Le nettoyage vient APRÈS la rotation. Trois mécanismes de stockage : fichiers chiffrés (ansible-vault), variables injectées par le pipeline, et coffres-forts — seuls ces derniers apportent journalisation des accès, rotation et durée de vie limitée.