Jenkins Buildslave für Docker
Bei der Nutzung von eigenen Docker-Images stellt sich früher oder später die Frage, wie eine sinnvolle Build-Automatisierung am besten umgesetzt wird. Der Docker-Host, auf dem die Images gebaut werden, läuft früher oder später in Probleme durch verwaiste Volumes, Image-Layer und ganze Images. Soll auch der Buildserver als Docker-Container betrieben werden, so wird die Situation noch etwas komplizierter: Auf keinen Fall soll versehentlich ein Container während des Builds oder sogar der Container des Buildservers selbst "aufgeräumt" werden.
Damit ergeben sich im Prinzip folgende Optionen
-
Sehr sorgfältig erstellte Clean-Up Jobs
-
Verwendung von Docker-in-Docker zur Isolation des Docker-Image Builds
-
Verwendung eines speziellen Build-Slave, der eine Docker Buildumgebung bereitstellt und jederzeit entsorgt und neu erstellt werden kann
Die zuletzt genannte Option wird im folgenden exemplarisch mit Jenkins, Ansible und Vagrant/VirtualBox vorgestellt. Vagrant dient dabei lediglich zur Veranschaulichung, in produktiven Umgebungen wird man auf Cloud-Resourcen oder VMware ESX zurückgreifen.
Docker in Docker vs. Phönix Maschinen
Um in einem unter Docker laufenden Buildsystem wiederum Docker zur Verfügung zu haben, gibt es prinzipiell die Möglichkeit, z.B. durch ein Socket-Mount, den Docker-Daemon des Hosts zu verwenden. Damit werden Umgebungen mit unterschiedlichen Anforderungen vermischt: Der Host benötigt für die Buildumgebung eine stabile Docker-Instanz, die lediglich ein einzelnes oder einige wenige Docker-Images beherbergt. Gleiches gilt für Docker-Volumes und gestartete Container. Veränderliche Daten beinhalten lediglich die Konfiguration des Buildsystems und unterstützende Dienste, jedoch keine temporären Images aus Build-Jobs.
Auch aus Sicherheitsgründen ist eine Trennung der Umgebungen ratsam: Aus einem Build sollte keine negative Beeinflussung des Hosts resultieren.
Als eine Form der Isolation könnte Docker-in-Docker eingesetzt werden. Dabei ergeben sich im Detail Probleme durch die geschachtelten Dateisysteme und im Zusammenhang mit der Security-Implementierung des Linux Kernels.
Damit bleibt als sinnvollste Option die Verwendung von Build-Slaves, die als sogenannte "Phönix Maschinen" so vorgesehen sind, dass sie regelmäßig zerstört und automatisiert neu augesetzt werden. Virtuelle Maschinen eigenen sich als Isolationslevel dazu sehr gut und lassen sich relativ einfach provisionieren und auch bedarfsgerecht skalieren.
Als Werkzeug zur Automatisierung von virtuellen Maschinen hat sich Vagrant in Entwicklerkreisen etabliert. Stellvertretend für andere Provisionierungsansätze wird daher Vagrant im folgenden verwendet.
Jenkins Slave Launcher
Im weiteren wird davon ausgegangen, dass Jenkins als Docker-Container betrieben wird. Als Docker-Netzwerk wird das Bridge-Netzwerk 172.17.0.0/16 mit dem Docker-Host unter 172.17.0.1. (Die Netzwerkkonfiguration ist in diesem Beispiel lediglich durch die Konstellation mit Vagrant relevant.)
mkdir jenkins-data
docker run -it -u $(id -u) --rm \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins-data:/var/jenkins_home \
--name jenkins \
jenkins/jenkins:alpine
Jenkins erlaubt auf einfache Weise Build-Slaves einzurichten.
Dazu wird unter Manage Jenkins >> Manage Nodes der Eintrag "New Node" ausgewählt. Um Jenkins einen Slave bekannt zu machen, kann Jenkins aktiv einen entsprechenden Build-Slave via SSH provisionieren oder alternativ eine Java Webstart Konfiguration bereitstellen, wodurch sich der Client mit dem Jenkins Knoten verbindet.
Zur Provisionierung via SSH wird zunächst ein Name für den Jenkins-Slave vergeben, zum Beispiel "docker slave" und als Host "172.17.0.1" konfiguriert. Damit Jenkins sich via SSH auf den Docker Build-Slave verbinden kann, werden noch Zugangsdaten benötigt. Im einfachsten Fall können diese Credentials Username und Passwort sein, zum Beispiel "jenkins" und "password". Da der SSH Port unter 2201 zur Verfügung stehen wird, muss dieser unter "advanved" noch eingetragen werden.
Als nächstes wird die Bereitstellung der virtuellen Maschine durch Vagrant vorgenommen. Vagrant nutzt sogenannte Basis-Images für die zu erzeugenden virtuellen Maschinen. Damit Docker lauffähig ist, wird ein 64bit Linux-System verwendet, im Beispiel ein Ubuntu.
Zunächst werden Einstellungen der virtuellen Maschine, wie das zur Verfügung stehende RAM und das Port-Forwarding von Port 2201 auf dem Host zu dem durch SSH genutzten Port 22 des Gastsystems angegeben. Anschließend folgt die Konfiguration des Systems innerhalb der virtuellen Maschine.
Vagrant unterstützt mehrere Konfigurationswerkzeuge, um die virtuelle Maschine einsatzbereit zu machen. Für die Konfiguration wird Ansible verwendet, doch zunächst muss sichergestellt werden, dass eine lauffähige Python-Umgebung für Ansible bereit steht - dies erledigt ein einfaches Shellkommando. Anschließend kann Ansible durch Vagrant aufgerufen werden. (Damit Ansible genutzt werden kann, muss dies auf dem Rechner, auf dem Vagrant ausgeführt wird, installiert werden.)
Das vollständige Vagrantfile ist im folgenden zu sehen:
require 'socket'
#local machine name as instance identifier
host = "jenkins-#{Socket.gethostname}"
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/zesty64"
config.vm.box_check_update = false
config.vm.hostname = host
config.vm.provider "virtualbox" do |vb|
vb.memory = "2560"
vb.cpus = "2"
vb.name = "jenkins-slave"
end
config.vm.network "forwarded_port", guest: 5005, host: 5005
#ssh for jenkins
config.vm.network "forwarded_port", guest: 22, host: 2201
config.vm.provision "shell", inline: <<-SHELL
sudo apt-get install python-minimal -y
SHELL
config.vm.provision "ansible" do |ansible|
ansible.playbook = "playbook.yml"
end
end
Ansible installiert die von Jenkins benötigte Java-Laufzeitumgebung und richtet den Nutzer ein, mit dem sich der Jenkins-Master anmeldet, um die Jenkins-Slave Konfiguration vorzunehmen. Außerdem wird Docker eingerichtet - schließlich soll der Jenkins-Slave Docker-Images erzeugen. Die Konfiguration für Ansible könnte beispielsweise wie folgt aussehen:
---
- hosts: all
become_method: sudo
tasks:
- name: install Java 8 runtime
become: yes
apt:
name: openjdk-8-jre-headless
state: present
- name: install socat util
become: yes
apt:
name: socat
state: present
- name: ensure docker repository key is installed
become: yes
apt_key:
id: "9DC858229FC7DD38854AE2D88D81803C0EBFCD88"
keyserver: "hkp://p80.pool.sks-keyservers.net:80"
state: present
- name: ensure docker package repository is available
become: yes
apt_repository:
repo: 'deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable'
state: present
- name: ensure docker and dependencies are installed
become: yes
apt:
name: docker-ce
update_cache: yes
- name: ensure docker can use insecure registries in 10/8
become: yes
lineinfile: "dest=/etc/default/docker regexp=^DOCKER_OPTS line=DOCKER_OPTS='--insecure-registry 10.0.0.0/8'"
- name: restart docker service
become: yes
service:
name: docker
state: restarted
- name: add jenkins group
become: yes
group:
name: jenkins
state: present
- name: add jenkins user
become: yes
user:
password: "{{ 'password' | password_hash('sha512') }}"
update_password: on_create
name: jenkins
comment: "Jenkis User"
uid: 1001
group: jenkins
groups: docker
Die Konfiguration ist nun abgeschlossen und die virtuelle Maschine kann mittels
vagrant up
gestartet werden.
Jenkins kann den Build-Slave nun in Betrieb nehmen. Damit ausschließlich Jobs auf diesem - und möglicherweise weiteren Slaves - ausgeführt werden, jedoch nicht auf dem Master, sollte dieser als Ziel für Build-Jobs deaktiviert werden. Dies erfolgt ebenfalls unter der Node-Verwaltung von Jenkins.
Mit diesem Setup auf Basis von temporären virtuellen Maschinen lassen sich alle Anforderungen an Skalierbarkeit, Sicherheit und Isolation erfüllen. Die Verwendung von Phönix-Servern als Build-Slaves bringt somit deutliche Vorteile und sollte als best-practice etabliert werden.
Zu den Themen Ansible, Jenkins und Docker bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:
Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.