Dapr の動作をローカルと AKS で確認する
分散アプリケーションランタイム「Dapr」について手元でワイワイ動作させてみました。
まえがき
[Dapr とは]
Dapr (Distributed Application Runtime)は、クラウドネイティブなマイクロサービス開発を簡素化するための、オープンソースのランタイムフレームワークです。Daprは、分散システムの要素(例えば、ステート管理、サービス間通信、イベント駆動アーキテクチャなど)を抽象化し、開発者がこれらの機能を直接コードに組み込むことなく、シンプルなAPI経由で使用できるようにします。Daprはプラットフォームに依存せず、どの言語やフレームワークでも使用できます。これにより、マイクロサービスの開発とデプロイを大幅に効率化できます。(by ChatGPT)
Dapr に関しての理解を深くしたいなと思っていたので、記事を書くことによって自ずと知識が深まるんじゃないかと期待してこの記事を書いてます。(浅い考え)
詳しくはこちらの記事にも書かれているのでご参考ください。
今回はローカル環境の Docker を使って動作を確認し、その後チュートリアルのアプリケーションを使って AKS に Dapr をインストールし、デプロイしていきます。
ローカル環境での確認
Dapr をインストール
まずは動作を手元で確認してみます。私の環境は M2 MacBook Air になります。Air いいですね、軽くて。
CLI のインストールから開始します。
% brew install dapr/tap/dapr-cli
アプリケーションの作成
手元での確認用にこのドキュメントをみて進めていましたが、せっかくなので言語を変えて Go で同じことをやってみたいと思います。
ちなみに ChatGPT 君に手助けしてもらいました。ありがとう、心の友。
Go で Dapr を使うので、go get しておきます。
% go get github.com/dapr/go-sdk/client
実際に作成したアプリケーションは以下となります。
package main
import (
"context"
"fmt"
"log"
"strconv"
"time"
dapr "github.com/dapr/go-sdk/client"
)
const (
storeName = "statestore"
key = "counter"
)
func main() {
client, err := dapr.NewClient()
if err != nil {
log.Fatalf("Failed to create Dapr client: %v", err)
}
defer client.Close()
item, err := client.GetState(context.Background(), storeName, key, nil)
if err != nil {
log.Fatalf("Failed to get state: %v", err)
}
var counter int
if item.Value != nil {
counter, err = strconv.Atoi(string(item.Value))
if err != nil {
log.Fatalf("Failed to parse counter: %v", err)
}
}
for {
counter++
fmt.Printf("Counter = %d\n", counter)
err = client.SaveState(context.Background(), storeName, key, []byte(strconv.Itoa(counter)), nil)
if err != nil {
log.Fatalf("Failed to save state: %v", err)
}
time.Sleep(1 * time.Second)
}
}
ついでに、先日使った Draft も使って Dockerfile も作ってもらいました。やはり便利です。
FROM golang:1.20
ENV PORT 80
EXPOSE 80
WORKDIR /go/src/app
COPY . .
RUN go mod vendor
RUN go build -v -o app
RUN mv ./app /go/bin/
CMD ["app"]
実行
では実行していきます。この時にちゃんと Docker 立ち上げておかないとエラーになります(当たり前のことですが、私は忘れていました)
まず初期化します。
% dapr init
⌛ Making the jump to hyperspace...
ℹ️ Container images will be pulled from Docker Hub
(略)
✅ Success! Dapr is up and running. To get started, go here: https://aka.ms/dapr-getting-started
「Success」と出ました。プリケーションを Dapr コマンドを使って実行します。
% dapr run --app-id dapr-go go run main.go
ℹ️ Starting Dapr with id dapr-go. HTTP Port: 58112. gRPC Port: 58113
ℹ️ Checking if Dapr sidecar is listening on HTTP port 58112
(略)
ℹ️ Checking if Dapr sidecar is listening on GRPC port 58113
ℹ️ Dapr sidecar is up and running.
ℹ️ Updating metadata for appPID: 92690
ℹ️ Updating metadata for app command: go run main.go
✅ You're up and running! Both Dapr and your app logs will appear here.
== APP == dapr client initializing for: 127.0.0.1:58113
== APP == Counter = 1
== APP == Counter = 2
== APP == Counter = 3
== APP == Counter = 4
== APP == Counter = 5
== APP == Counter = 6
^Cℹ️
(略)
== APP == signal: interrupt
✅ Exited Dapr successfully
✅ Exited App successfully
アプリケーションは以下のようなカウントをしています。
== APP == Counter = 1
== APP == Counter = 2
== APP == Counter = 3
== APP == Counter = 4
== APP == Counter = 5
== APP == Counter = 6
一度アプリケーションを止めてみます。その後再開すると、カウンターが再開することがわかります。
% dapr run --app-id dapr-go go run main.go
ℹ️ Starting Dapr with id dapr-go. HTTP Port: 58142. gRPC Port: 58143
ℹ️ Checking if Dapr sidecar is listening on HTTP port 58142
(略)
ℹ️ Updating metadata for app command: go run main.go
✅ You're up and running! Both Dapr and your app logs will appear here.
== APP == dapr client initializing for: 127.0.0.1:58143
== APP == Counter = 7
== APP == Counter = 8
== APP == Counter = 9
== APP == Counter = 10
^Cℹ️
terminated signal received: shutting down
INFO[0005] Dapr shutting down app_id=dapr-go instance=MihonoMacBook-Air.local scope=dapr.runtime type=log ver=1.11.1
(略)
✅ Exited Dapr successfully
✅ Exited App successfully
続きの == APP == Counter = 7
からカウントされました。前の状態が保存されていますね。何が起きているのか動作を確認してみましょう。
ここに
ローカル環境用に Dapr を最初に初期化したときに、Redis コンテナーが自動的にプロビジョニングされています。 その後、Redis コンテナーは、Dapr により、statestore.yaml という名前のコンポーネント構成ファイルを使用して、既定の状態ストア コンポーネントとして構成されています。 その内容は次のようになります。
と記載されています。ほう...?では、Redis コンテナーが作成されているかを確認してみましょう。
(下の3つが自動的に作成されたもの)ここで状態が保存されていることがわかります。
コンポーネント構成ファイル
既定の場合は、以下のような構成ファイルで Kubernetes だと用意されているようです。
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
- name: actorStateStore
value: "true"
ドキュメントを読んでいくと namespace で分けたりもできそうです。
必要な場合は、コンポーネントを特定のアプリケーションにさらに限定することもできます。 production 名前空間内で、Redis キャッシュへのアクセスを DaprCounter アプリケーションのみに制限することができます。
AKS で実装してみる。
動作がなんとなくわかったところで、AKS で実装してみます。今回使うのは以下のリポジトリになります。
参考にしたサイト:
リポジトリ:
※上記のイメージはリポジトリから引用させていただきました。
状態ストアはさまざまなコンポーネントから選択できます。今回は Redisで作成します。
何はともあれまずは gitclone でサンプルアプリケーションを Clone します。
% git clone https://github.com/dapr/quickstarts.git
% cd quickstarts/tutorials/hello-kubernetes/
AKS の作成
AKS を作成します。
% myResourceGroup=<リソースグループ名>
% location=japaneast
% myAKSCluster=<AKS クラスター名>
% az group create --name $myResourceGroup --location $location
% az aks create -g $myResourceGroup -n $myAKSCluster --enable-managed-identity --node-count 3 --enable-addons monitoring --enable-msi-auth-for-monitoring --generate-ssh-keys
作成後、AKS に接続します。
% az aks get-credentials --resource-group $myResourceGroup --name $myAKSCluster
接続できたことを確認します。
% kubectl get pods
No resources found in default namespace.
Dapr 拡張機能をクラスターに作成します。詳しくはこちらを参考にしてください。
拡張機能を作成して AKS クラスターに Dapr をインストールします。
% az k8s-extension create --cluster-type managedClusters \
--cluster-name $myAKSCluster \
--resource-group $myResourceGroup \
--name dapr \
--extension-type Microsoft.Dapr
うっかり見逃しがちですが、初めてインストールする場合はこちら N にしましょう。私は見逃しました。
Is Dapr already installed in the cluster? (y/N): N
これで準備は完了です。
Redis ストアの作成
Azure Portal から作成します。20分くらいかかりますので気長に待ちます。作成後、インスタンスのホスト名をメモします。
例:XXXXXX.redis.cache.windows.net
[設定]-[アクセスキー] から、アクセスキーをメモしておきます。
アクセスキーを先ほど作成した AKS に格納します。Kubernetes の Secret を作成し、そこに入れます。今回は検証のため、Secret を使用しましたが、Azure KeyVault に入れたいですね。
% kubectl create secret generic redis --from-literal=redis-password=<your-redis-password>
作成できていることを確認します。
% kubectl get secret
NAME TYPE DATA AGE
redis Opaque 1 6s
Dapr コンポーネントの構成
Clone した "hello-kubernetes" の中のディレクトリ "deploy" の中に YAML ファイルが複数あります。その中の "redis.yaml" を開いて編集します。
- redisHost: メモしたホスト名 + ポート番号 (例:xxxxxxx.redis.cache.windows.net:6380)
- redisPassword: Secret 名を redis-password から変更した場合は、key をその名前に変更
- enableTLS: true を追加
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: xxxxxx.redis.cache.windows.net:6380
- name: redisPassword
secretKeyRef:
name: redis
key: redis-password
- name: enableTLS
value: true
auth:
secretStore: kubernetes
構成を適用します。
% kubectl apply -f ./deploy/redis.yaml
component.dapr.io/default created
Dapr サイドカーを使用して Node.js アプリをデプロイ
今回はそのまま用意されているアプリを使います。まずは Node.js アプリをデプロイ
% kubectl apply -f ./deploy/node.yaml
service/nodeapp created
deployment.apps/nodeapp created
稼働を確認します。
% kubectl get pods
NAME READY STATUS RESTARTS AGE
nodeapp-7bbf67cd77-dt46j 2/2 Running 0 67s
STATUS が Error になっていた場合は、正しく稼働していません。kubectl describe pod <Pod 名>
を確認し以下のようなメッセージが出ていた場合は、Redis の接続がうまく行っていない場合があるので、設定を見直ししましょう。
- Pod の状態
% kubectl get pod
NAME READY STATUS RESTARTS AGE
nodeapp-7bbf67cd77-hwkcp 1/2 Error 4 (55s ago) 2m5s
- エラーメッセージ
Warning Unhealthy 5s (x4 over 35s) kubelet Readiness probe failed: Get "http://10.244.2.11:3501/v1.0/healthz": dial tcp 10.244.2.11:3501: connect: connection refused
Warning Unhealthy 5s (x3 over 35s) kubelet Liveness probe failed: Get "http://10.244.2.11:3501/v1.0/healthz": dial tcp 10.244.2.11:3501: connect: connection refused
私は以下の点に引っかかりました。ちゃんとドキュメントは読みましょうということですね。
- RedisHost の名前が設定されていなかった
- port をちゃんと見てなかった
xxxxxx.redis.cache.windows.net:6380
Pod が立ち上がったことを確認して、External-IP を確認します。
% kubectl get svc nodeapp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nodeapp LoadBalancer 10.0.184.136 XX.44.185.69 80:31297/TCP 3m49s
確認
% curl XX.44.185.69/ports
{"DAPR_HTTP_PORT":"3500","DAPR_GRPC_PORT":"50001"}%
では、curl を使ってアプリケーションに命令をしてみます。この sample.json
には、{"data":{"orderId":"42"}}
が格納されています。
% curl --request POST --data "@sample.json" --header Content-Type:application/json $EXTERNAL_IP/neworder
curl を使用して、命令が保持されていることを確認します。
% curl $EXTERNAL_IP/order
{"orderId":"42"}%
Python アプリを使う
次に Python アプリをデプロイします。これは Dapr のデフォルトリスニングポートである localhost:3500 に JSON メッセージを投稿する基本的な Python アプリです。詳しくはリポジトリの中身を見ていただくとわかりますが、1秒ごとに orderId の番号が増えている動作をしています。
それではデプロイします。
% kubectl apply -f ./deploy/python.yaml
% kubectl get pods
NAME READY STATUS RESTARTS AGE
nodeapp-7bbf67cd77-dt46j 2/2 Running 0 15m
pythonapp-6bdf8dd995-dcp7k 2/2 Running 0 81s
ではカウントが増えているか確認してみましょう。
% curl $EXTERNAL_IP/order
{"orderId":657}%
% curl $EXTERNAL_IP/order
{"orderId":659}%
Redis 側も見てみよう
Redis 周りもちゃんと使われているか見てみたいと思います。Redis に接続します。
% key="アクセスキー"
% redis-cli -h XXX.redis.cache.windows.net -a $key -p 6380 --tls
XXX.redis.cache.windows.net:6380> HGET nodeapp||order data
"{\"orderId\":1646}"
monitor コマンドでも見てみると
XXX.redis.cache.windows.net:6380> monitor
OK
1689985041.744649 [0 xxxx.253.120.25:3072] "EVAL" "\n\tlocal etag = redis.pcall(\"HGET\", KEYS[1], \"version\");\n\tif type(etag) == \"table\" then\n\t redis.call(\"DEL\", KEYS[1]);\n\tend;\n\tlocal fwr = redis.pcall(\"HGET\", KEYS[1], \"first-write\");\n\tif not etag or type(etag)==\"table\" or etag == \"\" or etag == ARGV[1] or (not fwr and ARGV[1] == \"0\") then\n\t redis.call(\"HSET\", KEYS[1], \"data\", ARGV[2]);\n\t if ARGV[3] == \"0\" then\n\t redis.call(\"HSET\", KEYS[1], \"first-write\", 0);\n\t end;\n\t return redis.call(\"HINCRBY\", KEYS[1], \"version\", 1)\n\telse\n\t return error(\"failed to set key \" .. KEYS[1])\n\tend" "1" "nodeapp||order" "0" "{\"orderId\":1483}" "1"
1689985041.744803 [0 lua] "HGET" "nodeapp||order" "version"
1689985041.744822 [0 lua] "HGET" "nodeapp||order" "first-write"
1689985041.744834 [0 lua] "HSET" "nodeapp||order" "data" "{\"orderId\":1483}"
1689985041.744853 [0 lua] "HINCRBY" "nodeapp||order" "version" "1"
できてましたね。
まとめ
Dapr を手元と AKS で動かしてみることにより、少し動作の雰囲気がわかりました。分散システムを作るときの状態管理に Dapr を使うのはとても良さそうです。
実際にアプリケーションを1から構築すると、もっと理解が深まると思うので EC サイトなどを作って動作検証をしてみたいと思います。
参考
Discussion
参考にされている... !
最近AI に浮気してるので Container 周りにもキャッチアップ頑張ります :)