powerdns : une zone plus recente que sa version servie est rechargee ; zones le voit

PowerDNS servait chez les deux locataires une zone anterieure a son fichier (console
absente) : le handler de rechargement avait ete abandonne. Reconciliation a chaque
passage (bind-reload-now) et controle dans la sonde zones. Plancher /etc/hosts rejoue
sur les deux locataires ; console se resout depuis les edges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-09-29 05:29:45 -04:00
parent fbd4094d3f
commit ba2020d486
4 changed files with 99 additions and 1 deletions

View file

@ -1,5 +1,47 @@
# CHANGELOG — Set-OPS
## 2026-09-29 (30) — `console` ne se résolvait pas : un plancher périmé et une zone jamais rechargée
**Constat.** Depuis l'edge de chaque locataire, `console.<domaine>` ne se résolvait pas,
alors que l'edge la sert. Les autres expositions se résolvaient.
**Première cause : le plancher `/etc/hosts` n'avait pas été rejoué.** Il dérive du plan
(inventaire + `expose`), mais n'est posé que par le socle (`serveur_debian`). La console a
été ajoutée au plan après le dernier passage du socle ; seul `ops-01`, redéployé depuis,
la portait. Rejoué seul (`hosts_statiques`, sans `common_packages` et sa mise à jour
complète) sur les 26 machines des deux locataires, après simulation : `console` ajoutée,
`forge-01` et `forge` retirées (l'ancienne forge des locataires, sortie du plan).
**Seconde cause, plus grave : PowerDNS servait une zone antérieure à son fichier.** Le
fichier de zone, réécrit le 16 septembre, portait `console` ; PowerDNS servait la version
chargée à son démarrage (13 septembre chez Chezlepro, 14 chez Technolibre). Le rechargement
ne passait que par un handler, et un handler en attente est abandonné quand une tâche échoue
plus loin dans le même passage ; aux passages suivants, le fichier ne change plus et rien ne
le redemande. La sonde `zones` comptait les zones servies, pas leur fraîcheur : verte.
- **Rôle `serveur_powerdns`** : à chaque passage, chaque zone (interne et publique) dont le
fichier est plus récent que son chargement (`bind-domain-status`) est rechargée
(`bind-reload-now`). Ce qui est à jour n'est pas touché.
- **Sonde `zones`** : critique si une zone servie est plus ancienne que son fichier
au-delà de `serveur_powerdns_sonde_tolerance` (900 s), en nommant les zones. Mise en
défaut par ce paramètre : critique.
- **Posé sur les trois** (Chezlepro, Technolibre, site) : deux zones rechargées chez chaque
locataire ; au site, la zone et une zone inverse réécrites (le DNS public `site-dnspub-01`
n'y était pas encore). `zones` au vert partout, « chacune conforme à son fichier ».
`console` se résout depuis les deux edges, par le plancher et par PowerDNS.
**Une erreur de ma part, rattrapée par la garde de syntaxe.** `${#tableau[@]}` dans une sonde
ouvre un commentaire Jinja. `test_sondes_syntaxe.py` l'a vu ; mais j'avais enchaîné le
déploiement de Technolibre sans attendre le test, et la pose de la sonde y a échoué (la
réconciliation, elle, était passée). Corrigé et redéployé ; la configuration publique
réécrite pendant ce passage raté ne différait que par des commentaires.
**Ce qui n'est pas traité ici.** Les machines des locataires interrogent le résolveur du
site, qui répond NXDOMAIN pour leurs noms internes : leur résolution repose entièrement sur
le plancher. C'est l'état voulu tant que le basculement vers leur résolveur
(`client_resolveur_apply`) n'est pas confirmé. Et rien ne signale encore qu'un plancher est
en retard sur le plan : il suit le plan au déploiement, pas en continu.
## 2026-09-29 (29) — `passerelle` fait le trajet d'un visiteur jusqu'à la page de connexion ; fin de la revue des sondes
Cinquième et dernière des sondes « répond » de la revue (25).

View file

@ -84,6 +84,10 @@ serveur_powerdns_zones_inverses: >-
# --- Sonde de supervision -----------------------------------------------------------
# La zone souveraine et son inverse : deux au minimum.
serveur_powerdns_sonde_minimum: 2
# Ecart tolere (secondes) entre la date d'un fichier de zone et son chargement par
# PowerDNS : le temps qu'un deploiement ecrive puis recharge. Au-dela, la zone servie
# n'est pas celle du disque. Mise en defaut PAR PARAMETRE : une valeur tres negative.
serveur_powerdns_sonde_tolerance: 900
# --- ZONES PUBLIQUES : `autorite: primaire-cache` (tasks/zones-publiques.yml) ---------
#

View file

@ -133,6 +133,40 @@
# en vrai deploiement, la tache s'execute normalement.
when: not ansible_check_mode
# LE FICHIER SUR DISQUE N'EST PAS LA ZONE SERVIE (2026-09-29). Le rechargement passait
# seulement par un handler. Or un handler en attente est ABANDONNE si une tache echoue plus
# loin dans le meme passage — et aux passages suivants le fichier ne change plus, donc plus
# rien ne le redemande. Constate chez les deux locataires : zone reecrite le 16 septembre
# (ajout de `console`), PowerDNS la servait telle que chargee le 13 ou le 14. Treize jours
# de NXDOMAIN sur un nom que le fichier portait, et aucune sonde ne le voyait.
#
# On compare donc a chaque passage, zone par zone, la date du fichier a celle ou PowerDNS
# l'a chargee, et on recharge ce qui est en retard. Ce qui est juste n'est pas touche.
- name: Recharger toute zone dont le fichier est plus recent que la version servie
ansible.builtin.shell:
executable: /bin/bash
cmd: |
set -uo pipefail
verifier() { # $1 = option d'instance ("" ou --config-name=public), $2 = repertoire
local f z charge
for f in "$2"/*.zone; do
[[ -e "${f}" ]] || continue
z=$(basename "${f}" .zone)
charge=$(pdns_control $1 bind-domain-status "${z}" 2>/dev/null \
| grep -oP 'parsed into memory at \K.*')
if [[ -z "${charge}" ]] || (( $(date -r "${f}" +%s) > $(date -d "${charge}" +%s) )); then
pdns_control $1 bind-reload-now "${z}" >/dev/null && echo "RECHARGEE ${z}"
fi
done
}
verifier "" "{{ serveur_powerdns_zone_directory }}"
{% if serveur_powerdns_dns_public_site | length > 0 %}
verifier "--config-name=public" "{{ serveur_powerdns_public_zones_dir }}"
{% endif %}
register: serveur_powerdns_reconciliation
changed_when: "'RECHARGEE' in serveur_powerdns_reconciliation.stdout"
when: not ansible_check_mode
# --- Sonde de supervision (docs/supervision-conception.md) --------------------------
# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la
# fait tourner et pousse le verdict, sans savoir ce qu'elle mesure.

View file

@ -8,6 +8,7 @@
set -uo pipefail
MINIMUM={{ serveur_powerdns_sonde_minimum }}
TOLERANCE={{ serveur_powerdns_sonde_tolerance }}
systemctl is-active --quiet pdns || { echo "L'autoritatif (pdns) n'est pas actif."; exit 2; }
[[ "$(pdns_control rping 2>&1 | head -1)" == "PONG" ]] \
@ -21,4 +22,21 @@ if (( n < MINIMUM )); then
echo "L'autoritatif ne sert que ${n} zone(s), moins que ${MINIMUM} — la resolution interne repondra NXDOMAIN.|zones=${n};${MINIMUM};;0;"
exit 2
fi
echo "Autoritatif : ${n} zone(s) servie(s).|zones=${n};${MINIMUM};;0;"
# LA ZONE SERVIE EST-ELLE CELLE DU DISQUE ? (2026-09-29) Compter les zones ne dit pas
# lesquelles sont a jour : chez les deux locataires, PowerDNS a servi pendant treize jours
# une zone anterieure a son fichier — `console` y manquait — et cette sonde restait verte.
# `bind-domain-status` donne l'heure de chargement ; on la compare a celle du fichier.
retard=()
for f in {{ serveur_powerdns_zone_directory }}/*.zone; do
[[ -e "${f}" ]] || continue
z=$(basename "${f}" .zone)
charge=$(pdns_control bind-domain-status "${z}" 2>/dev/null | grep -oP 'parsed into memory at \K.*')
if [[ -z "${charge}" ]]; then retard+=("${z}=jamais chargee"); continue; fi
ecart=$(( $(date -r "${f}" +%s) - $(date -d "${charge}" +%s) ))
(( ecart > TOLERANCE )) && retard+=("${z}=fichier plus recent de $(( ecart / 3600 )) h")
done
if [[ -n "${retard[*]}" ]]; then # (le compteur de tableau de bash ouvrirait un commentaire Jinja)
echo "L'autoritatif sert ${n} zone(s), mais PAS celles du disque : ${retard[*]} — les noms ajoutes depuis repondent NXDOMAIN. Redeployer serveur_powerdns.|zones=${n};${MINIMUM};;0;"
exit 2
fi
echo "Autoritatif : ${n} zone(s) servie(s), chacune conforme a son fichier.|zones=${n};${MINIMUM};;0;"