Windows-Jenkins-Slave als Jenkins-Swarm mittels Ansible konfigurieren

Ansible ist ein nützliches Werkzeug, um schnell neue Slaves für eine CI-Umgebung bereit zu stellen. Docker ermöglicht es dabei, auch komplexe Systemlandschaften in einer solchen Testumgebung abzubilden und zu starten. Und mit Jenkins-Swarm registriert sich der Slave ohne großen Aufwand automatisch beim entsprechenden Jenkins-Server. Docker und Ansible sind in der Linuxwelt etabliert, aber manchmal fordern technische Rahmenbedingungen den Einsatz der Windows-Plattform, um bei Integrations- und Systemtests möglichst nah an der Produktivumgebung zu sein. Dass die Vorteile von Ansible und Docker auch in Kombination mit Windows zum tragen kommen können, soll der folgende Blogpost zeigen. Dabei läuft Ansible mit Linux als Kontrollsystem und es wird ein Windows-Slave mit Jenkins-Swarm und Docker Engine konfiguriert.

Problematik

Wir haben bei uns aktuell einen Kunden, für den wir eine Anwendung entwickeln, die ausschließlich unter Windows betrieben wird. Die Anwendung ist serverbasiert und setzt auf dem Wildfly 10.1 auf. Desweiteren stellt der Kunde eine Reihe von Diensten zur Verfügung, die ebenfalls ausschließlich unter Windows lauffähig sind und die wir mit unserem Server integrieren. Um die Integrationstests nun möglichst nah an der Produktivumgebung auszuführen, haben wir uns entschlossen, einen Slave mit Windows einzusetzen. Bis dahin hatten wir bei unseren Slaves auf Linux gesetzt und mussten uns daher Schritt für Schritt an eine lauffähige Konfiguration herantasten. Um die Schritte reproduzierbar zu gestalten und gegebenenfalls in Zukunft auch für andere Windows-Slaves wiederzuverwenden, haben wir uns dazu entschieden, Ansible als Grundlage zu verwenden. Unter Linux haben wir mit diesem Vorgehen gute Erfahrungen gemacht.

Vorbereitung

Die Kommunktion zwischen Linux und Windows erfolgt über “Windows Remote Management (winrm)”. Unter Linux muss dafür die “Python library for Windows Remote Management” installiert werden.

pip install "pywinrm>=0.2.2"

Eine Anleitung für die Installation von “pip” findet  sich hier.

Für die Vorbereitung der Powershell steht auf der Ansible-Seite ein entsprechendes Script bereit. Dort wird auch genauer beschrieben, mit welchen Methoden die Verbindung abgesichert werden kann. Für das folgende Beispiel beschränken wir uns auf eine einfache Verbindung. Daher kann das Script mittels

wr https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1 -UseBasicParsing | iex

über die Powershell ausgeführt werden. Wichtig ist dabei, dass die Powershell mit Adminstratorrechten läuft.

Paketmanager

Einer der Nachteile von Windows gegenüber Linux war immer ein fehlendes Paketmanagement, das einfache Installationen und Updates von Software erlaubt und Abhängigkeiten verwaltet. Dinge wie Maven oder Git mussten einzeln geladen, installiert und gepflegt werden. Mit Chocolatey gibt es einen Paketmanager für Windows, der über ein Ansible-Modul unterstützt wird und dessen Repostory viele wichtige Pakete enthält. Dazu gehören z.B. Java, Git, Docker (Engine, Client, Machine), Maven und Gradle. Ob die Pakete auch regelmäßig aktualisiert werden, sollte man vor dem Einsatz nochmal prüfen. Aber für die hier benötigten Pakete war die Aktualität gegeben. Bevor man sich also überlegt, ein Programm “von Hand” zu installieren, lohnt sich eine Suche in den angebotenen Paketen. Es wird dort im Disclaimer allerdings ausdrücklich daraufhin gewiesen, dass es sich um von der Community verwaltete Pakete handelt. Die Pakete selbst enthalten keine Binaries, sondern lediglich einen Link darauf. Das ist natürlich eine potentielle Fehlerquelle (z.B. wenn ein Anbieter sich entscheidet, den Link zu ändern), die für einen produktiven Einsatz kritischer Systeme beachtet werden sollte. Es ist aber auch möglich, eigene Repositories aufzusetzen.

Ansible Konfiguration

Die Installation von Jenkins Swarm gestaltet sich denkbar einfach, da unter Ansible-Galaxy bereits eine vorkonfigurierte Rolle vorhanden ist. Diese Rolle kann eingebunden werden und bietet eine Vielzahl von Variablen, um den Swarm-Client entsprechend der eigenen Anforderungen zu konfigurieren.

- hosts: jenkins_slave
  vars:
    jenkins_master_url: 'http://jenkins.example.com'
    #...
    java: C:\Program Files\Java\jre1.8.0_144\bin\java.exe
  roles:
    - { role: mmagonde.jenkins-windows-swarm }

Bei der Variable “java” muss der Pfad zur java.exe hinterlegt werden. Benutzt man, wie weiter unten gezeigt, Chocolatey zur Installation von Java, wird dieses in einem Verzeichnis installiert, das die Versionsnummer enthält. Daher ist ist hilfreich, die Versionen zu fixieren, um Konflikte zu vermeiden. Bezüglich der Rolle aus Ansible-Galaxy gibt es leider auch eine schlechte Nachricht: Mit dem Creators-Update von Windows 10 tritt ein Problem im Zusammenhang mit dem zugrunde liegenden Service-Wrapper NSSM auf, der von der Rolle verwendet wird, um Jenkins als Dienst zu installieren. Dieses Problem sorgt dafür, dass installierte Dienste unter Umständen nicht starten. Leider gehört Jenkins-Swarm zu diesen Diensten.  Es gibt bereits einen Fix dafür, allerdings erst als Pre-Release und somit wird es noch etwas dauern, bis das entsprechende NSSM-Modul für Ansible aktualisiert wird. Bis dahin bestehen folgende Alternativen:

  1. Das Creators-Update nicht installieren und auf den Fix warten.
  2. Einen andereren Service-Wrapper einsetzen. Das bringt allerdings einen gewissen Aufwand mit sich, da die nötigen Tasks selbst geschrieben werden müssen. Die Galaxy-Rolle kann dabei als Vorlage dienen.
  3. Erstmal akzeptieren, dass der erste Durchlauf von Ansible mit einem Fehler abbricht. Dann bei Windows anmelden und die Kommandozeile als Administrator öffnen und folgenden Befehl ausführen:

    nssm set Jenkins_Agent AppNoConsole 1

    Danach Ansible erneut starten. Dieses Mal ist der Durchlauf dann erfolgreich. Wir werden uns bei einer Aktualisierung von Jenkins-Swarm für diese Option entscheiden, da sie bei nur einem Windows-Slave einen geringen Aufwand mit sich bringt. Bei mehreren Slaves könnte man auch überlegen, die Galaxy-Rolle mit diesem Befehl zu ergänzen.

Zusätzlich können dann weitere Dienste wie git und Java mit Chocolatey installiert werden:

- name: Install git
  win_chocolatey:
    name: git.install
    state: latest
- name: Install JRE 8
  win_chocolatey:
    name: jre8
    version: 8.0.144

Mit “state” kann angegeben werden, wie die Installation durchgeführt werden soll. “Latest” installiert immer die aktuellste Version. Es ist aber auch möglich mit dem Attribut “version” eine bestimmte Version zu fixieren. Alle weiteren Optionen für Chocolatey finden sich hier.

Das Anlegen von Benutzern und Verzeichnissen oder das Kopieren von Einstellungen kann ebenfalls mit vorhanden Ansible-Modulen vorgenommen werden:

- name: Ensure user ci is present
  win_user:
    name: "{{ jenkins_local_user }}"
    password: "{{ jenkins_local_password }}"
    groups:
      - Users
- name: Create directory structure
  win_file: path={{item.path}} state=directory
  with_items:
   - { path: 'C:\ci\ci-tools\swarm' }
- name: Copy maven settings 
  win_copy: src={{ item.file }} dest=C:\Users\ci\.m2\{{ item.filename }}
  with_items:
   - { file: .m2/settings.xml, filename: settings.xml }
   - { file: .m2/settings-security.xml, filename: settings-security.xml }

Die Konfiguration von Docker besteht aus zwei Schritten: Die Installation der Docker Engine erfolgt mit Hilfe von Chocolatey und ist daher sehr übersichtlich.

- name: Install Docker Engine
  win_chocolatey:
    name: docker-for-windows
  notify: "install docker"

Docker wird dabei auch als Dienst registriert und automatisch gestartet. Der zweite Teil ist etwas aufwändiger und stellt sicher, dass Hyper-V auf dem Windows-Rechner vorhanden und aktiviert ist. Umgesetzt wird dieser Teil mittels Handler, der nach der erfolgreichen Installation ausgeführt wird. Der Rechner wird neu gestartet, wenn Hyper-V noch nicht aktiviert war.

- win_command: dism /online /get-featureinfo /featurename:Microsoft- Hyper-V
  listen: "install docker"
  register: hyperv_installed_out
  
- name: install Hyper-V Feature
  win_command: dism.exe /Online /Enable-Feature:Microsoft-Hyper-V /All /Quiet /NoRestart
  listen: "install docker"
  register: hyperv_install
  failed_when: hyperv_install.rc != 3010
  notify: "reboot system"
  when: "'State : Disabled' in hyperv_installed_out.stdout"
- name: rebooting system
  win_reboot:
    shutdown_timeout_sec: 3600
    reboot_timeout_sec: 3600
  listen: "reboot system"
  when: hyperv_install is defined and hyperv_install.rc == 3010

Erfahrungen

Jenkins-Swarm hat sich in der Version 3.3 noch etwas instabil verhalten und wollte sich teilweise erst nach einem Neustart des gesamten Systems wieder mit dem Jenkins-Server verbinden. Dies war allerdings eher die Ausnahme als die Regel. Wir werden demnächst Jenkins-Swarm auf die Version 3.5 aktualisieren. Sollte dies Auswirkungen auf die Stabilität des Swarm-Clients haben, werden wir das in diesem Blogpost noch ergänzen.

Docker läuft ebenfalls weitestgehend stabil.  Allerdings muss man Container, Images und Volumes regelmäßig aufräumen und auch aufpassen, dass die virtuelle Festplatte von Hyper-V nicht voll wird, da der Speicher nicht freigegeben wird, während Docker läuft. Nach einem Neustart von Docker konnten wir dann beobachten, dass die virtuelle Festplatte des zugrunde liegenden Linux entsprechend der freigegebenen Speichermenge schrumpfte.

Mit Hyper-V ist die Docker Engine unter Windows nicht so leichtgewichtig wie unter Linux und für den Einsatz in einer virtualisierten Umgebung nur bedingt geeignet. Mittels Nested Virtualization ist es aber grundsätzlich möglich, die Engine auch unter einem virtualisierten Windows zu betreiben. Da wir im Moment nur einen Windows-Slave im Einsatz haben, haben wir uns für eine physische Maschine als Grundlage entschieden. Anstatt der Engine könnte man auch überlegen Docker Machine zu nutzen und nur den Docker Client auf dem Slave zu installieren. Das setzt dann allerdings einen dedizierten Docker Host voraus, könnte aber den Einsatz in einer virtualisierten Umgebung erleichtern.

Fazit

Wie der Blogpost zeigt, ist es keine Zauberei mehr, einen Jenkins-Slave mit Windows automatisch zu einer bestehenden CI-Umgebung hinzuzufügen. Der Aufwand, der für die weitere Pflege der Ansible-Konfiguration anfallen kann, sollte dabei aber nicht unterschätzt werden. Ansible bietet bereits viele Module für Windows, die die Konfiguration stark vereinfachen. Allerdings sind davon viele noch nicht als “stable” markiert, wodurch sich die Schnittstellen noch ändern können. Man kann sich also nicht darauf verlassen, dass eine laufende Konfiguration mit einer neuen Version von Ansible noch funktioniert. Wie wir gesehen haben, reicht auch schon eine Aktualisierung des Betriebssystem aus, um eine einstmals funktionierende Konfiguration funktionsunfähig zu machen. Bei Updates ist daher Vorsicht angebracht. Positiv hervorgehoben werden kann, dass es mit Chocolatey einen Paketmanager gibt, der die Installation von Programmen angenehm einfach macht. Dabei sollte man aber im Hinterkopf behalten, dass das von der Community verwaltete Repository zu Instabilitäten führen kann.