Container und verteilte Anwendungen – So funktionieren moderne IT-Architekturen

Wer sich mit Docker und Kubernetes beschäftigt, kommt früher oder später mit dem Begriff verteilte Anwendungen in Berührung. Zunächst erscheint das Thema relativ einfach: Statt eine Anwendung auf einem einzigen Server auszuführen, werden ihre Komponenten auf mehrere Systeme verteilt. Doch sobald diese Komponenten miteinander kommunizieren, Daten austauschen und gemeinsam auf Anfragen reagieren müssen, entstehen völlig neue Anforderungen an die Infrastruktur.

Genau hier spielen Container eine wichtige Rolle. Sie ermöglichen es, einzelne Anwendungskomponenten standardisiert bereitzustellen und weitgehend unabhängig voneinander auszuführen. Allerdings reicht es nicht aus, eine Anwendung einfach in mehrere Container aufzuteilen, um daraus automatisch eine zuverlässige verteilte Architektur zu machen.

Denn Container und verteilte Anwendungen bringen Herausforderungen mit sich, die bei einer klassischen Anwendung auf einem einzelnen Server häufig kaum auffallen. Netzwerkverbindungen können abbrechen, einzelne Dienste können ausfallen und Daten müssen unter Umständen über mehrere Systeme hinweg konsistent gehalten werden.

In diesem Artikel schauen wir uns deshalb genauer an, was verteilte Anwendungen eigentlich sind, welche Rolle Docker und Kubernetes dabei übernehmen und warum Themen wie Service Discovery, Skalierung, Fehlertoleranz und Monitoring so wichtig werden. Außerdem betrachten wir konkrete Beispiele aus dem Betrieb moderner IT-Infrastrukturen und gehen der Frage nach, wann sich eine verteilte Architektur tatsächlich lohnt.

Was sind verteilte Anwendungen?

Eine verteilte Anwendung besteht aus mehreren Komponenten, die auf unterschiedlichen Prozessen oder Rechnern ausgeführt werden und über ein Netzwerk miteinander kommunizieren. Für den Anwender erscheint das Gesamtsystem häufig trotzdem wie eine einzige Anwendung.

Ein einfaches Beispiel ist ein Onlineshop. Während der Benutzer im Browser Produkte betrachtet, laufen im Hintergrund möglicherweise mehrere voneinander getrennte Dienste. Ein Web-Frontend stellt die Benutzeroberfläche bereit, ein Backend verarbeitet die Geschäftslogik und eine Datenbank speichert Produkte, Bestellungen und Benutzerinformationen.

Zusätzlich könnte es einen Dienst für die Zahlungsabwicklung, einen Cache für häufig benötigte Informationen und einen weiteren Dienst für den Versand von E-Mails geben.

All diese Komponenten arbeiten zusammen, obwohl sie nicht zwingend auf demselben Server laufen müssen.

Der entscheidende Unterschied zu einer einfachen Anwendung besteht also darin, dass mehrere unabhängig ausgeführte Prozesse gemeinsam eine Funktion bereitstellen. Dabei kann die Kommunikation synchron über HTTP oder gRPC erfolgen. Alternativ können Nachrichten über einen Message Broker wie RabbitMQ oder Apache Kafka ausgetauscht werden.

Wichtig ist allerdings, dass eine verteilte Anwendung nicht automatisch aus Microservices bestehen muss. Auch eine klassische Anwendung mit einem separaten Datenbankserver ist technisch betrachtet bereits auf mehrere Systeme verteilt.

Microservices sind lediglich eine besonders verbreitete Architekturform verteilter Anwendungen, bei der einzelne fachliche Funktionen als eigenständige Dienste umgesetzt werden.

Warum werden Anwendungen überhaupt verteilt?

Wenn eine Anwendung auf einem einzigen Server zuverlässig funktioniert, stellt sich zunächst die Frage, warum man sie überhaupt auf mehrere Systeme verteilen sollte.

Schließlich entsteht dadurch zusätzlicher Aufwand. Netzwerkverbindungen müssen verwaltet werden, mehrere Systeme benötigen Monitoring und Fehler lassen sich häufig schwieriger nachvollziehen.

Trotzdem gibt es gute Gründe für verteilte Architekturen.

Ein wesentlicher Vorteil ist die Möglichkeit, einzelne Komponenten unabhängig voneinander zu skalieren. Wenn beispielsweise die Weboberfläche eines Onlineshops besonders viele Anfragen erhält, müssen nicht zwangsläufig alle anderen Komponenten ebenfalls zusätzliche Ressourcen bekommen.

Stattdessen können weitere Instanzen des Web-Frontends gestartet werden, während Datenbank und Hintergrundverarbeitung unverändert bleiben.

Darüber hinaus ermöglicht eine verteilte Architektur eine organisatorische Trennung. Verschiedene Teams können unterschiedliche Dienste entwickeln und unter geeigneten Voraussetzungen unabhängig voneinander bereitstellen.

Auch die Verfügbarkeit kann verbessert werden. Wenn mehrere Instanzen eines Dienstes auf unterschiedlichen Servern laufen, muss der Ausfall eines einzelnen Servers nicht automatisch die gesamte Anwendung betreffen.

Allerdings gilt diese Aussage nur dann, wenn auch die Abhängigkeiten entsprechend ausgelegt sind. Drei Webserver helfen beispielsweise wenig, wenn alle auf eine einzige Datenbank angewiesen sind und diese Datenbank nicht mehr erreichbar ist.

Deshalb sollte eine verteilte Architektur immer als Gesamtsystem betrachtet werden.

Welche Rolle spielen Container bei verteilten Anwendungen?

Container sind keine zwingende Voraussetzung für eine verteilte Anwendung. Schließlich gab es verteilte Systeme lange bevor Docker und Kubernetes entwickelt wurden.

Dennoch hat die Container-Technologie die Bereitstellung und Verwaltung solcher Anwendungen erheblich vereinfacht.

Ein Container verpackt eine Anwendung zusammen mit den benötigten Laufzeitabhängigkeiten in eine standardisierte Umgebung. Dadurch können die gleichen Container-Images auf unterschiedlichen kompatiblen Hosts ausgeführt werden.

Gerade bei verteilten Anwendungen ist das praktisch, weil häufig viele verschiedene Komponenten betrieben werden müssen.

Stell dir beispielsweise eine Plattform mit zehn unterschiedlichen Backend-Diensten vor. Jeder dieser Dienste verwendet möglicherweise andere Bibliotheken oder Softwareversionen.

Ohne Container müsstest du sicherstellen, dass die benötigten Abhängigkeiten auf den jeweiligen Servern vorhanden sind und sich nicht gegenseitig beeinflussen.

Mit Container-Images lässt sich dagegen ein großer Teil dieser Abhängigkeiten direkt zusammen mit der Anwendung bereitstellen.

Außerdem können neue Instanzen relativ schnell gestartet werden, weil nicht für jeden Dienst ein vollständiges Gastbetriebssystem hochgefahren werden muss.

Allerdings solltest du nicht den Fehler machen, Container mit vollständiger Unabhängigkeit gleichzusetzen. Netzwerk, Storage, CPU-Architektur, Kernel und externe Dienste bleiben wichtige Abhängigkeiten.

Container erleichtern also die Bereitstellung verteilter Anwendungen, lösen aber nicht automatisch die Herausforderungen verteilter Systeme.

Monolithische Anwendungen und Microservices im Vergleich

Wenn über verteilte Anwendungen gesprochen wird, fällt häufig der Begriff Microservices. Dabei entsteht manchmal der Eindruck, dass eine moderne Anwendung unbedingt aus möglichst vielen kleinen Diensten bestehen muss.

Das sehe ich etwas anders.

Eine monolithische Anwendung ist nicht grundsätzlich eine schlechte Architektur. Bei einem Monolithen sind mehrere Funktionen innerhalb einer gemeinsamen Anwendung zusammengefasst. Dadurch können sie häufig direkt miteinander kommunizieren, ohne dass für jeden Aufruf eine Netzwerkverbindung benötigt wird.

Das vereinfacht zunächst die Entwicklung und Fehlersuche. Außerdem lassen sich kleinere Anwendungen häufig schneller und mit weniger Infrastrukturaufwand bereitstellen.

Microservices verfolgen dagegen den Ansatz, fachliche Funktionen in eigenständige Dienste aufzuteilen.

Beispielsweise könnte ein Onlineshop aus einem Produktservice, einem Bestellservice und einem Benutzerservice bestehen.

Jeder Dienst besitzt eine klar definierte Aufgabe und kommuniziert über festgelegte Schnittstellen mit anderen Diensten.

Dadurch lassen sich einzelne Komponenten unabhängig weiterentwickeln und skalieren. Gleichzeitig steigt allerdings die Komplexität der Kommunikation.

EigenschaftMonolithMicroservices
BereitstellungHäufig gemeinsame AnwendungEinzelne Dienste
KommunikationOft interne FunktionsaufrufeHäufig Netzwerkkommunikation
SkalierungOft gesamte AnwendungEinzelne Dienste
FehlersucheMeist zentralerÜber mehrere Dienste verteilt
BetriebsaufwandHäufig geringerMeist höher
TeamorganisationHäufig gemeinsamUnabhängige Teams möglich

Welche Architektur sinnvoller ist, hängt stark von der Größe der Anwendung, den organisatorischen Anforderungen und dem vorhandenen Fachwissen ab.

Ein sauber aufgebauter Monolith kann für viele Projekte die bessere Wahl sein. Schließlich bringt eine Verteilung nur dann Vorteile, wenn diese den zusätzlichen Verwaltungsaufwand rechtfertigen.

Docker Compose als Einstieg in verteilte Anwendungen

Bevor wir uns mit Kubernetes beschäftigen, lohnt sich ein Blick auf Docker Compose.

Docker Compose ermöglicht es, mehrere zusammengehörige Container über eine gemeinsame YAML-Datei zu definieren und zu starten.

Damit lassen sich beispielsweise ein Webserver, eine Anwendung und eine Datenbank als zusammengehörige Umgebung betreiben.

Besonders praktisch ist, dass Compose automatisch ein gemeinsames Netzwerk bereitstellen kann. Dadurch können sich Container über ihre jeweiligen Dienstnamen erreichen, ohne dass feste IP-Adressen konfiguriert werden müssen.

Schauen wir uns ein einfaches Beispiel an.

Praxisbeispiel: Eine Anwendung mit PostgreSQL und Adminer

In diesem Beispiel starten wir eine PostgreSQL-Datenbank zusammen mit Adminer, einer webbasierten Oberfläche zur Verwaltung von Datenbanken.

Es handelt sich um eine einfache Mehrcontainer-Anwendung, die sich gut eignet, um die grundlegende Kommunikation zwischen Diensten zu verstehen.

Erstelle dafür eine Datei mit dem Namen compose.yaml:

services:
  database:
    image: postgres:18
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Please set POSTGRES_PASSWORD}
      POSTGRES_DB: appdb
    volumes:
      - postgres_data:/var/lib/postgresql
    networks:
      - backend

  adminer:
    image: adminer:5
    ports:
      - "127.0.0.1:8080:8080"
    depends_on:
      - database
    networks:
      - backend

volumes:
  postgres_data:

networks:
  backend:

Bevor du die Umgebung startest, musst du die Umgebungsvariable POSTGRES_PASSWORD setzen. Für diesen lokalen Test kann das beispielsweise in der Shell erfolgen. Achte darauf, dass das verwendete Passwort nicht in einer öffentlich zugänglichen Datei oder einem Git-Repository landet.

Anschließend startest du die Container mit:

docker compose up -d

Wenn die benötigten Images heruntergeladen wurden und beide Container gestartet sind, kannst du Adminer unter folgender Adresse öffnen:

http://localhost:8080

Für die Anmeldung verwendest du PostgreSQL als Datenbanksystem, database als Servernamen, appuser als Benutzer und appdb als Datenbanknamen.

Interessant ist dabei, dass Adminer nicht die IP-Adresse des Datenbankcontainers kennen muss. Stattdessen wird der Dienst über den Namen database angesprochen.

Docker Compose stellt dafür innerhalb des gemeinsamen Netzwerks eine entsprechende Namensauflösung bereit.

Außerdem haben wir den Adminer-Port bewusst nur an die lokale Loopback-Adresse gebunden. Damit eignet sich die Konfiguration für lokale Tests, ohne die Verwaltungsoberfläche unnötig über sämtliche Netzwerkschnittstellen des Hosts zu veröffentlichen.

Das PostgreSQL-Volume sorgt wiederum dafür, dass die Daten unabhängig vom Lebenszyklus des Datenbankcontainers gespeichert werden.

Allerdings ist diese Umgebung noch keine hochverfügbare verteilte Infrastruktur. Beide Dienste laufen normalerweise auf demselben Docker-Host. Außerdem stellt depends_on in dieser einfachen Form nur eine Startreihenfolge her und garantiert nicht, dass PostgreSQL bereits vollständig bereit ist.

Für komplexere Umgebungen sind deshalb zusätzliche Prüfungen und Wiederverbindungsmechanismen notwendig.

Warum Netzwerkkommunikation bei verteilten Anwendungen so wichtig ist

Sobald mehrere Dienste miteinander kommunizieren, wird das Netzwerk zu einem wesentlichen Bestandteil der Anwendung.

Bei einem Monolithen erfolgt ein interner Funktionsaufruf normalerweise direkt innerhalb desselben Prozesses. In einer verteilten Architektur kann derselbe Vorgang dagegen eine Netzwerkverbindung erfordern.

Damit entstehen zusätzliche Fehlerquellen.

Ein Dienst kann beispielsweise erreichbar sein, aber zu langsam antworten. Ebenso kann eine DNS-Auflösung fehlschlagen oder eine Verbindung aufgrund eines Netzwerkproblems unterbrochen werden.

Deshalb sollten Anwendungen niemals davon ausgehen, dass jeder Netzwerkaufruf erfolgreich ist.

Besonders wichtig sind dabei Timeouts und Wiederholungsmechanismen.

Ein Timeout verhindert, dass eine Anwendung unbegrenzt auf die Antwort eines anderen Dienstes wartet. Wiederholungsmechanismen können dagegen helfen, vorübergehende Fehler zu überbrücken.

Allerdings sind unkontrollierte Wiederholungen gefährlich. Wenn ein bereits überlasteter Dienst ständig neue Anfragen erhält, kann sich die Situation dadurch weiter verschlechtern.

Außerdem sind nicht alle Operationen problemlos wiederholbar. Wird beispielsweise eine Bestellung zweimal verarbeitet, kann das erhebliche Folgen haben.

Deshalb spielen bei verteilten Anwendungen auch idempotente Operationen eine wichtige Rolle. Eine idempotente Operation kann mehrfach ausgeführt werden, ohne dass dadurch unerwünschte zusätzliche Auswirkungen entstehen.

Gerade bei Zahlungs- und Bestellprozessen sollte dieses Verhalten bewusst berücksichtigt werden.

Service Discovery: Wie finden sich Container gegenseitig?

In einer verteilten Umgebung können Container jederzeit neu gestartet oder ersetzt werden. Dadurch ändern sich unter Umständen auch ihre IP-Adressen.

Würde eine Anwendung dauerhaft mit fest konfigurierten Container-IP-Adressen arbeiten, wären Probleme praktisch vorprogrammiert.

Deshalb verwenden moderne Container-Plattformen Mechanismen zur Service Discovery.

Darunter versteht man die Möglichkeit, Dienste über stabile Namen oder Endpunkte zu erreichen, obwohl sich die zugrunde liegenden Instanzen verändern.

In Docker Compose erfolgt die Kommunikation häufig über die Namen der definierten Services.

In Kubernetes übernimmt ein Kubernetes Service eine vergleichbare Aufgabe auf Clusterebene.

Ein Service stellt einen stabilen Netzwerkendpunkt für eine Gruppe passender Pods bereit. Die zugehörigen Pods werden normalerweise über Labels und Selektoren identifiziert.

Wenn ein Pod ersetzt wird, kann Kubernetes die zugehörigen Service-Endpunkte entsprechend aktualisieren.

Dadurch müssen andere Anwendungen nicht wissen, auf welchem Worker Node ein bestimmter Pod gerade ausgeführt wird.

Allerdings sollte man auch hier zwischen Service Discovery und tatsächlicher Verfügbarkeit unterscheiden. Ein stabiler Service-Name hilft wenig, wenn dahinter keine funktionsfähigen Anwendungsinstanzen vorhanden sind.

Kubernetes als Plattform für verteilte Anwendungen

Während Docker Compose für lokale Entwicklungsumgebungen und überschaubare Anwendungen eine gute Lösung ist, steigen die Anforderungen bei größeren Infrastrukturen häufig deutlich an.

Wenn Anwendungen über mehrere Server verteilt betrieben werden sollen, müssen Ressourcen geplant, Instanzen überwacht und Ausfälle behandelt werden.

Genau hier kommt Kubernetes ins Spiel.

Kubernetes ist eine Plattform zur Orchestrierung containerisierter Anwendungen. Dabei definierst du einen gewünschten Zustand, den Kubernetes mithilfe verschiedener Controller und weiterer Komponenten herzustellen versucht.

Ein Beispiel wäre ein Backend-Dienst, von dem ständig drei Instanzen laufen sollen.

Die gewünschte Anzahl wird in einem Deployment festgelegt. Kubernetes sorgt anschließend dafür, dass entsprechende Pods erstellt und geeigneten Nodes zugewiesen werden.

Fällt eine Instanz aus, wird normalerweise eine Ersatzinstanz erstellt.

Zusätzlich können Services verwendet werden, um die einzelnen Pods über einen stabilen Endpunkt erreichbar zu machen.

Damit übernimmt Kubernetes viele Aufgaben, die bei einem manuellen Betrieb über mehrere Server hinweg deutlich komplizierter wären.

Ein einfaches Kubernetes Deployment

Die folgende Konfiguration erstellt drei Nginx-Pods:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-frontend
  template:
    metadata:
      labels:
        app: web-frontend
    spec:
      containers:
        - name: nginx
          image: nginx:stable
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: "100m"
              memory: "64Mi"
            limits:
              memory: "256Mi"

Die Konfiguration wird beispielsweise als deployment.yaml gespeichert und anschließend mit folgendem Befehl angewendet:

kubectl apply -f deployment.yaml

Mit dem folgenden Befehl lässt sich überprüfen, auf welchen Nodes die Pods ausgeführt werden:

kubectl get pods -o wide

Wichtig ist dabei, dass drei Replikate nicht automatisch bedeuten, dass die Pods auf drei unterschiedlichen Nodes laufen. Dafür können zusätzliche Topology-Spread-Constraints oder Anti-Affinity-Regeln notwendig sein.

Außerdem sollte in produktiven Umgebungen ein konkret freigegebenes Container-Image verwendet werden, damit Deployments reproduzierbar bleiben.

Skalierung: Wie verteilte Anwendungen mit steigender Last umgehen

Ein wesentlicher Vorteil verteilter Anwendungen ist die Möglichkeit, einzelne Komponenten unabhängig voneinander zu skalieren.

Wenn beispielsweise ein Backend besonders viele Anfragen verarbeiten muss, können zusätzliche Instanzen dieses Dienstes bereitgestellt werden.

In Kubernetes lässt sich die Replikazahl eines Deployments manuell verändern:

kubectl scale deployment web-frontend --replicas=5

Daraufhin versucht Kubernetes, fünf entsprechende Pods bereitzustellen.

Für eine automatische Skalierung kann der Horizontal Pod Autoscaler eingesetzt werden. Dieser passt die Anzahl der Pods anhand konfigurierter Metriken an.

Allerdings funktioniert horizontale Skalierung nicht bei jeder Anwendung gleich gut.

Ein zustandsloser Webserver lässt sich häufig relativ einfach mehrfach betreiben. Bei einer Datenbank ist die Situation deutlich komplizierter, weil Lese- und Schreibzugriffe koordiniert werden müssen.

Außerdem können zusätzliche Anwendungsinstanzen die Last auf gemeinsam verwendete Abhängigkeiten erhöhen.

Wenn beispielsweise zehn Backend-Pods dieselbe Datenbank verwenden, kann die Datenbank zum eigentlichen Engpass werden.

Deshalb sollte Skalierung immer das Gesamtsystem berücksichtigen und nicht nur die Anzahl der Container.

Datenkonsistenz und verteilte Datenbanken

Eine der größten Herausforderungen verteilter Anwendungen ist der Umgang mit Daten.

Solange alle Komponenten auf eine zentrale Datenbank zugreifen, ist die Datenhaltung zunächst vergleichsweise übersichtlich. Allerdings entsteht dadurch möglicherweise ein zentraler Abhängigkeitspunkt.

Bei größeren Architekturen werden Daten deshalb teilweise über mehrere Dienste oder Datenbanksysteme verteilt.

Das bringt neue Herausforderungen mit sich.

Stell dir vor, eine Bestellung wird in einem Bestellservice gespeichert. Gleichzeitig muss ein anderer Dienst den Lagerbestand reduzieren, während ein weiterer Dienst die Zahlung verarbeitet.

Was passiert, wenn die Bestellung erfolgreich gespeichert wurde, die Kommunikation mit dem Zahlungsdienst aber abbricht?

Ein klassischer Datenbank-Commit reicht in einer solchen Architektur nicht immer aus, um alle beteiligten Dienste konsistent zu halten.

Stattdessen benötigt man geeignete Verfahren zur Koordination verteilter Geschäftsprozesse.

Ein möglicher Ansatz ist das Saga-Pattern. Dabei wird ein übergreifender Geschäftsprozess in mehrere lokale Transaktionen aufgeteilt. Schlägt ein Schritt fehl, können definierte Kompensationsaktionen ausgeführt werden.

Auch Eventual Consistency spielt in verteilten Systemen eine Rolle. Dabei können Informationen zwischen einzelnen Komponenten vorübergehend voneinander abweichen, bevor sie wieder in einen konsistenten Zustand überführt werden.

Ob dieses Verhalten akzeptabel ist, hängt stark vom jeweiligen Anwendungsfall ab.

Bei einem Produktkatalog kann eine kurze Verzögerung möglicherweise toleriert werden. Bei einer Finanztransaktion müssen dagegen wesentlich strengere Anforderungen berücksichtigt werden.

Deshalb sollte die Datenarchitektur frühzeitig geplant werden, bevor Anwendungen auf viele unabhängige Dienste verteilt werden.

Fehlertoleranz: Was passiert, wenn ein Dienst ausfällt?

In einem verteilten System gehören Teilausfälle zum normalen Betriebsrisiko.

Ein einzelner Dienst kann nicht erreichbar sein, während alle anderen Komponenten weiterhin funktionieren.

Genau das unterscheidet verteilte Anwendungen von einer einfachen lokalen Prozessarchitektur.

Deshalb sollten Anwendungen so entwickelt werden, dass der Ausfall einzelner Abhängigkeiten nicht zwangsläufig das gesamte System lahmlegt.

Eine Möglichkeit besteht darin, mehrere Instanzen wichtiger Dienste zu betreiben. Zusätzlich können Load Balancer den eingehenden Netzwerkverkehr auf verfügbare Instanzen verteilen.

Darüber hinaus sollten Anwendungen geeignete Timeouts, Wiederholungsmechanismen und gegebenenfalls Circuit Breaker verwenden.

Ein Circuit Breaker verhindert beispielsweise, dass ein dauerhaft fehlerhafter Dienst immer wieder mit neuen Anfragen belastet wird.

Stattdessen kann die Anwendung vorübergehend auf weitere Aufrufe verzichten oder eine kontrollierte Fehlermeldung zurückgeben.

Auch Kubernetes unterstützt Fehlertoleranz durch verschiedene Mechanismen. Dazu gehören der Ersatz ausgefallener Pods und die Verwendung von Readiness- und Liveness-Probes.

Allerdings ist Kubernetes nicht in der Lage, fachliche Fehler innerhalb einer Anwendung automatisch zu korrigieren.

Wenn beispielsweise eine Bestellung aufgrund einer fehlerhaften Geschäftslogik doppelt verarbeitet wird, hilft es nicht, den betreffenden Pod einfach neu zu starten.

Die Anwendung selbst muss deshalb für den Umgang mit solchen Fehlerfällen ausgelegt sein.

Synchrone und asynchrone Kommunikation

Bei verteilten Anwendungen stellt sich außerdem die Frage, wie einzelne Komponenten Informationen austauschen sollen.

Eine synchrone Kommunikation ist beispielsweise ein HTTP-Aufruf, bei dem ein Dienst unmittelbar auf die Antwort eines anderen Dienstes wartet.

Das ist relativ einfach zu verstehen und eignet sich für viele typische Anwendungsfälle.

Allerdings entsteht dabei eine direkte zeitliche Abhängigkeit. Wenn der aufgerufene Dienst nicht antwortet, muss der aufrufende Dienst entsprechend reagieren.

Bei der asynchronen Kommunikation werden dagegen Nachrichten oder Ereignisse übertragen, ohne dass zwingend sofort eine Antwort benötigt wird.

Dafür kommen häufig Message Broker wie RabbitMQ oder Apache Kafka zum Einsatz.

Ein Beispiel wäre die Verarbeitung einer Bestellung.

Nachdem eine Bestellung gespeichert wurde, könnte die Anwendung ein Ereignis veröffentlichen. Andere Dienste verarbeiten dieses Ereignis anschließend unabhängig voneinander.

Dadurch kann die direkte Kopplung zwischen einzelnen Komponenten reduziert werden.

Allerdings entstehen neue Anforderungen an die Nachrichtenverarbeitung. Nachrichten können mehrfach zugestellt werden, verspätet eintreffen oder bei fehlerhafter Konfiguration verloren gehen.

Deshalb sind auch hier Idempotenz, Fehlerbehandlung und ein geeignetes Wiederholungsverfahren wichtig.

Monitoring und Logging in verteilten Anwendungen

Je mehr Komponenten eine Anwendung besitzt, desto schwieriger wird die Fehlersuche.

Bei einem klassischen Monolithen lassen sich viele Probleme zunächst innerhalb einer einzigen Anwendung untersuchen.

In einer verteilten Architektur kann eine Benutzeranfrage dagegen mehrere Dienste durchlaufen.

Wenn dabei ein Fehler auftritt, reicht es häufig nicht mehr aus, nur die Logdatei eines einzelnen Containers zu betrachten.

Deshalb benötigen verteilte Anwendungen ein durchdachtes Observability-Konzept.

Dazu gehören insbesondere Logs, Metriken und Traces.

Logs dokumentieren einzelne Ereignisse und Fehlermeldungen. Metriken liefern messbare Informationen wie Antwortzeiten, Fehlerraten oder Ressourcenverbrauch. Traces helfen dabei, den Ablauf einer Anfrage über mehrere Dienste hinweg nachzuvollziehen.

Gerade Distributed Tracing ist bei komplexeren Microservice-Architekturen besonders hilfreich.

Mit Technologien wie OpenTelemetry können entsprechende Telemetriedaten erfasst und an geeignete Analyseplattformen übertragen werden.

Für Metriken werden häufig Prometheus und Grafana eingesetzt, während zentrale Logs beispielsweise mit Loki gesammelt werden können.

Entscheidend ist jedoch nicht, möglichst viele Werkzeuge zu installieren.

Vielmehr sollte die Überwachung konkrete Fragen beantworten können: Welcher Dienst verursacht die Verzögerung? Warum steigt die Fehlerrate? Welche Abhängigkeit ist nicht erreichbar?

Eine verteilte Anwendung ohne ausreichende Beobachtbarkeit kann im Fehlerfall sehr schwer zu administrieren sein.

Sicherheit und Netzwerksegmentierung

Durch die Aufteilung einer Anwendung auf mehrere Dienste entstehen zusätzliche Kommunikationswege.

Während innerhalb eines Monolithen viele Aufrufe direkt im Prozess stattfinden, werden bei Microservices häufig Netzwerkverbindungen benötigt.

Dadurch vergrößert sich die Anzahl potenzieller Angriffspunkte.

Deshalb sollten Container nicht automatisch mit allen anderen Containern kommunizieren dürfen.

In Docker lassen sich Anwendungen auf verschiedene Netzwerke verteilen. In Kubernetes können NetworkPolicies verwendet werden, sofern das eingesetzte Netzwerk-Plugin diese unterstützt.

Zusätzlich sollten Dienste mit möglichst geringen Berechtigungen betrieben werden.

Wichtige Sicherheitsmaßnahmen sind beispielsweise:

  • Container möglichst ohne Root-Rechte ausführen.
  • Unnötige Netzwerkverbindungen beschränken.
  • Zugangsdaten über geeignete Secret-Verwaltung bereitstellen.
  • Container-Images regelmäßig aktualisieren und prüfen.
  • Interne APIs authentifizieren und autorisieren.
  • Verschlüsselung für sensible Netzwerkkommunikation einsetzen.
  • Administrationsschnittstellen nicht unnötig öffentlich bereitstellen.

Gerade bei größeren Kubernetes-Umgebungen ist es sinnvoll, diese Anforderungen frühzeitig zu standardisieren.

Nachträglich gewachsene Berechtigungen und unübersichtliche Netzwerkfreigaben lassen sich häufig nur mit erheblichem Aufwand korrigieren.

Wann lohnt sich eine verteilte Container-Architektur?

Nachdem wir die verschiedenen technischen Aspekte betrachtet haben, bleibt eine entscheidende Frage: Wann lohnt sich der zusätzliche Aufwand überhaupt?

Meiner Meinung nach hängt das vor allem von den Anforderungen an Skalierung, Verfügbarkeit und unabhängige Entwicklung ab.

Wenn eine Anwendung aus wenigen Komponenten besteht, selten verändert wird und auf einem einzelnen Server problemlos funktioniert, kann eine einfache Architektur vollkommen ausreichend sein.

Docker Compose bietet in solchen Fällen häufig bereits genügend Möglichkeiten, um mehrere Dienste sauber zu verwalten.

Anders sieht es aus, wenn viele Anwendungen unabhängig voneinander entwickelt und regelmäßig bereitgestellt werden.

Auch bei unterschiedlichen Lastprofilen einzelner Dienste oder hohen Anforderungen an die Verfügbarkeit kann eine verteilte Architektur sinnvoll sein.

Kubernetes bietet dann eine standardisierte Grundlage für die Verwaltung containerisierter Workloads über mehrere Nodes hinweg.

Allerdings sollte man sich bewusst machen, dass diese Vorteile mit zusätzlichen Anforderungen an Monitoring, Netzwerke, Sicherheit und Datenhaltung verbunden sind.

Deshalb würde ich niemals eine Anwendung ausschließlich deshalb in Microservices aufteilen, weil Kubernetes bereits vorhanden ist.

Die Architektur sollte sich aus den Anforderungen der Anwendung ergeben und nicht aus der Entscheidung für ein bestimmtes Werkzeug.

Häufig gestellte Fragen zu Containern und verteilten Anwendungen

Was sind verteilte Anwendungen einfach erklärt?

Verteilte Anwendungen bestehen aus mehreren Komponenten, die in unterschiedlichen Prozessen oder auf verschiedenen Rechnern ausgeführt werden und über ein Netzwerk zusammenarbeiten. Für den Benutzer erscheinen diese Komponenten häufig als eine gemeinsame Anwendung.

Warum werden Container für verteilte Anwendungen verwendet?

Container ermöglichen eine standardisierte Bereitstellung einzelner Anwendungskomponenten. Dadurch können Dienste leichter unabhängig voneinander entwickelt, getestet und ausgeführt werden. Außerdem lassen sich zusätzliche Instanzen vergleichsweise schnell bereitstellen.

Ist Kubernetes für verteilte Anwendungen notwendig?

Nein. Verteilte Anwendungen können auch ohne Kubernetes betrieben werden. Für kleinere Umgebungen kann beispielsweise Docker Compose ausreichend sein. Kubernetes wird besonders dann interessant, wenn zahlreiche containerisierte Anwendungen über mehrere Server hinweg automatisiert verwaltet werden sollen.

Was ist der Unterschied zwischen Microservices und Containern?

Microservices beschreiben einen Architekturansatz, bei dem eine Anwendung in eigenständige Dienste aufgeteilt wird. Container sind dagegen eine Technologie zur isolierten Ausführung von Anwendungen. Microservices können in Containern betrieben werden, müssen es aber nicht.

Wie kommunizieren Container miteinander?

Container können über virtuelle Netzwerke miteinander kommunizieren. Dabei werden häufig HTTP, gRPC oder andere Netzwerkprotokolle verwendet. In Docker Compose können Dienste über ihre Namen gefunden werden, während Kubernetes Services und DNS für die Erreichbarkeit entsprechender Workloads bereitstellt.

Was passiert, wenn ein Container in einer verteilten Anwendung ausfällt?

Das hängt von der Architektur und der verwendeten Plattform ab. Kubernetes kann beispielsweise ausgefallene Pods ersetzen. Damit die gesamte Anwendung verfügbar bleibt, müssen jedoch auch Abhängigkeiten, Datenhaltung, Netzwerkverbindungen und geeignete Wiederherstellungsmechanismen berücksichtigt werden.

Sind verteilte Anwendungen automatisch hochverfügbar?

Nein. Die Verteilung auf mehrere Container oder Server führt nicht automatisch zu hoher Verfügbarkeit. Erst durch Redundanz, geeignete Fehlerbehandlung, zuverlässige Storage-Systeme und eine durchdachte Architektur kann die Ausfallsicherheit verbessert werden.

Fazit: Container vereinfachen verteilte Anwendungen, ersetzen aber keine gute Architektur

Container und verteilte Anwendungen gehören heute eng zusammen. Die Container-Technologie ermöglicht eine standardisierte Bereitstellung einzelner Dienste, während Plattformen wie Kubernetes deren Verwaltung über mehrere Server hinweg automatisieren können.

Dadurch entstehen interessante Möglichkeiten, insbesondere bei der Skalierung, bei automatisierten Deployments und beim Betrieb größerer Anwendungslandschaften.

Allerdings sollte man die damit verbundenen Herausforderungen nicht unterschätzen.

Sobald mehrere Dienste miteinander kommunizieren, spielen Netzwerkausfälle, Datenkonsistenz, Service Discovery und Observability eine wesentlich größere Rolle als bei einer einfachen Anwendung auf einem einzelnen Server.

Außerdem wird deutlich, dass Kubernetes zwar viele Infrastrukturaufgaben automatisiert, aber keine fehlerhafte Anwendungsarchitektur reparieren kann.

Mein Tipp wäre deshalb, zunächst die grundlegenden Zusammenhänge zu verstehen. Starte mit einer kleinen Mehrcontainer-Anwendung über Docker Compose und beobachte, wie die Dienste miteinander kommunizieren.

Trenne anschließend einzelne Komponenten, simuliere Netzwerkprobleme und untersuche, wie sich deine Anwendung bei einem Dienstausfall verhält.

Wenn du diese Grundlagen verstanden hast, kannst du den nächsten Schritt gehen und dieselbe Architektur innerhalb eines Kubernetes-Clusters betreiben.

Dort lernst du dann, wie Deployments, Services, Scheduling und automatische Wiederherstellungsmechanismen zusammenarbeiten.

Mein Fazit: Container machen die Bereitstellung verteilter Anwendungen einfacher. Ob daraus ein zuverlässiges System entsteht, entscheidet jedoch die Architektur und nicht die Anzahl der eingesetzten Container.

Eine gut geplante Anwendung mit wenigen Diensten kann langfristig wesentlich besser funktionieren als eine unnötig komplexe Microservice-Architektur.

Deshalb sollte die wichtigste Frage immer lauten: Welche Probleme möchte ich durch die Verteilung tatsächlich lösen?

Wenn du darauf eine klare Antwort hast, lässt sich auch entscheiden, ob Docker Compose ausreicht oder Kubernetes die passende Grundlage für deine Infrastruktur ist.

Weiterführende Informationen

Weitere technische Informationen findest du in der offiziellen Docker-Compose-Dokumentation.

Für die Arbeit mit verteilten Anwendungen innerhalb eines Kubernetes-Clusters empfehle ich außerdem die Kubernetes-Dokumentation zu Services und Service Discovery.

Wenn du dich näher mit Observability beschäftigen möchtest, findest du zusätzliche Grundlagen auf der offiziellen OpenTelemetry-Webseite.

Nach oben scrollen