Zurück zur Übersicht

Refactoring von Terraform-Code

Hausgerüst

Die IT-Landschaft vieler Unternehmen wächst oft parallel mit der Unternehmensentwicklung. Insbesondere dann, wenn im agilen Prozess schnell iteriert wird, stoßen Entwicklungsteams nach einiger Zeit an ihre Grenzen:

  • Aus einer Feature-Sicht führen notwendige Maßnahmen wie Code-Refactorings neben dem Tagesgeschäft zu geringerer Geschwindigkeit und gesenkter Produktivität, da keine neuen Features entstehen.
  • Aus der Entwicklungsperspektive erschwert eine unaufgeräumte Codebase vor allem das täglich Arbeiten und das Onboarding neuer Teammitglieder. Beides führt zu gesenkter Produktivität und geringerer Geschwindigkeit.

Dieser Interessenkonflikt ist insbesondere bei Code interessant, der Infrastruktur in der Cloud definiert. Sogenannte Infrastructure as Code Tools (kurz: IaC) wie Terraform ermöglichen das programmatische Definieren und Aufbauen von Infrastruktur in der Cloud. Anders als Anwendungscode ist Infrastruktur-Code jedoch wesentlich empfindlicher gegenüber Änderungen, insbesondere bei historisch gewachsenen Codebasen. Häufig ist nicht klar, wie einzelne Teile der Infrastruktur miteinander zusammenhängen. Gleichzeitig ist jede Änderung immer mit direkten Auswirkungen auf die Anwendungsinfrastruktur verbunden, ggf. sogar mit Ausfallzeiten. Umso wichtiger ist ein strukturiertes und geplantes Herangehen an das Refactoring von Infrastructure as Code.

Haus Berge Terraform Refactoring

Zwei Blickwinkel auf Refactorings

Eines der häufigeren Refactorings im IaC-Umfeld ist das Auftrennen von einem großen (monolithischen) Projekt in viele kleine Projekte. Aus einer organisatorischen Sicht ist dies immer dort sinnvoll, wo einzelne Teams die Verantwortlichkeit für Teile einer Anwendung teilen. Frei nach dem DevOps-Motto “you build it, you run it” sollte jedes Team selbst die Verantwortung für ein Anwendungsteil übernehmen können - auch für die Infrastruktur. Damit das möglich ist, muss der Code entsprechend getrennt werden. Das viel zitierte “Conway’s Law” schlägt in dieselbe Kerbe: Trennt man aufgrund von Wachstum des Unternehmens ein Team in mehrere Teams auf, so wird der Code irgendwann folgen müssen, denn die Teamstruktur folgt zwangsweise der Code-Struktur.

Auch aus technischer Sicht ist das Auftrennen der Infrastruktur in kleinere Teile sinnvoll. Tritt etwa bei einem Deployment ein Fehler auf, begrenzt sich der Umfang auf einen isolierten Teil der Anwendung. Der sogenannte Blast Radius wird verringert und parallele Deployments werden überhaupt erst ermöglicht. Auch das teilweise Aufsetzen einer Anwendung, zum Beispiel für Testzwecke, wird durch eine Auftrennung erst möglich. Weiterhin kann die Definition der Infrastruktur nun im selben Repository wie der Anwendungscode selbst liegen, wodurch die Developer Experience stark verbessert wird. Kleine, isolierte Projekte sind besser und schneller zu überblicken, wodurch Kontextwechsel minimiert werden. Onboardings profitieren hiervon ebenfalls.

Zusammenführen von Anwendungs- und Infrastruktur-Code als Best Practice

Vor einem Refactoring stellt sich immer die Frage: Wie sieht das Zielbild aus? Die Erfahrung zeigt deutlich: Strukturiert man Code so, dass alles mit fachlicher bzw. technischer Nähe zueinander verortet wird, etwa in demselben Repository, können anstrengende Kontextwechsel während der Entwicklung minimiert werden. Für Infrastrukturdefinitionen bedeutet dies, dass sie optimalerweise beim Anwendungscode liegen. Dennoch ist eine gewisse Trennung, etwa auf Verzeichnisebene, empfehlenswert (/code, /infrastructure).

 

 

Refactoring von Infrastructure as Code - Die Vorteile auf einen Blick

  • Verbesserte Developer-Experience

  • Beschleunigte Einarbeitungsphase für (neue) Entwickler

  • Geringerer Blast Radius bei Fehlkonfigurationen

  • Kleine, übersichtliche Infrastruktur-Projekte statt monolithischer Infrastruktur

  • Wiederherstellen möglicherweise verwässerter Grenzen zwischen Anwendungs- und Infrastruktur-Code

  • Deployments einzelner, isolierter Projekte und Services möglich

  • Erhöhte Robustheit der Gesamtapplikation

 

Mit den Vorteilen im Blick können Refactoring-Vorhaben in die Praxis umgesetzt werden. Doch wie kann hierbei effektiv, dennoch pragmatisch und effizient vorgegangen werden? Unsere Best-Practices auf Basis der Erfahrungen unserer Entwicklungsteams sind:

Step-by-Step vom monolithischen Terraform-State zu isolierten Projekten

1. Startpunkt finden: Dazu beginnt man mit einer Bestandsanalyse: Wie ist die aktuelle Infrastruktur strukturiert? Gibt es vielleicht Teile der Infrastruktur mit besonders wenigen Abhängigkeiten? Was sind die zentralen Bausteine der Infrastruktur? Wo werden immer wieder Änderungen gemacht? Gibt es vielleicht Wissenssilos bei Mitarbeitenden oder gibt es seitens des Projektmanagements priorisiert zu bearbeitende Infrastruktur?

All diese Fragen zielen darauf ab, einen möglichen Einstiegspunkt für das Refactoring zu finden. Es sollte ein Stück der Infrastruktur gewählt werden, welches nicht zu groß ist und nicht allzu oft geändert wird. So umgeht man Konflikte mit dem täglichen Entwicklungsgeschäft und kann Erfahrungen beim Refactoren sammeln.

2. Deep-Dive: Ist ein passender Teil der Infrastruktur gefunden, geht es daran, diesen so gut wie möglich zu verstehen. Wo gibt es Abhängigkeiten zu anderer Infrastruktur. Wie können diese Abhängigkeiten verwaltet werden? Was soll alles aus dem ursprünglichen Code herausgelöst werden? Gegebenenfalls hilft es, an dieser Stelle ein Architekturdiagramm der Infrastruktur anzufertigen. So werden eventuelle Wissenssilos abgebaut und die Dokumentation gleichzeitig verbessert.

Tipp: Mithilfe der Terraform CLI und GraphViz lässt sich der Terraform State als Bild visualisieren! (link)

3. Umzug des Codes: Ist klar, in welchem Umfang das Herauslösen eines Teils der Infrastruktur geschehen soll, kann mit dem Umzug begonnen werden. Es sollte ein neues Git-Repository angelegt werden, in welchem das neue Projekt mit dem Anwendungscode liegen wird. Falls der Anwendungscode bereits ein eigenes Git-Repository hat, kann auch hierhin verschoben werden. Es ist hierbei empfehlenswert, für die Zeit des Umzuges auf einem neuen Feature-Branch zu arbeiten. Gerade bei umfangreichen Refactorings lohnt es sich, ein “Template-Repository” anzulegen, auf dessen Basis die neuen Projekt-Repositories entstehen. So kann viel Zeit beim Anlegen neuer Projekte gespart werden.

Tipp: Mit dem Tool Terragrunt können auch klassischerweise nicht-parametrisierbarer Terraform-Code parametrisiert werden. Das erleichtert das Verwalten mehrerer Terraform-Projekte stark. Das macht Terragrunt zu einem interessanten Werkzeug, um das Refactoring zu vereinfachen.

4. Verschieben von Terraform-States: Nachdem der Code umgezogen wurde, müssen auch die Einträge der von Terraform verwalteten Ressourcen im Terraform-State verschoben werden. Denn um den Code zu Refactoren, müssen nur in Ausnahmefällen auch AWS-Ressourcen verändert oder gar neu erstellt werden. Terraform bietet hierfür Befehle zum Importieren und Entfernen von State-Einträgen zu Ressourcen.

Es empfiehlt sich, Ressourcen im neuen Projekt zunächst ausschließlich zu Importieren. Ein initiales Ausführen des Befehls ‘terraform plan –out=”plan.tfplan”’  ergibt eine Übersicht über alle Ressourcen, die aus dem ursprünglichen State in den neuen State überführt werden müssen. Die ausgegebene Datei kann während des Verschiebens der Ressourcen als Referenz genutzt werden. Ob Ressourcen noch nicht überführt wurden, kann durch erneutes Ausführen des Plan-Befehls geprüft werden. Ist die Ausgabe nicht deckungsgleich mit der Ausgabe desselben Befehls im Bestandsprojekt, wurden noch nicht alle Ressourcen importiert.
Erst wenn alle Ressourcen im neuen Projekt importiert wurden, sollten diese aus dem alten Terraform-State entfernt werden.

5. Test and Repeat!  Nachdem der Code eines Teilprojektes erfolgreich herausgelöst wurde, muss sichergestellt werden, dass die betroffenen Ressourcen noch wie zuvor funktionieren. Gerade wenn Ressourcen verändert oder neu erstellt wurde, ist dies besonders wichtig. Automatisierte End-to-End Tests oder Tests der Infrastruktur (Stichwort Terratest), aber auch explorative manuelle Tests können dies neben typischen Unit- und Integrationtests der Anwendung selbst, sicherstellen.

Die Schritte werden so lange wiederholt, bis kein Teil der Infrastruktur sinnvoll weiter herausgetrennt werden kann. Der übrig gebliebene Code bildet den Kern der Infrastruktur.

Zeit und Ressourcen für die Durchführung

Gerade bei großen, historisch gewachsenen Projekten ist es schwierig, ein derart umfangreiches Refactoring im Tagesgeschäft abzuwickeln. Unserer Erfahrung nach ist es ratsam, das Refactoring parallel zum Tagesgeschäft, im Idealfall mit dedizierten Mitarbeitenden, vorzunehmen und die Zwischenergebnisse etappenweise zurückfließen zu lassen. Ein timeboxed Ansatz, eingeteilt in einzelne Arbeitspakete und losgelöst von agilen (Scrum) Prozessen für einen begrenzten Zeitraum hilft ebenfalls. Durch das etappenweise Vorgehen profitiert das gesamte Team und Learnings finden leichter Anwendung in laufenden oder neuen Entwicklungsprojekten und können dort meist schnell und einfach umgesetzt werden.

Fazit

Die Ergebnisse des Refactorings sind nicht nur für die Weiterentwicklung bestehender Applikationen interessant, sondern auch für Neuentwicklungen. So entstehen durch die Erstellung vieler kleiner Projekte meist Repository-Templates, die sich wiederholende Tätigkeiten beim Erstellen eines neuen Projekts in Zukunft minimieren. Die Developer-Experience wird erhöht, wodurch Einarbeitungszeiten in Projekte minimiert werden. Das Thema DevOps wird im Unternehmen gestärkt, indem jedes Projekt von einzelnen Entwicklern oder Teams isoliert betrachtet, gewartet und weiterentwickelt werden kann (“You build it, you run it”). Automatisierung kann als Schlüsselfaktor für Produktivitätssteigerung und nachhaltiges Wachstum identifiziert und auf Projektebene eingesetzt werden.

Das Refactoring von bestehendem und gewachsenem Terraform-Code kann somit den Grundstein für eine effiziente und kosteneffektive AWS-Infrastruktur legen.