Automation
GitHub Actions : empêcher deux déploiements de production de se chevaucher
Un runbook de production pour diagnostiquer des déploiements GitHub Actions concurrents, sérialiser les écritures, valider la version active et reprendre ou rollbacker sans ajouter une course de plus.
Deux commits sont fusionnés à quelques minutes d’intervalle. Deux workflows GitHub Actions déploient alors le même service en production. Le second run applique la version la plus récente, puis le premier, plus lent, termine après lui et remet l’ancienne version en ligne. Les deux pipelines sont verts, mais la production a reculé sans qu’aucun job nommé « rollback » n’ait été exécuté.
Ce runbook traite le déploiement comme une écriture concurrente sur une ressource partagée. Son objectif est de prouver quelle exécution a modifié quoi, stabiliser la production, puis choisir une stratégie de sérialisation adaptée. Le point de sortie doit être explicite : conserver la version active, redéployer le commit attendu ou rollbacker vers un artefact connu.
Reconstruire la chronologie avant de relancer
Ne relancez pas immédiatement le dernier workflow. Une nouvelle exécution peut rejoindre la même course et effacer les preuves utiles. Relevez d’abord, pour chaque run, le commit, l’artefact, l’environnement, les heures de début et de fin, l’identité de déploiement et la version observée après le job.
target: payments-api-production
expected_commit: <commit-sha>
observed_version: <version-from-runtime>
runs:
- run_id: <older-run-id>
commit: <older-commit-sha>
artifact_digest: <digest>
started_at: <timestamp>
deployment_started_at: <timestamp>
completed_at: <timestamp>
conclusion: success
- run_id: <newer-run-id>
commit: <newer-commit-sha>
artifact_digest: <digest>
started_at: <timestamp>
deployment_started_at: <timestamp>
completed_at: <timestamp>
conclusion: success
evidence:
- github_deployment_history
- cloud_activity_logs
- runtime_version_endpoint
- immutable_artifact_digest L’heure de fin du workflow ne suffit pas. Un job peut préparer un artefact longtemps avant de modifier la cible, ou déclencher un déploiement asynchrone qui se termine après le run GitHub. Cherchez le moment où l’état de production a réellement changé.
Identifier la ressource qui doit être verrouillée
Le verrou ne porte pas nécessairement sur le workflow. Il porte sur la ressource modifiée : un environnement, un slot, un cluster, un namespace, un groupe de ressources ou un tenant. Deux workflows différents qui écrivent sur le même service doivent partager le même groupe de concurrence.
Même verrou requis
Deux branches déploient payments-api en production
Un workflow applicatif et un workflow infrastructure modifient le même slot
Un déploiement manuel et un déploiement automatique ciblent le même environnement
Verrous séparés possibles
Environnements de production indépendants
Services sans ressource, base ou route partagée
Préproduction et production si aucune étape commune n'est modifiée
À vérifier
Jobs réutilisables appelés par plusieurs workflows
Déploiements déclenchés par workflow_run ou repository_dispatch
Scripts qui ciblent un environnement par défaut
Matrices qui convergent vers la même cible Un groupe trop large bloque inutilement des services indépendants. Un groupe trop fin laisse passer la collision. N’utilisez pas seulement le nom de branche si plusieurs branches peuvent atteindre la même production.
Sérialiser les écritures avec concurrency
Placez la concurrence au niveau du job qui écrit sur la cible. Les étapes de build et de test peuvent rester parallèles ; les déploiements vers la même production doivent être sérialisés. Pour une production avec migration, bascule de trafic ou mutation non transactionnelle, évitez d’annuler un job déjà en cours.
name: deploy-production
on:
workflow_dispatch:
push:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<pinned-version>
- run: ./ci/build-and-test.sh
deploy:
needs: build-and-test
runs-on: ubuntu-latest
environment: production
concurrency:
group: payments-api-production
cancel-in-progress: false
queue: single
steps:
- uses: actions/checkout@<pinned-version>
- run: ./ci/deploy.sh
- run: ./ci/verify-production.sh Avec queue: single, le groupe limite l’exécution concurrente et conserve au plus une exécution en attente ; une nouvelle exécution remplace l’attente plus ancienne. Cette stratégie convient quand la production doit converger vers le dernier état approuvé. Utilisez queue: max si chaque changement doit impérativement s’exécuter dans l’ordre, par exemple pour une suite de migrations non compactables. La file peut alors accumuler jusqu’à sa limite : chaque run doit revalider sa base avant d’écrire.
cancel-in-progress: true convient mieux à des calculs idempotents ou à des previews jetables. En production, annuler un job au milieu d’une migration, d’une rotation ou d’un transfert de trafic peut laisser un état partiel. Si l’annulation est retenue, le script doit gérer le signal d’arrêt et vérifier l’état réel avant toute reprise.
Ajouter un garde-fou au plus près de la cible
La concurrence GitHub réduit les collisions, mais elle ne couvre pas un déploiement lancé hors GitHub ni une écriture asynchrone qui survit au job. Ajoutez une seconde barrière : environnement protégé, lease dans le moteur de déploiement, précondition sur la version active ou verrou porté par la plateforme.
set -euo pipefail
EXPECTED_BASE="<version-that-was-validated>"
ACTIVE_VERSION="$(./ops/read-active-version.sh)"
if [ "$ACTIVE_VERSION" != "$EXPECTED_BASE" ]; then
echo "Production changed after this run was prepared."
echo "Expected base: $EXPECTED_BASE"
echo "Active version: $ACTIVE_VERSION"
exit 42
fi
./ops/acquire-deployment-lease.sh payments-api-production
./ops/deploy-immutable-artifact.sh <artifact-digest> Cette précondition joue le rôle d’un contrôle optimiste : le run refuse d’écrire si sa base n’est plus celle qui a été validée. Elle est particulièrement utile lorsqu’un approbateur garde un job en attente pendant qu’un autre changement atteint la production.
Rendre chaque déploiement attribuable
Un tag latest ou un numéro de build local au workflow ne suffit pas. L’artefact doit être immuable et relié au commit, au run et à la cible. La production doit exposer au minimum une version lisible par une sonde ou un inventaire.
{
"environment": "production",
"service": "payments-api",
"commit": "<commit-sha>",
"workflowRunId": "<run-id>",
"artifactDigest": "sha256:<digest>",
"deployedAt": "<timestamp>",
"previousVersion": "<version>",
"validation": {
"health": "passed",
"smokeTest": "passed",
"errorBudgetSignal": "stable"
}
} Conservez cette preuve dans l’historique de déploiement et dans les logs de la plateforme. Le verdict success du workflow doit signifier que la version attendue est active et validée, pas seulement que la commande de déploiement a été acceptée.
Décider quoi faire pendant l’incident
Après avoir stoppé les nouvelles écritures, comparez la version active avec le commit attendu. Annulez les runs en attente devenus obsolètes. Ne relancez qu’un artefact immuable déjà construit ; reconstruire le même commit pendant l’incident introduit une variable supplémentaire.
Conserver la version active
Elle correspond au commit approuvé le plus récent
Les probes et indicateurs applicatifs sont stables
Aucun déploiement plus ancien ne peut encore écrire
Redéployer le commit attendu
La production exécute un artefact plus ancien ou inconnu
Le digest attendu est disponible et vérifié
La cible est verrouillée et les runs obsolètes sont annulés
Rollbacker
Le dernier commit attendu échoue après déploiement
L'artefact précédent est connu et compatible avec l'état des données
La validation post-rollback est prête
Bloquer la reprise
La version active n'est pas attribuable
Un déploiement asynchrone peut encore terminer
Une migration irréversible est en cours
Le verrou ne couvre pas tous les chemins d'écriture Un rollback ne doit pas être une relance aveugle de l’ancien workflow. C’est un nouveau changement contrôlé vers un artefact identifié, avec les mêmes barrières de concurrence et la même validation.
Valider la correction sans recréer la course
Testez la nouvelle règle avec trois exécutions courtes vers une cible de validation : une active, une en attente, puis une troisième plus récente. Vérifiez quel run reste en attente, qu’aucune écriture ne se chevauche et que les logs rendent la décision compréhensible. Testez aussi une exécution manuelle et tout workflow réutilisable qui atteint la même cible.
En production, surveillez le premier déploiement sérialisé jusqu’à la validation applicative. Confirmez le commit, le digest, la version active, les erreurs et le comportement des routes ou migrations. Gardez un moyen de geler les nouveaux déclenchements sans supprimer l’historique.
Conclusion
Un chevauchement de pipelines est un problème de concurrence, pas seulement un défaut de CI. La correction durable consiste à verrouiller la ressource réellement partagée, garder les builds parallèles, sérialiser les écritures, refuser les bases devenues obsolètes et prouver la version active après chaque déploiement.
La décision finale reste opérationnelle : conserver la version en ligne, redéployer l’artefact attendu ou rollbacker vers le précédent. Dans les trois cas, aucun run plus ancien ne doit pouvoir reprendre la main après la validation.