MinikubeでDaprを試してみた(Service Invocation/State Management)

# はじめに

Daprの動きが気になったので、Minikubeを使って確認してみました。
まずはサービス間呼び出し、stateへの格納をサンプルを使って動かしてみて、APIドキュメントを確認しています。
また、導入時のオーバーヘッドも簡単にみてみました。

# 準備

## Minikube環境の準備

Linux + Docker 環境で Minikubeを動かすためのセットアップ

### Dockerインストール

```
$ curl -fsSL https://get.docker.com  | sh -
$ sudo usermod -aG docker <ユーザー名>
```

### Minikubeインストール

```
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
$ chmod 755 kubectl 
$ sudo mv kubectl /usr/local/bin/.
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64   && chmod +x
 minikube
$ sudo install minikube /usr/local/bin/
$ sudo apt install conntrack
$ minikube start --driver=none
$ sudo chown -R <ユーザー:グループ> .kube/
$ sudo chown -R <ユーザー:グループ> .minikube/ 
```

# Daprの導入

## CLI導入

公式ドキュメントに沿ってCLIを導入します
[公式ドキュメントはこちら](https://github.com/dapr/docs/blob/master/getting-started/environment-setup.md)

minikubeが動作しているサーバーで下記コマンドを実行します。

```
$ curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | /bin/bash
```

## MinikubeにDaprを導入

バージョンは2020/05/03時点で最新の0.6.0を指定します

```
$ dapr init --kubernetes --runtime-version 0.6.0
```

正常にインストールが終わればdefaultのネームスペースにPodが作成されます。

```
NAME                                     READY   STATUS    RESTARTS   AGE
dapr-sidecar-injector-7849f77c4b-vxfgq   1/1     Running   0          23m
dapr-operator-7d9668fd8b-nmrtm           1/1     Running   0          23m
dapr-sentry-756f7799fd-blgp6             1/1     Running   0          23m
dapr-placement-7dbcc6bf59-fvth4          1/1     Running   0          23m
```

# サンプルを実行する

Gitに公開されているサンプルの中から、Kubernetesのサンプルを実行してみます。

![Architecture_Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306826/9f2e7452-2ebc-145e-eea9-83fe3334e682.png)

やってることをざっくり書くと、pythonで無限ループでインクリメントしたカウントを引数にnodeのアプリを呼び出し、nodeはひたすらカウントをredisに書き込みます。
これで、python-node間のAPI呼び出しとnode-redis間のデータ登録をDapr経由で行われることが確認できます。

## サンプルのデプロイ

### Redis

サンプルにRedis自体のデプロイは含まれていないので、事前にHelmからデプロイしておきます。

```
$ sudo snap install helm --classic
$ helm repo add stable https://kubernetes-charts.storage.googleapis.com
$ helm install redis stable/redis
```

RedisのPodが立ち上がったらパスワードを確認します。

```
$ kubectl get secret --namespace default redis -o jsonpath="{.data.redis-password}" | base64 --decode
```

### アプリ

```
$ git clone https://github.com/dapr/samples.git
$ cd samples/2.hello-kubernetes/deploy
```

#### Redisの設定を埋め込む

ホスト名とパスワードを埋め込みます。ホスト名はredis-master(kubectl get serviceで確認できる）、パスワードはさっき確認したやつ。

```
$ vi redis.yaml

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  metadata:
  - name: redisHost
    value: <ホスト名>:6379
  - name: redisPassword
    value: <パスワード>
```

#### デプロイ

```
$ kubectl apply -f redis.yaml
$ kubectl apply -f node.yaml
$ kubectl apply -f python.yaml
```

正しくデプロイできたら、kubectl get poで見たときに、node,pythonが２つずつコンテナが起動します。

```
NAME                                     READY   STATUS    RESTARTS   AGE
dapr-operator-6b577ddf48-b878j           1/1     Running   0          13h
dapr-placement-6648767c45-6wfzb          1/1     Running   0          13h
dapr-sentry-6b674cb77b-llg9v             1/1     Running   0          13h
dapr-sidecar-injector-77bff6d75b-pp657   1/1     Running   0          13h
nodeapp-57cdcb5bdc-gxxmm                 2/2     Running   0          12h
pythonapp-f777dd679-n87t8                2/2     Running   0          12h
redis-master-0                           1/1     Running   0          12h
redis-slave-0                            1/1     Running   0          12h
redis-slave-1                            1/1     Running   0          12h
```

また、nodeのログを見ると、neworderが登録され続けてるのが確認できます。

```
Got a new order! Order ID: 45793
Successfully persisted state.
Got a new order! Order ID: 45794
Successfully persisted state.
Got a new order! Order ID: 45795
Successfully persisted state.
Got a new order! Order ID: 45796
Successfully persisted state.
Got a new order! Order ID: 45797
Successfully persisted state.
```

# API呼び出し仕様

サンプルとAPIドキュメントを見ながら、呼び出し仕様を確認します。

### python

無限ループで http://localhost:3500/v1.0/invoke/nodeapp/method/neworder を繰り返し呼び出すだけのコードです。
locahost:3500/v1.0/invoke/でDaprのサイドカーにリクエストを投げ、nodejsの/neworderへリクエストが渡るようです。

### node.js

neworderのリクエストを受けたら http://localhost:${daprPort}/v1.0/state/statestore へオーダー登録を模したリクエストを投げます。
これがRedisへの登録処理になります。

## Service Invocationの仕様

サービス間呼び出しをDaprで統一的に扱う仕様です。

[ドキュメント](https://github.com/dapr/docs/blob/master/reference/api/service_invocation_api.md)

HTTP Requestの仕様

daprPortは自PodのYAMLに記載しているDaprポート、appIdは呼び出し先YAMLに記載しているコンテナのアノテーションに記載したdapr.io/id、method-nameは呼び出し先アプリ内で実装しているAPIのパスになります。

```
POST/GET/PUT/DELETE http://localhost:<daprPort>/v1.0/invoke/<appId>/method/<method-name>
```

## State Managementの仕様

今回のRedisのようなstate（状態）維持をDaprで統一的に扱うための仕様です。

[ドキュメント](https://github.com/dapr/docs/blob/master/reference/api/state_api.md)

基本的には以下のようなComponentをデプロイします。（今回のサンプルではredis.yamlがそれ）

```
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: <NAME>
  namespace: <NAMESPACE>
spec:
  type: state.<TYPE>
  metadata:
  - name:<KEY>
    value:<VALUE>
  - name: <KEY>
    value: <VALUE>
```

#### アクセスするためのAPI

APIの細かい仕様はドキュメントに記載がありますが、ポイントはstorenameとComponentのmetadata.nameが一致するstateにアクセスされる点ですね。

POST

```
POST http://localhost:<daprPort>/v1.0/state/<storename>
```

GET

```
GET http://localhost:<daprPor>/v1.0/state/<storename>/<key>
```

DELETE

```
DELETE http://localhost:<daprPort>/v1.0/state/<storename>/<key>
```

サポートしているstateは下記ドキュメントに記載があります。KeyValueだけでなくドキュメントDBやRDBのデータベースもサポートされているようです。

[statestoreのドキュメント](https://github.com/dapr/components-contrib/tree/master/state)

ドキュメントDBやRDBはどんな風に使えるのかまた見てみたいと思います。

# 使用メモリ

Sidecarがインジェクションされるので、どれくらいオーバーヘッドがあるのか確認。
（Daprがないときは、同じアプリでアノテーションでDaprをfalseにして確認）

#### Daprがないとき。

```
NAME                         CPU(cores)   MEMORY(bytes)   
nodeapp-59cb95db9b-zjzdf     0m           15Mi            
pythonapp-76f498d768-tbv6g   3m           13Mi            
redis-master-0               6m           4Mi             
redis-slave-0                5m           4Mi             
redis-slave-1                6m           4Mi             
```

#### Daprがあるとき。

```
NAME                                     CPU(cores)   MEMORY(bytes)   
dapr-operator-6b577ddf48-b878j           1m           7Mi             
dapr-placement-6648767c45-6wfzb          1m           5Mi             
dapr-sentry-6b674cb77b-llg9v             1m           4Mi             
dapr-sidecar-injector-77bff6d75b-pp657   1m           5Mi             
nodeapp-57cdcb5bdc-6r489                 5m           34Mi            
pythonapp-f777dd679-n87t8                6m           26Mi            
redis-master-0                           8m           4Mi             
redis-slave-0                            7m           4Mi             
redis-slave-1                            5m           4Mi             
```

結果はmetrics-serverのkubectl top poの内容になります。
Dapr本体は合計で21Mi。
SideCarはnodeで19Mi、pythonで13Mi増加しました。

これくらいなら、オーバーヘッドを過剰に意識する必要はなさそうです。

# ハマった点

元々、自宅オンプレ（プロキシ配下）のk3sから始めたのですが

- k3sだとうまく動かなかった（なぜかSidecarがインジェクションされない）
- Minikubeでもプロキシ配下だとうまく動かなかった（Sidecarはインジェクションされるが、ヘルスチェックでエラーになる）

となり、結果、GCP上にサーバー立ててMinikubeで確認しました。動かなかった理由は時間あるときにソースベースで追ってみたいと思います。

# まとめ

サンプルベースで動かしてみて、動かしながらAPIドキュメントを見たところ、そんなに難しくなく使える感じがしました。（細かい設定もドキュメントを見ながらやればできそう）
今回確認したAPI呼び出しやデータアクセスが全てlocalhostのhttp呼び出しで書けるのは外部に依存しない実装が書きやすく、DDDなどとも相性が良さそうな印象でした。
また、オーバーヘッドも大きくないので、とりあえず導入してみても良さそうです。（まだGAではないですが）

次はドキュメントDBやRDBとPub/Subを見てみたいと思います。