GitHub Actions tue lentement les équipes d’ingénierie
(iankduncan.com)- Largement utilisé parce qu’il s’agit d’une CI incluse par défaut dans le repo, mais son inefficacité structurelle et son expérience utilisateur instable nuisent à la productivité des développeurs
- Le chargement lent du visualiseur de logs et les plantages du navigateur, ainsi que la syntaxe YAML complexe et les erreurs d’expression, entraînent un débogage répétitif
- Une architecture où l’on ne possède pas les ressources de calcul montre ses limites en matière de performance, de scalabilité et de contrôle de l’environnement
- En cherchant à contourner ces problèmes, on finit souvent par reconstruire la CI elle-même avec du YAML complexe ou d’énormes scripts Bash
- À l’inverse, Buildkite propose, grâce à une structure YAML simple, des agents auto-hébergeables et une expérience de logs pragmatique, une alternative de CI soutenable sur le long terme
Les problèmes de GitHub Actions
- Le visualiseur de logs de GitHub Actions est inefficace : même pour vérifier une simple erreur, il faut plusieurs clics et chargements de page
- En cas d’échec d’un build, il faut passer de la page récapitulative des checks à la page d’exécution du workflow, puis à la page du job, puis cliquer sur une étape repliée, soit 3 à 4 changements de page, chacun avec son propre temps de chargement
- Sur de gros logs de build, il fait planter le navigateur de manière répétée, et l’utilisation de la recherche peut bloquer Chrome de façon reproductible
- Sur les logs longs, le défilement lui-même cesse de fonctionner, au point qu’il faut télécharger l’artefact de log brut et l’ouvrir dans un éditeur de texte
- Le bouton retour renvoie non pas à la page de PR d’origine, mais vers des pages d’interface GitHub Actions imprévisibles, remplissant l’historique du navigateur d’URL Actions
- Un processus de débogage improductif
- Pour vérifier des variables d’environnement, on ajoute une étape
run: envpuis on push à nouveau, ce qui crée une boucle de feedback de 20 minutes ; pour un simple changement d’une ligne, ce processus peut être répété une dizaine de fois - À force de répéter ces boucles de 20 minutes, une journée entière de travail finit absorbée par l’attente de la CI
- Pour vérifier des variables d’environnement, on ajoute une étape
- Les limites structurelles de YAML
- Le YAML de GitHub Actions est une forme particulière qui combine son propre langage d’expression, un modèle d’objets de contexte et des règles d’interpolation de chaînes
- Dans une expression
${{ }}, une simple erreur de guillemet peut faire attendre 4 minutes avant même que le runner ne démarre, pour découvrir ensuite qu’une chaîne a été perdue - La syntaxe des expressions se situe dans un entre-deux (liminal space) : trop complexe pour être de la simple configuration, trop limitée pour être un vrai langage de programmation
- On l’apprend non pas par la documentation, mais à travers l’expérience de l’échec
- Les risques de sécurité de la Marketplace
- Quand on importe une action externe avec
uses:, on accorde à un tiers non vérifié un accès au dépôt, aux secrets et à l’environnement de build - Il est possible d’épingler un SHA, mais peu de gens le font réellement ; et même dans ce cas, on exécute du code opaque non relu avec des droits d’accès à
GITHUB_TOKEN - La Marketplace mélange des actions maintenues par la communauté de qualité variable, composées pour la plupart de scripts shell et de Dockerfile
- La gestion des dépendances manque de transparence, avec un risque d’exécution de code non sûr
- Quand on importe une action externe avec
- Les contraintes de l’environnement de calcul
- Les runners par défaut de GitHub Actions sont des runners mutualisés appartenant à Microsoft, lents, limités en ressources et impossibles à personnaliser de manière significative
- Le coût des runners plus puissants est du niveau où la finance vous envoie une demande de réunion pour “en discuter”, sans pour autant donner le contrôle de l’environnement
- Il existe au moins six startups — Namespace, Blacksmith, Actuated, Runs-on, BuildJet notamment — spécialisées uniquement dans l’accélération des runners GitHub Actions, ce qui prouve à lui seul les insuffisances de l’environnement de calcul par défaut
- Mettre en place un self-hosted runner résout le problème du calcul, mais laisse intacts tous les autres : expressions YAML, modèle d’autorisations, Marketplace, visualiseur de logs, etc.
Des problèmes de détail, mais qui s’accumulent
actions/cache: les clés de cache sont déroutantes, les cache misses surviennent silencieusement, l’éviction reste opaque, et le temps passé à déboguer le cache dépasse celui qu’il est censé faire gagner- Workflows réutilisables : impossibilité de les imbriquer au-delà d’une certaine profondeur, pas d’accès propre au contexte du workflow appelant, pas de test isolé possible
- Le modèle de permissions
GITHUB_TOKEN:permissions: write-allest beaucoup trop large, et les autorisations fines forment un labyrinthe à cause des interactions entre paramètres au niveau du dépôt, du workflow et du job - Le contrôle de la concurrence (
concurrency) : annuler les exécutions en cours sur la même branche se fait en une ligne, mais aucun contrôle fin au-delà n’est prévu - Impossible d’utiliser des secrets dans les conditions
if: une exécution conditionnelle commeif: secrets.DEPLOY_KEY != ''est impossible ; c’est raisonnable du point de vue sécurité, mais cela oblige à contourner le problème quand on veut un workflow qui fonctionne à la fois sur un fork et sur le dépôt principal
Le piège du « faisons simplement un script Bash »
- Les ingénieurs fatigués du YAML de CI sont tentés de remplacer toute la logique par des scripts bash dans
run:, mais avec le temps s’y ajoutent conditions, fonctions, parsing d’arguments et parallélisation - Trois mois plus tard, on se retrouve avec 800 lignes de bash qui réimplémentent le parallélisme des jobs avec
waitet des fichiers PID, ainsi que leur propre logique de retry et de parsing de sortie - Au final, on n’a pas échappé au système de CI : on a simplement construit à la main en bash un pire système de CI, sans framework de test et que plus personne ne peut suivre
- Bash est adapté comme colle (glue), mais l’utiliser comme système de build ou test harness revient à déplacer la complexité d’un endroit avec garde-fous vers un autre qui en est dépourvu
L’approche alternative de Buildkite
-
Un visualiseur de logs fiable
- Le visualiseur de logs de Buildkite affiche correctement les logs sans faire planter le navigateur, en restituant tel quel le formatage ANSI et celui des frameworks de test
- Grâce à la fonctionnalité Annotation, les étapes de build peuvent afficher directement en Markdown sur la page du build un résumé des échecs de tests, un rapport de couverture ou des liens de déploiement
- Les agents s’exécutant sur votre propre infrastructure, il est possible de se connecter en SSH à la machine de build pour déboguer directement
-
Une structure YAML simple
- Le YAML de Buildkite est une structure de données pure qui décrit le pipeline, en déclarant seulement les étapes, les commandes et les plugins
- Quand une logique réelle est nécessaire, on l’écrit dans un vrai langage de programmation exécutable en local
- Cela maintient clairement la frontière : « l’orchestration dans la configuration, la logique dans le code », précisément la frontière que GitHub Actions brouille
-
Un contrôle total de l’environnement de calcul
- Les agents Buildkite existent sous forme d’un seul binaire et peuvent tourner aussi bien sur votre cloud, on-premise ou sur du matériel personnalisé
- On peut contrôler entièrement le type d’instance, le cache, le stockage local et le réseau, depuis de grosses instances EC2 avec SSD NVMe et 20 Go de cache de couches Docker jusqu’au Raspberry Pi
- Il n’existe pas d’industrie tierce du « Buildkite, mais plus rapide » : il suffit de lancer une machine plus grosse
- Pour un mainteneur individuel qui s’occupe d’une petite bibliothèque open source, l’offre gratuite de GitHub Actions sur les dépôts publics garde toute sa valeur
- Le texte vise surtout les équipes qui exploitent des systèmes de production, où le temps de CI se mesure en heures d’ingénierie perdues chaque semaine et où un build de 45 minutes coûte à la fois en calcul et en main-d’œuvre
- Dans ce contexte, le surcoût opérationnel des agents Buildkite est rapidement amorti
-
Prise en charge des pipelines dynamiques
- Dans Buildkite, les étapes du pipeline sont des données, et un script peut générer (emit) dynamiquement d’autres étapes au runtime puis les téléverser
- Dans un monorepo, cela permet de générer exactement les étapes de build et de test nécessaires à partir des fichiers modifiés, sans matrices codées en dur ni spaghetti de
if: contains(...) - Le
matrix, les conditionsifet les workflows réutilisables de GitHub Actions essaient de s’en approcher, mais finissent par construire une machine de Rube Goldberg dans un langage déclaratif à l’expressivité limitée
-
La simplicité de la structure des plugins
- Structurellement, c’est similaire à la Marketplace de GitHub Actions : on récupère du code depuis des dépôts tiers
- La différence est que les plugins Buildkite sont le plus souvent non pas des images Docker, mais de fins hooks shell, avec une surface réduite qu’on peut lire en entier en quelques minutes
- Comme tout s’exécute sur votre propre infrastructure, c’est l’utilisateur qui maîtrise le blast radius
-
Des détails pensés pour l’expérience utilisateur
- Buildkite permet d’afficher des emojis personnalisés (
:parrot:,:docker:, etc.) à côté des étapes du pipeline ; cela paraît anecdotique, mais montre une attention réelle à l’expérience produit - GitHub Actions donne l’impression d’un produit conçu par comité qui ne s’est jamais demandé : « est-ce agréable à utiliser ? »
- Buildkite permet d’afficher des emojis personnalisés (
Conclusion : comment choisir un système de CI
- GitHub Actions a conquis le marché grâce à son avantage d’outil intégré par défaut, avec la gratuité pour les dépôts publics, une intégration à une plateforme déjà utilisée par tout le monde, et un niveau « assez bon » (Good Enough)
- C’est un peu l’Internet Explorer de la CI : on continue à l’utiliser parce que le coût de migration est réel et que le temps est limité
- Buildkite est supérieur en termes de durabilité d’usage et d’expérience développeur
- Pour un projet open source simple, GitHub Actions suffit, mais dans un environnement de production à grande échelle, Buildkite est plus adapté
- Dans l’histoire des systèmes de CI, la part de marché revient non pas au meilleur système, mais à celui avec lequel il est le plus facile de démarrer
- GitHub Actions est la CI la plus facile pour commencer, Buildkite est la CI la meilleure pour continuer à utiliser, et à long terme c’est ce second critère qui compte
- Si l’outil de CI est structurellement conçu pour consommer le temps des développeurs, le problème n’est pas le développeur, mais bien l’outil lui-même
3 commentaires
On dirait que le vrai problème, c’est surtout que la CI devient elle-même trop complexe.
On dirait que le même article a été publié deux fois. Mais ces temps-ci, ça semble être une combinaison plutôt correcte pour qu’une IA le compose..
Commentaires sur Hacker News
J’ai utilisé plusieurs systèmes de CI. J’ai beaucoup utilisé CircleCI et GitHub Actions, mais je n’arrive pas à la même conclusion que l’auteur
Avant, Jenkins était dédié à Java et Travis à Rails, mais ce type de CI spécialisée a fini dans une impasse. Aujourd’hui, la CI a évolué pour devenir simplement un orchestrateur de workflows
Si je suis passé de CircleCI 2 à GitHub Actions, c’est aussi parce que CircleCI n’a pas bien réussi cette transition. GHA était suffisamment expressif
Le navigateur de logs ou la syntaxe YAML, ce sont des problèmes mineurs. L’important, c’est la maîtrise des ressources de calcul et les pipelines dynamiques : le premier point est possible avec toutes les CI, et le second est l’avantage de Buildkite
Ma conclusion, c’est qu’Actions est en pratique assez bon, et que si je lançais une nouvelle entreprise, j’utiliserais Buildkite ; pour l’open source, j’utiliserais Actions
Si on ne comprend pas le graphe de build, il faut conserver l’état du build incrémental, ce qui provoque des bugs intermittents. C’est pourquoi il faut des systèmes qui comprennent profondément la structure du build, comme UnrealEngine Horde ou UBA
Avec une CI générique, un build peut prendre plus d’une journée
De mon côté, je n’utilise que les bonnes parties de GHA. Par exemple, GitHub est excellent comme répartiteur d’événements, mais médiocre comme orchestrateur de workflows, donc je délègue cette partie à un autre système
Quand on consulte des logs des dizaines de fois par jour, un mauvais confort de lecture fait baisser la productivité. Lire des logs bruts en ignorant les codes d’échappement, c’est vraiment pénible
Je garde les choses simples. Je mets toute l’orchestration dans un script deploy.sh, que j’exécute sur mon Mac local ou sur AWS CodeBuild
Le YAML se résume à une seule ligne,
bash deploy.sh. Tant qu’il y a un conteneur Docker, cela fonctionne de la même façon partout, sur Azure, GitHub Actions, etc.La stratégie essentielle dans tout environnement CI, c’est d’avoir un système de build identique en local
Je commence toujours avec un Makefile. Docker, builds CI, linting : c’est le Makefile qui pilote tout. Quand le projet grossit, on peut passer à d’autres outils, mais la base reste un outil de déclenchement unique
Sur mobile, j’utilise beaucoup Fastlane : ça réduit le boilerplate et apporte une structure. Au final, comme c’est du Ruby, on peut toujours en sortir si nécessaire
(lien vers la documentation GNU Make)
Au fond, cela revient à dire “utilisez simplement des scripts Bash”, sauf qu’on ajoute une complexité inutile
Dans mon entreprise actuelle, on ne peut plus exécuter tout le pipeline en local, ce qui a conduit à une énorme infrastructure CI où l’on relance les builds dix fois pour tester une seule MR
J’ai eu l’impression que cet article ressemblait à une pub pour Nix/Buildkite
Une CI devrait simplement exécuter des scripts ou des cibles de build. La CI ne devrait fournir que l’environnement et la configuration ; la logique doit rester dans le code
Cela donne une indépendance vis-à-vis de la CI, ce qui facilite le passage d’un système à l’autre
GitLab CI gère plutôt bien cette complexité. Les templates et les fonctions de composition des jobs sont excellents, mais le débogage est difficile et la logique conditionnelle échoue parfois de manière imprévisible
.shont eu une migration très facileEn revanche, celles qui avaient tout détaillé dans l’interface ont souffert
Le problème, ce n’est pas la CI/CD en soi, mais la culture qui consiste à programmer dans des fichiers de configuration
La boucle
git commit -m "try fix"suivie de 10 minutes d’attente est bien trop courante. Il manque toujours des environnements CI reproductibles en localSi l’isolation des environnements est définie comme une règle, alors n’importe quel outil peut convenir. Au final, tout est dans l’harmonie entre l’outil et la méthodologie
actaident énormément à reproduire la CI en localLe titre “ça tue les équipes d’ingénierie” est exagéré. GitHub Actions est tout à fait correct
Je le préfère à Bitbucket ou GitLab
La fiabilité de GitHub s’est beaucoup dégradée récemment
actions/checkoutéchoue sans raison, des jobs de release s’exécutent deux fois, ou bien restent en attente pendant 40 minutesJe l’utilise depuis des années, mais la stabilité de base baisse vraiment. Je regrette d’être passé à côté de Buildkite
GitHub Actions fait partie des pires outils de CI que j’ai utilisés (au niveau de Jenkins)
À l’inverse, Buildkite est le meilleur. Grâce aux pipelines dynamiques, on peut générer automatiquement des étapes de relance quand des tests échouent, ou ajuster les tests parallèles selon les changements de code
Le fait de pouvoir utiliser une configuration de machine différente pour chaque job de CI est aussi un gros avantage. Je le recommande vivement
Les gens qui défendent une CI simple basée sur des scripts n’ont probablement pas d’expérience sur de vrais gros projets