Kolumne: Docker rockt Java

Docker meets Jenkins

Peter Roßbach

© Software & Support Media

In einem Jenkins-Setup befinden sich meist neben dem Master verschiedene Slave-Instanzen. Für die Projekte ist eine umfangreiche Konfiguration zum Erzeugen, Testen und Publizieren notwendig. Damit Jenkins-Slave-Instanzen für viele Projekte genutzt werden können, kommt es zu einer komplexen Installation mit unerwünschten Abhängigkeiten zwischen den Projekten. Die Provisionierung der verschiedenen Build-Tools ist aufwendig. In diesem Artikel werden die Grundlagen beschrieben, Jenkins mit vielen Docker-Containern zu betreiben.

In den letzten zwei Jahren hat sich Docker in der Produktion und Entwicklung stark verbreitet. Immer mehr Werkzeuge, Programmiersprachen und Services stehen als Docker-Image bereit. Zeit, von diesen fertigen Bausteinen in der eigenen CI-/CD-Welt zu profitieren. Jenkins wird seit einem Jahr als Trusted Build auf dem Docker Hub angeboten. Die aktuellen stabilen Jenkins-Releases (LTS) werden auf der Basis von OpenJDK 8 und Debian 8 bereitgestellt. Wer immer die aktuelle Version von Jenkins einsetzen möchte, kann dies mit der Version von jenkinsci/jenkins realisieren. Der Start des eigenen Jenkins-Masters gelingt mit Docker beispielsweise folgendermaßen:

 $ mkdir -p jenkins
$ docker run -d —p 8080:8080 \
  -v $(pwd)/jenkins:/var/jenkins_home \
  --name jenkins-master jenkins
 

Die Konfiguration und Daten des Jenkins-Masters werden im Beispiel lokal auf einem Host-Volume gespeichert – nicht vergessen, den Nutzer jenkins auf einem Linux-Host einzurichten. Nun kann die Administration des Jenkins im Browser, mit Scripts oder durch das REST-API erfolgen. Es müssen nicht nur Nutzer und Jobs, sondern auch viele Plug-ins installiert werden. Für diese Installation ist es sinnvoll, ein eigenes Docker-Jenkins-Image zu erzeugen.

Den eigenen Jenkins-Master installieren

Das Basis-Jenkins-Image stellt für diesen Zweck direkt ein Installationsskript für die Plug-ins bereit. Im Beispiel werden noch entsprechende Erweiterungen der Gruppen und Nutzer vorgenommen, damit der Jenkins-Master auf den Docker Daemon des Hosts zugreifen kann. Es werden dem Jenkins-Nutzer sudo-Privilegien eingeräumt. Im speziellen Fall wird einfach die aktuelle Version von Jenkins und der Plug-ins installiert, die im Moment der Erzeugung vorliegen (Listing 1). Wer eine kontrollierte Versionierung benötigt, muss die entsprechenden Versionsangaben herausfinden und im Dockerfile eintragen. Man sollte die Abhängigkeiten der Module manuell berücksichtigen, die Installationsreihenfolge in der Datei plugin.txt ist entscheidend.

Damit auf den Docker Daemon zugegriffen werden kann, wird das Docker Binary des Docker-Hosts und der Kommunikations-Socket docker.sock beim Start des Jenkins-Containers übergeben. Der Installationspfad des Docker Binaries ist auf einigen Distributionen /usr/bin/docker. Alternativ könnte das Docker Binary direkt im eigenen Image installiert werden. Dabei kann es aber schnell zu Versionsproblemen auf verschiedenen Hosts kommen. Die Docker-CLI kann nicht auf Docker Daemons mit niedriger API-Version zugreifen. Im Beispiel wird das aktuellste LTS-Jenkins mit einem Pull des Basis-Images immer aktuell gehalten. Um das eigene Jenkins-Image aktuell zu halten, muss es regelmäßig aktualisiert werden. Dies wäre ein sinnvoller Einsatz für die eigene Jenkins-Pipeline.

Mit diesem Stand des Jenkins-Masters ist es nun möglich, bestehende Docker-Git-Projekte als Jenkins-Job mit einfachen Docker-CLI-Aufrufen regelmäßig zu bauen und beispielsweise in die eigene Docker Registry zu pushen. Der Aufbau einer Team-CI-/CD-Pipeline kann also auch ohne zentrales Management erfolgen. Die Installation weiterer Build-Werkzeuge ist nun natürlich im eigenen Jenkins-Master möglich. Aber eigentlich wäre es flexibler, für die verschiedenen Jobs eine Pipeline für spezielle Umgebungen zu schaffen. Weiterhin ist die Skalierung der verschiedenen Jobs eines Projekts oft eine Herausforderung. Jenkins sieht für die Lösung dieser Anforderungen die Bereitstellung entsprechender Jenkins-Slaves vor.

$ mkdir -p jenkins-master
$ cd jenkins-master
$ cat >Dockerfile <<EOF
FROM jenkins

USER root
RUN apt-get update \
  && apt-get install -y sudo \
  && rm -rf /var/lib/apt/lists/* \
  && groupmod --new-name docker users \
  && usermod -a -G docker jenkins \
  && usermod -a -G sudo jenkins \
  && echo "jenkins ALL=NOPASSWD: ALL" >> /etc/sudoers

USER jenkins
COPY plugins.txt /usr/share/jenkins/plugins.txt
RUN /usr/local/bin/plugins.sh /usr/share/jenkins/plugins.txt
EOF
$ cat >plugins.txt <<EOF
scm-api:latest
git-client:latest
git:latest
greenballs:latest
docker-plugin:latest
durable-task:latest
ssh-slaves:latest
token-macro:latest
EOF
$ docker stop jenkins-master
$ docker rm jenkins-master
$ docker build --pull=true —t infrabricks/jenkins-master .
$ docker run -d -v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/local/bin/docker:/usr/bin/docker \
-v $(pwd)/../jenkins:/var/jenkins_home \
--name jenkins-master \
-p 8080:8080 -p 50000:50000 -p 53691:53691 \
infrabricks/jenkins-master

Eigene Docker Jenkins Slaves erstellen

Mit dem Jenkins-Docker-Plug-in werden die Slave-Docker-Images einfach in jeder Ausführung auf einem entfernten Docker-Host gestartet (Abb. 1.). Der vorbereitet Jenkins-Slave wird über das SSH-Protokoll eingebunden (Listing 2). Jeder Job wird also nun in einer kontrollierten Umgebung auf einem bestimmten Docker Host gestartet. Verschiedenen Docker-Hosts mit entsprechenden Slave Images lassen sich so über die Jenkins-Cloud-Konfiguration einbinden. Der Slave wird für jeden Job erneut gestartet. Wenn irgendwelche Daten zwischen den Builds ausgetauscht werden sollen, müssen diese durch Data-Container oder Host-Volumes an den Slave angebunden werden. Leider unterstützt das Docker-Plug-in noch kein HTTPS. Damit ist ein Betrieb nur in geschützten privaten Netzen empfehlenswert. Fast alle Remote-Docker-Daemons nutzen aus Sicherheitsgründen die Kommunikation über TLS und erfordern entsprechende Client-Zertifikate. Hoffentlich wird dieses Problem bald behoben, denn das verwendet Projekt docker-java unterstützt TLS schon länger. Alternative können die eigenen Jenkins-Build-Slaves manuell als SSH-Knoten in Jenkins angemeldet werden. Das Management der Slaves könnte beispielsweise über einen Docker-Swarm-Cluster erfolgen und die Maschinen mit docker-machine erzeugt werden. Allerdings werden diese Slaves dann für verschiedene Jobs genutzt. Welche Slaves für welche Art von Build-Job genutzt wird, kann über die Zuweisung entsprechender Label in Jenkins erfolgen. Statt ein Standard Passworts für den Jenkins-Nutzer zu verwenden, sollte besser ein Zertifikat zwischen dem Jenkins-Master und dem Slave genutzt werden. Für jeden Slave-Container sollte dann beim Start der gültige öffentliche Schlüssel als Volume eingebunden werden. Und es muss verhindert werden, dass eine SSH-Kommunikation mit einem Log-in zustande kommt. Leider gibt es noch keinen wirklichen Standard in Docker, der es erlaubt, Geheimnisse sicher für Prozesse in einem Docker-Container bereitzustellen. Eine Art Tresor-Volume, ähnlich der Kubernetes Secret Volumes wäre hilfreich. Eine Integration mit dem Vault-Server von Hashicorp wäre nützlich.

Abb. 1: Docker Jenkins

 FROM java:8-dk

RUN DEBIAN_FRONTEND=noninteractive apt-get update \
 && apt-get install -y openssh-server sudo \
 && sed -i 's|session    required     pam_loginuid.so|session    optional     pam_loginuid.so|g' \
  /etc/pam.d/sshd \
 && mkdir -p /var/run/sshd \
 && groupadd -g 1000 jenkins \
 && useradd -d /home/jenkins -s /bin/bash -m jenkins -u 1000 -g jenkins \
 && echo "jenkins:jenkins" | chpasswd \
 && echo "jenkins ALL=NOPASSWD: ALL" >> /etc/sudoers \
 && rm -rf /var/lib/apt/lists/*

ENV HOME /home/jenkins

RUN curl --create-dirs -sSLo /usr/share/jenkins/slave.jar http://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/2.52/remoting-2.52.jar \
  && chmod 755 /usr/share/jenkins \
  && chmod 644 /usr/share/jenkins/slave.jar

VOLUME /home/jenkins
WORKDIR /home/jenkins

EXPOSE 22

CMD [ "/usr/sbin/sshd", "-D" ]
 

Mehr geht immer …

Natürlich gibt es weitere Plug-ins, Projekte und Slaves für die gemeinsame Nutzung von Docker und Jenkins. Mit den Plug-ins ist eine vereinfachte Definition einzelner Docker-Anweisungen, die Integration in das Workflow-Plug-in oder die Anbindung von Notifications des Docker-Hub-Build-Servers möglich. Die Vielfalt der verschiedenen schon existierenden Möglichkeiten ist beeindruckend:

Wenn die verschiedenen Jenkins-Jobs zum Ziel haben, Docker-Images zu erzeugen oder komplexere Docker-Integrationstest auf einem Slave gestartet werden sollen, muss für jeden Slave eigentlich ein eigner Docker Daemon vorhanden sein. Sonst kann es schnell zu Namens- oder Netzwerkkonflikten kommen. Da nun der Slave in einem Container läuft, kann der Docker Daemon des Hosts bereitgestellt werden, wie beim beschriebenen Jenkins-Master. Allerdings teilen sich diesen Daemon dann evtl. verschiedene Slaves auf einem Host. Wer noch mehr Unabhängigkeit benötigt, kann seine Jenkins-Slaves natürlich als Docker-In-Docker-Images aufbauen (DinD GitHub, Docker Official Repository).

Fazit

Die eigene Build-Pipeline jedes einzelnen DevOps-Teams rückt mit Jenkins als Docker-Image und den vorhandenen Plug-ins in greifbare Nähe. Allerdings bleibt die Konfiguration der zahlreichen Optionen in Jenkins eine Herausforderung. Die automatische und vollständige Provisionierung aller Bestandteile von Jenkins und seinen zahlreichen Plug-ins durch ein REST-API ist noch nicht ausreichend vorhanden. Die Docker-Container sorgen jedenfalls dafür, dass Installation, Betrieb und Wartung der eigenen Jenkins-Build-Pipeline einen beträchtlichen Schritt nachvollziehbarer und übertragbarer wird. Die Vereinfachung auf verschiedenen Docker-Maschinen Build-Umgebungen schnell und zuverlässig bereitzustellen senkt die Hürde weitere Qualitätsmaßnahmen zu gestalten. Kurzfristige Experimente lassen sich schnell durch Cloud-Maschinen umsetzen, da die Provisionierung durch die Docker-Jenkins-Slaves vereinfacht erfolgen kann. Wir alle können gespannt darauf sein, welche neuen Ideen durch die Kombination von Docker und Jenkins noch entstehen.

Geschrieben von
Peter Roßbach
Peter Roßbach
Peter Roßbach ist ein Infracoder, Systemarchitekt und Berater vieler Websysteme. Sein besonderes Interesse gilt dem Design und der Entwicklung von komplexen Infrastrukturen. Er ist Apache Tomcat Committer und Apache Member. Mit der bee42 solutions gmbh realisiert er Infrastrukturprodukte und bietet Schulungen auf der Grundlage des Docker-Ökosystems, aktuellen Webtechnologien, NoSQL-Datenbanken und Cloud-Plattformen an.
Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "Docker meets Jenkins"

avatar
400
  Subscribe  
Benachrichtige mich zu:
Johan
Gast

Vielen Dank für diese Übersicht. Nun stellt sich mir die Frage, wie sinnvolll es ist, docker jenkins slaves für jeden job neu zu starten. Manche Umgebungen sind doch so komplex, dass dies bedeuten würde, dass zusätzlich für die Einrichtung der Slaves viel Zeit drauf geht, bevor überhaupt die Jobs selbst starten würden. Wie sehen Sie das?