Procédure de release de Perf Sentinel
Ce document décrit la procédure de release de bout en bout pour perf-sentinel, applicable à partir de 0.7.0. La procédure inclut un gate de validation obligatoire sur le simulation lab qui bloque le tag d'une version qui n'a pas été éprouvée de bout en bout sur un cluster k3d réel.
Le gate est un contrôle préalable piloté par l'opérateur, pas un job CI. Il s'exécute contre un ledger append-only (release-gate/lab-validations.txt) qui enregistre chaque validation lab et son verdict. La CI ne peut pas reproduire un run de lab, donc automatiser le gate dans le workflow de release reviendrait à le vider de sa substance.
Prérequis
- Checkout local du repo
perf-sentinel-simulation-labsur un commit récent demain. - Un environnement
k3d+ Docker fonctionnel pour le lab (voirdocs/QUICKSTART.mddu lab). - Droits de push sur le repo
perf-sentinelet une identité de signature de tag. La procédure utilisegit tag -s, qui passe par GPG par défaut (requiertuser.signingkeyconfiguré). La signature SSH fonctionne aussi viagit config gpg.format sshavec une clé enregistrée comme signataire. ghCLI authentifié quand vous avez besoin d'interroger l'API REST GHCR.
Procédure
1. Ouvrir une branche release
git checkout main && git pull
git checkout -b release/X.Y.ZLa release arrive sur main par un commit de fusion, qui garde chaque commit et sa signature, et le tag vX.Y.Z posé sur ce commit de fusion la délimite : git log vPREV..vX.Y.Z liste ses commits. Le commit de fusion est la seule méthode de fusion que le dépôt autorise. Convention de nommage : la branche est release/X.Y.Z (sans v initial), le tag publié plus tard est vX.Y.Z (avec v initial). scripts/check-tag-version.sh accepte les deux formes en entrée.
2. Code, tests, bumps de version
Appliquer le travail de feature, fix ou refactor pour la release. Puis incrémenter toutes les références de version en lockstep.
Vérifié par scripts/check-tag-version.sh (la CI le lance comme premier job de release.yml, lançable aussi en local) :
Cargo.tomlworkspace[workspace.package].version- Chaque
crates/*/Cargo.toml: soitversion.workspace = true(résout vers la version du workspace), soit une version explicite qui doit correspondre au tag. Le pin intra-workspaceperf-sentinel-core = { version = "X.Y.Z", path = "..." }danscrates/sentinel-cli/Cargo.tomlest aussi vérifié ici.
Pris en charge par l'opérateur (pas de gate CI, à auditer à la main avec grep -RIn "<previous_version>" docs/ charts/ CHANGELOG.md) :
docs/ci-templates/*: la constantePERF_SENTINEL_VERSIONdansgithub-actions-baseline.yml,github-actions-report-cleanup.yml,github-actions.yml,gitlab-ci.yml, etjenkinsfile.groovy.- CI et CI : snippets d'exemple qui affichent
perf-sentinel@vX.Y.Z. docs/schemas/examples/*.json: uniquement le champbinary_verification_url(qui pointe toujours vers la dernière release). Les autres champs de version (perf_sentinel_version,binary_version,binary_versions) restent figés à la baseline historique de l'exemple.CHANGELOG.md: déplacer le contenu de[Unreleased]sous un nouveau titre[X.Y.Z]daté du jour.
Lancer les gates locaux :
cargo fmt --all
cargo clippy --workspace --all-targets --features daemon -- -D warnings
cargo clippy --workspace --no-default-features -- -D warnings
cargo test --workspace
scripts/check-tag-version.sh vX.Y.ZLes deux invocations de clippy couvrent l'ensemble des features par défaut et le build no-default-features, puisque plusieurs modules sont derrière #[cfg(feature = "...")]. La CI exécute la même matrice.
2.5 Fraîcheur des données de référence GreenOps
C'est un audit opérateur, aucun script ne le vérifie automatiquement. Les données de référence embarquées alimentent le pipeline de scoring carbone et sont livrées comme source Rust (donc couvertes par cargo test --workspace à l'étape 2). Les tests garantissent la correction, pas la fraîcheur. Le workflow refresh-datasets régénère les trois tables scriptées deux fois par an (ou via workflow_dispatch) et ouvre une PR. Cette étape vérifie que ces PR ont été fusionnées assez récemment. Avant de taguer, confirmer les millésimes déclarés dans :
crates/sentinel-core/src/score/cloud_energy/table_data.rs:SPECPOWER_VINTAGE, estampillé avec la date de l'instantanéccf-coefficientsparscripts/refresh-instance-power.py. Les lignes manuelles (familles absentes des CSV CCF) restent danstable.rs.crates/sentinel-core/src/score/carbon_data.rs:CARBON_TABLE_VINTAGE(ember-<latest-data-year>), estampillé parscripts/refresh-carbon-data.py. Les lignes subnationales (Amérique du Nord, Brésil BR-CS) restent manuelles danscarbon.rs.crates/sentinel-core/src/score/equivalence_data.rs:BASE_CARBONE_VINTAGE(version de la Base Carbone et date de sa dernière mise à jour),DESNZ_VINTAGEetEPA_VINTAGE, estampillés parscripts/refresh-equivalences.py, qui réécrit aussi la table dedocs/SHARE-CARD.mdet de sa version française. Vérifier que chaque ligne de la Base Carbone est encoreValide génériqueet son libellé encore exact, et consulter les pages du DESNZ et de l'EPA pour une édition plus récente (le DESNZ en publie une chaque juin ;DESNZ_URL,DESNZ_VINTAGE,EPA_URLetEPA_VINTAGEse changent à la main).crates/sentinel-core/src/score/carbon_profiles.rs: profils horaires de réseau ENTSO-E / EIA / AEMO / Electricity Maps, rafraîchis au moins annuellement et renormalisés à la main quand un rafraîchissement scripté déplace une valeur annuelle au-delà de 5 pour cent. Millésime exposé viaCARBON_PROFILES_VINTAGE.crates/sentinel-core/src/score/carbon.rs: constantes PUE par fournisseur (AWS, GCP, Azure, OVHcloud, Scaleway, générique), rafraîchies quand un fournisseur publie un nouveau rapport de durabilité. OUTSCALE ne publie aucun PUE et suit la valeur générique. OVHcloud et Scaleway publient sur un cycle annuel, respectivement en indicateurs d'exercice et en rapport d'impact. Millésime exposé viaPUE_VINTAGE.
Afficher tous les millésimes en une commande :
grep -rn 'VINTAGE' crates/sentinel-core/src/score/La fenêtre de données devrait couvrir la date de release avec une marge confortable (typiquement : table SPECpower dans les 2 derniers trimestres, profils de réseau dans l'année en cours). Sinon, soit rafraîchir les données dans cette release (en mettant aussi à jour la constante _VINTAGE correspondante), soit documenter le report dans CHANGELOG.md pour que la péremption soit explicite pour les utilisateurs en aval.
3. Bumper le chart Helm en lockstep
La version du chart et appVersion bougent avec la version applicative :
# charts/perf-sentinel/Chart.yaml
version: A.B.C # à incrémenter à chaque changement du chart
appVersion: "X.Y.Z" # suit la release perf-sentinelDeux annotations du même fichier et le changelog du chart bougent avec appVersion, et seule la première saute aux yeux :
artifacthub.io/images: le tagghcr.io/institut-du-numerique-responsable/perf-sentinel:X.Y.Z. Artifact Hub annonce cette image, donc un tag périmé y pointe les lecteurs vers le daemon précédent.scripts/check-chart-appversion-annotation.shtourne dans la CI des PR et fait échouer le build quand il traîne derrièreappVersion.artifacthub.io/changes: une nouvelle entrée par release, décrivant le changement du point de vue de l'opérateur.charts/perf-sentinel/CHANGELOG.md: la section correspondante, quescripts/check-chart-version-bumped.shexige.
scripts/check-chart-version-bumped.sh tourne dans la CI des PR et rejette tout changement de chart sans incrément de version et sans entrée CHANGELOG.md sous charts/perf-sentinel/. scripts/check-helm-tag-version.sh valide le tag du chart au moment de la release.
Les contrôles du chart sont tous exécutables en local, et moins coûteux qu'un aller-retour CI :
scripts/check-chart-appversion-annotation.sh
scripts/check-chart-version-bumped.sh main
scripts/check-helm-tag-version.sh chart-vA.B.CCorrectif chart-only isolé (exception au lockstep) : un bug du chart qui doit être corrigé sans aucun changement du binaire (par exemple values.schema.json 0.9.16, voir charts/perf-sentinel/CHANGELOG.md) peut être publié seul via un tag chart-vA.B.C, qui déclenche directement helm-release.yml, sans tag d'application v* et sans gate de validation lab. appVersion reste sur la dernière version applicative publiée, donc il traîne temporairement derrière le version du chart. La prochaine release applicative doit faire avancer appVersion/version du chart du nombre de releases chart-only prises entre-temps. Une release sautée de 0.9.15 vers un chart-only 0.9.16 veut dire que la prochaine release applicative est en 0.9.17, pas 0.9.16, pour que les deux numéros retombent sur la même valeur au lieu de rester décalés d'un cran en permanence.
4. Valider sur le simulation lab
Pousser la branche release :
git push -u origin release/X.Y.ZL'image Docker est publiée sur GHCR exclusivement par release.yml au push d'un tag v*, pas par ci.yml. Deux options pour obtenir une image dans le cluster lab :
- Option A (recommandée pour une validation propre) : construire l'image localement depuis le checkout de la branche release et l'importer dans le cluster k3d : Puis épingler les manifests du lab sur le tag chargé localement.bash
docker build -t perf-sentinel:vX.Y.Z-rc . k3d image import perf-sentinel:vX.Y.Z-rc -c <cluster-name> - Option B : pousser un tag de pré-release (
vX.Y.Z-rc.1) pour déclencherrelease.ymlsur une image candidate. L'image devient disponible sur GHCR en environ 10 minutes. Épingler les manifests du lab sur le digest résultant (résolution via API REST GHCR, voirdocs/TROUBLESHOOTING.mddu lab).
Lancer le lab de bout en bout :
cd <path-to>/perf-sentinel-simulation-lab
make down
make up
make seed-services
make validate-findings # attendu : 10/10 scénarios passent
make verify-all-scenarios # attendu : 24/24 résultats détecteurs concordentSi l'une des étapes échoue, ne pas enregistrer de PASS. Corriger le problème de fond dans perf-sentinel, reconstruire l'image, et relancer le lab.
Si tout passe, enregistrer la validation dans le ledger :
# Depuis le repo lab, produit une ligne séparée par des tabulations sur stdout.
scripts/record-validation.sh vX.Y.Z PASS
# Copier la ligne et l'ajouter à release-gate/lab-validations.txt dans
# le repo perf-sentinel. Le ledger est append-only. Ne jamais éditer
# les entrées existantes.Chemin canonique : scripts/release.sh
Les étapes 5 et 6 ci-dessous sont enveloppées dans une commande unique qui échoue fermé :
scripts/release.sh vX.Y.ZLe script lance tous les pre-checks (branche, arbre propre, identité de signature, sync remote, absence du tag), puis les deux gates (scripts/check-tag-version.sh et release-gate/check-lab-validation.sh), puis demande confirmation, puis signe le tag et pousse main d'abord, puis le tag. Si un seul pre-check ou gate refuse, rien n'est modifié. Si le push du tag échoue une fois main poussé, le tag local est annulé pour ne pas laisser de référence orpheline sur le remote.
C'est le chemin recommandé parce qu'il rend l'oubli du gate structurellement impossible. Utilisez scripts/release.sh vX.Y.Z --dry-run pour vérifier que tous les gates passent au vert avant de tagger pour de vrai. Passez --yes pour sauter la confirmation interactive en contexte scripté. Passez --skip-lab pour contourner explicitement le gate de validation lab. Ce flag émet un avertissement d'audit bien visible et n'écrit jamais le ledger. Il sert pour une release validée par d'autres moyens, par exemple un changement qui ne touche que la CLI ou la doc, couvert par la suite E2E. Le flag ne saute que le gate lab, tous les autres pre-checks et le gate de version s'appliquent toujours.
La prose ci-dessous reste la référence de ce que le script automatise, et le repli quand un opérateur veut piloter les étapes à la main. Le run de lab (étape 4) reste piloté par l'opérateur, le script vérifie uniquement que la ligne PASS de l'étape 4 est présente et fraîche dans le ledger.
5. Gate préalable
De retour dans le checkout perf-sentinel :
release-gate/check-lab-validation.sh --version vX.Y.ZL'argument --version accepte soit vX.Y.Z soit X.Y.Z (cette dernière forme est normalisée en vX.Y.Z en interne), suivant la convention de scripts/check-tag-version.sh. La colonne 1 du ledger doit toujours porter le v initial (par exemple v0.7.2). Sortie attendue en cas de succès :
release-gate: PASS for vX.Y.Z dated YYYY-MM-DD (lab commit <sha>, <N>d old, threshold 30d). OK to release.Le gate a trois modes d'échec, chacun avec un remède actionnable :
| Échec | Message | Remède |
|---|---|---|
| Version absente ou seulement entrées FAIL | no PASS entry for vX.Y.Z in ... | Relancer le lab et ajouter une ligne PASS. |
| Dernière entrée PASS trop ancienne | latest PASS for vX.Y.Z is N days old ... Threshold is 30 days. | Relancer le lab sur la branche actuelle et ajouter une entrée PASS fraîche. |
| Fichier ledger absent | ledger ... not found. | S'assurer que release-gate/lab-validations.txt est présent à côté du script, ou définir LEDGER=/path/to/file. |
Le seuil d'âge est configurable pour les scénarios de rattrapage ou d'audit. --max-age-days 365 accepte des entrées jusqu'à un an d'ancienneté. La valeur par défaut de 30 jours est la valeur de travail, ne pas la modifier pour des releases normales.
6. Merge et tag
Après que le gate passe :
gh pr merge <PR> --merge
git checkout main && git pull
git tag -s vX.Y.Z -m "vX.Y.Z"
git push origin vX.Y.Z
git branch -d release/X.Y.Zmain exige des commits signés. Le commit de fusion garde les commits de la branche avec leur SHA et leur signature, et GitHub signe lui-même le commit de fusion. Si main a bougé depuis la création de la branche, mettez la branche à jour en local avec git merge origin/main, qui signe la fusion, plutôt qu'avec le bouton Update branch de GitHub, dont la variante rebase retire les signatures. GitHub supprime la branche distante après la fusion.
Le push du tag déclenche .github/workflows/release.yml. Son premier job relance scripts/check-tag-version.sh comme gate de vérification, puis la matrice de build produit les binaires. Le job de publication pousse vers crates.io strictement (pas de repli souple en cas de rate-limit), et le job docker scanne l'image avec Trivy (échec bloquant sur HIGH ou CRITICAL) avant de pousser le manifest multi-arch sur GHCR et Docker Hub.
La provenance de chaque binaire de release est attestée par actions/attest-build-provenance (Sigstore OIDC, keyless), ce qui produit des attestations de provenance SLSA (Build niveau 2) vérifiables via gh attestation verify. La migration depuis slsa-framework/slsa-github-generator vers actions/attest-build-provenance a eu lieu en 0.7.1. Chaque release publie aussi un SBOM SPDX (perf-sentinel-sbom.spdx.json, généré par Syft et attesté sous le prédicat SPDX) et embarque les données cargo-auditable dans chaque binaire, pour que cargo audit bin fonctionne sur l'artefact livré. Voir Chaîne d’appro..
7. Release du chart Helm
Le tag du chart est enveloppé dans le même genre de commande qui échoue fermé que le tag du binaire :
scripts/release-chart.sh chart-vA.B.CIl lance tous les pre-checks (branche, arbre propre, identité de signature, sync remote, absence du tag) et le gate de version (scripts/check-helm-tag-version.sh). Il lance aussi un gate image GHCR qui refuse de tagger tant que l'image du daemon épinglée par le chart (ghcr.io/institut-du-numerique-responsable/perf-sentinel:<appVersion>) n'est pas publiée, pour qu'un helm install ne tire jamais une image manquante. Attendez que release.yml publie cette image (typiquement 5 à 10 minutes après l'étape 6), puis lancez le script. --dry-run vérifie les gates, --yes saute la confirmation, et --skip-image-check contourne le gate image. Le tag est toujours signé.
Le repli manuel, une fois l'image GHCR en ligne :
git tag -s chart-vA.B.C -m chart-vA.B.C
git push origin chart-vA.B.CL'un ou l'autre chemin déclenche .github/workflows/helm-release.yml. Le workflow valide le tag du chart contre Chart.yaml via scripts/check-helm-tag-version.sh, empaquette le chart et le pousse sur GHCR comme artefact OCI, et le signe avec cosign en keyless. Il atteste aussi la provenance de build SLSA et un SBOM SPDX (tous deux vérifiables via gh attestation verify) et crée la GitHub Release en brouillon. Publiez le brouillon une fois que vous l'avez relu.
8. Publier une révision du dashboard Grafana, s'il a changé
Le dashboard publié sur grafana.com/grafana/dashboards est une photo, pas un miroir. Il ne bouge que si quelqu'un téléverse une révision. Une release qui touche les panneaux ou les métriques qu'ils lisent laisse donc le catalogue périmé tant que cette étape n'est pas faite.
Vérifiez si c'est le cas, par rapport au tag précédent :
git diff vA.B.B..vA.B.C -- examples/grafana-dashboard.jsonAucune sortie, rien à publier. Sinon, téléversez le fichier chez Grafana Labs, dans votre profil sous les dashboards de l'organisation, comme une nouvelle révision du dashboard existant et non comme un nouveau dashboard, puis notez la révision dans les notes de release. Le JSON porte déjà le bloc __inputs dont le catalogue a besoin, ne le retirez pas.
Deux changements imposent une révision même si aucun panneau n'a bougé : une métrique lue par ce dashboard renommée ou supprimée, et une nouvelle règle [thresholds] dont le panneau de gate manquerait. Les deux viennent de l'application et non du chart, lisez donc le CHANGELOG du binaire plutôt que celui du chart pour trancher.
Une release qui donne une nouvelle dépendance à l'un des deux dashboards déplace aussi son plancher de compatibilité, et rien ne le vérifie à votre place. Sur examples/grafana-dashboard.json, la pastille Compatibility nomme deux métriques dont la présence date le daemon : un panneau qui se met à lire une métrique ajoutée par cette release réclame cette métrique comme nouvelle sentinelle haute. Sur examples/grafana-findings-dashboard.json, la même pastille porte le plancher sous forme d'expression régulière sur les mineures inférieures, donc oublier de le déplacer affiche comme compatible un daemon cassé. Ne le déplacez que si la dépendance est réelle, une route, un paramètre de requête, un champ ou un label dont les panneaux ne peuvent pas se passer, jamais à chaque release. La rangée History (Hub) lit PerfSentinelHub et non le daemon, donc un changement de ce côté ne déplace jamais le plancher du daemon : sa propre dépendance est la version du Hub que nomme la description de son panneau.
Les variantes françaises sous examples/FR/ sont générées depuis les deux fichiers anglais et jamais modifiées à la main. Une release qui a changé l'un des deux tableaux de bord anglais les régénère avec python3 scripts/translate-dashboards-fr.py, et --check confirme qu'elles sont à jour. Seul l'aperçu anglais est publié chez Grafana Labs.
Ce que fait le workflow de release
release.yml exécute ces jobs à chaque push de tag v* :
- check-versions :
scripts/check-tag-version.sh "${GITHUB_REF_NAME}"rejette toute divergence entre le tag et les fichiers de version du workspace (Cargo.toml uniquement, voir l'en-tête du script pour le périmètre exact). - build (matrice) : construit
perf-sentinelpourlinux-amd64-musl,linux-arm64-musl,macos-arm64etwindows-amd64. Le binaire Linux arm64 est construit nativement sur un runnerubuntu-24.04-arm(et non via l'outil Dockercross), et chaque binaire est produit aveccargo auditable buildpour embarquer sa liste de dépendances et permettrecargo audit bin. Les variantes musl utilisentmimalloccomme allocateur global (voir 07 · CLI, config & release). - sbom : Syft lit la liste de dépendances depuis les données
cargo-auditableembarquées dans le binaire Linux amd64, émet un SBOM SPDX, et l'atteste sous le prédicat SPDX, en miroir du job SBOM du chart. - release : rassemble les artefacts (binaires plus le SBOM), calcule les checksums SHA-256, atteste la provenance de build via Sigstore (OIDC keyless), et crée la release GitHub avec tous les assets et les notes tirées de
CHANGELOG.md. - publish-crate : publie
perf-sentinel-corepuisperf-sentinelsur crates.io, attend que l'index se mette à jour, fait échouer le workflow en cas de dépassement du délai au lieu d'émettre un simple avertissement. - docker : construit l'image multi-arch, la scanne avec Trivy (
exit-code: 1sur CVE HIGH ou CRITICAL), envoie le SARIF, puis la pousse sur GHCR et Docker Hub.
Le release gate n'est jamais invoqué depuis ce workflow. Si une PR ajoute une étape gate à release.yml, la rejeter. Le gate valide contre un cluster k3d réel que la CI ne peut pas reproduire, et une vérification automatisée vide dégraderait silencieusement la garantie du gate.
Dépannage
Le gate échoue avec "no PASS entry" alors que le lab vient de tourner. La ligne issue de record-validation.sh est imprimée sur stdout, pas ajoutée automatiquement. Ouvrir release-gate/lab-validations.txt et coller la ligne à la main. Vérifier que les séparateurs sont des tabulations, pas des espaces, avec cat -t release-gate/lab-validations.txt (sur macOS) ou cat -A (sur GNU).
Le gate échoue avec "is N days old". Une validation périmée signifie typiquement que la branche release a accumulé des commits depuis le run lab. Relancer le lab sur le dernier commit, ajouter une ligne PASS fraîche, et retenter le gate.
Le gate imprime warning: ignoring malformed line N. Un ajout antérieur a été corrompu (tabulations cassées, mauvais nombre de colonnes). Ouvrir le ledger, trouver la ligne N, corriger le séparateur ou le nombre de colonnes. Le gate continue de traiter le reste du fichier.
check-tag-version.sh échoue sur crates/sentinel-cli/Cargo.toml. Le script vérifie à la fois le version du workspace et le pin intra-workspace sur perf-sentinel-core. Les deux doivent être incrémentés ensemble.
publish-crate expire en attendant l'index crates.io. Le job échoue strictement pour éviter une release dans un état partiel. Attendre 5 minutes, puis relancer le job en échec depuis l'UI GitHub Actions. Si la crate est déjà sur l'index, le job le détectera et finira.
Le scan Trivy signale un CVE HIGH ou CRITICAL. Le job docker bloque. Vérifier si une reconstruction de l'image de base résout le problème (docker pull puis relancer le workflow). Si le CVE est dans une dépendance Cargo, mettre à jour la dépendance dans une nouvelle PR et publier une release patch. Ne pas contourner le scan.
Référence du format de ledger
Le ledger à release-gate/lab-validations.txt est séparé par des tabulations, append-only, et ignore les lignes commençant par # ou vides. Chaque entrée a quatre colonnes :
<version>\t<lab_commit_sha>\t<YYYY-MM-DD>\t<PASS|FAIL>version: correspond à la forme du tag, avec levinitial (par exemplev0.7.2). Le gate compare cette colonne à son argument--versionlittéralement.lab_commit_sha: SHA court de la HEAD du repo lab au moment de la validation, utilisé pour reproduire l'état du lab si une question se pose plus tard.date: date calendaire UTC à laquelle la validation s'est terminée. Doit être strictementYYYY-MM-DD. Le gate rejette tout autre format (les chaînes floues commenowne sont pas acceptées).verdict:PASSouFAIL. Le gate n'accepte quePASS.
Les entrées FAIL ne sont pas strictement requises (le gate les traite comme une entrée manquante), mais les enregistrer dans le ledger garde une trace des raisons pour lesquelles une version candidate a été retenue.