Experience Dapr - Hello World


Dapr 是微軟開發的 Distributed Application Framework。核心概念是 Sidecar 模式,搭配 Building BlocksComponents 等類似 Middleware / Pipeline 的概念,以及 K8s CRD (Custom Resource Definition) 的 擴充 Extensions / Plugins 的機制,以這些為主要設計的結構,然後完善大部分分散式系統 (或者說 Microservice) 所必要的基礎建設、開發體驗、維運考量、及架構擴展的需求。

這些概念與特性疊加起來實在是非常吸引我,加上 Dapr 本身很輕量,版本也已經到了 1.0 版,官方宣稱 production ready,可以一試了!底下是探索性的整理使用體驗。

在這之前,我也 Study 過類似的框架,其中有 Line 的 Armeria、carousell 的 Orion … 不過概念以及考量的成熟度,還是 Dapr 比較完整。


Experience Dapr

Setup, Initial

依照官方文件的 Getting Started,直接在本機安裝好一個環境,並且初始化。

1
2
3
4
5
6
7
8
9
10
11
12
❯ dapr init
⌛ Making the jump to hyperspace...
↑ Downloading binaries and setting up components...
Dapr runtime installed to /Users/rick/.dapr/bin, you may run the following to add it to your path if you want to run daprd directly:
export PATH=$PATH:/Users/rick/.dapr/bin
✅ Downloaded binaries and completed components set up.
ℹ️ daprd binary has been installed to /Users/rick/.dapr/bin.
ℹ️ dapr_placement container is running.
ℹ️ dapr_redis container is running.
ℹ️ dapr_zipkin container is running.
ℹ️ Use `docker ps` to check running containers.
✅ Success! Dapr is up and running. To get started, go here: https://aka.ms/dapr-getting-started

完成初始化之後,可以在 docker 發現啟動了三個 container,如下:

1
2
3
4
5
~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bf0fc3456f59 daprio/dapr "./placement" 10 days ago Up 52 minutes 0.0.0.0:50005->50005/tcp dapr_placement
a2837dfafc81 openzipkin/zipkin "start-zipkin" 10 days ago Up 52 minutes (healthy) 9410/tcp, 0.0.0.0:9411->9411/tcp dapr_zipkin
64a7fd503cc3 redis "docker-entrypoint.s…" 10 days ago Up 52 minutes 0.0.0.0:6379->6379/tcp dapr_redis

更新版本,例如 v1.0.0 -> v1.1.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
~$ dapr uninstall --all
ℹ️ Removing Dapr from your machine...
ℹ️ Removing directory: /Users/rick/.dapr/bin
ℹ️ Removing container: dapr_placement
ℹ️ Removing container: dapr_redis
ℹ️ Removing container: dapr_zipkin
ℹ️ Removing directory: /Users/rick/.dapr
✅ Dapr has been removed successfully

~$ dapr init --runtime-version=1.1.0
⌛ Making the jump to hyperspace...
↓ Downloading binaries and setting up components...
Dapr runtime installed to /Users/rick/.dapr/bin, you may run the following to add it to your path if you want to run daprd directly:
export PATH=$PATH:/Users/rick/.dapr/bin
✅ Downloaded binaries and completed components set up.
ℹ️ daprd binary has been installed to /Users/rick/.dapr/bin.
ℹ️ dapr_placement container is running.
ℹ️ dapr_redis container is running.
ℹ️ dapr_zipkin container is running.
ℹ️ Use `docker ps` to check running containers.
✅ Success! Dapr is up and running. To get started, go here: https://aka.ms/dapr-getting-started

~$ dapr --version
CLI version: 1.0.0
Runtime version: 1.1.0

Dapr Runtime and Components

接下來就是 dapr 初始化的 component 定義檔、以及 bin,這些東西會放在 $HOME/.dapr 下,如下:

1
2
3
4
5
6
7
8
9
10
11
12
~/.dapr ❯ tree -L 2
.
├── bin/
│   ├── daprd # dapr runtime, sidecar
│   ├── dashboard
│   └── web/
├── components/
│   ├── pubsub.yaml # 預設的 pubsub: 也就是 docker ps 裡的 redis
│   └── statestore.yaml # 預設的 store: 也就是 docker ps 裡的 redis
└── config.yaml # dapr sidecar 的配置檔

3 directories, 5 files

查看相關 images 的大小:

1
2
3
4
5
6
7
8
9
10
11
12
## Bin 底下的東西大小不大
~/.dapr ❯ ls -lah
total 254304
drwxrwxrwx 5 rick staff 160B Feb 26 20:17 .
drwxr-xr-x 5 rick staff 160B Feb 26 20:17 ..
-rwxrwxrwx 1 rick staff 92M Feb 26 20:17 daprd
-rwxrwxrwx 1 rick staff 32M Feb 26 20:17 dashboard
drwxr-xr-x 3 rick staff 96B Feb 26 20:17 web

## docker images,大小約 200MB,不算大。
~$ docker images | grep dapr
daprio/dapr latest 226e3e87fe9f 2 weeks ago 208MB

Dapr CLI

看看 dapr 版本資訊,亂試 bin 底下的東西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
## Dapr CLI 位置
which dapr
/usr/local/bin/dapr

## Dapr CLI & Runtime 版本
❯ dapr --version
CLI version: 1.0.0
Runtime version: 1.0.1

## Dapr Runtime
~/.dapr/bin ❯ ./daprd --version
1.0.1

## 莫名其妙就開啟 dashboard 了,如截圖:
~/.dapr/bin ❯ ./dashboard
Dapr Dashboard running on http://localhost:8080

Dashboard 的 Overview 有 Dapr Applications、Components (這兩個應該是 ~/.dapr/components 裡的宣告 )、還有 Dapr Configurations

直接看看 Dapr CLI 有哪一些東西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
~$ dapr
__
____/ /___ _____ _____
/ __ / __ '/ __ \/ ___/
/ /_/ / /_/ / /_/ / /
\__,_/\__,_/ .___/_/
/_/

===============================
Distributed Application Runtime

Usage:
dapr [command]

Available Commands:
completion Generates shell completion scripts
components List all Dapr components. Supported platforms: Kubernetes
configurations List all Dapr configurations. Supported platforms: Kubernetes
dashboard Start Dapr dashboard. Supported platforms: Kubernetes and self-hosted
help Help about any command
init Install Dapr on supported hosting platforms. Supported platforms: Kubernetes and self-hosted
invoke Invoke a method on a given Dapr application. Supported platforms: Self-hosted
list List all Dapr instances. Supported platforms: Kubernetes and self-hosted
logs Get Dapr sidecar logs for an application. Supported platforms: Kubernetes
mtls Check if mTLS is enabled. Supported platforms: Kubernetes
publish Publish a pub-sub event. Supported platforms: Self-hosted
run Run Dapr and (optionally) your application side by side. Supported platforms: Self-hosted
status Show the health status of Dapr services. Supported platforms: Kubernetes
stop Stop Dapr instances and their associated apps. . Supported platforms: Self-hosted
uninstall Uninstall Dapr runtime. Supported platforms: Kubernetes and self-hosted
upgrade Upgrades a Dapr control plane installation in a cluster. Supported platforms: Kubernetes

Flags:
-h, --help help for dapr
-v, --version version for dapr

跟很多開發工具都很像,這些後面應該都用得到。

Launch an Application

經過上述觀察,試跑一個 dapr application,啥扣都還沒寫,依照文件跑以下:

1
2
3
4
5
6
7
8
9
10
❯ dapr run --app-id myapp --dapr-http-port 3500
WARNING: no application command found.
ℹ️ Starting Dapr with id myapp. HTTP Port: 3500. gRPC Port: 62638
ℹ️ Checking if Dapr sidecar is listening on HTTP port 3500

... 中間的 Log 後面整理 ...

ℹ️ Checking if Dapr sidecar is listening on GRPC port 62638
ℹ️ Dapr sidecar is up and running.
✅ You're up and running! Dapr logs will appear here.

看起來就跑起來了,啟動的 Log 訊息很多,開啟 HTTP Port: 3500. gRPC Port: 62638 兩個。

底下整理針對 Dapr App 跑起來之後的觀察與探索。

檢查 Container

查看 docker ps 的狀況,會發現還是只有那三個 container!並沒有多了一個叫 myapp 的 container。

1
2
3
4
5
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bf0fc3456f59 daprio/dapr "./placement" 10 days ago Up 52 minutes 0.0.0.0:50005->50005/tcp dapr_placement
a2837dfafc81 openzipkin/zipkin "start-zipkin" 10 days ago Up 52 minutes (healthy) 9410/tcp, 0.0.0.0:9411->9411/tcp dapr_zipkin
64a7fd503cc3 redis "docker-entrypoint.s…" 10 days ago Up 52 minutes 0.0.0.0:6379->6379/tcp dapr_redis

探索 Log

在還沒搞清楚 這個 myapp 到底是啥鬼之前,我仔細的看了前述省略的 Log 內容,整理如下。

整理幾個重點:

  • 每一行 Log 都會描述 app_id, instance, scope, type, ver 等資訊,注意 scope 的差異。
  • scope 出現以下幾種:
    • dapr.runtime,
      • dapr.runtime.grpc.api, dapr.runtime.grpc.internal
      • dapr.runtime.http
      • dapr.runtime.actor, dapr.runtime.actor.internal.placement
    • dapr.metrics
    • dapr.contrib
  • Initialized name resolution to standalone: 看起來是跟 Service Invocation / Service Discovery 有關係
  • component loaded: 載入兩個 compontnets,應該就是 ~/.dapr/components 底下的配置
    • name: pubsub, type: pubsub.redis
    • name: statestore, type: state.redis/
  • enabled middleware
    • gRPC tracing => scope: dapr.runtime.grpc.api + dapr.runtime.grpc.internal
    • gRPC metrics => scope: dapr.runtime.grpc.api + dapr.runtime.grpc.internal
    • metrics http => scope: dapr.runtime.http
    • tracing http => scope: dapr.runtime.http
  • 起來兩個 listners:
    • API gRPC server is running on port 62638
    • http server is running on port 3500
  • 其他看不懂的:
    • actor runtime started
    • failed to read from bindings
    • placement tables updated

底下是完整的 Log (方便一目瞭然,有些 KV 換行整理說明):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
INFO[0000] starting Dapr Runtime \
-- version 1.0.0 \
-- commit 6314733 \
app_id=myapp \
instance=iStar.local \
scope=dapr.runtime \
type=log \
ver=1.0.0

INFO[0000] log level set to: info app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] metrics server started on :62639/ app_id=myapp instance=iStar.local scope=dapr.metrics type=log ver=1.0.0
INFO[0000] standalone mode configured app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] app id: myapp app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] mTLS is disabled. Skipping certificate request and tls validation app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] local service entry announced: myapp -> 192.168.2.253:62643 app_id=myapp instance=iStar.local scope=dapr.contrib type=log ver=1.0.0
INFO[0000] Initialized name resolution to standalone app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] component loaded. name: pubsub, type: pubsub.redis/ app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] waiting for all outstanding components to be processed app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] component loaded. name: statestore, type: state.redis/ app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] all outstanding components processed app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0

INFO[0000] enabled gRPC tracing middleware app_id=myapp instance=iStar.local scope=dapr.runtime.grpc.api type=log ver=1.0.0
INFO[0000] enabled gRPC metrics middleware app_id=myapp instance=iStar.local scope=dapr.runtime.grpc.api type=log ver=1.0.0
INFO[0000] API gRPC server is running on port 62638 app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] enabled metrics http middleware app_id=myapp instance=iStar.local scope=dapr.runtime.http type=log ver=1.0.0
INFO[0000] enabled tracing http middleware app_id=myapp instance=iStar.local scope=dapr.runtime.http type=log ver=1.0.0
INFO[0000] http server is running on port 3500 app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] The request body size parameter is: 4 app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] enabled gRPC tracing middleware app_id=myapp instance=iStar.local scope=dapr.runtime.grpc.internal type=log ver=1.0.0
INFO[0000] enabled gRPC metrics middleware app_id=myapp instance=iStar.local scope=dapr.runtime.grpc.internal type=log ver=1.0.0
INFO[0000] internal gRPC server is running on port 62643 app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0

INFO[0000] actor runtime started. actor idle timeout: 1h0m0s. actor scan interval: 30s app_id=myapp instance=iStar.local scope=dapr.runtime.actor type=log ver=1.0.0
WARN[0000] failed to read from bindings: app channel not initialized app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] dapr initialized. Status: Running. Init Elapsed 11.713ms app_id=myapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] placement tables updated, version: 0 app_id=myapp instance=iStar.local scope=dapr.runtime.actor.internal.placement type=log ver=1.0.0

傳送請求

依照文件,發送一個 POST 給 Application,如下:

1
2
3
4
~$ curl -X POST \
-H "Content-Type: application/json" \
-d '[{ "key": "name", "value": "Bruce Wayne"}]' \
http://localhost:3500/v1.0/state/statestore

到 dapr_redis container 檢查:

1
2
3
4
5
6
7
8
9
~$ docker exec -it dapr_redis redis-cli

127.0.0.1:6379> keys *
1) "myapp||name"
127.0.0.1:6379> hgetall "myapp||name"
1) "data"
2) "\"Bruce Wayne\""
3) "version"
4) "1"

用 Dapr CLI 觀察

用 dapr cli 可以觀察到應用程式存在:

1
2
3
4
5
6
7
8
❯ dapr list
APP ID HTTP PORT GRPC PORT APP PORT COMMAND AGE CREATED PID
myapp 3500 56336 0 11m 2021-03-09 08:51.48 20204

## 停掉 myapp
❯ dapr stop myapp
✅ app stopped successfully: myapp

疑問:我的 App 在哪?

這個名叫 myapp 的 dapr app 跑起來了,但是,我並沒有指定任何程式給 dapr cli,但是上述的 curl 卻能放東西到 redis,這是怎麼一回事?

這個疑惑先放在這裡,後面會說明。

Hello World

上個跑半天,不知道在做啥鬼,直接開跑官方 QuickStarts 的 Hello World 範例。下圖是官方提供的架構圖,我把相關的 port 都標上去了:

底下快速把 Hello World 跑起來:

1
2
3
4
~$ git clone https://github.com/dapr/quickstarts.git
~$ cd quickstarts/hello-world
~$ npm install
~$ dapr run --app-id nodeapp --app-port 3000 --dapr-http-port 3500 node app.js

同前面的 myapp 一樣,會出現一堆 log,大部分都大同小異,只有最後面會出現這樣的訊息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
❯ dapr run --app-id nodeapp --app-port 3000 --dapr-http-port 3500 node app.js

ℹ️ Starting Dapr with id nodeapp. HTTP Port: 3500. gRPC Port: 63958
INFO[0000] starting Dapr Runtime -- version 1.0.0 -- commit 6314733 app_id=nodeapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] log level set to: info app_id=nodeapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] metrics server started on :63959/ app_id=nodeapp instance=iStar.local scope=dapr.metrics type=log ver=1.0.0
INFO[0000] standalone mode configured app_id=nodeapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] app id: nodeapp app_id=nodeapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0

... 略 ...

INFO[0000] internal gRPC server is running on port 63963 app_id=nodeapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] application protocol: http. waiting on port 3000. This will block until the app is listening on that port. app_id=nodeapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
== APP == Node App listening on port 3000!

INFO[0000] application discovered on port 3000 app_id=nodeapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] application configuration loaded app_id=nodeapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] actor runtime started. actor idle timeout: 1h0m0s. actor scan interval: 30s app_id=nodeapp instance=iStar.local scope=dapr.runtime.actor type=log ver=1.0.0
INFO[0000] dapr initialized. Status: Running. Init Elapsed 65.36200000000001ms app_id=nodeapp instance=iStar.local scope=dapr.runtime type=log ver=1.0.0
INFO[0000] placement tables updated, version: 0 app_id=nodeapp instance=iStar.local scope=dapr.runtime.actor.internal.placement type=log ver=1.0.0
ℹ️ Updating metadata for app command: node app.js
✅ You're up and running! Both Dapr and your app logs will appear here.

這次和上次不一樣,多了 == APP == Node App listening on port 3000! 這段訊息,其他差不多都一樣。

Post Message

透過 curl 發送訊息:

1
2
3
4
5
~$ curl -XPOST \
-d @sample.json \
-H "Content-Type:application/json" \
http://localhost:3500/v1.0/invoke/nodeapp/method/neworder

App 的 Log 出現以下訊息,收到 Post 並且寫入 persistance 了。

1
2
3
== APP == Got a new order! Order ID: 42

== APP == Successfully persisted state.

或者透過 Dapr CLI:

1
2
3
4
~$ dapr invoke \
--app-id nodeapp \
--method neworder \
--data '{"data": { "orderId": "42" } }'

也會得到同樣的結果。

Post 的位置?

上述的 URI 長這樣:

http://localhost:3500/v1.0/invoke/nodeapp/method/neworder

  1. Post 的 port 是 3500 - dapr http port, 而不是 app port 3000
  2. 這個結構的規則是如下:

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

這是透過 dapr runtime (sidecar) 的 HTTP API 打指定的 app 的 method,其中主要的機制是 Service Invocation 的實作。

詳細的規格參考 Service invoction API reference

直接打 port 3000 呢?像這樣:

1
2
3
4
curl -XPOST \
-d @sample.json \
-H "Content-Type:application/json" \
http://localhost:3000/neworder

是可以 work 的,就是跳過 Sidecar 直接 invoke Application.

Get Order

上述是打 neworder ,取得 GetOrder 把流程走完,同樣的可以用 curl or dapr cli

1
2
3
4
5
6
7
curl http://localhost:3500/v1.0/invoke/nodeapp/method/order

# Dapr CLI
dapr invoke \
--app-id nodeapp \
--method order \
--verb GET

回傳結果:

1
2
{"orderId":"42"}
✅ App invoked successfully

PythonApp

官方的 Hello World 中,最後用 python 寫另一個 service,每秒自動建立新訂單的迴圈。

同樣的把這個 python 當作一個 Darp App 跑起來:

1
2
3
4
5
6
❯ dapr run --app-id pythonapp cmd /c "python3 app.py"

❯ dapr list
APP ID HTTP PORT GRPC PORT APP PORT COMMAND AGE CREATED PID
pythonapp 49312 49313 0 python3 app.py 5s 2021-03-09 23:36.26 3159
nodeapp 3500 63958 3000 node app.js 35m 2021-03-09 23:00.42 84911

Q and A

Q: 無法暫停 Dapr CLI 應用程式?

  1. dapr list 檢查狀況
  2. crtl + z 丟到 background
  3. kill -9 <PID> 砍掉
1
2
3
4
5
6
7
8
9
10
# 正常啟動
~$ dapr list
APP ID HTTP PORT GRPC PORT APP PORT COMMAND AGE CREATED PID
my-app 3500 50045 0 8s 2021-05-08 07:44.23 6942

# 啟動異常
~$ dapr list
APP ID HTTP PORT GRPC PORT APP PORT COMMAND AGE CREATED PID
0 0 0 28s 2021-05-08 07:39.41 6475


小結

簡單整理 Dapr 的初體驗,剛開始還沒搞清楚到底是怎麼運做,到後來漸漸了解整體設計的想法,是一個可以期待、深度研究的設計。

下一篇 將整理 Dapr Run on K8s,以及詳細的運作過程與原理,對於上述 Sidecar 的想法會更清晰,同時也會展開 dapr 幾個很重要的概念,像是 Building Blocks、Components 。


延伸閱讀

Dapr 系列文章

分散式系統系列文章



Comments

  • 全站索引
  • 關於這裏
  • 關於作者
  • 學習法則
  • 思考本質
  • 一些領悟
  • 分類哲學
  • ▲ TOP ▲