2022年6月21日火曜日

What is Dapr?  Dapr provides basic functionality so that developers do not have to create these functions themselves. Dapr also supports Python's FastAPI.

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 コメント:

コメントを投稿