GitHub Actions tötet Engineering-Teams langsam
(iankduncan.com)- Wird weithin genutzt, weil es als standardmäßig im Repo enthaltenes CI gilt, aber strukturelle Ineffizienz und eine instabile User Experience senken die Produktivität von Entwicklern
- Langsames Laden des Log-Viewers und Browser-Abstürze sowie komplexe YAML-Syntax und Expression-Fehler führen zu wiederholtem Debugging
- Durch eine Architektur ohne Besitz der Compute-Ressourcen zeigen sich Grenzen bei Performance, Skalierbarkeit und Kontrolle über die Umgebung
- Beim Versuch, viele Probleme zu umgehen, entsteht immer wieder die Situation, dass man mit komplexem YAML oder riesigen Bash-Skripten das CI selbst neu baut
- Im Vergleich dazu bietet Buildkite mit einer einfachen YAML-Struktur, selbst hostbaren Agents und einer praxistauglichen Log-Erfahrung eine langfristig wartbare CI-Alternative
Die Probleme von GitHub Actions
- Der GitHub-Actions-Log-Viewer ist ineffizient: Schon zum Prüfen eines einfachen Fehlers sind mehrere Klicks und Seitenladevorgänge nötig
- Bei einem fehlgeschlagenen Build braucht man von der Check-Zusammenfassungsseite → zur Workflow-Ausführungsseite → zur Job-Seite → bis zum Klick auf den eingeklappten Step 3–4 Seitenwechsel, jeweils mit separatem Laden
- Bei großen Build-Logs bringt er den Browser wiederholt zum Absturz, und beim Nutzen der Suchfunktion friert Chrome reproduzierbar ein
- Bei langen Logs funktioniert sogar das Scrollen nicht mehr, sodass man am Ende das rohe Log-Artefakt herunterladen und in einem Texteditor öffnen muss
- Der Zurück-Button führt nicht zur ursprünglichen PR-Seite zurück, sondern auf unvorhersehbare GitHub-Actions-UI-Seiten, und der Browser-Verlauf füllt sich mit Actions-URLs
- Unproduktiver Debugging-Prozess
- Um Umgebungsvariablen zu prüfen, fügt man einen
run: env-Step hinzu und pusht erneut, was zu einer Feedback-Schleife von 20 Minuten führt; bei einer einzeiligen Änderung wiederholt sich dieser Vorgang schnell ein Dutzend Mal - Wiederholte 20-Minuten-Feedback-Schleifen führen dazu, dass ein ganzer Arbeitstag in Wartezeit auf CI aufgeht
- Um Umgebungsvariablen zu prüfen, fügt man einen
- Strukturelle Grenzen von YAML
- GitHub-Actions-YAML ist eine spezielle Form, in der eine eigene Expression-Sprache, ein Kontext-Objektmodell und Regeln für String-Interpolation kombiniert werden
- Wenn man in einem
${{ }}-Ausdruck nur ein Anführungszeichen falsch setzt, merkt man oft erst nach 4 Minuten Wartezeit auf den Start des Runners, dass ein String verloren gegangen ist - Die Expression-Syntax liegt in einem Grenzbereich (liminal space): zu komplex für Konfiguration, zu eingeschränkt für eine echte Programmiersprache
- Die Syntax wird nicht über die Dokumentation, sondern über Fehlversuche gelernt
- Sicherheitsrisiken des Marketplace
- Wenn über die Syntax
uses:externe Actions eingebunden werden, gibt man unverifizierten Dritten Zugriff auf Repository, Secrets und Build-Umgebung - SHA-Pinning ist zwar möglich, wird aber kaum praktiziert; und selbst dann führt man undurchsichtigen, ungelesenen Code zusammen mit Zugriffsrechten auf
GITHUB_TOKENaus - Im Marketplace stehen von der Community gepflegte Actions sehr unterschiedlicher Qualität nebeneinander, die meist aus Shell-Skripten und Dockerfiles bestehen
- Das Dependency-Management ist intransparent, und unsicherer Code kann ausgeführt werden
- Wenn über die Syntax
- Beschränkungen der Compute-Umgebung
- Die Standard-Runner von GitHub Actions sind gemeinsam genutzte Runner im Besitz von Microsoft, langsam, ressourcenbeschränkt und ohne nennenswerte Anpassbarkeit
- Die Kosten größerer Runner liegen auf einem Niveau, bei dem die Finanzabteilung schon mit „Wir sollten mal reden“-Meetinganfragen reagiert, und trotzdem bleibt die Umgebung unkontrollierbar
- Es gibt mindestens sechs Startups wie Namespace, Blacksmith, Actuated, Runs-on, BuildJet und weitere, die sich ausschließlich darauf spezialisieren, die Langsamkeit von GitHub-Actions-Runnern zu beheben – allein das belegt die Schwächen der Standard-Compute-Umgebung
- Mit einem self-hosted runner lässt sich das Compute-Problem lösen, aber YAML-Expressions, Berechtigungsmodell, Marketplace und Log-Viewer bleiben unverändert problematisch
Detaillierte, aber kumulative Probleme
actions/cache: Cache-Keys sind verwirrend, Cache-Misses passieren stillschweigend, Cache-Eviction ist intransparent, sodass das Debugging des Cachings mehr Zeit kostet, als es spart- Wiederverwendbare Workflows: Lassen sich nicht über eine gewisse Tiefe hinaus verschachteln, bieten keinen sauberen Zugriff auf den Kontext des aufrufenden Workflows und können nicht isoliert getestet werden
- Berechtigungsmodell von
GITHUB_TOKEN:permissions: write-allist viel zu weitreichend, und fein granular gesetzte Berechtigungen werden durch die Interaktion zwischen Einstellungen auf Repository-, Workflow- und Job-Ebene labyrinthartig komplex - Steuerung der Parallelität (concurrency): Das Abbrechen laufender Ausführungen auf demselben Branch ist mit einer Zeile möglich, aber feinere Steuerung darüber hinaus wird nicht unterstützt
- Secrets können nicht in
if-Bedingungen verwendet werden: Bedingte Ausführung wieif: secrets.DEPLOY_KEY != ''ist nicht möglich; sicherheitstechnisch nachvollziehbar, aber für Workflows, die sowohl in Forks als auch im Haupt-Repository funktionieren müssen, sind Workarounds nötig
Die Falle von „Dann nehmen wir einfach Bash-Skripte“
- Entwickler, die von CI-YAML genervt sind, verspüren oft die Versuchung, alles durch
run:-Bash-Skripte zu ersetzen, aber mit der Zeit kommen Bedingungen, Funktionen, Argument-Parsing und Parallelisierung dazu - Drei Monate später hat man 800 Zeilen Bash, die Job-Parallelisierung mit
waitund PID-Dateien nachbaut, inklusive eigener Retry-Logik und eigener Output-Parsing-Logik - Am Ende ist man dem CI-System nicht entkommen, sondern hat sich ein noch schlechteres CI-System in Bash gebaut, ohne Test-Framework und ohne dass irgendjemand ihm folgen kann
- Bash eignet sich als Glue, aber wenn man es als Build-System oder Test-Harness benutzt, verlagert man Komplexität nur von einem Bereich mit Leitplanken in einen ohne
Der alternative Ansatz von Buildkite
-
Stabiler Log-Viewer
- Der Log-Viewer von Buildkite zeigt Logs korrekt an, ohne den Browser abstürzen zu lassen, und rendert ANSI-Farben sowie Formatierung von Test-Frameworks unverändert
- Mit der Funktion Annotation können Build-Steps Zusammenfassungen von Testfehlern, Coverage-Reports, Deployment-Links usw. direkt als Markdown auf der Build-Seite ausgeben
- Da Agents auf der eigenen Infrastruktur laufen, kann man sich per SSH auf die Build-Maschine verbinden und direkt debuggen
-
Einfache YAML-Struktur
- Das YAML von Buildkite ist eine reine Datenstruktur zur Beschreibung der Pipeline, in der nur Steps, Commands und Plugins deklariert werden
- Wenn echte Logik nötig ist, schreibt man Skripte in einer echten Programmiersprache, die lokal ausführbar ist
- Damit bleibt die Grenze „Orchestrierung in der Konfiguration, Logik im Code“ klar erhalten – genau die Grenze, die GitHub Actions verwischt
-
Volle Kontrolle über die Compute-Umgebung
- Buildkite-Agents laufen als einzelnes Binary in der eigenen Cloud, On-Premises oder auf beliebiger Custom-Hardware
- Instanztyp, Caching, lokaler Storage und Netzwerk lassen sich vollständig kontrollieren; unterstützt wird alles von großen EC2-Instanzen mit NVMe-Laufwerken und 20-GB-Docker-Layer-Cache bis hin zum Raspberry Pi
- Es gibt keine Third-Party-Industrie nach dem Motto „Buildkite, aber schneller“ – man nimmt einfach eine größere Maschine
- Für individuelle Maintainer kleiner Open-Source-Bibliotheken bleibt die kostenlose Stufe für öffentliche Repositories von GitHub Actions weiterhin wertvoll
- Die Hauptzielgruppe dieses Artikels sind Teams, die Produktionssysteme betreiben, in denen CI-Zeit als verlorene Engineering-Zeit pro Woche messbar ist und 45-minütige Builds sowohl Compute-Kosten als auch Personalkosten verursachen
- In solchen Umgebungen amortisiert sich der Overhead für den Betrieb von Buildkite-Agents sehr schnell
-
Unterstützung für dynamische Pipelines
- In Buildkite sind Pipeline-Steps Daten, und Skripte können zur Laufzeit dynamisch weitere Steps erzeugen (emit) und hochladen
- In Monorepos lassen sich so nur genau die benötigten Build- und Test-Steps auf Basis geänderter Dateien erzeugen, ohne hart codierte Matrizen oder
if: contains(...)-Spaghetti matrix,if-Bedingungen und wiederverwendbare Workflows in GitHub Actions versuchen das anzunähern, führen aber dazu, dass man eine Rube-Goldberg-Maschine in einer deklarativen Sprache mit zu geringer Ausdruckskraft baut
-
Einfachheit der Plugin-Struktur
- Strukturell ähnelt das dem GitHub-Actions-Marketplace, da ebenfalls Code aus Third-Party-Repositories eingebunden wird
- Der Unterschied ist, dass Buildkite-Plugins meistens keine Docker-Images, sondern schlanke Shell-Hooks (thin shell hooks) sind; die Oberfläche ist kleiner, und man kann den gesamten Code in wenigen Minuten lesen
- Da sie auf der eigenen Infrastruktur laufen, kann der Benutzer den blast radius selbst kontrollieren
-
Detailfunktionen mit Fokus auf User Experience
- Buildkite kann Custom Emojis (
:parrot:,:docker:usw.) neben Pipeline-Steps anzeigen; das wirkt trivial, zeigt aber sorgfältige Aufmerksamkeit für die Produkterfahrung - GitHub Actions wirkt wie ein von einem Komitee entworfenes Produkt, das sich nie gefragt hat: „Macht das eigentlich Spaß?“
- Buildkite kann Custom Emojis (
Fazit: Kriterien für die Wahl eines CI-Systems
- GitHub Actions hat den Markt durch den Vorteil der Standardintegration (default) dominiert: kostenlose Nutzung für öffentliche Repositories, eingebaut in eine Plattform, die ohnehin alle verwenden, und auf dem Niveau von „gut genug“
- Es ist das Internet Explorer unter den CI-Systemen; weil Wechselkosten real sind und Zeit begrenzt ist, wird es weiter genutzt
- Buildkite ist bei langfristiger Nutzbarkeit und Developer Experience überlegen
- Für einfache Open-Source-Projekte reicht GitHub Actions aus, aber in großen Produktionsumgebungen ist Buildkite besser geeignet
- In der Geschichte von CI-Systemen gewinnt Marktanteile nicht das beste System, sondern das System, mit dem man am einfachsten anfangen kann
- GitHub Actions ist das CI, mit dem man am leichtesten startet, Buildkite das CI, mit dem man am besten weitermacht – und langfristig ist Letzteres entscheidend
- Wenn ein CI-Tool strukturell die Zeit von Entwicklern verbraucht, liegt das Problem nicht bei den Entwicklern, sondern beim Tool selbst
3 Kommentare
Ich glaube, das Problem ist, dass CI an sich immer komplexer wird.
Es wirkt, als wäre derselbe Beitrag zweimal gepostet worden. Aber für eine von KI zusammengestellte Kombination scheint das heutzutage ganz ordentlich zu sein..
Hacker-News-Kommentare
Ich habe mehrere CI-Systeme verwendet. CircleCI und GitHub Actions habe ich oft genutzt, komme aber zu einem anderen Schluss als der Autor.
Früher war Jenkins auf Java spezialisiert und Travis auf Rails, aber solche spezialisierten CI-Systeme waren letztlich eine Sackgasse. Inzwischen hat sich CI einfach zu einem Workflow-Orchestrator weiterentwickelt.
Der Grund für unseren Wechsel von CircleCI 2 zu GitHub Actions war ebenfalls, dass CircleCI diesen Übergang nicht sauber geschafft hat. GHA war ausdrucksstark genug.
Dinge wie der Log-Browser oder die YAML-Syntax sind Nebensachen. Entscheidend sind Eigentum an den Compute-Ressourcen und dynamische Pipelines; Ersteres geht in jedem CI-System, Letzteres ist eine Stärke von Buildkite.
Mein Fazit ist, dass Actions in der Praxis ziemlich gut ist; wenn ich ein neues Unternehmen gründen würde, würde ich Buildkite nutzen, und für Open Source Actions.
Wenn man den Build-Graphen nicht versteht, muss man inkrementelle Build-Zustände beibehalten, und das verursacht sporadische Bugs. Deshalb braucht man Systeme wie UnrealEngine Horde oder UBA, die die Build-Struktur tief verstehen.
Mit einer verallgemeinerten CI kann ein Build auch mal länger als einen Tag dauern.
Ich nutze bei GHA eher nur die guten Teile. GitHub ist zum Beispiel hervorragend als Event-Dispatcher, aber nicht besonders gut als Workflow-Orchestrator, deshalb delegiere ich diesen Teil an ein anderes System.
Wenn die Logs, die man dutzendfach pro Tag ansieht, unbequem sind, leidet die Produktivität. Rohe Logs zu lesen und dabei Escape-Codes zu ignorieren, ist wirklich schmerzhaft.
Ich halte es einfach. Die gesamte Orchestrierung steckt in einem deploy.sh-Skript, das lokal auf dem Mac oder in AWS CodeBuild läuft.
Das YAML besteht einfach aus einer Zeile
bash deploy.sh. Solange es einen Docker-Container gibt, läuft es identisch überall, etwa auf Azure oder in GitHub Actions.Die Kernstrategie in jeder CI-Umgebung ist, ein mit lokal identisches Build-System zu haben.
Ich fange immer mit einem Makefile an. Docker, CI-Builds, Linting und alles andere werden vom Makefile gestartet. Wenn das Projekt wächst, wechsle ich manchmal auf andere Tools, aber die Grundlage ist ein einziges Trigger-Tool.
Ich nutze auf Mobile viel Fastlane; es reduziert Boilerplate und bringt Struktur hinein. Letztlich ist es Ruby, sodass man bei Bedarf immer noch ausbrechen kann.
(Link zur GNU-Make-Dokumentation)
Im Grunde heißt es nur: „Schreib einfach ein Bash-Skript“, nur mit unnötiger zusätzlicher Komplexität.
Auch in meinem aktuellen Unternehmen konnten wir irgendwann nicht mehr die komplette Pipeline lokal ausführen, und so entstand eine riesige CI-Infrastruktur, in der man zum Testen eines einzigen MRs zehn Builds laufen lässt.
Für mich wirkte der Artikel wie Werbung für Nix/Buildkite.
Es reicht, wenn CI einfach Skripte oder Build-Targets ausführt. CI sollte nur die Umgebung und Konfiguration liefern; die Logik sollte im Code liegen.
So bekommt man CI-Unabhängigkeit, was einen Wechsel zwischen Systemen erleichtert.
GitLab CI geht mit dieser Komplexität ziemlich gut um. Die Template- und Job-Kompositionsfunktionen sind hervorragend, aber das Debugging ist schwierig und die bedingte Logik schlägt oft unerwartet fehl.
.sh-Dateien gepackt hatten, sehr einfach.Teams, die alles fein granular in der UI aufgebaut hatten, hatten es deutlich schwerer.
Das Problem ist nicht CI/CD selbst, sondern eine Kultur des Programmierens in Konfigurationsdateien.
Die Schleife aus
git commit -m "try fix"und danach 10 Minuten Warten ist viel zu verbreitet. An lokal reproduzierbaren CI-Umgebungen fehlt es immer noch.Wenn man Umgebungsisolation als Richtlinie festlegt, funktioniert es mit jedem Tool. Entscheidend ist letztlich das Zusammenspiel von Tooling und Methodik.
acthelfen sehr dabei, CI lokal nachzubilden.Der Titel, dass es „Engineering-Teams tötet“, ist übertrieben. GitHub Actions ist völlig okay.
Ich bevorzuge es gegenüber Bitbucket oder GitLab.
In letzter Zeit hat GitHubs Zuverlässigkeit stark nachgelassen.
actions/checkoutschlägt ohne ersichtlichen Grund fehl, Release-Jobs laufen doppelt, oder etwas wartet einfach 40 Minuten lang.Ich nutze es seit Jahren, aber die grundlegende Stabilität wird schwächer. Ich bedaure, Buildkite verpasst zu haben.
GitHub Actions ist eines der schlechtesten CI-Tools, die ich je benutzt habe, auf einer Stufe mit Jenkins.
Buildkite hingegen ist das beste. Dank dynamischer Pipelines kann man bei Testfehlschlägen automatisch Retry-Schritte erzeugen oder parallele Tests je nach Codeänderung anpassen.
Ein weiterer großer Vorteil ist, dass man für jeden CI-Job eine andere Maschinenkonfiguration verwenden kann. Klare Empfehlung.
Leute, die für eine „einfache skriptbasierte CI“ argumentieren, haben wahrscheinlich keine Erfahrung mit echten Großprojekten.