cloud-init n est pas un logiciel d installation : c est une SOURCE DE VERITE EXTERNE. Il se reveille a chaque demarrage et relit le lecteur attache par l hyperviseur, qui peut redefinir comptes, cles SSH, mots de passe et reseau. Sur une machine que le plan possede, c est un second maitre — que le plan ne decrit pas, que make valider ne mesure pas, et qui parle en premier. Sa tache est finie a la premiere seconde : c est parce qu il a REUSSI a poser l adresse et les cles qu Ansible a pu entrer. TROIS MOITIES, ET ELLES SE DEFONT SEPAREMENT. - le GABARIT le garde : sans lui un clone n a ni adresse ni nom ; - le SOCLE ne l installe plus : le garder produisait un va-et-vient a chaque deploiement, deux changed par passage, idempotence perdue ; - le DURCISSEMENT le retire (roles/cloud_init_retrait, en dernier). P63 garde les trois, plus le CONTENU du role : une coquille vide passerait les trois premiers controles sans rien fermer. Quatre controles negatifs rejoues. CE QUI REND LE RETRAIT SUR EST MESURE, PAS SUPPOSE (obs-01, 2026-09-09) : /etc/network/interfaces.d/50-cloud-init n appartient a aucun paquet — dpkg -S ne le trouve pas — et le postrm ne le nomme jamais, meme en purge. L adresse survit. Le role le verifie quand meme, avant et apres, et n accuse que si le retrait l a emporte : une VM qui perd ce fichier ne se plaint pas, elle repart sans adresse et plus personne ne peut entrer. DEUX CHOIX DITS FRANCHEMENT. cloud-guest-utils reste (growpart : ni service, ni port, ni source de donnees). Les ~29 paquets orphelins ne sont pas retires par defaut : autoremove deciderait a partir des drapeaux dpkg, et un durcissement ne doit pas pouvoir surprendre. NON DEPLOYE : le code est ecrit, valide et prouve ; il n a pas ete applique a la flotte. Essai a blanc sur obs-01 : cloud-init a retirer, configuration reseau intacte. make prouver : CONFORME, 62 OK, 0 echec, 1 saute (63 preuves). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
159 lines
8.1 KiB
YAML
159 lines
8.1 KiB
YAML
---
|
|
- name: Appliquer le groupe serveur_debian
|
|
hosts: serveur_debian
|
|
become: true
|
|
# Le verrou dpkg est tenu par les maj automatiques de Debian, par vagues, plusieurs
|
|
# minutes apres le premier demarrage. Pose ICI plutot que dans chaque role : toute
|
|
# tache apt du play en herite, y compris celles des roles inclus.
|
|
module_defaults:
|
|
ansible.builtin.apt:
|
|
lock_timeout: 300
|
|
|
|
gather_facts: true
|
|
|
|
pre_tasks:
|
|
- name: Vérifier que la cible est Debian
|
|
ansible.builtin.assert:
|
|
that:
|
|
- ansible_facts.distribution == "Debian"
|
|
fail_msg: "Ce playbook est prévu pour Debian."
|
|
|
|
# Résolveur d'AMORÇAGE, posé avant `common_packages` — donc avant le premier `apt`.
|
|
# Sans lui, le déploiement échoue avant d'avoir commencé : le gabarit doré transporte
|
|
# un `/etc/resolv.conf` figé, et le `dns-nameservers` écrit par cloud-init reste sans
|
|
# effet faute de `resolvconf` (qu'on ne peut pas installer sans résolution — la boucle
|
|
# se referme). Mesuré le 2026-08-06 sur deux VM neuves.
|
|
#
|
|
# PASSAGE DE RELAIS : `client_resolveur` reprend ce fichier plus tard pour le basculer
|
|
# vers `127.0.0.1`. La garde ci-dessous le respecte — sans elle, chaque déploiement
|
|
# défairait la bascule, et deux rôles se disputeraient le même fichier sans fin.
|
|
# SOURCE D'ARTEFACTS D'AMORÇAGE, posée avant le premier `apt` — symétrique exacte du
|
|
# résolveur d'amorçage ci-dessous, et pour la même raison de fond : LA PREMIÈRE
|
|
# MACHINE D'UN ÉCOSYSTÈME N'A NI RÉSOLVEUR NI CACHE, ET C'EST ELLE QUI DOIT LES
|
|
# CONSTRUIRE.
|
|
#
|
|
# Mesuré le 2026-08-28 sur `ops-01`, première machine de la reconstruction de
|
|
# Chezlepro. Le socle restait bloqué sur son premier `apt update`, sans message qui
|
|
# parle de réseau :
|
|
#
|
|
# sortie Internet TCP 443 OK
|
|
# DNS UDP 53 -> 9.9.9.9 MUET
|
|
#
|
|
# Le DNS sortant est fermé PAR CONCEPTION — un tenant résout chez lui, son DNS ne
|
|
# traverse jamais la frontière. Or apt vise `deb.debian.org` PAR SON NOM, et le cache
|
|
# de l'écosystème n'existe pas encore.
|
|
#
|
|
# UN MANDATAIRE HTTP RÉSOUT CE QUE LE CLIENT NE PEUT PAS RÉSOUDRE : apt lui envoie
|
|
# l'URL absolue, et c'est LUI qui traduit le nom. La machine n'a donc besoin d'aucun
|
|
# DNS pour installer ses paquets — seulement d'une ADRESSE.
|
|
#
|
|
# C'EST DE LA FILIATION, PAS UNE RUSTINE (docs/filiation-emancipation.md). L'hébergeur
|
|
# nourrit la première machine de son locataire le temps que celui-ci monte son propre
|
|
# cache. L'emprunt est DÉCLARÉ — l'intrant nomme l'amont — et il s'efface de lui-même :
|
|
# `client_artefacts` écrit `00-setops-artefacts`, qui trie APRÈS ce fichier-ci, donc
|
|
# gagne dès que l'écosystème a sa propre source. Le plancher reste dessous, comme
|
|
# `/etc/hosts` reste sous le DNS.
|
|
#
|
|
# Aucun flux à ouvrir : `serveur_cache_site` accepte déjà le 3142 depuis
|
|
# `voisins_site`, qui résout les SUPERNETS des tenants — donc toute machine, pas
|
|
# seulement leur cache. Vérifié depuis `ops-01` : HTTP 200 en 0,158 s.
|
|
- name: Poser la source d'artefacts d'amorçage (avant tout apt)
|
|
ansible.builtin.copy:
|
|
dest: /etc/apt/apt.conf.d/00-setops-amorcage-artefacts
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
content: |
|
|
// GÉNÉRÉ par Set-OPS (socle) — source d'artefacts d'AMORÇAGE, intrant
|
|
// `artefacts_amorcage`. Remplacé par `client_artefacts` (00-setops-artefacts,
|
|
// qui trie après) dès que l'écosystème a sa propre source.
|
|
//
|
|
// UNE ADRESSE, JAMAIS UN NOM : au moment où ce fichier sert, la machine n'a pas
|
|
// encore de résolveur, et le DNS sortant d'un tenant est fermé par conception.
|
|
Acquire::http::Proxy "http://{{ artefacts_amorcage }}";
|
|
// `DIRECT` est obligatoire : apt fait HÉRITER la valeur HTTPS de la valeur HTTP
|
|
// quand elle n'est pas définie, et enverrait les dépôts tiers en HTTPS dans un
|
|
// cache qui refuse les tunnels (mesuré le 2026-08-23).
|
|
Acquire::https::Proxy "DIRECT";
|
|
when:
|
|
- artefacts_amorcage is defined
|
|
- artefacts_amorcage | string | length > 0
|
|
|
|
- name: Lire le résolveur en place
|
|
ansible.builtin.slurp:
|
|
src: /etc/resolv.conf
|
|
register: serveur_debian_resolv_actuel
|
|
failed_when: false
|
|
changed_when: false
|
|
|
|
- name: Poser le résolveur d'amorçage (avant tout apt)
|
|
ansible.builtin.copy:
|
|
dest: /etc/resolv.conf
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
content: |
|
|
# Géré par Set-OPS — résolveur d'amorçage (intrant `dns_amorcage`).
|
|
# Remplacé par `client_resolveur` lorsqu'il bascule vers le résolveur local.
|
|
{% for serveur in dns_amorcage.split(',') if serveur | trim %}
|
|
nameserver {{ serveur | trim }}
|
|
{% endfor %}
|
|
# L'AMORÇAGE NE DOIT PAS DÉFAIRE CE QUI EST DÉJÀ EN PLACE (2026-08-25).
|
|
#
|
|
# La garde ne protégeait que l'hôte du résolveur lui-même (`127.0.0.1`). Partout
|
|
# ailleurs, le socle réécrivait `/etc/resolv.conf` avec `dns_amorcage` — donc
|
|
# **défaisait la bascule** de `client_resolveur` à chaque passage.
|
|
#
|
|
# Ça ne s'était jamais vu chez un tenant : `dns_amorcage` y vaut l'adresse du
|
|
# résolveur du tenant, si bien que réécrire remettait la même valeur. La
|
|
# coïncidence masquait le défaut.
|
|
#
|
|
# Sur le site, les deux ont divergé quelques heures — l'amorçage pointait encore sur
|
|
# la frontière alors que `client_resolveur` avait basculé les machines sur
|
|
# `site-dns-01`. Chaque déploiement du socle les ramenait en arrière, en silence, et
|
|
# ça ne s'est vu qu'en retirant la règle de pare-feu vers la frontière : les cinq
|
|
# machines ont alors perdu la résolution d'un coup.
|
|
#
|
|
# On ne pose donc l'amorçage que si rien de sensé n'est déjà là : ni la loopback, ni
|
|
# un résolveur déclaré de cet écosystème.
|
|
vars:
|
|
serveur_debian_resolv_texte: >-
|
|
{{ serveur_debian_resolv_actuel.content | default('') | b64decode | string }}
|
|
serveur_debian_resolveurs_declares: >-
|
|
{{ (groups['serveur_resolveur'] | default([]))
|
|
| map('extract', hostvars, 'ansible_host') | select | list }}
|
|
when:
|
|
- dns_amorcage is defined
|
|
- dns_amorcage | string | length > 0
|
|
- "'127.0.0.1' not in serveur_debian_resolv_texte"
|
|
- serveur_debian_resolveurs_declares
|
|
| select('in', serveur_debian_resolv_texte) | list | length == 0
|
|
|
|
roles:
|
|
# LE PLANCHER AVANT LE PREMIER `apt` (2026-08-25).
|
|
#
|
|
# `hosts_statiques` venait APRÈS `common_packages`, qui commence par un `apt update`.
|
|
# Or apt vise le cache PAR SON NOM — `site-cache-01.<domaine>` — ce qui est voulu :
|
|
# une configuration agnostique de l'adressage.
|
|
#
|
|
# Le jour où les adresses changent, la boucle se referme : apt échoue faute de
|
|
# résoudre, donc le socle n'atteint jamais `hosts_statiques`, donc le plancher reste
|
|
# périmé, donc apt échoue. Mesuré le 2026-08-25 après le découpage du site en zones :
|
|
# cinq machines bloquées, `/etc/hosts` vide de toute entrée du site.
|
|
#
|
|
# Le résolveur d'amorçage est déjà une `pre_task` pour exactement cette raison. Le
|
|
# plancher est de la même nature : il ne s'installe pas, **il rend installable**.
|
|
- hosts_statiques
|
|
- common_packages
|
|
- qemu_guest_agent
|
|
# `cloud_init` N'EST PLUS APPLIQUE ICI (2026-09-09). Il reste au GABARIT, qui en a
|
|
# besoin : il est le seul chemin vers la premiere seconde d'un clone (P56). Mais sur
|
|
# une machine deja nee, le reinstaller n'a plus d'objet — et le durcissement le
|
|
# retire juste apres. Les garder tous les deux produisait un va-et-vient a chaque
|
|
# deploiement : le socle installe, le durcissement retire, deux `changed` par passage,
|
|
# et l'idempotence perdue. P63 garde les trois moities de cette decision.
|
|
- sudo_ansible
|
|
- chrony
|
|
- ssh_baseline
|
|
- systemd_ssh_auto
|
|
- motd
|