Blog

Teaser: Gitlab und Perl

28.12.2020 // Gregor Goldbach

Gitlab bietet viel Funktionalität, die für Perl-Projekte sinnvoll genutzt werden kann. Unsere Schulung soll am praktischen Beispiel einer kleinen CPAN-Distribution einen Einblick in die Möglichkeiten bieten.

Zunächst geben wir einen kurzen Überblick über »Gitlab an sich«. Dafür verwalten wir die Quellen eines Beispielprojektes innerhalb von Gitlab. Die Metadaten dieser Beispieldistribution pflegen wir innerhalb der Distribution in einer Form, die dann später von metaCPAN ausgewertet wird.

Da das weit verbreitete Github sich mit seinen »pull requests« (PRs) von den »merge requests« (MRs) Gitlabs leicht unterscheidet, gehen hierauf im Anschluss etwas ausführlicher ein.

Wir zeigen außerdem, wie sich die Entwicklungsaufgaben mit Tickets abbilden lassen und wie die Arbeitsabläufe über Labels übersichtlich an Boards dargestellt werden können.

Entwicklerinnen können ihr Projekt in einem Wiki dokumentieren, das als eigenes Git-Repository geführt wird.

Der für Entwickler vermutlich interessanteste Teil nimmt den größten Raum ein: Die CI-Stufe von Gitlab. Was bedeutet »continuous integration« im Zusammenhang mit Gitlab? Wie konfigurieren Team-Mitglieder die CI ihres Projekts? Wir konfigurieren die CI-Stufe für unser Beispielprojekt, das auf Dist::Zilla basiert, und lassen die automatisierten Tests nach jedem Commit in Gitlab durchlaufen.

Neben Grundkenntnissen in Git und Perl benötigen die Teilnehmer einen eigenen Laptop. Diese Schulung ermöglicht den Teilnehmern, Perl-Projekte in Gitlab zu bearbeiten und beliebige Befehle in Docker-Images durch Gitlabs CI-Stufe ausführen zu lassen.

Gitlab und Perl bieten wir in einer kurzen GPW-Fassung und einer längeren Fassung.

Sie haben Interesse an der Schulung? Dann melden Sie sich bei uns.


Permalink:

Perl-Schulungen 2021

07.12.2020 // Renée Bäcker

Nachdem wir uns das Jahr 2020 Zeit genommen haben, um die Perl-Academy etwas umzubauen (mit neuem Design, der Einführung dieses Blogs, mit Gregor als zusätzlicher Trainer, ...), wollen wir heute unseren Plan für 2021 vorstellen.

Wir werden wenige feste Termine für offene Schulungen haben. Wir wollen mehr Firmenschulungen anbieten und offene Schulungen zusätzlich planen, wenn sich Interessenten melden.

Unsere Schulungsthemen werden wir am Lebenszyklus einer Anwendung ausrichten. Auch in agilen Umgebungen hat man immer wieder den Lebenszyklus in klein. Momentan haben wir vier Themen geplant:

Einsteigen werden wir mit User Story Mapping. Kein originäres Perl-Thema, aber eine ganz praktische Vorgehensweise, um eine Anwendung aus Sicht des Benutzers zu planen.

Die mit User Story Mapping geplante Software werden wir mit Mojolicious umsetzen. Dabei werden wir mit einer kleinen Version anfangen um die Grundlagen von Mojolicious kennenzulernen. Anschließend werden wir die Anwendung wachsen lassen, um die weitergehenden Fähigkeiten von Mojolicious nutzen zu können.

Natürlich muss man nicht erst die User-Story-Mapping Schulung besucht haben, um an der Mojolicious-Schulung teilzunehmen.

Das Thema Sicherheit in Perl-Anwendungen spielt natürlich auch bei Mojolicious-Anwendungen ein Thema. Wir haben eine eigene Schulung daraus gemacht, um dem Ganzen genügend Raum zu geben und uns nicht auf Mojolicious zu beschränken.

Als viertes Thema bieten wir Gitlab und Perl an. Die Versionsverwaltung begleitet die Anwendung ein Leben lang. Wir zeigen, was man abseits der reinen Versionsverwaltung mit Gitlab machen kann – von Continuous Integration bis zum Ausliefern der Anwendung.

Interesse an einer der Schulungen? Dann melden Sie sich bei uns!


Permalink:

Gitlab umziehen

04.12.2020 // Renée Bäcker

In verschiedenen Projekten setzen wir ein jeweils selbst gehostetes Gitlab ein. Gitlab bietet viel mehr als nur die Versionskontrolle. Gregor hat vor einiger Zeit schon einmal etwas dazu geschrieben, wie wir bei Perl-Services.de Gitlab benutzen.

Bei einem Projekt stand der Umzug in die Cloud zu AWS an und damit auch das Gitlab, das in dem Projekt genutzt wurde. Für das Projekt gibt es aber nicht alle Möglichkeiten der AWS-Cloud, so dass das Gitlab nicht in gewohnter Umgebung (ein Ubuntu-Linux) fortgeführt werden konnte, sondern auf ein RHEL8-System wechseln musste.

Voraussetzungen

Bevor der Umzug losgeht, muss eine Serverinstanz mit RHEL8 erstellt werden. Nach der Erstellung per SSH mit der Maschine verbinden.

Da RHEL8 kein Docker mehr unterstützt, kommt Podman zum Einsatz. Podman ist eine Container-Engine ähnlich zu Docker. Es können die Befehle von Docker genutzt werden, da Podman einen docker-Alias anlegt. Man bekommt Podman auch, wenn man

yum install docker

nutzt.

Gitlab-Image herunterladen

Wenn man von einem anderen Server migriert, muss man dort erst einmal schauen, welche Version zum Einsatz kommt. Die gleiche Version muss dann auch (erst einmal) auf dem neuen Server laufen. Wenn es neuere Versionen von Gitlab gibt, sollte nach dem Umzug dann noch das Update gemacht werden.

Die entsprechend getaggten Gitlab-Images sind unter https://hub.docker.com/r/gitlab/gitlab-ce/tags zu finden.

Auf dem neuen Server führt man einfach

docker pull gitlab/gitlab-ce:<version>-ce.0 aus.

Verzeichnisse erstellen

Damit die Daten auch persistent auf der EC2-Instanz bleiben, werden beim Starten des Containers Verzeichnisse der Instanz in den Container gemappt. Die müssen vor dem Start des Containers existieren:

$ mkdir -p /srv/gitlab/config
$ mkdir -p /srv/gitlab/data/backups
$ mkdir -p /srv/gitlab/logs

Als systemd-Service einrichten

Um anschließend Gitlab als Service starten und stoppen zu können, muss es entsprechend eingerichtet sein. Dazu in der Datei /etc/sysconfig/gitlab folgendes eintragen:

IMAGE=docker.io/gitlab/gitlab-ce:<version>-ce.0
HOSTNAME=git.domain.tld
NAME=gitlab-ce

Damit sind dann Umgebungsvariablen gesetzt, die in der Service-Datei genutzt werden. Die ist unter /etc/systemd/system/gitlab.service zu finden. In dieser Service-Datei ist beschrieben, wie der Gitlab-Service aussieht:

[Unit]
Description=GitLab Podman container
After=network.target

[Service]
Type=simple
TimeoutStartSec=5m
Restart=always
RestartSec=30s
EnvironmentFile=/etc/sysconfig/gitlab
ExecStartPre=-/usr/bin/podman rm ${NAME}
ExecStart=/usr/bin/podman run \
    --name ${NAME} \
    --hostname ${HOSTNAME} \
    --publish 443:443 \
    --publish 80:80 \
    --publish 8014:22 \
    --volume /srv/gitlab/config:/etc/gitlab:Z \
    --volume /srv/gitlab/logs:/var/log/gitlab:Z \
    --volume /srv/gitlab/data:/var/opt/gitlab:Z \
    ${IMAGE} 
ExecReload=-/usr/bin/podman stop ${NAME} 
ExecReload=-/usr/bin/podman rm ${NAME} 
ExecStop=-/usr/bin/podman stop ${NAME}

[Install]
WantedBy=multi-user.target

Ich gehe hier nicht auf alle Einzelheiten ein. Nur so viel: Wo die Umgebungsvariablen zu finden sind, wird in EnvironmentFile definiert. Dort wir die Datei eingetragen, die im Schritt vorher definiert wurde. Bei den Befehlen zum Starten, Neustarten und Stoppen des Dienstes taucht hier podman auf. Wer Docker einsetzt, muss bei diesen Befehlen dann das podman durch docker ersetzen.

Die Port-Weiterleitungen können auch abweichen. In dem Projekt wurde der Port 22 von Gitlab über den Port 8014 des Servers bekanntgemacht, damit der Server selbst per SSH über den Port 22 ansprechbar bleibt.

Abschließend muss der Service noch aktiviert werden:

systemctl enable gitlab.service

machen, damit das Gitlab bei jedem Systemstart ebenfalls gestartet wird

Backup einspielen

Bevor man das Backup einspielen kann, muss es erstellt werden. Dazu auf der alten Instanz folgendes machen:

$ docker exec -it gitlab /bin/bash/
gitlab-rake gitlab:backup:create

Das erstellt eine .tar-Datei in */srv/gitlab/data/backups *. Diese muss an die gleiche Stelle auf dem neuen Server geschoben werden. Der Dateiname ist nach diesem Muster aufgebaut: <Timestamp>*<YYYY>*<mm>*<dd>*<version>.tar.

In dem Backup sind nicht die Dateien /srv/gitlab/config/gitlab.rb und /srv/gitlab/config/gitlab-secrets.json enthalten. Diese müssen separat auf den neuen Server kopiert werden.

Anschließend müssen auf dem neuen Server ein paar Services von Gitlab gestoppt werden:

$ docker exec -it gitlab /bin/bash
/ gitlab-ctl reconfigure
/ gitlab-ctl start
/ gitlab-ctl stop unicorn
/ gitlab-ctl stop sidekiq

Sind die Services gestoppt, kann das Backup eingespielt werden:

/ gitlab-rake gitlab:backup:restore --trace

Abschließend gitlab neu starten:

/ gitlab-ctl restart


Permalink:

Wie setzen wir die Gitlab-CI ein?

26.11.2020 // Gregor Goldbach

In meinem Artikel über die Optimierung von Docker-Images habe ich erwähnt, dass wir die »Gitlab-CI« einsetzen. In diesem Artikel beschreibe ich nun näher, was das eigentlich ist und welche Erfahrungen wir gemacht haben.

Was bedeutet CI?

Mit »CI« wird in der Software-Entwicklung »continuous integration« bezeichnet, also die fortlaufende Integration von Software-Komponenten. Dies ist ein Vorgehen im Team und weniger ein Werkzeug, bei dem Änderungen an der gemeinsamen Code-Basis fortlaufend an einer definierten Stelle zusammengeführt und auf ihre Qualität hin untersucht werden.

Diese Untersuchung besteht in der Regel aus einer Reihe von automatisierten Tests, die Abweichungen von gewünschten Eigenschaften aufzeigen sollen. Hierzu kann auch das Einhalten von Codier-Richtlinien zählen.

Durch die häufige Integration und einem hohen Grad an Automatisierung möchte das Team sicherstellen, dass Fehler und Abweichungen schneller gefunden und behoben werden können.

Die fortlaufende Integration ist also ein Baustein, um qualitätssichernde Maßnahmen im Softwareentwicklungsprozess umzusetzen.

Gitlabs CI-Umsetzung

Das Produkt Gitlab unterstützt fortlaufende Integration durch eine textuelle Beschreibung von »Pipelines«, in denen konfigurierbare Befehle ausgeführt werden.

Nach einem Push von Änderungen in einem Repository wird ein Docker-Container gestartet, in dem dann die beschriebenen Befehle ausgeführt werden.

Das folgende Beispiel zeigt eine hypothetische Distribution Foo::Bar::Baz, die mit Dist::Zilla in Ubuntu gebaut und getestet wird.

image: ubuntu

stages:
  - build

build:
  stage: build
  script:
    - dzil build
    - dzil cover
    - dzil test
  coverage: '/^Total.+\s+(\d+.\d)$/'
  artifacts:
    when: always
    name: "${CI_BUILD_STAGE}_${CI_BUILD_REF_NAME}"
    paths:
      - foo-bar-baz-*.tar.gz

Diese Befehle werden typischerweise das Compilieren und Linken von Software sein, an die sich Testautomatisierung und weitere Prüfungen anschließen.

Wenn alle Befehle erfolgreich ausgeführt wurden, ist der Build erfolgreich. Erfolg oder Misserfolg eines Builds werden dem Entwickler, dessen Änderungen die Pipeline ausgelöst haben, per E-Mail mitgeteilt. Der Zustand eines Builds ist zusätzlich im Gitlab-Projekt sichtbar.

Welche Arten von Pipelines es gibt und wie dies in Gitlabs Oberfläche dargestellt wird, ist in Gitlabs Dokumentation nachzulesen.

Wie nutzen wir die Gitlab-CI?

Eine Pipeline wird wie oben beschrieben nach einem Push der Änderungen gestartet. Bei der Konfiguration dieser Pipelines legen wir auf zwei Aspekte großen Wert: Geschwindigkeit und Reproduzierbarkeit des Builds.

Geschwindigkeit ist für uns wichtig, da wir binnen weniger Minuten Rückmeldung über die Qualität der Änderungen erhalten wollen. Wenn hier etwas bricht, wollen wir den Fehler sofort korrigieren, da diese Behebungen meist einfacher sind als bei großen Änderungen.

Geschwindigkeit erreichen wir durch konfektionierte Docker-Images. Außerdem stimmen wir die Reihenfolge der Tests aufeinander ab und verfolgen beim Schreiben der Tests einen »fail fast«-Ansatz. Wenn ein Build fehlschlägt, bekommen wir so früh Rückmeldung.

Die Reproduzierbarkeit eines Builds ist für uns wichtig, damit wir Builds nachvollziehen können, indem wir die Docker-Images auf beliebigen Systemen bauen. In der Vergangenheit hatte uns Gitlab öfter Fehler in Builds gemeldet, die wir dann lokal auf unseren Entwicklungsrechnern nicht nachvollziehen konnten. Grund hierfür war die Installation von Abhängigkeiten und dadurch entstandene Versionsunterschiede.

Die Reproduzierbarkeit erhalten wir, indem wir so gut es geht die Versionen der Abhängigkeiten angeben und während des Builds keine Komponenten nachinstallieren. Da Installationen entfallen, erhöht sich als Nebenwirkung auch die Build-Geschwindigkeit.

Dist::Zilla

Für uns hat sich Dist::Zilla als sehr hilfreich erwiesen. Einerseits ermöglichen uns die Kommandos dieses Werkzeugs, die Konfiguration einer Pipeline übersichtlich zu halten. Andererseits bietet die Funktionalität von dzil vieles, was wir sinnvoll einsetzen können.

Kommandos, die wir nutzen, sind zum Beispiel »test« für das Ausführen der Tests, »build« zum Erstellen der Distribution und »cover« zum Messen der Testabdeckung.

Wir setzen Dist::Zilla ein, um am Ende eines Builds die Distribution als Artefakt »an den Build zu hängen«. Das von dzil erstellte Archiv wird einem Build zugeordnet; wir können es dann im Browser, in anderen Projekten oder über die API übertragen.

Auch bei der Versionsverwaltung ist Dist::Zilla nützlich: Sein git-Bundle hilft dabei, Branches nach dem Veröffentlichen einer Distribution zu taggen. Somit ist automatisiert und nachvollziehbar im Git-Repository eine Versionshistorie abrufbar.

Zusammenfassung

Das Produkt Gitlab hilft einem Team beim fortlaufenden Integrieren von Software-Komponenten. Entwickler können »Pipelines« über Textdateien beschreiben, in denen Befehle nach Änderungen einer gemeinsamen Code-Basis ausgeführt werden. Wir können damit schnelle Rückmeldungen über Code-Änderungen und reproduzierbare Builds erhalten. Dist::Zilla hilft uns, einen Build übersichtlich zu beschreiben und effizient durchzuführen.

Weiterlesen


Permalink:

Perl-Schulungen vor dem Deutschen Perl-/Raku-Workshop 2021 in Leipzig

10.11.2020 // Renée Bäcker

Auch während der aktuell hohen Infektionszahlen in der Corona-Pandemie schauen wir nach vorne. Nach aktuellem Stand findet der Deutsche Perl-/Raku-Workshop 2021 Ende März in Leipzig statt (sollte die Corona-Situation das nicht hergeben, wird da mit Sicherheit reagiert).

Wir wollen den Workshop zum Anlass nehmen, am Tag vorher (23. März 2021) zwei Halbtagesschulungen anzubieten:

Den Tag beginnen wird Gregor mit Gitlab und Perl. In dem Kurs, der von 08:00 Uhr bis 12:00 Uhr stattfinden wird, geht es um die Entwicklung von Perl-Distributionen mit Hilfe der Plattform Gitlab. Gregor wird anhand einer CPAN-Distribution kurz die hierfür relevanten integrierten Komponenten der Plattform vorstellen und insbesondere auf die continuous integration eingehen. Ein Schwerpunkt liegt hier auf der schnellen Rückmeldung an Entwickler nach einem Commit.

Am Nachmittag (13:30 Uhr bis 17:30 Uhr) geht es dann um REST-APIs mit Mojolicious. Ich werde hierbei kurz auf REST an sich eingehen, bevor eine Schnittstelle definiert und dann als Mojolicious-Anwendung mit Leben gefüllt wird. Dabei werden dann auch Themen wie Sicherheit etc. angesprochen. Zum Abschluss wird die Schnittstelle noch getestet.

Als Veranstaltungsort ist das Hotel Michaelis geplant und die Teilnehmerzahl ist auf 10 Personen pro Schulung beschränkt.

Wer Interesse an einer der Schulungen hat, kann sich gerne bei uns melden. Die Teilnahme an einer Schulung kostet 200,00 € netto (238,00 € inkl. MwSt), ein Kombiticket für beide Schulungen kostet 350,00 € netto (416,50 € inkl. MwSt).

Wir werden natürlich die Entwicklungen bzgl. Corona weiter im Auge behalten. Sollte eine Präsenzveranstaltung nicht möglich sein, werden wir die Schulungen als Online-Veranstaltung anbieten.


Permalink:

Optimierung von Docker-Images

14.10.2020 // Gregor Goldbach

Wenn man als Entwickler mit der Erstellung von Docker-Images anfängt, dann ist das Image zunächst auf Funktionalität optimiert. Mit fortschreitender Entwicklung wird eine schnelle Rückmeldung über das Build-Ergebnis an die Entwickler wichtiger. Zudem soll ein Build reproduzierbar sein, damit von der CI-Umgebung gemeldete Fehler nachvollzogen und Änderungen für den Betrieb einplant werden können. Dieser Artikel zeigt, worauf man bei achten sollte, damit das klappt.

Zuerst: Funktionalität

Wir nutzen Gitlab und haben uns zu Beginn der Nutzung dieses Dienstes darauf konzentriert, überhaupt einen Build nach einem Commit durchlaufen zu lassen. Das ist manchmal gar nicht so einfach, da diverse Abhängigkeiten benötigt werden, die vor Ausführung von Tests in einem laufenden Container installiert werden müssen.

Wir bei Perl-Services haben uns zu Beginn auf die CI-Stufe von Gitlab verlassen. Da es hier aber mitunter etwas dauern kann, bis ein Build nach einem Commit startet, haben wir schnell gemerkt, dass lokales Bauen von Images sinnvoller ist. Dafür haben wir dann ein Makefile erstellt:

#!/bin/make -f
#
# Makefile pointers...
# Self-documenting:
#    https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
# Argument passing:
#    https://stackoverflow.com/questions/2826029/passing-additional-variables-from-command-line-to-make

#
# To add a new option, follow the format below, e.g.:
#     make command: ## Documentation of command
#             ./the-command-to-run.sh

# Default to displaying the help
.DEFAULT_GOAL := help

.PHONY: help

REGISTRY=registry.gitlab.com
IMAGE_NAME=perlservices/groupname/projectname-docker
IMAGE_TAG=latest
FULL_IMAGE_NAME=${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}

# Displays all the make options and their descriptions
help:
    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

build: ## Build Docker image
    docker build -t ${FULL_IMAGE_NAME} .

shell: ## Start a container from the image built and run a shell in it
    docker run --rm -it --entrypoint /bin/bash \
        -v $$PWD/src:/src \
        -p 8080:8080 \
        ${FULL_IMAGE_NAME}

Dieses Makefile erstellt beim Aufruf von make build ein Docker-Image anhand des im gleichen Verzeichnisses liegenden Dockerfiles. Der Aufruf von make shell startet einen Container mit diesem Image und öffnet eine Shell darin, wobei hier das lokale Verzeichnis src im Container unter /src zur Verfügung steht.

Mit Hilfe dieses Makefiles können wir recht einfach ein Docker-Image erstellen und dieses lokal weiterentwickeln.

Schnelle Rückmeldung durch kurze Durchlaufzeit

Damit eine Perl-Distribution im Rahmen eines Docker-Images erstellt werden kann, werden viele Bausteine benötigt.

Einerseits benötigen wir auf Systemebene einige Werkzeuge. Dazu gehört zum Beispiel curl, um andere Builds auf Gitlab anzustoßen. Diese installieren wir über den Paketmanager des Betriebssystems.

Anderseits brauchen wir auf Anwendungsebene Module, die wir installieren müssen. Wenn sie in der geeigneten Version als Paket des Betriebssystems vorliegen, installieren wir sie ebenfalls mit dem Paketmanager des Betriebssystems. Wenn dies nicht der Fall ist, installieren wir sie von CPAN.

Diese Installationen dauern entsprechend lange und greifen auf externe Quellen zu. Dadurch dauert der Build lange und kann schlimmstenfalls fehlschlagen.

Daher haben wir hier in zwei Schritten optimiert: Wir erstellen für eine zu entwickelnde Perl-Distribution (oder Anwendung) ein spezifisches Image, das alle benötigten Abhängigkeiten enthält.

Im Dockerfile installieren wir zunächst mit apt-get install die benötigten Pakete des Betriebssystems:

FROM registry.gitlab.com/perlservices/groupname/projectname-docker:latest

RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
  lsb-core \
  wget

# Add PostgreSQL repo for the matching release of the OS
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | tee  /etc/apt/sources.list.d/pgdg.list

RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
  postgresql \
  postgresql-client \
  git \
  apache2 \
  curl \
  phppgadmin \
  php-pgsql \
  cpanminus \
  libdbd-pg-perl \
  libnet-ssleay-perl \
  libxml-parser-perl \
  gosu \
  ...

Die Perl-Abhängigkeiten legen wir in einem cpanfile fest:

...
requires 'Mojolicious' => 8;
requires 'Mojolicious::Plugin::Bcrypt' => 0;
requires 'Mojolicious::Plugin::Status' => 0;
requires 'Moo' => 0;
requires 'MySQL::Workbench::DBIC', '>= 1.13';
requires 'MySQL::Workbench::Parser', '>= 1.06';
...

Das so erstellte Image verwenden wir dann in der .gitlab-ci.yaml unseres Projektes. So können dann sofort nach einem Commit in der CI-Stufe die konfigurierten Befehle ausgeführt werden, denn alle Abhängigkeiten sind bereits im Image vorhanden.

Reproduzierbare Builds durch genaue Versionsangaben

Gerade zu Beginn der Arbeit mit den oben beschriebenen projektspezifischen Docker-Images haben wir öfter Probleme mit der Nachvollziehbarkeit von Fehlern im Build auf Gitlab gehabt. Ursache dafür war, dass sich Abhängigkeiten in jedem Build geändert haben.

Beispiele für diese Änderungen:

  • Eine Perl-Distribution wird auf CPAN zwischen zwei Builds aktualisiert.
  • Lokal ist eine indirekte Abhängigkeit in einer anderen Version installiert.

Es wird dann unter Umständen auf Gitlab ein Fehler im Build angezeigt, der lokal beim Entwickler nicht mehr oder anders auftritt.

Durch das Erstellen von projektspezifischen Docker-Images werden schon viele Abhängigkeiten festgezurrt, so dass hier weitestgehend reproduzierbare Builds umgesetzt werden konnten.

Eine wesentliche Abhängigkeit ist jedoch noch nicht festgezurrt: Die des grundlegenden Images für alle Builds – Das verwendete Betriebssystem.

Vielfach wird in Beispielen für ein Dockerfile entweder kein Tag bei einem verwendeten Image angegeben oder eines mit dem Namen »latest«. Beide führen dazu, dass jeweils die letzte Fassung eines Images verwendet wird:

FROM debian:latest

Im schlimmsten Fall kann es dadurch dazu kommen, dass zwischen zwei Builds ein Versionssprung des Betriebssystems stattfindet und alle abhängigen Images neu gebaut werden. In der Regel läuft dies nicht ohne Probleme ab und zieht weitere Arbeiten nach sich.

Die dann auftretenden Probleme im Build können im günstigsten Fall eine kurze Verzögerung bedeuten, im schlimmsten Fall ungeplante Wartungsarbeiten nach sich ziehen.

Außerdem bedeuten gerade bei Betriebssystemen neue Versionen größere Änderungen im Betrieb, die sorgfältig vorbereitet und eingeplant werden müssen.

Diese Art von Überraschung kann umgangen werden, indem in Dockerfiles bei jedem verwendeten Image stets ein Tag mit angegeben wird, das eine feste Version spezifiziert.

Es sollte also auf Angaben wie »rolling« und »latest« verzichtet werden.

FROM ubuntu:20.04

Zusammenfassung

Während Entwickler zunächst auf Funktionalität bedacht sind, wenn sie in CI-Umgebungen mit dem Erstellen von Docker-Images beginnen, zeigt sich sehr schnell in der täglichen Arbeit, dass weitere Faktoren wichtig sind. Schnelle Rückmeldung durch kurze Durchlaufzeiten und stabiler Betrieb durch reproduzierbare Builds werden mit zunehmender Zeit wichtiger.

Dieser Artikel hat beschrieben, wie durch projektspezifische Images die Durchlaufzeit verringert werden kann. Außerdem wurde gezeigt, wie durch das ausdrückliche Konfigurieren von Softwareversionen planvolle Updates ohne große Überraschungen im Betrieb möglich werden.


Permalink:

Wie nutzen wir Gitlab?

01.09.2020 // Gregor Goldbach

Gitlab ist eine webbasierte Plattform für die kontinuierliche Auslieferung von Software. Diese Plattform bietet viele Funktionen, die über die reine Softwareentwicklung und das Deployment hinausgehen. Wir erstellen damit Perl-Distributionen, Docker-Images sowie Dokumentation.

Hauptteil

Gitlab ist eine webbasierte Plattform für die kontinuierliche Auslieferung von Software. Unter anderem integriert es Komponenten wie ein Wiki, ein Ticket-System und die Unterstützung des Versionskontrollsystems Git. Außerdem bietet es eine automatisierte Ausführung von beliebigem Code in Docker-Images an, die von Commits ausgelöst werden können.

Welche Befehle ausgeführt werden, kann man in einer YAML-Datei beschreiben. Dort wird auch beschrieben, welches Docker-Image dafür verwendet werden soll und unter welchen Bedingungen die Befehle ausgeführt werden sollen.

Wie wir bei Perl-Services diese Möglichkeiten nutzen, beschreibe ich in diesem Artikel.

Automatisiertes Erstellen von Perl-Distributionen

Als wir begonnen haben, Gitlab zu evaluieren, haben wir zunächst kleinere Perl-Distributionen als Projekt angelegt und die Git-Repositorys eingebunden. In den YAML-Dateien dieser Projekte haben wir dann konfiguriert, dass nach jedem Commit die Unit-Tests ausgeführt werden und zum Abschluss die Perl-Distribution paketiert werden sollen. Hier ein Auszug aus einem mittlerweile so nicht mehr existenten Projekt:

image: ubuntu:rolling

build:
  script:
    - apt-get update -qq
    - cpanm --installdeps .
    - dzil authordeps | cpanm
    - dzil listdeps | cpanm
    - dzil build

artifacts:
    when: always
    name: "${CI_BUILD_STAGE}_${CI_BUILD_REF_NAME}"
    paths:
      - *.tar.gz

Unser Ansatz war zunächst, Fehler durch Tests zu finden und bei erfolgreichem Testdurchlauf die Perl-Distribution zu erstellen. Die benötigten Abhängigkeiten haben wir mit cpanm installiert. Dabei haben wir festgestellt, dass wir gerne schneller Rückmeldung erhalten würden. Dies führte dann dazu, dass wir die Build-Pipeline optimiert haben.

Verkürzte Durchlaufzeiten erreichen wir einerseits durch eine Anpassung der Befehle, andererseits aber auch dadurch, dass wir ein Docker-Image verwenden, das für diese Distribution optimiert ist und bereits alle Abhängigkeiten enthält.

Was sind die nächsten Schritte? Wir prüfen, ob wir die erfolgreich gebauten Distributionen automatisiert in einen Pinto-Server laden oder auf CPAN veröffentlichen.

Erzeugen von Docker-Images

Im vorherigen Abschnitt habe ich erwähnt, dass wir Docker-Images für die Entwicklung von Perl-Distributionen optimieren. Wir wollen dafür alle Abhängigkeiten einer Perl-Distribution in einem Docker-Image zur Verfügung stellen, damit wir die Durchlaufzeit der Perl-Distribution klein halten und dann schnell Rückmeldung nach einem Commit bekommen.

Wie haben wir dies mit Gitlab erreicht? Wir setzen Dist::Zilla ein und beschreiben Abhängigkeiten über dessen Konfiguration dist.ini und das cpanfile. Im verwendeten Docker-Image werden diese darüber beschriebenen Abhängigkeiten mit cpanm installiert.

Dafür haben wir Gitlab so konfiguriert, dass bei einer Änderung dieser Dateien im Master-Branch der Perl-Distribution das verwendete Docker-Image neu erstellt wird. Hier ein beispielhafter Auszug aus einer Gitlab-Konfiguration:

trigger_image_build:
  stage: trigger
  script:
    - curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=master https://gitlab.com/api/v4/projects/9398220/trigger/pipeline
  only:
    refs:
      - /^master$/
    changes:
      - cpanfile
      - dist.ini

Die Beschreibungen der Abhängigkeiten werden beim Erstellen des Docker-Images aus dem abhängigen Projekt gelesen. Hier ein beispielhafter Auszug aus einem Dockerfile:

RUN cd /home/mydocker/src && \
  git clone https://gitlab-ci-token:${CI_TOKEN}@gitlab.com/perlservices/project.git && \

[...]
  
RUN cd /home/mydocker/src/project && \
   cpanm --skip-satisfied --installdeps . && \
   dzil authordeps --missing | cpanm --skip-satisfied && \
   dzil listdeps | cpanm --skip-satisfied

Die Gitlab-Dokumentation hat uns hier teilweise eher verwirrt als geholfen, aber letzten Endes haben wir ein perfekt abgestimmtes Image erhalten, in dem alle Abhängigkeiten enthalten sind. Dadurch können in dem Projekt, das auf diesem Image basiert, nach einem Commit sofort die Unit-Tests ausgeführt werden können.

Die Durchlaufzeiten sind daher sehr gering und liegen bei einem gehosteten Gitlab-Repository bei etwa drei Minuten. Der Löwenanteil dieser drei Minuten liegt darin begründet, dass wir auf einen freien Gitlab-Runner warten müssen.

Um die Durchlaufzeiten weiter zu verringern, migrieren wir die Repositorys derzeit auf einen eigenen Gitlab-Server.

Hinweis: Das müssen wir wegen dem Fall des Privacy Shields ohnehin machen 🙂

Dokumentation erstellen

Die in Projekten zu erstellende Dokumentation reicht je nach Wunsch des Kunden von kurzen Beschreibungen über HTML-Dateien bis zu sauber mit LaTeX gesetzten Dokumenten. Das Ausgangsformat der Dokumente ist für uns stets Markdown, da dies einfach zu schreiben ist, wir uns auf das Wesentliche konzentrieren können und alle gewünschten Zielformate erzeugt werden können.

In Gitlab erzeugen wir mit pandoc aus den Quelldateien das Zielformat. Gegebenenfalls verarbeiten wir das Resultat automatisiert weiter. Wenn als Ergebnis beispielsweise ein PDF erzeugt werden soll, dann bedeutet dies, dass wir aus Markdown LaTeX-Dokumente erzeugen und diese im Anschluss mit lualatex in ein PDF wandeln. Damit dies möglich ist, haben wir uns ein Docker-Image erstellt, in dem TeX Live installiert ist.

Da wir lualatex aus TeX Live einsetzen, ist unser Docker-Image mehrere Gigabyte groß. Diese Kröte müssen wir wohl schlucken. Pandoc leistet sehr gute Dienste und die Entscheidung für Markdown als Ausgangsformat hat sich bewährt.

Wir werden diese Kombination beibehalten, jedoch gelegentlich prüfen, ob wir das Docker-Image etwas verkleinern können 🙂

Zusammenfassung

Wir erstellen mit Gitlab automatisiert Perl-Distributionen, Docker-Images und Dokumente in unterschiedlichen Formaten. Gitlab als Plattform hat sich hierfür bewährt. Durch den Einsatz einer selbst betriebenen Gitlab-Instanz erhoffen wir uns geringere Durchlaufzeiten.

Links


Permalink: