Der nachfolgende Artikel beschreibt die Einrichtung von Git, Gerrit und Jenkins auf einem Continuous Integration (CI) Server. Ziel ist es, Code-Änderungen zunächst zu überprüfen, bevor sie ins Software Configuration Management (SCM) System – Git – integriert werden. Mittels Gerrit können manuelle Code-Reviews stattfinden, so dass Änderungen zunächst von anderen Entwicklern abzusegnen sind. Vier Augen sehen bekanntlich mehr als zwei. Hier kommt auch Jenkins ins Spiel. Er übernimmt die Rolle eines weiteren Reviewers und stimmt für einen Commit, solange das beinhaltende Projekt kompilierbar ist und die Testfälle erfolgreich durchlaufen. Für jeden Commit finden also eine manuelle und eine automatisierte Prüfung statt. Erst wenn beide erfolgreich sind, wird ein Commit integriert und steht somit als gemeinsame Codebasis zur Verfügung.
Durch dieses Verfahren ist gewährleistet, dass der Code jederzeit kompilierbar ist, Testfälle durchlaufen und mindestens vier Augen über jede Änderung geschaut haben. Die Code-Qualität steigt damit merklich.
Server
Als Server verwende ich ein Ubuntu 12.10 System, welches in einer VMWare betrieben wird. Von ein paar Ubuntu Spezifika abgesehen, spielt das gewählte Betriebssystem aber keine Rolle.
Bevor man mit der Installation der CI-Komponenten loslegen kann, müssen zunächst die Grundvoraussetzungen geschaffen werden. So wird auf dem Server SSH, Java und Maven3 benötigt. Die ersten zwei kann man bequem über die Paketverwaltung beziehen:
sudo apt-get install ssh
sudo apt-get install openjdk-7-jdk
Für Maven3 ist eine manuelle Installation notwendig. Unter der URL http://maven.apache.org kann die aktuelle Version 3.0.5 gedownloadet werden, die man anschließend entpackt und nach /usr/local kopiert:
sudo cp -R apache-maven-3.0.5 /usr/local
Nun legt man einen symbolischen Link an, der auf Maven verweist:
sudo ln -s /usr/local/apache-maven-3.0.5/bin/mvn /usr/bin/mvn
Will man überprüfen, ob die Installation geklappt hat, kann die Maven Version erfragt werden:
mvn -version
Weiterhin wird ein SSH-Key benötigt, mit dem sich Jenkins gegenüber Gerrit authentifizieren soll. Der Key muss im Benutzer-Verzeichnis unter /home/username/.ssh/id_rsa vorliegen, wo er standardmäßig auch erzeugt wird. Soll er in einem anderen Verzeichnis liegen, z.B. weil Jenkins unter einem eigenen Benutzer gestartet wird, kann während des Erstellungsprozesses ein abweichendes Zielverzeichnis angegeben werden. Der Befehl zur Erstellung des SSH-Keys lautet:
ssh-keygen
Es werden die Dateien id_rsa und id_rsa.pub erstellt, wobei letztere den öffentlichen Key beinhaltet, der in Gerrit dem Jenkins-Benutzer zuzuordnen ist. Sofern noch nicht vorhanden, kann man an dieser Stelle einen weiterer SSH-Key für den eigenen Arbeitsrechner generieren. Dieser Key wird dann dem eigenen Gerrit-Benutzer zugewiesen. Wenn es sich beim Arbeitsrechner um ein Windows System handelt, ist der Befehl ssh-keygen über die Eingabeaufforderung nicht verfügbar. Nach erfolgter Git-Installation kann der Befehl aber auf der Git-Bash verwendet werden. Hierbei handelt es sich um eine Konsole, die zusätzlich zu den Git-Kommandos auch Linux-Befehle beinhaltet. Git kann unter der URL http://git-scm.com/ bezogen werden.
Sofern man im Netzwerk einen eigenen DNS-Server betreibt bzw. der Router entsprechende Möglichkeiten besitzt, kann dem CI-Server eine feste IP und die Domain CIServer zugewiesen werden. Andernfalls kann man den CI-Server in die Host-Dateien aller im Netzwerk befindlichen Rechner eintragen. So erspart man sich das Arbeiten mit IP-Adressen. Diese wird mittels ifconfig erfragt und in folgender Form in die Datei /etc/hosts eingetragen:
192.168.xxx.xxx CIServer CIServer
Der CI-Server ist nun unter der Domain CIServer erreichbar, was sich mit ping CIServer überprüfen lässt.
Git
Auch Git lässt sich wie zuvor SSH und Java über die Paketverwaltung installieren:
sudo apt-get install git
Anschließend wird ein Repository erstellt, wofür ein neues Verzeichnis erzeugt und hier das Git-Repository angelegt wird:
git –bare init
Die Option bare sorgt dafür, dass das Repository ohne Working-Directory generiert wird. Nun muss eine Möglichkeit geschaffen werden, mit der Jenkins auf Git zugreifen kann. Der einfachste Weg ist es, das anonyme Git-Protokoll zu verwenden. Hierbei ist keine Authentifizierung nötig und es reicht aus, den Git-Daemon mit entsprechenden Parametern zu starten:
git daemon –export-all –base-path=/path/to/git/repository
Gerrit
Als nächstes wird Gerrit von der Projekt-Seite http://code.google.com/p/gerrit/ in der aktuellen Version 2.5.2 als WAR-Archiv (gerrit-full-2.5.2.war) gedownloadet und in ein beliebiges Verzeichnis kopiert. Die Version 2.6 ist bislang nur als Release-Kandidat verfügbar. Die Einrichtung erfolgt mit dem Befehl:
java -jar gerrit-full-2.5.2.war init
Während der Installationsroutine werden zu tätigenden Einstellungen abgefragt. Hier eine kurze Übersicht über die einzelnen Punkte und der zu wählenden Optionen:
- Git Repository: path/to/repository
- SQL Datenbank: Default – H2
- Authentication Method: DEVELOPMENT_BECOME_ANY_ACCOUNT (Liste aller Möglichkeiten mit ?)
- Email Einstellungen: Mit Enter überspringen
- Container Process – Run as: Default (Aktuell eingeloggter Benutzer)
- Java Runtime: Default (Die zuvor installierte Java-Version wird automatisch erkannt)
- Copy gerrit.war ins Zielverzeichnis: yes
- SSH Daemon – Listen on address: Default (*)
- SSH Daemon – Listen on port: Default (29418)
- Install Bouny Castle Crypto v144 (optinal): no
- Behind reverse proxy: no
- Use ssh (https://): no
- Http Daemon – address: Default (*)
- Http Daemon – Port: Default (8080)
- Promt to install core Plugins: no
Default-Einstellungen werden mit Enter bestätigt, nur abweichende Angaben müssen explizit gemacht werden. Bei der Authentifizierungsmethode wird einfachhalthalber die Einstellung DEVELOPMENT_BECOME_ANY_ACCOUNT gewählt. In einer LIVE-Umgebung darf diese Konfiguration nicht zum Einsatz kommen, da Benutzer hierbei frei und ohne Angabe eines Passwortes zwischen den Accounts wählen können. Zusätzlich stehen diese Authentifizierungsvarianten zur Auswahl:
- OpenID (Default)
- HTTP: Die Authentifizierung findet über einen Web-Server oder Servlet-Container statt, nicht in Gerrit selber.
- HTTP_LDAP: Zusätzlich zur HTTP-Authentifizierung kann sich der Benutzer über LDAP einloggen.
- LDAP
Sofern man auf Gerrit-Benachrichtigungen via Email verzichtet, müssen diesbezüglich keine Einstellungen vorgenommen werden. Wie auch Jenkins kann Gerrit unter einem eigenen Benutzer ausgeführt werden. Gibt man nichts an, wird Gerrit unter dem aktuell eingeloggten Benutzer gestartet. Wenn man sonst die Standard-Einstellungen beibehält, kann das Web-Frontend von Gerrit unter der URL http://CIServer:8080 in einem Browser aufgerufen und Commits über den Port 29418 getätigt werden.
Das Installationsverzeichnis enthält den Ordner bin, indem sich das Skript gerrit.sh zum Starten und Stoppen des Dienstes befindet:
./gerrit.sh start -d /path/to/gerrit
Weitere Einstellungen werden über das Web-Frontend vorgenommen. Unter Become wird zunächst ein neuer Account registriert, bestehend aus Name, Email und Benutzername. Außerdem muss der öffentliche SSH-Key, den man sich für den Arbeitsrechner erstellt hat, in das entsprechende Feld kopiert werden. Anhand des Benutzernamens und des SSH-Keys werden später die Commits gegenüber Gerrit authentifiziert. Ist hier kein oder ein falscher SSH-Key eingetragen, wird Gerrit die Commits ablehnen. Zu beachten ist, dass der zuerst registrierte Benutzer automatisch Admin-Rechte erhält. Falls auch andere Benutzer dieses Recht benötigen, muss der Account explizit der Gruppe Administrator hinzugefügt werden. Da Jenkins einen eigenen Gerrit-Benutzer erhält, meldet man sich nun ab, um einen weiteren Account zu registrieren. Als Name und Benutzername wird Jenkins gewählt und anschließend der für ihn erstellte SSH-Key eingetragen. Ist dies geschehen, benötigt man wieder Admin-Rechte, so dass man sich mit seinem eigenen Account einloggen muss. Dank der Einstellung DEVELOPMENT_BECOME_ANY_ACCOUNT kann unter dem Punkt Choose einfach der gewünschte Account ausgesucht werden. Nun wird der Benutzer Jenkins der Gruppe Non-Interaction Users hinzugefügt. Hierzu wählt man Groups -> List und hier die entsprechende Gruppe.
Auf der folgenden Seite kann Jenkins der Gruppe hinzugefügt werden. Anschließend müssen die Projekte konfiguriert werden. Hierzu wählt man Projects -> List. Bislang befindet sich hier nur der Punkt All-Projects, dessen Einstellungen man für neue Projekte übernehmen kann. Nimmt man hier Konfigurationen vor, die in allen Projekten benötigt werden, spart man sich entsprechenden Mehraufwand. Nach der Auswahl von All-Projects wird der Menüpunkt Access gewählt.
Wichtig ist, dass Accounts aus der Gruppe Non-Interaction Users Lese-Rechte auf refs/* erhalten. Des Weiteren benötigt diese Gruppe Rechte für Code Review und Verified auf refs/heads/*. Registrierte Benutzer müssen beim Code-Review auch mit +2 voten können, um diesen erfolgreich abzuschließen. Weiterhin erhalten Administratoren das Recht zu Submitten, um Code-Änderungen in die gemeinsame Code-Basis integrieren zu können. Diese Option steht allerdings erst zur Verfügung, wenn Jenkins-Build und Code-Review erfolgreich abgeschlossen sind. Nun kann über den Link Create New Project ein neues Projekt erstellt werden. Über Rights Inherit From werden die Eigenschaften von All-Projects übernommen.
Das leere Projekt kann nun auf dem Arbeitsrechner ausgecheckt werden:
git clone ssh://username@CIServer:29418/de.mz
Wie bereits erwähnt, findet die Authentifizierung über die Kombination username und SSH-Key statt. Nun wird in das Verzeichnis de.mz gewechselt und ein Maven Projekt erstellt, indem hier die Datei pom.xml mit folgendem Inhalt erzeugt wird:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>de.mz</groupId>
<artifactId>de.mz</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
</project>
Anschließend kann man sich den Status in Git anzeigen lassen:
git status
Hier wird die pom.xml unter untracked files aufgelistet. Um diese Datei zu tracken, wird folgender Befehl ausgeführt:
git add .
Nun kann die pom.xml ins lokale Git-Repository commitet werden:
git commit -m ‚My first commit‘
Da die Änderung auch für andere Entwickler verfügbar gemacht werden soll, wird sie in den Gerrit gepushed:
git push
Das wird allerdings zu einem Fehler führen, da Gerrit Commits auf refs/head/master nicht zulässt. Hier landen Code-Änderungen erst nach erfolgreichem Jenkins-Verify, Code-Review und anschließendem submit. Entwickler haben ihre Änderungen zunächst auf refs/for/master zu pushen, wofür die Push-Konfiguration angepasst werden muss:
git config remote.origin.push HEAD:refs/for/master
Diese Anpassung ist im Repository unter .git/config ersichtlich. Nach einem erneuten push, liegt nun der erste Commit in Gerrit vor.
Jenkins
Auf der Seite http://jenkins-ci.org/ kann die aktuelle und beste Jenkins Version als WAR-Archiv gedownloadet werden – zurzeit ist das die Version 1.508. Auch Jenkins wird in ein beliebiges Verzeichnis kopiert und anschließend gestartet. Da sowohl Jenkins als auch Gerrit standardmäßig auf Port 8080 laufen, muss ein anderer Port (z.B. 8081) gewählt werden:
java -jar jenkins.war –httpPort=8081
Nun kann das Web-Frontend von Jenkins in einem Browser unter der URL http://CIServer:8081 aufgerufen werden, wo alle weiteren Konfigurationen vorzunehmen sind. Zunächst wird das Gerrit-Trigger-Plugin installiert. Unter Jenkins verwalten -> Plugins verwalten findet sich der Punkt Verfügbar. Hier kann man aus einer langen Liste von verschiedensten Plugins wählen. In der Kategorie Build-Auslöser findet sich das Plugin Gerrit-Trigger, welches für die Integration von Jenkins und Gerrit benötigt wird. Man aktiviert die entsprechende Checkbox und wählt unten den Button Download now and install after restart.
Nach dem Neustart von Jenkins müssen weitere Konfigurationen vorgenommen werden. Diesmal wählt man Jenkins verwalten -> System konfigurieren. Auf dieser Seite werden nun nacheinander die folgenden Punkte abgearbeitet:
JDK: Als Namen trägt man java-7-openjdk ein. Die Checkbox bei Automatisch Installieren kann entfernt werden, da Java bereits zu Beginn installiert wurde. Es muss lediglich unter JAVA_HOME der Pfad zum JDK7 angegeben werden, unter Ubuntu ist dies /usr/lib/jvm/java-1.7.0-openjdk-i386:
Git: Als Name wird git eingetragen und als Installationsverzeichnis der entsprechende Pfad. Unter Ubuntu ist das /usr/bin/git:
Maven: Als Name wird apache-maven-3.0.5 eingetragen, bei MAVEN_HOME der Pfad zur Maven-Installation. Unter Ubuntu ist das /usr/local/apache-maven-3.0.5:
Jenkins Location: Als Jenkins URL wird http://CIServer:8081 eingetragen:
Git plugin: Die Property user.name wird auf jenkins gesetzt und als user.email eine beliebige Email-Adresse. Die Daten entsprechen dem Jenkins-Benutzer unter Gerrit:
Jenkins ist nun vollständig konfiguriert, es folgen allerdings weitere Einstellungen für das Gerrit-Trigger-Plugin. Diese werden unter Jenkins verwalten -> Gerrit Trigger vorgenommen. Als Hostname wird CIServer eingetragen und als Frontend URL http://CIServer:8080/. Wichtig ist, dass nicht auf localhost verwiesen wird, da andernfalls nicht korrekt auf Gerrit-Commits verlinkt werden kann. Sofern der Standard Gerrit-SSH-Port verwendet wurde, ist hier 29418 einzutragen. Als Username wird der für Jenkins erstellte Gerrit-Benutzer angegeben und auf dessen SSH Key verwiesen. Anschließend kann die Connection getestet werden:
Trotz erfolgreicher Prüfung der Verbindung, kommt es beim Speichern zu einem Fehler:
The connection to Gerrit is down! Check your settings and the Gerrit server.
Diese Fehlermeldung ist etwas irreführend, da der Gerrit-Server gestartet wurde und auch weiterhin läuft. Nicht laufen tut der Gerrit-Trigger. Hierfür findet sich auf derselben Seite ein Control, mit dem der Trigger explizit gestartet werden muss.
Erst jetzt können die Einstellungen erfolgreich gespeichert werden, womit die Konfiguration vollständig abgeschlossen ist. Nun wird der erste Jenkins-Job erstellt, der für die Verifizierung der Gerrit-Commits zuständig ist. Hierfür wird ein Free-Style-Projekt angelegt und der Name gerrit-verifyer vergeben. Erst auf der folgenden Seite, wird der Job genauer definiert. Zunächst wird beim Punkt Source-Code-Management Git gewählt. Zugegriffen wird per SSH, welches als Protokoll bei der Repository URL angegeben wird. Zugreifender Benutzer ist jenkins und das gewählte Repository de.mz, welches auf dem CIServer liegt. Unter Refspec wird $GERRIT_REFSPEC und bei Branches to build $GERRIT_BRANCH eingetragen:
Anschließend wird der Button Erweitert… gewählt. Unter den nun erscheinenden Einstellungen muss das Häkchen bei Clean after checkout gesetzt werden. Andernfalls wird immer der gleiche Commit gebaut, statt jeweils die aktuellen Änderungen auszuchecken. Als Choosing strategy wird Gerrit Trigger selektiert:
Um bei einem neuen Gerrit-Commit automatisch den Job zu starten, wird als Build-Auslöser Gerrit event gewählt. Im nun aufklappenden Bereich wird unter Gerrit Projekt das Projekt de.mz eingetragen und der Branch master verwendet:
Als letztes wird das Build-Verfahren bestimmt. Da es sich beim Projekt de.mz um ein Maven-Projekt handelt, wird die Option Maven Goal aufrufen hinzugefügt. Die Goals sind clean und package:
Der Job ist nun vollständig konfiguriert und kann gespeichert werden. Möchte man den Job allerdings mit „Jetzt bauen“ ausführen, wird es zwangsläufig zu einem Fehler kommen:
Couldn’t find remote ref $GERRIT_REFSPEC
Hintergrund ist, dass die Platzhalter $GERRIT_REFSPEC und $GERRIT_BRANCH vom Gerrit-Trigger ersetzt werden. Build-Prozesse außerhalb des Triggers, können mit den Platzhaltern nichts anfangen. Dennoch kann man bereits getätigte Gerrit-Commits von Jenkins bauen lassen, indem man auf der Startseite von Jenkins den Punkt Query and Trigger Gerrit Patches wählt. Hier können beliebige Gerrit-Search-Querys ausgeführt und in der Ergebnisliste, die zu bauenden Commits selektiert werden. Unter anderem kann nach der Change-Nr – hier 98 – gesucht werden, die in Gerrit ersichtlich ist: refs/changes/98/98/1.
Bei zukünftigen Commits ist dieser manuelle Schritt nicht mehr notwendig, da der Job bei einem Commit automatisch getriggert wird. Jenkins hat nun den Commit verified, so dass die Änderung nur noch von einem Entwickler gereviewt werden muss. Sobald das geschehen ist, kann die Code-Änderung von einem Administrator mit dem Button Submit Patch Set 1 in die gemeinsame Code-Basis integriert werden. Der Commit wird anschließend unter Merged aufgelistet und steht jedem Entwickler nach einem fetch zur Verfügung:
git fetch
git checkout origin/master
Sicherlich ist die Integration von Git in eine IDE ein nicht unwichtiger Punkt. Daher wird im nächsten Schritt erläutert, wie man in Eclipse mit dem Plugin EGit Zugriff auf die Git Funktionalität erhält. Verwendet man eine andere oder keine IDE, kann dieser Teil des Artikels getrost übersprungen werden.
Eclipse
Unter http://www.eclipse.org/downloads/ kann die aktuelle Eclipse Version bezogen werden. Zurzeit ist das Eclipse Juno in der Version 4.2.2. Außerdem werden die folgenden Plugins benötigt, die man über die JUNO Update Side http://download.eclipse.org/releases/juno installieren kann:
- Eclipse Egit
- Egit Plug-in Import Support
- m2e – Maven Integration for Eclipse
Weiterhin wird über Windows -> Preferences -> Maven -> Discovery -> Open Catalog der m2e-Marketplace geöffnet. Unter m2e Team providers findet sich der zu installierende Punkt m2e-egit.
Nach einem Neustart von Eclipse wird in die Git-Repository-Exploring Perspektive gewechselt. Über Add an existing local Git repository fügt man das bereits geklonte Repository hinzu, wozu nur der entsprechende Pfad im Dateisystem anzugeben ist. Hätte man das Projekt de.mz nicht bereits über die Konsole ausgecheckt, könnte alternativ die Option Clone a Git repository gewählt werden. So aber, wird das bestehende Repository weiterverwendet und Änderungen können beliebig über die IDE oder direkt im Dateisystem vorgenommen werden. Unter Working Directory wird das Projekt nun als Maven-Projekt importieren.
Das Projekt ist jetzt in der Java-Perspektive verfügbar und kann beliebig bearbeitet werden. Bevor sich Änderungen allerdings zu Gerrit pushen lassen, muss zunächst die Push-Konfiguration von Egit angepasst werden. Auch hier wird standardmäßig nach refs/head/master gepusht, statt nach refs/for/master. In der Git-Perspektive wird unter de.mz -> Remotes -> origin die Konfiguration geöffnet und unter dem Punkt Advanced Source ref auf HEAD und Destination ref auf refs/for/master gesetzt. Anschließend wird die Spezifikation hinzugefügt. Wichtig ist, dass man die Checkbox bei Save specifications in origin configuration setzt, da andernfalls die Anpassung bei jedem Commit notwendig ist.
Leider erkennt Eclipse nach erfolgreichem Projekt-Import nicht immer, dass das Projekt Git zugewiesen ist. Um dies manuell nachzuholen, wählt man im Context-Menü Team -> Share Project. Anschließend muss das verantwortliche Repository Plugin gewählt werden, in diesem Fall also Git. Anschließend können Änderungen bequem aus der IDE heraus commitet werden.
Autostart
Im letzten Schritt muss sichergestellt werden, dass die Dienste Gerrit, Jenkins und Git auch nach einem Neustart des Servers verfügbar sind. Hierzu werden die Start-Skripte in die systemweite Cron-Tabelle /etc/crontab eingetragen:
@reboot user /path/to/gerrit/bin/gerrit.sh start -d /path/to/gerrit
@reboot user java -jar /path/to/jenkins/jenkins.war –httpPort=8081
@reboot user git daemon –export-all –base-path=/path/to/git/repository
Jeder der drei Einträge wird beim Server-Reboot mit den Rechten des Benutzers user ausgeführt. Sofern man für die einzelnen Dienste separate Benutzer angelegt hat, muss stattdessen dieser eingetragen werden.