Beliebte Suchanfragen
//

Exploring Dapr: A Deep Dive into Distributed Application Runtime

10.7.2024 | 10 minutes of reading time

In a recent blog post, we introduced Dapr (Distributed Application Runtime) and highlighted its potential as a valuable tool for cloud-native applications, in combination with Aspire. This post dives deeper into the inner workings of Dapr, explaining its architecture, components, and how to leverage its capabilities effectively.

Dapr under the hood

As previously mentioned, Dapr (Distributed Application Runtime) is an excellent toolkit for building distributed applications. It offers various components, known as Building Blocks, each serving a specific function. Dapr's strength lies in its language-agnostic nature, allowing developers to create applications in any language of their choice while leveraging as many of Dapr's Building Blocks as needed.

Sidecars and Architecture

Dapr achieves this by being divided into two distinct parts. The first part is the Dapr runtime, which is the core of Dapr, responsible for orchestration and acting as a portable control plane. The second part is the sidecar, which is unique to each Dapr-enabled application. A sidecar is a secondary process running alongside the application, serving as a transparent companion. When an application needs to use a building block, it communicates through the sidecar, which facilitates the use of the specified component. Integrating with a sidecar is straightforward, as all building blocks are exposed as HTTP or gRPC-based APIs. Applications simply call their own sidecar, which provides access to the necessary APIs to utilize the building blocks.

Buildings Blocks

As of version 1.1, Dapr provides multiple building blocks, with different maturities:

Source: https://docs.dapr.io/images/building_blocks.png

  • Service Invocation: Service invocation allows applications to interact through well-defined endpoints using HTTP or gRPC messages. Dapr offers an endpoint that functions as a reverse proxy with integrated service discovery, incorporating distributed tracing and error handling.
  • State Management: Application state refers to any data an application needs to retain beyond a single session. Dapr offers key/value-based state and query APIs with pluggable state stores for persistence.
  • Pub/Sub Messaging: Pub/Sub is a loosely coupled messaging pattern in which publishers send messages to a topic, and subscribers subscribe to that topic to receive the messages. Dapr facilitates the pub/sub pattern for communication between applications.
  • Workflows: The Workflow API allows you to define long-running, persistent processes or data flows that involve multiple microservices using Dapr workflows or workflow components. It can be integrated with other Dapr API building blocks, enabling workflows to invoke other services or retrieve secrets, thus offering flexibility and portability.
  • Resource Bindings: A binding provides a two-way connection to an external cloud or on-premise service or system. Dapr enables you to invoke the external service through its binding API and allows your application to be triggered by events from the connected service.
  • Actors: An actor is a self-contained unit of computation and state with single-threaded execution. Dapr offers an actor implementation based on the virtual actor pattern, providing a single-threaded programming model and automatically garbage collecting actors when they are not in use.
  • Secrets: Dapr offers a secrets building block API that integrates with various secret stores, including public cloud stores, local stores, and Kubernetes, to manage secrets. Services can use the secrets API to retrieve sensitive information, such as a database connection string.
  • Configuration: The Configuration API allows you to retrieve and subscribe to application configuration items from supported configuration stores. This enables an application to access specific configuration information, such as during startup or when changes are made in the configuration store.
  • Distributed Lock: The distributed lock API allows you to lock a resource, ensuring that multiple instances of an application can access the resource without conflicts and maintain consistency guarantees.
  • Cryptography: The Cryptography API allows you to perform cryptographic operations, such as encrypting and decrypting messages, without exposing keys to your application.

Portability is key aka Dapr Components

The impressive aspect of these building blocks is their interchangeability. To use a building block, you first need to configure a component. For example, with Pub/Sub Messaging, Dapr supports a variety of message broker systems such as Redis, Kafka, RabbitMQ, and cloud-based solutions like AWS SNS/SQS or Azure Service Bus. To use RabbitMQ, you would simply configure a component like this:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: order-pub-sub
spec:
  type: pubsub.rabbitmq
  version: v1
  metadata:
  - name: host
    value: "amqp://localhost:5672"
scopes:
  - orderprocessing
  - checkout

Once Dapr has loaded this component, utilizing it becomes a breeze. For example in C#, it would be as simple as:

await client.PublishEventAsync(order-pub-sub, checkout, eventPayload, cancellationToken);

This makes Dapr-enabled applications highly portable across different environments, as the core application code does not change. If you switch from RabbitMQ to Redis, for example, all you need to do is reconfigure the component, and everything will continue to work seamlessly.

Added capabilities

Since the sidecars are a central component, not only in providing APIs for the building blocks but also within the actual traffic flow, they offer additional capabilities in terms of observability, security, and resiliency.

  • Observability: Dapr provides distributed tracing, metrics, and logs, all of which can be exported to a receiver of your choice using OpenTelemetry.
  • Resiliency: The sidecar manages timeouts, retries/back-offs, and circuit breaking, as well as health checks for both the application and the sidecar itself.
  • Security: Connections between sidecars and the control plane are secured using mTLS. Additionally, for service invocation and pub/sub, you can implement access policies to control who can access what, providing fine-grained access control.

These enhanced capabilities make Dapr a powerful tool for managing the complexities of distributed applications.

Is Dapr a Service mesh?

Let's answer that question simply: No, Dapr is not a service mesh. Does it offer overlapping capabilities? Yes, definitely. So, how exactly does Dapr compare to the likes of Linkerd, Istio, or potentially Cilium?

First off, a service mesh focuses primarily on networking inside a cluster, providing capabilities to enhance everything network-related. Dapr, on the other hand, is application-centric. It provides building blocks that make building distributed applications easier and quicker. While both use sidecars, this is a technical implementation detail rather than a defining feature of a service mesh. The overlapping capabilities between service meshes and Dapr include service-to-service encryption (mTLS), service invocation, metrics and tracing, and resiliency features like retries..

Source: https://docs.dapr.io/images/service-mesh.png

Networking-wise, when a Dapr-enabled app calls another app, it uses the application's ID known to Dapr. Dapr then routes that request through the underlying network, which could potentially be a service mesh that handles the actual networking path.

This distinction explains why it's not a matter of choosing Dapr or a service mesh, but rather how Dapr and a service mesh can work together effectively.

Example

Alright, that's been a lot of theory so far. Let's dive into an actual example. Since Dapr is a portable runtime, all it takes to run a Dapr-enabled app is an environment, either self-hosted or within Kubernetes (K8s) with the Dapr runtime installed. For this example, we'll use a simple local Kubernetes cluster (Minikube, Kind, K3s, etc.) and the well-known microservice-demo from Google Cloud, which I've edited to utilize Dapr.

Let's create a quick Kind cluster for this example, though any other local Kubernetes environment will work as well:

kind create cluster -n dapr-demo

Once the cluster is installed, the only thing missing is the actual Dapr runtime. Fortunately, Dapr provides a convenient CLI that we'll use heavily for this demo. To get the runtime into our local cluster, use the following command:

dapr init --dev -k
⌛  Making the jump to hyperspace...
ℹ️  Note: To install Dapr using Helm, see here: https://docs.dapr.io/getting-started/install-dapr-kubernetes/#install-with-helm-advanced

ℹ️  Container images will be pulled from Docker Hub
✅  Deploying the Dapr control plane with latest version to your cluster...
✅  Deploying the Dapr dashboard with latest version to your cluster...
✅  Deploying the Dapr Redis with latest version to your cluster...
✅  Deploying the Dapr Zipkin with latest version to your cluster...
ℹ️  Applying "statestore" component to Kubernetes "default" namespace.
ℹ️  Applying "pubsub" component to Kubernetes "default" namespace.
ℹ️  Applying "appconfig" zipkin configuration to Kubernetes "default" namespace.
✅  Success! Dapr has been installed to namespace dapr-system. To verify, run `dapr status -k' in your terminal. To get started, go here: https://aka.ms/dapr-getting-started

This command will install the following components inside our cluster as part of the Dapr runtime, which we can quickly check with:

dapr status -k
  NAME                   NAMESPACE    HEALTHY  STATUS   REPLICAS  VERSION  AGE  CREATED
  dapr-operator          dapr-system  True     Running  1         1.13.5   1d   2024-07-03 15:10.23
  dapr-sidecar-injector  dapr-system  True     Running  1         1.13.5   1d   2024-07-03 15:10.23
  dapr-dashboard         dapr-system  True     Running  1         0.14.0   1d   2024-07-03 15:10.25
  dapr-sentry            dapr-system  True     Running  1         1.13.5   1d   2024-07-03 15:10.23
  dapr-placement-server  dapr-system  True     Running  1         1.13.5   1d   2024-07-03 15:10.23

Not only does this command install the Dapr runtime, but it also deploys a default Zipkin installation as a tracing backend and a Redis instance for pub/sub and state store purposes.

For this pub/sub setup, the necessary components will be applied to Kubernetes by default with the --dev command:

dapr components -k
⚠  In future releases, this command will only query the "default" namespace by default. Please use the --namespace flag for a specific namespace, or the --all-namespaces (-A) flag for all namespaces.
  NAMESPACE  NAME        TYPE          VERSION  SCOPES  CREATED              AGE
  default    pubsub      pubsub.redis  v1               2024-07-08 13:51.37  38s
  default    statestore  state.redis   v1               2024-07-08 13:51.36  39s

With the runtime installed and healthy, we can proceed to the next step. The Dapr CLI also has a run command, which can launch apps and sidecars directly through Dapr. Additionally, it has a multi-run capability to start multiple apps (and their sidecars) together, similar to Docker Compose, but Dapr handles the sidecar placement and wiring. In the example repo mentioned above, I've already set this up. Hence, a simple:

kubectl apply -f ./src/emailservice/components/subscription.yml
dapr run -k -f dapr.yaml

This will bring up the entire microservice demo. To see that everything has started, we can use the Dapr dashboard:

dapr dashboard -k
ℹ️  Dapr dashboard found in namespace:	dapr-system
ℹ️  Dapr dashboard available at:	http://localhost:8080

You can also port-forward the frontend service to access the demo app:

kubectl port-forward -n default svc/frontend 8081:80

Once inside the demo app, you can go through an end-to-end flow, from selecting a product to completing the checkout. After completing the checkout, by checking the email service logs, you should see a result like this:

emailservice {"timestamp": 1720179148.6441565, "severity": "INFO", "name": "emailservice-server", "message": "A request to send order confirmation email for order fcd1bdec-3ac1-11ef-b1a7-faf1d76d4272 to someone@example.com has been received.", "taskName": null}

How does this work

This works by configuring the frontend service to use its sidecar for interservice communication by setting the addresses of those services to the sidecar.

Example:

    env:
      PRODUCT_CATALOG_SERVICE_ADDR: localhost:50001
      AD_SERVICE_ADDR: localhost:50001
      CART_SERVICE_ADDR: localhost:50001
      CHECKOUT_SERVICE_ADDR: localhost:50001
      CURRENCY_SERVICE_ADDR: localhost:50001
      SHIPPING_SERVICE_ADDR: localhost:50001
      RECOMMENDATION_SERVICE_ADDR: localhost:50001
      SHOPPING_ASSISTANT_SERVICE_ADDR: localhost:50001

Now, the frontend service goes through the sidecar rather than directly to the service, allowing for control of the traffic flow. For pub/sub, the checkout service sends a gRPC request to the sidecar to emit the "checkout" event. The sidecar connects to Redis to actually emit the event. The email service's sidecar subscribes to the topic in Redis and, once an event is emitted, wraps the event into an HTTP call to notify the email service. Additionally, in the default Kubernetes configuration, all services communicate using mTLS, ensuring secure communication.

Conclusion and next steps

In conclusion, Dapr stands out as a robust and versatile tool for building cloud-native, distributed applications. Its architecture, based on the separation of the runtime and sidecar, enables seamless integration and flexibility across various programming languages and environments. The array of building blocks, such as Service Invocation, State Management, Pub/Sub Messaging, and many others, allows developers to easily incorporate essential functionalities into their applications.

Dapr's portability is a key advantage, allowing applications to remain consistent across different environments by simply reconfiguring components. This makes it highly adaptable and efficient, particularly in hybrid cloud setups or during migrations. The added features of observability, security, and resiliency further enhance its appeal, providing comprehensive support for modern application needs. Overall, Dapr is a valuable addition to any cloud-native toolkit, offering a comprehensive and flexible solution for managing the complexities of distributed application development and deployment.

share post

Likes

2

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.