Daprとは
DaprはOSSでMicrosoftがリードするOSSの分散アプリケーション向けランタイムです。
Daprによって分散アプリケーションを構築するために必要な基本機能が提供されるため、
開発者はこれらの機能を自作する必要がなくなります。
以下はGitHubのDaprリポジトリで紹介されているDaprの概要です。
様々なプログラミング言語からHTTP APIもしくはgRPC APIを通じてビルディングブロックと呼ばれる様々な機能が利用できることが分かります。Daprは任意の環境で実行できるように設計されており、MicrosoftのクラウドサービスであるAzure以外にもAWSやGCPといったクラウド環境上での動作がサポートされています。
サポートされる言語
様々な言語向けにDaprのSDKが提供されており、SDKを利用することで簡単にDaprをアプリケーションに組み込めます。SDKは
- Daprのビルディングブロックを呼び出すためのClient SDK
- 別サービスからの呼び出しやトピックのサブスクライブを実現するための利用するServer extensions
- 仮想アクターを構築するためのActor SDK
の3種に分かれます。
2021/7時点での言語別各種SDKの対応状況は以下の通りです。
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 | ✔ |
ビルディングブロック
Daprはいくつかのビルディングブロックから構成されており、開発者は自分に必要なビルディングブロックだけを選択して利用できように構成されています。提供されるビルディングブロックは以下の通りで、分散アプリケーションの構築に必要となる一通りの機能を有していることがわかります
Service invocation
gRPC APIもしくはHTTP APIを利用して、別のアプリケーションをセキュアに呼び出すためのビルディングブロックです
- 名前空間
- mTLSによる認証
- アクセスコントロール
- リトライ
- サービスディスカバリ
- ロードバランシング
- トレーシング
といった機能が提供されます
State management
キー/バリュー形式でステート管理を実現するためのビルディングブロックです
ステートの保存先として
- MongoDB
- Redis
- Azure Cosmos DB
- Azure Blob Storage
等のコンポーネントがサポートされています
Publish & subscribe
いわゆるPub/Subですね。メッセージのPublishとSubscribeを実現するためのビルディングブロックです。
- メッセージのルーティング
- At-least-onceなメッセージ配信の保証
- コンシューマーグループの管理
といった機能が提供されます
Bindings
外部システムとDaprを接続するためのビルディングブロックです。外部システムのポーリングやリトライ処理といった定型的な処理はBindingsに任せることで、自前実装が不要になります。
Bindingsは外部リソースのイベントをトリガーにアプリケーションをトリガーするためのInput bindings、外部リソースを呼び出すためのOutput bindings2つの機能が提供されます。Bindingsを利用すると、アプリケーションコードからはデータの保存先がS3なのか?それともDynamoDBなのか?といったことを意識せずにデータを保存できるようになります。
GitHubのリポジトリでは様々な外部リソース向けのBindingsが開発されています
https://github.com/dapr/components-contrib/tree/master/bindings
Actors
任意の言語、プラットフォームでアクターモデルを利用するためのビルディングブロックです。アクターパターンはメッセージを受信して1度に1つずつ処理する自己完結型のユニット=アクターとしてコードを記述します。
アクターモデルはシングル スレッドで動作し、複数のアクターが同時実行可能ですが、各アクターは受信したメッセージを一度に 1 つずつ処理します。 アクター内でアクティブなスレッドは常に1つ以下ということが保証され、同時実行システムや並列システムを簡単に作成できるようになります。
Observability
可観測性を提供するビルディングブロックです。
- 分散トレーシング
- メトリックの収取
- ログの収集
- ヘルスチェック
といった機能をサポートします。このビルディングブロックはOpenTelemetryとZipkinをサポートするため、New RelicやDataDogといった外部サービスとの連携も容易に実現できます。
Secrets management
DBの接続文字列、APIキー、クライアント証明書といったシークレットを管理するためのビルディングブロックです。このビルディングブロックを利用すると、アプリケーションコードから
- 環境変数
- ローカル ファイル
- Kubernetes シークレット
- AWS Secrets Manager
- Azure Key Vault
- GCP Secret Manager
- HashiCorp Vault
といったシークレットストアを簡単に利用できるようになります
やってみる
なんとなくDaprの概要が分かったので、実際にサンプルアプリを動かしてみます。今回は以下のリポジトリで提供されているサンプルアプリHello KubernetesをEKS上にデプロイしてみます
https://github.com/dapr/quickstarts/tree/master/hello-kubernetes
環境
今回利用した環境です
- Kubernetes: 1.19
- eksctl: 0.56.0
- kubectl: v1.19.6-eks-49a6c0
- Dapr CLI: 1.2.0
- Helm: v3.6.2+gee407bd
今回はCloudShell環境に諸々のCLIツールをインストールし、CloudShell上からコマンドを実行していきます。
EKSクラスタの構築
まずEKSクラスタを構築します
$ eksctl create cluster --name dapr-cluster --managed
CloudFormationのスタックがデプロイされるので
- ksctl-dapr-cluster-cluster
- eksctl-dapr-cluster-nodegroup-ng-xxxxxxxx
2つのスタックがCREATE_COMPLETE状態に変わるまで待ちます。スタックのデプロイが完了したら簡単に動作確認しておきましょう。
$ 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
Daprの初期化
続いてEKSクラスタにDaprのコンポーネントをデプロイします
$ 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...
✅ 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
デプロイされたサービスを確認してみます
$ 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
デプロイされたPodは以下の通りです
$ 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
RedisのState Storeをデプロイ
今回デプロイするサンプルアプリHello KubernetesはState StoreにRedisを利用します。Helmを使ってRedisのState Storeを作成しておきましょう。
まずはリポジトリの追加
$ 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!⎈
続いてbitnami/redis
チャートをインストール
$ 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
2. 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
Redisのサービス、Podが動作していることを確認しておきます
$ 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
サンプルアプリのデプロイ
準備ができたので、サンプルアプリをデプロイしていきます。まずGitHubのリポジトリをクローン
$ 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.
Node.jsのサンプルアプリをデプロイします
$ cd quickstarts/hello-kubernetes
$ kubectl apply -f ./deploy/node.yaml
service/nodeapp created
deployment.apps/nodeapp created
サンプルアプリのソースコードはこちらです。State StoreとしてはRedisを利用していますが、アプリケーションコードからはRedisの存在が隠蔽されており、単にHTTPリクエストを発行するだけでRedisに対して読み書きできることが分かります。
// ------------------------------------------------------------
// 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}!`));
3つのルートが定義されたExpressのアプリで、それぞれの処理概要は以下の通りです
- GET /ports
- Daprのポート番号を返却する
- POST /neworder
- State Storeに注文データを保存する
- GET /order
- State Storeから注文データを取得する
デプロイ完了後に確認すると、nodeappという名前のサービスが動作していることが分かります。
$ kubectl get svc nodeapp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nodeapp LoadBalancer 10.100.241.126 xxxxxx.ap-northeast-1.elb.amazonaws.com 80:31990/TCP 96s
Podの前段にELBがデプロイされているので、ELBのFQDNを取得します
$ export NODE_APP=$(kubectl get svc nodeapp --output 'jsonpath={.status.loadBalancer.ingress[0].hostname}')
curlコマンドで簡単に動作確認します
$ curl $NODE_APP/ports
{"DAPR_HTTP_PORT":"3500","DAPR_GRPC_PORT":"50001"}
レスポンスが返却されました。ちゃんと動いてそうですね。
続いてPythonのサンプルアプリをデプロイします。こちらのサンプルアプリは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)
デプロイします
$ kubectl apply -f ./deploy/python.yaml
deployment.apps/pythonapp created
しばらくするとサンプルアプリのPodが起動してきます
$ kubectl get pods --selector=app=python
NAME READY STATUS RESTARTS AGE
pythonapp-fcb4f49b-gv4tr 2/2 Running 0 26s
Node.jsアプリのログを確認するとPythonアプリから連続して注文データがPOSTされていることが分かります
$ kubectl logs --selector=app=node -c node --tail=-1
Got a new order! Order ID: 82
Successfully persisted state.
Got a new order! Order ID: 83
Successfully persisted state.
...略
Node.jsアプリのエンドポイント/order
にアクセスするとState Storeに保存された注文データがPythonアプリから上書きされていることが分かります
$ curl $NODE_APP/order
{"orderId":177}
$ curl $NODE_APP/order
{"orderId":178}
最後に redis-cliからState Store(Redis)の中身を直接確認してみましょう
$ 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"
まとめ
Daprの概要調査とサンプルアプリのデプロイまで試してみました。今回はDaprのビルディングブロックのうち、State managementぐらいしか試せていませんが、他のビルディングブロックについても改めて試してみたいと思います。
0 コメント:
コメントを投稿