Ein ausführlicher Vergleich zweier CI/CD-Server: Concourse und Jenkins.
Sofus Albertsen
Sofus is a Continuous Delivery Trainer and Consultant in Copenhagen. Before joining Eficode Praqma he was assistant professor on an Applied Science Bachelor program. He flies kites and in the summer he escapes the modern world by spending two weeks in a beach hut without electricity or network coverage.
Was brauchen wir von einem CI/CD-System? Wie entscheiden wir, welches wir einsetzen sollten? In diesem Blogbeitrag betrachten wir, wie ein modernes CI/CD-System aussehen sollte, und vergleichen zwei häufig genutzte Build-Systeme: Jenkins Pipelines und Concourse CI.
Kriterien zur Bewertung einer Build-Engine
Nutzer wählen eine Build-Engine wie Jenkins, Travis oder VSTS oft aus weniger rationalen Gründen. Die Entscheidung kann emotional sein oder darauf beruhen, dass „ein Teammitglied schon einmal damit gearbeitet hat“, statt fundiert getroffen zu werden. Die Wahl des passenden CI/CD-Systems sollte auf den Kriterien basieren, die für das Unternehmen und das Team relevant sind – nicht auf alten Gewohnheiten oder Bauchgefühl.
Bei Eficode (ehemals Praqma) unterstützen wir täglich Dutzende Unternehmen auf ihrem Weg zu Continuous Delivery. Aus dieser Erfahrung heraus haben wir eine Liste von Kriterien zusammengestellt, die jedes CI/CD-System erfüllen sollte.
Euer Unternehmen hat möglicherweise besondere Anforderungen, und manche Kriterien sind für euch wichtiger als andere. Deshalb müsst ihr entscheiden, welches Tool für euch am besten geeignet ist.
Wir haben unsere Anforderungen in zwei Kategorien unterteilt: Entwicklung und Betrieb. Dieser Beitrag konzentriert sich auf die Entwicklungsseite des CI-Servers.
Anforderungen mit Fokus auf die Entwicklung
Ist Open Source.
So können wir eigene Plugins hinzufügen, Bugs beheben und die Vision hinter der Technologie verstehen.Unterstützt Pipelines as Code.
Infrastructure as Code!Muss alle wichtigen Betriebssysteme unterstützen.
Windows, OSX und Linux (mindestens Debian- und Red-Hat-basierte Distributionen)Ermöglicht die Einrichtung einer Pipeline für alle wichtigen Bereiche der Softwareentwicklung.
Embedded, Web, Desktop und MobileKann sowohl lokal als auch serverseitig genutzt werden.
Kann definierte Schritte parallel ausführen.
Der gleiche Source Code sollte parallel auf zehn Betriebssystemen gebaut werden können.Kann Artefakte von einem Build-Schritt zum nächsten speichern.
Kann einen Pipeline-Schritt erneut auslösen, um Probleme zu analysieren.
So können wir eigene Plugins hinzufügen, Bugs beheben und die Vision hinter der Technologie verstehen.
Windows, OSX und Linux (mindestens Debian- und Red-Hat-basierte Distributionen)
Embedded, Web, Desktop und Mobile
Der gleiche Source Code sollte parallel auf zehn Betriebssystemen gebaut werden können.
Concourse und Jenkins Pipelines im Detail
Unser Fokus bei Jenkins liegt ausschließlich auf den „neuen“ Pipeline-Jobs, da Jenkins seine Entwicklung derzeit darauf konzentriert. Wenn wir also von „Jenkins“ sprechen, meinen wir „Jenkins Pipeline“.
Historie
Concourse
Die Entwicklungsteams des CloudFoundry-Projekts hatten mit ihren Pipelines in Jenkins mehrere Probleme. Sie versuchten, diese zum Laufen zu bringen, hatten jedoch Schwierigkeiten mit den verschiedenen Plattformen und Services, die CloudFoundry benötigt, und versanken in der Plugin-Architektur von Jenkins. Daraus entstand Concourse CI, unter der Leitung von Alex Suraci und seinem Team. Die Vision für Concourse war eine modernere, weniger Plugin-getriebene Build-Engine, die neuere Technologien wie Container nutzen und Pipelines zu zentralen Bestandteilen machen sollte. Da CloudFoundry Open Source ist, ist es natürlich auch Concourse CI.
Jenkins
Die Geschichte von Jenkins ist lang und zeigt, dass die Community dahinter lebendig und stark ist. Sie kann sich auch gegen einen großen Konzern stellen, wenn sie sich für eine eigene Vision entscheidet.
Jenkins ist ausgereift und verfügt über eine ausreichend große Nutzerbasis, um praktisch alles zu bewältigen, was ihr von einer Plattform braucht.
Allerdings wird Jenkins weitgehend von Cloudbees kontrolliert. Seit 2014 ist der Erfinder Kohsuke Kawaguchi CTO von Cloudbees. Das bedeutet: Was Cloudbees macht, macht auch Kohsuke – und damit letztlich auch die Community. Cloudbees entwickelt Jenkins aktiv weiter, damit es die neuen Standards für CI-Server erfüllt.
Terminologie und Architektur
Concourse
Concourse ist im Kern eine Container-Technologie, bietet aber auch Optionen, um Anwendungen einfach auf virtuellen Maschinen auszuführen.
Ein Concourse-Build-System besteht aus einem Master, einer PostgreSQL-Datenbank zur dauerhaften Speicherung und einer beliebigen Anzahl von Workern.
Der Concourse-Master ist למעשה ATC (kurz für Air Traffic Control), das Herzstück des Systems. Für hohe Verfügbarkeit können mehrere ATCs betrieben werden, sofern sie dieselbe PostgreSQL-Datenbank verwenden. ATC orchestriert die Pipeline und verteilt die Last in Concourse.
Die Worker werden anschließend über TSA registriert (kurz für Transportation Security Administration, ein Wortspiel mit der Flugverkehrskontrolle). Dabei handelt es sich למעשה um das SSH-Protokoll, über das die Worker sich durch einen Reverse Tunnel vom TSA aus mit dem Master verbinden. Der Master kann dann den Datenverkehr steuern.
Da Concourse Entwicklern nicht erlaubt, eine Pipeline über den Server einzurichten, benötigt ihr eine Binärdatei namens fly, um eine neue Pipeline einzurichten.
Eine Pipeline in Concourse besteht aus drei Elementen: Resources, Resource Types und Jobs. Eine Resource ist in Concourse ein Objekt, das ihr in die Pipeline laden oder aus ihr heraus übertragen müsst. Gute Beispiele sind Git, Artifact Storage und E-Mails, aber auch abstrakte Dinge wie Zeit, andere Pipelines und Serverkonfigurationen.
Mit Resource Types lassen sich externe Resources definieren, die nicht vom Concourse-Team erstellt wurden. Sie werden anschließend wie eine native Resource eingebunden und genauso behandelt. Jobs sind schließlich die Ausführungselemente, die Code bauen oder Automatisierung für eine Resource ausführen.
Jenkins
Die Terminologie von Jenkins lässt sich wie folgt beschreiben:
Der Jenkins-Master ist ein fortschrittlicher Scheduler, der auf Grundlage von Job-Definitionen Builds auf Nodes überwacht und ausführt. Ein Job kann auf verschiedene Arten definiert werden: als Standard-Job über JobDSL oder über die neue Jenkins Pipeline.
Architektonisch gesehen ist Jenkins „nur“ eine Java-basierte WAR-Datei für den Server und eine JAR-Datei für die Nodes, die die Jobs bauen. Jenkins speichert sämtliche Konfigurationen und Logs in Dateien auf dem Datenträger des Masters. Der Jenkins-Kern besteht lediglich aus dem Scheduler, der Kommunikation mit den Nodes und der Ausführung. Alles andere übernehmen Plugins – und davon gibt es viele!
Derzeit gibt es 1431 Plugins für Jenkins. Das ist zugleich eine Stärke und eine Schwäche. Die Stärke liegt darin, dass Jenkins zum Schweizer Taschenmesser der CI geworden ist.
Wenn Jenkins etwas für euch erledigen soll, hat wahrscheinlich schon jemand das passende Plugin entwickelt! Die Vielzahl an Plugins hat jedoch ihren Preis: Nicht alle sind produktionsreif oder funktionieren mit dem neuen Pipeline-Konzept.
Da Jenkins schon lange vor Containern existierte, liegt der Fokus darauf, den Build auf einem Node statt in einem Container auszuführen. Jenkins unterstützt Container jedoch gut – direkt über die Shell oder über ein Plugin. Dadurch ist es einfach, damit zu arbeiten und Logs abzurufen.
Hello World
Concourse
Concourse-YAML wird normalerweise auf mehrere Dateien aufgeteilt, für eine bessere Lesbarkeit wurde hier jedoch alles inline eingefügt:
jobs:- name: hello-world public: true plan: - task: hello-world config: platform: linux image_resource: type: docker-image source: {repository: ubuntu} run: path: sh args: - -exc - | echo "hello world"Dadurch wird einfach ein einzelner Job ohne Resources und ohne Eingaben für den Job erstellt, der „Hello World“ ausgibt. Ihr könnt ihn über die Weboberfläche oder über das Command Line Interface namens fly auslösen: fly -t yourconcourses execute --config tests.yml
Dies kann für jede Task in der Pipeline verwendet werden und gibt dem Entwickler vollen Zugriff auf einen Job mit seinen lokalen Binärdateien und Dateien – auch weiter downstream in späteren Jobs.
Jenkins
Jenkins bietet zwei Varianten der Pipeline-DSL: deklarative und skriptbasierte Pipelines.
Für beide müsst ihr einen Pipeline-Job einrichten – entweder in der Jenkins-UI oder über die API. Jenkins unterstützt die Pipeline sowohl im VCS als auch direkt in der Job-Definition in Jenkins. Danach ist eure DSL-Datei sehr schlank, sodass „Hello World“ fast nur eine Zeile benötigt.
#Declarative pipeline pipeline { agent any stages { stage('Build') { steps { echo 'hello world' } } } } #Scripted pipeline node { stage('Build') { echo 'hello world' } }Fazit
Gewinner: Beide
Der Einstieg ist mit beiden Systemen unkompliziert. Könnten wir sie nicht zum Laufen bringen, würden wir sie nicht bewerten.
Betriebssysteme
Concourse
Einen Concourse-Worker oder -Master erstellt ihr, indem ihr die Concourse-Binärdatei herunterladet und ihr entweder das Argument „worker“ oder „master“ übergebt. Der Ablauf ist derselbe, unabhängig davon, ob ihr Windows, Linux oder OS X verwendet. Das ist möglich, weil Concourse in GoLang geschrieben ist.
Allerdings laufen alle nativen Ressourcen sowie die meisten Community-Ressourcen in Containern, sodass zusätzliche Aspekte zu berücksichtigen sind. Das bedeutet, dass ein typisches Windows-Setup weiterhin mindestens einen Linux-Worker benötigt, um auf Ressourcen zuzugreifen. Außerdem arbeitet Windows zwar daran, Container nutzbar zu machen, Concourse hat dies jedoch erst als künftiges Feature vorgesehen. Daher führt ein Windows-Worker seine Prozesse immer in einer virtuellen Maschine aus und trennt sie anhand der Ordnerstruktur statt wie ein Container.
Ein Beispiel für einen unter Windows laufenden Pipeline-Job findet ihr in unserem Git-Phlow-Windows-Job – unter OSX funktioniert es ähnlich (oft ist es jedoch einfacher, Linux-Agenten zu verwenden).
Ein gutes Beispiel findet ihr hier (achtet auf „platform: darwin“). Es stammt aus dem Blog von David Karlsson über die Einrichtung von Concourse für die Mobile- und OSX-Entwicklung.
Jenkins
Jenkins-Knoten laufen entweder auf Bare-Metal-Servern oder VMs. Solange eine JVM für das jeweilige Betriebssystem verfügbar ist, läuft Jenkins. Wie ihr eure Knotenumgebung beschreibt, bleibt euch überlassen: mit einem Drittanbieter-Tool wie Ansible, Chef oder Puppet oder indem ihr einen Server manuell installiert (Wenn ihr das tut, solltet ihr auch diesen Teil automatisieren!). Knoten fügt ihr eurer Farm entweder über die UI des Masters hinzu oder mit dem Jenkins-Swarm-Plugin, durch das die Knoten ihren Master kontaktieren.
Fazit
Gewinner: Unentschieden
Container bieten zwar klare Vorteile, bringen aber auch eine gewisse Komplexität mit sich. Die JVM ist einfach einzurichten und läuft überall, ist jedoch eingeschränkter.
Pipelines: von Embedded bis Mobile
Concourse
Concourse eignet sich hervorragend für die Web-, Mobile- und Desktop-Entwicklung. Im Embedded-Bereich laufen dagegen viele FPGA-Tools unter Windows. Das ist für Concourse zwar kein Hindernis, ihr profitiert jedoch nicht von den Vorteilen dieser Technologie.
Die Linux-Tools, etwa NT FPGA, sind stark GUI-lastig, was zu ähnlichen Problemen führt. Concourse ist am stärksten, wenn es die Vorteile von Containern nutzen kann, und Prozesse, deren erneute Ausführung aufwendig ist, sollten keine Container verwenden. Dauert ein Build eine halbe Stunde, hilft es wenig, dass Container nach einem Fehler kostengünstig erneut ausgeführt werden können.
Bei vielen Embedded-Anwendungen müssen Ressourcen gesperrt werden, um den Build-Ablauf sicherzustellen. Concourse ist jedoch normalerweise für rein atomare Prozesse ausgelegt.
Jenkins
Wie oben beschrieben
Wenn es eine JVM gibt, läuft Jenkins.
In dieser Hinsicht führt es also jede Pipeline gleich gut aus – unabhängig davon, ob es sich um Mobile Apps (Beispiel hier und hier), einen Haskell-Compiler oder eingebettelte FPGA-Entwicklung handelt.
Bei der Arbeit mit einer knappen Ressource bietet das Lockable Resources Plugin eine sehr effektive Möglichkeit, die Ressource über mehrere Nodes hinweg zuzuweisen.
Der Code zum Sperren einer Ressource ist sehr einfach und unkompliziert:
echo 'Starting' lock('my-resource-name') { echo 'Do something here that requires unique access to the resource' // any other build will wait until the one locking the resource leaves this block } echo 'Finish'Ihr könnt sogar die Reihenfolge ändern, in der Ressourcen zugewiesen werden, und so die FIFO-Warteschlange umkehren:
lock(resource: 'staging-server', inversePrecedence: true) { node { servers.deploy 'staging' } input message: "Does ${Url}staging/ look good?" }Fazit
Gewinner: Jenkins
Jenkins gewinnt hier. Es läuft überall, und Ressourcen lassen sich einfach sperren – für viele ein Muss.
Von Entwicklern initiierte Arbeit
Concourse
Concourse ermöglicht Entwicklern, Jobs auf dem Server über ihr eigenes Terminal auszuführen, indem sie Folgendes ausführen:
fly -t myserver execute --config myjob.ymlAnschließend werden die bereitgestellten lokalen Eingaben verwendet und der Job ausgeführt. Das ist besonders hilfreich, weil Entwickler ihren Code debuggen können, ohne die Pipeline durchlaufen zu müssen.
Dafür wird lediglich ein Concourse-Server benötigt, den Entwickler ansprechen können und der den Job in einer isolierten Umgebung auf einem passenden Slave gemäß den Vorgaben ausführt.
Jenkins
In diesem Bereich ist Jenkins gar nicht vertreten. Um etwas auf den Build-Servern auszuführen, benötigen wir einen serverdefinierten Job. Punkt.
Um etwas zu testen, müsst ihr also den Git-Roundtrip durchlaufen:
Git push --> Jenkins pull --> Jenkins build --> Jenkins response --> Repeat
Mit einer Multibranch-Pipeline könnt ihr die Pipeline jedoch an eure Anforderungen anpassen und sie in einen Nicht-Master-Branch pushen, um die Ausführung zu sehen.
In einem Enterprise-Setup könnte man vielleicht argumentieren, dass „ihr keine Ressourcen für etwas verwenden solltet, das keine ordentliche Pipeline hat“.
Ich muss zugeben, dass ich gerne einen Job zur Ausführung an den Server übermitteln können würde. Das wäre eine hervorragende Funktion.
Fazit
Gewinner: Concourse
Concourse zeigt den Weg, wenn es um die von Entwicklern initiierte Ausführung von Pipelines geht. Jenkins, hier müsst ihr also nachlegen!
Eure Pipeline parallelisieren
Concourse
Sowohl einzelne Jobs (aggregate), Ressourcen als auch ganze Pipelines können parallel ausgeführt werden. Wenn sich eine Ressource ändert, kann sie alle davon abhängigen Jobs auslösen. Ändert sich die Ressource kurz darauf erneut, wird der Job parallel mit den neuen Eingaben ausgeführt.
Auch unser git phlow enthält gute Beispiele dafür aus Produktionscode.
- name: afterburner plan: - aggregate: - get: praqma-tap - get: git-phlow #contains the formula update script - get: gp-version passed: [takeoff] - get: phlow-artifact-darwin-s3 passed: [takeoff] trigger: true - task: brew-release file: git-phlow/ci/brew/brew.yml on_failure: put: slack-alert params: text: | brew release failed https://concourse.bosh.praqma.cloud/teams/$BUILD_TEAM_NAME/pipelines/$BUILD_PIPELINE_NAME/jobs/$BUILD_JOB_NAME/builds/$BUILD_NAME - put: praqma-tap params: repository: updated-praqma-tapJenkins
Jenkins unterstützt dies nativ in beiden DSL-Varianten. Es erstellt einen schlanken Job auf dem Master, um die Builds zu koordinieren. Das bedeutet, dass alle parallelen Ausführungen in einer Stage abgeschlossen sein müssen, bevor die nächste Stage ausgeführt wird.
Im Folgenden findet ihr Beispiele dafür, wie eine Liste paralleler Ausführungen sowohl in Scripted als auch in Declarative aussehen kann.
Jenkinsfile (Scripted Pipeline) stage('Test') { parallel linux: { node('linux') { try { sh 'run-tests.sh' } finally { junit '**/target/*.xml' } } }, windows: { node('windows') { try { sh 'run-tests.bat' } finally { junit '**/target/*.xml' } } } }Jenkinsfile (Declarative Pipeline) pipeline { agent none stages { stage('Test') { parallel { stage('Windows') { agent { label "windows" } steps { bat "run-tests.bat" } post { always { junit "**/TEST-*.xml" } } } stage('Linux') { agent { label "linux" } steps { sh "run-tests.sh" } post { always { junit "**/TEST-*.xml" } } } } } } }Fazit
Gewinner: Concourse
Concourse gewinnt diese Kategorie, wenn auch nur knapp.
Wenn ihr Tasks in Jenkins parallel ausführt, müssen alle abgeschlossen sein, bevor ihr erneut verzweigen könnt. Concourse definiert dies deutlich flexibler und kann daher von beliebigen Bedingungen abhängen.
Artifact-Handling
Concourse
Der Workflow in Concourse trennt Resources und Jobs sehr strikt. Ein Job kann aus mehreren Tasks bestehen, die Artifacts gemeinsam nutzen können. Ein Job kann jedoch nur dann ein Artifact mit einem anderen Job teilen, wenn die Resource zwischen den Jobs gepusht und gespeichert wird. Concourse selbst speichert niemals Artifacts und erzwingt dieses Pull/Push-Prinzip, um atomare Jobs sicherzustellen.
Jenkins
Jenkins kann Artifacts mit dem archive-Keyword auf dem Master speichern. Generell empfehlen wir für diesen Zweck jedoch ein dediziertes Artifact-Management-System wie Artifactory.
Um Dateien innerhalb desselben Pipeline-Skripts von einem Node auf einen anderen zu übertragen, beispielsweise wenn ihr eure Webanwendung in eure Stresstest-Umgebung übertragen müsst, könnt ihr die Funktion stash/unstash verwenden. Sie stellt die Artifacts dem jeweiligen Node bei Bedarf bereit.
Fazit
Gewinner: Jenkins
Beide bieten Funktionen von Drittanbietern, aber Jenkins unterstützt zusätzlich das Speichern von Artifacts auf dem Master.
Die eigentliche Frage ist: Wollt ihr wirklich, dass euer CI/CD-System gleichzeitig auch euer Artifact-Management-System ist?
Pipelines erneut ausführen
Concourse
Das ist ganz einfach: Geht entweder zum Web-Client und klickt in einem Job auf „+“, oder führt ihn erneut über die fly-Kommandozeile aus. Anschließend wird versucht, denselben Prozess wie zuvor mit denselben Inputs auszuführen – oder mit neuen, falls sie aktualisiert wurden.
Jenkins
Ihr könnt eine komplette Pipeline jederzeit mit denselben Parametern erneut ausführen.
Eine Stage innerhalb einer Pipeline auszulösen, ist (zu meiner großen Frustration) nur in Jenkins Enterprise möglich. Cloudbees arbeitet jedoch an einer Funktion für declarative Pipelines, nicht aber für die fortgeschritteneren Scripted Pipelines. Für mich ist das ein sehr enttäuschender Schritt.
Fazit
Gewinner: Concourse
Da Concourse jeden Job in einer Pipeline als atomare Aktion behandelt, ist das erneute Auslösen naheliegend. Jenkins muss diese Funktion (erneut) implementieren, um mit Concourse gleichzuziehen.
Fazit
Und der Gewinner ist ….
Um ehrlich zu sein: Ein solches Fazit gibt es nicht, denn es hängt ganz davon ab, worauf ihr bei eurem Setup am meisten Wert legt. Jenkins ist bei unseren Kunden der De-facto-Standard, aber Concourse hat Vorzüge, die es zu einem ernstzunehmenden Wettbewerber machen.
- CI/CD
Subscribe to our newsletter
Related blogs