Alle Artikel

Dapr: Distributed Application Runtime

Dapr stellt verteilten Systemen einen Open-Source-Werkzeugkasten bereit: modulare Bausteine für die Kommunikation zwischen Services, Monitoring und ereignisgetriebenes Design.

·

Max Körbächer

Any 1024x562

Hinweis: Dieser Beitrag wurde aus dem Englischen übersetzt. Fachbegriffe, Code und Befehle wurden bewusst im Original belassen.

Dapr – ein Werkzeugkasten für verteilte Systeme

Die wachsende Komplexität verteilter, containerisierter Systeme stellt viele Entwickler vor neue Herausforderungen. Innerhalb einer Anwendung entstehen neue Schnittstellen, die auch neue Integrationsansätze erfordern. Weitere Probleme ergeben sich nicht nur technisch, sondern auch organisatorisch – für Projekte und deren Betrieb.

Im Kern wirft das einige Fragen auf. Wer muss die notwendigen Systeme für die Integrationsschicht innerhalb einer verteilten Anwendung bereitstellen? Wer verwaltet diese Schicht? Wer verantwortet den Betrieb? Wie lange dauert es, die verschiedenen für die Integration benötigten Dienste aufzusetzen (Message Bus, State Store, Keystore, TLS, Auth)?

Ein entwicklergetriebenes Open-Source-Projekt namens Dapr (Distributed Application Runtime) ist ein offensiver Vorschlag, diese Fragen zu beantworten: notwendige Middleware selbst zu nutzen und dennoch die Gewissheit robuster, zuverlässiger Dienste zu haben.

Dapr ist dabei nicht mit einer Java-Runtime zu vergleichen. Es stellt vielmehr eine Vielzahl von Funktionen und einfachen Integrationen bereit, wie sie alle verteilten Systeme benötigen. Wir betrachten Dapr als Werkzeugkasten, aus dem wir uns mit den passenden Werkzeugen bedienen können.

Die Bausteine von Dapr

Dapr versteht sich als modularer Baukasten bzw. Werkzeugkasten, aus dem sich die passenden Bausteine entnehmen lassen. Derzeit stehen sieben solcher Bausteine bereit, wobei das Framework als Open-Source-Projekt kontinuierlich weiterentwickelt und flexibel erweitert werden kann. Ziel jedes Bausteins ist es, verbreitete Herausforderungen einfach, skalierbar, robust und nach Best Practices zu lösen. Die folgende Abbildung gibt einen kurzen Überblick über die aktuellen Bausteine.

Quelle: https://docs.dapr.io/concepts/building-blocks-concept/

Dapr ist auf hohe Wiederverwendbarkeit ausgelegt – nicht nur bei der Bereitstellung von Diensten und deren direkter Nutzung im Code, sondern auch innerhalb der eigenen Quellcodestrukturen. Diese sogenannten Komponenten kommen in verschiedenen Bausteinen zum Einsatz. So nutzen etwa das State Management und der Actor-Baustein die Komponente „state", um bestimmte Zustände zu speichern. Die Komponente „state" spricht im Hintergrund beispielsweise mit einer Redis-Datenbank.

Die Funktionen und Dienste der einzelnen Bausteine werden in den folgenden Kapiteln erläutert. Zunächst betrachten wir jedoch die allgemeine Funktionsweise von Dapr.

Grundlegende Funktionsweise

Dapr ist cloud- bzw. infrastrukturagnostisch und lässt sich sowohl als eigenständige Lösung als auch als Container auf einem Kubernetes-Cluster betreiben. Letzteres vereinfacht einige Schritte beim Deployment und ist am weitesten verbreitet. Die folgende Abbildung zeigt daher eine Kubernetes-Installation.

Für Betrieb und Konfiguration von Dapr werden mehrere Management-Pods initialisiert. Jeder davon erfüllt eine bestimmte Aufgabe:

  • Operator: Verwaltet Komponenten-Updates und Kubernetes-Service-Endpunkte für Dapr (etwa State Stores, Pub/Subs usw.).

  • Sentry: Verwaltet mTLS zwischen Diensten und fungiert als Zertifizierungsstelle.

  • Sidecar Injector: Injiziert Dapr in annotierte Deployments und ergänzt die Umgebungsvariablen DAPR_HTTP_PORT und DAPR_GRPC_PORT, damit Anwendungen unkompliziert mit Dapr kommunizieren können, ohne die Ports von Dapr fest zu verdrahten.

  • Actor Placement: Wird ausschließlich für Actors genutzt. Erstellt Zuordnungstabellen, die Actor-Instanzen auf Pods abbilden.

Quelle: https://docs.dapr.io/concepts/overview/

Wie bereits am Beispiel der Komponente „state" erläutert, kann Dapr unterschiedliche Datenbanken ansprechen. Weitere beispielhafte Werkzeuge, die sich als Komponenten einbinden lassen, sind rechts unter components aufgeführt.

Der spannendste Teil von Dapr ist der Sidecar-Container, der innerhalb eines Pods parallel zum Container mit der Anwendungslogik läuft. Das Sidecar-Muster ist im Kubernetes-Umfeld weit verbreitet, um Deployments zu erweitern, ohne zusätzliche Agents, Daemons oder Ähnliches direkt in den Anwendungscontainer zu installieren. Wir vertiefen das Sidecar-Muster hier nicht weiter, doch eines ist wichtig zu verstehen: Container innerhalb eines Pods teilen sich sämtliche Ressourcen. Im Fall von Dapr kommuniziert die Anwendung über HTTP oder gRPC mit dem Dapr-Sidecar. Ein Beispiel dazu folgt gleich.

Woher weiß Dapr, welcher Pod ein Sidecar braucht?

Damit Dapr Kubernetes anweisen kann, ein Sidecar in einem Pod zu platzieren, genügen Annotationen im Deployment-Manifest. Eigene Einstellungen lassen sich ebenfalls über Annotationen setzen. Die Annotation „dapr.io/" wird im Deployment-Manifest unter spec.template.annotations angegeben. Das Flag „enabled" bestimmt, ob ein Sidecar injiziert wird, während das Flag „app-id" dem Pod eine eindeutige Kennung zuweist.

Beispiel für eine Annotation von dapr

Wenn Sie mit Kubernetes-Deployments oder -Annotationen nicht vertraut sind, empfehlen wir das entsprechende Kapitel der Kubernetes-Dokumentation auf kubernetes.io.

Komplexe, verteilte Anwendungen integrieren

Wie eingangs versprochen, gehen wir nun etwas tiefer auf einige ausgewählte Bausteine von Dapr, deren Funktionen und Möglichkeiten ein. Zugleich möchten wir alle ermutigen, Dapr selbst auszuprobieren. Die folgenden Abschnitte bieten einen Einstieg und Überblick über die am häufigsten anzutreffenden Anwendungsfälle.

Service-to-Service Invocation

Grundlage für die Funktionsfähigkeit verteilter Anwendungen ist das Zusammenspiel ihrer Dienste. In den vergangenen Jahren haben sich mehrere Schnittstellenmuster etabliert, die diese Kommunikation standardisiert abbilden. Für direkte, gekoppelte Aufrufe haben sich die Muster REST und gRPC durchgesetzt. Aufgrund seiner Performancevorteile geht der Trend zuletzt deutlich in Richtung gRPC.

Für verteilte Anwendungen ergeben sich aus verschiedenen Blickwinkeln unterschiedliche Anforderungen:

  • Wo befindet sich der Zieldienst? Und wie wird er adressiert?

  • Welche Übertragungsfehler können auftreten und wie ist damit umzugehen?

  • Welches Protokoll wird für die Übertragung genutzt bzw. vom Empfänger erwartet?

  • Wie wird sichergestellt, dass die Aufrufe verschlüsselt sind?

  • Wie lassen sich Zugriffsrichtlinien und Autorisierung umsetzen?

  • Wie lassen sich Fehler in der Übertragung sichtbar machen?

  • Wie lassen sich Schnittstellen und Protokolle standardisieren und von Software-Frameworks unabhängig halten?

Dapr beantwortet diese Fragen mit einem Reverse-Proxy-Muster: Die Kommunikation zwischen Komponenten erfolgt nicht direkt, sondern über das von Dapr als Container oder Prozess bereitgestellte Sidecar. Das Dapr-Sidecar fungiert dabei als „Bote" für die genannten Anforderungen.

Quelle: https://docs.dapr.io/developing-applications/building-blocks/service-invocation/service-invocation-overview/

Die Anwendung übergibt den Aufruf per gRPC oder HTTP an Dapr – entweder über einen Aufruf von localhost:{dapr-port}/v1.0/invoke/{app-id}/method/{method-name} oder über das Dapr-Client-SDK. Dank seiner sprachagnostischen Umsetzung erlaubt Dapr den flexiblen Einsatz von Frameworks. Für die meisten verbreiteten Sprachen (etwa Java, .NET, Python, PHP …) gibt es bereits SDKs, die diese Funktionalität und die weiteren Bausteine in sprachspezifischen Clients kapseln.

Sobald der Aufruf beim Dapr-Sidecar eintrifft, wird der richtige Empfänger über mDNS (lokal) oder kubedns (Kubernetes) ermittelt und per gRPC an das entsprechende Dapr-Sidecar des Empfängers übergeben. Der Empfänger wird über seinen Namen angegeben und kann mit Kubernetes-Services und deren Lastverteilungsmechanismen genutzt werden. Die Kommunikation zwischen den Sidecars ist per mTLS abgesichert und damit verschlüsselt. An dieser Stelle sei erwähnt, dass sich auch Traffic von containerisierten Diensten wie nginx oder Traefik über Dapr an die Pods übergeben lässt. So kann der Verkehr vom Cluster-Ingress bis zum eigentlichen Dienst verschlüsselt übertragen werden, ohne Service Meshes oder Overlay-Netzwerke einzusetzen. Gerade dieser Punkt findet sich auf vielen Anforderungslisten zur Sicherheit.

Das empfangende Sidecar leitet den Aufruf gemäß den von der Empfängeranwendung registrierten Methoden weiter. Bei Fehlern in der Kommunikation oder beim Empfänger reicht Dapr diese Anfrage protokollagnostisch an den Absender zurück. Kann Dapr keinen passenden Empfänger ermitteln, wird auch diese Information an die sendende Anwendung zurückgegeben und lässt sich entsprechend behandeln. Dapr versucht mehrfach, den Empfänger zu erreichen, um Nichtverfügbarkeiten etwa durch einen vorübergehend nicht erreichbaren Pod auszuschließen. Dieses Vorgehen muss also nicht in der Anwendung abgebildet werden.

Durch die Abstraktion der Kommunikationsschicht ermöglicht Dapr zudem die Kommunikation über mehrere Protokolle hinweg (HTTP/gRPC). So lässt sich eine bestehende Anwendung Schritt für Schritt auf gRPC migrieren.

Tracing und Beobachtung

Indem die Kommunikation an Dapr übergeben wird, entsteht eine zentrale, anwendungsunabhängige Möglichkeit, die Aufrufe zu überwachen. Dapr nutzt die standardisierten „W3C Trace Context"-Header (HTTP: traceparent, gRPC: grpc-trace-bin) und ergänzt diese Informationen bei Bedarf. Die Daten werden im Zipkin-Format gesammelt und lassen sich direkt oder über den OpenTelemetry Collector an externe Monitoring-Systeme weiterleiten. Die daraus folgende Unabhängigkeit von konkreten Implementierungen erlaubt es Teams jeder Größe mit unterschiedlichen Programmiersprachen, Logs und Metriken einheitlich zu erfassen.

Publish and Subscribe

Das Publish-and-Subscribe-Muster ist eine Alternative zum direkten Aufruf einer Servicemethode. Vor allem der Vorteil der (zeitlichen) Entkopplung ermöglicht es, resiliente und asynchrone Anwendungen effizient umzusetzen. Häufig kommen externe Messaging-Dienste zum Einsatz, die entweder selbst betrieben werden (RabbitMQ, Kafka, NATS, Redis …) oder als PaaS-Komponenten verfügbar sind (Google Cloud Pub/Sub, Azure Event Hub/Service Bus, AWS SNS/SQS). Die Vielzahl an Möglichkeiten und Formaten hat dazu geführt, dass sich Cloud Events in den letzten Jahren als übergreifender Formatstandard etabliert hat. Die Protokolle und Schnittstellen selbst blieben davon jedoch unberührt und unterscheiden sich weiterhin je Dienst.

Gerade beim Betrieb mehrerer Anwendungen, die in unterschiedlichen Abteilungen entwickelt wurden, kann diese Vielfalt eine große Herausforderung sein, da Betriebs- und Testwissen je nach Komponente unterschiedlich ausfallen muss.

Auch hier versucht dapr über den Abstraktionsansatz eine Vereinfachung für Entwicklung, Test und Betrieb zu bieten.

Wie beim direkten Aufruf einer Servicemethode erfolgen auch Publish und Subscribe über das Dapr-Sidecar. Die Anwendung selbst kommuniziert über generische HTTP-/gRPC-Aufrufe mit dem Sidecar und arbeitet unabhängig vom Message Broker. Die daraus entstehende Flexibilität erlaubt es Entwicklern, je nach Umgebung (lokal, On-Premises, Cloud) unterschiedliche Broker zu nutzen und aufwendige Mocks für Tests zu vermeiden. Dem Betrieb wiederum gibt sie die Chance, die Zahl der eingesetzten Technologien zu verringern, ohne die Entwicklung einzuschränken. Zudem verschafft dies Dapr mehr Unabhängigkeit im Hinblick auf Vendor Lock-in.

Für die Nachrichtenübertragung garantiert Dapr mehrere Eigenschaften ohne Implementierungsaufwand:

  • Garantierte Zustellung einer Nachricht (Quality of Service (QoS) Level 1: At-least-once)

  • Verteilung von Nachrichten an mehrere Subscriber (Competing Consumers Pattern)

  • Zugriffsbeschränkung (Topic Scoping)

  • Begrenzung der Nachrichtenlebensdauer (Message TTL)

  • Fehlerbehandlung (Retry/Drop)

Die Anbindung des jeweiligen Message- oder Event-Brokers erfolgt als Dapr-Komponente. Die Konfiguration enthält komponentenspezifische Parameter. Das folgende Beispiel zeigt die Konfiguration eines Azure Event Hub als Publish-&-Subscribe-Komponente:

Konfiguration von Pub/Sub mit Azure Event Hub

Die angegebenen Verbindungsdaten eines Azure-Storage-Accounts erlauben es Dapr, Checkpoints zu speichern, die das „Competing Consumers Pattern" ermöglichen.

Innerhalb der Anwendung lässt sich eine Nachricht durch Aufruf von localhost:{dapr-port}/v1.0/publish/{component-name}/{topic-name} veröffentlichen.

Subscriptions können entweder aus der Anwendung heraus (programmatisch) oder über eine zentrale Konfiguration (deklarativ) erfolgen. Eingehende Nachrichten übergibt Dapr über die angegebene Route an die Anwendung. Die folgende Abbildung zeigt schematisch die Anbindung eines Redis.

Quelle: https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-publish-subscribe/

Auch diese Funktionalität ist in den verfügbaren SDKs sprachspezifisch aufbereitet. Gerade hier hilft die Entkopplung, Tests zu vereinfachen und die Schnittstellen unabhängig von der gewählten PubSub-Komponente zu prüfen. So lassen sich anwendungsübergreifende Testkomponenten schaffen, die auf Dapr basierende generische Message-Broker-Mocks bieten. Beim Einsatz von Dapr kann die Standardisierung der Schnittstellen somit auch den Testaufwand senken.

Resource Bindings und Trigger

Resource Bindings und Trigger erlauben es, beliebige Dienste an Dapr anzubinden und ereignisgetriebene Architekturen umzusetzen. Dadurch entstehen keine Abhängigkeiten zu dienstspezifischen SDKs oder Bibliotheken. Praktisch sind den anbindbaren Diensten kaum Grenzen gesetzt. Die derzeit verfügbaren Bindings finden sich im GitHub-Repository von Dapr und umfassen diverse gemanagte Cloud-Dienste, Datenbanken sowie Plattformen wie Twitter oder Twilio. Sie lassen sich um eigene Bindings ergänzen oder als Grundlage weiterentwickeln.

Damit Dapr auf einen Dienst reagieren und eine Anwendung ansprechen kann, wenn die veröffentlichte Schnittstelle aufgerufen wird, muss zunächst ein Input Binding angelegt werden. Dazu wird in Kubernetes eine neue Ressource erstellt, die mit dem entsprechenden Binding-Typ und weiteren Parametern konfiguriert wird (linke Abbildung). Die Anwendung kann anschließend lokal auf den Dienst lauschen und beim Eintreten eines Ereignisses reagieren (rechte Abbildung).

Das Vorgehen für ein Output Binding ist im Wesentlichen gleich. Die entsprechende Komponente muss konfiguriert werden und lässt sich anschließend innerhalb der Anwendung aufrufen – etwa mit einem einfachen curl-Befehl. Output Bindings werden ausgelöst, indem eine HTTP-POST-Anfrage an den Endpunkt /v1.0/bindings/<binding-name> des Dapr-Sidecars gesendet wird. Selbstverständlich ist dieser Aufruf auch per gRPC möglich.

Wie Ihnen vermutlich bereits aufgefallen ist, erfolgen Anbindung und Adressierung externer Komponenten in allen Bausteinen auf dieselbe Weise. Auch die Handhabung folgt stets demselben Schema.

Licht und Schatten von Dapr

Was leistet Dapr für mich?

  • Die direkte Adressierung von Kommunikationspartnern

  • Die Absicherung der Service-to-Service-Invocation-Kommunikation (S2SI)

  • Die Entkopplung von PubSub-Diensten

  • Das Monitoring des Datenverkehrs

  • … sowie den Komfort der hier nicht weiter behandelten Bausteine

Dapr bietet gerade Anwendungsentwicklern einen leicht handhabbaren Werkzeugkasten. Ein nicht zu vernachlässigender Vorteil ist die Standardisierung der Integrationswerkzeuge. Besonders beim Einsatz mehrerer Bausteine sinkt die Komplexität schnell, und Wissen wird gebündelt. Die Vielzahl an Integrationsmöglichkeiten, die Entwicklern offensteht, wird durch Dapr reduziert. Auch Betriebsteams kann das entgegenkommen, da geringere Varianz den Aufwand senkt.

Unterm Strich erleichtert der Einsatz von Dapr Entwicklungsteams den Einstieg in verteilte Anwendungsentwicklung und nimmt ihnen ohne großen Zusatzaufwand wesentliche Aufgaben ab.

Was Dapr nicht löst

  • Messaging funktioniert nur über Publish & Subscribe, nicht über Queues.

  • Wird etwa Kafka für Pub/Sub genutzt, lassen sich keine Kafka Streams verwenden – Szenarien wie Machine Learning sind damit nicht möglich.

  • Wer eine neue Komponente benötigt, muss sie selbst entwickeln oder vorschlagen. Viele Unternehmen sind jedoch noch nicht bereit, in die Open-Source-Entwicklung einzusteigen.

Ebenso stellt sich die Frage, wer Dapr und alle angebundenen Komponenten betreibt. Durch die starke Vereinfachung lösen sich spezifische Abhängigkeiten auf. Dennoch braucht es eine Abstimmung zwischen Unternehmen und Entwicklung. Für den Betrieb bedeutet das zunächst mehr Komplexität.

Alternativen

Eine Alternative zu Dapr ist das Go-Micro-Framework, das einen ähnlichen Umfang und Integrationsansatz bietet. Wie der Name nahelegt, steht es allerdings nur für die Programmiersprache Go zur Verfügung. Versuche, Dapr mit Werkzeugen wie Istio, Knative oder Keptn zu vergleichen, gehen ins Leere, da diese andere Einsatzgebiete haben und sich weit eher mit Dapr kombinieren lassen. Am ehesten vergleichbar sind SDKs von Public-Cloud-Anbietern – dort liegt der Fokus jedoch stärker auf der Nutzung und Bereitstellung von Ressourcen.

Ein Blick in die Glaskugel

In den vergangenen Monaten ist Dapr stark gewachsen, hat auf GitHub bereits knapp 15.000 Sterne gesammelt und wird von Microsoft unterstützt. Microsoft sieht Dapr als strategisch wichtiges Thema, das sich perfekt in die Gesamtausrichtung zur Unterstützung von Entwicklern einfügt. In der Community kursieren zudem Gerüchte, dass Dapr bald ein natives Feature im Azure Kubernetes Service (AKS, dem gemanagten Kubernetes-Dienst) wird. Darüber hinaus ist der Einsatz von Dapr als Integrationsschicht zwischen verschiedenen PaaS-Komponenten – also zwischen Kubernetes, Serverless Functions (etwa Azure Functions) oder anderen Hosting-Optionen – natürlich eine spannende Überlegung.

Wir freuen uns auf die weitere Entwicklung von Dapr. Für viele Entwicklungsprojekte kann der Einsatz von Dapr die Komplexität beim Bau von Microservices verringern und starre Abhängigkeiten zwischen Technologien auflösen. Natürlich ist es (noch) nicht für jedes Szenario geeignet, doch es adressiert viele der verbreiteten Probleme und liefert eine web-scale-taugliche Antwort.

Ursprünglich verfasst von Florian Eidner von Cluster Reply & Max Körbächer von Liquid Reply

Teilen