La plupart des pipelines défaillants échouent pour les mêmes quelques raisons. La logique se cache dans le YAML. Un échec est avalé. Un fichier temporaire survit à la tâche. Un jeton peut écrire plus que nécessaire.
Cet article présente dix règles que j’utilise pour éviter ces échecs. Chaque règle renvoie à du code fonctionnel et testé dans rzv-ci-plb, le guide CI de Runzivo, mon dépôt compagnon à code source ouvert. Les trois prochains articles approfondissent les scripts, la gestion des erreurs et la sécurité.

1. Gardez le YAML court et mettez la logique dans des scripts
Erreur : un bloc run: de 40 lignes avec des boucles, des reprises et des vérifications if. Vous ne pouvez ni l’exécuter en local, ni bien l’analyser, ni le tester.
À faire : gardez en ligne les commandes de trois lignes ou moins. Déplacez dans un script tout ce qui comporte des conditions, des boucles, des reprises ou du nettoyage. Chaque script doit aussi pouvoir s’exécuter en dehors de GitHub Actions.
La limite de trois lignes est ma convention, pas une règle de GitHub. Elle s’inspire du Google Shell Style Guide, qui recommande de passer à un langage plus structuré pour les scripts de plus de 100 lignes ou dont le flux de contrôle n’est pas simple.
Exemple : examples/nodejs/ci/01-test.sh.
2. Échouez clairement et conservez le code de sortie
Erreur : || true ou continue-on-error: true sans commentaire. La tâche devient verte et le bogue est livré.
À faire : commencez les scripts Bash par set -Eeuo pipefail. Installez un trap ERR qui indique le fichier, la ligne et la commande. Échouez avec un code de sortie précis. Si vous devez ignorer un échec, écrivez pourquoi à côté de la ligne.
Exemple : die et errors_install_traps dans scripts/lib/03-errors.sh.
3. Nettoyez toujours
Erreur : des répertoires temporaires, des conteneurs et des ressources de test laissés derrière lorsqu’une étape échoue en cours de route.
À faire : enregistrez le nettoyage au moment où vous créez chaque ressource. Exécutez-le depuis un trap EXIT qui conserve l’état de sortie d’origine, et gérez aussi INT et TERM. Un trap ne peut pas s’exécuter après un SIGKILL ou la perte d’un exécuteur ; gardez donc vos ressources jetables.
Exemples : scripts/lib/04-cleanup.sh et le nettoyage des conteneurs dans examples/docker/ci/02-test.sh.
4. Utilisez des chemins absolus
Erreur : cd ../.. et des chemins relatifs qui ne fonctionnent que depuis un seul répertoire.
À faire : utilisez $GITHUB_WORKSPACE dans les flux de travail. Dans les scripts, résolvez les chemins à partir de ${BASH_SOURCE[0]}. Mettez chaque chemin entre guillemets. Validez les chemins qui proviennent d’une entrée.
Ces deux lignes ouvrent scripts/ci/01-validate.sh :
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd -P)"Chemins d’entrée : path_within dans scripts/lib/02-paths.sh.
5. Journalisez de la même façon partout
Erreur : un mur de sortie sans noms d’étapes et un message d’erreur qui dit seulement « échec ».
À faire : utilisez [INFO], [WARN] et [ERROR] avec des horodatages UTC. Utilisez les commandes de flux de travail ::group:: et ::error:: de GitHub, et écrivez un court résultat dans $GITHUB_STEP_SUMMARY. N’imprimez jamais un identifiant.
Exemple : scripts/lib/01-logging.sh.
6. Accordez le moindre privilège
Erreur : aucune clé permissions:, des actions tierces épinglées à une étiquette mobile, et du texte d’événement collé directement dans du code shell.
À faire : définissez permissions: contents: read en haut du flux de travail. Épinglez les actions tierces à un SHA de commit complet. Transmettez les valeurs non fiables par des variables d’environnement. L’article 4 traite de cela en détail.
Voir docs/security.md.
7. Rendez les tâches reproductibles et sûres à relancer
Erreur : des versions d’outils non épinglées, aucun délai d’expiration et deux déploiements qui se font concurrence.
À faire : épinglez les versions des outils et leurs sommes de contrôle, et validez les fichiers de verrouillage. Définissez timeout-minutes. La valeur par défaut d’une tâche est de six heures, selon la référence de la syntaxe des flux de travail. Ajoutez un groupe concurrency. Ne relancez que les échecs dont vous savez qu’ils sont temporaires.
Exemple : les versions épinglées et les empreintes SHA-256 dans scripts/ci/00-install-tools.sh.
8. Séparez les tests, la compilation et le déploiement
Erreur : une seule tâche qui teste une demande de tirage (pull request) et détient aussi les identifiants de production.
À faire : les vérifications des demandes de tirage s’exécutent sans secrets de déploiement. Les déploiements s’exécutent une fois les vérifications réussies, depuis main, au moyen d’un environnement GitHub avec des règles de protection lorsque votre forfait les prend en charge. Je nomme les environnements selon l’étape, par exemple 04-prd-01 pour le premier environnement de production.
Voir la barrière de déploiement dans docs/environments.md.
9. Validez chaque flux de travail automatiquement
Erreur : découvrir une coquille dans le YAML après la fusion.
À faire : exécutez des analyseurs et des tests à chaque demande de tirage. Dans le dépôt, deux scripts s’en chargent et s’exécutent de la même façon en local :
bash scripts/ci/01-validate.sh # actionlint, shellcheck, shfmt, zizmor
bash scripts/ci/02-test.sh # Bats tests and every exampleactionlint vérifie les fichiers de flux de travail et exécute ShellCheck sur les blocs run:. ShellCheck et shfmt couvrent les scripts. zizmor recherche les problèmes de sécurité. Bats teste les scripts, y compris les chemins d’échec.
Exemples : run_lint dans scripts/ci/01-validate.sh et les tests dans tests/bats/.
10. Concevez pour la réutilisation et le débogage
Erreur : les mêmes 30 étapes copiées dans chaque dépôt.
À faire : utilisez des flux de travail réutilisables pour des tâches entières et des actions composites pour les étapes répétées. Téléversez les rapports de test comme artefacts lorsqu’une tâche échoue.
Exemples : ci.yml qui appelle des flux de travail réutilisables, l’action composite setup-ci et le téléversement d’artefacts en cas d’échec.
Deux règles de nommage
Aucune n’est une règle de GitHub. Les deux rendent un pipeline plus facile à lire.
- Scripts numérotés. Chaque script et chaque fichier Bats commence par un nombre à deux chiffres selon l’ordre d’exécution :
01-build.shavant02-test.sh.00est réservé à l’installation. Voir scripts numérotés. - Environnements numérotés. Les identifiants suivent le format
<NN>-<env>-<NN>:01-dev-01,02-qat-01,03-stg-01,04-prd-01, avec les codesdev,qat,stgetprd. Les listes sont ainsi triées du développement à la production. Voirdocs/environments.md.
Un flux de travail qui suit les dix règles
Le flux de travail ne fait que coordonner. Le script fait le travail. Voici le fichier lint.yml du dépôt, tel quel :
name: Lint
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
defaults:
run:
shell: bash
jobs:
lint:
name: actionlint, shellcheck, shfmt
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install tools
uses: ./.github/actions/setup-ci
with:
tools: actionlint shellcheck shfmt
- name: Lint
run: bash "${GITHUB_WORKSPACE}/scripts/ci/01-validate.sh" lintLes exemples par langage se trouvent dans examples/nodejs, examples/python, examples/golang et examples/docker.
Les exécuter sur Runzivo
Les exemples s’exécutent sur les exécuteurs hébergés par GitHub. Ils s’exécutent aussi sur Runzivo, qui est en accès anticipé. Runzivo attribue à chaque tâche admissible une microVM neuve à usage unique. Pour l’essayer, changez seulement runs-on pour l’étiquette documentée :
runs-on: rzv-ubuntu-24.04-x64-2vcpu-8gbLe fichier runzivo.yml du dépôt lit l’étiquette dans la variable de dépôt RUNZIVO_RUNNER_LABEL et ne s’exécute que sur push et sur déclenchement manuel. Consultez la documentation de Runzivo.
Dans le dépôt
docs/best-practices.md: les dix normes, chacune liée à du codescripts/lib/:01-logging.sh,02-paths.sh,03-errors.sh,04-cleanup.shscripts/ci/:00-install-tools.sh,01-validate.sh,02-test.sh.github/workflows/:ci.yml,lint.yml,security.yml,runzivo.ymlet un flux de travail par exempledocs/environments.md: les identifiants d’environnement et la barrière de déploiement
La suite de cette série
- Cessez d’écrire du Bash complexe dans le YAML de GitHub Actions
- Gestion des erreurs : traps, nettoyage, journalisation et codes de sortie
- Sécuriser GitHub Actions : permissions, secrets et demandes de tirage non fiables
Chaque exemple de cette série est testé dans rzv-ci-plb, the Runzivo CI playbook, fixé à l’étiquette v0.1.0.