Umstieg von Jenkins zu Gitlab CI

Umstieg von Jenkins zu Gitlab CI

Benjamin Kaup

4. September 2020

DEVOPS

Ausgangssituation

Es ist wie so oft, man kommt in ein Projekt und hat dort Tools und Verfahren, welche “schon immer so gemacht” werden. So war es auch in diesem Fall. Ein Kunde setzt eine Jenkins-Instanz ein, welche per Ansible auf AWS deployt wird. So weit, so gut. Wären da nicht die vielen “Aber”.

Viele Probleme beziehen sich auf die Komplexität die dahinter steckt, welche aber notwendig ist um den Jenkins in einer für die Kundencompliance akzeptablen Art und Weise laufen zu lassen. Hier tun sich einige Sicherheitslücken auf, die durch die vielen Skripte und vielen Automatisierungen übersehen wurden und sich erst nach einigen Blicken offenbaren. Ein weiteres Problem ist die Häufigkeit der Aktualisierungen. Sicher, man kann beim Jenkins selber auf LTS Versionen zurückgreifen und verpasst trotzdem keine der wichtigsten Funktionen. Aber gerade die Plugins und deren Aktualisierung macht das Leben damit wirklich unangenehm.

Ein Beispiel: Für die Compliance war es wichtig, dass User sich per SSO anmelden müssen. Der einfachste Weg war in diesem Fall, dies über das Repository (Gitlab) zu machen. Hierfür gab es sogar ein Plugin, welches allerdings seit einigen Monaten nicht mehr aktualisiert wurde. Erst einmal ein geringes Problem, doch nach einigen Wochen Einsatz kam es natürlich wie es kommen musste, eine Sicherheitslücke wurde für dieses Plugin öffentlich. Da nicht mit einer schnellen Behebung zu rechnen war, mussten also alternative Konzepte her. Am Ende wurde das Plugin doch nach ein paar wenigen Wochen aktualisiert und alles war wieder gut. Jedoch steht diese Sicherheitslücke symbolisch für das Problem, welches wir hier erfahren mussten. Man verlässt sich auf viele freie – und ehrenamtliche – Entwickler und deren Schnelligkeit bei Behebung von Sicherheitslücken und anderen Fehlern.

Die Motivation

Der Kunde hatte für Pipelines anderer Projekte und für das gesamte Repository Gitlab im Einsatz. Es hatte sich also angeboten, vorhandene Strukturen zu nutzen und weniger selbst zu erstellen. Der Jenkins benötigte eine eigene Serverinstanz, welche mit Gitlab CI wegfallen würde. Die notwendigen Runner für Gitlab CI standen bereits zur Verfügung, mussten nur für einen Umstieg etwas erweitert werden, was die Kapazität anging.

Der erste Gedanke ist dann natürlich “Umstieg – sofort”. Was in solchen Projekten aber eher ungünstig ist, denn jeder Wechsel braucht Kapazitäten im Team. Jedes Teammitglied sollte die Build-Pipeline verstehen und ändern können, also muss auch jeder sich auf einen Umstieg einlassen. Der Aufwand alle mit zu nehmen und alle Schritte von Jenkins auf Gitlab CI zu übertragen, wurde mit hohem Aufwand geschätzt. Nun muss sich das erst einmal für den Auftraggeber lohnen, warum sonst sollte er ein funktionierendes und laufendes System mit hohem Aufwand ändern. Never change a running system …

Also mussten für eine Entscheidung erst einmal Fakten her und die Opportunitätskosten mit einander verglichen werden.

Unterschiede

In den bestehenden Pipelines wurde viel mit Groovy gescriptet, um bestimmte Schritte in den Anforderungen entsprechend auszuführen. Zum Beispiel wird ein Sonarqube eingesetzt um die Qualität des Codes zu überprüfen. In Jenkins gibt es hierfür ein Plugin, welches den Test auslöst und auf das Ergebnis wartet. In Gitlab CI mussten wir die Funktion, dass auf das Testergebnis gewartet wird selber bauen, da es keine passende Möglichkeit in Form eines Containers, oder ähnliches gab. Das daraus resultierende Skript ist zwar nur wenige Zeilen lang, bedeutet aber, dass wir uns bei eventuellen Änderungen der API, oder ähnliches mehr Aufwand schaffen.

In Gitlab CI ist das grundsätzlich ebenfalls alles möglich, allerdings nicht einfach in der Pipelinedefinition mit wenigen Scriptbefehlen. Einfachere Aufgaben kann man per shell-script in der Definition lösen, aber gerade bei komplizierteren Aufgaben führt kein Weg an einer Auslagerung in ein extra Skript vorbei. Eine Lösung war hier ein Buildcontainer, der Scripte für alle möglichen Schritte bereithält und von jedem Schritt als Basis genutzt wird. Gut für die Übersicht, Abstriche gibt es, weil dieser Container erst gebaut werden muss und damit wieder ein Repository mehr entsteht.

Wie angesprochen waren die Gitlab CI Runner in diesem Projekt schon vorhanden, was sehr viele Vorteile mit sich bringt (kein Wartungsaufwand, definiertes Servicelevel, weniger Tools im Einsatz), aber auch gehörige Nachteile. Als Beispiel sei hier zu nennen, dass die Runner zwischendurch auch mal ausgefallen sind, was auch trotz Servicelevel mal vorkommt. Die Runner sind auch von anderen Teilprojekten in Gebrauch, daher kann es zu Engpässen kommen. Und man muss natürlich viel mehr darauf achten, was aus Security-Sicht dort landet, es haben ja auch andere Teams Zugriff auf diese Runner (wobei hier die ausschließlich containerisierten Schritte von Gitlab CI wenig Spielraum lassen, etwas auf den Systemen zu hinterlassen).

Hinzu kommt die geringere Individualität. In agilen Projekten sind Teams unabhängig und können – wenn auch im Rahmen – ihre eigene Umwelt definieren. Hier schränken gemeinsam genutzte Runner und ein System, auf welches nur Nutzungs-Recht besteht natürlich entsprechend ein. Plugins gibt es nicht, und eigene Einstellungen am System auch nicht.

Durchführung

Nach den Abwägungen und den Abschätzungen des Aufwandes wurde in einzelnen Teams beschlossen, den Umstieg einfach mal zu testen in einzelnen Teilservices. Hierzu wurde die Jenkins-Pipeline bestmöglich in Gitlab CI übertragen, ohne Schritte weg zu lassen. In einem zweiten Schritt wurden die Schritte überdacht, ob eventuell welche zusammengelegt, parallelisiert oder geändert werden können und müssen.

Nach einem Aufwand von mehreren Tagen für diese Testpipeline stand eine Definition fest, mit der alle Umgebungen des Services beliefert werden konnten und die Anforderungen des Kundens zwecks Compliance, Security, etc. eingehalten werden konnten. Das Team war am gesamten Prozess beteiligt und kannte deswegen die inzwischen knapp 1.000 Zeilen Pipeline-Skript. Beim erstellen wurde zudem auf eine größtmögliche Kompatibilität , sowohl für andere Services als auch für andere Teams gelegt. Also eine generische Pipeline für alle Teams des Kunden.

Die Erweiterung auf andere Services lief reibungslos. Einfach die Definiton einbinden, ein Probelauf – und es lief.
Die generische Funktion, also die Erweiterung auf andere Teams hätte ebenfalls reibungslos laufen können, allerdings widersprach das der kollektiven Entscheidung eines anderen Teams. Aber nur weil die Möglichkeit dafür bedacht wurde, heisst das ja nicht, dass man daran gefesselt ist. So wurden die meisten Schritte von dem anderen Team adaptiert, und wo es nötig war individualisiert. Was bleibt ist der gemeinsame Ablageort, und tatsächlich noch etwa 70-80% der Definition, welche bei den Teams gleich ist. Dies ermöglicht auch immer noch bei Änderungen der Anforderungen ein schnelles und einfaches Eingreifen.

Fazit

Die wichtigste Erkenntnis aus diesem Unterfangen war für mich, dass beide Systeme ihre Vor- und Nachteile haben und tatsächlich auch recht unterschiedlich unter der Haube sind. Welches System das “Beste” ist, lässt sich auf keinen Fall pauschal sagen. In diesem Projekt, mit diesen Umständen, mit diesem Team und in dieser Umgebung war die Entscheidung von Jenkins auf Gitlab zu wechseln aber überfällig und auch nachhaltig sehr positiv. Bisher gab es noch keine Wünsche zurückzukehren, geschweige denn “das war aber damals viel besser”-Aussagen. Mit anderen Teams, Projekten oder Kunden kann das aber ganz anders aussehen.