From 3ca4839a0289ba3e44e578ba37cf38d186969cd9 Mon Sep 17 00:00:00 2001 From: Johan LEROY Date: Wed, 23 Sep 2026 10:44:51 +0200 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20ne=20ram=C3=A8ne=20jamais=20un?= =?UTF-8?q?=20environnement=20sur=20un=20commit=20plus=20ancien?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les CI de deux push rapprochés peuvent finir dans le désordre. deploy.yml faisait alors reset --hard sur un GITHUB_SHA plus ancien que celui déjà déployé, et le groupe concurrency deploy- ne garde qu'un job en attente : un troisième arrivé annulait le précédent, qui n'était jamais déployé. - un commit qui précède celui déjà déployé est ignoré, avec une annotation dans le run ; - le groupe concurrency cède la place à un flock posé dans le clone de la VM, tenu du fetch jusqu'à la sonde de santé : les déploiements passent un par un, aucun n'est annulé ; - les trois étapes n'en font plus qu'une, le verrou tombant avec le shell qui l'a posé ; les journaux restent découpés par ::group::. ADR 0014 et 50-cicd.md décrivent les deux gardes. --- .github/workflows/deploy.yml | 29 ++++++++++++------- ...ne-ci-unique-et-deploiement-conditionne.md | 6 +++- docs/architecture/50-cicd.md | 11 +++++-- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 05db884..40067df 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -5,6 +5,9 @@ # déploie `GITHUB_SHA`, le commit testé, pas la pointe de branche du moment (ADR 0014). # Piège : jamais de déclencheur `pull_request` ici. Sur un dépôt public, une PR de fork # exécuterait son code sur la machine de production (ADR 0009) - job deploy. +# Piège : les CI de deux push finissent parfois dans le désordre. Un commit qui précède celui déjà +# déployé est ignoré, et le verrou est un `flock` sur la VM plutôt qu'un groupe `concurrency` : +# GitHub n'y garde qu'un job en attente, et le suivant l'évince sans bruit. name: Déploiement @@ -20,9 +23,6 @@ jobs: name: Déploie sur la VM runs-on: [self-hosted, linux, eni-g3] timeout-minutes: 30 - concurrency: - group: deploy-${{ github.ref_name }} - cancel-in-progress: false environment: name: ${{ github.ref_name == 'main' && 'prod' || 'rec' }} url: ${{ github.ref_name == 'main' && 'https://enervision.local' || 'https://rec.enervision.local:8443' }} @@ -30,29 +30,38 @@ jobs: ENVIRONNEMENT: ${{ github.ref_name == 'main' && 'prod' || 'rec' }} PORT_HTTPS: ${{ github.ref_name == 'main' && '443' || '8443' }} steps: - - name: Aligne le dossier de l'environnement sur le commit testé + # Un seul step : le verrou tombe avec le shell qui l'a posé. + - name: Déploie le commit testé, sans jamais reculer run: | cd "/srv/enervision/${ENVIRONNEMENT}" + exec 9>"$(git rev-parse --git-dir)/verrou-deploiement" + flock 9 + + echo "::group::Aligne le dossier de l'environnement sur le commit testé" git fetch --quiet origin "${GITHUB_REF_NAME}" + deploye="$(git rev-parse HEAD)" + if [ "$deploye" != "$GITHUB_SHA" ] && git merge-base --is-ancestor "$GITHUB_SHA" "$deploye"; then + echo "::notice::${GITHUB_SHA:0:7} précède le commit déjà déployé (${deploye:0:7}) : rien à déployer." + exit 0 + fi git checkout --quiet "${GITHUB_REF_NAME}" git reset --quiet --hard "${GITHUB_SHA}" git log -1 --format='%h %s' + echo "::endgroup::" - - name: Reconstruit et redémarre la stack - run: | - cd "/srv/enervision/${ENVIRONNEMENT}" + echo "::group::Reconstruit et redémarre la stack" make stack-up + echo "::endgroup::" - - name: Attend que l'API réponde derrière le proxy - run: | + echo "::group::Attend que l'API réponde derrière le proxy" for _ in $(seq 1 36); do if curl --fail --silent --insecure "https://localhost:${PORT_HTTPS}/api/v1/health/ready"; then exit 0 fi sleep 5 done + echo "::endgroup::" echo "L'API ne répond pas après 3 minutes" >&2 - cd "/srv/enervision/${ENVIRONNEMENT}" compose="docker compose -f docker-compose.yml -f docker-compose.prod.yml" $compose ps $compose logs --tail=50 backend proxy diff --git a/docs/adr/0014-pipeline-ci-unique-et-deploiement-conditionne.md b/docs/adr/0014-pipeline-ci-unique-et-deploiement-conditionne.md index a7100a2..97681cc 100644 --- a/docs/adr/0014-pipeline-ci-unique-et-deploiement-conditionne.md +++ b/docs/adr/0014-pipeline-ci-unique-et-deploiement-conditionne.md @@ -44,7 +44,11 @@ passent en `workflow_call` et n'ont plus de déclencheur propre. exiger dans les règles de branche** : un composant sauté par son filtre ne publie aucun check interne, qui resterait « en attente » s'il était exigé. 4. **`deploy`.** Il appelle `deploy.yml`, sur les seuls push, et seulement si `CI ok` a réussi. - `deploy.yml` aligne le dossier de l'environnement sur `GITHUB_SHA`, le commit testé. + `deploy.yml` aligne le dossier de l'environnement sur `GITHUB_SHA`, le commit testé, sauf + si ce commit précède celui déjà déployé : les CI de deux push peuvent finir dans le désordre, + et un environnement ne recule jamais. Les déploiements d'un même environnement passent un par + un sous un verrou `flock` sur la VM, et non dans un groupe `concurrency`, où GitHub ne garde + qu'un job en attente et annule le précédent quand un troisième arrive. `deploy.yml` n'a toujours **aucun déclencheur `pull_request`** : il n'accepte que `workflow_call` et `workflow_dispatch`, dans l'esprit de l'ADR 0009. diff --git a/docs/architecture/50-cicd.md b/docs/architecture/50-cicd.md index deed4a9..5f6feab 100644 --- a/docs/architecture/50-cicd.md +++ b/docs/architecture/50-cicd.md @@ -111,8 +111,15 @@ et non sur la pointe de branche du moment, qui a pu avancer pendant la CI. Il la migrations Alembic dans le conteneur backend, et attend jusqu'à trois minutes que `/api/v1/health/ready` réponde derrière le proxy. Cette sonde ne vérifie que la connexion à la base et la présence de TimescaleDB : sans la migration, le déploiement serait vert sur une base -sans schéma, et c'est pourquoi `make stack-up` la porte. Un groupe de concurrence par branche, -sans annulation, empêche deux déploiements simultanés du même environnement. +sans schéma, et c'est pourquoi `make stack-up` la porte. + +Les CI de deux push rapprochés peuvent finir dans le désordre. Deux gardes empêchent un +environnement de reculer ou de sauter un commit : + +- un commit qui **précède** celui déjà déployé est ignoré, avec une annotation dans le run ; +- les déploiements d'un même environnement passent un par un sous un verrou `flock` posé dans le + clone de la VM. Un groupe `concurrency` ne convenait pas : GitHub n'y garde qu'un job en + attente, et un troisième arrivé l'annule sans erreur. Le job ne fait pas de `actions/checkout` dans son espace de travail, et c'est voulu : le dossier de l'environnement est stable, hors du runner, parce que `.env`, certificats et volumes doivent