Uberspace 7 - Episode 4

From stateful to stateless - the whole enchilada

Bei Uberspace habt ihr die Möglichkeit, eure eigenen Domains aufzuschalten, Ports in der Firewall zu öffnen und Zertifikate einzutragen. Bei all diesen Aktionen interagiert ihr mit Konfigurationsdateien, auf die ihr eigentlich keinen Zugriff habt - mit dem Webserver, unserem HTTPS-Frontend und der Firewall.

Wie wir das bisher gemacht haben

Sicherlich kennt ihr unsere uberspace-add-domain, uberspace-add-certificate und uberspace-add-port-Skripte und vielleicht habt ihr schon mal einen Blick hinen geworfen. Falls nicht, hier mal ein kleiner, beispielhafter Ausschnitt:

touch "/etc/httpd/domains.d/${DOMAIN}"
chown ${SUDO_USER} "/etc/httpd/domains.d/${DOMAIN}"

VHOSTCONF=/etc/httpd/conf.d/virtual.${SUDO_USER}.conf
if [[ ${DOMAIN} =~ ^\*\. ]] ; then
    VHOSTCONF=/etc/httpd/conf.d/wildcard.${SUDO_USER}.conf
    if [ ! -e ${VHOSTCONF} ] ; then
        grep -v ^ServerAlias /etc/httpd/conf.d/virtual.${SUDO_USER}.conf > ${VHOSTCONF}
    fi
fi

sed -i -e "s/^ServerName .*/&\nServerAlias ${DOMAIN}/" ${VHOSTCONF}

Hier sind zwei Dinge interessant:

Erstens:

Wir machen das bisher alles in Bash per grep, sed, awk und Verwandtschaft. Das geht zwar irgendwie, ist aber schlecht lesbar und nicht sonderlich intiutiv. Hier noch ein weiteres Beispiel aus uberspace-add-certificate:

domain=`openssl x509 -noout -subject -in $CERT |awk -F CN= '{ print $2 }' |awk -F / '{ print $1 }' |sed 's/^\*/wildcard/' |sed 's/[^a-z0-9\-\.]/_/g'`

Und seien wir ehrlich: Das können nur die Wenigsten aus dem Stand lesen, erklären und debuggen. Damit ist es eigentlich kein guter, wartbarer Code, den man einfach mal irgendjemandem in die Hand drücken kann, der das ganze dann weiterentwickeln soll. Und das ist nicht gut.

Zweitens:

Wir schreiben und lesen direkt in den Konfigurationen. Damit gibt es nur einen Ort, an dem die Informationen vorhanden sind: Direkt in den Konfigurationsdateien. Wenn sich hier nun z.B. mal durch einen Versionssprung das Schema ändert, haben wir ein Problem: Wir müssten dann ein Stück Software zur Migration bauen, welches die Konfigurationen ausliest, in ein neues Format überspielt und dann wieder wegspeichert. Das kostet Zeit und Geld und eigentlich möchte man das alles wegabstrahieren. Obendrein ist die Herangehensweise anwendungsspezifisch und wenn wir z.B. unser HTTPS-Frontend austauschen möchten (hint, hint...), müssten wir schon wieder einen Hammer für ein ganz spezifisches Problem entwickeln.

Ein weiteres Problem: Abbhängigkeiten zwischen Konfigurationen sind nur sehr schwer und komplex abbildbar. Aktuell ist das kein Problem, da diese Abhängigkeiten kaum bestehen aber auch hier wird in Zukunft ein bisschen was passieren. Lange Rede, kurzer Sinn: Das muss auch anders gehen.

Wie wir das in Zukunft machen

Wir haben beschlossen, hier eine Abstraktionsebene einzuziehen und die Informationen zentral auf jedem System vorzuhalten. Und zwar the whole enchilada: Zertifikate, Domains, Ports. Hier ein Beispiel aus unserem aktuellen Prototypen:

{
  "brigitte": {
    "domains": {
      "einedomain.at": {
        "mail": true, 
        "web": true
      }, 
      "nocheinedomain.de": {
        "web": true
      }
    }, 
    "ports": {
      "56322": { "protocol": "both" }, 
      "56311": { "protocol": "tcp" }
    },
    "certificates": [
      {
        "key": "keyfoo", 
        "crt": "crtbaaar"
      }
    ]
  }, 
}

Das bringt uns (und euch!) gleich eine ganze Reihe an Vorteilen:

  • States befinden sich an einer Stelle, das System ist ansonsten weitestgehend stateless - zumindest was "unsere" Tools angeht: Wir können mal eben alle Konfigurationsdateien neu schreiben ohne, dass es uns all zu große Kopfschmerzen bereitet.

  • Configs werden aus den Facts generiert: Aus den Daten können wir einfacher uniforme Konfigurationsdateien generieren, was uns die Wartung unserer Systeme sehr vereinfacht. Wir können außerdem rechnt schnell einzelne Programme austauschen und mit den Daten einfach neue Konfigurationsdateien für die neuen Programme generieren, ohne dass wir uns groß um Migration kümmern müssen.

  • Zentralisierte States können schneller und weniger fehleranfällig ausgelesen werden: Wir müssen nicht mehr ständig alles überall parsen und Daten aus allen möglichen Ecken des Systems zusammen kratzen sondern haben alles in einem vordefinierten Format an einer Stelle.

Außerdem müssen wir durch höhere Programmiersprachen wie z.B. Ansible und Python das Rad nicht ständig neu erfinden. Einen String auf Validität zu prüfen ist in Bash gar nicht so einfach, in echten Programmiersprachen aber eine Fingerübung.

Ihr seht: Wir machen uns Gedanken. Viele der Änderungen, die wir mit Uberspace 7 umsetzen werden, sind grundlegende Designentscheidungen und die wollen nicht über's Knie gebrochen werden.

Weitere Einblicke in unsere kleine Hostingschmiede sind schon in Arbeit - Stay tuned! 🌮


Header von Dale Cruse ((CC BY 2.0))