Clusterward
← Zurück zum Blog
BetriebAktualisiert Florian Apel

Im Cluster oder mit GitHub Actions: Wo soll das Image gebaut werden?

Ein Build im Cluster läuft dort, wo die Anwendung läuft. GitHub Actions baut dort, wo der Code liegt. Beide brauchen keinen Docker-Daemon, und beide haben einen Haken, den man erst im Betrieb sieht.

Titelbild: Im Cluster oder mit GitHub Actions: Wo soll das Image gebaut werden?

Ein Deployment aus Git braucht ein Image, und ein Image braucht einen Build. Die alte Antwort war ein Build-Server mit Docker. Die neue Antwort ist eine von zwei: im Cluster ohne Docker-Daemon oder außerhalb mit GitHub Actions. Beide funktionieren. Welche passt, hängt von drei Fragen ab.

Im Cluster: Der Build läuft, wo die Anwendung läuft

Gebaut wird von einem Werkzeug, das ein Dockerfile ohne Docker-Daemon baut. Es läuft als normaler Pod im Cluster: klont das Repository, führt die Anweisungen des Dockerfiles aus, pusht das Ergebnis in die Registry. Kein privilegierter Container, kein Socket, kein Build-Server.

Vorteile:

  • Der Build passiert dort, wo das Image später läuft. Registry, Netz und Zugangsdaten sind schon da.
  • Kein Konto bei einem Drittanbieter, keine Runner-Minuten, keine Abhängigkeit von GitHub.
  • Private Repositories erreicht der Job über ein Token, das als Secret nur für die Dauer des Builds existiert.

Nachteile:

  • Der Build verbraucht CPU und Speicher des Clusters. Ein großer Build neben Produktions-Pods kann diese ausbremsen, wenn Requests und Limits des Build-Jobs nicht gesetzt sind.
  • Kein Layer-Cache über Builds hinweg, sofern man ihn nicht in der Registry ablegt. Jeder Build beginnt bei null.
  • Ein hängender Build ist ein hängender Pod, den jemand aufräumen muss.

GitHub Actions: Der Build läuft bei GitHub

Clusterward stößt den Workflow per workflow_dispatch an, GitHub baut auf seinen Runnern und pusht in die Registry, Clusterward wartet auf das Ergebnis und rollt aus.

Vorteile:

  • Null Last auf dem Cluster. Ein Build mit acht Gigabyte Speicher kostet keine Produktions-Ressourcen.
  • Der Cache von GitHub Actions beschleunigt wiederholte Builds spürbar.
  • Der Workflow ist Teil des Repositories, Entwickler kennen ihn.

Nachteile:

  • Runner-Minuten kosten Geld, je nach Kontingent des Plans.
  • Ein grüner Workflow beweist nur, dass der Workflow durchlief, nicht, dass das richtige Image gepusht wurde. Ein Workflow mit hart kodiertem Tag pusht fröhlich woandershin und meldet Erfolg.
  • Das Git-Token der Anwendung braucht Rechte auf Actions, und die Abhängigkeit von GitHubs Verfügbarkeit.

Den zweiten Nachteil haben wir live erlebt: Workflow grün, das erwartete Tag fehlte in der Registry, der Pod hing zwei Minuten in ImagePullBackOff, am Ende stand eine irreführende Zeitüberschreitung. Seitdem prüft Clusterward nach jedem GitHub-Actions-Build, ob das erwartete Tag wirklich in der Registry liegt, bevor es ausrollt. Nur ein eindeutiges "nicht vorhanden" stoppt das Deployment; eine flüchtige Störung der Registry darf kein funktionierendes Deployment blockieren. Details unter Deployments.

Die drei Fragen

Frage

Im Cluster

GitHub Actions

Hat der Cluster Reserven für Builds?

Ja, mit Requests und Limits am Job

Egal, der Build ist woanders

Darf der Code den Cluster verlassen?

Muss er nicht, das Repository wird im Cluster geklont

Er liegt ohnehin bei GitHub

Wie oft wird gebaut?

Selten bis mittel

Oft, mit Cache

Wer souverän bleiben will und wenige Builds am Tag hat, baut im Cluster. Wer zwanzigmal am Tag baut und ohnehin bei GitHub ist, nimmt Actions. Beides parallel im selben Cluster ist kein Widerspruch: pro Build wählbar, nicht pro Cluster.

Requests für den Build-Job

Der häufigste Fehler beim Build im Cluster ist ein Build-Job ohne Ressourcenangaben. Kubernetes plant ihn irgendwohin, er nimmt sich, was er kriegt, und ein Produktions-Pod auf demselben Node bekommt weniger. Drei Werte gehören an jeden Build: CPU-Request, Speicher-Request und optional ein Node-Pool, auf dem Builds laufen dürfen. Ein eigener kleiner Pool für Builds, der ohne Builds auf null skaliert, ist die sauberste Lösung.

Das Token

Beide Wege brauchen ein Token für private Repositories. Für den Build im Cluster ein Lesetoken, das der Job als Secret bekommt und das nach dem Build gelöscht wird. Für Actions ein Token mit dem Recht, Workflows zu starten. In beiden Fällen gilt: Das Token liegt verschlüsselt in der Control Plane und wird nie angezeigt, und es ist pro Anwendung, nicht pro Organisation.

Beim Push gilt dasselbe Prinzip. Ein Build im Cluster führt das Dockerfile Ihres Repositorys aus, direkt neben dem Schlüssel, mit dem gepusht wird. Clusterward gibt dem Build-Job deshalb einen eigenen Schlüssel, der nur die Container Registry des Projekts nutzen darf – nie den Schlüssel, mit dem Cluster und Datenbanken angelegt werden.

Die dritte Variante: eigene CI, Clusterward rollt aus

Manche Teams haben ihre Pipeline längst: Tests, Build und Push laufen in GitHub Actions oder GitLab CI, und das Image liegt in der Registry. Dann fehlt nur der letzte Schritt, das Ausrollen. Dafür gibt es in Clusterward API-Tokens. Unter Settings → API tokens entsteht ein Token mit Rolle, Anwendungsbereich und Ablaufdatum, auf Wunsch mit der Rolle "CI deploy", die nur Anwendungen ausrollen darf. Der Workflow stößt Deploy oder Neustart per Bearer-Header an und fragt den Status ab, bis das Deployment gesund ist. Das Token kann weder Nutzer noch Rollen noch weitere Tokens verwalten, und jede seiner Aktionen steht unter seinem Namen im Audit-Log. Mehr dazu unter Deployments.

Ein Beispiel aus dem Alltag

Eine Agentur betreibt zwölf Kundenprojekte auf einem Cluster mit zwei Nodes. Jedes Projekt wird ein- bis zweimal pro Woche deployt. Das sind rund zwanzig Builds pro Woche, jeder zwei bis vier Minuten. Für diesen Cluster ist der Build im Cluster richtig: Die Builds laufen nachts oder nebenbei, verbrauchen kurz eine vCPU und ein Gigabyte und sind weg. Ein GitHub-Konto mit Runner-Minuten wäre eine zusätzliche Abhängigkeit für zwanzig kurze Jobs.

Ein SaaS-Anbieter mit einer Anwendung und einem Team von acht Entwicklern deployt zwanzigmal am Tag. Jeder Build braucht sechs Minuten und vier Gigabyte, weil das Frontend gebündelt wird. Hier ist GitHub Actions richtig: Mit Cache dauert der Build zwei Minuten, der Cluster merkt nichts, und die Entwickler sehen den Build dort, wo sie ohnehin arbeiten.

Das Dockerfile entscheidet mehr als das Werkzeug

Beide Werkzeuge bauen dasselbe Dockerfile, und die meisten langsamen Builds sind langsame Dockerfiles. Drei Regeln, die bei beiden wirken:

  1. Abhängigkeiten vor dem Code kopieren. Erst package.json und Lockfile, dann installieren, dann der Rest. So bleibt der Installationsschritt im Cache, solange sich die Abhängigkeiten nicht ändern.
  2. Multi-Stage-Builds. Bauen in einem Image mit Compiler, ausliefern in einem schlanken Image ohne. Das fertige Image ist kleiner, der Push schneller, die Angriffsfläche geringer.
  3. Ein `.dockerignore`. Ohne sie wandert node_modules oder .git in den Build-Kontext, und der Build klont und kopiert Hunderte Megabyte, die niemand braucht.

Wenn der Build hängt

Im Cluster ist ein hängender Build ein Pod im Zustand Running, der nie fertig wird, meist wegen eines Netzwerkzugriffs im Dockerfile, der auf ein Timeout wartet. Ein Job braucht deshalb eine Frist, nach der Kubernetes ihn beendet, und eine Möglichkeit, ihn aus dem Cockpit abzubrechen. Bei GitHub Actions gilt dasselbe für den Workflow-Lauf: Ein Timeout pro Job, sonst blockiert ein hängender Build die Runner-Minuten und das Deployment wartet auf ein Ergebnis, das nie kommt.

Welches Image läuft wirklich?

Ein Tag wie latest wandert mit jedem Build. Clusterward fragt deshalb bei jedem Deploy die Registry, auf welches Image das Tag gerade zeigt, und rollt genau dieses per Digest aus. Neue Builds bekommen zusätzlich ein festes Tag sha-<Commit>, das auch nach dem nächsten Build noch auf dasselbe Image zeigt.

Geht eine Version schief, spielt „Restore this version…“ in der Deploy-Historie das exakte Image einer früheren gesunden Version zurück – egal ob im Cluster oder mit GitHub Actions gebaut wurde. Mehr dazu unter Deployments und Backups & Wiederherstellung.

Fazit

Im Cluster bauen, wenn er Reserven hat und der Code im eigenen Netz bleiben soll. GitHub Actions, wenn oft gebaut wird und die Last woanders liegen soll. In beiden Fällen: das Image vor dem Rollout prüfen, Requests am Build setzen, Token pro Anwendung. Der Build-Weg ist eine Einstellung pro Build, keine Glaubensfrage.

Welcher Build-Weg passt zu Ihrem Repository? Schreiben Sie uns, wie oft Sie bauen und wo Ihr Code liegt. Wir sagen Ihnen, ob der Build im Cluster oder GitHub Actions besser passt. Frage zum Build stellen →

Quellen und weiterführende Links

Häufige Fragen

  • Ein Build im Cluster läuft dort, wo die Anwendung läuft; GitHub Actions baut auf GitHubs Runnern, dort wo der Code liegt. Beide brauchen keinen Docker-Daemon. Der Build im Cluster verbraucht CPU und Speicher des Clusters, GitHub Actions kostet Runner-Minuten und macht vom Token und von GitHubs Verfügbarkeit abhängig.