Entwicklungs- und Produktionsumgebung meiner SaaS-Projekte

Zwei Hochhäuser die identisch aussehen

Nachdem ich bereits beschrieben habe, wie ich auf die Idee für ein Micro-SaaS gekommen bin, möchte ich heute ein wenig auf die verwendete Technik eingehen. Von der Reihenfolge her sollte das Projekt zuerst validiert werden, aber als Entwickler habe ich mir schon vorher viele Gedanken gemacht, welche Entwicklungs- und Produktionsumgebung ich verwenden möchte, damit der Deployment- und Hostingprozess so einfach wie möglich funktioniert.

Einige Probleme schlecht abgestimmter Entwicklungs- und Produktivumgebungen

Wenn du bereits an mehreren Monolith-Projekten gearbeitet hast oder wenn du an einem Monolith-Projekt über einen Zeitraum von mehreren Jahren gearbeitet hast, dann kennst du sicherlich einige der Probleme aus eigener Erfahrung.

Fehlende Libraries auf dem Produktivsystem

Es ist schon einige Male vorgekommen, dass ich Features entwickelt habe und erst nach dem Deployment feststellen musste, dass bestimmte Libraries auf dem Produktivserver fehlten. In der Entwicklungsumgebung waren diese bereits vorhanden, da sie für andere Projekte installiert waren.

Projektabhängige Pakete gibt es nur für Linux, aber nicht für Windows.

Da die meisten Webanwendungen auf Linux basierten Systemen laufen, werden auch die meisten Projekte im Webbereich primär für Linux basierte Systeme entwickelt. Ich hatte schon öfters den Fall, dass ich ein geeignetes Paket für ein Problem gefunden hatte, es aber in meiner Entwicklungsumgebung nicht einsetzen konnte.

Du arbeitest an mehreren Projekten mit unterschiedlichen Abhängigkeiten und Versionsnummern

Jedes Projekt hat seine spezifischen Eigenheiten und Abhängigkeiten. Angefangen beim Webserver (Apache oder NGINX), über die Version der ausführenden Skriptsprache (PHP Version, Python Version, Node Version) und natürlich die Versionsnummern der nachinstallierten Bibliotheken via php composer, python pip oder Node npm. Schließlich gibt es noch Abhängigkeiten von Diensten wie Datenbanken.

Sobald du anfängst, lokal auf deiner Entwicklungsmaschine zwischen den Projekten zu wechseln, kommen die verschiedenen Versionsnummern mit dem Produktivsystem in Konflikt.

Bereits belegte Ports

Wenn man für verschiedene Projekte ähnliche oder identische Dienste laufen lassen möchte, die je nach Projekt getrennt voneinander laufen müssen, kommt man auf die Idee, die Ports unterschiedlich zu belegen, um darauf zugreifen zu können. Das wird schnell unübersichtlich.

Der Wechsel von einem Projekt zum anderen ist aufwändig

Seien es mehrere Projekte, an denen parallel gearbeitet wird, oder ein Projekt, das für die Entwicklung neu aufgesetzt werden muss. Es ist frustrierend, wenn die Umstellung mit zusätzlichem Aufwand verbunden ist.

Anforderungen an die Entwicklungs- und Produktivumgebung

Aus den aufgetretenen Problemen ergeben sich folgende Anforderungen.

  1. Die Entwicklungs- und die Produktionsumgebung sollten möglichst identisch sein. 
  2. Alle Abhängigkeiten eines Projektes sollen isoliert von den Abhängigkeiten anderer Projekte laufen.
  3. Mehrere Projekte sollen gleichzeitig auf der Entwicklungsumgebung laufen, ohne einander zu stören.

Docker als kleinster gemeinsamer Nenner 

Als Entwickler ist es fast unmöglich, nicht von Docker gehört zu haben. Deshalb werde ich nicht viel darüber schreiben. Natürlich ist Containerisierung der erste Ansatz, um Abhängigkeiten zu isolieren und die Entwicklungsumgebung so identisch wie möglich mit der Produktionsumgebung zu halten. Die Frage ist also nicht, ob man Docker einsetzt, sondern wie man seine Projekte in Docker organisiert.

Kubernetes oder Docker Swarm als Laufzeitumgebung?

Wie so oft gibt es auch für dieses Problem passende Lösungen. In unserem Fall sind das Kubernetes oder Docker Swarm. Leider sind diese Lösungen eher für größere Projekte gedacht, die über mehrere Server verteilt sind.

Die meisten Projekte, vor allem wenn man etwas Neues entwickelt, bestehen in den allermeisten Fällen aus einem Webserver, der entwickelten Anwendung und einer Datenbank. In der Anfangszeit liegt es zusätzlich noch auf einem Server, auf dem eventuell noch andere Projekte mit ähnlicher Struktur gehostet werden.

Kubernetes und Co. bringen für diesen Anwendungsfall zu viel unnötige Komplexität mit und können ihre Stärken (verteiltes System) nicht ausspielen. Wenn man aber schon Erfahrung mit Kubernetes hat, sollte man sich nicht aufhalten lassen und die Möglichkeiten nutzen.

Die Lösung, die ich für den Anfang gewählt habe, funktioniert nur mit docker und docker compose.

Das Traumpaar Traefik und OpenSSH

Die verwendeten Werkzeuge sind Traefik und OpenSSH. Traefik fungiert als Proxy für den HTTP/S-Verkehr. Das bedeutet, dass Traefik auf den Ports 80 und 443 lauscht. Alle weiteren Container aus den einzelnen Projekten, die ebenfalls auf diese Ports angewiesen sind, teilen Traefik mit, auf welchem Hostnamen sie besucht werden sollen. Wie man Traefik genau benutzt, kann man zum Beispiel in diesem  Video von Christian Lempa anschauen.

OpenSSH läuft ebenfalls in einem Container und dient als SSH-Tunnel für die Datenbanken. Ich benutze es hauptsächlich in der Entwicklungsumgebung. So kann ich mich mit meiner IDE oder Datenbank-GUI mit der Datenbank verbinden, ohne kryptische Ports veröffentlichen zu müssen. Die  Anleitung, der ich dabei gefolgt bin, stammt von Peter Packet.

Traefik wird bei mir auch produktiv eingesetzt. Das bedeutet, dass sich die Konfiguration der Entwicklungs- und Produktivumgebung der einzelnen Projekte nur geringfügig unterscheidet. Diese unterschiedlichen Konfigurationswerte landen alle in einer .env Datei. Das hat enorme Vorteile beim Hosten neuer Projekte. Die Einrichtung eines neuen Projekts besteht im Wesentlichen aus vier Schritten

  1. Domain DNS-A-Records setzen (Falls nötig)
  2. Auf dem Server git clone project-repository ausführen.
  3. .env Datei anpassen
  4. docker compose up ausführen

Fertig. Bestaune das Projekt im öffentlichen Internet.

Fazit

Mit dieser Lösung kann ich jede Webanwendung entwickeln und hosten. Der eingeschränkte Einsatz von Plesk oder cPanel ist überflüssig. Denn mit dem beschriebenen Ansatz kann jeder beliebige Docker-Container (Monitoring-Tools, Marketing-Tools oder Tracking-Tools) gestartet und auf einen Hostnamen gesetzt werden.

Mit einem vorbereiteten Basissystem der Anwendung kann direkt mit der Entwicklung begonnen werden, ohne sich um Hosting und Setup kümmern zu müssen.

Die Kosten für einen kleinen V-Server sind so gering wie der Dauerbetrieb eines einzelnen Containers bei Google Cloud oder AWS. (Der Dauerbetrieb ist vor allem dann notwendig, wenn das “Cold Start”-Problem vermieden werden soll). Mit dem Unterschied, dass auf einem kleinen einzelnen V-Server nicht nur mehrere Container, sondern auch mehrere Projekte gehostet werden können.