Unsere Infrastruktur-Evolution: Vom Einzelserver zu Kubernetes
In diesem Artikel zeigen wir, wie wir unsere Infrastruktur für die Entwicklung und Bereitstellung von Webanwendungen im Laufe der Jahre weiterentwickelt haben. Im Mittelpunkt steht nicht der exakte Zeitverlauf, sondern unser Entwicklungsweg. Diese Erfahrungen können auch für andere kleine und mittlere Unternehmen wertvoll sein.
In Zukunft werden wir weitere Artikel veröffentlichen, die einzelne Punkte vertiefen.
Ein Server für Alles
Von Anfang an nutzen wir Hetzner als Provider und mieten dort unsere Server, auf denen wir selbst die nötige Software installieren und konfigurieren.
Als wir mecodia gegründet haben, betrieben wir die gesamte notwendige Software zunächst auf einem einzelnen Server. Dazu gehörten beispielsweise ein E-Mail-Setup mit SMTP/IMAP-Komponenten, ein Issuetracking-System und eine eigens gebaute Software für Zeiterfassung und Kundenverwaltung.
Auch die ersten kleinen Kundenprojekte konnten ohne Probleme auf diesem Server betrieben werden. Dieser Server war ein typischer ‘Pet-Server’, also ein Haustier mit Namen, das persönlich gehegt und gepflegt wurde.
Das erste große Projekt
Mit wachsenden Projekten reichte die Kapazität eines einzelnen Servers nicht mehr aus. Bald stießen wir an die Grenzen von Arbeitsspeicher und CPU-Leistung. Wir erkannten auch, dass eine zentrale Server-Lösung für Feripro nicht mehr ausreichend ist. Daher haben wir erstmals auf eine verteilte Infrastruktur gesetzt. Die Datenbank wurde auf einem dedizierten Server gehostet, während die Instanzen der Anwendung auf mehrere Server verteilt wurden. Zwar blieb jeder Kunde auf einem Server, doch konnten für neue Kunden problemlos weitere Server eingerichtet werden.
Die Server wurden nicht manuell eingerichtet, sondern mit Software und Konfiguration vollständig automatisiert erstellt. Für die Umsetzung dieser Anforderungen haben wir Salt eingesetzt. Die jeweiligen Server wurden mit den erforderlichen Paketen installiert und konfiguriert. Auch neue Kunden wurden anhand einer zentralen Konfiguration auf dem entsprechenden Server aufgesetzt.
In der Entwicklungsumgebung setzten wir lokal mehrere virtuelle Server mit VirtualBox auf, die mit demselben Salt-Setup eingerichtet wurden. Der Vorteil war, dass das Setup in Produktion und Entwicklung gleich war. Allerdings war die gleichzeitige Nutzung mehrerer virtueller Maschinen (VMs) eher schwerfällig.
Der Start mit Containern
Schon in den frühen Tagen von Docker starteten wir, neue Anwendungen lokal und in Produktivumgebungen als Container zu betreiben. Container sind deutlich leichter als VMs. Sie benötigen weniger Speicherplatz, starten schneller und lassen sich problemlos aus vorgefertigten Images erstellen. Mit docker-compose kann man mehrere Container gleichzeitig verwalten, um ein Setup mit getrennter Anwendung und Datenbank zu haben.
Ab dieser Zeit setzten wir bei neuen Kundenprojekten meist auf kundenspezifische Server, auf denen die notwendigen Container für Datenbank und Anwendung liefen. Abgesehen von kleineren Konfigurationsunterschieden entsprach die Entwicklungsumgebung exakt dem Produktivsetup.
Kubernetes-Setup v1
Mit der Zeit wurde es etwas umständlich, die einzelnen Server zu pflegen, die jedes Kundenprojekt hatte. Wir wollten also lieber ein zentrales System um alle Container zu verwalten. Während docker-compose für die Container-Orchestrierung einzelner Projekte gut funktioniert, ist es nicht geeignet, viele verschiedene Projekte zentral zu verwalten.
Erste Berührungspunkte
An einem unserer Transformation-Days hat sich eine Gruppe gebildet, die sich ein paar Tage mit Kubernetes beschäftig hat. Kubernetes ist ein Open-Source-System zur Orchestrierung von Containern auch über mehrere Server verteilt. Wir hatten kein spezifisches Vorwissen und versuchten, etwas Funktionstüchtiges damit zu bauen. Zu Beginn wirkte der volle Funktionsumfang von Kubernetes komplex. Doch am Ende unserer Arbeitsphase hatten wir mit Rancher und einem Treiber für Hetzner-Nodes einige Cluster erstellt und einfache Anwendungen darin bereitgestellt.
Produktives Setup
Nachdem uns unser Experiment mit Rancher überzeugt hatte, haben wir unsere erste produktive Umgebung damit aufgebaut. Wir hatten danach zwei Cluster, eines zum Testen und ein Produktiv-Cluster. Die Konfiguration für Cluster-Workloads erfolgte manuell in YAML-Syntax und wurde in einem Git-Repository abgelegt.
Für Objektspeicher wählten wir MinIO. Zu Objektspeichern wird es auch einen eigenen Beitrag geben.
Rancher war für den Einstieg sehr gut geeignet. Es bot von Anfang an eine grafische Oberfläche, mit der alle grundlegenden Informationen zugänglich waren. Auch die integrierte Lösung für Monitoring war schnell und einfach nutzbar.
Nach einigen Jahren wollten wir jedoch eine Lösung, die wir von Grund auf kontrollieren und deren Aufbau wir vollständig verstehen.
Kubernetes Setup v2
Wir haben in der Zwischenzeit in einem anderen Projekt eine große Infrastruktur auf AWS verwaltet. In diesem Projekt wurde Terraform eingesetzt, um alle Ressourcen zu erstellen und verwalten. Mit Terraform definiert man seine Infrastruktur als Code und lässt diese dann durch das Tool erstellen und konfigurieren.
Unser Ziel war es, nun auch die eigene Infrastruktur mehr durch Code zu verwalten und mit Terraform zu erstellen. Wir haben ein Open-Source-Projekt auf GitHub als Basis genutzt, es kontinuierlich weiterentwickelt und verwenden es seither intern.
Kurz und knapp: Wir nutzen k3s als Distribution, die auf Servern mit Ubuntu-Betriebssystem läuft. Das gesamte Setup wird mit Terragrunt und Terraform in drei Umgebungen (Dev, Staging, Prod) verwaltet.
Wir haben viel in die Automatisierung von Aufgaben gesteckt. Unser Jenkins-Setup macht automatische Releases, baut Images und Helm Charts, und kann auch Anwendungen in allen unseren Clustern installieren und aktualisieren. Auch Cluster- und Betriebssystem-Upgrades werden durch Automatisierung unterstützt. So können wir nach Tests in Dev und Staging Kubernetes-Updates schnell und fehlerfrei im Produktivsystem umsetzen.
Zusammenfassung
Die Zeit hat gezeigt, dass es sich stets als vorteilhaft erwiesen hat, möglichst viel von der Infrastruktur durch Code zu definieren. Dadurch werden der Status Quo dokumentiert, Prozesse standardisiert und menschliche Fehler vermieden.
Der Fokus auf Automatisierung und eine einheitliche Struktur mit nur projektspezifischen kleinen Abweichungen macht die Infrastruktur zu einer Plattform, deren Funktionen wie Monitoring, Logging und Backups von allen Entwicklern selbstständig genutzt werden können. Dadurch wird die Zahl der Situationen, in denen auf einen Admin gewartet werden muss, deutlich verringert.
Statt einem Zoo von einzelnen Servern, die jeweils für sich aktualisiert und gepflegt werden müssen, haben wir nun einen zentralen Überblick darüber.
In Kürze werden wir weitere Artikel veröffentlichen, die die verschiedenen Etappen und Themen auf unserem Weg zur aktuellen Infrastruktur beschreiben.