Merge branch 'main' into sustainability
commit
c83ade69c7
|
|
@ -52,7 +52,6 @@
|
|||
showstringspaces=false,
|
||||
}
|
||||
|
||||
% Markierung des Verfassers eines Kapitels (Pflicht laut Profvorgabe)
|
||||
\newcommand{\authornote}[1]{\textit{\small Verfasst von: #1}\par\medskip}
|
||||
|
||||
\graphicspath{ {./bilder/} }
|
||||
|
|
@ -87,6 +86,7 @@
|
|||
\maketitle
|
||||
|
||||
\begin{abstract}
|
||||
\authornote{Christopher Schmitt}
|
||||
\blindtext
|
||||
\end{abstract}
|
||||
|
||||
|
|
@ -209,15 +209,15 @@
|
|||
\section{CI/CD}
|
||||
\authornote{Christopher Schmitt}
|
||||
|
||||
Die Begriffe \ac{CI}, \ac{CD} und Continuous Deployment werden in der Literatur nicht einheitlich verwendet. Shahin et al.~\cite{shahin_continuous_2017} grenzen sie klar ab. \ac{CI} bezeichnet das automatische Übersetzen und Testen jeder Codeänderung gegen den gemeinsamen Hauptzweig. \ac{CD} ergänzt diesen Vorgang um die Eigenschaft, dass jeder geprüfte Stand jederzeit in eine produktionsnahe Umgebung ausgeliefert werden kann. Der Schritt in die Produktion bleibt dabei eine bewusste, manuelle Freigabe. Continuous Deployment automatisiert auch diesen letzten Schritt. Alle drei Stufen bildet GitLab mit denselben Bausteinen ab: Runner, Pipelines und Jobs. Gesteuert werden sie zentral über eine einzige Konfigurationsdatei im Repository \cite{cowell_automating_2023}.
|
||||
\ac{CI}, \ac{CD} und Continuous Deployment bauen aufeinander auf, werden in der Literatur aber oft vermischt. \ac{CI} bezeichnet das automatische Übersetzen und Testen jeder Codeänderung gegen den gemeinsamen Hauptzweig. \ac{CD} steht in dieser Arbeit für Continuous Delivery. Jeder geprüfte Stand lässt sich damit jederzeit in eine produktionsnahe Umgebung ausliefern, der eigentliche Sprung in die Produktion bleibt aber eine bewusste, manuelle Freigabe. Continuous Deployment geht einen Schritt weiter und automatisiert auch diese Freigabe. Die Abkürzung CD wird dabei für beide letztgenannten Stufen verwendet, was regelmässig zu Verwechslungen führt \cite{shahin_continuous_2017}. Alle drei Stufen bildet GitLab mit denselben Bausteinen ab: Runner, Pipelines und Jobs. Gesteuert werden sie über eine einzige Konfigurationsdatei im Repository \cite{cowell_automating_2023}.
|
||||
|
||||
\subsection{GitLab Runner}
|
||||
|
||||
Die GitLab-Instanz selbst führt keine \ac{CI}/\ac{CD}-Arbeit aus. Diese Aufgabe übernimmt ein separater Hilfsprozess, der \emph{Runner} \cite{gitlab_gitlab_nodate}. Ein Runner registriert sich einmal mit einem Token bei der Instanz und holt sich danach selbständig offene Aufgaben ab. Durch diese Trennung kann die Ausführung horizontal skaliert werden. Plattformabhängige Aufgaben lassen sich gezielt auf der passenden Hardware ausführen \cite{painter_practical_2024}. Runner können auf drei unterschiedlichen Ebenen registriert werden:
|
||||
\begin{itemize}
|
||||
\item \emph{Instanz} Auf Instanzebene registrierte Runner stehen allen Projekten der GitLab-Instanz zur Verfügung. Sie werden in der Regel von der Administration bereitgestellt.
|
||||
\item \emph{Gruppe} Auf Gruppenebene registrierte Runner nehmen Aufgaben aus sämtlichen Projekten innerhalb dieser Gruppe und ihrer Subgruppen an. Eingesetzt werden sie meistens dann, wenn eine Gruppe besondere Anforderungen an Hardware oder Software stellt.
|
||||
\item \emph{Projekt} Auf Projektebene registrierte Runner sind ausschliesslich an ein einzelnes Projekt gebunden. Diese Variante eignet sich für sensitive Projekte, bei denen kein gemeinsam genutzter Runner verwendet werden soll.
|
||||
\item \textbf{Instanz} Auf Instanzebene registrierte Runner stehen allen Projekten der GitLab-Instanz zur Verfügung. Sie werden in der Regel von der Administration bereitgestellt.
|
||||
\item \textbf{Gruppe} Auf Gruppenebene registrierte Runner nehmen Aufgaben aus sämtlichen Projekten innerhalb dieser Gruppe und ihrer Subgruppen an. Eingesetzt werden sie meistens dann, wenn eine Gruppe besondere Anforderungen an Hardware oder Software stellt.
|
||||
\item \textbf{Projekt} Auf Projektebene registrierte Runner sind ausschliesslich an ein einzelnes Projekt gebunden. Diese Variante eignet sich für sensitive Projekte, bei denen kein gemeinsam genutzter Runner verwendet werden soll.
|
||||
\end{itemize}
|
||||
Wie ein Runner eine konkrete Aufgabe ausführt, hängt vom konfigurierten \emph{Executor} ab. Der Executor entscheidet, in welcher Umgebung das vom Anwender hinterlegte Skript läuft. Tabelle~\ref{tab:executors} listet die in der Praxis am häufigsten verwendeten Executors auf.
|
||||
\begin{table}[H]
|
||||
|
|
@ -230,19 +230,19 @@
|
|||
Executor & Ausführungsumgebung & Typischer Einsatz \\ \midrule
|
||||
\texttt{shell} & direkt auf dem Hostsystem & einfache Setups, Eigenbau-Server \\
|
||||
\texttt{docker} & je Aufgabe ein neuer Container & in der Praxis der Standardfall \\
|
||||
\texttt{docker-machine} & ephemere Cloud-VMs & elastische Skalierung \\
|
||||
\texttt{docker-machine} & kurzlebige Cloud-VMs & elastische Skalierung \\
|
||||
\texttt{kubernetes} & ein Pod je Aufgabe & grosse Cluster, Autoskalierung \\
|
||||
\texttt{ssh} & entfernter Host über SSH & Legacy-Systeme \\ \bottomrule
|
||||
\end{tabular}%
|
||||
}
|
||||
\end{table}
|
||||
Die Wahl des Executors hat neben der technischen auch eine organisatorische Dimension. Nach Rostami Mazrae et al.~\cite{rostami_usage_2023} startet der \texttt{shell}-Executor zwar mit minimalem Aufwand, wird in produktiven Umgebungen aufgrund fehlender Isolation aber üblicherweise durch \texttt{docker} oder \texttt{kubernetes} ersetzt. Sicherheitsrisiken entstehen vor allem dann, wenn mehrere Projekte denselben \texttt{shell}-Runner verwenden und ein Skript das Hostsystem dauerhaft verändert.
|
||||
Die Wahl des Executors ist auch eine Sicherheitsfrage. Der \texttt{shell}-Executor ist am schnellsten eingerichtet, kapselt die Jobs aber nicht voneinander ab. In der Produktion wird er deshalb meist durch \texttt{docker} oder \texttt{kubernetes} ersetzt \cite{rostami_usage_2023}. Kritisch wird es, wenn mehrere Projekte denselben \texttt{shell}-Runner teilen und ein Job das Hostsystem dauerhaft verändert. Diese Änderung wirkt dann auf alle folgenden Jobs.
|
||||
|
||||
\subsection{Pipelines}
|
||||
|
||||
Eine \emph{Pipeline} ist in GitLab die oberste Ebene der Automatisierung. Sie umfasst sämtliche Schritte, die als Reaktion auf ein Ereignis im Repository ablaufen \cite{gitlab_gitlab_nodate}. Ausgelöst wird eine Pipeline durch unterschiedliche Ereignisse. Dazu gehören ein Push, das Öffnen eines \emph{Merge Requests}, ein hinterlegter Zeitplan, ein manueller Klick in der Weboberfläche oder das Ende einer anderen Pipeline \cite{cowell_automating_2023}.
|
||||
|
||||
Innerhalb einer Pipeline werden die einzelnen Arbeitsschritte zu sogenannten \emph{Stages} zusammengefasst. Alle Schritte einer Stage laufen parallel und müssen erfolgreich abgeschlossen sein, bevor die nächste Stage beginnt \cite{gitlab_gitlab_nodate}. Eine in der Praxis verbreitete Reihenfolge der Stages ist \texttt{lint}, \texttt{test}, \texttt{build} und \texttt{deploy}. Abbildung~\ref{fig:pipeline-stages} zeigt das Verhältnis von Pipeline, Stages und Jobs.
|
||||
Innerhalb einer Pipeline werden die einzelnen Arbeitsschritte zu sogenannten \emph{Stages} zusammengefasst. Alle Schritte einer Stage laufen parallel und müssen erfolgreich abgeschlossen sein, bevor die nächste Stage beginnt \cite{gitlab_gitlab_nodate}. Eine in der Praxis verbreitete Reihenfolge der Stages ist \texttt{lint}, \texttt{test}, \texttt{build} und \texttt{deploy}. In \texttt{lint} prüfen statische Werkzeuge den Code auf Stil und offensichtliche Fehler. \texttt{test} führt die automatischen Tests aus. In \texttt{build} wird die Anwendung paketiert, etwa zu einem Container-Image. \texttt{deploy} bringt das Ergebnis in eine Zielumgebung. Abbildung~\ref{fig:pipeline-stages} zeigt das Verhältnis von Pipeline, Stages und Jobs.
|
||||
|
||||
\input{diagrams/pipeline_stages}
|
||||
|
||||
|
|
@ -273,7 +273,7 @@ deploy-staging:
|
|||
- main
|
||||
\end{lstlisting}
|
||||
|
||||
Aus der starren Stage-Reihenfolge lässt sich mit dem Schlüsselwort \texttt{needs} ein gerichteter azyklischer Graph bilden, ein sogenannter \ac{DAG}. Ein Schritt darf dann starten, sobald seine konkret benannten Vorgänger fertig sind. Unnötige Wartezeiten entfallen \cite{cowell_automating_2023}. Wiederkehrende Pipeline-Bausteine können über das Schlüsselwort \texttt{include} aus anderen Repositories nachgeladen werden \cite{gitlab_gitlab_nodate}. Mit den in Abschnitt~\ref{subsec:komponenten} beschriebenen CI/CD-Komponenten existiert seit GitLab 16.0 ein weitergehender Mechanismus zur Wiederverwendung von Pipeline-Logik.
|
||||
Die starre Stage-Reihenfolge lässt sich mit dem Schlüsselwort \texttt{needs} aufbrechen. Daraus entsteht ein gerichteter Graph ohne Zyklen, ein sogenannter \ac{DAG}. Ohne Zyklen heisst, dass kein Job über Umwege wieder von sich selbst abhängt. Ein Job startet, sobald seine konkret benannten Vorgänger fertig sind, und wartet nicht mehr auf die gesamte vorherige Stage. Unnötige Wartezeiten entfallen \cite{cowell_automating_2023}. Wiederkehrende Pipeline-Bausteine lassen sich über das Schlüsselwort \texttt{include} aus anderen Repositories nachladen \cite{gitlab_gitlab_nodate}.
|
||||
|
||||
\subsection{Jobs}
|
||||
|
||||
|
|
@ -283,26 +283,26 @@ deploy-staging:
|
|||
|
||||
Während ein Job läuft, entstehen häufig Dateien, die spätere Jobs weiterverarbeiten. Beispiele sind ein gebautes Bundle, ein Container-Image oder ein Testbericht. GitLab unterscheidet dafür zwei Mechanismen mit unterschiedlicher Lebensdauer \cite{gitlab_gitlab_nodate}:
|
||||
\begin{itemize}
|
||||
\item \emph{Artefakte} sind benannte Ergebnisse eines Jobs. Sie werden nach dem erfolgreichen Abschluss eines Jobs an die GitLab-Instanz hochgeladen, stehen dort zum Download bereit und werden an spätere Jobs der Pipeline weitergegeben.
|
||||
\item \emph{Caches} dienen ausschliesslich dazu, wiederholte Ausführungen zu beschleunigen. Typisch ist das Zwischenspeichern heruntergeladener Abhängigkeiten zwischen Pipeline-Läufen. Über Jobgrenzen hinweg ist der Cache nicht garantiert verfügbar.
|
||||
\item \textbf{Artefakte} sind benannte Ergebnisse eines Jobs. Sie werden nach dem erfolgreichen Abschluss eines Jobs an die GitLab-Instanz hochgeladen, stehen dort zum Download bereit und werden an spätere Jobs der Pipeline weitergegeben.
|
||||
\item \textbf{Caches} dienen ausschliesslich dazu, wiederholte Ausführungen zu beschleunigen. Typisch ist das Zwischenspeichern heruntergeladener Abhängigkeiten zwischen Pipeline-Läufen. Über Jobgrenzen hinweg ist der Cache nicht garantiert verfügbar.
|
||||
\end{itemize}
|
||||
Das Ziel einer Auslieferung wird durch das Konzept der \emph{Environments} beschrieben \cite{cowell_automating_2023}. Jedes Environment fasst alle Auslieferungen in eine bestimmte Zielumgebung zusammen. In der Weboberfläche wird zusätzlich protokolliert, welche Version dort gerade läuft. Einen Sonderfall bilden \emph{Review-Apps}. Für jeden offenen Merge Request erzeugt GitLab automatisch eine isolierte, kurzlebige Auslieferung, in der die geänderte Anwendung vor dem Merge begutachtet werden kann \cite{gitlab_gitlab_nodate}. Nach Rostami Mazrae et al.~\cite{rostami_usage_2023} ist diese direkte Kopplung zwischen Review und laufender Vorschau eines der Merkmale, mit denen sich GitLab von alternativen Plattformen abgrenzt.
|
||||
|
||||
Zampetti et al.~\cite{zampetti_empirical_2020} identifizieren in einer empirischen Untersuchung 79 wiederkehrende Anti-Muster in CI-Konfigurationen. Häufige Probleme sind unnötig sequentielle Stages, zu grobgranulare Jobs und das Vermischen von Cache und Artefakten. Lesbarkeit und Wartbarkeit der Pipeline-Definition sind damit ein eigenes Qualitätskriterium.
|
||||
Auch die Pipeline-Definition selbst kann schlecht gebaut sein. Zampetti et al.~\cite{zampetti_empirical_2020} sammeln typische Fehler aus realen CI-Konfigurationen. Zwei davon treten besonders oft auf. Stages laufen nacheinander, obwohl sie parallel könnten. Ausserdem werden Cache und Artefakte vermischt, sodass ein späterer Job auf Dateien zugreift, die nicht zuverlässig vorhanden sind. Die Pipeline schlägt dann unregelmässig fehl, je nachdem, ob die Datei gerade im Cache liegt.
|
||||
|
||||
\subsection{CICD-Komponenten}\label{subsec:komponenten}
|
||||
|
||||
Mit dem Schlüsselwort \texttt{include} können bereits seit längerem ganze Pipeline-Fragmente aus anderen Repositories oder von einer entfernten \ac{URL} eingebunden werden \cite{gitlab_gitlab_nodate}. In der Praxis stellen sich dabei zwei wiederkehrende Probleme. Eingebundene Fragmente sind in den meisten Fällen weder versioniert noch besitzen sie eine eigene Schnittstelle. Eine Änderung am Fragment kann sämtliche Pipelines mit einbinden \cite{cowell_automating_2023}.
|
||||
Mit dem Schlüsselwort \texttt{include} können bereits seit längerem ganze Pipeline-Fragmente aus anderen Repositories oder von einer entfernten \ac{URL} eingebunden werden \cite{gitlab_gitlab_nodate}. In der Praxis stellen sich dabei zwei wiederkehrende Probleme. Eingebundene Fragmente sind in den meisten Fällen weder versioniert noch besitzen sie eine eigene Schnittstelle. Eine Änderung am Fragment schlägt sofort auf sämtliche einbindenden Pipelines durch \cite{cowell_automating_2023}.
|
||||
|
||||
Seit GitLab 16.0 lösen \emph{CI/CD-Komponenten} dieses Problem. Eine Komponente ist ein versionierter und parametrisierbarer Pipeline-Baustein, der ähnlich wie eine Software-Bibliothek verwendet wird \cite{gitlab_gitlab_nodate}. Komponenten werden in einem dedizierten Projekt entwickelt und veröffentlicht. Drei Bestandteile sind dabei zwingend vorgesehen:
|
||||
CI/CD-Komponenten lösen diese Probleme. Eingeführt wurden sie experimentell mit GitLab 16.0, allgemein verfügbar sind sie seit Version 17.0 \cite{gitlab_gitlab_nodate}. Eine Komponente ist ein versionierter und parametrisierbarer Pipeline-Baustein, der ähnlich wie eine Software-Bibliothek verwendet wird. Komponenten werden in einem dedizierten Projekt entwickelt und veröffentlicht. Drei Bestandteile gehören dazu:
|
||||
\begin{itemize}
|
||||
\item \emph{Manifest} In einer Datei \texttt{template.yml} beschreibt die Komponente ihre Eingabeparameter mit Datentyp und Defaultwert sowie die von ihr beigesteuerten Jobs.
|
||||
\item \emph{Versionsschild} Komponenten werden ausschliesslich über Git-Tags freigegeben. Ein Konsument referenziert eine Komponente immer mit einer konkreten Version. Damit kann sich der eingebundene Stand nicht unter dem Konsumenten verändern.
|
||||
\item \emph{Katalogeintrag} Ein Projekt, das den Status \textit{Components Project} erhält, erscheint im zentralen \emph{Component Catalog} der GitLab-Instanz. Die Suche nach geeigneten Komponenten erfolgt dort, ohne dass Repository-Pfade auswendig bekannt sein müssen.
|
||||
\item \textbf{Manifest} Eine Komponente beschreibt in einer YAML-Datei, meist \texttt{template.yml}, ihre Eingabeparameter mit Datentyp und Defaultwert sowie die Jobs, die sie beisteuert.
|
||||
\item \textbf{Version} Komponenten werden über Git-Tags veröffentlicht. Das einbindende Projekt referenziert eine Komponente mit einer festen Version, wahlweise auch mit einem Branch oder einem Commit. Bei einer festen Version kann sich der eingebundene Stand nachträglich nicht mehr ändern.
|
||||
\item \textbf{Katalog} Wird eine Komponente als Release veröffentlicht, erscheint sie im \emph{CI/CD Catalog} der GitLab-Instanz. Dort lassen sich Komponenten suchen und einbinden, ohne dass der genaue Repository-Pfad bekannt sein muss.
|
||||
\end{itemize}
|
||||
Listing~\ref{lst:component} zeigt die Einbindung einer beispielhaften Lint-Komponente in eine bestehende Pipeline.
|
||||
|
||||
\begin{lstlisting}[language=yaml,caption={Einbindung einer CI/CD-Komponente},label={lst:component}]
|
||||
\begin{lstlisting}[language=yaml,caption={Einbindung einer CI/CD-Komponente mit gesetzten \texttt{inputs}},label={lst:component}]
|
||||
include:
|
||||
- component: $CI_SERVER_FQDN/team/lint@1.2.0
|
||||
inputs:
|
||||
|
|
@ -314,13 +314,13 @@ stages:
|
|||
- test
|
||||
\end{lstlisting}
|
||||
|
||||
Im Gegensatz zum klassischen \texttt{include} besitzt die Komponente damit eine echte Aufrufsignatur. Die Eingabewerte werden vom Aufrufer im Block \texttt{inputs} gesetzt und sind unter dem Präfix \texttt{\$[[ inputs.<name> ]]} innerhalb der Komponente verfügbar \cite{gitlab_gitlab_nodate}. Nach Cowell et al.~\cite{cowell_automating_2023} ist diese Trennung zwischen Schnittstelle und Implementierung der wesentliche Schritt, der Pipeline-Logik in grösseren Organisationen wartbar hält. Die Anti-Muster, die Zampetti et al.~\cite{zampetti_empirical_2020} beschreiben, lassen sich auf diese Weise an einer einzigen Stelle korrigieren, ohne dass sämtliche Pipelines angefasst werden müssen.
|
||||
Anders als beim klassischen \texttt{include} hat die Komponente damit eine klar definierte Schnittstelle mit benannten Eingabeparametern. Die Werte setzt das einbindende Projekt im Block \texttt{inputs}. Innerhalb der Komponente stehen sie über die Schreibweise \texttt{\$[[ inputs.<name> ]]} zur Verfügung \cite{gitlab_gitlab_nodate}. Der Aufruf in Listing~\ref{lst:component} verweist mit \texttt{\$CI\_SERVER\_FQDN} auf eine von GitLab vorgegebene Variable. Sie enthält den vollständigen Hostnamen der Instanz, den \ac{FQDN}, sodass der Pfad nicht fest verdrahtet werden muss. Diese Trennung von Schnittstelle und Implementierung hält die Pipeline-Logik auch in grösseren Organisationen wartbar \cite{cowell_automating_2023}. Ein Fehler an einer Komponente lässt sich dann an einer einzigen Stelle beheben, statt in jeder einbindenden Pipeline.
|
||||
|
||||
\subsection{Anwendungsbeispiel}
|
||||
|
||||
Zur praktischen Demonstration der vorhergehenden Konzepte wurde eine kleine Webanwendung auf der in Abschnitt~\ref{sec:selfhosted} beschriebenen selbstgehosteten Instanz aufgesetzt. Die Anwendung ist bewusst minimal gehalten. So liegt der Fokus auf der \ac{CI}/\ac{CD}-Konfiguration und nicht auf dem Anwendungscode.
|
||||
|
||||
Die Demoanwendung ist ein \texttt{HTTP}-Dienst, der in \emph{Node.js} mit dem Web-Framework \emph{Express} geschrieben ist. Bereitgestellt wird ein einzelner Endpunkt \texttt{GET /}, der eine kurze HTML-Seite mit der Bezeichnung der aktuellen Umgebung und der im Build verbauten Versionskennung ausliefert. Beide Werte werden zur Laufzeit über Umgebungsvariablen aus dem Container gelesen und im jeweiligen Deploy-Job gesetzt. Eine kleine Testsuite mit dem Framework \texttt{vitest} prüft, dass der Endpunkt eine HTML-Antwort mit Statuscode~200 und der erwarteten Versionsangabe liefert.
|
||||
Die Demoanwendung ist ein \texttt{HTTP}-Dienst, der in \emph{Node.js} mit dem Web-Framework \emph{Express} geschrieben ist. Bereitgestellt wird ein einzelner Endpunkt \texttt{GET /}, der eine kurze HTML-Seite mit der Bezeichnung der aktuellen Umgebung und der im Build verbauten Versionskennung ausliefert. Beide Werte werden zur Laufzeit über Umgebungsvariablen aus dem Container gelesen und im jeweiligen Deploy-Job gesetzt. Eine kleine Testsuite mit dem Framework \texttt{vitest} prüft, dass der Endpunkt eine HTML-Antwort mit Statuscode~200 und der erwarteten Versionskennung liefert.
|
||||
|
||||
Die zugehörige \texttt{.gitlab-ci.yml} definiert drei Stages: \texttt{lint}, \texttt{test} und \texttt{deploy}. Die Stage \texttt{deploy} enthält dabei zwei Jobs, die sich gegenseitig ausschliessen. Tabelle~\ref{tab:demo-pipeline} fasst zusammen, welcher Schritt unter welchen Bedingungen ausgeführt wird und welches Environment er bedient.
|
||||
\begin{table}[H]
|
||||
|
|
@ -338,13 +338,15 @@ stages:
|
|||
\end{tabular}%
|
||||
}
|
||||
\end{table}
|
||||
Die ersten beiden Stages werden bei jedem Push auf jeden Branch ausgeführt. Sie entsprechen den klassischen \ac{CI}-Schritten nach Duvall et al.~\cite{duvall_continuous_2007} und Fowler~\cite{fowler_continuous_2006} und sollen ein schnelles, automatisches Feedback auf jeden Codestand erzeugen. Die Stage \texttt{deploy} deckt den Continuous-Delivery-Anteil ab. Auf einem Feature-Branch steht der Job \texttt{deploy:staging} als manueller Schritt in der Pipeline-Übersicht bereit. Eine Änderung kann so bei Bedarf in einer laufenden Vorschau begutachtet werden, bevor sie in den Hauptzweig wandert. Ein Push auf \texttt{main} liefert automatisch nach \texttt{production} aus. Nach der Begriffsabgrenzung von Shahin et al.~\cite{shahin_continuous_2017} entspricht der Pfad nach \texttt{main} damit Continuous Deployment, der manuelle Pfad nach Staging klassischem Continuous Delivery.
|
||||
Die ersten beiden Stages werden bei jedem Push auf jeden Branch ausgeführt. Sie entsprechen den klassischen \ac{CI}-Schritten nach Duvall et al.~\cite{duvall_continuous_2007} und Fowler~\cite{fowler_continuous_2006} und sollen ein schnelles, automatisches Feedback auf jeden Codestand erzeugen. Die Stage \texttt{deploy} deckt den Continuous-Delivery-Anteil ab. Hier trennen sich zwei Pfade. Auf einem Feature-Branch steht der Job \texttt{deploy:staging} als manueller Schritt in der Pipeline-Übersicht bereit. Eine Änderung kann so bei Bedarf in einer laufenden Vorschau begutachtet werden, bevor sie in den Hauptzweig wandert. Ein Push auf \texttt{main} liefert automatisch nach \texttt{production} aus. Nach der Begriffsabgrenzung von Shahin et al.~\cite{shahin_continuous_2017} entspricht der Pfad nach \texttt{main} damit Continuous Deployment, der manuelle Pfad nach Staging klassischem Continuous Delivery.
|
||||
|
||||
Im Rahmen der Live-Präsentation wird der Lebenszyklus einer Codeänderung in drei Schritten gezeigt. Zunächst wird auf einem Feature-Branch eine sichtbare Änderung am Anwendungscode vorgenommen und in das Repository übertragen. Die Stages \texttt{lint} und \texttt{unit-test} laufen automatisch durch. Danach wird der Job \texttt{deploy:staging} über die Weboberfläche freigegeben. Die Änderung ist unmittelbar unter der Staging-Adresse sichtbar, ohne dass sie bereits im Hauptzweig liegt. Den Abschluss bildet der Merge Request gegen \texttt{main}. Wird er akzeptiert, durchläuft eine neue Pipeline auf \texttt{main} erneut \texttt{lint} und \texttt{unit-test} und löst anschliessend automatisch \texttt{deploy:prod} aus. Die neue Version ist damit ohne weiteren manuellen Eingriff in der produktiven Umgebung erreichbar.
|
||||
Die Jobs laufen auf zwei verschiedenen Runnern. \texttt{lint} und \texttt{unit-test} nutzen einen Runner mit Docker-Executor und führen ihre Schritte im Container \texttt{node:20-alpine} aus. Die Deploy-Jobs sind über \texttt{tags} an einen zweiten Runner mit Shell-Executor gebunden. Sie bauen das Image mit \texttt{docker build} und starten es mit \texttt{docker run}, brauchen also direkten Zugriff auf den Docker-Daemon des Zielhosts. So bleibt die Prüfung im isolierten Container, während die Auslieferung auf dem Host selbst arbeitet.
|
||||
|
||||
Im Rahmen der Live-Präsentation wird der Lebenszyklus einer Codeänderung in drei Schritten gezeigt. Zunächst wird auf einem Feature-Branch eine sichtbare Änderung am Anwendungscode vorgenommen und in das Repository übertragen. Die Stages \texttt{lint} und \texttt{unit-test} laufen automatisch durch. Danach wird der Job \texttt{deploy:staging} über die Weboberfläche freigegeben. Die Änderung ist unmittelbar unter der Staging-Adresse sichtbar, ohne dass sie bereits im Hauptzweig liegt. Den Abschluss bildet der Merge Request gegen \texttt{main}. Wird er akzeptiert, durchläuft eine neue Pipeline auf \texttt{main} erneut \texttt{lint} und \texttt{unit-test} und löst anschliessend automatisch \texttt{deploy:prod} aus. Die neue Version läuft damit ohne weiteren Eingriff in der Produktion.
|
||||
|
||||
\section{Selbstgehostete Lösung}\label{sec:selfhosted}
|
||||
\authornote{Christopher Schmitt}
|
||||
|
||||
|
||||
Neben dem Bezug als \ac{SaaS} lässt sich GitLab auch auf eigener Hardware selbst betreiben. Diese Variante wird von GitLab als \emph{Self-Managed} bezeichnet \cite{gitlab_gitlab_nodate}. Sie kommt hauptsächlich in zwei Fällen zum Einsatz. Entweder erlauben Datenschutz- oder Compliance-Vorgaben nicht, dass Quellcode auf fremden Servern liegt. Oder die \ac{SaaS}-Variante wird mit steigender Nutzerzahl zu teuer. Installation, Konfiguration, Aktualisierungen und Sicherungen müssen dafür allerdings selbst übernommen werden \cite{painter_practical_2024}. Im Folgenden wird beschrieben, wie eine solche Instanz im Rahmen dieser Arbeit auf einem privaten Linux-Server aufgesetzt wurde und welcher Aufwand dabei anfiel.
|
||||
|
||||
\subsection{Installation}
|
||||
|
|
@ -355,9 +357,9 @@ stages:
|
|||
|
||||
Der wesentliche Vorteil des Omnibus-Paketes ist, dass die einzelnen Komponenten nicht selbst installiert und aufeinander abgestimmt werden müssen. Ein einzelner Aufruf des Paketmanagers genügt. Auf debian-basierten Systemen wird das GitLab-Paketrepository einmalig hinzugefügt und anschliessend \texttt{apt install gitlab-ce} ausgeführt \cite{gitlab_gitlab_nodate}.
|
||||
|
||||
Für diese Arbeit wurde GitLab \ac{CE} auf einem privaten Linux-Server installiert. Als Betriebssystem kam eine aktuelle LTS-Variante von Ubuntu zum Einsatz. GitLab empfiehlt für eine produktive Instanz mit bis zu 20 aktiven Nutzern mindestens vier Kerne und 8\,GiB Arbeitsspeicher \cite{gitlab_gitlab_nodate}. Da die verwendete Hardware diesen Richtwert beim Arbeitsspeicher nicht vollständig erreicht, wurde zusätzlich eine Swap-Datei eingerichtet, um Lastspitzen während des ersten \texttt{reconfigure}-Laufs aufzufangen. Die Installation selbst lief unauffällig. Das Paket wurde heruntergeladen und entpackt. Das mitgelieferte Skript \texttt{gitlab-ctl reconfigure} brachte die Instanz innerhalb weniger Minuten in einen lauffähigen Zustand. Eine erste Anmeldung über die Weboberfläche war direkt möglich.
|
||||
Für diese Arbeit wurde GitLab \ac{CE} in Version 18.11 auf einem privaten Linux-Server unter Ubuntu 24.04 LTS installiert. GitLab empfiehlt für eine produktive Instanz mit bis zu 20 aktiven Nutzern mindestens vier Kerne und 8\,GiB Arbeitsspeicher \cite{gitlab_gitlab_nodate}. Der verwendete Server liegt mit einer 12-Kern-CPU, 64\,GiB Arbeitsspeicher und einer NVMe-SSD deutlich darüber. Damit bleiben Reserven für Lastspitzen und spätere Dienste. Die Installation selbst lief unauffällig. Das Paket wurde heruntergeladen und entpackt. Das mitgelieferte Skript \texttt{gitlab-ctl reconfigure} brachte die Instanz innerhalb weniger Minuten in einen lauffähigen Zustand. Eine erste Anmeldung über die Weboberfläche war direkt möglich.
|
||||
|
||||
Eine Besonderheit des hier beschriebenen Aufbaus betrifft die Erreichbarkeit der Instanz. Anstatt die Instanz über eine öffentliche Domain freizugeben, wird die gesamte Kommunikation über ein privates Overlay-Netzwerk auf VPN-Basis abgewickelt. Der Webserver \texttt{nginx} bindet sich dadurch ausschliesslich an die lokale Schnittstelle. Externe Anfragen aus dem Overlay-Netzwerk werden über einen vorgelagerten Proxy entgegengenommen und auf den lokalen Port weitergereicht. Ein zugehöriges \ac{TLS}-Zertifikat wird vom Overlay-Dienst selbst bereitgestellt. Auf eine direkte Exposition der Ports 80 und 443 zum öffentlichen Internet wird auf diese Weise vollständig verzichtet.
|
||||
Eine Besonderheit des hier beschriebenen Aufbaus betrifft die Erreichbarkeit der Instanz. Anstatt sie über eine öffentliche Domain freizugeben, läuft die gesamte Kommunikation über ein privates Overlay-Netzwerk auf VPN-Basis. Der Webserver \texttt{nginx} bindet sich dabei nur an die lokale Schnittstelle und ist von aussen nicht direkt erreichbar. Ein Proxy des Overlay-Dienstes nimmt die Anfragen auf demselben Host entgegen, beendet die \ac{TLS}-Verbindung und reicht sie an den lokalen Port weiter. Das zugehörige Zertifikat stellt der Overlay-Dienst selbst aus. Zugriff haben nur Geräte, die im Overlay-Netzwerk freigegeben sind. Die Ports 80 und 443 sind zu keinem Zeitpunkt zum öffentlichen Internet geöffnet.
|
||||
|
||||
\subsection{Konfiguration}
|
||||
|
||||
|
|
@ -382,7 +384,7 @@ stages:
|
|||
}
|
||||
\end{table}
|
||||
|
||||
Eine geführte Erstkonfiguration, wie sie die \ac{SaaS}-Variante bietet, existiert nicht. Nach der Installation läuft die Instanz zwar, befindet sich aber in einem generischen Standardzustand. Alles Projektspezifische muss selbst zusammengesucht werden. Im hier beschriebenen Aufbau war dies der zeitaufwendigste Teil der Bereitstellung. Die reine Paketinstallation war in unter zehn Minuten abgeschlossen. Das anschliessende Einrichten von Erreichbarkeit, Mailversand, Container Registry und Runnern beanspruchte mehrere Stunden, weil die Optionen in \texttt{gitlab.rb} ohne Vorwissen über die jeweiligen Komponenten schwer einzuordnen sind \cite{painter_practical_2024}.
|
||||
Eine geführte Erstkonfiguration, wie sie die \ac{SaaS}-Variante bietet, existiert für die Self-Managed-Variante nicht \cite{painter_practical_2024}. Nach der Installation läuft die Instanz in einem generischen Standardzustand. In der Praxis fiel der Aufwand aber geringer aus als erwartet. Die reine Paketinstallation war in unter zehn Minuten abgeschlossen. An \texttt{gitlab.rb} mussten nur wenige Optionen angepasst werden, im Wesentlichen die Erreichbarkeit über das Overlay-Netzwerk und das lokale Binding von \texttt{nginx}. Der grösste Teil der projektspezifischen Einrichtung wie Gruppen, Projekte, Nutzer und die Registrierung der Runner liess sich anschliessend über die Weboberfläche erledigen. Ein tieferer Eingriff in die Konsole war dafür nicht nötig.
|
||||
|
||||
\subsection{Betrieb}
|
||||
|
||||
|
|
@ -393,13 +395,57 @@ stages:
|
|||
\section{Evaluierung}
|
||||
|
||||
\subsection{Vorgehensweise}
|
||||
|
||||
%Kriterien ausdenken
|
||||
|
||||
\authornote{Christopher Schmitt}
|
||||
|
||||
Bewertet wird GitLab als selbstbetriebene Plattform. Wo der Aufbau dieser Arbeit es zulässt, stützt sich die Bewertung auf die in Abschnitt~\ref{sec:selfhosted} beschriebene selbstgehostete \ac{CE}-Instanz und die \ac{CI}/\ac{CD}-Pipeline aus dem Anwendungsbeispiel. Kompatibilität, Skalierbarkeit und Dokumentation lassen sich an einer einzelnen Instanz nicht erproben und werden auf Ebene der Plattform beurteilt.
|
||||
|
||||
Die Bewertung folgt einem Katalog aus sieben Kriterien: Funktionalität, Performanz, Nachhaltigkeit, Sicherheit, Kompatibilität, Skalierbarkeit und Dokumentation. Funktionalität, Performanz, Sicherheit und Kompatibilität orientieren sich an etablierten Softwarequalitätsmerkmalen. Nachhaltigkeit, Skalierbarkeit und Dokumentation kommen hinzu, weil sie im Eigenbetrieb stärker ins Gewicht fallen als bei einer fertig betriebenen \ac{SaaS}-Lösung.
|
||||
|
||||
Innerhalb jedes Kriteriums wird qualitativ argumentiert. Herangezogen werden die offizielle Dokumentation, die zitierte Literatur und die eigenen Beobachtungen aus Installation und Betrieb. Die Performanz bildet die Ausnahme. Hier werden die Laufzeiten der Demo-Pipeline gemessen und gegenübergestellt.
|
||||
|
||||
Zwei Grenzen sind vorab zu nennen. Die Instanz läuft auf einem einzelnen Server, Aussagen zur Skalierbarkeit stützen sich daher auf die Architektur und die Herstellerangaben und nicht auf einen eigenen Lasttest. Die gemessenen Laufzeiten stammen aus einer bewusst kleinen Anwendung und zeigen Grössenordnungen, keine repräsentativen Benchmarks.
|
||||
|
||||
\subsection{Funktionalität}
|
||||
|
||||
\subsection{Performanz}
|
||||
|
||||
\authornote{Christopher Schmitt}
|
||||
|
||||
Die reine Nutzlast der \ac{CI}-Jobs im Anwendungsbeispiel ist winzig. Auf einem Entwicklungsrechner mit 32 Kernen, warmem npm-Cache und zuvor leerem \texttt{node\_modules} braucht \texttt{npm ci} für die 221 Pakete des Projekts rund eine Sekunde, \texttt{npm run lint} etwa 0,3 Sekunden und \texttt{npm test} etwa 0,8 Sekunden. Tabelle~\ref{tab:perf-local} listet diese lokalen Referenzwerte. Sie sind keine Runner-Zeiten und sollen nur die Grössenordnung der eigentlichen Arbeit zeigen.
|
||||
|
||||
\begin{table}[H]
|
||||
\centering
|
||||
\caption{Lokale Referenzmessungen der Pipeline-Schritte auf einem Entwicklungsrechner, nicht auf dem GitLab Runner}
|
||||
\label{tab:perf-local}
|
||||
\begin{tabular}{@{}lll@{}}
|
||||
\toprule
|
||||
Schritt & Befehl & Gemessene Zeit \\ \midrule
|
||||
Abhängigkeiten & \texttt{npm ci} & rund 1\,s \\
|
||||
Lint & \texttt{npm run lint} & rund 0,3\,s \\
|
||||
Test & \texttt{npm test} & rund 0,8\,s \\ \bottomrule
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
|
||||
Auf dem Runner sieht das anders aus. Gemessen wurde die Demo-Pipeline auf der selbstgehosteten Instanz, einmal in der ursprünglichen Konfiguration und einmal mit zwei Korrekturen am Cache. Tabelle~\ref{tab:perf-runner} stellt die drei Varianten gegenüber. Die eigentliche Prüfung liegt in jeder Variante unter einer Sekunde. Den Rest eines Jobs verbrauchen der Start des \texttt{node:20-alpine}-Containers, das Klonen des Repositorys, die Installation der Abhängigkeiten und, je nach Konfiguration, das Sichern und Wiederherstellen des Caches.
|
||||
|
||||
\begin{table}[H]
|
||||
\centering
|
||||
\caption{Runner-Laufzeiten der Demo-Pipeline nach Cache-Konfiguration in Sekunden, Steady-State mit Cache-Treffer, reine Ausführungszeit ohne die Wartezeit in der Warteschlange}
|
||||
\label{tab:perf-runner}
|
||||
\begin{tabular}{@{}lrr@{}}
|
||||
\toprule
|
||||
Cache-Konfiguration & \texttt{lint} & \texttt{unit-test} \\ \midrule
|
||||
\texttt{node\_modules} (Original) & 10,5 & 11,2 \\
|
||||
ohne Cache & 7,6 & 7,8 \\
|
||||
npm-Paketspeicher \texttt{.npm} & 7,7 & 8,3 \\ \bottomrule
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
|
||||
Die ursprüngliche Konfiguration cacht das falsche Verzeichnis. Sie legt \texttt{node\_modules} unter einem aus \texttt{package.json} berechneten Schlüssel ab. Der erste Schritt jedes Jobs ist aber \texttt{npm ci}, und dieser Befehl löscht \texttt{node\_modules} vor der Installation und baut es neu auf. Ob der Cache traf oder nicht, änderte an der Laufzeit darum nichts. Im Treffer-Lauf meldet der Runner \texttt{Successfully extracted cache}, im Lauf ohne Treffer \texttt{Cache file does not exist}, und \texttt{npm ci} installiert in beiden Fällen die 221 Pakete in rund zwei Sekunden neu. Der Cache kostet hier sogar Zeit, statt sie zu sparen. Das grosse \texttt{node\_modules}-Verzeichnis wird bei jedem Job gesichert und wiederhergestellt, ohne je genutzt zu werden. Nach Zampetti et al.~\cite{zampetti_empirical_2020} ist eine solche Fehlkonfiguration von Cache und Artefakten ein häufiger Schwachpunkt von CI-Pipelines.
|
||||
|
||||
Zwei Korrekturen wurden gemessen. Ohne Cache entfällt das Sichern und Wiederherstellen, und der \texttt{lint}-Job sinkt von 10,5 auf 7,6 Sekunden, der \texttt{unit-test}-Job von 11,2 auf 7,8. Allein das Entfernen des unwirksamen Caches spart also rund drei Sekunden je Job. Die zweite Korrektur cacht statt \texttt{node\_modules} den npm-Paketspeicher unter \texttt{.npm} und ruft \texttt{npm ci} mit den Optionen \texttt{-{}-cache .npm} und \texttt{-{}-prefer-offline} auf. Bei einem Treffer installiert \texttt{npm ci} dann aus dem lokalen Speicher, die Installationszeit sinkt von rund zwei Sekunden auf knapp eine (981\,ms im Messlauf). Am Job ändert das hier kaum etwas, weil der eingesparte Download durch den Aufwand für den \texttt{.npm}-Cache wieder aufgewogen wird. Der Paket-Cache lohnt sich erst, wenn der Download die Installation bestimmt. Auf dieser Instanz mit schnellem Zugriff auf die Registry ist das nicht der Fall. Caches beschleunigen wiederholte Läufe also nicht von selbst. Sie sind zudem, anders als Artefakte, über Jobgrenzen hinweg nicht garantiert verfügbar \cite{cowell_automating_2023}.
|
||||
|
||||
Der \ac{DAG} über das Schlüsselwort \texttt{needs} ist hier kaum ein Hebel. Die Demo-Pipeline läuft nahezu linear, erst \texttt{lint}, dann \texttt{test}, dann \texttt{deploy}, und bietet kaum Parallelität, die sich lohnt. Bei grösseren Pipelines sieht das anders aus. Dort ist die Parallelisierung serieller Stages der zentrale Hebel, und Zampetti et al.~\cite{zampetti_empirical_2020} führen serielle Stages, die parallel laufen könnten, als typischen Konfigurationsfehler. Der \texttt{deploy:staging}-Job baut das Docker-Image neu. Das Image würde die Abhängigkeiten ein weiteres Mal installieren, doch der Docker-Layer-Cache hält den Abhängigkeits-Layer vor und überspringt diesen Schritt. So lief der Job im Messlauf in 2,3 Sekunden durch. Erst eine Änderung an \texttt{package.json} macht den Cache-Layer ungültig und löst die Installation erneut aus.
|
||||
|
||||
\subsection{Nachhaltigkeit}
|
||||
|
||||
Unter Betrachtung der in \ref{sec:scalability} erwähnten Referenzarchitektur wird betrachtet inwiefern die von GitLab vorgeschlagenen Architekturen sich bezüglich ihres Stromverbrauchs bemessen. GitLab liefert Empfehlungen für virtuelle Server für die Anbieter \ac{GCP}, \ac{AWS} und Azure. Ausgenommen von GPUs sind CPU und Arbeitsspeicher die Komponenten mit dem höchsten Stromverbrauch \cite{davyEstimatingAWSEC22022}. im folgenden wird eine Schätzung abgeben, die auf Daten von \ac{AWS} Instanzen basieren und sich auf die Leistung von CPU und Arbeitsspeicher beschränkt. Im konkreten werden Instanzen der Typen \texttt{c5, c5n} und \texttt{m5} in der GitLab Empfehlung erwähnt. \\
|
||||
|
|
@ -554,9 +600,8 @@ stages:
|
|||
\section{Ausblick}
|
||||
|
||||
\section{Zusammenfassung}
|
||||
|
||||
|
||||
|
||||
\authornote{Christopher Schmitt}
|
||||
|
||||
\section*{Abkürzungsverzeichnis}
|
||||
\begin{acronym}[Abkürzungsverzeichnis]
|
||||
\acro{IDE}{Integrated Development Environment}
|
||||
|
|
@ -577,6 +622,7 @@ stages:
|
|||
\acro{TLS}{Transport Layer Security}
|
||||
\acro{DAG}{Directed Acyclic Graph}
|
||||
\acro{URL}{Uniform Resource Locator}
|
||||
\acro{FQDN}{Fully Qualified Domain Name}
|
||||
\acro{SP}{Special Publication}
|
||||
\acro{NIST}{National Institute of Standards and Technology}
|
||||
\acro{ISO}{Internationale Organisation für Normung}
|
||||
|
|
|
|||
|
|
@ -9,47 +9,50 @@
|
|||
\centering
|
||||
\resizebox{\columnwidth}{!}{%
|
||||
\begin{tikzpicture}[
|
||||
node distance=6mm and 8mm,
|
||||
node distance=8mm and 9mm,
|
||||
font=\footnotesize\sffamily,
|
||||
every node/.style={align=center},
|
||||
comp/.style={draw, rounded corners=2pt, minimum height=8mm, minimum width=20mm, fill=blue!5},
|
||||
store/.style={draw, rounded corners=2pt, minimum height=8mm, minimum width=20mm, fill=green!8},
|
||||
ext/.style={draw, dashed, rounded corners=2pt, minimum height=8mm, minimum width=20mm, fill=gray!5},
|
||||
comp/.style={draw, rounded corners=2pt, minimum height=9mm, minimum width=24mm, fill=blue!5},
|
||||
store/.style={draw, rounded corners=2pt, minimum height=9mm, minimum width=24mm, fill=green!8},
|
||||
ext/.style={draw, dashed, rounded corners=2pt, minimum height=9mm, minimum width=24mm, fill=gray!5},
|
||||
arr/.style={-{Stealth[length=2mm]}, thick},
|
||||
extarr/.style={-{Stealth[length=2mm]}, thick, dashed},
|
||||
]
|
||||
|
||||
% Ebene 1: Eingang
|
||||
\node[comp] (browser) {Browser / git-Client};
|
||||
\node[comp, right=of browser] (nginx) {nginx\\(Reverse Proxy)};
|
||||
% Eingangsebene
|
||||
\node[comp] (nginx) {nginx\\(Reverse Proxy)};
|
||||
\node[comp, above=of nginx] (browser) {Browser /\\git-Client};
|
||||
\node[comp, left=of nginx] (registry) {Container\\Registry};
|
||||
\node[ext, right=of nginx] (runner) {GitLab Runner\\(extern)};
|
||||
|
||||
% Ebene 2: Anwendungsschicht
|
||||
% Anwendungsebene
|
||||
\node[comp, below=of nginx] (puma) {Puma\\(Rails-App)};
|
||||
\node[comp, right=of puma] (sidekiq) {Sidekiq\\(Hintergrundjobs)};
|
||||
\node[ext, right=of sidekiq] (mail) {SMTP-Server\\(extern)};
|
||||
|
||||
% Ebene 3: Datenschicht
|
||||
\node[store, below=of puma] (pg) {PostgreSQL\\(Daten)};
|
||||
% Datenebene
|
||||
\node[store, below=of puma] (gitaly) {Gitaly\\(Repositorien)};
|
||||
\node[store, right=of gitaly] (pg) {PostgreSQL\\(Daten)};
|
||||
\node[store, right=of pg] (redis) {Redis\\(Cache, Queue)};
|
||||
\node[store, left=of pg] (gitaly) {Gitaly\\(Repo-Storage)};
|
||||
|
||||
% Ebene 4: Add-Ons
|
||||
\node[comp, below=of pg] (registry) {Container\\Registry};
|
||||
\node[ext, right=of registry] (runner) {GitLab Runner\\(extern)};
|
||||
\node[ext, left=of registry] (mail) {SMTP-Server\\(extern)};
|
||||
|
||||
% Verbindungen
|
||||
% Web-Anfragen
|
||||
\draw[arr] (browser) -- (nginx);
|
||||
\draw[arr] (nginx) -- (registry);
|
||||
\draw[arr] (nginx) -- (puma);
|
||||
\draw[extarr] (runner) -- (nginx);
|
||||
|
||||
% Anwendungsebene -> Datenebene
|
||||
\draw[arr] (puma) -- (gitaly);
|
||||
\draw[arr] (puma) -- (pg);
|
||||
\draw[arr] (puma) -- (redis);
|
||||
\draw[arr] (puma.west) to[bend right=20] (gitaly.north);
|
||||
\draw[arr] (sidekiq) -- (pg);
|
||||
\draw[arr] (sidekiq) -- (redis);
|
||||
\draw[arr] (sidekiq.south) to[bend left=15] (pg.east);
|
||||
\draw[arr] (puma.south) to[bend left=10] (registry.north);
|
||||
\draw[arr, dashed] (runner.north) to[bend left=15] (puma.east);
|
||||
\draw[arr, dashed] (puma.west) to[out=180, in=160, looseness=1.1] (mail.west);
|
||||
\draw[arr] (puma.south east) -- (redis.north west);
|
||||
|
||||
% Hintergrundjobs -> externer Mailversand
|
||||
\draw[extarr] (sidekiq) -- (mail);
|
||||
|
||||
\end{tikzpicture}%
|
||||
}
|
||||
\caption{Komponenten einer Self-Managed-Instanz im Omnibus-Paket. Gestrichelte Komponenten laufen ausserhalb des Omnibus-Pakets und werden separat angebunden.}
|
||||
\caption{Komponenten einer Self-Managed-Instanz im Omnibus-Paket. Durchgezogen die im Paket enthaltenen Dienste, gestrichelt die extern angebundenen Komponenten (Runner, Mailversand).}
|
||||
\label{fig:omnibus-arch}
|
||||
\end{figure}
|
||||
|
|
|
|||
|
|
@ -40,16 +40,16 @@
|
|||
\node[job, below=of s4] (j4a) {staging};
|
||||
\node[job, below=of j4a] (j4b) {prod (manual)};
|
||||
|
||||
% needs/DAG: container braucht nur bundle, nicht alle vorigen
|
||||
\draw[dagarr] (j3a.east) to[bend left=55] node[midway, right=1pt, font=\tiny\itshape] {needs} (j3b.east);
|
||||
% needs/DAG: container startet sobald unit fertig ist, wartet nicht auf integration
|
||||
\draw[dagarr] (j2a.east) to[bend right=28] node[midway, above, sloped, font=\tiny\itshape] {needs} (j3b.west);
|
||||
|
||||
% Pipeline-Klammer drumherum
|
||||
\begin{pgfonlayer}{background}
|
||||
\node[draw, dotted, rounded corners=4pt, fit=(s1)(s4)(j2b)(j3b)(j4b), inner sep=4mm, label={[anchor=north west, font=\small\bfseries]north west:Pipeline}] {};
|
||||
\node[draw, dotted, rounded corners=4pt, fit=(s1)(s4)(j2b)(j3b)(j4b), inner sep=5mm, label={[anchor=south west, font=\small\bfseries, yshift=1.5mm]north west:Pipeline}] {};
|
||||
\end{pgfonlayer}
|
||||
|
||||
\end{tikzpicture}%
|
||||
}
|
||||
\caption{Aufbau einer Pipeline aus Stages und Jobs. Durchgezogene Pfeile: starre Stage-Reihenfolge. Gestrichelter Pfeil: needs-Beziehung (\acs{DAG}-Modus), die unnötiges Warten verkürzt.}
|
||||
\caption{Aufbau einer Pipeline aus Stages und Jobs. Durchgezogene Pfeile zeigen die starre Stage-Reihenfolge. Der gestrichelte Pfeil ist eine \texttt{needs}-Beziehung im \acs{DAG}-Modus: \texttt{container} startet, sobald \texttt{unit} fertig ist, und wartet nicht auf \texttt{integration}.}
|
||||
\label{fig:pipeline-stages}
|
||||
\end{figure}
|
||||
|
|
|
|||
Loading…
Reference in New Issue