Blogpost vom 23. April 2021
Infrastruktur für die Cloud mit Kubernetes
In diesem Beitrag befassen wir uns mit der optimalen Infrastruktur Ihrer Software in der Cloud – am Beispiel von Kubernetes. Und geben so Entscheidungshilfen auf dem Weg zu der passenden Lösung.
Der Business Case bestimmt die Architektur
Der Aufbau einer Infrastruktur hängt entscheidend vom jeweiligen Business Case ab. Deshalb ist es wichtig, sich von Experten beraten zu lassen, die einschätzen können, worauf es ankommt, bevor eine Entscheidung gefällt wird. Wichtig ist, dass diese Entscheidung nicht nur für den Augenblick richtig ist, sondern auch mit möglichen künftigen Bedürfnissen skaliert.
Ein cloud-agnostischer Ansatz kann von Fall zu Fall ebenso geraten sein. Hierbei beschränken wir uns nicht auf einen einzigen Cloud-Anbieter, sondern picken uns überall nur das heraus, was Mehrwert für den Kunden bringt. Das erhöht unseren Handlungsspielraum und mindert die Abhängigkeit zu einem einzigen Cloud-Anbieter.
Modular und Continuous
Unser Ansatz ist modular. Wir lösen Abhängigkeiten zwischen Komponenten auf und ermöglichen eine individuelle Entwicklung dieser Komponenten durch ein intelligentes CI/CD-Pipelining. So können einzelne Teile kontinuierlich entwickelt und verändert werden, ohne dass andere Bereiche des Systems davon berührt sind. Es folgt ein Beispiel, wie wir eine Systemarchitektur als Client-Server-Modell realisieren können.
Skalierbare Serverlandschaften
In jeder typischen Infrastruktur (ob Cloud oder nicht), wird vom DNS-Server zunächst eine Anfrage ausgelöst, um die IP-Adresse des Servers zu ermitteln. Die Skalierung sollte sich an der benötigten Verfügbarkeit orientieren. Wenn eine höhere Verfügbarkeit gebraucht wird, sollten die Server auf mehrere Regionen oder Cloud-Anbieter verteilt werden – je nachdem, welchen Grad an Verfügbarkeit/Ausfallsicherheit erreicht werden soll.
Content Delivery Network (CDN)
In jeder typischen Infrastruktur (ob Cloud oder nicht), wird vom DNS-Server zunächst eine Anfrage ausgelöst, um die IP-Adresse des Servers zu ermitteln. Die Skalierung sollte sich an der benötigten Verfügbarkeit orientieren. Wenn eine höhere Verfügbarkeit gebraucht wird, sollten die Server auf mehrere Regionen oder Cloud-Anbieter verteilt werden – je nachdem, welchen Grad an Verfügbarkeit/Ausfallsicherheit erreicht werden soll.
Lastenverteilung mit Load Balancer
Wenn es eine Anfrage gibt, die nicht vom CDN bedient werden kann, wird sie als nächstes an den Load Balancer weitergereicht. Dieser kann entweder regional, also mit regionalen IPs, oder global mit Anycast-IPs bestückt sein. In einigen Fällen können Load Balancer zudem auch zur Verwaltung des internen Datenverkehrs eingesetzt werden.
Neben Routing und Proxying des Datenverkehrs an den entsprechenden Backend-Service kann der Load Balancer auch Aufgaben wie SSL-Termination, die Integration mit CDN und sogar die Verwaltung einiger Aspekte des Netzwerkverkehrs übernehmen. Dies ist in der Praxis jedoch genau zu prüfen, weil mit jedem Overhead auch das Sicherheitsrisiko steigt.
Es gibt zwar Hardware-Load-Balancer, aber Software-Load-Balancer bieten mehr Flexibilität, Kostenreduzierung und Skalierbarkeit. Ähnlich wie bei den CDNs sind die großen Cloud-Anbieter zwar in der Lage, Load Balancer zur Verfügung zu stellen (z. B. GLB für GCP, ELB für AWS, ALB für Azure usw.), aber sie können auch direkt aus Kubernetes heraus bereitgestellt werden.
Wenn wir z. B. einen Ingress in Azure erstellen, wird im Hintergrund ein Load Balancer geschaffen, der den Datenverkehr empfängt und bei Bedarf auch andere Funktionen wie CDN, SSL-Redirects usw. übernehmen kann. Sollten initial nicht genügend Metriken vorliegen, um die passende Skalierung einzustellen, bietet es sich an, klein anzufangen und die Load Balancer schrittweise zu skalieren, wenn es z. B. um Architekturen wie diese geht:
Netzwerk- und Sicherheitsarchitektur
Der nächste wichtige Punkt, um den wir uns in der Architektur kümmern müssen, ist die Vernetzung selbst. Wenn wir die Sicherheit erhöhen wollen, sollten wir uns für ein privates Cluster entscheiden. Denn dort können den ein- und ausgehenden Verkehr moderieren, IP-Adressen hinter NATs maskieren, Netzwerke mit mehreren Subnetzen über mehrere virtuelle Netzwerke (z. B. Azure VNET) isolieren und vieles mehr.
Wie wir Ihr Netzwerk einrichten, hängt in der Regel davon ab, welchen Grad an Flexibilität Sie als Betreiber und Kunde suchen und wie wir diesen erreichen wollen. Bei der Einrichtung des richtigen Netzwerks geht es darum, die Angriffsfläche so weit wie möglich zu reduzieren und gleichzeitig einen flüssigen, regulären Betrieb zu ermöglichen.
Zum Schutz Ihrer Infrastruktur gehört neben der Einrichtung eines sicheren Netzwerks auch die Einrichtung von Firewalls mit den passenden Regeln und Beschränkungen, so dass nur der erlaubte Datenverkehr zu/von den jeweiligen Backend-Diensten zugelassen wird, eingehend wie ausgehend.
In vielen Fällen können die privaten Cluster mithilfe von Bastion-Hosts (aka Jump-Hosts) geschützt werden, die alle Operationen im Cluster tunneln können sowie alles, was Sie dem öffentlichen Netzwerk aussetzen müssen. Diese Hosts können typischerweise im gleichen Netzwerk wie das Cluster selbst eingerichtet werden.
Einige Cloud-Anbieter bieten im Kontext einer Zero Trust Security auch individuelle Lösungen an – so etwa Azure Bastion, welche die Aufgaben einer typischen VPN-Implementierungen übernehmen kann.
Sobald all dies aufgesetzt ist, folgt der nächste Schritt: das Einrichten des Netzwerks innerhalb des Clusters – angepasst an den jeweiligen Anwendungsfall. Dies kann folgende Aufgaben umfassen:
- Einrichtung der Service Discovery innerhalb des Clusters (typischerweise mit CoreDNS),
- Einrichtung eines Service-Meshes, sofern erforderlich (z. B. LinkerD, Istio, Consul etc.),
- Einrichtung von Ingress-Controllern und API-Gateways (z. B. Nginx, Ambassador, Kong, Gloo usw.),
- Einrichtung von Netzwerk-Plugins mit CNI, die die Vernetzung innerhalb des Clusters erleichtern (Weave Net, Calico, Flannel etc.),
- Einrichtung von Netzwerk-Policies, die die Kommunikation zwischen den Diensten moderieren sowie die Dienste nach Bedarf über die verschiedenen Diensttypen hinweg exponieren,
- Einrichtung der Inter-Service-Kommunikation zwischen den verschiedenen Diensten unter Verwendung von Protokollen und Tools wie etwa GRPC, Thrift oder HTTP sowie die
- Einrichtung von A/B-Tests, was einfacher sein kann, wenn ein Service-Mesh wie Istio oder Linkerd verwendet wird.
Für ein Beispiel in Azure empfehlen wir einen Blick in diese Dokumentation zu Terraform in Azure, die Anwendern hilft, die verschiedenen Netzwerkmodelle in Azure einzurichten – einschließlich Hub and Spoke.
Cloud-agnostischer Ansatz
Und das Interessante an der Vernetzung in der Cloud ist, dass sie nicht auf einen bestimmten Cloud-Anbieter beschränkt sein muss, sondern sich bei Bedarf über mehrere Anbieter erstrecken kann. Dabei helfen etwa Projekte wie Kubefed oder Crossplane.
Wenn Sie mehr über einige der Best Practices beim Einrichten von Virtuellen Netzwerken, Subnetzen und dem Netzwerk als Ganzes erfahren möchten, empfehlen wir, diese Seite durchzugehen – die Konzepte sind für jeden Cloud-Anbieter anwendbar, egal welchen.
Kubernetes
Wenn wir gemanagte Cluster wie AKS, EKS oder GKE verwenden, wird Kubernetes automatisch verwaltet, was uns eine Menge Komplexität erspart.
Wenn wir Kubernetes hingegen selbst verwalten, müssen wir uns auf eigene Faust um ein paar Dinge kümmern, wie z. B. das Sichern und Verschlüsseln des etcd-Speichers, das Einrichten von Netzwerken zwischen den verschiedenen Knoten in den Clustern, das regelmäßige Patchen dieser Knoten mit den neuesten Betriebssystemversionen und das Verwalten von Cluster-Upgrades zur Anpassung an die Upstream-Kubernetes-Versionen. Auch wenn sich viel Komplexität, z. B durch den Einsatz von Terraform, reduzieren lässt, ist es dennoch hilfreich und sinnvoll, ein eigenes Team dafür zu verpflichten.
Site Reliability Engineering (SRE)
Die Selbstverwaltung von Kubernetes erfordert auch, dass wir für das genutzte Tool oder die Anwendung entsprechende und spezifische Metriken aufsetzen, die entweder dem Push- oder dem Pull-Mechanismus folgen. Es sei denn, wir verwenden Dienste wie Service Mesh mit Sidecars, dann müssen wir keine benutzerdefinierte Instrumentierung vornehmen.
In solchen Szenarien können Tools wie Prometheus und/oder InfluxDB als Zeitreihendatenbanken fungieren, um alle Metriken zu sammeln, bspw. zusammen mit OpenTelemetry, um sie dann mithilfe so genannter Exporter aus der Anwendung und den verschiedenen Tools offenzulegen. Tools wie der Prometheus Alertmanager oder Alerts in Influx können überdies Benachrichtigungen und Alarme an mehrere Kanäle senden, während Grafana das Dashboard bereitstellt, um alles an einem Ort zu visualisieren und den Nutzern einen vollständigen Überblick über die Infrastruktur als Ganzes zu geben. Zusammengefasst würde der Observability-Stack mit Prometheus dann so aussehen:
Komplexe IT-Umgebungen wie diese erfordern auch die Verwendung von Log-Aggregationssystemen, damit alle Logs an einen einzigen Ort gestreamt werden können, um das Debugging zu erleichtern. Hier neigen manche Fachkollegen dazu, Graylog, den ELK- oder EFK-Stack mit Elastic Logstash oder FluentD zu verwenden, um die Log-Aggregation und Filterung auf Basis der jeweils vorliegenden Einschränkungen durchführen zu können. Aber es gibt auch neue Akteure in diesem Bereich, wie etwa Grafana Loki oder Promtail.
Verteiltes Tracing
Aber was ist mit dem Tracing von Anfragen, die sich über mehrere Microservices und Tools erstrecken? Hier wird verteiltes Tracing sehr wichtig, vor allem in Anbetracht der Komplexität, die Microservices mit sich bringen. Tools wie Zipkin und Jaeger waren Pioniere auf diesem Gebiet, der jüngste Neuzugang in diesem Bereich ist Grafana Tempo.
Die Log-Aggregation liefert zwar Informationen aus verschiedenen Quellen, aber nicht unbedingt den Kontext der Anfrage – und hier hilft das Tracing wirklich. Allerdings müssen wir berücksichtigen, dass das Hinzufügen von Tracing zum Technologie-Stack einen erheblichen Overhead mit sich bringt, da die Kontexte zwischen den Diensten zusammen mit den Anfragen propagiert werden müssen.
Tracing mit Jaeger
Doch die Zuverlässigkeit einer Webanwendung endet nicht mit der Überwachung, Visualisierung und Alarmierung. Wir müssen vielmehr bereit sein, mit regelmäßigen Backups und Failovers auf Ausfälle in allen Teilsystemen zu reagieren, damit es im Angriffsfall keinen großen Datenverlust gibt. Hierbei können Tools wie Rancher Longhorn eine echte Hilfe sein. Denn es hilft uns, regelmäßige Backups verschiedener Komponenten im Cluster zu erstellen, einschließlich Workloads, Speicher usw., indem es dieselben Kubernetes-Konstrukte nutzt, die auch wir verwenden.
Blockspeicher von Longhorn
Viele nicht-cloud-gehostete Kubernetes-Cluster unterstützen keinen persistenten Speicher. Externe Speicher-Arrays sind indes nicht portabel und können extrem teuer sein.
Longhorn schafft hier Abhilfe und bietet einen vereinfachten, leicht zu implementierenden sowie 100-prozentig quelloffenen, cloud-nativen und persistenten Blockspeicher – ohne den Kostenaufwand von Open Core oder proprietären Alternativen.
Datensicherheit mit Longhorn
Die inkrementellen Snapshot- und Backup-Funktionen von Longhorn sorgen außerdem für die Sicherheit der Volumendaten innerhalb und außerhalb des Kubernetes-Clusters. Geplante Backups von persistenten Speichervolumen in Kubernetes-Clustern werden zudem durch ein intuitives Management-UI vereinfacht.
Externe Replikationslösungen erholen sich oft nur langsam von einem Festplattenausfall, weil sie den gesamten Datenspeicher neu replizieren. Dies kann Tage dauern, in denen der Cluster schlecht performt und ein höheres Ausfallrisiko hat. Mit Longhorn können wir die Granularität maximal steuern, ein Disaster-Recovery-Volume in einem anderen Kubernetes-Cluster anlegen und im Notfall darauf ausweichen.
Für die Sicherung von Daten arbeitet Longhorn mit Back-ups auf Basis von Snapshots – wobei jedes Back-up eine ganze Kette einzelner, komprimierter Snapshots enthält. Wenn wir nun das Volume aus einem solchen Back-up wiederherstellen, enthält dieses indes zunächst nur einen einzigen Snapshot – eine zusammengefasste Version aller Snapshots in der ursprünglichen Kette und spiegelt die Live-Daten des Volumes zum Zeitpunkt der Erstellung des Backups wider.
Der Vorteil: Während Snapshots Terabytes groß sein können, bestehen Back-ups aus 2 MB-Dateien.
Wenn das Hauptcluster ausfällt, können wir die App im Desaster-Recovery-Cluster schnell mit einem definierten RPO und RTO hochfahren. So sieht die Architektur von Longhorn aus:
Clustering und Skalierung in verteilten Speichern
Hier haben sich Dateisysteme wie Ceph bereits bewährt. Wenn man jedoch bedenkt, dass Ceph nicht mit Blick auf Kubernetes entwickelt wurde und sehr schwer zu implementieren und zu verwalten ist, kann alternativ auch ein Projekt wie Rook helfen.
Zwar ist Rook nicht an Ceph gekoppelt und unterstützt auch andere Dateisysteme wie EdgeFS, NFS usw., aber Rook mit Ceph CSI ist dennoch ein „Match made in Heaven“.
Wie wir im nächsten Bild sehen können, übernimmt Rook die Installation, Konfiguration und Verwaltung von Ceph im Kubernetes-Cluster. Der Speicher wird darunter automatisch gemäß den Benutzerpräferenzen verteilt. All dies geschieht, ohne dass die Anwendung an Komplexität zunimmt.
Dashboard Image Registry
Eine Registry bietet eine Benutzeroberfläche, über die wir verschiedene Benutzerkonten pflegen, Images pushen/ziehen, Quoten verwalten, uns mit Webhooks über Ereignisse benachrichtigen lassen, Schwachstellen-Scans durchführen, die gepushten Images signieren und Vorgänge wie das Spiegeln oder Replizieren von Images über mehrere Image-Registries hinweg handhaben können.
Die namhaften Cloud-Anbietern bieten alle eine Image-Registry als Service an (z. B. Microsoft ACR, Google GCR, Amazon ECR usw.), wodurch ein Großteil der Komplexität entfällt. Wenn ein Cloud-Anbieter keine solche bereitstellt, können wir auch auf Drittanbieter-Registries wie Docker Hub, Quay usw. zurückgreifen oder eine eigene Registry aufsetzen.
Die eigene Registry selbst betreiben
Dies kann erforderlich sein, wenn die Registry vor Ort bereitgestellt werden soll, um mehr Kontrolle über sie zu haben oder etwa die Kosten für das Scannen von Schwachstellen zu reduzieren. Ist dies der Fall, dann kann eine private Image-Registry wie JFrog Artifactory oder Neuxs helfen. So sieht die Architektur von JFrog Artifactory aus:
CI/CD-Architektur
Kubernetes ist eine großartige Plattform für das Hosting aller Workloads in beliebigem Umfang. Sie erfordert allerdings auch eine Standardmethode für das Deployment mit einem optimierten Continuous-Integration/Continuous-Delivery-Workflow (CI/CD). Das Einrichten einer CI/CD-Pipeline empfiehlt sich hier ohne Einschränkungen.
Einige Dienste von Drittanbietern wie Gitlab CI, Travis CI, Circle CI oder Github Actions verfügen über eigene CI-Runner. Sie definieren einfach die Schritte in der Pipeline, die wir aufbauen möchten. Dazu gehören typischerweise: das Erstellen des Images, das Scannen des Images auf mögliche Schwachstellen, das Ausführen der Tests und das Pushen an die Registry sowie in einigen Fällen das Bereitstellen einer Vorschauumgebung für Freigaben.
Während die Schritte typischerweise die gleichen bleiben, wenn wir einen eigenen CI-Runner verwalten, müssen wir diesen so konfigurieren, dass er entweder innerhalb oder außerhalb des Clusters mit den entsprechenden Berechtigungen ausgestattet wird, um die Assets an die Registry zu pushen.
Fazit
In diesem Beitrag haben wir uns mit der Architektur einer kubernetes-basierten Cloud-Native-Infrastruktur beschäftigt. Wie wir oben gesehen haben, adressieren die verschiedenen Tools unterschiedliche Probleme in diesem Kontext. Sie sind wie Lego-Bausteine, von denen sich jeder auf ein bestimmtes Problem konzentriert und einen Großteil der Komplexität abstrahiert. Ergo reduziert. Dies ermöglicht es Anwendern, Kubernetes schrittweise zu nutzen und nur die Tools aus dem gesamten Stack zu verwenden, die sie in ihrem individuellen Anwendungsfall benötigen.
Wenn Sie Fragen haben oder Hilfe oder Beratung suchen, sprechen Sie uns an!