Uberspace 7 - Episode 2

Vielleicht fragt ihr euch, wie man so ein System denn überhaupt entwirft, was die einzelnen Phasen der Entwicklung sind und wie unsere Arbeitsweise so aussieht. Mit herzlichen Grüßen aus dem Bergwerk folgt hier ein kleiner Einblick in unsere tägliche Arbeit und den aktuellen Stand der Dinge.

Die Phasen der Entwicklung

... und was wir anders machen würden, wenn wir nochmal von vorne anfangen würden.

Features definieren

Im ersten Schritt haben wir uns Gedanken darüber gemacht, was ein Uberspace eigentlich ist: Was unsere User erwarten, was wir selbst erwarten und was wir davon zuverlässig liefern können. Wir haben also unsere Features umrissen und uns ist dabei aufgefallen, dass wir das gerade zum ersten mal machen. Bisher steht nirgendwo genau beschrieben, was der Funktionsumfang von Uberspace ist. Wir haben zwar einen Fließtext auf unserer Internetseite, der ist allerdings weder vollständig, noch wirklich aussagekräftig. Auch die Artikel im Wiki sind nicht einheitlich und es fehlen wichtige Informationen wie z.B. die aktuell installierten Versionen. Hinzu kommt, dass sich jedes Feature anders bedienen lässt - so ist z.B. die Vorgehensweise, aktuelle Versionen von bestimmten Tools zu bekommen, unterschiedlich. Das ist bei einem über die Jahre organisch gewachsenen System alles erklärbar, war uns aber nicht so bewusst, als wir angefangen haben, über Uberspace 7 nachzudenken.

Rethink all the things!

Die Ursachen dafür sind vielfältig und vielschichtig und der Zeitpunkt, in einem großen Rundumschlag einfach alles zu überdenken und überarbeiten ist gekommen. Das dauert natürlich länger, als stumpf einfach alles, was bisher auf unseren CentOS 6-Servern läuft, zu portieren. Die Zeit, die wir jetzt investieren, sparen wir am Ende des Tages aber wieder ein und erhalten im Gegenzug ein uniformes, skalierendes, getestetes und dokumentiertes System. So viele Dinge auf einmal!

Tests schreiben

Nachdem Features definiert wurden sollte man anfangen, Tests zu schreiben und damit die Anforderungen an die Features genau definieren. Dabei unterscheiden wir zwischen Unit testing:

  • Ich erwarte, dass ich auf Port 443 per HTTPS mit einem Webserver spreche, der nur gewisse Ciphers zulässt und andere abweist.
  • Ich erwarte, dass auf Port 22 ein sshd läuft.
  • Ich erwarte, dass auf Port 81 ein httpd läuft, der nicht von außen erreichbar ist.

... und behavioural tests - also User Stories:

  • Der User test führt php aus → Die Ausgabe von php --version sollte den String PHP 7.0 beinhalten.
  • Der User test fügt den String PHPVERSION=5.6 in die Datei ~/etc/versions ein und führt php aus → Die Ausgabe von php --version sollte den String PHP 5.6 beinhalten.
  • Der User führt uberspace-setup-svscan aus → Es läuft ein Prozess supervise svscan-test von User root und ein Prozess /command/svscan /home/test/service also User test.
  • und so weiter und sofort...

Diese Vorgehensweise hat gleich eine Reihe von Vorteilen:

  1. Wir haben damit eine genaue Definition von Features.
  2. Wir können unsere Features automatisiert testen.
  3. Wir können unsere Features einheitlich gestalten und die Komplexität minimieren.

Gerade Punkt 2 ist von großer Bedeutung! Unser Vorgehen bei Uberspace 6 sieht aktuell folgendermaßen aus: Wenn eine neue Version von php veröffentlicht wird, inkrementieren wir auf einem Buildserver die Versionsnummer, stoßen den Buildvorgang mit einem Shellscript an und verteilen die Software auf eine Hand voll Server. Wenn bis zum nächsten Tag nichts passiert ist, verteilen wir die Software auf alle Server. Wir testen momentan also streng genommen überhaupt nicht bzw. nur stichprobenartig. In Zukunft haben wir einen sehr viel besseren Überblick, ob und in wieweit alles nach einem Update zusammen spielt oder ob es an irgendwelchen Ecken knarrzt und knackt. Und hier geht's vor allem um Ecken und Kanten, die wir nicht unbedingt auf dem Schirm haben und auf die man nicht sofort kommt.

Ein weiterer Vorteil ist, dass wir inzwischen alles in VMs entwickeln, die wir nach Belieben wegwerfen können. Und durch das Einbinden eines CIs in unsere Entwicklungsumgebung sehen wir direkt und nach jedem Code Commit, ob alles so läuft, wie es soll:

Screenshot aus unserem CI

Features bauen

Sobald man die Test definiert hat, fängt man an, die Features um die Tests herum zu implementieren:

  • Was muss ich tun, damit der Test grün wird?
  • Wie muss ich das Paket bauen?
  • Wie sieht die Konfiguration aus?
  • Welche Dateien brauche ich an welcher Stelle?
  • Wie verringere ich die Komplexität?
  • Wie sorge ich dafür, dass der Code übersichtlich bleibt?
  • Wie sorge ich dafür, dass es keine Abhängigkeiten zwischen Features gibt?

Bisher haben wir Features über one shot Shellskripte realisiert, im letzten Jahr sind wir dazu übergegangen, mehr und mehr Ansible zu benutzen aber seien wir ehrlich - der Großteil von uns hat wenig Erfahrung mit ordentlicher Programmierung und wir haben in letzter Zeit so viel dazu gelernt, dass wir unsere ersten Gehversuche mit Ansible inzwischen eher belächeln und vieles überdenken müssen. Programmierer wird man nicht über Nacht und dem geneigten Leser wird aufgefallen sein, dass die Prozesse, der hier beschrieben werden, nicht mehr viel mit klassischer Systemadministration zu tun haben. Und da wir mit wachsender Codebasis und wachsender Erfahrung natürlich auch einiges dazugelernt haben, bietet es sich an, hier direkt Continuous Improvement zu implementieren.

Und das sieht so aus:

  1. Ansible role bauen, die die Funktionalität eines Shellskriptes abbildet, ...
  2. sehen, dass es besser geht, ...
  3. Code refaktorisieren und damit die Komplexität minimieren, ...
  4. wieder von vorne anfangen.

Der Stand der Dinge

Nun wie heißt es immer so schön? Hinterher ist man immer schlauer. Der Prozess, den ich bis hierher beschrieben habe, ist der Weg, zu dem wir gerne hin wollen aber nicht unbedingt der, den wir bisher strikt gegangen sind. Wir haben zu viel Zeit darauf investiert, aktuelle Infrastruktur erst nachzubauen und danach den Umfang zu definieren. Wir haben also einen Prototypen der läuft:

... und jede Menge Code: Commit Statistik

... und jede Menge Builds:

... aber bisher keine Tests und damit auch keine klare Definition von Features. Das ist zwar etwas unglücklich aber auch kein Weltuntergang - im besten Fall können wir unseren bereits vorhandenen Code dazu benutzen, um die noch zu definierenden Tests grün zu machen.

Wie geht's jetzt weiter?

Wir werden die nächsten Wochen dazu nutzen, Test zu schreiben und damit den genauen Funktionsumfang von Uberspace 7 festzurren. Sobald das geschehen ist, wird es auch eine Beta geben, bei der ihr euch live anschauen könnt, was wir uns so überlegt haben und wie sich das alles so anfühlt.


Header von Wikimedia (Public Domain)