Quantcast
Channel: Agilität – codecentric AG Blog
Viewing all 129 articles
Browse latest View live

RED “Runtime Environment for Developers”– Ein erster Blick unter die Haube

0
0

Im ersten Artikel dieser Blogserie hat mein Kollege Lukas Pustina bereits eine Einführung in RED (Runtime Environment for Developers), unserer Docker basierten Entwicklungsumgebung, gegeben. In diesem Artikel werde ich auf die ersten technischen Details von RED eingehen. Das Projekt ist auf GitHub verfügbar und wird mit jedem nachfolgenden Artikel dieser Serie erweitert.

Im Einführungsartikel wurde bereits erwähnt, dass die Grundlagen VirtualBox, Vagrant und Docker sind. Ziel dieses Artikels ist die Basiskonfiguration dieser 3 Komponenten, um die Folgenden Grundanforderungen erfüllen zu können:

  • Docker soll automatisiert installiert werden können
  • Docker Container sollen vom Host (PC / Laptop) aus erreichbar sein
  • Docker Container sollen statische IP Adressen zugewiesen bekommen können
  • Die statischen IP Adressen sollen nicht mit den automatischen von Docker zugewiesenen IP Adressen kollidieren

Vagrant

Mit Hilfe von Vagrant können wir in einer Datei – der Vagrantfile – beschreiben wie unsere virtuelle Maschine konfiguriert werden soll. Wir gehen hier nun auf die wichtigsten Bestandteile unserer Vagrantfile ein:

config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"

Wir nutzen Ubuntu 14.04 als Betriebssystem für unsere virtuelle Maschine, welches unter der angegebenen URL automatisch heruntergeladen und installiert wird.

config.vm.network "private_network", ip: "10.34.1.10", netmask: "255.255.0.0"

Die virtuelle Maschine bekommt die IP Adresse 10.34.1.10/16 zugewiesen. Diese IP Adresse ist wichtiger Bestandteil für die spätere Docker Konfiguration, da diese im selben Netz liegen muss!

config.vm.provider :virtualbox

VirtualBox wird als VM-Provider genutzt

vbox.customize ["modifyvm", :id, "--cpus", "2", "--memory", "2048"]

Hier wird die Anzahl der CPUs und die Größe des Hauptspeichers für die virtuelle Maschine gesetzt.

1.upto(8) do |nic|
vbox.customize ["modifyvm", :id, "--nicpromisc#{nic}", "allow-all"]
end

Mit diesem Befehl werden alle NICs auf promiscous mode “allow-all” gesetzt. Standardmäßig nehmen die NICs nur Datenpakete für die virtuelle Maschine an, weil sie keine weiteren IP Adressen innerhalb der virtuellen Maschine kennen. Da die Docker Container eine andere MAC Adresse als die virtuelle Maschine haben, würden die NICs diese Datenpakete verwerfen. Somit wäre es nicht möglich Datenpakete zwischen Host (Laptop / PC) und den Docker Containern zu routen. Mit dem Parameter promiscous mode “allow-all” werden jedoch sämtliche Frames anderer MAC Adressen akzeptiert, wodurch der Host später mit den Docker Containern kommunizieren kann.

config.vm.provision "docker", version: "1.4.0"

Vagrant bietet bereits einen eigenen Docker Provisioner an, der die Installation von Docker übernimmt. Es muss nur die Docker Version, in unserem Fall 1.4.0, angegeben werden.

config.vm.provision :shell, :path => "provisioning/run.sh"

Hier konfigurieren wir den Docker Daemon auf unsere Bedürfnisse. Wir werden im nächsten Abschnitt detailliert darauf eingehen.

config.vm.provision :unix_reboot

Nachdem die virtuelle Maschine vollständig konfiguriert wurde, muss diese nun nur noch neu gestartet werden. Hierfür benutzen wir das Folgende Vagrant Plugin.

Docker

Im provisioning Verzeichnis liegt das run.sh Skript, das von Vagrant beim Provisionieren der virtuellen Maschine ausgeführt wird. Das Skript ist dafür verantwortlich 2 Dateien an ihren richtigen Platz zu kopieren und Pipework in das /usr/local/sbin Verzeichnis herunterzuladen.

Docker Daemon mit zusätzlichen Optionen starten

Docker erlaubt es unter “/etc/default” eine Datei mit dem Namen “docker” abzulegen, die zusätzliche Docker Parameter beinhaltet, welche beim Start von Docker berücksichtigt wird. In unserem Fall sieht die docker Datei wie folgt aus:

-bip=10.34.1.10/16

Mit diesem Parameter setzen wir die IP Adresse für die Docker Bridge auf 10.34.1.10/16. Die Docker Bridge liegt somit im selben Netz wie die virtuelle Maschine. Dies ist eine Grundvoraussetzung um später vom Host (PC / Laptop) aus mit den Docker Containern kommunizieren zu können.

--fixed-cidr=10.34.128.0/17"

Hiermit beschränken wir den IP Bereich des Docker Netzwerks und umgehen somit IP Kollisionen wenn Docker Container statische IP Adressen mit Hilfe von Pipework zugewiesen bekommen. Aus der Sicht von Docker bedeutet dies, dass IP Adressen im Bereich von 10.34.128.1 bis 10.34.255.255 vergeben werden können.

VirtualBox und Docker miteinander vereinen

Die rc.local Datei wird bei jedem Systemstart ausgeführt und stellt sicher, dass zum Einen die virtuelle Maschine in das Docker Netzwerk integriert und zum Anderen Pipework installiert wird.

/sbin/iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
/sbin/iptables -A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
/sbin/iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT

Mit diesen 3 Zeilen aktivieren wir NAT, damit die Docker Container mit der Außenwelt kommunizieren können, denn die IP Adressen der Container gelten nicht im Host Netz (PC / Laptop), da sie dort nicht gehostet werden.

ip addr del 10.34.1.10/16 dev eth1
ip link set eth1 master docker0

Hier löschen wir die IP Adresse an eth1 und stecken das Interface in die Dockerbridge wodurch der Host (PC / Laptop), die VM und die Docker Container miteinander verbunden werden.

Fazit

Mit der hier vorgestellten Basiskonfiguration haben wir nun Folgende Ziele erreicht:

1. Docker wird automatisiert installiert

Dies wurde mit Hilfe des Docker Provisioner erzielt.

2. Docker Container sind vom Host (PC / Laptop) aus erreichbar

Dies können wir mit einem einfachen Test beweisen. Hierfür müssen wir uns in die virtuelle Maschine einloggen und einen Docker Container starten:

docker run -it stackbrew/ubuntu /bin/bash

Wenn der Docker Container gestartet wurde kann mit Hilfe von ifconfig die IP Adresse von eth0 herausgefunden werden. Diese IP Adresse solltet ihr nun von eurem Host (PC / Laptop) aus mit einem einfachen Ping Befehl erreichen können. Falls dies der erste Docker Container war den ihr gestartet habt, sollte die IP Adresse: 10.34.128.1 lauten.

3. Docker Container können statische IP Adressen zugewiesen bekommen

Die Grundlage hierfür haben wir mit der Installation von Pipework geschaffen. Um nun beispielsweise unserem vorher gestarteten Docker Container eine von uns definierte IP Adresse zuzuweisen muss Folgender Befehl ausgeführt werden:

sudo pipework docker0 <container-id> 10.34.10.11/16

Mit diesem Befehl würde unser Docker Container nun auch unter der IP Adresse 10.34.10.11 erreichbar sein.

4. Die statischen IP Adressen sollen nicht mit den automatischen von Docker zugewiesenen IP Adressen kollidieren

Da wir das Docker Netzwerk auf 10.34.128.0/17 beschränkt haben, können wir IP Adressen von 10.34.0.1 bis 10.34.127.255 an die Docker Container vergeben, ohne dass wir Konflikte mit den von Docker automatisch vergebenen IP Adressen bekommen.

Ausblick

Wir haben nun die Grundkonfiguration für RED fertig gestellt. Der nächste Artikel wird sich dann mit den Automatismen für das Bauen, Starten und Stoppen von unseren eigenen Docker Images/Containern beschäftigen. Also bleibt dran und meldet Euch bei uns, falls Ihr Fragen oder Anregungen habt.

The post RED “Runtime Environment for Developers” – Ein erster Blick unter die Haube appeared first on codecentric Blog.


Continuous Delivery für Microservices mit Jenkins und dem Job DSL Plugin

0
0

In klassischen monolithischen Architekturen ist die Zahl der Release-Artefakte gering und somit ist es auch relativ einfach die zugehörigen Jenkins-Jobs manuell zu erstellen und zu pflegen. Aber gerade in einer Microservice-Architektur steigt die Zahl der Deployment-Einheiten drastisch an und man benötigt hier nun definitiv eine automatisierte Lösung. Auch in Enterprise-Umgebungen mit sehr vielen gleichartigen Build-Jobs macht es nicht wirklich Sinn alle Jobs manuell anzulegen. Darüber hinaus sieht man bei vielen Jenkins-Installation separate Jobs für das Bauen, Releasen, Deployen und zum Testen der Applikationen:

Eine Continuous Delivery pipeline für eine monolithische Anwendung sieht dann z.B. so aus:

  1. build-application
  2. release-application
  3. deploy-application-to-dev
  4. run-tests
  5. deploy-application-to-qa

Ziele

Um den speziellen Anforderungen von Microservices in Bezug auf das Build- und Release-Management gerecht zu werden, zeige ich in diesem Blogartikel einen Weg, wie man die folgenden Ziele erreichen kann:

  1. Automatisierte Generierung eines Jenkins-Jobs, wenn der Benutzer ein neues Repository bzw. Modul ins SCM einfügt
  2. Nur noch 1 Jenkins-Job pro Release-Artefakt, der in der Lage ist den Build, das Release und das Deployment der Anwendung bzw. des Microservice durchzuführen

Build und Release

Bevor wir uns das Job DSL Plugin näher anschauen, möchte ich zunächst meinen favorisierten Weg für das Releasing von Anwendungen mit Maven zeigen. Vielleicht sind einige von euch auch nicht ganz so zufrieden mit dem Maven Release Plugin. Ich sehe hier zwei größere Probleme:

  1. Zu viele redundante Schritte (z.B. 3 volle clean/compile/test Zyklen!!)
  2. Instabilität – das Plugin modifiziert die pom.xml im SCM, so dass oft manuelle Schritte notwendig sind, um die Änderungen rückgängig zu machen, wenn vorher das Release schiefgelaufen ist

Für weitere Informationen zu diesem Thema kann ich die Blogartikel von Axel Fontaine empfehlen. Er zeigt ebenfalls einen alternativen Weg auf ein Release zu erstellen, den wir hier in diesem Artikel auch beschreiten werden.

Der Build ist sehr einfach:

  1. Jenkins startet den Build bei jedem SCM-Commit
  2. Jenkins baut die Software
  3. mvn clean package

Der Releaseprozess ist ebenfalls sehr einfach:

  1. Benutzer löst Releasebuild aus
  2. SNAPSHOT-Version in pom.xml wird durch Releaseversion ersetzt
  3. mvn build-helper:parse-version versions:set -DnewVersion=${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.incrementalVersion}-${BUILD_NUMBER}
  4. Jenkins baut die Software
  5. mvn clean package
  6. Artefakte in Repository deployen
  7. mvn deploy
  8. Version im SCM taggen
  9. mvn scm:tag

Einzelnen Job generieren

Um die Jobs nun automatisiert zu generieren, benötigen wir das Job DSL Plugin im Jenkins. Dies kann durch den normalen Jenkins-Plugin-Mechanismus installiert werden. Dieses Plugin ist wirklich zu empfehlen. Ich nutze es in einigen Projekten seit einem Jahr um Hunderte von Jobs zu generieren und wir hatten bisher keine nennenswerten Probleme damit. Auch wenn die Benutzung wirklich sehr einfach ist, würde ich für den Start auf jeden Fall dieses detaillierte Tutorial empfehlen, um die generelle Funktionsweise zu verstehen. Der in dem Tutorial erwähnte Seed-Job heißt hier “job-generator” und enthält im ersten Schritt folgendes DSL-Skript:

job(type: Maven) {
    name("batch-boot-demo")
    triggers { scm("*/5 * * * *") }
    scm {
		git {
		    remote {
		        url("https://github.com/codecentric/spring-samples")
		    }
		    createTag(false)
		}
	}
	rootPOM("batch-boot-demo/pom.xml")
	goals("clean package")
	wrappers {
		preBuildCleanup()
		release {
			preBuildSteps {
				maven {
					mavenInstallation("Maven 3.0.4")
					rootPOM("${projectName}/pom.xml")
					goals("build-helper:parse-version")
					goals("versions:set")
					property("newVersion", "\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.incrementalVersion}-\${BUILD_NUMBER}")
				}
			}
			postSuccessfulBuildSteps {
				maven {
				    rootPOM("${projectName}/pom.xml")
					goals("deploy")
				}
				maven {
					rootPOM("${projectName}/pom.xml")
					goals("scm:tag")
				}
				downstreamParameterized {
					trigger("deploy-application") {
						predefinedProp("STAGE", "development")
					}
				}
			}
		}
	}
	publishers {
		groovyPostBuild("manager.addShortText(manager.build.getEnvironment(manager.listener)[\'POM_VERSION\'])")
	}
}

Das obige DSL-Skript enthält die schon gezeigten Build- und Release-Schritte aus dem ersten Teil des Artikels. Um den Releaseprozess in einen Buildjob zu integrieren, nutzen wir das Jenkins Release Plugin, welches einen neuen Menupunkt in die Job UI einfügt (siehe Screenshot). Das “groovyPostBuild”-Element fügt die Versionsnummer der Buildhistorie hinzu (siehe Screenshot), so dass man sie direkt sehen kann, wenn man die Jobansicht öffnet. Um das Ganze direkt mal auszuprobieren, habe ich hier ein Docker-Image erstellt, was alle wichtigen Dinge enthält. Die notwendigen Installationsanweisungen befinden sich ebenfalls bei Github. Alternativ kann man natürlich auch schon einen evtl. bestehenden Jenkins nutzen und die fehlenden Plugins noch nachinstallieren.

job_view

Deployment

Aktuell ist es nun möglich die Artefakte zu bauen und zu releasen, aber das eigentliche Deployment auf das Zielsystem fehlt noch. Da wir keine separaten Deployment-Jobs für jedes Artefakt haben wollen, nutzen wir dafür das Promoted Builds Plugin. Dieses Plugin führt den Begriff der “Promotion” ein. Ein “promoted Build” ist ein erfolgreicher Build, der zusätzliche Kriterien erfüllt. In unserem Fall handelt es sich dabei um das Erreichen einer bestimmten Umgebung. Diese “promoted Builds” bekommen dann innerhalb der Buildhistorie ein kleines farbiges Sternchen für jede Umgebung (siehe Screenshot unten), wo die Anwendung mit der jeweiligen Version installiert wurde.

promotions

Um die Promotion-Stufen auch mitzugenerieren, wird folgendes DSL-Skript dem Hauptskript von oben hinzugefügt (Siehe auch job-dsl-example.groovy):

promotions {
	promotion("Development") {
		icon("star-red")
		conditions {
			manual('')
		}
		actions {
			downstreamParameterized {
				trigger("deploy-application","SUCCESS",false,["buildStepFailure": "FAILURE","failure":"FAILURE","unstable":"UNSTABLE"]) {
					predefinedProp("ENVIRONMENT","dev.microservice.com")
					predefinedProp("APPLICATION_NAME", "\${PROMOTED_JOB_FULL_NAME}")
					predefinedProp("BUILD_ID","\${PROMOTED_NUMBER}")
				}
			}
		}
	}
	promotion("QA") {
		icon("star-yellow")
		conditions {
			manual('')
			upstream("Development")
		}
		actions {
			downstreamParameterized {
				trigger("deploy-application","SUCCESS",false,["buildStepFailure": "FAILURE","failure":"FAILURE","unstable":"UNSTABLE"]) {
					predefinedProp("ENVIRONMENT","qa.microservice.com")
					predefinedProp("APPLICATION_NAME", "\${PROMOTED_JOB_FULL_NAME}")
					predefinedProp("BUILD_ID","\${PROMOTED_NUMBER}")
				}
			}
		}
	}
	promotion("Production") {
		icon("star-green")
		conditions {
			manual('prod_admin')
			upstream("QA")
		}
		actions {
			downstreamParameterized {
				trigger("deploy-application","SUCCESS",false,["buildStepFailure": "FAILURE","failure":"FAILURE","unstable":"UNSTABLE"]) {
					predefinedProp("ENVIRONMENT","prod.microservice.com")
					predefinedProp("APPLICATION_NAME", "\${PROMOTED_JOB_FULL_NAME}")
					predefinedProp("BUILD_ID","\${PROMOTED_NUMBER}")
				}
			}
		}
	}
}

Mit Hilfe des Elements “manual(‘user’)” ist es möglich die Ausführung einer Promotion auf bestimmte User oder auch Gruppen zu beschränken. Insbesondere beim Produktionsdeployment macht das Sinn ;-). Wenn die Promotion manuell ausgelöst wird, dann werden die konfigurierten Aktionen durchgeführt. In unserem Szenario wird letztendlich nur der Deploy-Job angestoßen, der die Anwendung auf die Maschine(n) installiert. Nach der erfolgreichen Ausführung und auch nur dann erhält der korrespondierende Build ein farbiges Sternchen. Mit dem “upstream(‘promotionName’)”-Element kann man zusätzlich noch die Promotionstufen voneinaner abhängig machen, z.B. ist ein Produktionsdeployment nur dann erlaubt, wenn das Artefakt vorher auch in QA installiert wurde. Das alles funktioniert schon sehr gut, aber leider gibt es hier auch eine schlechte Nachricht. Das Promoted Builds Plugin wird aktuell noch nicht offiziell vom Job DSL Plugin unterstützt. Mein Kollege Thomas hat hier schon sehr gute Arbeit geleistet, aber leider wurde der Pull Request bis heute noch nicht gemerged. Es ist jedoch schon ein Alpha-Release vorhanden, welches in der Zwischenzeit genutzt werden kann, um schonmal zu starten. Man kann nur hoffen, dass es hier bald bessere Neuigkeiten gibt. Eine Alternative dazu ist auch, dass man die Promotions in einem Templatejob definiert und diesen dann mit dem using-Element innerhalb der Job DSL referenziert.

Mehrere Jobs generieren

Im ersten Schritt oben wird bisher nur ein einziger Job generiert (“batch-boot-demo”). Als nächstes wollen wir nun direkt mehrere Jobs automatisiert generieren. In diesem Beispiel nutzen wir ein Repository bei Github welches einige Spring Projekte enthält. Mit der Github API ist es sehr einfach die Inhalte des Repos auszulesen.

def repository = 'codecentric/spring-samples'
def contentApi = new URL("https://api.github.com/repos/${repository}/contents")
def projects = new groovy.json.JsonSlurper().parse(contentApi.newReader())
projects.each {
    def projectName = it.name
    job(type: Maven) {
        name("${projectName}")
        triggers { scm("*/5 * * * *") }
 
		[...]
	}
}

Alternativ kann man sich natürlich auch ein kleines Shell-Skript schreiben, welches sich direkt mit der SCM-API unterhält und dann die Inhalte in eine Datei umleitet:

codecentric:spring-samples

Innerhalb des DSL-Skriptes kann dann auf die Datei zugegriffen werden:

readFileFromWorkspace("projects.txt").eachLine {
 
	def repository = it.split(":")[0]
	def projectName = it.split(":")[1]
 
	[...]
 
}

Nun ist es sehr wichtig dem Seed-Job einen SCM-Trigger zu verpassen, damit der Job auch jedesmal losläuft, wenn im SCM ein Projekt eingecheckt oder auch gelöscht wird. Das Job DSL Plugin ist sogar so intelligent, dass es bestehende Jenkins Jobs entfernt, wenn das zugehörige SCM-Projekt gelöscht wurde.

Was denkt ihr? Nutzt ihr bereits das Job DSL Plugin?

The post Continuous Delivery für Microservices mit Jenkins und dem Job DSL Plugin appeared first on codecentric Blog.

Rollentrennung und Fähigkeiten

0
0

Auslöser für den Blog-Artikel ist eine Diskussion die ich im Rahmen von Lean DUS,  einem lokalen Community Event in Düsseldorf, über folgendes Thema geführt habe: Was sollte ein Scrum Master alles können und was sollte er besser nicht können?

Dabei drehte sich die Diskussion darum, in wie fern es notwendig, hilfreich oder hinderlich ist, dass ein Scrum Master die Fähigkeiten besitzt, die für die anderen Rollen notwendig sind. Dabei habe ich eine unterschiedliche Auffassung darüber wahrgenommen, wie stark die Trennung der Rollen in der Praxis gelebt werden sollte. Inwieweit sollte sich der Scrum Master z. B. in die Arbeitsweise des Teams (oder des Product Owners) einmischen? Sollte er technische Qualität beurteilen können und seine Beurteilung dem Team spiegeln? In Rahmen dieses Artikels werd ich versuchen, die Frage “Wie stark sollte die Trennung der Rollen in der Praxis gelebt werden?” zu betrachten.

Aus meiner Erfahrung heraus ist solch eine Frage nicht einfach und allgemeingültig zu beantworten, sondern sollte je nach Rollenmodell und Situation unterschiedlich gehandhabt werden. Deshalb werde ich hier auf das Rollenmodell von Scrum in Entwicklungsprojekten, bzw. Produktentwicklungen eingehen. Auf vergleichbare Vorgehensmodelle lassen sich die Überlegungen übertragen.

Zunächst ist es notwendig, den Begriff “Rolle” etwas genauer zu beleuchten. Im hier betrachteten Zusammenhang geht es nicht um den Begriff “soziale Rolle” aus der Soziologie, sondern um “Rollen in einem Arbeitsprozess”. Eine leicht modifizierte Definition von www.projektmagazin.de lautet:

“Rolle bezeichnet eine temporäre Funktion einer Person oder Gruppe innerhalb der Arbeitsorganisation. Eine Rolle wird beschrieben durch Aufgaben, Befugnisse und Verantwortungen. Zur vollständigen Definition einer Rolle gehört die Angabe, ob sie teilbar und kombinierbar ist.”

Zusammengefasst definiert sich eine Rolle danach aus

  • den Aufgaben, die eine Rolle übernimmt,
  • den Befugnissen, die eine Rolle hat und
  • den Entscheidungen, die eine Rolle verantwortet.

Dabei unterstützen Rollendefinitionen die Arbeitsteilung innerhalb der Arbeitsorganisation.

Beispiel: Product Owner in Scrum

Die Rolle ist nicht teilbar und immer kombiniert mit der Rolle “Scrum Teammitglied”.

Aufgaben:

  • Verwaltung des Product Backlogs
  • Stakeholdermanagement
  • Zeitlich und inhaltliche Planung der Produktentwicklung
  • Fachliches Feedback einholen und berücksichtigen

Befugnisse:

  • Inhalt des Product Backlog festlegen (inkl. Reihenfolge)
  • Mit den Stakeholdern reden
  • Mit dem (Entwicklungs-)Team reden
  • Anteil der Zeit des Teams für Product Backlog Refinement nutzen
  • Aktuellen Sprint abbrechen

Verantwortete Entscheidungen:

  • Welche Anforderungen sind umgesetzt und welche nicht?
  • Wie ist die Umsetzung einer Anforderung im Detail ausgestaltet?
  • Wie wird mit den Stakeholdern kommuniziert? Und welche Informationen werden kommuniziert?
  • Welche Anforderungen werden als nächstes umgesetzt?

Aber warum helfen solche Rollendefinitionen bei der Arbeitsteilung?

Offensichtlich können sie zur Aufgabenverteilung innerhalb der Arbeitsorganisation genutzt werden. Da die Ausformulierung einer User Story durch einen Product Owner erfolgt und die technische Beurteilung durch Teammitglieder, kümmert sich kein Teammitglied, sondern der Product Owner um die neue fachliche Ideen der Stakeholder. Hat der Product Owner die Ideen ausformuliert, wird sich nicht der ScrumMaster (oder der Product Owner) um die technische Beurteilung (Machbarkeit, Aufwand) kümmern, sondern Mitglieder des Entwicklungsteams.

Weiterhin gibt es Erwartungen an eine Rolle, z. B. bezüglich der Auskunftsfähigkeit von Rolleninhabern. So erwarte ich z. B., dass mir ein Product Owner Aussagen über geplanten Umfang und Fertigstellungszeitpunkt geben kann, das Team die technische Architektur und Lösungsansätze erklären kann und der ScrumMaster die aktuellen Schwierigkeiten, die momentan besonders stören, benennen kann.

Rollen dienen auch dazu, festzulegen, wer den Kopf hinhält, wenn etwas nicht so läuft wie es sein sollte. Das klingt erstmal etwas böse. Aber es geht eigentlich darum, an wen und in welcher Intensität Feedback adressiert wird. So sollten Schwierigkeiten von Benutzern mit dem Produkt möglichst schnell und klar an den Product Owner berichtet werden, die Ergebnisse von Code-Analysen oder technische Probleme an das Entwicklerteam und Dinge, die Entwicklungsarbeit blockieren an den ScrumMaster.

Das zeigt, dass die Beschreibung einer Rolle als eine Schnittstellendefinition zwischen den unterschiedlichen Teilen einer Arbeitsorganisation fungiert. Dabei gilt, je enger diese Teile miteinander zusammen arbeiten, um so unschärfer kann diese Schnittstelle ausformuliert sein.

Wenn sich alle untereinander kennen und in dauernder Kommunikation stehen, dann sind formale und klare Rollendefinitionen nicht notwendig. Das ist der Grund warum Scrum innerhalb des Entwicklungsteams keine weiteren formalen Rollen definiert.Das bedeutet aber nicht, dass es keine impliziten Rollen gibt. Wenn es sich wirklich um ein “cross functional” Team mit teilweise sehr unterschiedlichen Expertisen (Tester, Entwickler, Redakteure, Designer etc.) handelt, werden sich innerhalb des Teams auch Rollen herauskristallisieren. Welche solcher impliziten Rollen es gibt, wie klar sie abgegrenzt von einander sind und wie dauerhaft sie existieren, ist aber der Selbstorganisation innerhalb des Teams überlassen.

Wird aber eine Schnittstelle außerhalb einer so intensiv zusammenarbeitenden Gruppe benötigt, werden formalere Rollen benötigt. Dies tritt z. B. in vielen Scrum-Of-Scrum Implementierungen auf. Oft haben Teams dann für übergreifende Themen (Architektur, Configuration Management, Test) explizite Ansprechpartner aus ihrem Team benannt, die sich um dieses Thema kümmern.

Zusammengefasst ergeben sich hieraus zwei Punkte:

  • Es gibt explizite (formale) und implizite Rollen.
  • Rollen verhalten sich gegeneinander anders je nachdem ob sie “intern” oder “extern” (lose) zusammenarbeiten.

Wie stark sollte die Trennung der Rollen in der Praxis gelebt werden?

Es ist klar, das implizite Rollen nicht klar getrennt sind. Diese Rollen verschieben/verändern sich je nach Situation sehr schnell. Das kann sowohl aufgrund von veränderten Rahmenbedingungen passieren, als auch dadurch, dass unterschiedliche Rolleninhaber die impliziten Rollen anders interpretieren.

Bei den formalen Rollen, die “intern” zusammenarbeiten ist es ebenfalls nicht notwendig, eine strikte Trennung vorzunehmen. Zwar sind diese Rollen in der Theorie klar und deutlich voneinander abgegrenzt und mit bewusst ins Rollenmodell eingeplanten Inter-Rollenkonflikten ausgestattet, aber in der praktischen Arbeit ist es durch die enge Zusammenarbeit möglich (und auch gewünscht), dass sich die Rolleninhaber untereinander unterstützen und aushelfen und damit die Rollengrenzen überschreiten. Obwohl eine Person eigentlich Teammitglied ist, agiert sie zeitweise als Product Owner oder als Scrum Master.

Nur in der externen Sicht auf die formalen Rollen kann eine eher striktere Trennung sinnvoll sein. Gerade bezüglich der Auskunftsfähigkeit und dem Adressieren von Feedback ist es effektiver, wenn es externen Beteiligten klar ist, welche Rolle sie ansprechen sollten.

Was bedeutet das jetzt für das Rollenmodell in Scrum?

Product Owner, Scrum Master und Team arbeiten sehr eng und intensiv zusammen, daher gilt meine Meinung nach untereinander die “interne” Sicht. Hier ist eine strenge Trennung der Rollen nicht zwingend notwendig (und auch meist nicht hilfreich). So kann es vorkommen, dass Teammitglieder am Product Backlog arbeiten oder Impediments aus dem Weg räumen.

Gerade wenn man die folgende einfache (und daher geniale) Verantwortungsbeschreibung der drei Rollen betrachtet:

  • Product Owner – Build the right thing
  • Team – Build the thing right
  • Scrum Master – Build it fast

3KreisePOSMTeam

beeinflussen die wenigsten Entscheidungen, die im Rahmen der täglichen Arbeit getroffen werden, nur einen dieser drei Verantwortungsbereiche. Oft sind sogar alle drei Bereiche betroffen. Daher ist ein kooperativer Umgang mit der Verantwortung notwendig und ein “Einmischen” in den Verantwortungsbereich der anderen Rollen nicht eine Ausnahme, sondern eher die Regel. So hat z. B. eine technische Architekturentscheidung natürlich Auswirkungen auf die Geschwindigkeit (sowohl auf die aktuelle, da ja stattdessen echte “Features” nicht gebaut werden, als auch (hoffentlich) auf die mittel- bis langfristige). Gleichzeitig schränkt sie die (faktisch) möglichen Anforderungen für den Product Owner ein.

Im Umgang mit den “externen” Rollen (zusammengefasst in den Stakeholdern) aber ist eine klare und deutliche Trennung der Rollen sinnvoll. So wenden sich die Stakeholder mit ihren Fragen und Wünschen an die richtige Rolle und ermöglichen damit den einzelnen Rolleninhabern, sich auf ihre Arbeit zu fokussieren.

Dabei möchte ich noch darauf hinweisen, dass in Umgebungen in denen ein Rollenmodell gerade erst eingeführt wird, und die Menschen noch unerfahren mit den Rollen sind, es oft hilfreich ist, die Rollen eine Zeit lang auch intern strikt zu trennen.

Was hat das für Auswirkungen auf die notwendigen Fähigkeiten für eine Rolle?

Innerhalb des Scrum Teams sind die Rollengrenzen nicht sonderlich streng getrennt. Deshalb sollten die Rolleninhaber in der Lage sein, auch in den anderen Rollen zu agieren. Dabei gelten die gleichen Überlegungen wie innerhalb jedes cross-funktionalen Teams. Mitglieder, die außerhalb ihrer Spezialbereiche agieren, müssen nicht perfekt sein, aber in den meisten Fällen Tätigkeiten gut genug ausführen können. Dabei gibt es einen gewisser Anteil an Aufgaben, die den Spezialisten für einen Bereich vorbehalten sind.

The post Rollentrennung und Fähigkeiten appeared first on codecentric Blog.

Jenkins Deployment Dashboard Plugin für Amazon EC2 Umgebungen

0
0

Dieser Blog Artikel beschreibt ein neues Jenkins Plugin, welches wir nach dem Feedback zur Continuous Delivery in the Cloud begonnen haben zu entwickeln. In den ursprünglichen Continuous Delivery Blog Artikeln haben wir das Setup und die Konfiguration einer Deployment Pipeline basierend auf Open Source Tools beschrieben. Um eine größere Anzahl an Test- und Produktivumgebungen über ein Dashboard zu verwalten, haben wir ein einfaches Dashboard aus statischem HTML generiert. Wir haben sehr viele Rückfragen zu diesem Dashboard erhalten und ob es ein Jenkins Plugin sei und wie es verwendet werden kann. Dieses Feedback hat uns dazu gebracht, dieses Dashboard als flexibles Jenkins Plugin zu entwickeln, Jenkins EC2 Deployment Dashboard Plugin. Wir denken, dass es jetzt eine ausreichende Stabilität besitzt, um offiziell released zu werden.

Welche Probleme löst das Jenkins Plugin?

Typischerweise haben Unternehmen verschiedene Umgebungen auf denen verschiedene Versionen einer Anwendung getestet werden können, bevor sie auf in die Produktionsumgebung deployed werden. Es sollte mindestens eine Test, Staging und Produktionsumgebung existieren. Testumgebungen werden oft von verschiedenen Entwicklern mit neuen Software Versionen bestückt. Größere Teams verlieren dabei schnell den Überblick welche Version aktuell deployt wurde. Um projektübergreifend Transparenz bzgl. der deployten Versionen zu schaffen, ist ein zentrales Dashboard sehr nützlich. Zusätzlich sollte es für den Product Owner möglich sein mit einem einfachen One-Click Build eine spezielle Software Version auf eine der Umgebungen zu deployen.

Dieses Plugin versucht diese Probleme der Übersichtlichkeit und Transparenz zu lösen.

Welche Vorraussetzungen müssen für die Verwendung des Jenkins EC2 Deployment Dashboards erfüllt sein?

Sie benötigen die folgenden Tools und Server Umgebungen um dieses Plugin verwenden zu können:

  • Amazon EC2 Instanzen für ihre Server Umgebungen (z.B. Produktion/Staging/Test Umgebungen)
  • Artifactory oder Nexus als Artefakt Repository um ihre Software Artefakte zu speichern (z.B. WAR, EAR, RPM, …)
  • Jenkins für das Bauen und Deployment ihrer Software (http://jenkins-ci.org)

Dashboard Ansicht

Hier ist ein Screenshot der Jenkins Deployment Dashboard Ansicht. Auf dem Dashboard sind alle konfigurierten EC2 Instanzen auf einen Blick zu sehen. Es wird der aktuelle Server Status angezeigt, sowie die deployte Software Version, Uptime, Instanz Typ und die aktuelle IP Adresse. Im oberen Bereich werden alle verfügbaren Software Versionen aus dem Artefakt Repository angezeigt. Aktuell unterstützt das PlugijFrog Artifactory und Sonatype Nexus als Artefakt Repository.

1-dashboard

Basic Workflow

  • Ein Continuous Integration Job baut die aktuellste Version der Software und lädt das fertige Artefakt in ein Artefakt Repository
  • Vom Deployment Dashboard aus, kann man alle vorhandenen Artefakt Versionen aus einer Dropdown Liste auswählen und eine Umgebung angeben, wohin die ausgewählte Version deployt werden soll
  • Der Deployment Job bekommt die VERSION und das ENVIRONMENT als Parameter übergeben. In diesem Job können sie ihre eigenen speziellen Deployment konfigurieren, die anhand der Version und der Umgebung das Deployment durchführen. Im Anschluss wird die EC2 Umgebung automatisch mit der VERSION getagged

Jenkins Plugin, Artifact Repository, AmazonEC2 Instances

Plugin Konfiguration

Globale Jenkins Konfiguration

Dieses Plugin ist jetzt über die Jenkins Update Site verfügbar. Das ist der einfachste Weg es zu installieren. In den folgenden Abschnitten wird die Konfiguration Schritt für Schritt beschrieben:

Das konfigurierte Artefakt Repository verwenden wir später für das Deployment Dashboard. Darüber werden alle für ein Deployment verfügbaren Version geladen.

0-jenkins-configuration-artifactory

Globale Amazon Zugangsdaten Konfiguration

Als nächstes müssen die globalen AWS Zugangsdaten eingerichtet werden. Hierfür bitte http://localhost:8080/jenkins/credential-store/ aufrufen

  • Danach Manage Credentials um die Zugangsdaten einzugeben
  • Im Anschluss die Verbindungsdaten testen

Die AWS Zugangsdaten werden benötigt, um die wichtigsten EC2 Umgebungsinformationen abfragen zu können und Label an den EC2 Instanzen aktualisieren zu können. Falls sie noch keine AWS Zugangsdaten in der AWS Konsole eingerichtet haben, finden sie hierzu die Anleitung weiter unten, inkl. einer vollständigen Beschreibung.

0-jenkins-configuration-credentials

Deployment Job Konfiguration

Als nächstes muss ein parametrierbarer Job angelegt werden, der zwei Parameter entgegennimmt. Diesen Job verwenden wir später für das Deployment der Anwendung.

  • Der erste Text Parameter bekommt den Namen VERSION
  • Der zweite Text Parameter den Namen ENVIRONMENT
  • Zum Schluss muss noch der Build Schritt mit dem Namen EC2 Environment und den passenden AWS Zugangsdaten ausgewählt werden

Der EC2 Environment Build Schritt tagged automatisch die EC2 Umgebung mit der übergebenen VERSION. In diesem Deployment Job können sie ihre eigenen Dployment Skripte konfigurieren und die übergebenen Parameter verwenden, um die korrekte VERSION ihrer Software auf das korrekte ENVIRONMENT zu deployen. Stellen sie zudem sicher, dass sie die korrekten AWS Zugangsdaten und AWS Region konfiguriert haben.

3-deployJob

Dashboard Ansicht Konfiguration

Nachdem wir alle Vorbereitungen getroffen haben, kommen wir jetzt zur Konfiguration der Dashboard Ansicht. 

  • Zuerst muss eine neue Ansicht vom Typ Deployment Dashboard angelegt werden, http://localhost:8080/jenkins/newView
  • Als nächstes konfigurieren wir die Ansicht:
    • Mit der Checkbox Display available Releases aktiviert man die Dropdown Liste mit den Artefakt Versionen auf dem Dashboard, sowie einen Deployment Knopf
    • Dazu muss die groupId und artifactId des Artefaktes angegeben werden, für welches die Versionen im Artefakt Repository gesucht werden sollen
    • Als nächstes können sie alle ihre EC2 Umgebungen anlegen, auf die ihre Software deployed werden soll
    • Als Build Job wählen sie bitte den Deploy Job aus, den wir zuvor angelegt hatten
    • Zum Schluss noch die Konfiguration speichern

Hier eine Übersicht, wie ihre Konfiguration aussehen sollte:

5-view-config

Nun sollten sie den Status ihrer EC2 Umgebungen auf dem Dashboard sehen, sowie die Versionen und weitere Details. In der oberen Hälfte des Dashboards ist eine Auswahlbox zu sehen, in der alle Versionen ihres Software Artefaktes angezeigt werden. Die Versionen wurden aus ihrem Artefakt Repository geladen. Wenn sie eine VERSION auswählen und ein ENVIRONMENT können sie mit dem Deploy Knopf den Deployment Job starten.

Fertig :-)

AWS Sicherheitseinstellungen

Zuerst ein paar generelle Informationen. AWS EC2 Instanzen können mit eigenen Labels getagged werden. Dieses Plugin verwendet Tags um die Umgebungen zu identifizieren und die Versionsnummer an der Umgebung zu speichern. Hier ist ein Screenshot von der AWS Management Konsole. Die EC2 Instanz mit der ID i-294e3b6b wurde mit der Version 1.4.416 getagged.

4-aws-tags

AWS Access Key anlegen

Um einen AWS Access Key mit ausreichend Rechten anzulegen, müssen sie die folgenden Schritte durchführen:

  • AWS Konsole starten https://console.aws.amazon.com/iam/home?#users
  • Neuen IAM User anlegen
  • Danach User Policy auswählen  Custom Policy → Select → Set Policy Name → zum Schluss das folgende Policy Statement setzen 
    • allow User to create tags
    • allow User to delete tags
    • allow User to describe instances

Anstatt jede Policy separat auszuwählen, können sie auch das folgende Statement kopieren.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1412928158000",
      "Effect": "Allow",
      "Action": [
        "ec2:DeleteTags",
        "ec2:DescribeInstances",
        "ec2:CreateTags"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

Zusammenfassung

Ich hoffe ihnen hilft das Plugin bei ihren Projekten weiter. Sie können den Source Code auf Github unter https://github.com/jenkinsci/ec2-deployment-dashboard/ forken. Über Anregungen und Rückmeldungen würde ich mich sehr freuen.

 

The post Jenkins Deployment Dashboard Plugin für Amazon EC2 Umgebungen appeared first on codecentric Blog.

Die Produkt-Speisekarte (Product Menu) – Eine neue Art das Product Backlog zu strukturieren

0
0

Seit einem Jahr bin ich nun stolzer Product Owner von CenterDevice. Und die ganze Zeit hadere ich mit mir und der Art und Weise, wie ich das Product Backlog in Form halte. Oder besser, wie ich daran scheitere. Vor drei Wochen entschied ich dann, dass ich nicht der Schuldige bin, sondern meine Herangehensweise. Also habe ich diese geändert.

Was waren die Schwierigkeiten, höre ich Euch fragen? Die Herausforderungen? CenterDevice ist aktuell ein StartUp, das kurz vor dem Durchbruch steht. Es ist nicht so, dass wir keine Kunden hätten, das Gegenteil ist der Fall. Wir haben so viele Kunden, dass die ganzen Kleinigkeiten, die aus dem Vertrieb, den Kundenbetreuern, dem Support und dem Management auf uns zukommen, uns ohne Probleme komplett beschäftigen können. Andererseits, und wie könnte es anders sein, hätte ich nichts gegen viel mehr Neukunden :)

Um die Kundenbasis auszubauen und das Wachstum zu steigern, würde ich neue Funktionen gerne schneller in Betrieb nehmen, neue Sachen ausprobieren, messen ob sie funktionieren, usw. Die ganze Lean StartUp-Palette eben.

Aber das in einem “klassischen” Product Backlog abzubilden war eine ziemliche Herausforderung. Eine lange Liste von Product Backlog Items, schön geordnet und geschätzt. Wir haben es als Scrum Team geschafft das Sprint Backlog während eines Sprints einigermaßen intakt zu halten. Aber in einem Kontext wie dem unseren ist einfach viel Dynamik im Product Backlog. Dies führte zu der Situation, dass die nächsten 1-2 Sprints einigermaßen auf die Releaseplanung projezierbar waren, aber danach herrschte das reine Chaos.

Eines Morgens kam ich darauf, dass nicht alle Einträge im Product Backlog gleich zu behandeln sind, und ich kategorisierte schnell in drei Töpfe, und ebenso schnell ersonn ich eine Metapher, die bis heute erstaunlich gut trägt. Wenn man bedenkt, dass das die erstbeste Idee war.

Meine Damen und Herren, darf ich vorstellen: Die Produkt-Speisekarte! (Jajaja, auf Deutsch hört sich das furchtbar an.)

Die Produkt-Speisekarte (Product Menu) – Eine neue Art das Product Backlog zu strukturieren

Die Produkt-Speisekarte (Product Menu) – Eine neue Art das Product Backlog zu strukturieren

Das Hauptgericht (Main Dish)

Das ist der Fokus. Das Entwicklungsteam wird sich hauptsächlich darauf konzentrieren. Jeden Sprint werden wir uns auf ein neues Hauptgericht konzentrieren. Um es in User Story-Sprache auszudrücken, dies ist eine “Epic”. Hauptgerichte können eine Hypothese sein, die es zu testen gilt, ein neues Feature, Verständnis das aufgebaut werden muss, etwas das es über den Kunden zu lernen gibt, etc. Es ist das Sprint Ziel, welches unter allen Umständen gehalten werden sollte. Als Product Owner ist es meine Pflicht das Hauptgericht in einige User Stories zu zerlegen (es ist eben eine Epic). I schätze, dass 5 bis 10 (ein bis zwei Handvoll) User Stories angemessen sind, sodass wir während der Sprintplanung noch etwas Platz haben, um alles “einzurütteln”. Meine Annahme ist weiterhin, dass es auch bei einem Hauptgericht einen abnehmenden Grenznutzen gibt. Also bringen wir es innerhalb eines Sprints so weit nach vorne, wie es sinnvoll ist. Jede Arbeit, die bei Sprintende noch nicht abgeschlossen ist, wird nicht ausgeliefert. Commits können rückgängig gemacht werden, Branches werden abgeschnitten. Wenn ich entscheide, dass es klug ist, das Thema weiter zu treiben, erstelle ich dafür später ein neues Hauptgericht.

Die Beilagen (Side Orders)

Jedes Hauptgericht wird im Sprint um einige Beilagen ergänzt. Das ist vermutlich, was dem normalen Product Backlog am nahesten kommt. Beilagen sind nach meinen Kriterien geordnet, aber nicht zusammenhängend. Sowohl zwischen den einzelnen Beilagen besteht (kaum) ein Zusammenhang, als auch zu dem Hauptgericht. Aber sie müssen erledigt werden, aus welchem Grund auch immer. Also liefern wir mit jedem Hauptgericht auch einige Beilagen aus. Wahrscheinlich so zwei bis drei, aber das hängt natürlich auch vom Entwicklungsteam und deren Velocity ab.

Die Knabbereien (Snacks)

Und dann gibt es noch die Vielzahl der kleinen Dinge: Kleine Bugs, kleine Verbesserungen, technische Aufgaben, usw. Diese Dinge sollten einerseits demnächst erledigt werden, wenn nicht würde ich das Ticket direkt löschen, andererseits ist mir die Organisation und Priorisierung aber für den Nutzen der Kleinigkeiten viel zu hoch. Stattdessen gibt es jetzt die Sammlung der Knabbereien, aus denen sich das Entwicklungsteam selbständig bedienen kann. Eine Knabberei sollte wirklich klein sein, maximal einen halben Tag Arbeit verursachen, je weniger desto besser. Wenn ich es als Product Owner für wichtig erachte, wann etwas erledigt sein sollte, müsste ich daraus eine Beilage machen.

Fazit

Mit dem Hauptgericht, den Beilagen und Knabbereien habe ich hoffentlich eine gute Balance gefunden, um das Scrum Team auf wichtige Dinge zu fokussieren, um das Product Backlog schnell umorganisieren zu können und trotzdem den Aufwand dafür minimal zu halten. Das CenterDevice Scrum Team hat gerade erst damit angefangen, diese Idee umzusetzen. Mit Erfahrungsberichten kann ich also leider noch nicht dienen, aber ich werde von mir hören lassen, sobald es etwas Neues gibt. Nach ersten Diskussionen scheint die Produkt-Speisekarte aber einen Nerv zu treffen, oder ist zumindest nach dem ersten Augenschein nicht kompletter Blödsinn. Also, was denkst Du über die Produkt-Speisekarte? Wirst Du das ausprobieren? Ist das etwas, das jeder Product Owner in seinem Werkzeugkoffer haben sollte? Wie siehst Du den Zusammenhang zu anderen Praktiken, die Du benutzt (oder kennst)?

Fragen & Antworten

Um die Beschreibung der Produkt-Speisekarte knapp zu halten, habe ich einige Aspekte in diesen Abschnitt ausgegliedert.

Ist das eine Abweichung von Scrum?

Nein. Laut Scrum kann sich das Product Backlog mit jedem Sprint komplett ändern, und es ist in der Verantwortung des Product Owners es zu warten. Das Product Backlog als Speisekarte aufzubauen ist nur ein spezieller Umgang mit den Product Backlog Items.

Wie verhält sich die Speisekarte zu Schätzungen und Releaseplänen?

Die geschätzten Stories helfen dem Team immer noch während der Planung ein gutes Gleichgewicht zwischen Hauptgericht und Beilagen zu finden. Als Product Owner stütze ich mich nicht mehr allzu sehr auf die Schätzungen (und habe es vermutlich auch kaum getan). Es ist viel einfacher jedem Sprint gedanklich ein Hauptgericht zuzuordnen, und um ein paar Beilagen zu ergänzen. Das funktioniert für meine Bedürfnisse und Ansprüche gut genug. Ja, damit habe ich vermutlich einen großen Schritt in Richtung #NoEstimates unternommen.

In welchen Umständen sollte man eine Produkt-Speisekarte anwenden?

Wenn ich versuche die Situation, in der sich CenterDevice gerade befinden, zu generalisieren – oder um genauer zu sein: die, des CenterDevice Core/Web-Teams; andere haben noch keine Speisekarte, dann sind das die Kernpunkte:

  • Es gibt ein bestehendes Produkt. Wenn Du auf der grünen Wiese anfängst, würde ich vermutlich nur Hauptgerichte benutzen, und die Beilagen und Knabbereien erstmal ganz beiseite lassen.
  • Der Weg in die Zukunft ist noch nicht klar, auch wenn man eine großartige Vision hat. Wenn Du nichts ausprobieren musst und eine klare Vorstellung davon hast (oder glaubst zu haben…) wie es weiter geht, dann brauchst Du vermutlich keine Hauptgerichte, sondern kannst dich ausschließlich von Beilagen (also dem “normalen” Product Backlog) ernähren.
  • Es gibt ausreichend Kunden, um Dich zu beschäftigen. Wenn Du es zulässt. Wenn Du noch nichts in Betrieb genommen hast, solltest Du das schleunigst tun :) Und wenn Dein Backlog nicht diese vielen, kleinen Items enthält: Gut für Dich, gut gemacht!

Kann ein Scrum Team mehr als ein Hauptgericht servieren?

Klar, warum nicht? Für uns würde das aktuell nicht funktionieren, aber warum nicht? Hey, warum skalierst Du die Scrum Teams nicht wie in einem Restaurant zu einer Großküche?

Wie hält man das Gleichgewicht zwischen Hauptgericht, Beilagen und Knabbereien?

Erfahrung und Zusammenarbeit während der Sprintplanung. Haltet das Gleichgewicht! Wenn Du Dich nur auf das Hauptgericht konzentrierst, wird es vermutlich langweilig, also würze es etwas nach! Wenn Du nur von den Knabbereien naschst, ist das auch ungesund!

 

The post Die Produkt-Speisekarte (Product Menu) – Eine neue Art das Product Backlog zu strukturieren appeared first on codecentric Blog.

Was haben Duplo, Batman und die Reihe 0 gemeinsam?

0
0

Eine ganze Menge, denn diese drei Gruppen von Masterstudenten der Fachhochschule Aachen (Campus Jülich) im Studiengang Technomathematik wollten wissen, wie es sich anfühlt, ein Projekt mit agilen Praktiken und einem modernen Technologie-Stack umzusetzen.

In einem 4tägigen Vorlauf von Seiten der Fachhochschule, der von Prof. Dr. Bodo Kraft und einem Doktoranden Marc Schreiber durchgeführt wurde, wurden die Basics zu Scrum, verteilter Versionsverwaltung, Build-Management, Release-Management, Continuous Integration und SOLID-Prinzipien gelegt.

Im anschließenden 3tägigen Praxisteil haben zwei Kollegen von uns (Michael Lex und Benedikt Ritter) diese Grundlagen vertieft und um die Themen TDD/ATDD, Softwarequalität und Softwaremetriken ergänzt. Das Ganze mittels einer konkreten Anwendung, die über fünf Sprints in den drei Gruppen gebaut werden musste.

Die Kollegen schlüpften dabei wechselseitig in die Rollen von Product Owner und Scrum Master und unterstützten die Teams bei (tool)technischen Fragen.

Fühlten sich die ersten Sprints noch etwas sperrig an, haben alle Teams im Laufe der Sprints Geschwindigkeit aufgenommen und es wurden eine Menge User Stories umgesetzt.

Gewonnen haben alle. Die Studenten können jetzt Begriffe wie Sprint Planning I + II, Backlog Grooming/Refinement, Daily Scrum, Retrospektive, Review, Task Board, Velocity und Story Points mit Leben füllen und konnten Teamskills verbessern. Die FH erfährt eine Aufwertung des Studiengangs und ein Alleinstellungsmerkmal im Wettbewerb mit anderen Hochschulen und uns von codecentric hat es nun bereits zum dritten Mal einfach nur Spaß gemacht.

Anklicken um

The post Was haben Duplo, Batman und die Reihe 0 gemeinsam? appeared first on codecentric Blog.

Generierte Jenkins Jobs und automatisches Branch Merging für Feature Branches

0
0

In meinem aktuellem Projekt nutzen wir sehr intensiv Feature Branches. Unser Master-Branch soll stets sauber, stabil und deploybar sein. Entwicklung, Code-Reviews und sogar die ersten Fachbereichstests finden in den Feature Branches statt.

Viele Projekte nutzen eine Menge von Jenkins-Jobs, um die Stabilität und Qualität sicherstellen zu können. Builds, Tests und Code-Metriken werden dabei häufig nur für den Master-Branch ausgeführt bzw. ermittelt.

Unser Ziel war die Reduzierung des Integrations-Risikos beim Zurückmergen eines Feature Branch in den Master-Branch. Die gleichen Jenkins-Jobs, die für den Master-Branch genutzt werden, können idealerweise auch für die Feature Branches genutzt werden. Entwickler sollten z.B. ihr Oberflächen-Test-Fehler oder Sonar-Violations direkt im Feature Branch fixen, nicht erst im Master-Branch. Als Nebeneffekt erhält das Produkt Owner Team eine klare Übersicht über alle Feature Branches und deren jeweiligen Status. Das Produkt Owner Team beginnt keinen Test, bevor nicht alle Feature-Branch-Jobs grün sind.


Jenkins hat keine eingebauten Features zur Unterstützung von Feature Branches. Bamboo zum Beispiel hat ein Feature mit dem Namen “plan branches“, aber für den Jenkins gibt es nichts Vergleichbares. In der Community findet man einige skizzierte Lösungen. Die meisten Ansätze basieren auf Job-Kopien und sind teilweise abhängig von einigen Jenkins-Plugins. Als Beispiel möchte ich einen Blog Post und eine zugehörige Thesis von zeroturnaround nennen. Hier wird das Thema Feature Branches in Verbindung mit Continuous Integration nochmal beschrieben und ebenfalls eine möglich Lösung genannt. Uns fehlten jedoch einige Punkte, z.B. eine einfache Branch-Status-Übersicht für den Fachbereich.

Also haben wir eine andere Lösung gesucht und auf Basis des Job-DSL-Plugin erstellt. Dabei haben wir gleich mehrere Verbesserungen umgesetzt.

  • Branch-Dashboard mit einem klar sichtbarem Status zu jedem Feature Branch
  • Automatisches erstellen/löschen von Jobs für Feature Branches, keine manuellen Aufwände
  • versionskontrolliere Job-Definitionen
  • Reduzierte Korrektur-Aufwände im Master-Branch nach der Integration von Feature Branches
  • Reduzierte Aufwände für Master-in-Feature-Branch-Merges (siehe Tipps & Tricks: Automatisches Merging)

Was haben wir gemacht?

  1. Ausdrücken der Jobs in DSL-Scripts
    Wir wollten schon immer unsere Job-Definition im SCM gesichert haben. Änderungen sollten nachvollziehbar und versionskontrolliert sein, genau wie Änderungen am restlichen Code. Also haben wir unsere Job-Definitionen in Form von DSL-Skripten des Job-DSL-Plugin ausgedrückt und im SCM abgelegt.
    // basic example-job, which checks out sources from mercurial,
    // runs a maven build and sends mails
    job {
        name('build.job')
        logRotator(-1,3)
        scm {
    	hg('http://mercurial.example.com/project123/','default')
        }
        triggers { scm('* 7-20 * * 1-5') }
        steps {
    	maven {
    	    rootPOM('parent/pom.xml')
    	    goals('clean install -T 1C')
    	    property('skipITs','true')
    	    property('maven.test.failure.ignore','true')
    	}
        }
        publishers {
    	mailer('devs@example.com',true,false)
    	archiveJunit('**/target/surefire-reports/*.xml')
        }
    }
  2. Setup des Seed-Job
    Der Seed-Job generiert die einzelnen Jobs anhand der DSL-Skripte. Hier gibt es ein gutes Tutorial zum einrichten des Seed-Jobs. Abweichend zum Tutorial nutzen wir jedoch die Option “Look on Filesystem” mit einem regulärem Ausdruck, um die DSL-Skripte aus dem SCM zu nutzen.
    Build-Step-Definition für den Seed-Job

    Build-Step-Definition für den Seed-Job


    Der Seed-Job wird durch SCM-Änderungen angetriggert. Zusätzlich läuft der Job einmal am Tag (früh morgens), falls z.B niemand im Master-Branch arbeitet, aber ein neuer Feature Branch hinzugefügt wurde.
  3. Erzeugen einer “new-line-separated” Datei mit offenen Branches
    Um zu jedem Feature Branch eine eigenen Job zu generieren, müssen wir wissen, welche Branches existieren. Wir nutzen ein kleines Shell-Skript, um die Branch-Name jeweils in eine Zeile in eine Datei zu schreiben. Das Skript wird als zusätzlicher Build-Step vom Seed-Job angestoßen. Wir nutzen Mercurial auf dem gleicher Server, wie unser Jenkins. Daher können wir recht einfach in das Verzeichnis unseres Mercurial-Repository wechseln und Mercurial nach den offenen Branches fragen.
    WORKING_DIR=$PWD
    cd /opt/mercurial/hg-repo
    hg branches | column -t | awk '{printf "%s\n",$1}' | sort > ${WORKING_DIR}/branches.txt
    cd $WORKING_DIR

    Shell-Step in Seed-Job

    Shell-Step in Seed-Job


    Da alle unsere Shell-Skripte ebenfalls im SCM abgelegt sind, befindet sich das Skript in einer eigenen Datei. Man könnte die einzelnen Befehle auch direkt in das Eingabefeld des Shell-Build-Step eintragen.
  4. Umstellen der DSL zur Erzeugung eines Jobs für jeden Branch
    Aktuell haben wir ein DSL-Skript für einen einfachen Build Job und eine Datei, die alle unsere aktiven Branches enthält. Jetzt erweitern wir das Skript, so dass es für jeden Branch läuft.
    // read the text-file containing the branch list
    def branches = []
    readFileFromWorkspace("seed-job","branches.txt").eachLine { branches << it }
     
    // for every branch...
    branches.each {
        def branchName = it
     
        job {
            // use the branchName-variable for the job-name and SCM-configuration
            name("branch.${branchName}.build.job")
            // ...
            scm {
    	    hg('http://mercurial.example.com/project123/',"${branchName}")
            }
            // ...
        }
    }

    Die Job-Konsole im Jenkins sollte in etwa so aussehen:

    Processing DSL script build.groovy
    Adding items:
        GeneratedJob{name='branch.featureFoo.build.job'}
        GeneratedJob{name='branch.featureBar.build.job'}

Jetzt wird für jeden Branch ein Job erzeugt. Gleichzeitig werden die Jobs nach zurückmergen der Branches in den master branch automatisch gelöscht. Hier ist keine manuelle Arbeit notwendig.

Einige Tipps & Tricks, falls ihr etwas ähnliches implementieren wollt:

  • “Repository Cache”
    Wir nutzen im Projekt Mercurial. Normalerweise würde jeder Job eine eigene Repository-Kopie anlegen. Im Mercurial-Jenkins-Plugin gibt es jedoch zwei Funktionen, die das verhindern. In den globalen Jenkins-Einstellungen finden man dazu die beiden Punkte “Use Repository Caches” und “Use Repository Sharing”.
  • Views erzeugen
    Wenn man viele Branches hat oder vielleicht mehrere Jobs pro Branch generieren will, wird es schnell unübersichtlich im Jenkins. Hier ist es eine gute Idee, ein paar Views zu erzeugen. Das Job-DSL-Plugin hilft hier ebenfalls.
    // example-view containing all jobs starting with "branch"
    view(type: ListView) {
        name 'Builds per Branch'
        jobs { regex("branch.*") }
        columns {
    	status()
    	weather()
    	name()
    	lastSuccess()
    	lastFailure()
    	lastDuration()
    	buildButton()
        }
    }
  • Auswahl-Parameter nutzen?
    Wir haben einen Deploy-Job, der von unserem Product Owner Team genutzt wird, um einen bestimmten Branch auf einen Test-Server zu deployen. Hier generieren wir keinen Deployment-Job für jeden Branch, sondern erzeugen einen Job, der die vorhandenen Branches als Auswahl-Parameter anbietet. Hierzu können wir unsere Datei mit den Branch einfach wiederverwenden.
    def branches = []
    readFileFromWorkspace("seed-job","branches.txt").eachLine { branches << it }
     
    job {
        name('deploy.test')
        parameters {
            // create a parameter "BRANCH", containing all entries from "branches"
            // default-value is the first entry from "branches"
            choiceParam('BRANCH',branches,'Which branch do you want to deploy?')
        }
        scm {
            hg('http://mercurial.example.com/project123/',"$BRANCH")
        }
        //...
    }
  • Automatisches Mergen?
    Nachdem wir die ersten Jobs für Branches erzeugt hatten, dachten wir über automatisches Mergen vom Master-Branch in die Feature Branches nach. In unserem Projekt ändern sich die Schnittstellen zu einigen benötigten Sub-Systemen recht häufig. Anschließend sind dann Änderungen im Zugriffscode notwendig, die von den Entwicklern direkt im Master-Branch durchgeführt werden. Diese Änderungen müssen jedoch auch in die Feature-Branches nachgezogen werden. Wir verbrachten viel Zeit mit einfachen Merges und dennoch kam es häufig zu Frustrationen beim Product Owner Team, wenn ein Feature in einem Branch getestet werden sollte, der Branch aber nicht auf dem aktuellem Schnittstellen-Stand war.
    Basierend auf den oben genannten Techniken haben wir also einen automatischen Merge implementiert. Solang es keine Merge-Konflikte gibt wird jede Änderung im Master Branch automatisch in die Feature-Branches gemerged. Der Merge wird über ein Shell-Skript durchgeführt, welches durch einen weiteren generierten Job ausgeführt wird. Falls es einen Merge-Konflikt gibt, muss der Entwickler manuell mergen. Im Jenkins ist damit jederzeit sichtbar, ob ein Branch gemerged werden konnte und wann die letzte Aktualisierung stattgefunden hat.
    # Job-DSL Automerge
     
    def branches = []
    // we use another file here, to filter some branches which should not get automerged
    readFileFromWorkspace("seed-job","branchesAutomerge.txt").eachLine { branches << it }
     
    branches.each {
        def branchName = it
     
        job {
    	name("branch.${branchName}.automerge")
    	triggers { cron('H 5 * * 1-5') }
    	wrappers {
    	    environmentVariables {
    		env('BRANCH', "${branchName}")
    	    }
     
    	    // we are using a single repository for the automerge-jobs. So we have to be sure, that only one job is using the repository
    	    exclusionResources('AUTOMERGE_REPO')
    	}
    	steps {
    	    criticalBlock {
    		shell(readFileFromWorkspace('parent/jenkinsJobs/scripts/automerge.sh'))
    	    }
    	}
        }
    }
    # automerge.sh
     
    # Jenkins uses "-e" parameter, but we want to handle the exit-code manually
    set +e
     
    WORKING_DIR=$PWD
    cd /var/lib/jenkins/repoAutomerge
     
    # reset local changes
    hg update -C .
    # get changes from repository
    hg pull
    # update to branch
    hg update -C ${BRANCH}
     
    # try the merge
    hg merge develop --config "ui.merge=internal:merge"
    mergereturn=$?
     
    case $mergereturn in
    	0)
    		echo '##################################'
    		echo '#####   Merge successfully   #####'
    		echo '##################################'
     
    		# commit and push
    		hg commit -m 'Automerge' -u 'AutoMerger'
    		hg push
     
    		rc=0
    		;;
    	1)
    		echo '####################################################'
    		echo '#####   Merge-Conflict, manual merge needed!   #####'
    		echo '####################################################'
    		rc=1
    		;;
    	255)
    		echo '############################################'
    		echo '#####   No Changes (Return-Code 255)   #####'
    		echo '############################################'
    		rc=0
    		;;
    	*)
    		echo '###############################################'
    		echo "#####   Merge-Returncode : $mergereturn   #####"
    		echo '###############################################'
    		rc=1
    		;;
    esac
     
    # reset local changes
    hg update -C .
     
    exit $rc

The post Generierte Jenkins Jobs und automatisches Branch Merging für Feature Branches appeared first on codecentric Blog.

Microservice-Deployment ganz einfach mit Giant Swarm

0
0

Mit Giant Swarm kann man Microservice-Anwendungen betreiben, ohne sich um die Bereitstellung der Infrastruktur oder Restriktionen durch die verwendeten Programmiersprachen kümmern zu müssen. Das Projekt befindet sich zur Zeit in der Alpha-Phase, für die man sich auf der Giant-Swarm-Seite registrieren kann. Ich wurde vor einigen Tagen in die Test-Phase aufgenommen und möchte testen, wie sich das Deployment von Microservice-Anwendungen mit Giant Swarm anfühlt.

Eine Giant-Swarm-Anwendung besteht aus einer Konfigurationsdatei (swarm.json) und ein oder mehreren Docker-Containern. Die Auslieferung und das Bauen der Container erfolgt mit dem Docker-Client. Mit dem swarm-Befehl kann die Anwendungskonfiguration ausgeführt und die Anwendung gesteuert werden.

Giant Swarm Overview

Eine Giant-Swarm-Anwendung besteht aus einem oder mehren Services. Diese beschreiben deren Domäne oder stellen technische Bausteine wie z.B. eine Datenbank dar. In der Giant-Swarm-Anwendungskonfiguration bestehen Services aus ein oder mehren Komponenten. Eine Komponente referenziert ein Docker-Image, das entweder aus der zentralen Registry Docker-Hub oder einer eigenen Docker-Registry heruntergeladen werden kann.

Services und Komponenten werden in der Konfigurationsdatei swarm.json beschrieben. Das folgende Listing zeigt die swarm.json Datei einer Microservice-Anwendung. Es handelt sich dabei um einen Online-Shop, der aus sechs Services besteht, einem technischen und fünf fachlichen Services. Der Service database-service stellt die NoSQL-Datenbanken Redis und MongoDB bereit. Diese werden von den fachlichen Services product-service, navigation-service und cart-service benutzt. Diese drei fachlichen Services werden dann wieder vom catalog-service und checkout-service benutzt. Die beiden letzten Services stellen sozusagen getrennte Domänen im User-Interface bereit. Eine vollständige Beschreibung der Microservice-Anwendung findet man in meinem Artikel “Microservices und die Jagd nach mehr Konversion”, die Giant-Swarm-Beispielkonfiguration befindet sich in meinem GitHub-Repository.

{
 "app_name": "shop",
 "services": [
   {
     "service_name": "database-service",
     "components": [
       {
         "component_name": "mongodb",
         "image": "mongo",
         "ports": [27017, 28017]
       },
       {
         "component_name": "redis",
         "image": "redis",
         "ports": [6379]
       }
     ]
   },
   {
     "service_name": "checkout-service",
     "components": [
         {
           "component_name": "shop",
           "image": "zutherb/monolithic-shop",
           "domains": { "monolithic-shop-zutherb.gigantic.io": 8080 },
           "ports": [8080],
           "dependencies": [
             {
               "name": "cart-service/cart",
               "port": 18100
             },{
               "name": "database-service/mongodb",
               "port": 27017
             }
           ]
         }
       ]
   },
   {
     "service_name": "catalog-service",
     "components": [
       {
         "component_name": "catalog",
         "image": "zutherb/catalog-frontend",
         "ports": [80],
         "domains": { "shop-zutherb.gigantic.io": 80 },
         "dependencies": [
           {
             "name": "product-service/product",
             "port": 18080
           },
           {
             "name": "navigation-service/navigation",
             "port": 18090
           },
           {
             "name": "cart-service/cart",
             "port": 18100
           },
           {
             "name": "checkout-service/shop",
             "port": 8080
           }
         ],
         "env": {
           "CHECKOUT_DESIGN": "standard"
         }
       }
     ]
   },
   {
     "service_name": "product-service",
     "components": [
       {
         "component_name": "product",
         "image": "zutherb/product-service",
         "ports": [18080],
         "dependencies": [
           {
             "name": "database-service/mongodb",
             "port": 27017
           }
         ]
       }
     ]
   },
   {
     "service_name": "navigation-service",
     "components": [
       {
         "component_name": "navigation",
         "image": "zutherb/navigation-service",
         "ports": [18090],
         "dependencies": [
           {
             "name": "database-service/mongodb",
             "port": 27017
           }
         ]
       }
     ]
   },
   {
     "service_name": "cart-service",
     "components": [
       {
         "component_name": "cart",
         "image": "zutherb/cart-service",
         "ports": [18100],
         "dependencies": [
           {
             "name": "database-service/redis",
             "port": 6379
           }
         ]
       }
     ]
   }
 ]
}

Die swarm.json kann mit dem Programm swarm gestartet werden. Das Konsolen-Programm lässt sich sehr einfach mit Brew auf einem Mac installieren, eine entsprechende Anleitung befindet sich in der Giant-Swarm-Dokumentation.

bz@cc $ swarm up
Starting application shop...
Application shop is up.
You can see all services and components using this command:
 
    swarm status shop
 
bz@cc $ swarm status
App shop is up
 
service             component   image                       instanceid    created              status
cart-service        cart        zutherb/cart-service        yhrer8pv6g4s  2015-04-29 08:41:01  up
catalog-service     catalog     zutherb/catalog-frontend    kn5yngzpq537  2015-04-29 08:40:58  up
checkout-service    shop        zutherb/monolithic-shop     5whwvc9l6x0k  2015-04-29 08:40:57  up
database-service    mongodb     mongo                       h5mhstve9dox  2015-04-29 08:40:55  up
database-service    redis       redis                       n34b441p94b9  2015-04-29 08:40:56  up
navigation-service  navigation  zutherb/navigation-service  hsldm8m61xrj  2015-04-29 08:41:00  up
product-service     product     zutherb/product-service     kkggq72p12db  2015-04-29 08:40:59  up

Nach dem Starten der Anwendung ist sie direkt verfügbar und kann über den folgenden Link aufgerufen werden. Man braucht sich keine Gedanken um Themen wie Infrastruktur, Service-Discovery und Skalierung zu machen, ganz im Zeichen der Docker-Philosophie “Build, Ship and Run – Any App, Anywhere”.

Das Fazit zur Alpha-Version von Giant Swarm ist durchaus positiv. Die Konfigurationsdatei ähnelt dem Format von Docker Compose und wenn man eine solche Beschreibung schon hat, lässt sich die Konfiguration relativ schnell migrieren. Das Starten der Anwendung ist mit dem Konsolen-Programm denkbar einfach. Das Konsolen-Programm stellt außerdem Kommandos bereit um Statistiken wie CPU- und Speicherverbrauch anzuzeigen, die einzelnen Komponenten zu skalieren und die Log-Dateien der einzelnen Komponenten zu überwachen. Alles hat im Test reibungslos funktioniert und Giant Swarm macht somit schon in der Alpha-Version einen guten Eindruck. Ich bin auf den ersten Projekteinsatz gespannt, da sich die PaaS-Lösung Giant Swarm wie ein Rundum-sorglos-Paket für das Deployment von Microeservice-Anwendungen an fühlt.

 

The post Microservice-Deployment ganz einfach mit Giant Swarm appeared first on codecentric Blog.


Microservice-Deployment ganz einfach mit Kubernetes

0
0

Im meinem letzten Artikel “Microservice-Deployment ganz einfach mit Giant Swarm” habe ich die PaaS-Lösung Giant Swarm als Rundum-sorglos-Paket für das Deployment von Microservice-Anwendungen vorgestellt. Mit Giant Swarm kann man die komplette Kontrolle von der Bereitstellung bis zur Verwaltung der Hardware und den Betrieb der Microservice-Anwendung abgegeben und braucht sich nicht mehr selbst um Infrastruktur-Aspekte zu kümmern.

Möchte man sich eine eigene Cloud mit eigener Hardware aufbauen, weil seine Daten nicht in die öffentliche Cloud gelangen dürfen, aber trotzdem ähnlich mit der Cloud arbeiten können, wie mit Giant Swarm empfiehlt es sich ein Clustermanagementwerkzeug zu benutzen. Ein solches Clustermanagementwerkzeug ist Kubernetes (griech. Steuermann). Kubernetes wird von Google entwickelt und ist ähnlich wie Giant Swarm für das Betreiben von Microservice-Anwendungen gedacht, die aus mehreren Container bestehen und über mehre Rechner hinweg laufen. Kubernetes stellt unter anderen Funktionalitäten bereit, um Anwendungen auszuliefern, zu betreiben und zu skalieren.

Kubernetes verfolgt dabei die Idee, dass der eigentliche Benutzer sich nicht darum kümmern soll, wo seine Arbeit im Cluster ausgeführt wird. Man gibt dem Cluster einfach Arbeit in Form eines Docker-Container und das Kubernetes-Cluster kümmert sich selbstständig darum, wo die Arbeit ausführt wird.

Was muss man nun tun, um ein Kubernetes-Cluster aufzubauen? Auf den Rechnern, auf denen die Docker-Container laufen sollen, muss der so genannten Kubelet-Daemon installiert werden. Diese Rechner werden auch Minions genannt. Weiterhin braucht man noch einen Rechner auf dem verschiedene Master-Komponeten (APIs, Scheduler, usw.) installiert werden. Eine wichtige Komponente stellt dabei der so genannte API-Server dar, der REST-Endpunkte bereitstellt. Diese werden unter anderem vom Konsolen-Programm kubectl benutzt, können aber auch von eigenen Programmen angesprochen werden, um das Cluster zu steuern. Kubelet-Daemon und API-Server benutzen zur Kommunikation den verteilten Key-Value-Speicher Etcd. Ein Architektur-Überblick ist in der nachfolgenden Darstellung zu sehen.

In einem Kubernetes-Cluster werden Docker-Container immer innerhalb von so genannten Pods gruppiert. Ein Pod kann ein oder mehrere Docker-Container enthalten. Die Docker-Container, die in einem Pod gruppiert werden, laufen garantiert auf dem selben Rechner im Cluster. Die Docker-Container, die von einem Pod gruppiert werden, können sich Daten über ein so genanntes Volume teilen. Ein Volume ist ein Verzeichnis, auf das die Docker-Container gemeinsam zugreifen können.

Wenn ein Docker-Container die Ausführung abbricht, sorgt der Kubelet-Agent automatisch dafür, dass der Container neu gestartet wird. Ein Container wird aber nicht automatisch auf einen anderen Rechner bewegt, wenn ein ganzer Rechner ausfällt. Das folgende Listing zeigt die Definition eines Pods für den Warenkorb-Service der Microservice-Anwendung aus meinem Artikel “Microservices und die Jagd nach mehr Konversion”:

{ "id": "cart",
  "kind": "Pod",
  "apiVersion": "v1beta1",
  "desiredState": {
    "manifest": {
      "version": "v1beta1",
      "id": "cart",
      "containers": [ {
          "name": "cart",
          "image": "zutherb/cart-service",
          "ports": [ {
              "containerPort": 18100 } ] } ] } },
  "labels": {
    "name": "cart",
    "role": "service” } }

Wenn man die Ausfallsicherheit der laufenden Container verbessern möchte, sollte man das Erstellen und Verwalten von Pods nicht selbst in die Hand nehmen, sondern einen Replication-Controller dafür verwenden. Ein Replication-Controller sorgt dafür, dass beliebig viele Kopien von einem Pod automatisch erstellt werden oder beim Ausfall eines Rechners der Pod auf einen anderen Knoten kopiert und ausgeführt wird. Das folgende Listing zeigt die Definition eines Replication-Controllers:

{ "id": "cart",
  "kind": "ReplicationController",
  "apiVersion": "v1beta1",
  "desiredState": {
    "replicas": 2,
    "replicaSelector": {"name": "cart"},
    "podTemplate": {
      "desiredState": {
        "manifest": {
          "version": "v1beta1",
          "id": "cart",
          "containers": [ {
              "name": "cart",
              "image": "zutherb/cart-service",
              "ports": [ {
                  "containerPort": 18100 } ] } ] } },
      "labels": {
        "name": "cart",
        "uses": "redis" } } },
  "labels": {
    "name": "cart",
    "role": "backend" } }

Moderne Web-Anwendungen bestehen häufig aus einer Vielzahl von verschiedenen Microservices. Wie es der Online-Shop in meinem Artikel “Microservices und die Jagd nach mehr Konversion” zeigt, kann es mehrere Benutzerschnittstellen geben, die auf verschiedene Backend-Services zugreifen und mit verschiedenen Datenbanken kommunizieren. Pods sind nicht zwangsläufig immer an der selben Stelle, spätestens wenn ein Rechner ausfällt, muss ein Pod bewegt werden. Pods können also kommen und gehen. Gerade wenn Replication-Controller verwendet werden. Jeder Pod bekommt eine eigene IP-Adresse, die aber veränderlich ist.

Wenn ein Pod Backendfunktionalitäten für einen Frontend-Pod bereitstellt, stellt sich die Frage, wie das Frontend einen einheitlichen Zugriff auf den Backend-Pod bekommt. Zum einen kann sich die IP eines Pods ändern und zum anderen wird ein zentraler Zugriffspunkt benötigt, wenn Replikationen eines Pods auf mehrere Rechner verteilt sind. Hierzu braucht man eine Instanz, die die Last auf die Pods verteilt. Eine solche Instanz ist ein Kubernetes-Service, der einen Satz an Pods über einen Selector auswählt. Das Ziel eines Kubernetes-Service ist es auch eine Verbindung zwischen nicht-Kubernetes-Anwendungen, also beliebigen Clients und den entsprechenden Pods zur Verfügung zu stellen. Im folgenden Listing ist die Definition des Warenkorb-Service zusehen, der als Loadbalancer auf die Pods unseres vorher definierten Replication-Controller fungiert:

{ "id": "cart",
  "kind": "Service",
  "apiVersion": "v1beta1",
  "port": 18100,
  "containerPort": 18100,
  "selector": { "name": "cart" },
  "labels": {
    "name": "cart",
    "role": "backend" } }

Wenn man mit virtuellen Maschinen ausprobieren möchte, wie sich die Arbeit mit Kubernetes selbst anfühlt, gibt es in meinem Github-Repository eine Vagrant-Datei mit der man ein Kubernetes-Cluster aufbauen kann, in dem man meinen Microservice-basierten Online-Shop betreiben kann. Um das virtuelle Cluster zu erstellen, muss man einfach die Befehle ausführen, die in der ReadMe beschrieben sind. Die Vagrant-Datei ist in der Lage ein Kubernetes-Cluster mit beliebig vielen Knoten aufzubauen, dazu muss man nur die Umgebungsvariable NUM_INSTANCES entsprechend anpassen.

Als Betriebssystem für das virtuelle Kubernetes-Cluster wird CoreOS verwendet. CoreOS ist eine spezielle Linux-Distribution um große, skalierbare Cluster auf unterschiedlichen Infrastrukturen einfach zu managen. CoreOS basiert auf ChromeOS und stellt ein sehr leichtgewichtiges Hostsystem bereit, das Docker-Container zum Ausführen von Applikationen benutzt. Damit bietet CoreOS eine gute Grundlage für ein Kubernetes-Cluster.

Damit man sieht, wie einfach die Arbeit mit einem Kubernetes-Cluster ist, kann man sich das nachfolgende Video ansehen, das ich erstellt habe. Darin wird gezeigt, wie man das Replikationsverhalten des Cluster bezüglich eines Katalog-Frontend im laufenden Betrieb mit dem Konsolen-Programm kubectl ändern kann. Außerdem sieht man, wie man einen A/B Test im Cluster deployen kann. Abschließend wird die Selbstheilungsfähigkeit des Clusters gezeigt, wenn ein Container beendet wird.

Google selbst sagt über Kubernetes noch: “Kubernetes is in pre-production beta!”. Trotzdem macht Googles Kubernetes schon einen recht ordentlichen Eindruck. Das Konsolen-Programm stellt Kommandos bereit um Pods zu skalieren, die Log-Dateien der Pods zu betrachten oder Rolling-Updates der einzelen Pods durchzuführen, wie man in nächsten Video sehen kann. Gerade das Rolling Update ist eine sehr schöne Funktion, die ich mir gerade für den Betrieb in meinen Projekten schon oft gewünscht habe, da es Zero-Downtime-Deployments stark vereinfacht und man Reaktionszeit bekommt, wenn bei einem Deployment doch etwas schief geht.

Die einzelnen Features funktionieren schon ganz gut, allerdings ist Kubernetes noch recht neu und dem entsprechend ist das Ökosystem um Kubenetes noch recht klein. Man sollte sich aber definitiv damit beschäftigen und Integrationsmöglichkeiten in bestehende Lieferketten ausloten. Gerade im Bereich Continuous Delivery bekommt man ganz neue Möglichkeiten. Man kann Multi-Deployment-Pipelines aufbauen, mit denen man jeden einzelnen Feature-Branch sehr einfach als Pod deployen kann. Die Fachseite kann die Feature so schneller abnehmen und wenn alle Tests in der Produktionspipeline fehlerfrei durchlaufen, wird das Feature direkt in Produktion ausgeliefert, was die Lieferzeiten deutlich senkt.

The post Microservice-Deployment ganz einfach mit Kubernetes appeared first on codecentric Blog.

Microservice-Deployment ganz einfach mit Docker Compose

0
0

In dem Artikel “Microservice-Deployment ganz einfach mit Giant Swarm” habe ich die PaaS-Lösung Giant Swarm als Rundum-sorglos-Paket für das Deployment von Microservice-Anwendungen vorgestellt. In einem weiteren Artikel “Microservice-Deployment ganz einfach mit Kubernetes” habe ich vorgestellt, wie man mit Kubernetes einen Cluster aus Docker-Containern aufbauen kann. Heute möchte ich vorstellen, wie man eine Microservice-Anwendung mit Docker-Bordmitteln aufbauen kann, um eine Microservice-Anwendung auf einem Entwicklerrechner oder einer AWS-Instanz zu betreiben, was sehr praktisch ist, wenn man schnell eine Produktpräsentation oder einem Integrationstest durchführen möchte.

Docker baut ein internes Netzwerk für alle Container auf, in dem jeder Container eine eigene IP-Adresse bekommt. Die IP-Adresse kann mit docker inspect angezeigt werden. Um die verschiedenen Container miteinander zu verlinken, wird Port-Mapping eingesetzt. Das Linking-System von Docker ermöglicht es beliebig viele Container miteinander zu verlinken. Wenn Container miteinander verbunden sind, setzt Docker entsprechende Umgebungsvariablen in den Zielcontainern. In diesen sind IP-Adressen und Ports der anderen Containern enthalten. Wie das aussieht, kann man im folgenden Listing sehen:

bz@cc $ docker ps
CONTAINER ID        IMAGE                               COMMAND                CREATED             STATUS              PORTS                                                NAMES
87bb5524067d        zutherb/product-service:latest      "/product-0.6/bin/pr   14 seconds ago      Up 13 seconds       0.0.0.0:18080-&gt;18080/tcp                          compose_product_1
bz@cc $ docker exec 87bb5524067d env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=87bb5524067d
MONGODB_PORT_27017_TCP=tcp://172.17.0.28:27017
MONGODB_PORT_27017_TCP_ADDR=172.17.0.28
MONGODB_PORT_27017_TCP_PORT=27017
MONGODB_PORT_27017_TCP_PROTO=tcp

Das folgende Listing zeigt die Verlinkung von vier Containern:

  • einer mit einem Online-Shop,
  • einer mit einem REST-basierten Warenkorb-Microservice,
  • jeweils einer mit den Datenbanken MongoDB und
  • Redis.

Für den Endbenutzer bedeutet das Verlinken von Containern das Ausführen mehrerer Docker-Kommandos.

bz@cc ~$ docker run -d --name mongodb mongo
705084daa3f852ec796c8d6b13bac882d56d95c261b4a4f8993b43c5fb2f846c
bz@cc ~$ docker run -d --name redis redis
784ebde0e867adb18663e3011b3c1cabe990a0c906396fc306eac669345628cf
bz@cc ~$ docker run -d -P --name cart --link redis:redis zutherb/cart-service
438b2657c7a5c733787fb32b7d28e1a0b84ba9e10d19a8a015c6f24085455011
bz@cc ~$ docker run -d -P -p 8080:8080 --name shop --link cart:cart --link mongodb:mongodb zutherb/monolithic-shop
9926e187faa215ac9044603d51adbd8d679d8076b4a349ebbc9917dade6d560e

Wie man durch das vorherige Listing sieht, sind schon zum Erstellen von einfachen Anwendungen mehrere Kommandos nötig. Das folgende Verteilungsdiagramm lässt erahnen, wie kompliziert das Ganze mit einer wachsenden Zahl von Microservices wird. Es wäre doch schön, wenn man eine Möglichkeit hätte, um solche komplexen Deployment-Szenarien zu beschreiben.

Deployment Diagramm Online Shop Docker

Verteilungsdiagramm eines Online-Shop mit Docker

Abhilfe schafft an dieser Stelle Docker Compose (früher Fig), mit dem sich ganze Microservice-Anwendungen in einer Datei beschreiben und mit einem einzelnen Kommando starten lassen. Hierfür definiert man alle Microservices einer Anwendung in einer docker-compose.yml Datei (früher fig.yml). Im folgenden Listing kann man die Beschreibung einer Online-Shop-Anwendung sehen, die man direkt auf jeden Rechner, auf dem Docker läuft, ausführen kann. Das komplette Beispiel findet sich auch in meinem GitHub-Repository:

mongodb:
  image: mongo
  ports:
     - "27017"
redis:
  image: redis
  ports:
     - "6379"
checkout:
  image: zutherb/monolithic-shop
  ports:
     - "8080"
  links:
     - mongodb
     - cart
product:
  image: zutherb/product-service
  ports:
     - "18080"
  links:
     - mongodb
navigation:
  image: zutherb/navigation-service
  ports:
     - "18090"
  links:
     - mongodb
cart:
  image: zutherb/cart-service
  ports:
     - "18100"
  links:
     - redis
catalog:
  image: zutherb/catalog-frontend
  ports:
     - "80:80"
  links:
     - product
     - navigation
     - cart
     - shop

Letztlich kann man mit dieser docker-compose.yml Datei und dem Kommando docker-compose up die komplette Umgebung starten. Das Konsolen-Programm docker-compose lässt sich sehr einfach mit Brew auf einem Mac installieren und es bietet Kommandos an, mit denen man den kompletten Lifecycle unserer Applikation steuern kann, wie z.B.:

  • Start, Stop und Rebuild eine Services,
  • Anzeigen der Stati der laufenden Services,
  • Anzeigen der Logs der laufenden Services und
  • An/Ausschalten von Services.

Im folgenden Listing sind die Phasen Starten und Stoppen des Online-Shop-Beispiels in der Shell zu sehen:

bz@cc ~$ docker-compose up -d
....
bz@cc ~$ docker ps
CONTAINER ID        IMAGE                               COMMAND                CREATED             STATUS              PORTS                                 NAMES
2730bad656eb        zutherb/catalog-frontend:latest     "dockerize -template   8 seconds ago       Up 7 seconds        443/tcp, 0.0.0.0:80-&gt;80/tcp           fig_catalog_1
3ba6e424506e        zutherb/navigation-service:latest   "/navigation-0.6/bin   10 seconds ago      Up 8 seconds        0.0.0.0:18090-&gt;18090/tcp              fig_navigation_1
db169f173cef        zutherb/monolithic-shop:latest      "/sbin/my_init"        12 seconds ago      Up 10 seconds       0.0.0.0:8080-&gt;8080/tcp                fig_shop_1
4db1ae8b366e        zutherb/cart-service:latest         "/cart-0.6/bin/cart"   13 seconds ago      Up 12 seconds       0.0.0.0:18100-&gt;18100/tcp              fig_cart_1
392fc3da4cfe        zutherb/product-service:latest      "/product-0.6/bin/pr   15 seconds ago      Up 13 seconds       0.0.0.0:18080-&gt;18080/tcp              fig_product_1
fd0ed6323975        redis:latest                        "redis-server /etc/r   16 seconds ago      Up 14 seconds       0.0.0.0:6379-&gt;6379/tcp                fig_redis_1
dbcbc99d4fb6        mongodb:latest                      "mongod"               17 seconds ago      Up 16 seconds       28017/tcp, 0.0.0.0:27017-&gt;27017/tcp   fig_mongodb_1
5efc445dbbbd        dockerui/dockerui:latest            "./dockerui"           19 seconds ago      Up 17 seconds       0.0.0.0:9000-&gt;9000/tcp                fig_dockerui_1
bz@cc ~$ docker-compose stop

Betrachten wir einen praktischen Anwendungsfall für Docker Compose. Möchte man beispielsweise einen A/B Test vom Deployment her integrativ testen, ist es nötig zwei unterschiedliche Versionen einer Microservice-Umgebung auszuliefern. Es gibt verschiedene Möglichkeiten für das Deployment von A/B-Tests. Entweder man baut eine neue Version des Microservice und pusht diese entsprechend in eine öffentliche oder private Docker-Registry oder man steuert den Test mit Umgebungsvariablen, die dann bestimmte Feature Toggles in der Applikation umschalten. Die folgenden Listings zeigen, wie diese beiden Alternativen in der docker-compose.yml Datei aussehen würden.

#Alternative Version des Service
catalog:
  image: zutherb/catalog-frontend:latest-b
  ports:
     - "80:80"
  links:
     - product
     - navigation
     - cart
     - checkout
#Umgebungsvariable für die Steuerung von FeatureToggles
catalog:
  image: zutherb/catalog-frontend
  ports:
     - "80:80"
  links:
     - product
     - navigation
     - cart
     - checkout
  environment:
     - CHECKOUT_DESIGN=amelia

Um das Deployment von unterschiedlichen Produkt-Konfigurationen mit Docker Compose zu verdeutlichen, habe ich das folgende Video erstellt, das noch fig als Kommando benutzt. Das fig-Kommando kann man jetzt einfach durch docker-compose ersetzen. Auch die Images wurden im dem Video schon vorher aus der zentralen Docker-Registry heruntergeladen. Deshalb sind die beiden Deployments von Variante A und Variante B recht schnell. Wenn man das Beispiel auf dem eigenen Rechner ausprobiert, müssen die Images noch aus der Docker-Registry heruntergeladen werden, das kann ein paar Minuten dauern. Das Beispiel selbst befindet sich in dem folgenden Github-Repository.

Zusammenfassend kann man sagen, dass Docker Compose das Deployment einer Microservice-Anwendung stark vereinfacht, die aus mehreren auf einem Host zu deployenden Einzelservices besteht. Man erstellt einfach eine docker-compose.yml Datei, in der man die Verbindungen der einzelnen Docker-Containern definiert und kann mit dem Konsolen-Programm docker-compose eine komplette Microservice-Umgebung starten. Noch viel einfacher kann das Deployment von komplexen Microservice-Anwendungen kaum sein.

The post Microservice-Deployment ganz einfach mit Docker Compose appeared first on codecentric Blog.

Microservice-Deployment ganz einfach ohne Docker mit der Linux-Paketverwaltung

0
0

In den letzten Artikeln der Serie “Microservice-Deployment ganz einfach” haben wir uns vorwiegend mit Werkzeugen beschäftigt, die auf dem Deployment-Werkzeug Docker aufbauen. In dem Artikel “Microservice-Deployment ganz einfach mit Giant Swarm” wurden die ersten Erfahrungen mit der PaaS-Lösung Giant Swarm beschrieben. In dem Artikel “Microservice-Deployment ganz einfach mit Kubernetes” wurde vorgestellt, wie man mit Kubernetes einen Cluster zum Betrieb von Docker-Containern aufbauen kann. Das auch der Betrieb einer Microservice-Anwendung auf einem einzelnen Rechner kein Problem ist, habe ich in dem Artikel “Microservice-Deployment ganz einfach mit Docker Compose” gezeigt.

Auch wenn Docker den Betrieb von Microservice-Anwendungen stark vereinfacht, ist es nicht zwingend nötig, Docker für das Deployment von Microservice-Anwendungen zu verwenden. Die meisten Linux-Distributionen verfügen über eine komfortable Software-Paketverwaltung, die auch zum Deployment von Microservice-Anwendungen benutzt werden kann. Wie in einem Docker-Container sollten in einem Linux-Paket alle notwendigen Dateien enthalten sein, um eine Anwendung auf einem Rechner ausführen zu können.

Konkrete Beispiele für Programme, um Software-Pakete zu installieren, sind:

  • RPM, das Pakete vom Typ *.rpm installieren und löschen kann und
  • Dpkg, das Pakete vom Typ *.deb installieren und löschen kann.

Beide Programme können aber keine Abhängigkeiten zwischen Paketen auflösen, da sie keine Funktionen haben, um Software nachzuladen. Dies können nur höhere Schichten der Paketverwaltung wie YUM oder APT tun. Im Folgenden betrachten wir Debian-Pakete als Beispiel näher. Die Konzepte für RPM-Pakete sind allerdings ähnlich.

Es gibt zwei Arten von Debian-Paketen:

  1. Zum Einen gibt es Binärpakete, die die ausführbaren Dateien, Konfigurationsdateien und Dokumentation beinhalten.
  2. Zum Anderen gibt es so genannte Quellpakete. Sie bestehen aus einer .dsc-Datei, die das Quellpaket beschreibt, einer .orig.tar.gz-Datei, welche die Quellen in einem gzip-komprimierten tar-Format enthält, und einer .diff.gz-Datei, die Debian-spezifische Änderungen an den Original-Quellen enthält.

Betrachten wir das Binärpaket, das wohl die meisten für das Deployment ihrer Anwendungen verwenden würden, da man sich das Kompilieren der Anwendung auf dem Zielrechner spart. Jedes Binärpaket besteht aus mindestens drei Dateien, die mit den Kommandos ar oder dpkg-deb entpackt werden können. Diese drei Dateien sind:

  • debian-binary: eine Textdatei mit der Versionsnummer des verwendeten Paketformats.
  • control.tar.gz: ein Archiv, das Dateien enthält, die zur Installation dienen oder Abhängigkeiten auflisten. Hier werden nur ein paar Dateien aufgeführt, die in dem Archiv enthalten sind. Eine weiterführende Beschreibung findet man in der offiziellen Debian FAQ zu .deb-Paketen.
    • Die control-Datei enthält Metainformationen zum Paket, wie etwa eine Kurzbeschreibung sowie weitere Informationen wie dessen Abhängigkeiten. Ein Beispiel für eine control-Datei ist im folgenden Listing zu sehen.
Source: shop-cart-service
Section: web
Priority: optional
Version: 4.2.42
Maintainer: Bernd Zuther <bernd.zuther@codecentric.de>
Homepage: http://www.bernd-zuther.de/
Vcs-Git: https://github.com/zutherb/AppStash.git
Vcs-Browser: https://github.com/zutherb/AppStash
Package: shop-cart-service
Architecture: amd64
Depends: redis-server (>= 2.8.13)
Description: Cart Service
    • preinst, postinst, prerm, postrm sind optionale Skripte, die vor oder nach dem Installieren bzw. Entfernen des Pakets ausgeführt werden. Diese Skripte werden mit root-Rechten ausgeführt.
  • data.* ist ein komprimiertes Archiv und enthält die eigentlichen Programmdaten mit relativen, mit dem Wurzelverzeichnis beginnenden Pfaden.

Im folgenden Listing ist zu sehen, wie die beschriebenen Bestandteile innerhalb eines Debian-Pakets aussehen:

bz@cc:~/$ ar t cart_0.6.20.deb
debian-binary
control.tar.gz
data.tar.gz
bz@cc:~/$ ar x cart_0.6.20.deb
bz@cc:~/$ ls
cart_0.6.20.deb  control.tar.gz  data.tar.gz  debian-binary
bz@cc:~/$ tar tzf data.tar.gz | head -n 14
./etc/
./etc/default/
./etc/init.d/
./etc/default/cart
./etc/init.d/cart
./usr/
./usr/share/
./usr/share/shop/
./usr/share/shop/cart/
./usr/share/shop/cart/bin/
./usr/share/shop/cart/lib/
./usr/share/shop/cart/bin/cart
./usr/share/shop/cart/bin/cart.bat
./usr/share/shop/cart/lib/cart-microservice-0.6.20.jar
bz@cc:~/$ tar tzf control.tar.gz
./postinst
./control
./md5sums
bz@cc:~/$ cat debian-binary
2.0

Betrachten wir nun, wie man die Debian-Paketverwaltung in einem Microservice-Projekt verwenden kann. In der folgenden Darstellung ist ein Verzeichnisbaum zu sehen. Darin ist zu erkennen, welche Dateien man anlegen muss, damit man diese zusammen mit den ausführbaren Dateien eines Warenkorb-Microservice in ein Deb-Paket verwandeln kann. Im debian-Verzeichnis befindet sich das control-Verzeichnis, aus dem das Archiv control.tar.gz generiert wird. Darin ist die oben beschriebene control-Datei und ein postinst-Script enthalten.

Nach dem Twelve-Factor-App-Konzept sollten Anwendungen immer als einzelne Prozesse ausgeführt werden. Auch Martin Fowler schlägt vor, dass man einen Microservice als einzelnen Prozess auf einen Rechner laufen lassen soll. Damit ein Prozess auf einem Linux-Rechner beim Betriebssystemstart automatisch gestartet wird, kann man ein so genanntes init.d-Script verwenden. In der Verzeichnis-Darstellung liegt im data-Verzeichnis unter /etc/init.d/ so ein init.d-Script. Alle Dateien, die unter dem data-Verzeichnis liegen, kommen so in das Debian-Paket, dass sie unter derselben Position unter dem Wurzelverzeichnis auf dem Zielrechner installiert werden. Unter dem Verzeichnis /data/etc/default liegt ein zusätzliches Script, das Umgebungsvariablen enthält, die vom dem Warenkorb-Microservice zum Starten benötigt werden. Dieses Script wird vom init.d-Script ausgeführt.

Verzeichnisstruktur Unix-Package

Verzeichnisstruktur eines Unix-Pakets

Im src-Verzeichnis liegt der Quellcode des Warenkorb-Microservice. Wie man an der build.gradle erkennen kann, wird das Projekt mit dem Buildwerkzeug Gradle gebaut. Wie bekommt man nun alle Teile zusammen, um ein Debian-Paket zu erhalten? Es ist sinnvoll das Bauen eines Debian-Pakets in den Anwendungsbuild zu integrieren. Mit JDeb gibt es eine Java-Bibliothek, um Debian-Pakete zu erstellen. Diese Bibliothek ist plattformunabhängig und baut Debian-Pakete ohne die Installation von irgendwelchen zusätzlichen Tools. Es gibt auch ein Gradle-Plugin, das auf JDeb basiert. Das folgende Listing zeigt die Verwendung dieses Plugins zusammen mit dem Spring-Boot-Plugin, dass das Gradle-Application-Plugin erweitert und den Teil der build.gradle, der für das Bauen eines Debian-Pakets verantwortlich ist:

apply plugin: 'spring-boot'
apply plugin: 'pkg-debian'
 
....
 
mainClassName = "io.github.zutherb.appstash.shop.service.cart.Boot"
def debName = "cart"
 
task prepareDeb {
   dependsOn installApp
 
   copy {
       from "${buildDir}/install/"
       into "${buildDir}/debian-data/usr/share/shop/"
   }
}
 
debian {
   packagename = "cart-service"
   controlDirectory = "${projectDir}/debian/control"
   changelogFile = "${projectDir}/debian/changelog"
   outputFile = "${buildDir}/debian/${debName}_${version}.deb"
 
   data {
       dir {
           name = "${projectDir}/debian/data"
           exclusions = ["**/.DS_Store", "changelog"]
       }
       dir {
           name = "${buildDir}/debian-data/"
           exclusions = ["**/.DS_Store"]
       }
   }
}

Das Gradle-Application-Plugin generiert zur Auslieferung einer beliebigen Anwendung die folgenden Verzeichnisse:

Verzeichnis Beschreibung
lib Enthält alle abhängigen Jar-Dateien und die Jar-Datei des Programms selbst.
bin Enthält generierte Start-Scripte, um das von der mainClassName referenzierte Programm zu starten.

Diese Verzeichnisse und die Start-Scripte kann man auch im einem Debian-Paket benutzen. Der Task prepareDeb führt dazu vorher den installApp Task aus, der die Verzeichnisstruktur generiert und die Start-Scripte erstellt, in der alle Jar-Artefakte der Anwendung enthalten sind, und kopiert diese innerhalb des build-Verzeichnisses in das Verzeichnis debian-data. Um das Gradle-Debian-Plugin zu konfigurieren, gibt es die debian { … }-Sektion. Darin definiert man die Variablen, die von JDeb benötigt werden, um ein Debian-Paket zu erstellen. In der debian { data }-Sektion ist das debian-data-Verzeichnis zusammen mit dem data-Verzeichnis definert. Die in den Verzeichnissen enthaltenen Dateien gelangen zusammen in das data-Archiv, wenn der Task buildDeb ausgeführt wird, der vom Gradle-Debian-Plugin bereitgestellt wird, um das Debian-Paket zu bauen.

Neben der Nutzung der offiziellen Paketquellen des Betriebssystem-Herstellers gibt es auch die Möglichkeit, eine eigene Paketquelle anzulegen. Dies kann zum Beispiel dann sinnvoll sein, wenn man ein eigenes Artefakt-Repository aufbauen möchte, in dem man alle Microservices seiner Microservice-Anwendung ablegt und das zur Installation benutzt wird. Mit dem Konsolen-Programm reprepro können sehr einfach eigene Paketquellen erstellt und verwaltet werden. Das folgende Listing zeigt, wie man reprepro von einem Buildserver aus benutzen kann, um sich eine eigene Paketquelle zu bauen. Stellt man das Verzeichnis /var/packages/debian über einen Webserver bereit, kann man die Quelle auf einen beliebigen Rechner hinzufügen und zur Installation einer Microservice-Anwendung nutzen.

echo *****************************************
echo * PUBLISH ARTIFACTS                     *
echo *****************************************
reprepro -Vb /var/packages/debian includedeb shop /tmp/*.deb
echo *****************************************
echo * REMOVE TMP ARTIFACTS                  *
echo *****************************************
rm -f /tmp/*.deb
echo *****************************************
echo * EXPOSE REPO ARTIFACTS                 *
echo *****************************************
reprepro -b /var/packages/debian/ export
reprepro -b /var/packages/debian/ list shop

Mit reprepro kann man sich sehr einfach eine Multi-Deployment-Pipeline aufbauen. Am Ende jedes einzelnen Builds pusht man das entstandene Debian-Paket mit dem Komando includedeb in die Paketquelle. Auf dem Zielrechner muss man nun die Paketquelle bekannt geben und kann den Warenkorb-Microservice ganz einfach mit dem Kommando apt-get install shop-cart-service installieren. Wie eine solche Multi-Deployment-Pipeline auf dem Jenkins Buildserver aussieht, zeigt die folgende Darstellung.

Microservice Multi-Deployment-Pipeline

Microservice Multi-Deployment-Pipeline

Das komplette Beispiel für eine solche Multi-Deployment-Pipeline kann man sich in meinem GitHub-Repository ansehen und ausführen. Im Repository ist eine Vagrant-Datei enthalten, mit der man sich ein Cluster aufbauen kann, das aus mehren virtuellen Maschinen besteht. In diesem Cluster befinden sich unter anderem zwei virtuelle Maschinen, auf denen ein Microservice-basierter Online-Shop läuft. Eine weitere virtuelle Maschine enthält den Buildserver zum Bauen der Microservice-Anwendung, der für deren Deployment eine auf Linux-Paketverwaltung aufbauende Multi-Deployment-Pipeline nutzt. Eine komplette fachliche Beschreibung der Anwendung befindet sich in meinem Artikel “Microservices und die Jagd nach mehr Konversion”.

Wie man an dem Beispiel sieht, muss man nicht immer Docker verwenden, um eine Microservice-Anwendung vollautomatisiert ausliefern zu können. Auch mit der Linux-Paketverwaltung kann man sich eine Registry aufbauen, wie wir sie von Docker kennen. Diese Registry nennt sich in diesem Zusammenhang aber “Paketquelle”. Eine solche Paketquelle kann man auf einem beliebigen Rechner benutzen, um die Microservice-Anwendung zu installieren. Anders als bei Docker, wo man über verschiedene Umgebungsvariablen in den laufenden Container beispielsweise die Adresse einer Datenbank mitgeteilt bekommt, muss man sich allerdings bei diesem Ansatz selbst um das Thema Service-Discovery kümmern. Allerdings hat man dann von Operationsseite auch die meisten Möglichkeiten, in den Betrieb der Microservice-Anwendungen einzugreifen.

The post Microservice-Deployment ganz einfach ohne Docker mit der Linux-Paketverwaltung appeared first on codecentric Blog.

Mock Server betreiben mit Mountebank und Docker

0
0

Bei der Entwicklung von Applikationen die von anderen Systemen abhängig sind, z.B. durch die Anbindung von Geschäftslogik und Daten, steht man vor der Frage, wie diese getestet werden sollen, falls die Systeme nicht verfügbar sind. Klassischerweise würde diese Anbindung über Mocks in Unit- oder funktionalen Tests durchgeführt werden. Trotzdem kann auf dem Weg durch die Applikation bis zur Netzwerkschicht einiges schief gehen.

In diesem Artikel betrachten wir eine Lösung, die einen Mock Server für solche allumfassenden Tests bereitstellt. Auf diese Art können Aufrufe durch eine Applikation gegen einen Mock Server durchgeführt und tiefgreifend getestet werden. Dadurch werden allumfassende Applikation und System-Ende-zu-Ende-Tests ermöglicht.

Mountebank

Mountebank ist ein Open Source Tool, das plattformunabhängige Multi-Protokoll Test Doubletten auf der Netzwerkschicht bereitstellt [1]. Eine zu testende Applikation muss nur auf die IP oder URL der Mountebank Instanz verweisen, statt auf die reale Abhängigkeit. Das ermöglicht, die Applikation durch alle Applikationsschichten zu testen, wie man es sonst traditionell mit Stubs und Mocks tun würde. Unterstützte Protkolle sind HTTP, HTTPS, TCP und SMTP.

Zu diesem Zweck steht eine DSL bereit, die genutzt werden kann, um Imposter Stubs zu konfigurieren, welche statisch oder dynamisch Responses für Requests erstellen [2]. Diese Stub-Funktionalität ist mit einem Proxy Mode erweiterbar, der Aufrufe zu den Originalsystemen aufnehmen und abspielen kann [3], und einem Mock Verfikationssystem, das eine Verfikation von Anfragen zu asynchronen Aufrufen an den Mock Server ermöglicht [4].

In den meisten Fällen reicht ein einfacher Request Response Mock Stub. Solch eine Definition könnte wie folgt aussehen, z.B. für einen HTTP Mock:

{
  "port": 8010,
  "protocol": "http",
  "name": "My Mock",
  "mode": "text",
  "stubs": [
    {
      "responses": [
        {
          headers: {
            'Content-Type': 'text/xml'
          },
          body: "..."
        }
      ],
      "predicates": [
        {
          "and": [
            {
              "equals": {
                "path": "/MyService/MyOperation"
              }
            },
            {
              "contains": {
                "body": "Mountebank"
              }
            }
          ]
        }
      ]
    }
  ]
}

Der Stub würde am Port 8010 auf einen HTTP Call warten. Wenn die Prädikate zutreffen, in diesem Fall ein Aufruf von /MyService/MyOperation welches den String “Mountebank” im POST Body enthalten würde, wird eine HTTP Response mit dem HTTP Content-Type “text/xml” und dem Body “…” gesendet.

Diese Definition kann an Mountebank entweder über das UI, das von Port 2525 erreichbar ist, über die REST API oder während des Applikationsstarts übergeben werden.

Mockdaten-Dateistruktur

Wenn die Applikation gestartet wird, kann eine Konfigurationsdatei automatisch an die Mountebank Instanz über die REST API gesendet werden.

Da solch eine Mockdaten-Datei viele verschiedene Stubs enthalten und dementsprechend groß und kompliziert werden kann, ist eine Vereinfachung notwendig. Diese ist möglich durch die Integration von JavaScript EJS Templating. Es kann verwendet werden, um eine größeren Mock-Datensatz bestehend aus mehreren Dateien und verschiedenen Ordern zu erstellen.

Die Hauptdatei “imposters.ejs”, die eine Liste für den Import von Mock Unterordnern für solch einen großen Mock Datensatz enthalten würde, könnte wie folgt aussehen:

{
  "port": 8010,
  "protocol": "http",
  "name": "NKD Mock",
  "mode": "text",
  "stubs": [
    &lt;% include myServiceA/imposters.ejs %&gt;,
    ...
  ]
}

Dabei würde im Unterordner die Datei “myServiceA/imposters.ejs”, welche die Stubs und Responses definiert, abgelegt werden. Zur weiteren Reduktion der Komplexität können diese wieder in separate Dateien ausgelagert werden:

{
  "responses": [
    {
      "inject": "&lt;%- stringify(filename, 'myServiceA/responseA.ejs') %&gt;"
    }
  ],
  "predicates": [
    {
      "and": [
        {
          "equals": {
            "path": "/MyService/MyOperation"
          }
        },
        {
          "contains": {
            "body": "Mountebank"
          }
        }
      ]
    }
  ]
},
{
  "responses": [
    {
      "inject": "&lt;%- stringify(filename, 'myServiceA/default.ejs') %&gt;"
    }
  ],
  "predicates": [
    {
      "equals": {
        "path": "/MyService/MyOperation"
      }
    }
  ]
}

In diesem Fall haben wir außerdem einen Standardfall, der greift, falls die anderen Prädikate nicht erfolreich evaluiert werden.

Die Response wird als Json-Objekt von eine JavaScript-Funktion aus der Reponse-Datei “myServiceA/default.ejs” zurück gegeben:

function() {
  return {
    headers: {
    'Content-Type': 'text/xml'
  },
    body: "..."
  };
}

Docker

Docker ist eine Open-Source-Technologie, die Virtualisierung von Maschinen in isolierten Containern auf einem Betriebssystem ermöglicht. Durch die Verwendung von Linux-Technologien wie Cgroups und Namespaces erlaubt es das schnelle und Ressourcen effiziente Erzeugen von Virtuellen Maschinen, womit man eine portable, nachvollziehbare und konsistente Infrastruktur aufbauen kann. Dies ist vor allem für die Erstellung, Durchführung und Nachvollziehbarkeit von Test-Szenarien, die infrastrukturbasiert sind, ein großer Vorteil.

Bauen des Mock Servers

Um ein Docker Image für dieses Szenario zu bauen, das die Mock Daten des Mock Servers bereits enthält, benutzen wir ein Docker Image, das Mountebank vorinstalliert hat und im Docker Repository verfügbar ist. Unsere Dockerfile würde wie folgt aussehen:

FROM                    cpoepke/mountebank-basis:latest
 
ADD resources/imposters /mb/
RUN ln -s /usr/bin/nodejs /usr/bin/node
 
EXPOSE 2525
EXPOSE 8010
 
CMD mb --configfile /mb/imposters.ejs --allowInjection

Beim Bauen des Docker Images werden die Mock Daten aus dem Ordner “resources/imposters” in den “/mb” Ordner des Docker Images kopiert, die Mountebank Ports der VM freigeschaltet und ein Start Kommando bereitgestellt, das beim Start des Containers Mountebank mit den Mock Daten startet.

Das Docker Image selber wird auf die übliche Weise gebaut:

build --tag="my-ws-mock" .

Starten des Mock Servers

Um den Mock Server mit bereits zugewiesen Ports zu starten, muss man folgendes Kommando ausführen:

run -t -i -p 2525:2525 -p 8010:8010 --name='my-ws-mock' my-ws-mock

Anmerkung: Bitte vergessen Sie nicht, das VM Port Fowarding auf Mac und Windows für boot2docker!

Weiterführende Arbeiten

Weitere Arbeiten können durch die Integration des Build-Prozesses für dieses Docker Image und das Deployment des Artefakts in ein Repository oder durch eine Continuous Integration/Continuous Deployment Pipeline durchgeführt werden. Daraufhin kann der Mock Server in automatisierten Integrations-, Akzeptanz-, Smoke- oder System-Ende-zu-Ende-Tests verwendet werden, abhängig von der Teststrategie. Sogar größere Szenarien mit verschiedene Mock Servern sind möglich, um verteilte Systeme wie z.B. Microservice basierte Architekturen zu testen, in denen die Mock Server während der Initialisierung der Infrastruktur gestartet werden.

Fazit

In diesem Artikel, haben wir gezeigt, wie ein externer Mock Server mit einem großen Mock Datensatz erstellt werden kann. Dieser Mock Server kann paketiert werden, durch die Verwendung einer Container Technologie wie Docker, und zusätzlich in eine Continuous Integration/Continuous Deployment Pipeline integriert werden. Dies ermöglicht das tiefgreifende Testen einer Applikation durch ihre Schichten bis hin zur Netwerk Schicht hindurch.

Referenzen

[1] http://www.mbtest.org
[2] http://www.mbtest.org/docs/api/stubs
[3] http://www.mulesoft.org/documentation/display/current/Unit+Testing
[4] http://www.mbtest.org/docs/api/mocks
[5] https://www.docker.com/whatisdocker/

The post Mock Server betreiben mit Mountebank und Docker appeared first on codecentric Blog.

Die Produkt-Speisekarte (Product Menu) – Eine neue Art das Product Backlog zu strukturieren

0
0

Seit einem Jahr bin ich nun stolzer Product Owner von CenterDevice. Und die ganze Zeit hadere ich mit mir und der Art und Weise, wie ich das Product Backlog in Form halte. Oder besser, wie ich daran scheitere. Vor drei Wochen entschied ich dann, dass ich nicht der Schuldige bin, sondern meine Herangehensweise. Also habe ich diese geändert.

Was waren die Schwierigkeiten, höre ich Euch fragen? Die Herausforderungen? CenterDevice ist aktuell ein StartUp, das kurz vor dem Durchbruch steht. Es ist nicht so, dass wir keine Kunden hätten, das Gegenteil ist der Fall. Wir haben so viele Kunden, dass die ganzen Kleinigkeiten, die aus dem Vertrieb, den Kundenbetreuern, dem Support und dem Management auf uns zukommen, uns ohne Probleme komplett beschäftigen können. Andererseits, und wie könnte es anders sein, hätte ich nichts gegen viel mehr Neukunden :)

Um die Kundenbasis auszubauen und das Wachstum zu steigern, würde ich neue Funktionen gerne schneller in Betrieb nehmen, neue Sachen ausprobieren, messen ob sie funktionieren, usw. Die ganze Lean StartUp-Palette eben.

Aber das in einem „klassischen“ Product Backlog abzubilden war eine ziemliche Herausforderung. Eine lange Liste von Product Backlog Items, schön geordnet und geschätzt. Wir haben es als Scrum Team geschafft das Sprint Backlog während eines Sprints einigermaßen intakt zu halten. Aber in einem Kontext wie dem unseren ist einfach viel Dynamik im Product Backlog. Dies führte zu der Situation, dass die nächsten 1-2 Sprints einigermaßen auf die Releaseplanung projezierbar waren, aber danach herrschte das reine Chaos.

Eines Morgens kam ich darauf, dass nicht alle Einträge im Product Backlog gleich zu behandeln sind, und ich kategorisierte schnell in drei Töpfe, und ebenso schnell ersonn ich eine Metapher, die bis heute erstaunlich gut trägt. Wenn man bedenkt, dass das die erstbeste Idee war.

Meine Damen und Herren, darf ich vorstellen: Die Produkt-Speisekarte! (Jajaja, auf Deutsch hört sich das furchtbar an.)

Die Produkt-Speisekarte (Product Menu) – Eine neue Art das Product Backlog zu strukturieren

Die Produkt-Speisekarte (Product Menu) – Eine neue Art das Product Backlog zu strukturieren

Das Hauptgericht (Main Dish)

Das ist der Fokus. Das Entwicklungsteam wird sich hauptsächlich darauf konzentrieren. Jeden Sprint werden wir uns auf ein neues Hauptgericht konzentrieren. Um es in User Story-Sprache auszudrücken, dies ist eine „Epic“. Hauptgerichte können eine Hypothese sein, die es zu testen gilt, ein neues Feature, Verständnis das aufgebaut werden muss, etwas das es über den Kunden zu lernen gibt, etc. Es ist das Sprint Ziel, welches unter allen Umständen gehalten werden sollte. Als Product Owner ist es meine Pflicht das Hauptgericht in einige User Stories zu zerlegen (es ist eben eine Epic). I schätze, dass 5 bis 10 (ein bis zwei Handvoll) User Stories angemessen sind, sodass wir während der Sprintplanung noch etwas Platz haben, um alles „einzurütteln“. Meine Annahme ist weiterhin, dass es auch bei einem Hauptgericht einen abnehmenden Grenznutzen gibt. Also bringen wir es innerhalb eines Sprints so weit nach vorne, wie es sinnvoll ist. Jede Arbeit, die bei Sprintende noch nicht abgeschlossen ist, wird nicht ausgeliefert. Commits können rückgängig gemacht werden, Branches werden abgeschnitten. Wenn ich entscheide, dass es klug ist, das Thema weiter zu treiben, erstelle ich dafür später ein neues Hauptgericht.

Die Beilagen (Side Orders)

Jedes Hauptgericht wird im Sprint um einige Beilagen ergänzt. Das ist vermutlich, was dem normalen Product Backlog am nahesten kommt. Beilagen sind nach meinen Kriterien geordnet, aber nicht zusammenhängend. Sowohl zwischen den einzelnen Beilagen besteht (kaum) ein Zusammenhang, als auch zu dem Hauptgericht. Aber sie müssen erledigt werden, aus welchem Grund auch immer. Also liefern wir mit jedem Hauptgericht auch einige Beilagen aus. Wahrscheinlich so zwei bis drei, aber das hängt natürlich auch vom Entwicklungsteam und deren Velocity ab.

Die Knabbereien (Snacks)

Und dann gibt es noch die Vielzahl der kleinen Dinge: Kleine Bugs, kleine Verbesserungen, technische Aufgaben, usw. Diese Dinge sollten einerseits demnächst erledigt werden, wenn nicht würde ich das Ticket direkt löschen, andererseits ist mir die Organisation und Priorisierung aber für den Nutzen der Kleinigkeiten viel zu hoch. Stattdessen gibt es jetzt die Sammlung der Knabbereien, aus denen sich das Entwicklungsteam selbständig bedienen kann. Eine Knabberei sollte wirklich klein sein, maximal einen halben Tag Arbeit verursachen, je weniger desto besser. Wenn ich es als Product Owner für wichtig erachte, wann etwas erledigt sein sollte, müsste ich daraus eine Beilage machen.

Fazit

Mit dem Hauptgericht, den Beilagen und Knabbereien habe ich hoffentlich eine gute Balance gefunden, um das Scrum Team auf wichtige Dinge zu fokussieren, um das Product Backlog schnell umorganisieren zu können und trotzdem den Aufwand dafür minimal zu halten. Das CenterDevice Scrum Team hat gerade erst damit angefangen, diese Idee umzusetzen. Mit Erfahrungsberichten kann ich also leider noch nicht dienen, aber ich werde von mir hören lassen, sobald es etwas Neues gibt. Nach ersten Diskussionen scheint die Produkt-Speisekarte aber einen Nerv zu treffen, oder ist zumindest nach dem ersten Augenschein nicht kompletter Blödsinn. Also, was denkst Du über die Produkt-Speisekarte? Wirst Du das ausprobieren? Ist das etwas, das jeder Product Owner in seinem Werkzeugkoffer haben sollte? Wie siehst Du den Zusammenhang zu anderen Praktiken, die Du benutzt (oder kennst)?

Fragen & Antworten

Um die Beschreibung der Produkt-Speisekarte knapp zu halten, habe ich einige Aspekte in diesen Abschnitt ausgegliedert.

Ist das eine Abweichung von Scrum?

Nein. Laut Scrum kann sich das Product Backlog mit jedem Sprint komplett ändern, und es ist in der Verantwortung des Product Owners es zu warten. Das Product Backlog als Speisekarte aufzubauen ist nur ein spezieller Umgang mit den Product Backlog Items.

Wie verhält sich die Speisekarte zu Schätzungen und Releaseplänen?

Die geschätzten Stories helfen dem Team immer noch während der Planung ein gutes Gleichgewicht zwischen Hauptgericht und Beilagen zu finden. Als Product Owner stütze ich mich nicht mehr allzu sehr auf die Schätzungen (und habe es vermutlich auch kaum getan). Es ist viel einfacher jedem Sprint gedanklich ein Hauptgericht zuzuordnen, und um ein paar Beilagen zu ergänzen. Das funktioniert für meine Bedürfnisse und Ansprüche gut genug. Ja, damit habe ich vermutlich einen großen Schritt in Richtung #NoEstimates unternommen.

In welchen Umständen sollte man eine Produkt-Speisekarte anwenden?

Wenn ich versuche die Situation, in der sich CenterDevice gerade befinden, zu generalisieren – oder um genauer zu sein: die, des CenterDevice Core/Web-Teams; andere haben noch keine Speisekarte, dann sind das die Kernpunkte:

  • Es gibt ein bestehendes Produkt. Wenn Du auf der grünen Wiese anfängst, würde ich vermutlich nur Hauptgerichte benutzen, und die Beilagen und Knabbereien erstmal ganz beiseite lassen.
  • Der Weg in die Zukunft ist noch nicht klar, auch wenn man eine großartige Vision hat. Wenn Du nichts ausprobieren musst und eine klare Vorstellung davon hast (oder glaubst zu haben…) wie es weiter geht, dann brauchst Du vermutlich keine Hauptgerichte, sondern kannst dich ausschließlich von Beilagen (also dem „normalen“ Product Backlog) ernähren.
  • Es gibt ausreichend Kunden, um Dich zu beschäftigen. Wenn Du es zulässt. Wenn Du noch nichts in Betrieb genommen hast, solltest Du das schleunigst tun :) Und wenn Dein Backlog nicht diese vielen, kleinen Items enthält: Gut für Dich, gut gemacht!

Kann ein Scrum Team mehr als ein Hauptgericht servieren?

Klar, warum nicht? Für uns würde das aktuell nicht funktionieren, aber warum nicht? Hey, warum skalierst Du die Scrum Teams nicht wie in einem Restaurant zu einer Großküche?

Wie hält man das Gleichgewicht zwischen Hauptgericht, Beilagen und Knabbereien?

Erfahrung und Zusammenarbeit während der Sprintplanung. Haltet das Gleichgewicht! Wenn Du Dich nur auf das Hauptgericht konzentrierst, wird es vermutlich langweilig, also würze es etwas nach! Wenn Du nur von den Knabbereien naschst, ist das auch ungesund!

 

The post Die Produkt-Speisekarte (Product Menu) – Eine neue Art das Product Backlog zu strukturieren appeared first on codecentric Blog.

Was haben Duplo, Batman und die Reihe 0 gemeinsam?

0
0

Eine ganze Menge, denn diese drei Gruppen von Masterstudenten der Fachhochschule Aachen (Campus Jülich) im Studiengang Technomathematik wollten wissen, wie es sich anfühlt, ein Projekt mit agilen Praktiken und einem modernen Technologie-Stack umzusetzen.

In einem 4tägigen Vorlauf von Seiten der Fachhochschule, der von Prof. Dr. Bodo Kraft und einem Doktoranden Marc Schreiber durchgeführt wurde, wurden die Basics zu Scrum, verteilter Versionsverwaltung, Build-Management, Release-Management, Continuous Integration und SOLID-Prinzipien gelegt.

Im anschließenden 3tägigen Praxisteil haben zwei Kollegen von uns (Michael Lex und Benedikt Ritter) diese Grundlagen vertieft und um die Themen TDD/ATDD, Softwarequalität und Softwaremetriken ergänzt. Das Ganze mittels einer konkreten Anwendung, die über fünf Sprints in den drei Gruppen gebaut werden musste.

Die Kollegen schlüpften dabei wechselseitig in die Rollen von Product Owner und Scrum Master und unterstützten die Teams bei (tool)technischen Fragen.

Fühlten sich die ersten Sprints noch etwas sperrig an, haben alle Teams im Laufe der Sprints Geschwindigkeit aufgenommen und es wurden eine Menge User Stories umgesetzt.

Gewonnen haben alle. Die Studenten können jetzt Begriffe wie Sprint Planning I + II, Backlog Grooming/Refinement, Daily Scrum, Retrospektive, Review, Task Board, Velocity und Story Points mit Leben füllen und konnten Teamskills verbessern. Die FH erfährt eine Aufwertung des Studiengangs und ein Alleinstellungsmerkmal im Wettbewerb mit anderen Hochschulen und uns von codecentric hat es nun bereits zum dritten Mal einfach nur Spaß gemacht.

Anklicken um

The post Was haben Duplo, Batman und die Reihe 0 gemeinsam? appeared first on codecentric Blog.

Microservices – Strategie vor Taktik

0
0

Dynamisch, unvorhersehbar, schwer planbar, volatil, irrational – das sind nur einige Merkmale der heutigen Geschäftswelt. Und dann gibt es noch Software, die dabei helfen soll, diese Probleme zu vereinfachen. Doch sie bringt noch zusätzliche Effekte mit, wie die rasante Geschwindigkeit neuer technischer Entwicklungen, neue Endgeräte oder eine immer stärkere Vernetzung von Systemen.

Es funktioniert oftmals nicht mehr, ein bestimmtes Ziel mithilfe eines präzisen oder umfassenden Plans und Vorgehens erreichen zu wollen. Viel wichtiger ist die Eigenschaft, sehr schnell auf die sich ebenso sehr schnell ändernden Einflüsse reagieren zu können – die Organisation, die Methodik und die Softwaresysteme müssen wendig und wandelbar sein. Schnelles Feedback ist nötig, um einzelne Subsysteme bei bestehendem Bedarf optimieren oder ersetzen zu können.


“Deshalb sind wir ja agil!”

Eine Änderung einer für das Umfeld geeigneten Methodik kann helfen, doch in der Produktentwicklung führt sie fast zwangsläufig auch zu organisatorischen, architektonischen und technischen Änderungen. Dadurch bedingt ändern sich auch die Strategien der Umsetzung, denn viele bestehende Strukturen sind nicht mehr kompatibel.

Um maximale Anpassbarkeit erreichen zu können, strebt man mit strategischer Softwareentwicklung zu einem Design, das Charakteristiken und Vorteile von jederzeit wandelbaren Systemen zeigt. Ignoriert man dies, befindet man sich oft genug wieder im alten Sumpf der Abhängigkeiten und Seiteneffekte.

Agile Softwareentwicklung fördert die Entwicklung kleinerer Einheiten, weil sie die schnelle und kontinuierliche Auslieferung funktionsfähiger und wertvoller Produkte bevorzugt.
Doch das allein reicht nicht aus. Die Architektur und Implementierung muss diese Methodik auch sinnvoll und effektiv unterstützen. Der Schlüssel dazu ist, die Methodik und entstehende System so „geschmeidig“ wie möglich zu halten.

Strategie ohne Taktik ist der langsamste Weg zum Sieg. Taktik ohne Strategie ist das Geräusch vor der Niederlage. – Sun Tzu

Zu oft wird dabei die Strategie außer Acht gelassen oder vernachlässigt und man fokussiert sich auf die Taktik. Doch „wie“ und „warum“ sind entscheidender als das „Was“. Von daher ist es wichtig, sich frühzeitig über die Strategie und deren mögliche Auswirkungen Gedanken zu machen und diese so schnell wie möglich mit der Taktik zu verifizieren. Eine taktische Maßnahme mag Teilprobleme schnell lösen und ist leicht änderbar, aber die Änderung einer Strategie ist mit hohen Kosten und Schmerzen verbunden.


Am Anfang wissen wir am wenigsten über unser Produkt

In der agilen Softwareentwicklung versucht man die Ungewissheit in der Planung, die Vielzahl an “Anforderungen” und möglichen Lösungen mit anderen Ansätzen zu lösen als im traditionellen Projektmanagement. Beispielsweise ist man bemüht, Anforderungen in Epics und User Storys aufzuteilen, um die Komplexität jeder einzelnen Anforderung zu verkleinern, Prozesse zu verbessern und Abhängigkeiten zu minimieren.

Dazu gibt es auch einige sehr gute begleitende Ansätze, bspw. Impact Mapping, um die wichtigsten oder wertvollsten Anforderungen zu ermitteln und einen Hinweis auf einen möglichen Lösungsweg zu bekommen. Arbeitet man mit mehreren Teams an einem großen System, können Teams zudem nach Fachgebieten und Kontexten, fachlicher und organisatorischer Art, aufgeteilt werden.

Dabei geht man inkrementell und iterativ vor, d. h. man beginnt mit den Anforderungen, die den größten Nutzen (welcher Art auch immer) bringen, und baut sie schrittweise auf. Sollte sich die Richtung als nicht zweckdienlich erweisen, kann man sie aufgrund der geringen Größe leicht korrigieren. Oft entdeckt man auf dieser Tour auch, dass sich manche Annahmen als falsch erwiesen haben und daher bestimmte Anforderungen nicht mehr benötigt werden oder modifiziert werden müssen.

Monolithische Systeme und Planungen besitzen nicht die nötige Anpassungsfähigkeit, um schnelle und kontinuierliche Änderungen durchführen zu können, sie sind oftmals schlicht zu träge. Zumal sie meist auch in so genannten Silos gebaut werden und die organisatorische Struktur eine Anpassung nicht oder nur schwer zulässt.

Daraus lässt sich unschwer erkennen, dass schnell und leicht anpassbare Software nicht mehr mit herkömmlichen, allumfassenden Ansätzen designt werden kann, um diese zwingend notwendige Fähigkeit zu erreichen und mit den sich ständig ändernden Anforderungen mithalten zu können.


Am Anfang wissen wir am wenigsten über unsere Software

Wenn es in der Produktentwicklung vor allem darum geht, schnell auf Änderungen und neue Erkenntnisse reagieren zu können, dann in der Software erst recht. Das gelingt mit kleineren Einheiten wesentlich  einfacher und sicherer als mit einem großen System, das sehr viele Abhängigkeiten besitzt. Zudem können sie auch schneller in Produktion gehen und einen Mehrwert erzeugen.

Eric Evans stellte 2003 in seinem Opus Magnum “Domain-Driven Design [DDD]. Tackling Complexity in the Heart of Software” einen strategischen Ansatz dar, der darauf abzielt, Komplexität in Softwaresystemen besser in den Griff zu bekommen.

Neben den eher taktischen Patterns im ersten sind es vor allem die im zweiten Teil vorgestellten strategischen Ansätze für Architekturen und Systeme, die dabei helfen. Zusätzlich zur wichtigsten Vorbedingung, dass Software die Sprache und Struktur der Domäne spiegeln sollte (Ubiquitous Language), ist hier besonders das Prinzip Bounded Context hervorzuheben.

Analog zur agilen Produktentwicklung versucht man in DDD kleinere Einheiten aus den großen Blöcken herauszuschneiden, die spezialisiert sind und eine Einheit im Sinne der Datenintegrität darstellen, um damit die Komplexität der einzelnen Komponenten zu verringern.

Wie beim ständigen Verfeinern der Anforderungen im Produktmanagement werden Modelle und Kontexte in DDD ständig hinterfragt und verfeinert, umgebaut oder sogar entfernt. Dies ist ebenfalls ein kontinuierlicher Prozess. Evans nennt dies „Model Exploration Whirlpool“:

Model Exploration Whirlpool

Abb. 1: Model Exploration Whirlpool (PDF)

Anpassbarkeit aller Instrumente und Artefakte ist auch hier die Grundbedingung, um eine Produkt- und Softwareentwicklung erfolgreich zu gestalten.

 

Aufteilung und Verkleinerung

Je spezifischer Produkte und Anforderungen definiert werden, desto spezifischer muss die Software dies reflektieren. Agilität mit monolithischen Systemen wird zumindest stark erschwert, in vielen Fällen ad absurdum geführt. Das heißt nicht, dass nun Hunderte von eigenständigen Produkten entwickelt werden müssen. Sie können als Einheit nach außen durchaus monolithisch als ein Produkt erscheinen, doch im Kern bestehen sie aus kleinen, autonomen und anpassbaren Einheiten.

Für die Erstellung und Verwaltung von Anforderungen gibt es Instrumente (z. B. Hierarchical Backlog oder Story Maps), die eine ähnliche Zielsetzung auf fachlicher Ebene besitzen. Diese teilen ein System nach den fachlichen Bereichen auf und innerhalb dieser dann auf die entsprechenden Storys des jeweiligen Bereiches. So gewinnt man nicht nur einen Gesamtüberblick über das Produkt, sondern kann durch die Aufteilung auch einzelne Anforderungen und Fortschritte in Relation zum gesamten Produkt verfolgen. Dies können sehr mächtige Werkzeuge zur interdisziplinären Abstimmung sein, doch bei zu großen Produkten bzw. Kontexten kann es unübersichtlich werden. Von daher profitieren auch diese Werkzeuge von der Verkleinerung der Komponenten.

Story Map

Abb. 2: Beispielhafte Story Map eines E-Commerce Systems

Betrachtet man nun den eigentlichen Code und die technische Implementierung, geht es in den meisten Fällen um Modularisierung, Trennung von Belangen und Minimierung von Abhängigkeiten. Im Zuge von TDD, hoher Automatisierung, Continuous Delivery und heterogenen und verteilten Systemen und Teams ist man dazu fast schon gezwungen.

Doch dieser “Zwang” führt in den meisten Fällen zu langfristig besser wartbarer Software, da die Komponenten kleiner, autonomer und dadurch weniger anfällig sind. Sie können zudem iterativ und inkrementell erstellt werden, ganz im Sinne von Agilität.

Selbstredend hat das auch seinen Preis, wie z. B. ein erhöhter Test-, Monitoring- und Koordinationsaufwand, aber der Nutzen überwiegt diese Kosten in den meisten Systemen bei Weitem. Zumal in der heutigen Zeit Systeme nicht mehr nur auf einem Device oder einer Plattform laufen und häufig verteilt sind oder in der Cloud bereitgestellt werden.

 

Context Is King

Bestimmte Fachkonzepte und -Begriffe ergeben nur in bestimmten Fachgebieten einen Sinn und können in anderen Kontexten eine völlig andere Bedeutung haben. Ohne einen klar abgegrenzten Kontext sind Begriffe und Konzepte eines Geschäftsmodells missverständlich und führen langfristig zu Problemen. Deswegen ist ebenfalls keine vernünftige Modellierung, z. B. eines Domain Models, möglich.

Im Zusammenhang mit den Diskussionen um Modularisierung von Software (z. B. mit Microservices) ist DDD (und vor allem eines seiner zentralen Konzepte Bounded Context) aktueller denn je.

Bounded Context Map

Abb. 3: “Karte” eines E-Commerce-Systems mit Domänen und Kontexten

Innerhalb dieser Domänen des Problemraums existieren ein oder mehrere Kontexte, die ebenfalls klar abgegrenzt sind und ein Domain Model kapseln – hier allerdings im Lösungsraum, d.h. vor allem der technischen Implementierung (hin und wieder auch der organisatorischen Umsetzung). Das Zusammenspiel und die Abhängigkeiten der Komponenten werden in einer Context Map visualisiert. Hierbei werden nicht nur technische, sondern auch organisatorische Abhängigkeiten transparent gemacht.

Context Map

Abb. 4: Context Map eines E-Commerce-Systems mit den Beziehungen zwischen Kontexten und möglichen Schnittstellen

Das Prinzip des Bounded Context ist sehr geeignet, um die Korrelation eines Services und seiner Kontextgrenzen darzustellen. Eine autonome Komponente (bzw. ein Microservice) ist dann die konkrete Implementierung eines Bounded Context.

Arbeitet nur ein Team an allen Kontexten, ist dies wenig problematisch. Sobald allerdings die Kontexte auf einzelne Teams verteilt werden, kommt man nicht umhin, die Beziehungen und Abhängigkeiten untereinander möglichst frühzeitig klar zu definieren.
Wichtig dabei ist: Die Context Map bildet in erster Linie die organisatorischen Abhängigkeiten ab. Denn die entscheidet, wie die Arbeit an einzelnen Kontexten strukturiert sein muss, damit es langfristig nicht zu Problemen kommt. Zusätzlich kann sie visualisieren, wie die Art der Zusammenarbeit geregelt ist und welche (technischen) Schnittstellen dafür bereit gestellt werden, wenn nötig.

Zwei Beispiele für Beziehungen und Abhängigkeiten:

Beziehung Abhängigkeiten (organisatorisch)
Relationship Beide Teams arbeiten eng zusammen und stimmen Änderungen und deren Auswirkungen stets gemeinsam ab.
Conformist Der Kontext im Downstream ist passiv und muss auf Änderungen des Modells im Upstream-Kontext reagieren. Das kann z. B. auch bedeuten, dass der Downstream-Kontext auch Daten empfängt, die er nicht benötigt. Eine Steigerung davon ist, wenn der Kontext im Upstream oft seine Schnittstelen oder Strukturen ändert. Dann benötigt man in der Regel einen Layer, der dafür sorgt, dass der eigene (Downstream-)Kontext dadurch nicht kompromittiert wird. Oft wird das mit einem sog. Anti-corruption Layer (ACL) gelöst.

Prominentes Beispiel für Conformist aus vielen IT-Abteilungen sind DBAdmins alter Schule, die das Schema einer Datenbank ohne Vorwarnung ändern können. Oder Fremdsysteme, aus denen man Daten abgreift, bei denen man aber nicht mal weiß, wer dafür zuständig ist und – selbst wenn man es wüsste – keinerlei Einfluss darauf hat. Beispiele für Abteilungen, die ohne Absprache Schnittstellen und Systeme ändern, gibt es in der Praxis leider zuhauf.

Diese Beispiele zeigen, wie wichtig es ist, sich auf strategischer Ebene über die Beziehungen von Kontexten im Klaren zu sein. Ansonsten zahlt man einen hohen Preis.
Dies gilt umso mehr in Systemen, die relativ autonom sind und von verschiedenen Teams gebaut werden.
In einem Folgeartikel werde ich detaillierter auf den Nutzen einer Context Map eingehen.

 

Skalierung – in alle Richtungen

Ein in den letzten Jahren häufig diskutiertes Thema ist die Skalierung agiler Methoden und insbesondere Scrum, vor allem in größeren Unternehmen. Es gibt eine Reihe von mehr oder weniger bekannten und auch erfolgreichen Lösungsansätzen.

Conway’s Law besagt, dass ein Softwaresystem die Kommunikationsstruktur seiner Organisation kopiert. Das führt bei vielen Softwaresystemen großer Organisationen zu “interessanten” Strukturen. Die Fortsetzung dieser Aussage, in der Conway vorschlägt, wie man dies vermeiden kann, wird oft nicht mitzitiert: “[…] Therefore, flexibility of organization is important to effective design.

Man kann das Ganze auch umkehren: die Organisation passt sich einem flexiblen, modularen und aus vielen kleinen, autonomen und spezialisierten Komponenten bestehenden System an. Die Beziehungen untereinander, technischer und organisatorischer Art, können in einer Art Karte (z. B. einer Context Map) visualisiert und transparent gemacht werden.

Die Karte aller Kontexte und Komponenten und ihrer Beziehungen untereinander ist die Visualisierung der Kommunikation der Organisation.

Von daher ist in vielen Fällen nicht die Skalierung nach oben oder zu mehr die beste Lösung, sondern hin zur Verkleinerung der Anforderungen und Komponenten des Problem- und Lösungsraums. Also der Aufteilung eines nicht in voller Gänze überblickbaren Systems in kleinere und damit besser einschätzbare und beherrschbare Anforderungen, Einheiten, Belange oder Subsysteme. Selbst das bekannte Beispiel von Spotify kann man eher als Aufteilung in kleinere, spezialisierte Einheiten denn als Skalierung nach oben sehen.

Es ist effizienter, eine Komponente genau einem Team zuzuordnen, statt eine große Anwendung auf mehrere Teams aufzuteilen. Allein der Aufwand der Kommunikation und der Abstimmung bezüglich der Anforderungen und Abhängigkeiten ist enorm im Gegensatz zu den wesentlich unabhängigeren, kleineren Komponenten. Natürlich kann ein Team für mehr als eine Komponente verantwortlich sein, aber eine Komponente sollte nur von einem Team gebaut (und betrieben) werden.

 

Also Microservices?

Modularität von Softwaresystemen ist nicht revolutionär neu – viele Konzepte von Microservices und anderen Ansätzen, die die Entwicklung (in jede Richtung!) skalierbarer Software fördern, sind schon sehr alt. Das macht nichts, es ist trotz des Wirbels um den Begriff eine “natürliche” und fast logische Evolution der agilen Produkt- und der strategischen Softwareentwicklung.
Vielleicht unter neuem Namen, aber mit den gleichen Prinzipien.

Warum überhaupt ein neuer Name her musste und es nicht einfach (Autonomous) Component heißt, ist fragwürdig. Schon 1996 definierte die European Conference on Object-Oriented Programming (ECOOP) eine Komponente als: „A software component is a unit of composition with contractually specified interfaces and explicit context dependencies only. A software component can be deployed independently and is subject to composition by third parties.“
Das passt ziemlich genau auf die gängigen Definitionen von Microservices …

Zumindest ist es sehr empfehlenswert für Systeme, die häufigen Änderungen ausgesetzt sind, also maximale Anpassbarkeit in jegliche Richtung haben müssen. Systemen, die sich nur langsam oder selten ändern, schadet es in den meisten Fällen dennoch nicht, im Gegenteil.

Vor allem besitzen wir heutzutage endlich viele neue technische Möglichkeiten, die das Ganze stark vereinfachen oder vieles erst möglich machen, z. B. sind wir in der Lage, unveränderliche Infrastruktur einzusetzen, Server und Plattformen nach Bedarf zu skalieren und ein- und ausschalten zu können, ja ganze Schichten von Technologien in kürzester Zeit zu ersetzen.

Micro-, Nano-, Pico- oder Macroservices?
Die zahlreichen Diskussionen um den Namen und vor allem die ideale Größe von Microservices (ich benutze hier den gebräuchlichsten Namen) sind Zeitverschwendung. Die Größe des Kontexts (bzw. Bounded Context) gibt die Größe eines Microservices vor, und die kann sich auch ändern (ein anderer interessanter und lesenswerter Vorschlag ist: Es muss in deinen Kopf passen…). Dabei sollte möglichst die Regel 1 Bounded Context = 1 Team = mindestens 1 Model/Aggregat gelten. Ein empfehlenswerter Ansatz ist, dass man (zu) groß anfängt und dann weiter aufteilt und verkleinert.

Dass dieser Ansatz fast zwangsläufig zu einer Änderung der Organisation führt, liegt nahe. Dass er eben aus diesem Grund häufig nicht oder nur bis zu einem bestimmten Punkt gegangen wird, leider auch.

Zu guter Letzt noch ein Vergleich der Charakteristiken von Microservices mit Konzepten und Ideen der agilen Produktentwicklung (angereichert mit Konzepten aus DDD und anderen Bereichen). Es soll beispielhaft darstellen, welche Konzepte korrelieren und/oder hilfreich für die Umsetzung sein können und ist keinesfalls von wissenschaftlicher Präzision, sondern ein reiner Ideenpool zur weiteren Beschäftigung mit diesem Thema. Hierbei liegen die Kriterien aus dem bekannten Artikel des Chefwissenschaftlers von Thoughtworks, Martin Fowler, über Microservices als Grundlage vor:

Microservices Agile Produktentwicklung (+ DDD et al.)
Componentization via services Bounded Context
Autonomous Component
1 Context = 1 Team = 1 Component
Domain Eventing
Organized around business capabilities Domain Knowledge
Ubiquituous Language
Backlog & Model Grooming
Product Owner / Stakeholder
Story Map / Product Vision Board
Products not projects Product Backlog
Product Roadmap
Product Team
Domain Language
Feature Iterations
Smart endpoints and dumb pipes Clean Architecture
Ports & Adapters
Context Map
(RESTful) APIs
Decentralized governance Context Mapping
Use Cases + Coordination Context
1 Transaction = 1 Aggregate
Cloud, Docker, Virtualization
Event-driven architecture, CQRS
Infrastructure automation Separate deployment
Continuous Delivery
DevOps
“You build it, you run it”
Phoenix Server & Immutable Environment
Design for failure
Evolutionary design
Adaptability
Fail fast
Inspect & Adapt
Chaos Monkeys
Adaptable (Clean, Hexagonal) Architecture
“Fake it ’til you make it”

 

The post Microservices – Strategie vor Taktik appeared first on codecentric Blog.


The Need for Speed – eine Geschichte über DevOps, Microservices, Continuous Delivery und Cloud-Computing

0
0

Derzeit sind drei Megatrends auszumachen, die die IT zu einem dramatischen Wechsel zwingen:

  • Wirtschaftsdarwinismus
  • Digitalisierung
  • Disruptive Technologien

Alle drei sind außerordentlich wichtig und Firmen, die diese Trends ignorieren, werden früher oder später hinter ihren Konkurrenten zurückbleiben (zumindest solange diese Firmen in einem effizienten Markt leben – nichteffiziente Märkte sind eine andere Geschichte).

Märkte im Wandel

Kommen wir zum ersten Trend und beginnen mit einer kurzen Erläuterung: „Darwinismus“ bedeutet kurz gesagt „Nur die Besten überleben“ (Survival of the fittest) – der „Beste“ ist nicht der Stärkste, sondern derjenige, der sich schnell genug an die Anforderungen der Umgebung anpassen kann.

Wirtschaftsdarwinismus, Digitalisierung und disruptive Technologien setzen die IT unter Druck

Mega-Trends setzen die IT unter Druck und erzwingen damit einen fundamentalen Wandel

Das gilt auch für Firmen: Nur die Firmen, die sich schnell genug an die sich ändernden Ansprüche und Forderungen ihrer Kunden anpassen können, werden auf lange (oder auch nur kurze) Sicht überleben.

Nun könnten Sie fragen: „Hey, was ist daran neu? Das ist doch schon immer so in der Wirtschaft.“ Und Sie hätten recht.

Entscheidend ist, dass die Märkte, in denen die meisten Firmen agieren, vor einigen Jahren einen drastischen Wandel erfahren haben. Fast ein Jahrhundert lang waren die meisten Märkte weit, nahezu unbeschränkt und träge: Die Nachfrage war immer größer als das Angebot und die größte Herausforderung für die Firmen in diesen Märkten war es, die Produktion ihrer Waren möglichst kosteneffizient zu skalieren.

Das änderte sich in den 1980ern: Langsam aber sicher wurde ein Markt nach dem anderen immer enger. Globalisierung, Marktsättigung und das Internet haben zu stark konkurrierenden und dynamischen Märkten geführt. (Tatsächlich gab es noch mehr Triebkräfte für den Wandel im Markt, doch das würde den Rahmen dieses Blogposts sprengen.) Die Märkte wurden stark bedarfsgesteuert und die neue größte Herausforderung der Firmen war, sich an die veränderten Bedürfnisse der Kunden schnell genug anzupassen. Die entscheidende Triebkraft wechselte von „kosteneffiziente Skalierung“ zu „Reaktionsfähigkeit“.

Veränderte Rolle der IT

Nun könnten Sie sagen: „Gut, das weiß ich alles. Doch was hat das mit IT zu tun?“

Die Sache ist die, dass Business und IT heutzutage eng miteinander verflochten sind. Man kann sie nicht mehr unabhängig voneinander betrachten, selbst wenn alles ganz anders begann:

Wenn Sie an die Beziehung zwischen Business und IT in den 1950ern und 1960ern denken, unterstützte die IT winzige Bruchteile der Geschäftsfunktionen – einen kleinen Bericht hier, eine kleine Analyse da. Das geschah ziemlich selektiv, da die damalige Rechenleistung der Mainframes recht beschränkt war (jedes moderne Mobiltelefon hat um Größenordnungen mehr Rechenleistung als die Computer in den 1960ern).

Dennoch war es für die Geschäftsabteilungen ziemlich angenehm, die Berichte und Analysen nicht mehr per Hand erstellen zu müssen. Und sie wollten mehr davon haben – was schließlich zur Softwarekrise führte: Der Bedarf an Software überstieg deutlich das Angebot.

Die Reaktion auf die Softwarekrise war die Erfindung des Software-Engineering: eine Ingenieurs-Disziplin mit dem Ziel, die Produktion von Software kosteneffizient zu skalieren. Klingt vertraut, nicht wahr? Die Industrialisierung stand Anfang des 20ten Jahrhunderts vor einem sehr ähnlichen Problem und der damalige Lösungsansatz war Taylorismus in seinen verschiedenen Varianten. Es dürfte also nicht überraschen, dass das Software-Engineering viele tayloristische Konzepte und Praktiken aufgegriffen und auf die Softwareentwicklung übertragen hat.

Und damals war das auch vollkommen in Ordnung. Doch mit der Zeit hat sich die Rolle der IT essentiell gewandelt. In den 1980ern begann der Siegeszug des PC und Netzwerke gewannen an Bedeutung. Das mooresche Gesetz wirkte weiter, Computer wurden immer leistungsfähiger. Letztlich unterstützte die IT nicht mehr nur ausgewählte Geschäftsfunktionen, sondern zunehmend die Geschäftsprozesse im Ganzen. Sie rutschte immer näher an das Business mit all seiner Komplexität.

Diese Entwicklung ging weiter, das Internet begann seinen Siegeszug in den 1990ern und mittlerweile ist die IT das Nervensystem jeder nichttrivialen Firma: Das gesamte Businesswissen ist in IT codiert, IT-Ausfälle von nur wenigen Stunden gelten für viele Firmen als existenzbedrohend. Das bedeutet aber auch, dass Sie Ihr Business nicht mehr ändern können – weder neue Produkte noch neue Features oder Prozessänderungen –, ohne die IT anzufassen.

The Need for Speed

Wenn man die oben beschriebenen Beobachtungen zusammenfasst, zeigt sich, dass die IT zu einem entscheidenden Erfolgsfaktor für jede Firma geworden ist, um im Wirtschaftsdarwinismus bestehen zu können. Egal wie gut Sie die die Bedürfnisse und Forderungen Ihrer Kunden kennen, wenn Ihre IT nicht schnell genug liefern kann, werden Sie das Nachsehen haben.

Dennoch klammern sich die meisten Firmen an die Ideen des herkömmlichen Software-Engineering, wo es das primäre Ziel ist, die Produktion von Software kosteneffizient zu skalieren. Das Problem ist, dass Skalierung der Softwareproduktion und IT-Kosteneffizienz nicht mehr die entscheidendsten Treiber sind (tatsächlich schreiben wir heutzutage viel zu viel Software, es dauert nur zu lange und sehr oft ergibt sich nur ein Bruchteil des erwarteten Mehrwerts).

Der entscheidendste Treiber für die heutige IT sind Durchlaufzeiten – wie lange es dauert, um eine Idee zum Kunden zu bringen. Es geht also um die erforderliche Zeit, um die gesamte IT-Wertschöpfungskette zu durchlaufen, beginnend beim Aufnehmen der Anforderungen bis die Lösung live in der Produktion läuft.

Je kürzer diese Durchlaufzeiten sind, desto besser kann man sich an den sich ständig verändernden Bedarf der hochdynamischen Märkte anpassen. Wir brauchen „Speed“!

Verschärfte Bedingungen

Wenn wir uns den zweiten Megatrend – die Digitalisierung – ansehen, wird es noch etwas schwieriger: Mit Digitalisierung wird die IT das Business nicht mehr nur unterstützen, sondern selbst zum Business. Wir erzeugen neue Businessprodukte, die teilweise oder gänzlich aus IT bestehen. Auf der einen Seite verlangt das nach Innovation, da wir gerade erst verschwommene Ideen entwickeln, welche zukünftigen Geschäftsmodelle Trends wie z.B. das Internet der Dinge (Internet of Things, IoT) ermöglichen werden. Andererseits bedeutet das, dass unsere IT noch robuster als zuvor sein muss, da auch unsere Kunden ständig damit in Berührung kommen.

Der dritte Megatrend sind disruptive Technologien wie Cloud-Computing, Mobile Services, Big Data und IoT. Diese Technologien eröffnen ganz andere Möglichkeiten, um Geschäftsanforderungen umzusetzen, bis hin zu einer Ebene, wo sie vollständig neuartige Geschäftsmodelle ermöglichen. Da wir aber gerade erst begreifen, was wir mit diesen Technologien anstellen können, verschärfen sie die oben beschriebenen Bedingungen:

Wir müssen verstehen und lernen, wir müssen Innovation treiben, wir müssen uns an die sich ständig ändernden Märkte anpassen und das Ganze muss schnell geschehen, wobei wir die neuen Möglichkeiten disruptiver Technologien berücksichtigen müssen. Und wir werden nicht erfolgreich sein, wenn wir uns dabei an Ideen klammern, die vor vielen Jahren in einer gänzlich anderen Geschäfts- und IT-Ära entwickelt wurden.

Wir müssen IT neu denken!

IT neu denken

Dies war die Kurzversion der langen Geschichte, warum Durchlaufzeiten zum wichtigsten Treiber für die heutige IT geworden ist, warum wir IT neu denken müssen.

Doch wenn das alte Wissen, auf der wir immer noch die meisten unserer IT-Prozesse und Arbeitsabläufe aufbauen, nicht geeignet ist, um IT neu zu denken, was ist es dann?

DevOps, Continuous Delivery, Microservices und Cloud-basierte Infrastruktur als Kernbausteine einer neuen IT

Kernbausteine einer neuen IT, die auf die veränderten Treiber reagiert

Das ist eine durchaus berechtigte Frage. Ein guter Ausgangspunkt sind üblicherweise Organisation und Prozesse: Die Organisations- und die Systemtheorie haben gezeigt, dass sich dezentrale Organisationen mit vorwiegend autonomen Einheiten viel besser und deutlich schneller an die sich ständig ändernden Anforderungen anpassen können als hierarchische Organisationen, die auf tayloristischen Prinzipien beruhen. Außerdem sind dezentrale Organisationen typischerweise ein ganzes Stück robuster gegenüber unerwarteten Anforderungen.

Wenn wir uns einen Moment lang umsehen, werden wir schnell erkennen, dass es bei DevOps genau um diesen Punkt geht: Wir schaffen vorwiegend autonome Teams, die ganzheitlich Verantwortung für dedizierte Geschäftsfähigkeiten übernehmen und wir lassen sie auf alle Änderungsforderungen in ihrem Kompetenzbereich reagieren. Tatsächlich gehört noch viel mehr zu DevOps, wie zum Beispiel ganzheitliche Optimierung von Durchlaufzeiten, Verstärkung von Feedback-Schleifen, um gegebenenfalls erforderliches Wissen bereitzustellen, und eine Kultur des beständigen Lernens. Dies sind ebenfalls sehr wichtige Eigenschaften von DevOps, die genau in die Richtung der Treiber zeigen, die wir oben gesehen haben.

Doch DevOps allein genügt nicht. Wir müssen sicherstellen, dass Teamautonomie nicht durch Software und Systeme verletzt wird, an denen die Teams arbeiten. Wenn zum Beispiel viele Teams an einem großen Monolithen arbeiten (der leider die Tendenz hat, im Laufe der Zeit zu einem großen Brocken Spaghetticode zu verkommen), werden die Teams normalerweise jede einzelne Änderung koordinieren müssen und die Autonomie ist dahin. Wenn wir Software nur in großen Alles-oder-nichts-Releases freigeben können, wenn wir mit einem Laufzeit-Megamonolithen konfrontiert sind, müssen die Teams sämtliche Änderungen koordinieren und sich auf ein Release-Datum verständigen – und die Autonomie ist ebenfalls dahin.

Wir brauchen also ein Architekturmuster, das Autonomie in Bezug auf Entwicklung und Bereitstellung von Software unterstützt. Microservices sind ein solches Architekturmuster. Deren Eigenständigkeit („Self-containment“) und problemlose Erfassbarkeit für den Einzelnen („Fits in one brain“) unterstützen Teamautonomie sehr gut. Zudem erlauben sie schnelle Änderungen und kurze Durchlaufzeiten. Faktisch sind Microservices nicht der einzige Stil, der den gegebenen Anforderungen entspricht, und zugegebenermaßen ist es recht einfach, sie zu verpfuschen und am Ende vor einem großen verteilten Kuddelmuddel zu stehen (auch bekannt als „verteilte Hölle“). Doch richtig gemacht unterstützen sie Autonomie und schnelle Anpassung sehr schön.

Autonome DevOps-Teams, die auf die sich ständig ändernden Forderungen reagieren, müssen ihre Software auch freigeben („deployen“) können, wann immer sie wollen. Und wenn sie ihre Software mehrmals pro Tag freigeben müssen, darf das keine Probleme machen. Software unabhängig nach Bedarf freizugeben verlangt normalerweise nach Automatisierung. Manuelles Erstellen, Testen und Freigeben ist viel zu teuer und fehleranfällig, wenn dies häufig geschehen muss. Dies ist die Domäne von Continuous Delivery, das ja gerade das konsequente Automatisieren von Erstellen, Testen und Freigeben von Software zum Ziel hat.

Der letzte Teil des Bilds ist die Infrastruktur, auf der die Software läuft, da sie ebenfalls sehr flexibel sein muss. Wenn es Tage, Wochen oder Monate dauert, um die erforderliche Infrastruktur für Entwicklung, Bauen, Testen oder Freigeben zu bekommen, ist jede Teamautonomie und Reaktionsfähigkeit dahin. Die Teams müssen in eigener Regie auf die erforderliche Infrastruktur zugreifen können („Self-service access“), wann immer sie sie brauchen, und sie müssen sie auch wieder abstoßen können, sobald sie sie nicht mehr brauchen. Genau diesen Anforderungen entspricht das Cloud-Bereitstellungsmodell. Darüber hinaus ermöglicht der API-basierte Ansatz elastisches Skalieren zur Laufzeit sowie bessere Automatisierung des Build-, Test- und Freigabeprozesses.

Weitere Überlegungen

Wir haben gesehen, dass DevOps, Microservices, Continuous Delivery und Cloud-Computing Kernbausteine einer neuen IT sind. Doch reicht das aus für unsere neue IT oder fehlen noch irgendwelche Teile?

Beyond Budgeting, EAM neu gedacht und Craftsmanship als ergänzende Bausteine

Bausteine einer neuen IT, einschließlich unterstützender Elemente

Tatsächlich fehlen einige unterstützende Teile. Das erste ist das Unternehmensarchitektur-Management (Enterprise Architecture Management, EAM), doch nicht auf die Weise, wie man es kennt. Es hat keinen Mehrwert, Unmengen von Bildern im Voraus zu zeichnen und zentral zu versuchen, die gesamte IT-Landschaft im Detail unmittelbar vom Elfenbeinturm aus vorauszuplanen und zu beschreiben. Dies wird standardmäßig ein Flaschenhals sein und ist ziemlich genau das Gegenteil von dem, worum es bei DevOps geht.

Dennoch gibt es einige wichtige Aufgaben in einer DevOps-Organisation, wo EAM eine Menge Mehrwert erzeugen kann: Selbst wenn die Teams möglichst unabhängig und autonom sein sollten, müssen sie trotzdem noch an einem gemeinsamen Ziel arbeiten. Hier bietet sich EAM an, um die gemeinsame Vision zu verbreiten und den Teams zu helfen, das große Ganze im Blick zu behalten.

Zweitens ist totale Teamautonomie nicht möglich, da alle Teams auf ein gemeinsames Ziel hin arbeiten und die von ihnen produzierte Software zusammenspielen muss. Das heißt Koordination. Die Teams müssen sich wechselseitig abstimmen, wie ihre Software mit der jeweils anderen zusammenarbeitet. Das kann recht umständlich und ineffizient sein, wenn über jede einzelne Interaktion von Grund auf neu abgestimmt werden muss. Deshalb ist es sinnvoll, sich über bestimmte Interaktionsstandards über alle Teams hinweg zu verständigen. Diese Minimalmenge an Standards zu ermitteln und zu pflegen, ist ebenfalls eine gute Aufgabe für EAM.

Eine andere Definition ist, dass sich EAM um das große Ganze und den Raum zwischen den Verantwortlichkeiten der Teams kümmert. Eine letzte Anmerkung zu EAM: Normalerweise ist dies kein Vollzeitjob und es sollte kein Elfenbeinturmteam damit betraut werden. Stattdessen sollte das EAM-Team aus Abgesandten der DevOps-Teams bestehen, die sich gelegentlich oder einfach nur bei konkretem Bedarf treffen.

Der zweite große unterstützende Baustein ist das Führungsmodell („Governance“). Wir müssen immer noch sicherstellen, dass die Visionen des Top-Level-Managements und die Arbeit der Teams zueinander passen. Wichtig ist Transparenz – was geschieht in den Teams? Und ebenso wichtig ist, dass das Top-Level-Management den Teams Richtungsänderungen effizient vermitteln kann.

Die herkömmlichen Führungsmodelle von hierarchischen Organisationen sind nicht geeignet für eine DevOps-Organisation, die Durchlaufzeiten und Marktreaktivität optimiert. Wir brauchen ein anderes Führungsmodell. Das gute daran ist, dass diese Führungsmodelle schon eine ganze Zeit bekannt sind und von diversen großen Firmen, die sie übernommen haben, erfolgreich in der Praxis erprobt wurden. Diese Modelle sind unter dem Namen „Beyond Budgeting“ oder „Beta-Kodex“ (eine Weiterentwicklung von „Beyond Budgeting“) bekannt.

Als dritter großer unterstützender Baustein ist Craftsmanship zu nennen. Ja, es geht um Menschen (leider vergessen wir das allzu oft)! Wir müssen allen beteiligten Personen helfen, ein verändertes Verständnis bzgl. der Art und Weise zu entwickeln, wie sie arbeiten.

Auf der einen Seite ist es wichtig, den Leuten dabei zu helfen, ständig die Art und Weise zu verbessern, wie sie ihre Arbeit erledigen und mit ihren Kollegen zusammenarbeiten. Man bezeichnet dies auch als „Meisterschaft“ („Mastery“). Eine Haltung der Art „alles wird sich finden“ ist nicht mehr ausreichend. Dies ist die Kernidee von Craftsmanship.

Auf der anderen Seite brauchen die Menschen kein stabiles Arbeitsmodell mehr, sondern vielmehr ein robustes Veränderungsmodell. Menschen brauchen Orientierung – einige Regeln und Ankerpunkte, an denen sie sich orientieren können. In der Vergangenheit wurde den Leuten ein Modell vorgesetzt, wie sie ihre Arbeit zu erledigen haben, entweder mithilfe detaillierter Prozessbeschreibungen oder über weniger förmliche Richtlinien.

Das Problem bei diesem Ansatz ist, dass sich die Arbeitsweise ständig ändert, da sich Firmen fortwährend an die sich ständig ändernden Bedürfnisse und Forderungen ihrer Kunden anpassen müssen. Demzufolge ist es nicht möglich, ein Arbeitsmodell bereitzustellen, das über einen längeren Zeitraum stabil ist. Deshalb ist es notwendig, den Leuten ein Modell an die Hand zu geben, mit dem sie leichter verstehen können, wann eine Veränderung notwendig ist und warum. Wir gehen also eine Stufe nach oben vom Bereitstellen eines stabilen Arbeitsmodells zum Bereitstellen eines robusten Veränderungsmodells. Selbst wenn dies kein zentraler Aspekt von Craftsmanship ist, gehört er zweifellos in diesen Bereich.

Fazit

Wir müssen IT neu denken – die Märkte, in denen die Firmen agieren, haben sich geändert, die IT selbst hat sich geändert. Die Rahmenbedingungen, unter denen unsere traditionellen IT-Modelle entwickelt wurden, existieren nicht mehr. Unser primärer IT-Treiber ist nicht mehr die kosteneffiziente Skalierung der Softwareproduktion, vielmehr geht es darum, die Durchlaufzeiten zu minimieren, um die Reaktionsgeschwindigkeit des Unternehmens zu maximieren.

Die Kernbausteine für diese neue IT sind DevOps, Microservices, Continuous Delivery und Cloud-Computing, die durch einige zusätzliche Elemente wie EAM (ebenfalls neu gedacht!), Beyond Budgeting und Craftsmanship ergänzt werden müssen.

Immerhin ist dies lediglich das „große Bild“, eine Draufsicht aus 10.000 Metern Höhe. Es gibt viele Schattierungen, die ich zugunsten der Kürze weggelassen habe – und dennoch ist es ziemlich lang geworden. Außerdem besteht jeder hier erwähnte Baustein aus vielen weiteren Details auf verschiedenen Ebenen. Und es sind jede Menge Details zu berücksichtigen, um nicht alles am Ende zu verpfuschen. Doch das sind andere Geschichten …

 

The post The Need for Speed – eine Geschichte über DevOps, Microservices, Continuous Delivery und Cloud-Computing appeared first on codecentric Blog.

Continuous Validation: Gestatten – Gareth

0
0

Hallo Welt! Darf ich vorstellen – Gareth. Er kann unausstehlich sein. Glauben Sie mir, ich weiß, wovon ich spreche. Nichtsdestotrotz macht er sich immer unentbehrlicher (s. Video).

Wenn Ihre Ideen unsinnig sind, wird er Sie klipp und klar und völlig nüchtern darauf hinweisen. Er wird es Ihnen nicht verschweigen, sondern Klartext mit Ihnen sprechen, wenn Ihre Annahmen verfehlt sind. Er wird stets überprüfen, ob Ihre Unternehmensziele erreicht werden. Sollte das nicht der Fall sein, wird er es Ihnen mitteilen.

Was genau macht Gareth?

Gareth validiert das Warum: den Grund, weshalb die jeweilige Funktionalität überhaupt erstellt wurde. Er sorgt dafür, dass die (Geschäfts-)Ziele erreicht werden und kümmert sich um die Validierung dieser Ziele. Wir alle wissen, dass die Umsetzung eines Features ein anderes beeinflussen kann. Das kann auch Auswirkungen auf den Erfolg der damit verbundenen Ziele haben.

Gareth liebt Unternehmensziele. Er will nicht über Funktionalität sprechen. Er will klare Annahmen wie: Die Leistung der Funktion x wird nach der Umsetzung dieser Veränderungen um 25 % besser; Feature y wird die Nutzung der Funktionalität z über die nächsten 3 Monate um mindestens 50 % verringern. Und so weiter.

Warum wir Gareth wirklich brauchen

Software Craftsmanship und Automatisierung werden allgemein groß geschrieben. Wir sind in der Lage, Software sehr schnell mit aktuellen Technologien zu erstellen und zu implementieren. Allerdings ist es die Post-Deployment-Phase, die uns Sorgen bereitet. Wie können wir sicher sein, dass neue Funktionalität auch tatsächlich die Auswirkungen hat, die das Unternehmen benötigt?

Wir sind der Überzeugung, dass User Stories und Backlogs zu detailliert und komplex sind, um bei der Kommunikation mit Stakeholdern hilfreich zu sein. Wir glauben, dass es sinnvoller ist, über Ziele und Annahmen zu sprechen: Welche Ziele steckt man sich und wie sollen diese überprüft werden?

Wir wissen, dass die Welt sich verändert. Auch unsere Software muss sich verändern. Noch ein Grund, weshalb wir Gareth brauchen: Man muss sich unbedingt darauf verlassen können, dass man seine Ziele auch dann erreicht, wenn man Teile der Software verändert hat. Manchmal macht ein Feature ein anderes überflüssig. Ist das der Fall, wird Gareth es Ihnen mitteilen.

gareth-logo-big

Wie Gareth funktioniert

Wir führen Validierung als zusätzliche Dimension einer User Story ein. Diese nennen wir Experiment. Ein Experiment hat den folgenden Aufbau:

Experiment: Name des Experiments
Baseline: Der ursprüngliche Zustand
Annahme: Welchen Effekt soll die Software erzielen?
Zeit: Welches Zeitintervall und welchen Zeitrahmen beansprucht das Experiment?
Maßnahme: Welche Maßnahme muss Gareth ergreifen, wenn ein Experiment gelingt/misslingt?

Im Folgenden beschreiben wir ein Beispiel:

Die Story:

Als Inhaber einer Hotel-Buchungsseite möchte ich,
dass meine Nutzer an erster Stelle alle vergünstigten Zimmerangebote
angezeigt bekommen,
damit diese zuerst gebucht werden.

Das Experiment könnte nun folgendermaßen aussehen:

Experiment: Ich möchte überprüfen, ob dadurch, dass die günstigeren Zimmer zuerst angezeigt werden, insgesamt mehr Zimmer gebucht werden.
Baseline: Ermittle die aktuelle Zahl der vergünstigten Zimmer, die pro Woche gebucht werden.
Annahme: Im ersten Monat wird die Zahl der Buchungen vergünstigter Zimmer jede Woche um 25 ansteigen.
Zeit: 1 Monat
Erfolgreich: Sende eine E-Mail mit Glückwünschen an das gesamte Team
Nicht erfolgreich: Sende eine E-Mail an den Product Owner.

Baseline: Ermittle die Zahl der vergünstigten Zimmer, die in den letzten 6 Monaten gebucht wurden.
Annahme: Nach 6 Monaten haben ist die Zahl der Buchungen vergünstigter Zimmer um 80 % angestiegen.
Zeit: 6 Monate
Erfolgreich: Versende eine Nachricht, in der daran erinnert wird, Kuchen für die Entwickler zu kaufen.
Maßnahme: Erstelle eine ausführliche Analyse im Backlog.


Gareth wird diese Validierungen so oft durchführen, wie es in den Annahmen beschrieben wird, und die angemessene Maßnahme ergreifen. Für Product Owner ist das eine großartige Möglichkeit zu gewährleisten, dass die richtigen Dinge erstellt und genauso bewahrt werden. Genau das möchten wir auch unseren Stakeholdern versichern können.

Für Teams ist es ebenfalls von großem Vorteil, dass nicht nur Code, sondern auch Unternehmensziele kontinuierlich validiert werden.

Gareth Schema Business

Holen Sie sich Gareth zu Hilfe

Haben Sie Interesse? Sie können Gareth gern ausprobieren. Die Software ist Open Source (GNU General Public License v.2.0), damit wir alle etwas davon haben. Möchten Sie sich in irgendeiner Form daran beteiligen? Dann wenden Sie sich an uns. Wir freuen uns auf Ihre Nachrichten!

Gareth finden Sie unter: https://github.com/craftsmenlabs

The post Continuous Validation: Gestatten – Gareth appeared first on codecentric Blog.

Canary-Release mit der Very Awesome Microservices Platform (Vamp)

0
0

Im letzten Artikel der Serie “Microservice-Deployment ganz einfach” erkläre ich, dass Docker nicht zwingend notwendig ist, um Microservice-Anwendungen auszuliefern. Wie der Artikel zeigt, kann man die Linux-Paketverwaltung benutzen, um Microservice-Anwendungen schnell und komfortabel auszuliefern. Leider muss man sich bei diesem Ansatz um die Auslastung seiner Rechner und die Verteilung der einzelnen Microservices auf die unterschiedlichen Rechner selbst kümmern. Es ist aus meiner Sicht aber wünschenswert, dass man einem Rechner-Cluster einfach Arbeit in Form eines Microservice gibt und sich das Cluster eigenständig darum kümmert, die Microservices entsprechend zu verteilen und auszuführen. In der Praxis interessiert mich eigentlich nicht wo ein Microservice läuft, sondern nur das ein bestimmter Microservice läuft. Mit Kubernetes stelle ich im Artikel “Microservice-Deployment ganz einfach mit Kubernetes” ein Werkzeug vor, dass mich bei der Verteilung der Container in einem Rechner-Cluster unterstützt und mir zusätzlich auch noch dabei hilft, dass sich die Container auf unterschiedlichen Rechnern gegenseitig finden. Mein Kollege Christian Zunker beschreibt die Service-Discovery-Thematik, die durch die Verteilung von Docker-Containern auf unterschiedlichen Rechnern entsteht, ausführlich in seinem Artikel “Variation des Ambassador Pattern von CoreOS”.

Microservice-Deployments mit Kubernetes

Kubernetes benutzt Pods, Replication Controller und Services, um Docker-Container in einem Rechner-Cluster zu verteilen und auszuführen. Weiterhin verfügt Kubernetes über ein sehr nützliches Feature, das so genannte Rolling-Update. Mit ihm kann man beliebige Docker-Container über mehre Knoten hinweg zeitverzögert ausliefern. Damit hat man die Möglichkeit einen Microservice auszurollen und mit realem Traffic entsprechende Daten über den Erfolg eines Deployments zu sammeln, bevor ein Microservice im kompletten Cluster ausgeliefert ist. Geht etwas schief, hat man immer noch die Möglichkeit das Deployment abzubrechen. Damit minimiert man die Gefahr, dass grobe Softwarefehler im kompletten Cluster in Produktion landen. Allerdings gilt das nur für Softwarefehler, wenn unsere Fachabteilungen bei der Produktentwicklung Änderungen machen, die die Benutzer nicht gut finden, dann stellt man das erst am geringeren Umsatz nach dem Deployment fest. Es ist aber aus meiner Sicht wünschenswert, dass man im Produkt-Entwicklungsprozess Kundenfeedback nach Fertigstellung einer neuen Funktionalität einholen kann, um eine Produktidee zu verbessern.

Dazu wäre es gut, wenn man einen Router hätte, der die Verteilung der Benutzer entsprechend bestimmter Regeln auf unterschiedliche Docker-Container organisiert, um das Benutzerverhalten mit einem Teil seiner Kunden messen und analysieren zu können. Einen solchen Router besitzt Kubernetes allerdings nicht. Die Verteilung der Benutzer ist abhängig von der Anzahl der Knoten im Kubernetes-Cluster, auf die die neue Version der Software ausgeliefert ist. Daher bietet Kubernetes out-of-the-box keine Möglichkeit, ein so genanntes Canary-Release abzubilden.

Canary-Release

Ein Canary-Release verwendet man, um das Risiko bei der Einführung einer neuen Software-Version zu vermindern. Man stellt Änderungen erst mal nur einem Teil seiner Benutzer zu Verfügung und misst wie dieser Teil auf die Änderungen reagiert, bevor man die neue Software auf der kompletten Infrastruktur ausrollt. Durch dieses Vorgehen hat man die Möglichkeit die Software schrittweise zu verbessern.

Ein Canary-Release ähnelt in der Ausführung einem Blue-Green-Deployment, das aber das Ziel verfolgt, die Downtime einer Applikation zu minimieren. Dazu hält man zwei Infrastruktur-Stränge für zwei unterschiedliche Softwareversionen vor und leitet die Benutzer über einen Router auf die entsprechende Version um. Bei einem Blue-Green-Deployment macht man einen harten Wechsel auf die neue Version der Software. Beim Canary-Release leitet man dagegen z.B. nur 5 Prozent des Traffic auf die neue Version um. Mit einem Canary-Release möchte man also vielmehr herrausfinden, ob eine neue Funktionalität wirklich eine signifikante Veränderungen im Verhalten der Benutzer auslöst. Diese Releases können auch benutzt werden, um A/B-Testing zu implementieren.

Canary Release

Microservice-Deployments mit Vamp

Vamp oder Very Awesome Microservices Platform vereinfacht das Durchführen von Canary-Releases. Vamp unterstützt im Moment Mesos und Marathon, später soll aber auch Kubernetes als Container-Manager hinzukommen. Die Plattform wird von der niederländischen Firma magnetic.io entwickelt. Ähnlich wie Kubernetes ist Mesos ein Dienst zum Starten von Docker-Containern in einem verteilten Rechner-Cluster. Mesos ist zuständig für Ressourcenverwaltung im Cluster und kann mit Marathon das Deployment von Microservice-Anwendungen durchführen. Marathon übernimmt dabei die Aufgabe eines Schedulers, der die Verteilung und Ausführung von Docker-Containern steuert. Viele große Firmen setzen ähnliche Technologie-Stacks schon erfolgreich in Produktion ein, darunter sind z.B, Apple, Holidaycheck und Otto.

Vamp ArchitekturVamp ist ein Dienst, der sich überhalb eines Container-Manager ansiedelt und aus mehren Bestandteilen besteht. Vamp-Core bietet eine Plattform-agnostische DSL und ein REST-API. Die DSL kann ähnlich wie Giant Swarm und Docker-Compose benutzt werden, um eine Microservice-Anwendung mit ihren Abhängigkeiten zu beschreiben. Diese Beschreibung benutzt die REST-Schnittstelle von Vamp-Core, um mit Marathon die Microservices im Mesos-Cluster zu deployen. Darüberhin beinhaltet die DSL auch Elemente zum Beschreiben von A/B Tests und Canary-Releases sowie zum Beschreiben von SLAs. Diese SLAs sorgen dafür, dass wenn bestimmte Antwortzeiten unterschritten werden und noch Ressourcen im Cluster verfügbar sind, automatische neue Docker-Container im Cluster gestartet werden. Zum Sammeln der Metriken, die für die SLAs und A/B Tests benötigt werden, gibt es den Metriken- und Event-Store Vamp-Pulse, in dem Daten von Core und Router in Elasticsearch gespeichert werden. Der Vamp Router ist eine Komponente die einen HAProxy steuert, der die Verbindung zwischen den laufenden Docker-Containern und den eigentlichen Benutzern realisiert.

Wie in den vorgangegangen Artikeln der Serie “Microservice-Deployment ganz einfach” benutze ich einen Online-Shop als Beispiel, um die Funktionionsweise vom Vamp zu zeigen. Diesen Online-Shop findet man in meinem Github-Repository. Die genaue Fachlichkeitsbeschreibung, die hinter diesem Beispiel steckt, erkläre in meinem Artikel “Microservices und die Jagd nach mehr Konversion”. Um Vamp für das Deployment unserer Microservice-Anwendung zu benutzen, müssen wir erst einmal einen so genannten Blueprint anlegen. Ein Blueprint ist eine Art Ausführungsplan für unsere Microservice-Anwendung, der in einer Datei gespeichert wird. Ein Blueprint beschreibt die abstrakte Topologie der Anwendung im Cluster. In einem Blueprint wird unter anderem beschrieben

  • welche Ports von außen erreichbar sind,
  • welche Abhängigkeiten zwischen den unterschiedlichen Microservices bestehen,
  • welche Microservices im Cluster für A/B Tests benutzt werden,
  • welche Routing-Regeln für A/B Tests bestehen,
  • wie viele Ressourcen (CPU, Speicher, Skalierung) reserviert werden müssen und
  • welche SLAs es geben soll.

Das folgende Listing zeigt einen Ausschnitt eines Blueprints des obengenanntem Online-Shops. Der vollständige Blueprint befindet sich auf Github.

---
name: shop:1.0

endpoints:
 catalog.port: 9050

clusters:

 catalog:
   services:
     -
       breed:
         name: catalog
         deployable: zutherb/catalog-frontend:latest
         ports:
           port: 80/http
         environment_variables:
           CHECKOUT_DESIGN: standard
           SHOP_PORT_8080_TCP_ADDR: $checkout.host
           SHOP_PORT_8080_TCP_PORT: $checkout.ports.port
           PRODUCT_PORT_18080_TCP_ADDR: $product.host
           PRODUCT_PORT_18080_TCP_PORT: $product.ports.port
           NAVIGATION_PORT_18090_TCP_ADDR: $navigation.host
           NAVIGATION_PORT_18090_TCP_PORT: $navigation.ports.port
           CART_PORT_18100_TCP_ADDR: $cart.host
           CART_PORT_18100_TCP_PORT: $cart.ports.port
         dependencies:
           product: product
           navigation: navigation
           cart: cart
           checkout: checkout
       scale:
         instances: 1
         cpu: 0.1
         memory: 128
       routing:
         weight: 100
     -
       breed:
         name: catalogWithAlternativeCheckoutDesign
         deployable: zutherb/catalog-frontend:latest-b
         ports:
           port: 80/http
         environment_variables:
           SHOP_PORT_8080_TCP_ADDR: $checkout.host
           SHOP_PORT_8080_TCP_PORT: $checkout.ports.port
           PRODUCT_PORT_18080_TCP_ADDR: $product.host
           PRODUCT_PORT_18080_TCP_PORT: $product.ports.port
           NAVIGATION_PORT_18090_TCP_ADDR: $navigation.host
           NAVIGATION_PORT_18090_TCP_PORT: $navigation.ports.port
           CART_PORT_18100_TCP_ADDR: $cart.host
           CART_PORT_18100_TCP_PORT: $cart.ports.port
         dependencies:
           product: product
           navigation: navigation
           cart: cart
           checkout: checkout
       scale:
         instances: 1
         cpu: 0.1
         memory: 128
       routing:
         weight: 0
         filters:
         - condition: User-Agent = Chrome

 checkout:
   services:
     breed:
       name: checkout
       deployable: zutherb/monolithic-shop:latest
       ports:
         port: 8080/http
       environment_variables:
         CART_PORT_18100_TCP_ADDR: $cart.host
         CART_PORT_18100_TCP_PORT: $cart.ports.port
         MONGODB_PORT_27017_TCP_ADDR: $mongodb.host
         MONGODB_PORT_27017_TCP_PORT: $mongodb.ports.port
       dependencies:
         mongodb: mongodb
         cart: cart
     scale:
       instances: 2
       cpu: 0.5
       memory: 512

Diesen Blueprint schickt man einfach an das REST-API von Vamp-Core und dann wird die Anwendung im Cluster deployt. Wie die Orchestierung in einem 5 Node Cluster aufsieht, kann man in folgenden Video sehen. Außerdem ist zu sehen, wie A/B Tests ins laufende Cluster ohne Downtime deployt werden.

Fazit

Zusammenfassend kann man sagen, dass Vamp über eine Plattform-agnostische DSL die Möglichkeit bietet Microservice-Anwendung zu beschreiben und in einem Mesos-Cluster auszuführen. Vamp nimmt die Arbeit ab, dass man sich darum kümmern muss, wo ein Microservice läuft. Mesos findet freie Ressourcen im Cluster und startet dort Docker-Container. Vamp sorgt dafür, dass die entsprechenden Endpunkte der Services den entsprechenden Docker-Container mitgeteilt werden, die andere Microservices darstellen. Dies passiert über Umgebungsvariablen und einem HAProxy als Ambassador.

Mit dieser Plattform bekommt man völlig neue Möglichkeiten Microservice-Anwendungen auszuliefern. Man kann sehr einfach A/B Tests formulieren, mit denen man komplette Prozesse verändern und testen kann. Zur Auswertung der A/B Test muss man im Moment aber leider noch auf ein Web-Tracking wie Google Analytics zurückgreifen.  Außerdem wird die Ausführung von Zero-Downtime-Deployments und Skalierungsszenarien stark vereinfacht. Wenn mehr Hardware gebraucht wird, fügt man einfach neue Maschinen in das Cluster und Vamp sorgt dafür, dass bei einer Verlangsamung von Antwort-Zeiten automatisch neue Docker-Container gestartet werden.

Für alle, die das Thema mehr interessiert und die auch auf der kommenden W-JAX in München sein werden. Ich halte dort am 3. November den Vortrag “Von Null auf Hundert mit Microservices”, wo ich dieses Thema ausführlicher beleuchte.

 

The post Canary-Release mit der Very Awesome Microservices Platform (Vamp) appeared first on codecentric Blog.

Contract Testing: Testen in einem Deploy-To-Production-Whenever-You-Want-Szenario

0
0

Continuous Deployment ist bisher nur in sehr wenigen Unternehmen angekommen. Die Regel sind immer noch eine Handvoll Releases im Jahr, die in einer mehrwöchigen manuellen Testphase getestet werden. Releases von verschiedenen Anwendungen werden gebündelt und gleichzeitig auf die Test- und Produktionssysteme gebracht. Es gibt Code-Freeze-Phasen, in denen nicht mehr entwickelt werden darf, und jede Menge Hotfixes, um Fehler doch noch zu beheben. Oder um gleich ein neues Feature hinterherzuschieben, das vor dem Code-Freeze nicht mehr fertig wurde.

Wie kommt man von solch einem System zu einem System, in dem jeder in Produktion gehen kann, wann er möchte, ohne dass das Gesamtsystem instabiler wird?

Ziel ist es, durch automatisierte Tests möglichst frühzeitig Probleme zu erkennen. Und das ist schwer! Unit-Tests sind inzwischen gut verstanden, und man findet kaum noch jemanden, der sagen würde, dass Unit-Tests nicht sinnvoll sind. Integrationstests in derselben Anwendung sind ebenfalls gut verstanden, und beide können einfach im Build automatisiert durchgeführt werden. Problematisch wird es, wenn es um Tests geht, die mehrere Anwendungen bzw. Services integrieren. Selten findet man Oberflächen-Akzeptanztests, die sich in Testsystemen automatisiert durch die Anwendung klicken. Schwierig ist hierbei, dass die Tests stark davon abhängen, dass die richtigen Testdaten im Testsystem vorhanden sind, und dass die Tests deswegen häufig instabil sind. Auch nicht klar ist, wann genau diese Akzeptanztests durchgeführt werden sollen, da sie sich nicht auf eine Anwendung, sondern auf das Gesamtsystem beziehen. Und sie ermitteln Fehler nicht vor dem Deployment in die Testumgebung, sondern erst danach.

Wenn man zurzeit auf das Thema Microservices und Testing kommt, ist eigentlich immer von Consumer Driven Contracts die Rede. Grob gesagt geht es darum, dass der Konsument eines Services zusammen mit dem Provider des Service einen Kontrakt definiert und in einem Test prüft, ob der Kontrakt eingehalten wird. Wenn der Provider nun eine Version baut, kann durch Ausführen der Tests aller Consumer sichergestellt werden, dass der Provider immer noch alle Konsumenten bedienen kann.

Was brauchen wir, um Stabilität sicherzustellen?

Es gibt zwei Seiten, die betrachtet werden müssen. Sagen wir, wir haben einen Service A, der Service B aufruft. Service A schreibt einen Contract Test für Service B.
Services

  • Beim Build von Service B wollen wir sicherstellen, dass der neue Build alle definierten Contracts einhält – da jeweils die aktuellste Version des Contracts.
  • Beim Deployment von Service A in eine Umgebung X wollen wir sicherstellen, dass der Contract, der von Service A definiert wurde, von der aktuell in der Umgebung deployten Version von Service B eingehalten wird.

Der zweite Punkt ist wichtig, weil er Überholer verhindert – wenn sich zwei Anwendungen auf einen Contract geeinigt haben, ist ja noch nicht klar, wann die Version deployt wird, die diesen Contract erfüllt. Durch den zweiten Punkt verhindern wir automatisiert Instabilität durch fehlerhafte Absprachen.

Und wie implementieren wir nun genau diese Contract Tests?

Es gibt Bibliotheken wie PACT oder Spring Cloud Contract, auf die ich in einem weiteren Blog Post eingehen werde. Vorher würde ich gerne ein Vorgehen vorstellen, dass noch nicht konkret von diesen Bibliotheken abhängt. Das Vorgehen hat die folgenden Voraussetzungen:

  • Es wird Docker verwendet, und Service A und Service B werden jeweils in einem Docker Container namens service-a bzw service-b betrieben.
  • Jeder Service kann in einem Self-Contained-Modus betrieben werden – das heißt, dass der Docker-Container mit dem Service in dem Modus isoliert gestartet werden kann, keine weiteren Abhängigkeiten benötigt und bei der gleichen Serie von Requests die gleichen Antworten liefert.

Docker

Dann werden die Tests folgendermaßen implementiert:

  • Service A stellt in seinem Git-Repository ein Unterverzeichnis/Projekt contract-service-a-service-b mit den Tests bereit. Dort liegt auch ein Docker-File, das mit einem docker run die Tests durchführt. Damit man es sich besser vorstellen kann, skizziere ich hier eine mögliche Implementierung:
    • Die Tests werden mit JUnit implementiert. Sie nutzen die Client-Zugriffsklassen des Service-A-Hauptprojekts und gehen gegen Service B, indem der Docker-DNS-Name service-b verwendet wird.
    • Es wird Maven verwendet. Der Docker-Container contract-service-a-service-b hängt von Java und Maven ab und kopiert bei Docker build den kompletten Projektinhalt in den Container.
    • Bei docker run werden nun alle Tests durch ein mvn test durchgeführt.
  • In der CD-Pipeline wird dafür gesorgt, dass beim Build von Service A auch der Docker-Container contract-service-a-service-b mit der gleichen Version gebaut wird.

Die Tests werden nun in der CI/CD – Pipeline beim Build von Service B genutzt:

  • Nach dem Build von Service B wird der Docker-Container service-b im Self-Contained-Modus gestartet.
  • In der Docker Registry werden nach Namenskonvention alle Images contract-*-service-b mit Tag LATEST gesucht und jeweils mit docker run gestartet. Die Tests werden durchgeführt. Falls ein Container Fehler bei der Testausführung meldet, wird der Build abgebrochen und das gebaute Image von Service B nicht in die Docker Registry gepusht.

Build

Außerdem werden sie beim Deployment von Service A in Umgebung X genutzt:

  • Es wird ermittelt, in welcher Version Service B in Umgebung X läuft. Der Docker-Container service-b wird in dieser Version im Self-Contained-Modus für den Test gestartet.
  • Der Test-Container contract-service-a-service-b mit der Version des Service A, die wir deployen wollen, wird gestartet und somit getestet, ob die zu deployende Version von Service A mit der deployten Version von Service B klarkommt.
  • Das wird wiederholt für weitere Contracts, die Service A mit anderen Services hat.

Deployment

Der größte Aufwand ist sicherlich die Bereitstellung eines Self-Contained-Modus für jeden Service bzw. jede Anwendung. Erfahrungen in bisherigen Projekten zeigen jedoch, dass so ein Modus viel Mehrwert bringt. Umgesetzt haben wir ihn, indem wir Service-Access-Klassen, die dafür verantwortlich waren, von Service A aus Service B aufzurufen, über Properties gesteuert durch Stub-Klassen ersetzt haben, die immer bestimmte Ergebnisse liefern. In der regulären Entwicklung war das sehr hilfreich, da die externen Services in den entsprechenden Umgebungen nicht immer alle Datenkonstellationen lieferten, die man für die Implementierung benötigte. Und für CI/CD hat man so eine sehr stabile Datenbasis für Oberflächen- und Contract-Tests.

Die Contract Tests selbst bieten schon einen großen Mehrwert, wenn sie sehr simpel gehalten werden. Allein schon eine Überprüfung, ob der Client mit der neuen API strukturell zurechtkommt, ist sehr hilfreich. Dank der stabilen Datenbasis von Service B im Self-Contained-Modus können aber beliebig aufwändige Tests geschrieben werden.

Was gewinnt man bei diesem Vorgehen?

Anwendungen können mit großer Sicherheit jederzeit nach Produktion gehen. Man schafft Unabhängigkeit im Deployment-Prozess. Manuelle Tests können zurückgefahren werden.

Was kostet dieses Vorgehen?

Der Self-Contained-Modus muss bereitgestellt werden. Und Consumer müssen Contract Tests schreiben.

Die sehr interessante Frage ist: Wiegt der Gewinn die Kosten auf?

The post Contract Testing: Testen in einem Deploy-To-Production-Whenever-You-Want-Szenario appeared first on codecentric AG Blog.

Planlos agil

0
0

Schon seit einigen Jahren fühlt es sich für mich immer irgendwie komisch an, in agilen Projekten von Planung zu sprechen und sogar Planungs-Meetings durchzuführen. Hier ein paar Gedanken, warum die Verwendung dieses Begriffs fragwürdig erscheint.

Planung, auch Projektplanung, kommt aus der Welt der klassischen Organisationsstruktur. Hier werden Pläne von Managern erstellt, die anschließend innerhalb von Abteilungen oder Projektteams umgesetzt werden. Eine strikte Trennung von Denken und Handeln, die wir in der agilen Welt nicht kennen.

Klassische Organisationsstrukturen sind aufgebaut wie eine Pyramide. In der Spitze der Pyramide werden von wenigen Entscheidern Ziele definiert. Manager auf den darunter liegenden Ebenen überlegen sich Wege, auf denen diese Ziele erreicht werden sollen. Diesen Prozess nennt man Planung.

Der Weg, der zur Erreichung eines Ziels führen soll, wird Plan genannt.

Auf den oberen Ebenen der Pyramide werden also mehrere Arten von Entscheidungen getroffen:

  • Entscheidung über das Ziel
  • Entscheidung über den Plan, also den Weg zum Ziel

Die unteren Ebenen der Pyramide werden anschließend zur Durchführung des Plans herangezogen.

Klassische Organisation

Diese Pyramide gibt es in verschiedenen Varianten (in Form unterschiedlicher Organisationsstrukturen). Alle  Varianten haben gemeinsam, dass  der Ort der Entscheidung weit vom Ort des Handelns entfernt liegt. Es existiert also immer eine strikte Trennung von “Denken” und “Handeln”, von “Plan Erstellung” und “Plan Ausführung”. Planung ist der Prozess mit dem die Denkenden den Handelnden vorschreiben, wie sie ein Ergebnis erzielen sollen.

Während eine Trennung von Denken und Handeln für einfache und komplizierte Probleme funktionieren kann, führt sie bei zunehmend komplexeren Aufgabenstellungen zu schlechten Ergebnissen. Das liegt vor allem daran, dass die immer komplexer werdenden Probleme und Aufgabenstellungen nicht mehr von wenigen Entscheidern, weit weg von den Problemstellungen, gelöst werden können. Das strikte Einhalten eines einmal erstellten Plans erweist sich in einem komplexen Umfeld als kontraproduktiv, da Pläne trotz eines Informationsdefizits und auf Basis von falschen Annahmen zustande kommen.

In agilen Unternehmen geht man davon aus, dass die Komplexität heutiger Aufgabenstellungen am besten adressiert wird, indem die Lösungsfindung von Personen möglichst nah an der Problemstellung durchführt wird.  Eine gute Strategie zur Lösung komplexer Probleme ist es, sich der Intuition eines Kollektivs zu bedienen. Das heißt, Entscheidungen werden am besten von den Handelnden selbst getroffen. Dadurch werden keine Ziele und Pläne zur Steuerung der Handelnden mehr benötigt. Der Prozess der Planung wird überflüssig.

Doch wie werden in agilen Unternehmen ohne Ziele und Pläne Ergebnisse erzeugt? – Wer definiert, welche Ergebnisse erzielt werden sollen?

An die Stelle eines festen Ziels tritt eine Vereinbarung zwischen Kunde und agiler Organisation. In dieser Vereinbarung wird festgelegt, welche Art von Kundennutzen erzeugt werden soll. Die konkrete Ausgestaltung des Kundennutzen ergibt sich erst im Verlauf der Zusammenarbeit. Dabei werden immer wieder Hypothesen aufgestellt, die im Anschluss in Form von Experimenten validiert oder verworfen werden. Dadurch wird eine Arbeitsweise geschaffen, bei der die Steuerung der Inhalte durch kontinuierliche Rückmeldung des Kunden erfolgt. Planung wird durch Vorbereitung der nächsten Iteration von Hypothese und Experiment ersetzt. Der Kunde entscheidet, ob das bereitgestellten Ergebnis ausreichend Nutzen erbringt und auf der geschaffenen Basis weitere Iterationen erfolgen sollen, oder ob mit Hilfe von weiteren Experimenten nach besseren Varianten gesucht werden muss.

screen-shot-2016-09-26-at-23-04-57

Worin besteht der Unterschied zwischen der Vorbereitung eines Experiments und klassischer Planung?

Laut Wikipedia bezeichnet Planung “die menschliche Fähigkeit zur gedanklichen Vorwegnahme von Handlungsschritten, die zur Erreichung eines Zieles notwendig scheinen”

Grob gesagt: Ohne Ziel kein Plan. Da man sich an das Ziel in einem komplexen Umfeld jedoch zunächst mittels iterativem Vorgehen annähern muss, ist es nicht sinnvoll von Planung im herkömmlichen Sinne zu sprechen. Darüber hinaus wird die Vorbereitung der Experimente im agilen Umfeld immer von den handelnden Personen selbst durchgeführt. Es handelt sich bei der Vorbereitung also nicht um einen Prozess bei dem Denkende Vorschriften für Handelnde erstellen.

Sprechen wir im agilen Umfeld von Planung meinen wir also etwas völlig anderes, als im klassischen Umfeld damit gemeint ist. Für verschiedene Dinge denselben Begriff zu verwenden führt spätestens dann zu Problemen, wenn Personen aus beiden Umfeldern aufeinandertreffen.
Eine klare Abgrenzung der Begrifflichkeiten würde hier helfen.

The post Planlos agil appeared first on codecentric AG Blog.

Viewing all 129 articles
Browse latest View live




Latest Images