Über unsMediaKontaktImpressum
Bernhard Cygan 15. Dezember 2015

Von Continuous Integration zu Continuous Delivery mit Jenkins Workflow

Die großen Internethändler und Social Media-Giganten reden heute alle von Continuous Delivery. Für alle anderen findet eine stille Revolution statt, da Unternehmen aller Art vom Startup bis zum Autohersteller und zum mittelständischen Betrieb nach Möglichkeiten suchen,  um ihre Software schneller als die Konkurrenz zu optimieren. Jeder sagt, dass man es tun soll, aber wie fängt man es am besten an?

Ich selbst habe von der Pike auf und auf die harte Tour gelernt, wie man PaaS- und SaaS-Frameworks baut, und vielleicht gibt es keinen wirklich bequemen Weg. Trotzdem kann ich ein paar Tipps geben, wie Entwicklungsteams von Continuous Integration (CI) zu Continuous Delivery (CD) gelangen können, indem sie die leistungsfähige Workflow-Funktionalität in Jenkins [1] nutzen.

Continuous Integration (CI) ist das Verfahren, mit dem die Arbeitskopien aller Entwickler mehrmals täglich mit einer gemeinsamen Mainline-Version gemerged werden. Dies gibt den Entwicklern ein schnelleres Feedback für den Fall, dass ihr Code einen Fehler verursacht. Der Ablauf wird in Abb.1 veranschaulicht.

Dieser typische CI-Prozess liefert den Entwicklern ein Feedback, wenn ihr Code bei einem der Integrationstests durchfällt. Was jedoch CD grundlegend von CI unterscheidet, ist, dass CD nicht nur den Code validiert, sondern auch die Applikation für den Einsatz unter den realen Einsatzbedingungen vorbereitet und dabei sogar berücksichtigt, wie sich das Benutzerverhalten auf die Applikation "im echten Einsatz" auswirken kann.

Vor der Behandlung weiterer Einzelheiten sollte an dieser Stelle mit einem häufigen Missverständnis aufgeräumt werden. Es ist zu beachten, dass bei Continuous Delivery die Entscheidung, ob und wann eine Software in der Produktivumgebung eingesetzt wird, nicht automatisch getroffen wird. Auf Wunsch kann jedoch manuell ein vollständig automatischer Prozess angestoßen werden.

Dies ist der Wirkungsbereich von "Continuous Deployment", eines DevOps-Verfahrens, bei dem Artefakte automatisch für verschiedene Stadien einer Build-Pipeline freigegeben werden. Wenn alle Stadien zufriedenstellend durchlaufen werden, wird die Software letztendlich in die Produktivumgebung entlassen. Wenn ein automatisierter Test in einer "Quasi-Produktivumgebung" positiv verläuft, führen die automatisierten Freigaben für die generierten Artefakte letztendlich dazu, dass die Software ohne menschliches Zutun in den Produktiveinsatz gelangt. Dies ist wie Continuous Delivery, aber vollständig automatisiert.

Dies kann für manche Teams sinnvoll sein und für andere nicht. In jedem Fall muss man eine klare Unterscheidung treffen, wenn man intern die Notwendigkeit von Continuous Delivery befürwortet. Technisch gesehen stellt die Einführung von Continuous Deployment keinerlei Risiko dar; auf organisatorischer Ebene kann sich das Projekt jedoch als etwas kniffelig erweisen, da es einen gefühlten Kontrollverlust mit sich bringt – was sich im einen oder anderen Fall als Hemmschwelle erweisen kann.

Welche Vorteile bringt CD gegenüber CI?

CI stellt einen Rahmen für Entwicklerteams zur Verfügung, um eine sinnvolle Aufgabenteilung zu erreichen. Hierbei kommt es jedoch weiterhin zum großen "Showdown" bei der Softwarefreigabe, da der Code die nötigen Tests und Qualitätskontrollen und letztendlich den betrieblichen Einsatz durchlaufen muss. Dies bedeutet, dass die vom einzelnen Entwickler realisierten Features einige Zeit lang noch nicht umgesetzt werden und in dieser Zeit nicht für die Benutzer verfügbar sind. Ebenso erhält der Entwickler wegen der langen Zykluszeiten kein zeitnahes Feedback, ob die realisierten Features tatsächlich wie gewünscht funktionieren.

Durch die Erweiterung von CI zu CD können neue Features und Verbesserungen schneller auf den Markt gebracht werden. Dies wird dadurch erreicht, dass man wesentlich kleinere inkrementelle Änderungen an der Software vornimmt und diese inkrementellen Änderungen mit messbarem Feedback verfolgt. Dies macht es auch einfacher, Änderungen durchzuführen oder vorherige Zustände wieder herzustellen, wenn die Dinge nicht nach Plan verlaufen.

Alle Tests werden jedes Mal wiederholt, bevor die Software in das nächste Stadium übernommen wird, was insgesamt für eine höhere Qualität sorgt. Durch das Einfügen von "Quality Gates" in wichtigen Stadien der Pipeline kann man sicherstellen, dass auch nichtfunktionale (z. B. wirtschaftliche) Anforderungen erfüllt werden. CD wendet die gleichen Softwareprinzipien wie CI an und ermöglicht die Nachvollziehbarkeit der Artefakte (wer hat was wann gemacht?) vom Quellcode bis zur Lieferung der Software-Applikationen.

Mitarbeiter spielen eine ebenso wichtige Rolle wie Tools, aber die Implementierung einer Software-Delivery-Pipeline an sich erzeugt eine gemeinsame Sicht und eine gemeinschaftliche Antriebskraft für den gesamten Prozess und alle daran beteiligten Teams.

Ausprobieren!

Ende der theoretischen Belehrung – wie wird es gemacht? Beginnen wir mit einer typischen CI-Pipeline für Java mit Maven: Eine typische Pipeline sieht etwa wie folgt aus:

  • maven compile - Quellcode kompilieren
  • maven test - Unit-Tests durchführen
  • maven package - kompilierten Code in eine a jar-Datei packen
  • maven install - Artefakte zum lokalen Repository hinzufügen
  • maven deploy - Artefakte zu einem Remote-Repository hinzufügen

    Hinweis: Die oben genannten Schritte werden wahrscheinlich alle in einem Build-Job durchgeführt!
  • WAR-Datei in Testsystem hochladen
  • Tests durchführen
  • Tests bestanden – FERTIG!

Ist damit wirklich alles erledigt?

An dieser Stelle haben wir eine Applikation, die validiert und getestet werden kann.

Im Prinzip führte man mvn deploy durch und lädt zu Testzwecken eine war-Datei in einem Applikationscontainer hoch. Dies wird normalerweise durch zwei Jenkins-Jobs erledigt (s. Abb.3).

Bevor eine Software jedoch freigegeben werden kann, muss man überlegen, was zu ihrem Einsatz erforderlich ist, in welcher Umgebung sie eingesetzt werden soll und wie sie verwendet werden soll.

  • Was ist mit Veränderungen der Runtime-Umgebung? (Linux / Windows, Tomcat / Jetty / WebSphere, MySQL / DB2 / Microsoft SQL Server)
  • Was ist mit Belastungstests? "Diese werden vor jeder Freigabe manuell durchgeführt."
  • Was ist mit Performance-Regressionen? "Vor jeder Freigabe werden Klick-Tests durchgeführt."
  • Was ist mit der Skalierbarkeit? "Die Server haben das letzte Release gut verkraftet."

Der wichtigste Aspekt bei der Festlegung einer CD-Pipeline ist, eine zunehmende Sicherheit von der Entwicklung bis zur Produktion zu gewährleisten. Hierzu sind ein detailliertes Verständnis der Abläufe sowie neue Strategien und Technologien zur Behandlung dieser Komplexitäten erforderlich.

Zielt die Software-Einführung auf eine saubere Installation oder ein System-Upgrade ab und welche Patches werden installiert? Das Mantra bei CD lautet "alles automatisieren". Daher müssen Tests eingebaut werden und man muss außerdem bestimmte Strategien in Betracht ziehen, z. B. Containerisierung mit Docker zur Behandlung von Veränderungen in der Zielumgebung.

Datenbanken können eine zusätzliche Komplexität mit sich bringen. Wie kann man Probleme vermeiden, wenn die Software-Einführung nicht nur eine, sondern gleich mehrere Datenbanken betrifft?

Performance-Tests sind eine Achillesferse bei der Einführung von Applikationen im Web-Maßstab. Zum Beispiel: Ist die Performance des virtualisierten Servers messbar und liefert die  Multitenant-Hardware die gewünschte Performance? Bei Adhoc-Tests besteht die Gefahr, dass die Tests vergessen werden. Wenn man diese Tests jede Nacht durchführt, kann man schnell feststellen, ob die Software die Applikation beschleunigt oder verlangsamt. Dies ist von der Infrastruktur sowie vom Applikationscode abhängig und erfordert das Festlegen von Vorgaben: Was ist akzeptabel und was nicht?

Es müssen Testfälle erstellt werden, die für die Applikation relevant sind. An dieser Stelle soll auf automatisierte Tests nicht näher eingegangen werden, aber die Beachtung aller oben genannten Punkte erhöht die Sicherheit, dass die Software für die Betriebsumgebung geeignet ist.

Was kann man mit CD verbessern und erreichen?

Die gute Nachricht ist, dass die Erstellung einer CD-Pipeline mit Jenkins Domain-Specific Language (DSL) nicht schwierig ist. Der CD-Prozess lässt sich mit wenigen Programmzeilen automatisieren.

Als erster Schritt kann man die beiden CI-Jobs in einen Workflow umwandeln:

Listing 1:

def String jenkinsTestHost = "localhost:8080/"
def String jenkinsProductionHost = "localhost:8081/"
def String pluginSource = "https://github.com/jenkinsci/subversion-plugin"
def String pluginFile = "subversion.hpi"
def String stashName = "plugin"
 
stage "Build"
node { 
  git url:pluginSource 
 
  def mvnHome = tool 'M3'
  sh "${mvnHome}/bin/mvn install"
 
  stash name: stashName, includes: "target/${pluginFile}"
  } 
checkpoint "plugin binary is built"
 
stage "Integration Tests and Quality Metrics"
parallel
  "Integration Tests": { 
    node { 
      echo "++++++++++ Integration Tests ++++++++++"
      def mvnHome = tool 'M3'
      sh "${mvnHome}/bin/mvn integration-test"
      }
    }, 
  "Quality Metrics": { 
    node { 
      echo "++++++++++ Quality Metrics ++++++++++"
      def mvnHome = tool 'M3'
      sh "${mvnHome}/bin/mvn sonar:sonar"
      } 
    }

Wenn diese Tests positiv verlaufen, können weitere Belastungstests als Quality Gate verwendet werden:

Listing 2:

// check that the clients still can work with the host
// we might want to limit the concurrency here because of resource limits
stage name: "Load Tests", concurrency: 2
  parallel 
"load test #1" : {
          node {
                executeLoadTest(jenkinsTestHost)
          }
      },
      "load test #2": {
          node {
                executeLoadTest(jenkinsTestHost)
          }
      },
      "load test #3": {
          node {
                executeLoadTest(jenkinsTestHost)
          }
      }
checkpoint "all tests are done"   

In dem Workflow-Job wurden die folgenden Stadien kombiniert und automatisiert

  • Kompilation
  • Integrationstests
  • Belastungstests als Quality Gateway mit einer typischen Umgebung
  • Belastungstests als Quality Gateway mit allen geforderten Umgebungen
  • Wenn alle Quality Gates positiv verlaufen sind, Freigabe aller Änderungen in die    Produktion

Listing 3:

stage "Deploy to Production"
node {
    input "All tests are ok. Shall we continue to deploy into production (This will initiate a Jenkins restart) ?"
    uploadPluginAndRestartJenkins ( jenkinsProductionHost, "plugin" )
}

Wie sieht der nächste Schritt aus? Man kann z. B. eine Kopie der CI-Pipeline testen. Eine Abstimmung mit der Operations-Abteilung ist sinnvoll – je besser die Zusammenarbeit mit den Ops-Mitarbeitern ist, desto früher kann man sich um die möglichen Fehler kümmern und auf dieser Grundlage eine funktionsfähige CD-Pipeline aufbauen.

Handhabung der Komplexität

So weit, so gut. Bisher wurde gezeigt, wie ein Workflow die Komplexität verringern kann oder wie man eine einzelne CD-Pipeline orchestrieren kann. Bei Verwendung mehrerer Pipelines lassen sich weitere Effizienzsteigerungen erzielen, indem man eine für zuverlässig erachtete Pipeline in eine Vorlage (Template) verwandelt, die dann auf andere Pipelines mit ähnlichen Anforderungen angewandt werden kann.

Zunächst kann man alle Ähnlichkeiten paralleler Jobs erfassen und die Variablen als Attribute berücksichtigen. Dies wird nachfolgend gezeigt, wobei die Unterschiede zwischen einer Template-Definition und dem ursprünglichen Workflow-Job im ersten Kommentar Zeile 3-6 dargestellt werden.

Listing 4:

def String jenkinsTestHost = "localhost:8080/"
def String jenkinsProductionHost = "localhost:8081/"
// pluginSource is a parameter now
// def String pluginSource = "https://github.com/jenkinsci/subversion-plugin" 
// pluginFile is a parameter now as well
// def String pluginFile = "subversion.hpi" 
def String stashName = "plugin"
 
stage "Build"
node { 
git url:pluginSource 
 
def mvnHome = tool 'M3'
sh "${mvnHome}/bin/mvn install"
 
stash name: stashName, includes: "target/${pluginFile}"
} 
checkpoint "plugin binary is built"
 
stage "Integration Tests and Quality Metrics"
parallel
"Integration Tests": { 
node { 
echo "++++++++++ Integration Tests ++++++++++"
def mvnHome = tool 'M3'
sh "${mvnHome}/bin/mvn integration-test"
}
}, 
"Quality Metrics": { 
node { 
echo "++++++++++ Quality Metrics ++++++++++"
def mvnHome = tool 'M3'
sh "${mvnHome}/bin/mvn sonar:sonar"
} 
}
 
// check that the clients still can work with the host
// we might want to limit the concurrency here because of resource limits
stage name: "Load Tests", concurrency: 2
  parallel 
"load test #1" : {
          node {
                executeLoadTest(jenkinsTestHost)
          }
      },
      "load test #2": {
          node {
                executeLoadTest(jenkinsTestHost)
          }
      },
      "load test #3": {
          node {
                executeLoadTest(jenkinsTestHost)
          }
      }
checkpoint "all tests are done"   
 
stage "Deploy to Production"
node {
    input "All tests are ok. Shall we continue to deploy into production (This will initiate a Jenkins restart) ?"
    uploadPluginAndRestartJenkins ( jenkinsProductionHost, "plugin" )
}

Man kann dies auch einen Schritt weiter führen und ein Template als Vorlage für die Funktion der Pipeline erstellen. Dies mag vielleicht nicht sehr eindrucksvoll aussehen (s. Abb.5), aber genau dies ist der Punkt: Ebenso wie eine Masterfolie für PowerPoint oder ein Stylesheet für eine Website abstrahiert und enthält dieses Template alle nötigen Elemente zum Aufbau einer funktionierenden Pipeline. Natürlich kennt man diese Vorgehensweise bereits seit vielen Jahren aus der Arbeit mit Build-Jobs.

Der bereits definierte Workflow hat die in Abb.5 gezeigte Form. Im nächsten Schritt wird hieraus ein Template zur Verwendung in Jenkins erzeugt.

Ein pluginSource-Attribut wird wie folgt definiert (s. Abb.6):

Die Variable in Abb.6 erzeugt den Dialog in Abb.7 – zu Demonstrationszwecken soll hier das Jenkins Subversion-Plugin erstellt werden.

In der praktischen Umsetzung sieht das Jenkins Stage View wie ein normaler Job aus, wobei jedoch anstelle der Job-Definition der Link zum Template "Build and deploy Jenkins plugin" erscheint (s. Abb.8).

Templates haben den großen Vorteil, dass sie eine vollständige Vererbung unterstützen. Dies bedeutet, dass – wie in der Analogie zum PowerPoint-Master – jede Änderung am Template in jedem abhängigen Build-Job im nächsten Durchgang berücksichtigt wird.

Dies erlaubt eine drastische Reduzierung der Zahl und Komplexität der Build-Job-Definitionen, die gepflegt werden müssen, wodurch wiederum mehr Zeit für produktivere (und werthaltigere) Tätigkeiten verfügbar ist.

Fazit

Hiermit wurden die wesentlichen Schritte des Übergangs zu CD mit Jenkins Workflow beschrieben. Jeder Entwickler ist aufgerufen, selbst zu testen, wie er damit die ersten Schritte auf dem Weg zu einer schnelleren Software-Realisierung gehen kann – sei es mit einem einzelnen kommerziellen Projekt oder durch Experimentieren mit einer TYPO3-Installation in der Cloud außerhalb der regulären Betriebszeiten.

Natürlich braucht man einen guten Prozess und gute Mitarbeiter, um erfolgreich zu sein. Bei der Einführung von CD müssen alle Beteiligten ihre Arbeitsweise ändern, so dass man auch die Bedürfnisse aller betroffenen Personen berücksichtigen muss. Ein Operations-Team arbeitet wahrscheinlich mit anderen Tools und hat andere Prioritäten als ein QA-Team, aber beide können extrem wertvolle Erfahrungen einbringen. Der Übergang von CI zu CD ist ein beratungsintensiver Prozess und erfordert einen Kulturwandel, aber auch hier kann man mit einem kleinen Schritt anfangen – wie z. B. mit dem firmeneigenen Linux-Guru Kaffee zu trinken.

Quellen
  1. Jenkins

Autor

Bernhard Cygan

Bernhard Cygan ist Senior Solution Architect bei CloudBees. Ausgehend von Tätigkeiten in Dev und Ops lernte er Agile Methoden und Prozesse schätzen.
>> Weiterlesen
Das könnte Sie auch interessieren
Kommentare (0)

Neuen Kommentar schreiben