Wenn ein funktionierendes Testsystem zum Engpass wird und wie wir das Problem gelöst haben
Von Ramon Schwammberger und dem Platform Engineering Team
Moderne Plattformen entwickeln sich nicht nur weiter; ihre Entwicklung beschleunigt sich. Je kürzer die Release-Zyklen werden und je komplexer die Infrastruktur wird, desto eher können selbst Systeme, die jahrelang zuverlässig funktioniert haben, plötzlich alles ausbremsen. Dies ist die Geschichte, wie wir unser Testsystem für Multi-Plattform-Umgebungen weiterentwickelt haben: nicht durch einen vollständigen Neubau, sondern indem wir es bei laufendem Betrieb schrittweise entwirrt und modernisiert haben.
Wie oft sagt man: «Never change a winning team.» Warum sollte man etwas anfassen, das funktioniert? Diese Haltung ist in der IT weit verbreitet. Wir konzentrieren uns auf den Aufbau neuer Funktionen, treiben Innovationen voran und arbeiten am nächsten grossen Entwicklungsschritt, während die Systeme, die uns bis hierhergebracht haben, im Hintergrund unverändert weiterlaufen. Zumindest so lange, bis sie uns zu bremsen beginnen. Nicht, weil sie plötzlich nicht mehr funktionieren, sondern weil sich alles um sie herum schneller weiterentwickelt.
Genau das ist mit unserem Testsystem passiert. Es fiel nicht aus. Im Gegenteil: Es erledigte seine Aufgabe weiterhin zuverlässig. Gleichzeitig entwickelte sich unsere Plattform jedoch immer schneller weiter – mit kürzeren Release-Zyklen für unsere Software, das zugrunde liegende Betriebssystem, den Kernel und die eingesetzten Tools. Daraus entstand eine Anforderung, die auf dem Papier simpel klang: Wir mussten sicherstellen, dass alles sowohl mit der bestehenden als auch mit der neuen Betriebssystemversion funktionierte. In der Praxis bedeutete das, dieselben Tests in zwei unterschiedlichen Umgebungen auszuführen und uns auf beide Ergebnisse verlassen zu können.
Der Preis einer Greenfield-Lösung
Wir alle wünschen uns manchmal eine Greenfield-Situation: die Möglichkeit, ein neues Testsystem von Grund auf zu entwickeln – ausgerichtet auf die heutigen Anforderungen, mit klaren Abgrenzungen und von Anfang an für verschiedene Plattformen konzipiert. In der Realität hat dieser Ansatz seinen Preis.
Uns war klar, dass der Aufbau eines «perfekten» Systems erheblich länger dauern würde als die Weiterentwicklung der bestehenden Lösung. Während wir am Redesign gearbeitet hätten, hätten unsere Kolleginnen und Kollegen ihren Code weiterhin manuell auf beiden Betriebssystemversionen validieren müssen. Ihr Aufwand hätte sich dadurch faktisch verdoppelt. Für das Unternehmen wären höhere Infrastrukturkosten entstanden, während unsere Kunden mit einer weniger zuverlässigen Performance und einer langsameren Bereitstellung neuer Funktionen hätten rechnen müssen. Ganz abgesehen davon wäre das Testen selbst zu kompliziert und zu stark verflochten geworden.
Als es darauf ankam, entschieden wir uns deshalb für einen Brownfield-Ansatz.
Eine dritte Dimension kommt hinzu
Bis zu diesem Zeitpunkt war die Infrastruktur eine implizite Grösse. Es gab eine Betriebssystemversion und ein festgelegtes Tooling. Das Testsystem musste lediglich den richtigen Software-Stack installieren und die Tests mit den verschiedenen Konfigurationen ausführen.
Mit der Einführung einer neuen Betriebssystemversion fiel diese Annahme weg. Die Infrastruktur war nicht mehr statisch. Sie wurde zu einer Grösse, die das System verstehen und berücksichtigen musste. Der Wechsel von einer Betriebssystemversion zur anderen erforderte zudem einen Neustart des Systems.
Die Entwicklerinnen und Entwickler definierten nun nicht mehr nur, was getestet werden sollte, sondern auch unter welchen Bedingungen: mit welcher Konfiguration, welchem Software-Stack und welcher Betriebssystemversion. Damit musste die Infrastruktur zu einem zentralen Eingabeparameter des Systems werden.
Anstatt die bestehende Implementierung weiter auszubauen, entschieden wir uns für einen gezielten Redesign-Schritt. Wir überarbeiteten die Komponente, die für das Auslösen und Planen der Tests zuständig war. Sie bestand aus einer Sammlung eng miteinander gekoppelter Skripte. Dadurch war es sehr schwierig, das System zu verstehen oder mögliche Ergebnisse vorherzusagen.
Wir ersetzten diese Skripte durch eine zentrale Softwarekomponente, die sämtliche Entscheidungen übernimmt und sich einfach testen lässt. Die wichtigste Rahmenbedingung dabei war, dass alles rundherum weiterhin funktionieren musste. Die Teams hatten ihre Tests bereits definiert – sie zu einer Überarbeitung zu zwingen, kam nicht infrage.
Deshalb liessen wir die Schnittstelle unverändert und führten lediglich im Hintergrund eine neue Implementierung ein. So konnten wir die Veränderung kontrolliert umsetzen. Zunächst ersetzten wir das Kernverhalten und überprüften, ob die Ergebnisse unverändert blieben. Erst danach begannen wir mit der Einführung der neuen Funktion, Tests über mehrere Plattformen hinweg auszuführen.
Unsere Entflechtungskompetenz im Stresstest
An diesem Punkt begann das System, seine verborgene Komplexität offenzulegen. Sobald die Tests auf beiden Betriebssystemversionen liefen, funktionierten plötzlich Komponenten nicht mehr, die eigentlich vollständig unabhängig davon sein sollten.
Ein Beispiel war der Release-Prozess. Selbst wenn die Tests erfolgreich waren, schlug der automatisierte Schritt fehl, der die Software in einen produktionsbereiten Zustand überführte. Keine unserer Änderungen betraf diese Logik direkt – und trotzdem wurde sie davon beeinflusst.
Dadurch wurde etwas sichtbar, das wir zuvor unterschätzt hatten: Das System war wesentlich enger gekoppelt, als es den Anschein hatte. Die Build-, Test- und Release-Schritte waren auf eine Weise miteinander verbunden, die erst durch die Veränderung zum Vorschein kam. Erinnern Sie sich an die Sammlung von Skripten, die wir ersetzt hatten? Auch die Build- und Release-Schritte arbeiteten mit ähnlichen Skripten, die auf subtile Weise miteinander interagierten.
Wir versuchten nicht, alles auf einmal zu entflechten. Das hätte zu lange gedauert und uns genau zu jenem Greenfield-Szenario zurückgeführt, das wir bewusst vermeiden wollten. Stattdessen entschieden wir uns für ein pragmatischeres Vorgehen: Wir ersetzten die Komponente, die wir benötigten, liessen alles andere stabil und nutzten unsere Erkenntnisse als Grundlage für künftige Verbesserungen.
Die Kopplung blieb bestehen – allerdings in kleineren Clustern.
Die physischen Grenzen überwinden
Die nächste Herausforderung war weniger architektonischer als vielmehr physischer Natur. Dieselben Tests in zwei Umgebungen auszuführen, ist einfach – sofern sich diese Umgebungen problemlos duplizieren lassen. In der Cloud ist das hauptsächlich eine Kostenfrage.
Doch nicht alle Tests laufen in der Cloud. Einige sind auf physische Hardware angewiesen. Und Hardware ist begrenzt: durch den verfügbaren Platz im Rack, die Stromversorgung und die Tatsache, dass sie sich nicht einfach bei Bedarf duplizieren lässt. Statt die Umgebungen zu vervielfachen, mussten wir sie daher wiederverwenden.
Die von uns implementierte Lösung war im Prinzip unkompliziert. Tests, die physische Maschinen benötigten, wurden zunächst auf der bestehenden Plattform ausgeführt. Anschliessend startete das System die Maschine mit der neuen Betriebssystemversion neu, führte dieselben Tests nochmals aus und stellte zum Schluss den ursprünglichen Zustand wieder her.
Entscheidend war dabei nicht die Idee an sich, sondern die vollständige Automatisierung. Aus Sicht der Entwicklerinnen und Entwickler änderte sich nichts. Sie mussten weder manuell zwischen Umgebungen wechseln noch Tests wiederholen oder Abläufe koordinieren. Das System übernahm die gesamte Sequenz.
Zuverlässigkeit durch kontrollierten Zugriff
Die Automatisierung brachte jedoch zusätzliche Komplexität mit sich. Der Testprozess bestand nicht aus einem einzigen linearen Ablauf, sondern aus zahlreichen kleineren Schritten, die über verschiedene Systeme verteilt waren. Manche Schritte bereiteten die Infrastruktur vor, andere führten Tests aus und wieder andere erforderten die Koordination mehrerer Maschinen.
Die Verdoppelung all dieser Schritte für zwei Betriebssystemversionen führte zu neuen Problemen. Die eigentlich unabhängigen Schritte begannen sich gegenseitig zu stören und miteinander zu konkurrieren, wenn sie parallel ausgeführt wurden.
Das System stellte zwar die richtige Reihenfolge der einzelnen Schritte sicher: zuerst die Maschine starten, dann die Tests ausführen und anschliessend die Maschine herunterfahren. Es berücksichtigte jedoch nicht, dass zwei Gruppen solcher Schritte gleichzeitig eingeplant werden konnten. Ein Test kann nicht ausgeführt werden, wenn ein anderer Job die dafür benötigte Testmaschine gerade heruntergefahren hat.
In einem idealen System würde dieses Problem auf Designebene gelöst: durch eine Neustrukturierung des Ausführungsmodells und eine geringere Fragmentierung. Das hätte jedoch erneut ein wesentlich umfassenderes Redesign erfordert.
Deshalb führten wir stattdessen einen Mechanismus zur Zugriffskontrolle ein. Sobald ein Test auf einem bestimmten Host gestartet wurde, blieb dieser Host für die gesamte Dauer der Ausführung gesperrt. So konnte kein anderer Prozess den Test beeinträchtigen.
Es war nicht die eleganteste Lösung. Sperren führen zu sequenziellen Abläufen und schränken die parallele Ausführung ein – etwas, das normalerweise vermieden werden sollte. In diesem Kontext war es jedoch der richtige Kompromiss. Wir konnten die Zuverlässigkeit des Systems gewährleisten, ohne alles andere anzuhalten.
Perfektion ist der Feind des Guten
Rückblickend war nicht eine einzelne Lösung der interessanteste Aspekt dieser Arbeit. Entscheidend war vielmehr die Erkenntnis, dass Systeme nur selten komplex werden, weil sie jemand bewusst so konzipiert hat. Sie entwickeln sich schrittweise in diese Richtung – langsam, inkrementell und oftmals aus Gründen, die zum jeweiligen Zeitpunkt durchaus sinnvoll waren. Genau das konnten wir bei unserem Testsystem beobachten.
Das heutige System ist nicht perfekt. Es ist jedoch grundlegend leistungsfähiger als zuvor. Tests können nun ohne manuelles Eingreifen auf mehreren Plattformen ausgeführt werden. Die Entwicklerinnen und Entwickler müssen ihre Arbeit nicht mehr duplizieren, und das System bremst die Weiterentwicklung der Plattform nicht länger aus – es unterstützt sie.
Zudem verstehen wir heute viel besser, wo und warum das System eng gekoppelt ist. Wir haben gesehen, wie sich Komplexität ansammelt, wenn Verantwortlichkeiten nicht klar voneinander getrennt sind, und wie selbst kleine Änderungen verborgene Abhängigkeiten sichtbar machen können. Dadurch fällt es uns leichter, das System zu verstehen – nicht nur in seinem heutigen Zustand, sondern auch im Hinblick auf seine künftige Entwicklung.
Die langfristige Richtung ist klar: Das Testsystem soll sich zunehmend wie eine Plattform verhalten. Die Entwicklerinnen und Entwickler definieren, was sie unter welchen Bedingungen testen möchten – und das System kümmert sich um den Rest.
Bis dahin ist es noch ein weiter Weg. Weitere Vereinfachungen werden notwendig sein und irgendwann auch gezielte Massnahmen, um die verbleibenden Kopplungen zwischen den Komponenten zu reduzieren. Doch das war nie das Ziel dieser Phase.
Statt alles auf einmal lösen zu wollen, konzentrierten wir uns auf die wichtigste Veränderung: Tests über mehrere Plattformen hinweg zu ermöglichen. Darum herum entwickelten wir die passende Lösung. Nicht perfekt, aber ein bedeutender Schritt nach vorn.
Lassen Sie die Komplexität
hinter sich
Sie möchten auch von der Open Systems SASE Experience profitieren? Unsere Experten helfen Ihnen gern weiter.
Kontakt
Open Systems