Autarke Teams und DevOps: So skaliert Continuous Delivery
Die meisten Entwicklungsabteilungen stehen heute vor der Herausforderung, trotz einer immer größeren und komplexeren Anwendungslandschaft ihre Release-Zyklen bei gleichbleibend hoher Qualität zu beschleunigen. Continuous Delivery bietet hierfür hilfreiche Lösungsansätze. Die Automatisierung des Software-Release-Prozesses und die Schaffung von Qualitätsmesspunkten in der Lieferkette reduzieren Wartezeiten und ermöglichen schnelles Feedback.
Doch gerade bei großen Anwendungen oder bei Anwendungen mit vielen Abhängigkeiten, ist es mit einer reinen Automatisierung der Lieferkette nicht getan. Hier geht die meiste Zeit bei der Koordination der vielfältigen Anforderungen und der Aufrechterhaltung der Integrierbarkeit verloren. Viele versuchen diese Komplexität durch die Aufteilung in verschiedene Umsetzungsteams in den Griff zu bekommen. Doch auch dann entstehen regelmäßig Wartezeiten, durch die notwendige Synchronisation der Teams mit nicht-integrierten Stellen wie Test, Infrastrukturbereitstellung und Fachanforderern sowie durch gemeinsame Integrationsphasen und Release-Termine.
Wie lässt sich also erreichen, dass Teams möglichst autark arbeiten? Und wie lässt sich das mit dem Ziel von Continuous Delivery, den Release-Prozess zu automatisieren, in Einklang bringen? Dieser Artikel beschreibt, wie autarke Teams und DevOps zusammen funktionieren.
Der Mehrwert autarker Teams
Um die Komplexität großer Anwendungen besser handhaben zu können, wird diese meist zergliedert und auf verschiedene Entwicklerteams verteilt. Doch allzu oft deckt das Team nicht den gesamten Erstellungsprozess ab und muss auf Input von außen warten. Wenn der Fachanforderer beispielsweise nicht in das Team integriert ist, muss das Team auf die Klärung ungenauer Anforderungen warten oder zunächst eigene Annahmen treffen und Änderungen dann später nacharbeiten. Ist der Fachtest nicht integriert, werden Fehler erst in Abnahmetestphasen erkannt, die zu einem früheren Zeitpunkt deutlich leichter zu beheben gewesen wären. Arbeitet das Team an einer gemeinsamen Plattform, wartet es oft auf Anpassungen des Frameworks, abhängige Komponenten oder auf zentrale Datenbankänderungen. Da der Release-Prozess einer Anwendung meist übergreifende Entwicklungsphasen vorsieht, zum Beispiel Phasen der Systemintegration oder der Abnahmetests, welche die Zuarbeit zu einem gemeinsamen Release-Termin verlangen, bewirken Verspätungen einzelner Teams die Verspätung des gesamten Anwendungsreleases.
All dies sind Beispiele für Synchronisationspunkte mit externen Stellen, an denen ein Team nicht autark agieren kann. Es kann Verzögerungen an diesen Punkten nicht selbst entgegenwirken, es hat hier keine Steuerungsmöglichkeiten. Zudem muss eine Abstimmung an Synchronisationspunkten oft formaler sein, da sie nicht in persönlicher Abstimmung passiert.
Damit ein Team autark arbeiten kann, sollte es alle Rollen vereinen, die für den Weg von der Idee zur produktiven Software erforderlich sind. Dazu gehören mindestens:
- der Fachanforderer oder Product Owner, der Entscheidungen über die Gestaltung des Produkts treffen kann,
- ein Requirements Engineer oder Business Analyst, der die Anforderungen ausdetailliert und vervollständigt,
- ein Test Engineer, der die Anwendung gegen die Anforderungen testet,
- alle Entwicklerrollen, die notwendig sind, um einen fachlichen Usecase durch den kompletten Anwendungsstack zu realisieren und
- ein Delivery Engineer, der den Erstellungs- und Releaseprozess der Anwendung automatisiert und damit Continuous Delivery ermöglicht.
Um die Teamgröße überschaubar zu halten, kann eine Person auch mehrere Rollen wahrnehmen. So können Anforderungsmanagement und Test gut auf eine Person vereint werden, da beide die Nutzerperspektive einnehmen. Außerdem könnten die Rollen zwischen Liefer-Sprints getauscht werden (z. B. Frontend- und Middletier-Entwicklung oder Business Analyst und Entwicklung). Damit kann man Wissensinseln vorbeugen und Ressourcen-Engpässe oder -Überschüsse ausgleichen.
Es werden Barrieren abgebaut und die Arbeit an einem gemeinsamen Ziel in den Mittelpunkt gestellt.
Während die meisten der genannten Rollen geläufig sein sollten, bedarf der Delivery Engineer ein wenig Erläuterung: Der Delivery Engineer kennt den Release-Prozess der Anwendung bis in die Produktion und versteht die Anforderungen, die für den produktiven Betrieb der eigenen Anwendungskomponente erforderlich sind. Er optimiert und automatisiert den Release-Prozess in Abstimmung mit dem Betrieb, so dass in allen Umgebungen Deployments möglichst identisch ablaufen. Weiterhin sorgt er dafür, dass
- jede Softwarelieferung eine Delivery-Pipeline durchläuft, in der Quality-Gates sicherstellen, dass Fehler schnell erkannt und nicht weiter ausgeliefert werden,
- manuelle Eingriffe in die Pipeline soweit wie möglich automatisiert werden, um menschliche Fehler zu vermeiden,
- jede Änderung an Software und Pipeline durch Versionskontrolle nachvollzogen werden kann und
- die Entwicklung den Bedarf des Betriebs optimal versteht und diesen frühzeitig bei der Entwicklung berücksichtigen kann.
Neben der Abdeckung von Kompetenzen ist auch der passende Schnitt des Teams entscheidend. Der Schnitt sollte entlang des entkoppelten fachlichen Bereichs geschehen, in dem ein Team dann auch Domänenwissen aufbauen kann.
Durch autarke Teams ergeben sich viele Vorteile. Die Kommunikation wird effizienter, da Klärungen schneller und weniger formal herbeigeführt werden können. Wartezeiten werden reduziert, da die Verfügbarkeit der Ressourcen auf das Team zugeschnitten ist. Doch vor allem werden Barrieren zwischen den Rollen abgebaut und die Arbeit an einem gemeinsamen Ziel in den Mittelpunkt gestellt.
Oft besteht die Anwendungslandschaft aber aus vielen Anwendungsteilen oder -modulen, die voneinander abhängen und sich einer gemeinsamen Plattform bedienen. Hier muss das Team zum einen gewährleisten, dass alle Anwendungsteile kompatibel bleiben und dort, wo eine einheitliche Plattform gewünscht ist, keine redundanten Lösungen entstehen. Bei der Systemintegration und dem Anwendungs-Deployment kommen beispielsweise alle Anwendungsteile zusammen. Daher werden diese üblicherweise zentral gesteuert. Ein Buildmanagement-Team oder ein Continuous Delivery-Team wird daher oft als zentrales Team aufgesetzt. Damit bildet dieses Team jedoch wieder einen Synchronisationspunkt, mit dem sich die fachlichen Teams abstimmen müssen.
Wie lässt sich also die Agilität von autarken Teams mit den Qualitätsansprüchen von Continuous Delivery vereinen, wenn jedes Team nur einen Teil der gesamten Lösung liefert?
Architekturen für autarke Teams
Die Anwendungsarchitektur und der Release-Prozess spielen eine wichtige Rolle, wenn es darum geht, die in den Teams liegenden Anwendungskomponenten wirklich eigenständig zu entwickeln. Die Unterteilung eines Monolithen in fachliche Komponenten reicht hier allein meist nicht aus. Hilfreicher sind ein Schnittstellenkonzept, das die Kopplung der Komponenten reduziert, und ein Versionskonzept, um die Kompatibilität zu anderen Komponenten zu beschreiben. Ferner sollten die Daten eindeutig einer Komponente zugeordnet werden und auch nur von dieser verwaltet werden. Andere Komponenten nutzen dann ausschließlich die Schnittstellen dieser Komponente, um auf deren Daten zuzugreifen.
Damit sind die Teams immer noch an einen gemeinsamen Release-Prozess gebunden, bei dem alle Komponenten als eine Anwendung ausgeliefert werden. Der nächste Schritt in Richtung autarkes Team besteht daher darin, einen Deployment- und Release-Prozess auf Komponentenebene herzustellen. Als Architekturansätze eignen sich dafür besonders Produkt-Plattformen oder eine Microservices-Architektur.
Produkt-Plattformen nutzen gemeinsame Basiskomponenten, werden darauf aufbauend aber eigenständig entwickelt und ausgeliefert. Die einzelnen Produkte sind dabei zur Laufzeit meist unabhängig voneinander. Basiskomponenten decken zum Beispiel Querschnittsanforderungen wie Authentifizierung, Logging oder eine Rahmenanwendung ab. Dieser Ansatz eignet sich, wenn Teams bereits unabhängige Entwicklungsprojekte führen, dann aber im weiteren Verlauf feststellen, dass viele Dinge redundant gelöst werden und übergreifend effizienter gelöst werden könnten.
Microservices [1] sind fachlich geschnittene Komponenten, die eigenständig ausgeliefert werden können und mit anderen Microservices über ein einheitliches Schnittstellenformat wie JSON über eine REST-API kommunizieren. Sie werden gezielt so geschnitten, dass sie vollständig von einem Team entwickelt werden können. Da sie einen klaren Schnittstellenvertrag definieren, eignen sich Microservices für die Ablösung von Anwendungskomponenten, die mit anderen Komponenten kommunizieren müssen. Um sich von der Infrastruktur anderer Services zu entkoppeln, bringen Microservices meist ihre eigene Ablaufumgebung mit. Diese besteht oft aus sehr leichtgewichtigen Applikations- oder Webservern wie Jetty [2], Wildfly Swarm [3] oder Spring Boot [4].
Container-Plattformen wie Docker [5] eignen sich ebenfalls sehr gut als Deployment-Einheit für Microservices, da diese maximale Freiheiten für die Technologie der Ablaufumgebung im Container bieten und trotzdem so leichtgewichtig sind, dass sie sich in großer Zahl mit schnellen Re-Deployments betreiben lassen. Das Deployment eines Containers ist abgesehen von Umgebungsvariablen, über welche die jeweils passenden Endpunkte angebunden werden, in allen Umgebungen gleich. So ist für ein einheitliches Vorgehen gesorgt.
Trotz der Freiheiten, die eine Microservices-Architektur bietet, muss gerade hier ein einheitliches Schnittstellen- und Betriebskonzept definiert werden. Da jeder Service separat deployed wird, sind Automatisierung und Qualitätssicherung der Lieferung hier besonders wichtig.
Autarke Teams benötigen die Kompetenzen des Delivery Engineers. Nur dann kann ein Team selbständig Produkte oder Services entwickeln, die stabil betrieben und effizient über einen automatisierten Prozess ausgeliefert werden.
Zentral starten, verteilt ausbauen
Zu Beginn einer DevOps-Initiative ist ein Team üblicherweise nur bis zur Bereitstellung eines fachlich qualitätsgesicherten Anwendungsmoduls verantwortlich. Die oben beschriebenen Kompetenzen eines Delivery Engineers können hier meist noch nicht geleistet werden. Dazu braucht es zum einen den regelmäßigen Austausch zwischen Betrieb und Entwicklung und zum anderen Erfahrung in Aufbau und Betrieb von Continuous Delivery-Pipelines.
Als Initiator für diese Maßnahmen eignet sich ein zentrales DevOps-Team, das als Enabler für Continuous Delivery auftritt. Dieses Team sollte drei Dinge in sich vereinen:
- Wissen über den aktuellen Lieferprozess,
- Werkzeuge zum Aufbau von Continuous Delivery-Pipelines
- und Kompetenz, um die Prinzipien von Continuous Delivery zu vermitteln.
Eine der ersten Aufgaben dieses Teams sollte es sein, die Delivery Engineer-Kompetenz in den fachlichen Teams aufzubauen, damit die Prinzipien von Continuous Delivery dort vertreten und aufgegriffen werden.
Diese Prinzipien beschreiben die wesentlichen Grundpfeiler, die für den Betrieb von Continuous Delivery einzuhalten sind. Beispiele dafür sind:
- Jedes Release nach Produktion muss die Delivery-Pipeline durchlaufen haben.
- Der Release-Kandidat muss in mindestens einer Testumgebung auf die gleiche Weise wie in Produktion deployt worden sein.
- Manuelle Änderungen auf den CI- und Ablaufumgebungen sind zu vermeiden.
- Infrastruktur-Änderungen geschehen nur über ein Versionskontrollsystem.
- Ein Deployment in verschiedene Umgebungen muss ohne erneuten Build möglich sein.
Aufbauend auf diesen Prinzipien stellt das zentrale Team Plattform-Richtlinien auf, die die Integrierbarkeit der Anwendungsmodule sicherstellen und die Prinzipien von Continuous Delivery in konkrete Lösungsansätze übertragen. Hier ein paar Beispiele:
- Ein Lieferartefakt muss als Docker Image, RPM-Packet oder Jar-Datei bereitgestellt werden.
- Ein Service muss seine API über REST im JSON-Format anbieten.
- Release Candidates müssen in ein definiertes Artefakt-Repository deployt werden.
- Umgebungsspezifische Konfiguration muss über Umgebungsvariablen angebunden werden.
Die Continuous Delivery-Prinzipien gelten als Entscheidungshilfe, wenn die Plattform-Richtlinien keine genaue Vorgabe machen.
Das zentrale DevOps-Team etabliert im weiteren Verlauf den regelmäßigen Austausch zwischen Entwicklung und Betrieb und sucht erste Anknüpfungspunkte für eine gegenseitige Unterstützung. Dabei wird das Team die ersten Lösungsbausteine der Delivery-Pipeline zunächst selbst entwickeln, um sowohl der Entwicklung als auch dem Betrieb den Mehrwert von Continuous Delivery zu demonstrieren und beiden noch unbekannte Tools und Praktiken näherzubringen. Dementsprechend sollten dies Lösungen sein, von denen beide Seiten profitieren. Zu diesem Anlass erfolgt eine Analyse des bestehenden Lieferprozesses und die Identifizierung von Wartezeiten sowie von Gaps zwischen Betriebs- und Entwicklungsprozessen. Ein erster Ansatz könnte sein, das Verfahren zum Deployment in die Produktivumgebung für Testumgebungen nutzbar zu machen, um Einheitlichkeit zwischen Test und Produktion zu schaffen.
Zusammengefasst: Das zentrale DevOps-Team schafft zu Beginn der Initiative den Rahmen für DevOps, indem es die Werte und neues Tool-Know-how an Teams und Betrieb vermittelt und die Teams damit befähigt, betreibbare Software früher und autarker auszuliefern.
Plattform-Team als Servicelieferant
Nach der beschriebenen Initiierungsphase übernimmt das zentrale DevOps-Team die Rolle eines Plattform-Teams, das die Delivery-Plattform weiterentwickelt. Die Delivery-Plattform umfasst eine Menge von Werkzeugen und Services, die die Integrierbarkeit der in den Teams entwickelten Komponenten in die Plattform sicherstellen. Dazu können Vorlagen für den Aufbau eines Microservices gehören oder Skripte für das Deployment einer Komponente in eine Zielumgebung.
In einer weiteren Ausbaustufe kann das Plattform-Team Self-Services bereitstellen, mit denen die Entwicklungsteams ihre Infrastruktur-Bedürfnisse ohne Zuarbeit herstellen können. So könnten sie mit Tools wie Rundeck [6] das Setup einer Testumgebung oder das Zurücksetzen einer Testdatenbank durch Knopfdruck auslösen. Über Chat-Bots wie Hubot [7] können sie Automatisierungs-Tasks in Chat-Software wie Slack [8] oder HipChat [9] integrieren. Diese ChatOps-Tools eignen sich auch für kurze Fragen an die Infrastruktur, wie: "Wurde Release 2.0 schon auf die Abnahmetest-Umgebung deployt?".
Die fachlichen Teams werden häufig die Treiber neuer Anforderungen an die Delivery-Plattform sein. Zum Beispiel, weil sie wiederkehrende Aufgaben automatisiert haben wollen, oder weil die Test-Infrastruktur neue Anforderungen erfüllen soll. Das Plattform-Team darf dabei allerdings nicht wieder ein Synchronisationspunkt werden, der die Teams behindert. Deshalb sollte ein Prozess etabliert werden, der diesem Team Freiheiten zur eigenständigen Weiterentwicklung seiner Delivery-Pipeline gibt und der den anderen Teams die Möglichkeit gibt, davon zu profitieren.
Das Plattform-Team sollte sich als Servicelieferant verstehen, der die Teams beim Aufbau ihrer Delivery-Pipeline unterstützt.
Weiterentwicklung der Delivery-Plattform
Mithilfe der Continuous Delivery-Kompetenz der Delivery Engineers in den fachlichen Teams und mithilfe des Plattform-Teams kann die Weiterentwicklung der Delivery-Plattform sowohl aus den Teams heraus als auch von zentraler Stelle initiiert werden. Entsteht die Anforderung aus einem Team heraus, kann dieses zunächst mit einer internen Lösung den kurzfristigen Bedarf stillen und die Anforderung konkretisieren. Wenn es aber um einen Aspekt geht, der potenziell mehrere Teams betrifft oder auch anderen Teams Mehrwerte bringt, sollte das Plattform-Team diese Lösung anschließend übernehmen. Dazu ist es einerseits notwendig, den Mehrwert der Lösung für die Plattform zu bewerten, und andererseits, die Lösung bei Bedarf zu erweitern, so dass sie für alle Teams nutzbar wird.
Ein geeigneter Rahmen zur Identifizierung solcher Themen wäre eine "Community of Practice", in der sich Vertreter des Plattform-Teams und die Delivery Engineers der Entwicklungsteams in regelmäßigen Intervallen treffen. Bei diesen Treffen können die Teilnehmer neue Anforderungen an die Delivery-Pipelines der Teams vorstellen und klären, ob die Anforderungen teamübergreifend gelten. Sollte dies der Fall sein, werden gegebenenfalls zusätzliche Anforderungen aus anderen Teams mit aufgenommen und ein Team für die Umsetzung bestimmt. Ein solches Umsetzungsteam kann auch aus einer Kombination des Plattform-Teams und des anfordernden Delivery Engineers bestehen.
In der "Community of Practice"-Runde können weiterhin Punkte im Lieferprozess identifiziert werden, die die Teams regelmäßig aufhalten oder die Lieferqualität gefährden. Hier kann das Plattform-Team bei der Lösungsfindung unterstützen oder andere Teams können mit ihren Erfahrungen zu einer solchen beitragen. Sowohl Plattform-Team als auch Service-Teams und Betrieb tragen also zur Weiterentwicklung der Delivery-Plattform bei (s. Abb.1).
Deployments in autarken Teams
Wenn jedes Team Services oder Produkte nach eigenen Release-Zyklen deployt, entfällt eine übergreifende Integrationstestphase, so wie sie sonst üblicherweise vor dem Release einer Gesamtanwendung stattfindet. Trotzdem müssen die Teams die Integrierbarkeit eines neuen Service mit seiner Umwelt gewährleisten. Es ist also wichtig, bei getrennten Deployments stärker auf die Stabilität der Schnittstellen zu achten, damit abhängige Services weiterhin funktionieren. Die Stabilität der Schnittstelle kann über Contract-Tests geprüft werden. Dabei wird der Service als Ganzes, aber mit Mocks für aufgerufene Services, über seine Schnittstelle getestet. Diese Tests dienen gleichzeitig als Dokumentation für potenzielle Nutzer des Service.
Doch was, wenn eine neue Funktionalität die Änderung des Service eines anderen Teams erfordert? Müssen dann beide Services gleichzeitig deployt werden? Mit Feature-Toggles lassen sich der Zeitpunkt des Releases eines Services und der Zeitpunkt der Aktivierung eines Features voneinander trennen. Der anzupassende Service wird dabei mit einer früheren Änderung deployt, wobei die Änderung noch deaktiviert ist. Beim Deployment des zweiten Service aktiviert das Team die Änderung über eine Konfigurationsänderung. Nach dem Umstieg wird der Feature-Toggle wieder ausgebaut, um keine technischen Schulden aufzubauen [10].
In der Delivery-Pipeline des Services sollte es eine Stufe geben, in der das Team einen Systemintegrationstest mit den produktiven Ständen der anderen Services der Gesamtanwendung durchführt. Dazu sollten die Entwickler entweder einen Abzug der Produktiv-Umgebung verwenden, oder eine neue Umgebung mit den produktiven Services provisionieren können.
Eine wichtige Neuerung bei der Einführung getrennter Deployments besteht darin, dass das Entwicklungsteam Services nicht mehr an einen Betrieb übergibt und sich damit also auch nicht mehr von der Verantwortung entbinden kann. Das Team ist jetzt selbst für die Stabilität der Services in Produktion verantwortlich. Der Betrieb stellt lediglich die Ablaufumgebung bereit. Schon dadurch sollte das Team ein frühes Interesse an Themen wie Monitoring und Ausfallsicherheit haben, die es vormals an den Betrieb delegiert hat (s. Abb.2).
Fazit
Bei DevOps geht es nicht nur um eine Automatisierung der Infrastruktur und Delivery-Pipelines. Die Optimierung des Lieferprozesses beginnt schon beim Aufbau der Entwicklungsteams. Nur wenn dieses Team mit allen Kompetenzen bis in die Auslieferung ausgestattet ist, kann es Anforderungen maximal flexibel und agil in Lösungen "gießen". Die DevOps-Initiative sollte dazu genutzt werden, diese Chance wahrzunehmen und eigenverantwortliche, autarke Teams zu formen. Gleichzeitig muss aber auch Raum geschaffen werden, um teamübergreifende Aspekte der Lieferkette zu erkennen und wiederverwendbar umzusetzen.