What is Dapr?
Dapr is Microsoft's leading OSS runtime for distributed applications.
Dapr provides the basic functionality needed to build distributed applications.
developers do not need to create these features themselves.
The following is an overview of Dapr as presented in the Dapr repository on GitHub.
Dapr is designed to run in any environment, including Microsoft's Azure cloud service, as well as other cloud services such as AWS and GCP. Dapr is designed to run in any environment and is supported on cloud environments such as AWS and GCP, in addition to Microsoft's Azure cloud service.
Supported Languages
Dapr SDKs are available for a variety of languages, making it easy to integrate Dapr into your applications.
Client SDK for calling Dapr building blocks
Server extensions for calling from other services and subscribing to topics
Actor SDK for building virtual actors
Three types of Actor SDKs are available.
The following table shows the status of support for each SDK by language as of July 2021.
Language Status Client SDK Server extensions Actor SDK
NET Stable ✔ ASP.NET Core ✔
Python Stable ✔ gRPC FastAPI Flask
Java Stable ✔ Spring Boot ✔
Go Stable ✔ ✔ ✔ ✔
PHP Stable ✔ ✔ ✔ ✔ ✔
C++ In development ✔
Rust In development ✔
Javascript In development ✔ ✔
SDK languages
Building Blocks
Dapr consists of a number of building blocks, allowing developers to select and use only the building blocks they need. The building blocks provided are listed below, and are all the functionality needed to build a distributed application.
Service invocation
Building block to securely invoke another application using the gRPC API or HTTP API.
Namespace
mTLS authentication
Access control
Retries
Service discovery
Load Balancing
Tracing
State management
State management
Building blocks for state management in key/value format
State can be stored in
MongoDB
Redis
Azure Cosmos DB
Azure Blob Storage
and other components are supported
State store component specs
Publish & subscribe
This is so-called Pub/Sub. This is a building block to realize Publish and Subscribe of messages.
Message routing
Guarantee of at-least-once message delivery
Consumer group management
and other functions such as
Bindings
Building blocks for connecting external systems to Dapr. Routine processes such as polling and retry processing for external systems can be handled by Bindings, eliminating the need for home-grown implementations.
Bindings provides two functions: Input bindings for triggering the application with events from external resources, and Output bindings for calling external resources. Is the data stored in S3? Or is it DynamoDB? Bindings allows you to store data without having to be aware of whether the data is stored in S3 or DynamoDB.
Bindings for various external resources have been developed in the GitHub repository
https://github.com/dapr/components-contrib/tree/master/bindings
Actors
Building blocks for using the Actor model in any language and platform. The Actor pattern writes code as a self-contained unit = actor that receives messages and processes them one at a time.
The Actor model runs in a single thread, allowing multiple actors to run concurrently, but each actor processes incoming messages one at a time. There is always guaranteed to be no more than one active thread in an actor, making it easy to create concurrent and parallel systems.
Observability
Building block that provides observability.
Distributed tracing
Metric Harvesting
Log collection
Health checks
and other functions. This building block supports OpenTelemetry and Zipkin, making it easy to integrate with external services such as New Relic and DataDog.
Secrets management
This building block manages secrets such as DB connection strings, API keys, and client certificates. This building block allows the application code to manage the
Environment variables
Local files
Kubernetes secrets
AWS Secrets Manager
Azure Key Vault
GCP Secret Manager
HashiCorp Vault
You can easily use secret stores such as
Let's give it a try!
Now that we have somewhat of an overview of Dapr, let's actually run a sample app. This time, let's deploy Hello Kubernetes, a sample app provided in the following repository, on EKS
https://github.com/dapr/quickstarts/tree/master/hello-kubernetes
Environment
Here is the environment we used this time
Kubernetes: 1.19
eksctl: 0.56.0
kubectl: v1.19.6-eks-49a6c0
Dapr CLI: 1.2.0
Helm: v3.6.2+gee407bd
This time, we will install various CLI tools in the CloudShell environment and execute commands from CloudShell.
Building an EKS cluster
First, create an EKS cluster
$ eksctl create cluster --name dapr-cluster --managed
The CloudFormation stack will be deployed.
ksctl-dapr-cluster-cluster
eksctl-dapr-cluster-nodegroup-ng-xxxxxxxxxx
Wait until the two stacks change to the CREATE_COMPLETE state. After the stacks are deployed, let's check the operation briefly.
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 19m
$ kubectl get node
NAME STATUS ROLES AGE VERSION
ip-192-168-25-77.ap-northeast-1.compute.internal Ready <none> 9m44s v1.19.6-eks-49a6c0
ip-192-168-62-49.ap-northeast-1.compute.internal Ready <none> 9m43s v1.19.6-eks-49a6c0
Initializing Dapr
Next, deploy the Dapr components on the EKS cluster
$ dapr init --kubernetes
⌛ 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
✅ Deploying the Dapr control plane to your cluster...
Dapr has been installed to namespace dapr-system. To verify, run `dapr status -k' in your terminal: https://aka.ms/dapr-getting-started
Let's check the deployed service
$ kubectl get svc --namespace dapr-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dapr-api ClusterIP 10.100.180.138 <none> 80/TCP 9m37s
dapr-dashboard ClusterIP 10.100.56.127 <none> 8080/TCP 9m37s
dapr-placement-server ClusterIP None <none> 50005/TCP,8201/TCP 9m37s
dapr-sentry ClusterIP 10.100.29.44 <none> 80/TCP 9m37s
dapr-sidecar-injector ClusterIP 10.100.178.19 <none> 443/TCP 9m37s
The deployed pods are as follows
$ kubectl get pods --namespace dapr-system
NAME READY STATUS RESTARTS AGE
dapr-dashboard-58b4647996-4fzs2 1/1 Running 0 10m
dapr-operator-85bdd7d89d-9tndp 1/1 Running 0 10m
dapr-placement-server-0 1/1 Running 0 10m
dapr-sentry-76bfc5f7c7-wqhs6 1/1 Running 0 10m
dapr-sidecar-injector-786645f444-ph62k 1/1 Running 0 10m
Deploying Redis State Store
Hello Kubernetes, the sample app we will deploy this time, uses Redis as its State Store, so let's create a Redis State Store using Helm.
First, add the repository
$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
... Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!
Then install bitnami/redis chart
$ helm install redis bitnami/redis
NAME: redis
LAST DEPLOYED: Sun Jul 18 04:16:20 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
** Please be patient while the chart is being deployed **
Redis(TM) can be accessed on the following DNS names from within your cluster:
redis-master.default.svc.cluster.local for read/write operations (port 6379)
redis-replicas.default.svc.cluster.local for read-only operations (port 6379)
To get your password run:
export REDIS_PASSWORD=$(kubectl get secret --namespace default redis -o jsonpath="{.data.redis-password}" | base64 --decode)
To connect to your Redis(TM) server. 1:
Run a Redis(TM) pod that you can use as a client:
kubectl run --namespace default redis-client --restart='Never' --env REDIS_PASSWORD=$REDIS_PASSWORD --image docker.io/bitnami/redis:6.2.4-debian-10-r13 --command -- sleep infinity
Use the following command to attach to the pod:
kubectl exec --tty -i redis-client \
--namespace default -- bash
Connect using the Redis(TM) CLI:
redis-cli -h redis-master -a $REDIS_PASSWORD
redis-cli -h redis-replicas -a $REDIS_PASSWORD
To connect to your database from outside the cluster execute the following commands:
kubectl port-forward --namespace default svc/redis-master 6379:6379 &
redis-cli -h 127.0.0.1 -p 6379 -a $REDIS_PASSWORD
Confirm that the Redis service and pod are running
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 37m
redis-headless ClusterIP None <none> 6379/TCP 3m6s
redis-master ClusterIP 10.100.237.250 <none> 6379/TCP 3m6s
redis-replicas ClusterIP 10.100.16.88 <none> 6379/TCP 3m6s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis-master-0 1/1 Running 0 3m17s
redis-replicas-0 1/1 Running 0 3m17s
redis-replicas-1 1/1 Running 0 2m27s
redis-replicas-2 1/1 Running 0 110s
Deploying the sample app
Now that we are ready, we will deploy the sample app. First, clone the GitHub repository
$ git clone https://github.com/dapr/quickstarts.git
Cloning into 'quickstarts'...
remote: Enumerating objects: 2593, done.
remote: Counting objects: 100% (295/295), done.
remote: Compressing objects: 100% (151/151), done.
remote: Total 2593 (delta 164), reused 233 (delta 134), pack-reused 2298
Receiving objects: 100% (2593/2593), 10.30 MiB | 26.04 MiB/s, done.
Resolving deltas: 100% (1528/1528), done.
Deploy the Node.js sample app
$ cd quickstarts/hello-kubernetes
$ kubectl apply -f . /deploy/node.yaml
service/nodeapp created
deployment.apps/nodeapp created
Here is the source code for the sample app, which uses Redis as the State Store, but the application code hides the presence of Redis, so you can read and write to Redis by simply issuing an HTTP request.
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
const express = require('express');
const bodyParser = require('body-parser');
require('isomorphic-fetch');
const app = express();
app.use(bodyParser.json());
// These ports are injected automatically into the container.
const daprPort = process.env.DAPR_HTTP_PORT;
const daprGRPCPort = process.env.DAPR_GRPC_PORT;
const stateStoreName = `statestore`;
const stateUrl = `http://localhost:${daprPort}/v1.0/state/${stateStoreName}`;
const port = 3000;
app.get('/order', (_req, res) => {
fetch(`${stateUrl}/order`)
.then((response) => {
if (!response.ok) {
throw "Could not get state."
}
return response.text();
}).then((orders) => {
res.send(orders);
}).catch((error) => {
console.log(error);
res.status(500).send({message: error});
});
});
app.post('/neworder', (req, res) => {
const data = req.body.data;
const orderId = data.orderId;
console.log("Got a new order! Order ID: " + orderId);
const state = [{
key: "order",
value: data
}];
fetch(stateUrl, {
method: "POST",
body: JSON.stringify(state),
headers: {
"Content-Type": "application/json"
}
}).then((response) => {
if (!response.ok) {
throw "Failed to persist state."
}
console.log("Successfully persisted state.");
res.status(200).send();
}).catch((error) => {
console.log(error);
res.status(500).send({message: error});
});
});
app.get('/ports', (_req, res) => {
console.log("DAPR_HTTP_PORT: " + daprPort);
console.log("DAPR_GRPC_PORT: " + daprGRPCPort);
res.status(200).send({DAPR_HTTP_PORT: daprPort, DAPR_GRPC_PORT: daprGRPCPort })
});
app.listen(port, () => console.log(`Node App listening on port ${port}! `)));
Here is an Express app with three routes defined, each with the following process summary
GET /ports
Returns the port number of Dapr
POST /neworder
Stores order data in the State Store
GET /order
Retrieve order data from the State Store
If you check after deployment is complete, you will see that the service named nodeapp is running.
$ kubectl get svc nodeapp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nodeapp LoadBalancer 10.100.241.126 xxxxxxxx.ap-northeast-1.elb.amazonaws.com 80:31990/TCP 96s
Since the ELB is deployed in the front of the pod, get the FQDN of the ELB
$ export NODE_APP=$(kubectl get svc nodeapp --output 'jsonpath={.status.loadBalancer.ingress[0].hostname}')
A quick check of the operation with the curl command
$ curl $NODE_APP/ports
{"DAPR_HTTP_PORT": "3500", "DAPR_GRPC_PORT": "50001"}
The response is returned. It seems to be working properly.
Next, deploy the Python sample app. This sample app is an app that keeps sending order data to the State Store.
# ------------------------------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------------------------------
import os
import requests
import time
dapr_port = os.getenv("DAPR_HTTP_PORT", 3500)
dapr_url = "http://localhost:{}/v1.0/invoke/nodeapp/method/neworder".format(dapr_port)
n = 0
while True:
n += 1
message = {"data": {"orderId": n}}
try:
response = requests.post(dapr_url, json=message, timeout=5)
if not response.ok:
print("HTTP %d => %s" % (response.status_code,
response.content.decode("utf-8")), flush=True)
except Exception as e:
print(e, flush=True)
time.sleep(1)
Deploy
$ kubectl apply -f . /deploy/python.yaml
deployment.apps/pythonapp created
After a while, the sample app pod will be launched
$ kubectl get pods --selector=app=python
NAME READY STATUS RESTARTS AGE
pythonapp-fcb4f49b-gv4tr 2/2 Running 0 26s
Checking the logs of the Node.js app, we can see that the order data is continuously POSTed from the Python app
$ kubectl logs --selector=app=node -c node --tail=-1
Order ID: 82
Successfully persisted state.
Order ID: 82 Successfully persisted state.
Successfully persisted state.
... Abbreviation
Accessing the Node.js app endpoint /order shows that the order data stored in the State Store has been overwritten from the Python app
$ curl $NODE_APP/order
{"orderId":177}
$ curl $NODE_APP/order
{"orderId":178}
Finally, let's check the contents of the State Store (Redis) directly from redis-cli
$ export REDIS_PASSWORD=$(kubectl get secret --namespace default redis -o jsonpath="{.data.redis-password}" | base64 --decode)
$ kubectl exec -it redis-master-0 -- redis-cli -a $REDIS_PASSWORD
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> hgetall "nodeapp||order"
1) "data"
2) "{\"orderId\":707}"
3) "version"
4) "700"
Summary
We have tried an overview survey of Dapr and deployed a sample application. This time, we only tested State management among Dapr's building blocks, but we would like to try other building blocks again.
Reference
Dapr Docs
Dapr for .NET Developers
Microsoft Releases Dapr 1.0, an Open Source Distributed Application Runtime, Offering Kubernetes Support, Inter-Service Messaging, State Management, and More
What is Dapr, Microsoft's leading runtime for modern distributed systems?
Dapr, Microsoft's Distributed Application Runtime for "Microservices Development," Reaches Version 1.0
0 コメント:
コメントを投稿