Aller au contenu

Certification runtime & observabilité — 12 septembre 2026

Status: CLOSED / PASS

Cette page conserve la preuve de clôture du chantier infrastructure réalisé le 12 septembre 2026. Elle documente les décisions, les bascules live, les incidents rencontrés, les validations et les règles qui deviennent normatives pour les opérations suivantes.

Périmètre

Le chantier a couvert :

  • migration de l'autorité Docker/containerd vers /mnt/data ;
  • réalignement Alloy et cAdvisor sur le nouveau Docker data-root ;
  • correction/provisionnement des dashboards Grafana ;
  • bascule Prometheus sur la configuration qui scrape Alloy ;
  • retrait contrôlé de Promtail ;
  • déploiement du worker self-contained immuable ;
  • certification Runtime Guard v2 ;
  • durcissement GitHub de la préparation des futures release authorities.

Aucune réintroduction de WordPress Core sur l'hôte n'a été autorisée.

Autorités Git et runtime

Release live certifiée

0cb30193acfd245717e2b1203b0f6309f6697e36

Cette release est le résultat des corrections observabilité incluant notamment les PR #275 et #276.

La production a été certifiée contre :

/mnt/data/cmonchoix-release-authority/0cb30193acfd245717e2b1203b0f6309f6697e36

Durcissement GitHub post-certification

La PR #277 a ajouté l'autorité de préparation des release worktrees et ses tests.

Merge SHA :

7cbcc4c847c7d0313f53777cc6ee1c8dc288f452

Ce commit ne nécessitait pas de redéploiement du runtime live : il durcit la création des futures releases et ne remplace pas rétroactivement la release déjà certifiée.

Les workflows de la PR puis du merge sur main ont été certifiés verts, notamment :

  • CI ;
  • P0 Deployment Authority Off-Live Certification ;
  • P1 Docker Data Mount Guard ;
  • P1 Observability Discord Alert Delivery.

Stockage Docker/containerd

État final :

Docker data-root  /mnt/data/docker
containerd root   /mnt/data/containerd

La migration a conservé l'état applicatif et les volumes persistants. Les anciens répertoires /var/lib/docker et /var/lib/containerd ne sont plus l'autorité de stockage active.

Les binds observabilité ont été réalignés :

Alloy
/mnt/data/docker/containers
→ /var/lib/docker/containers:ro

cAdvisor
/mnt/data/docker
→ /var/lib/docker:ro

Le root filesystem n'est donc plus la destination normale de croissance du store Docker/containerd.

Alloy

Le cut Alloy a recréé uniquement le service Alloy depuis la release certifiée.

Contrats validés :

  • service running ;
  • RestartCount=0 ;
  • /ready retourne Alloy is ready. ;
  • config montée depuis la release exacte ;
  • source Docker hôte /mnt/data/docker/containers ;
  • état Alloy durable sous /mnt/data/cmonchoix-infra/observability/alloy ;
  • ancien état Promtail monté en lecture seule ;
  • disparition de l'erreur historique labelallow stage config cannot be empty.

cAdvisor

Le cut cAdvisor a recréé uniquement le service cAdvisor depuis la release certifiée.

Contrats validés :

  • service running ;
  • RestartCount=0 ;
  • bind /mnt/data/docker → /var/lib/docker:ro ;
  • endpoint /metrics disponible ;
  • métriques CPU et mémoire présentes ;
  • visibilité des conteneurs WordPress, MariaDB et platform-worker ;
  • aucun autre conteneur recréé.

Grafana

Grafana a été recréé seul depuis la release certifiée.

Le premier contrôle a révélé un problème de permissions sur les fichiers de provisioning : la release avait été matérialisée sous umask 077, produisant des répertoires 0700 et des fichiers 0600.

Grafana s'exécute non-root et ne pouvait donc pas traverser certains chemins sous :

/etc/grafana/provisioning
/etc/grafana/dashboards

Les permissions de la release active ont été normalisées sans modifier le contenu Git :

directories 0755
files       0644

Après correction :

  • /api/health retourne database: ok ;
  • aucun permission denied dans les logs de provisioning ;
  • les quatre dashboards JSON sont visibles ;
  • le provisioning dashboards se termine correctement ;
  • Nginx Proxy Manager résout le nouveau conteneur Grafana ;
  • l'accès public reste derrière https://grafana.cmonchoix.com ;
  • le port 3000 n'est pas publié directement sur Internet.

Loki

Un contrôle /ready a initialement retourné HTTP 503. La procédure s'est arrêtée avant toute mutation dépendante de Loki.

Le diagnostic read-only a montré que le 503 était transitoire. Après convergence :

  • /ready retourne HTTP 200 ;
  • le ring est actif ;
  • /loki/api/v1/labels retourne HTTP 200 ;
  • une requête {job="docker"} retourne des entrées récentes ;
  • Alloy ne signale aucune erreur de push vers Loki.

La règle retenue est fail-closed : un readiness 503 doit être diagnostiqué, jamais ignoré parce que le conteneur est simplement running.

Prometheus

Le Prometheus live historique ne contenait pas encore le job Alloy. Le premier essai de retrait de Promtail a donc été bloqué avant mutation par le garde-fou :

ERROR: missing jobs: ['alloy']

Prometheus a ensuite été recréé seul depuis la release 0cb30193....

Le premier démarrage a révélé le même problème umask 077 que Grafana :

USER=nobody
open /etc/prometheus/prometheus.yml: permission denied

Les permissions de monitoring/prometheus.yml et monitoring/prometheus-rules/ ont été normalisées en lecture pour le runtime non-root, sans changement de contenu Git.

Après recréation finale :

prometheus   up
node         up
cadvisor     up
alertmanager up
alloy        up

Contrats validés :

  • service ready ;
  • RestartCount=0 sur le conteneur final ;
  • SHA de /etc/prometheus/prometheus.yml identique au fichier de la release ;
  • provenance Compose exacte ;
  • up{job="alloy"} == 1 ;
  • up{job="cadvisor"} == 1 ;
  • Promtail encore présent pendant toute cette bascule.

Retrait définitif de Promtail

Promtail n'a été retiré qu'après certification des cinq targets Prometheus et des endpoints Alloy/Loki/Grafana.

État de rollback conservé :

/home/ubuntu/.local/state/cmonchoix/promtail-retirement/20260912T053613Z

Après suppression du conteneur ccx_promtail, un événement unique a été généré dans le syslog :

CCX_ALLOY_ONLY_PROBE_20260912T053613Z

Le probe a été trouvé dans Loki après suppression de Promtail. Cela constitue la preuve que le chemin suivant fonctionne seul :

syslog → Alloy → Loki

Marqueurs de clôture :

PROMTAIL_CONTAINER_REMOVED=PASS
ALLOY_ONLY_LOG_FLOW=PASS
PROMETHEUS_POST_RETIRE=PASS
CRITICAL_IDS_UNCHANGED=PASS
CORE_POST_RETIRE=PASS
GRAFANA_POST_RETIRE=PASS
PROMTAIL_ABSENT=PASS
PROMTAIL_RETIREMENT=PASS

Le répertoire d'état historique Promtail sous /mnt/data/cmonchoix-infra/observability/promtail est conservé comme preuve/compatibilité de positions et ne doit pas être nettoyé automatiquement.

Worker self-contained

Le dernier cut live a utilisé l'autorité versionnée :

scripts/platform-worker-safe-deploy.sh

Image exacte :

ghcr.io/neweden52/cmonchoix-platform-worker@sha256:fa23328f8f5076ebce06963cadc0108d6c1a9d2b3983dccbcd1d3a8e1a64d3ef

État rollback worker conservé :

~/.local/state/cmonchoix/deployments/20260912T053821Z

Le safe-deploy a validé son preflight puis recréé uniquement platform-worker avec les contraintes :

--no-deps
--no-build
--pull never

Contrats post-cut :

  • image exacte par RepoDigest ;
  • worker running ;
  • RestartCount=0 ;
  • exactement deux mounts RW : uploads et runtime-spool ;
  • aucun bind root /var/www/html ;
  • WP-CLI opérationnel ;
  • sync doctor idle ;
  • projections PASS ;
  • IDs WordPress, MariaDB, Redis et observabilité inchangés ;
  • Promtail toujours absent ;
  • WordPress Core toujours absent du checkout host ;
  • checkouts live et release propres.

Runtime Guard v2 final

La clôture live officielle a été effectuée avec :

CCX_LIVE_ROOT=/mnt/data/cmonchoix-platform \
CCX_GUARD_COMPOSE_FILE=/mnt/data/cmonchoix-release-authority/0cb30193acfd245717e2b1203b0f6309f6697e36/docker-compose.yml \
bash /mnt/data/cmonchoix-release-authority/0cb30193acfd245717e2b1203b0f6309f6697e36/scripts/runtime-readonly-guard.sh --runtime

Résultat :

RUNTIME_GUARD_FAILURES=0
RUNTIME_GUARD_WARNINGS=0
RUNTIME_GUARD_STATUS=PASS
CCX_RUNTIME_GUARD_V2_RUNTIME=PASS
FINAL_RUNTIME_CERTIFICATION=PASS

Le guard a notamment certifié :

  • runtime Docker disponible ;
  • unicité et santé WordPress/MariaDB/Redis/worker ;
  • image worker et mounts exacts ;
  • filesystem worker self-contained et read-only pour les assets embarqués ;
  • WordPress installé dans le worker ;
  • plugin runtime actif ;
  • CLI CCX disponible ;
  • contrat DB valide et base writable ;
  • 59 tables observées ;
  • merchant registry contract PASS ;
  • merchant activation authority PASS ;
  • fonctions runtime CCX PASS ;
  • absence de marqueurs SQL finalize non résolus.

Cause racine des permissions de release

Cause : création d'un worktree Git sous umask 077 sans phase explicite de normalisation des permissions nécessaires aux conteneurs non-root.

Le problème n'était pas un défaut Grafana ou Prometheus : les bind mounts pointaient vers des fichiers légitimes mais illisibles par leurs utilisateurs runtime.

La correction structurelle a été fusionnée via PR #277 :

scripts/release-authority-prepare.sh
tests/Operations/release-authority-prepare-smoke.sh
tests/Architecture/DeploymentAuthorityBoundaryTest.php

Le helper reproduit et neutralise explicitement le cas umask 077, reste idempotent et refuse les worktrees dirty ou étrangers.

Procédure normative pour les futures releases

  1. Choisir un SHA complet certifié sur GitHub.
  2. Garder le checkout live propre ; ne jamais le reset pour préparer la release.
  3. Préparer/revalider la release avec scripts/release-authority-prepare.sh.
  4. Exiger CCX_RELEASE_AUTHORITY_PREPARE=PASS.
  5. Valider le Compose depuis la release exacte.
  6. Effectuer uniquement des cuts service par service lorsqu'ils sont nécessaires.
  7. Pour le worker, utiliser exclusivement platform-worker-safe-deploy.sh.
  8. Ne jamais utiliser docker compose down, --build, --remove-orphans ou un prune de volumes comme raccourci de rollout.
  9. Vérifier les endpoints et targets d'observabilité après toute mutation concernée.
  10. Terminer par runtime-readonly-guard.sh --runtime et exiger zéro failure/zéro warning.

Verdict

STORAGE_MIGRATION=PASS
ALLOY_CONTROLLED_SWITCH=PASS
CADVISOR_CONTROLLED_SWITCH=PASS
GRAFANA_PROVISIONING=PASS
PROMETHEUS_CONTROLLED_SWITCH=PASS
PROMTAIL_RETIREMENT=PASS
PLATFORM_WORKER_FINAL_DEPLOY=PASS
CCX_RUNTIME_GUARD_V2_RUNTIME=PASS
FINAL_RUNTIME_CERTIFICATION=PASS

Le chantier est CLOSED / PASS. Toute évolution ultérieure doit partir de cet état comme nouvelle baseline de production.