GitLab Continuous Integration

Mit der neuen Hauptversion 8 wurde GitLab offiziell um eine eigene Continuous Integration (CI) Komponente ergänzt, die bisher als dediziertes Projekt gepflegt wurde. Diese gab es bereits früher. Während man früher externe Lösungen, wie beispielsweise Jenkins, verwenden musste, kann man nun gepflegte Projekte leicht automatisiert übersetzen, testen, etc.

Doch wozu dient eine CI? CI findet hauptsächlich in der professionellen Anwendungsentwicklung Verwendung und automatisiert maßgeblich Teilschritte zur Programmübersetzung und Erhöhung der Quellcode-Qualität. Um mal einige praktikable Beispiele zu nennen:

  • Übersetzung von Teilprogrammen
  • Zusammensetzen des Gesamtprogramms
  • Tests von Teilprogrammen anhand Spezifikationen (Test-Units)
  • Datei-Replikation auf andere Systeme (z. B. andere Entwicklungssysteme)

Dabei integriert sich die CI in eine Versionsverwaltung, um sich beispielsweise auf die neuesten Commits zu beziehen oder automatisiert auf Code-Änderungen zu reagieren.

Ich habe bisher GitLab in Kombination mit Jenkins verwendet, um automatisiert RPM-Pakete für mehrere Enteprise Linux-Architekturen und -Versionen zu erstellen. Soweit ich das beurteilen kann (ich bin kein Anwendungsentwickler), bietet Jenkins gegenüber GitLab CI eindeutig mehr Flexibilität und die größere Feature-Auswahl, ist aber auch deutlich komplexer zu konfigurieren. Bei der Implementation meiner Lösung seinerzeits gab es bei mir viel Nachlesebedarf. GitLab CI ist simpler und dürfte sich deswegen vermutlich nicht für alle Kundengrößen eignen; für mein Szenario ist es allerdings eindeutig die bessere Wahl. Ich habe das hier vorgestellte Konzept binnen weniger Stunden implementiert und getestet.

Die meisten CI-Lösungen verwenden auf einem oder mehreren Build-Systemen entsprechende Agenten bzw. Runner-Prozesse, um die Prozesse (Code kompilieren, Dateien verteilen,...) zu steuern. Während bei Jenkins hier eine Java-Anwendung zum Einsatz kommt, bietet GitLab CI mehrere Runner an.

Vorbereitungen

Um GitLab CI zu verwenden, muss GitLab in der Version 8.0 oder höher installiert sein. Das Feature wird projektweise aktiviert und konfiguriert. Für die Steuerung der einzelnen Prozesse wird eine simple YAML-Datei mit dem Namen .gitlab-ci.yml im Hauptordner des Projekts erstellt - dazu gleich mehr.

Projekt-Einstellungen

Um CI für ein GitLab-Projekt zu aktivieren, genügen die folgenden Schritte:

CI Runner-Konfiguration

  1. Auswahl des Projekts, Anklicken von Settings und Project Settings.
  2. Anklicken von Builds und Save Changes.
  3. Auswahl von CI Settings, um erweiterte Parameter zu setzen. Dazu zählen u.a. Timeouts und automatische Builds.
  4. Anklicken von Save Changes.
  5. Auswahl von Runners, Notieren/Kopieren der CI-URL und dem CI-Token - diese Informationen werden später benötigt, um die Runner zu registrieren.

Zur Steuerung der einzelnen Schritte auf dem Runner muss die YAML-Konfiguration erstellt werden. In dieser werden einzelne Teilschritte, Skripte und eventuelle Abhängigkeiten zu Runnern anhand Tags definiert.

Ein einfaches Beispiel sieht wie folgt aus:

 1before_script:
 2  - hostname
 3
 4build:
 5  script:
 6    - "javac *.java"
 7
 8clean:
 9  script:
10    - "find . -type f -name '*.tmp' -exec rm {} ;"

In diesem Beispiel werden zwei Jobs definiert:

  • build - Skript zur Kompilierung aller Java-Quellcodes im aktuellen Verzeichnis
  • clean - Entfernen aller Dateien, die mit .tmp enden

Vor dem Ausführen der Jobs wird das hostname-Kommando ausgeführt. Der Job wird auf einem System aus dem Pool für das Projekt verfügbarer Runner ausgeführt. Durch die Verwendung von Tags kann man dieses Verhalten weiter einschränken:

 1before_script:
 2  - hostname
 3
 4build_stable_jars:
 5  script:
 6    - "javac *.java"
 7  tags:
 8    - java
 9  only:
10    - master
11
12build_hipster_swag:
13  script:
14    - "bundle exec swag"

In diesem Beispiel gibt es erneut zwei Jobs:

  • build_stable_jars - Übersetzen von Java-Quellcodes aus der master-Branch des Repositories; wird lediglich auf Runnern mit dem java-Tag ausgeführt
  • build_hipster_swag - Paketieren von Ruby-Anwendungen; kann auf allen Runnern ausgeführt werden und beschränkt sich auf keine Branch

Auf der GitLab-Webseite gibt es weitere Beispiele.

Runner-Einstellungen

Für die Runner-Anwendung empfiehlt sich die Erstellung eines dedizierten System-Benutzers. Für diesen Benutzer müssen auch ein Passwort gesetzt und ein SSH-Key generiert werden. Beim SSH-Key ist wichtig, dass keine Passphrase angegeben wird, um ein passwortloses Steuern zu ermöglichen. Diese Informationen werden nachher in GitLab hinterlegt, um den Runner-Prozess zu kontrollieren:

1# useradd --comment "GitLab service user" --system -m su-gitlab-ci
2# passwd su-gitlab-ci
3# su - su-gitlab-ci
4$ ssh-keygen

Für GitLab CI stehen mehrere Clients zur Verfügung. Neben dem offiziellen, in Go entwickelten, Client gibt es auch eine Scala/Java- und Node-Implementation.

Ich persönlich habe mich für den offiziellen Client entschieden. Neben Windows, OS X und FreeBSD unterstützt dieser auch Debian- und Red Hat Enterprise Linux-basierende Distributionen. Alternativ gibt es auch distributionsunabhängige Binärpakete. Für unterstützte Linux-Distributionen empfiehlt GitLab die Verwendung eines Skripts zur automatischen Konfiguration der Paketquellen. Ich habe mich dafür entschieden, diese Konfiguration selbst vorzunehmen und die folgenden Repository-URLs zur Konfiguration der Paketquellen verwendet:

Release/Architektur URL
Enterprise Linux 7, x86_64 https://packages.gitlab.com/runner/gitlab-ci-multi-runner/el/7/x86_64
Enterprise Linux 6, x86_64 https://packages.gitlab.com/runner/gitlab-ci-multi-runner/el/6/x86_64
Enterprise Linux 6, i686 https://packages.gitlab.com/runner/gitlab-ci-multi-runner/el/6/i686
Fedora 23, x86_64 https://packages.gitlab.com/runner/gitlab-ci-multi-runner/fedora/23/x86_64
Fedora 23, i686 https://packages.gitlab.com/runner/gitlab-ci-multi-runner/fedora/23/i686
Fedora 22, x86_64 https://packages.gitlab.com/runner/gitlab-ci-multi-runner/fedora/22/x86_64
Fedora 22, i686 https://packages.gitlab.com/runner/gitlab-ci-multi-runner/fedora/22/i686
Note

Der GPG-Key für Paketsignierung ist unter https://packages.gitlab.com/gpg.key zu finden.

Zur Installation genügt der folgende Yum-Aufruf:

1# yum install gitlab-ci-multi-runner

Nachdem alle Runner installiert wurden, müssen diese mit GitLab registriert werden. Dazu wird das gitlab-runner Kommando auf dem Build-System ausgeführt. Während der Konfiguration werden die CI-URL und der dazugehörige Token angegeben. Darüber hinaus können pro System Tags vergeben werden. Insbesondere bei Projekten, die auf unterschiedlichen Systemen erstellt werden sollen (z. B. RPM-Paketebau) ist das äußerst hilfreich. Zur Steuerung des Runners stehen lokale Shells, SSH, Parallels (für VMs) oder Docker zur Verfügung. Bei der Verwendung von SSH müssen gültige Login-Daten und der zuvor generierte SSH-Key angegeben werden.

 1# sudo gitlab-runner register
 2 Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/ci):
 3 http://gitlab.localdomain.loc/ci
 4 Please enter the gitlab-ci token for this runner:
 5 xxx
 6 Please enter the gitlab-ci description for this runner:
 7 [gitlab.localdomain.loc]:
 8 Please enter the gitlab-ci tags for this runner (comma separated):
 9 rpm764,generic
10 INFO[0035] 7ab95543 Registering runner... succeeded
11 Please enter the executor: ssh, shell, parallels, docker, docker-ssh:
12 ssh
13 Please enter the SSH server address (eg. my.server.com):
14 gitlab.localdomain.loc
15 Please enter the SSH server port (eg. 22):
16 22
17 Please enter the SSH user (eg. root):
18 su-gitlab-ci
19 Please enter the SSH password (eg. docker.io):
20 myPassword
21 Please enter path to SSH identity file (eg. /home/user/.ssh/id_rsa):
22 /home/su-gitlab-ci/.ssh/id_rsa
23 INFO[0143] Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

Der erste Build

Zeit, mit etwas Einfachem zu beginnen. Als Beispiel dient eine einfache kleine C-Anwendung namens test.c:

1#include<stdio.h>
2
3int main() {
4    printf("GitLab CI rocks!n");
5    return 0;
6}

Die CI-Konfigurationsdatei .gitlab-ci.yml sieht wie folgt aus:

1build:
2  script:
3    - "rm *.out || true"
4    - "gcc *.c"

In diesem Beispiel wird ein Job build definiert, der zwei Kommandos ausführt. Das erste Kommando löscht eventuell vorher übersetzte C-Programme, während der zweite Aufruf den Quellcode übersetzt.

Nach einem erfolgten Commit wird automatisch die CI ausgelöst. Ersichtlich ist das durch einen Eintrag links im Builds-Menü - hier ist im Idealfall eine 1 zu sehen (für einen aktuell laufenden Build-Prozess). Ein Klick auf das Menü öffnet die Übersicht aktueller und vergangener Builds. Mit einem Klick auf einzelne Builds lassen sich Konsolenausgaben und Git-Informationen (Commit-Nummer, Branch, Autor) anzeigen. Per Mausklick auf das entsprechende Symbol lassen sich Builds wiederholen.

GitLab bietet auch die Möglichkeit, Build zeitgesteuert auszuführen - z. B. spätestens alle 5 Stunden. In diesem Fall würde nach einer gewissen Zeit ein Build ausgelöst werden, sofern kein Commit erfolgt. Dieses Verhalten kann in den Projekt-Einstellungen unterhalb CI Settings konfiguriert werden (Build Schedule). In diesem Dialog findet sich auch ein Status-Bild (Status badge), welches beispielsweise in die README.md Datei das Repositories eingebunden werden kann - so ist der aktuelle Build-Status immer ersichtlich.

Ausblick

Ich bin mit der Lösung sehr zufrieden; mit Jenkins hatte ich leider häufig Probleme - nicht zuletzt wegen Java. Häufige Updates und Re-Konfigurationen waren die Folge. GitLab CI integriert sich wunderbar in die Oberfläche, die ich ohnehin schon zum Entwickeln verwende. Für mich bedeutet das weniger Administrations- und Verwaltungsaufwand. Je nach Projektumfang dürfte die integrierte CI-Lösung nicht allen Anforderungen gewachsen sein - für kleinere Setups ist sie allerdings ideal. Ich bin gespannt, inwiefern GitLab die Funktionen erweitert. 🙂

Auf der GitLab-Webseite gibt es weitere Informationen.

Übersetzungen: