Dapr 入門教程

Dapr(Distributed Application Runtime) 是微軟於 2019 年 10 月 16 日 首次發佈 [1] 的分佈式程序運行時,到現在已經過去 1 年多,從最初的 v0.1.0 到現在的 v1.0.0-rc2,加入了好多新的功能。支持的中間件越來越多,基本上主流的中間件 (本地版和各雲提供商的託管版) 都可以被支持。Dapr 運行時也從原來的只支持單 Instance 變成了 v1.0.0-rc1 以後的支持多 Instance(HA mode),讓我們一起進入精彩的 Dapr 的世界。

Dapr 是什麼?

參考 Dapr 官方網站 [2] ,"An event-driven, portable runtime for building microservices on cloud and edge",或者複雜點說,"Dapr is a portable, event-driven runtime that makes it easy for any developer to build resilient, stateless and stateful applications that run on the cloud and edge and embraces the diversity of languages and developer frameworks"。翻譯過來就是,Dapr 是一個在雲和邊緣構建微服務用的事件驅動的,可移植的運行時。更復雜的來說,"Dapr 是一個可移植的,事件驅動的運行時,使開發人員可以輕鬆創建在雲和邊緣上運行的有彈性,無狀態和有狀態的應用程序,支持語言和開發人員框架的多樣性"。聽起來讓人一頭霧水,讓我解釋一下它到底是什麼意思。

Dapr 能爲我做什麼?

Dapr 提供如下的 Building blocks:

  1. Service-to-service invocation,服務間調用,指的是一個微服務可以通過 Dapr 調用另一個微服務。

  2. State management,狀態管理,指的是一個微服務通過 Dapr 把狀態數據保存到某個地方(PostgreSQL,Redis,DynamoDB 等等)。

  3. Publish and subscribe,就是所謂的消息總線(Message Bus),或者說發佈者 / 訂閱者模式。一個微服務把消息通過 Dapr 發佈(Publish)給某個 Topic,所有訂閱(Subscribe)這個 Topic 的微服務都能通過 Dapr 收到這個消息。

  1. Resource bindings,就是所謂的消息隊列(Message Queue,或者叫消息代理,Message Broker),分爲兩種綁定,一種是輸出綁定(Output Binding),一種是輸入綁定(Input Binding)。出和入是看數據的流向,輸出綁定就是作爲 Producer 的 App 把消息通過 Dapr 傳給消息隊列,輸入綁定就是作爲 Consumer 的 App 通過 Dapr 從消息隊列裏得到消息。補充一點,這裏的消息隊列和 Pub/Sub 裏的消息總線有什麼區別呢?可以這樣理解: 一個消息進入消息總線的話,所有訂閱者都能得到這個消息。而一個消息進入消息隊列的話,由 Consumer 來取,一次只有一個人能得到。此外,消息總線是不要求處理順序的,兩個消息進入消息總線,誰先被拿到順序是不一定的,而消息隊列可以保證是先入先出的。

  1. Actors,實現了 Actor Model[3] 。

  2. Observability, 可觀測性,就是 Dapr 提供了模板讓你方便地進行觀測,比如說用 Prometheus+Grafana 來看系統的 Metrics,用 Zipkin 來進行分佈鏈路追蹤,用 Elasticsearch+Fluentd+Kibana(俗稱 EFK)來對日誌進行檢索。

  3. Secrets,就是密鑰,指的是一個微服務通過 Dapr 從 Secret Store 取得密鑰。

安裝 Dapr

安裝 CLI

在安裝 Dapr runtime 之前需要先安裝 Dapr 的 CLI。有兩個原因,第一,如果你以後不是用 Helm 來安裝 Dapr 的話,需要用 Dapr CLI 來執行 dapr init。第二,即使你不用 Dapr CLI,而是用 Helm 來安裝,以後查看 Dapr 系統狀態的時候還是要用到 Dapr CLI,比如查看 Runtime 的狀態(dapr status -k)、查看 Components 狀態(dapr components -k)、查看 Configurations 狀態(dapr configurations -k),甚至啓動 Dashboard 的時候還是要執行(dapr dashboard -k)。你可以在很多 OS 上安裝 Dapr CLI,比如 Windows,Linux,Mac OS,我用的是 Windows10 系統,可以選擇在 Windows 上直接裝,也可以在 WSL(Windows Subsystem for Linux) 上裝,這裏選擇在 WSL 上安裝(沒有 WSL 的話在 windows 商店裏選一個免費的 Linux 裝上。我選的是 Ubuntu)。參考官方文檔 How-To: Install Dapr CLI[4] ,在 Ubuntu 裏執行:

$ wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash
Your system is linux_amd64
Installing Dapr CLI...

Getting the latest Dapr CLI...
Installing v0.11.0 Dapr CLI...
Downloading https://github.com/dapr/cli/releases/download/v0.11.0/dapr_linux_amd64.tar.gz ...
[sudo] password for ubuntu:
dapr installed into /usr/local/bin successfully.
CLI version: 0.11.0
Runtime version: n/a

To get started with Dapr, please visit https://docs.dapr.io/getting-started/

安裝完成。看看版本號:

$ dapr --version
CLI version: 0.11.0
Runtime version: n/a

CLI 是 0.11.0 版的,Runtime 因爲還沒裝,所以顯示 n/a。因爲接下來要安裝 1.0.0-rc.2 的 Runtime,1.x 相對於 0.x 版有了重大的改進 (比如多 Instance 模式),爲了版本的統一性 (低版本的 CLI 操作高版本的 Runtime 可能會出問題),把 CLI 升級到 v1.0.0-rc.2 版的吧。

curl -LO https://github.com/dapr/cli/releases/download/v1.0.0-rc.2/dapr_linux_amd64.tar.gz
tar -xzf dapr_linux_amd64.tar.gz
sudo cp dapr /usr/local/bin/dapr

再看一下版本:

$ dapr --version
CLI version: 1.0.0-rc.2
Runtime version: n/a

CLI 安裝完畢。接下來安裝 Runtime。

安裝 Runtime

這裏有兩種選擇,一個是 Standalone mode,一個是 Kubernetes mode。我們兩個都試一下。

安裝 Docker Engine

Dapr Runtime 安裝的前提條件是要先安裝 Docker。不然安裝 Dapr 的時候會報 "could not connect to Docker. Docker may not be installed or running" 的錯。這裏參考 Docker 的官方文檔 Install Docker Engine on Ubuntu[5] 來安裝 Docker Engine。

$ sudo apt-get update
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io

安裝完成。把 docker 的 service 啓動:

$ sudo service docker start

看看 service 是否啓動:

$ service docker status
 * Docker is running

OK,Docker Engine 安裝完成。

安裝 Standalone 版的 Runtime

用 dapr init 來安裝單機版,可以指定版本號。目前最新的是 1.0.0-rc.2。

$ dapr init --runtime-version=1.0.0-rc.2
⌛  Making the jump to hyperspace...
←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑↗  Downloading binaries and setting up components...
Dapr runtime installed to /root/.dapr/bin, you may run the following to add it to your path if you want to run daprd directly:
    export PATH=$PATH:/root/.dapr/bin
→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←↑→↓←✅  Downloaded binaries and completed components set up.
ℹ️  daprd binary has been installed to /root/.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
export PATH=$PATH:/root/.dapr/bin

完成。看看都裝了什麼東西。

$ docker ps
CONTAINER ID   IMAGE               COMMAND                  CREATED         STATUS                   PORTS                              NAMES
293b479ed26c   openzipkin/zipkin   "start-zipkin"           5 minutes ago   Up 4 minutes (healthy)   9410/tcp, 0.0.0.0:9411->9411/tcp   dapr_zipkin
a07696221fd2   daprio/dapr         "./placement"            5 minutes ago   Up 4 minutes             0.0.0.0:50005->50005/tcp           dapr_placement
c475c415332b   redis               "docker-entrypoint.s…"   6 minutes ago   Up 6 minutes             0.0.0.0:6379->6379/tcp             dapr_redis

跑起來三個 Container。一個 dapr_zipkin,一個 dapr_placement,一個 dapr_redis。看看版本號:

$ dapr --version
CLI version: 1.0.0-rc.2
Runtime version: 1.0.0-rc.2

至此單機版的 Dapr Runtime 安裝完成。dapr list 一下看看,什麼也沒有。因爲我們還沒有啓動 App。

安裝 Kubernetes 版的 Runtime

安裝之前需要先有 Kubernetes 環境,minikube,AWS 託管的 EKS,Azure 託管的 AKS,GCP 託管的 GKE 等等。這裏選擇在本地安裝 minikube。參考 官方文檔 [6]

$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
$ sudo install minikube-linux-amd64 /usr/local/bin/minikube

看看版本:

$ minikube version
minikube version: v1.16.0
commit: 9f1e482427589ff8451c4723b6ba53bb9742fbb1

先執行

$ sudo usermod -aG docker $USER && newgrp docker

不然 minikube 啓動的時候會報錯。然後啓動 minikube 環境:

$ minikube start
😄  minikube v1.16.0 on Ubuntu 20.04
✨  Automatically selected the docker driver
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
💾  Downloading Kubernetes v1.20.0 preload ...
    > preloaded-images-k8s-v8-v1....: 491.00 MiB / 491.00 MiB  100.00% 2.56 MiB

🔥  Creating docker container (CPUs=2, Memory=3100MB) ...
🐳  Preparing Kubernetes v1.20.0 on Docker 20.10.0 ...
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔎  Verifying Kubernetes components...
🌟  Enabled addons: storage-provisioner, default-storageclass
💡  kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A'
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

看看 docker ps 的結果:

$ docker ps
CONTAINER ID   IMAGE                                           COMMAND                  CREATED          STATUS                    PORTS                                                                                                      NAMES
cc47e6e56d32   gcr.io/k8s-minikube/kicbase:v0.0.15-snapshot4   "/usr/local/bin/entr…"   4 minutes ago    Up 4 minutes              127.0.0.1:49156->22/tcp, 127.0.0.1:49155->2376/tcp, 127.0.0.1:49154->5000/tcp, 127.0.0.1:49153->8443/tcp   minikube
fedf3508c0f4   daprio/dapr:1.0.0-rc.2                          "./placement"            43 minutes ago   Up 43 minutes             0.0.0.0:50005->50005/tcp                                                                                   dapr_placement
c52f4a72abc4   redis                                           "docker-entrypoint.s…"   44 minutes ago   Up 44 minutes             0.0.0.0:6379->6379/tcp                                                                                     dapr_redis
af7b8dd7dcf3   openzipkin/zipkin                               "start-zipkin"           44 minutes ago   Up 44 minutes (healthy)   9410/tcp, 0.0.0.0:9411->9411/tcp                                                                           dapr_zipkin

多了個 minikube 的 Container。接下來安裝 kubectl:

$ 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 +x ./kubectl
$ sudo mv ./kubectl /usr/local/bin/kubectl

看看版本:

$ kubectl version --client
Client Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.1", GitCommit:"c4d752765b3bbac2237bf87cf0b1c2e307844666", GitTreeState:"clean", BuildDate:"2020-12-18T12:09:25Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}

安裝 Dapr Runtime 可以用 Dapr CLI 也可以用 Helm。我們選擇用 Helm 來安裝:

$ curl https://baltocdn.com/helm/signing.asc | sudo apt-key add -
$ sudo apt-get install apt-transport-https --yes
$ echo "deb https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
$ sudo apt-get update
$ sudo apt-get install helm

看下版本:

$ helm version
version.BuildInfo{Version:"v3.4.2", GitCommit:"23dd3af5e19a02d4f4baa5b2f242645a1a3af629", GitTreeState:"clean", GoVersion:"go1.14.13"}

終於可以安裝 Dapr 了。這裏指定目前最新的版本號 1.0.0-rc.2。

$ helm repo add dapr https://dapr.github.io/helm-charts/
$ helm repo update
$ kubectl create namespace dapr-system
$ helm install dapr dapr/dapr --version 1.0.0-rc.2 --namespace dapr-system

看下版本號:

$ dapr status -k
  NAME                   NAMESPACE    HEALTHY  STATUS   REPLICAS  VERSION     AGE  CREATED
  dapr-dashboard         dapr-system  True     Running  1         0.5.0       1m   2020-12-26 02:22.08
  dapr-sidecar-injector  dapr-system  True     Running  1         1.0.0-rc.2  1m   2020-12-26 02:22.08
  dapr-sentry            dapr-system  True     Running  1         1.0.0-rc.2  1m   2020-12-26 02:22.08
  dapr-operator          dapr-system  True     Running  1         1.0.0-rc.2  1m   2020-12-26 02:22.08
  dapr-placement-server  dapr-system  True     Running  1         1.0.0-rc.2  1m   2020-12-26 02:22.08

我們看看啓動了什麼 Pod:

$ kubectl get pods -n dapr-system
NAME                                     READY   STATUS    RESTARTS   AGE
dapr-dashboard-6f749469dd-z7hzx          1/1     Running   0          3m45s
dapr-operator-699cd79686-nndhd           1/1     Running   0          3m45s
dapr-placement-server-0                  1/1     Running   0          3m45s
dapr-sentry-7c4fb54fb7-xsx5q             1/1     Running   0          3m45s
dapr-sidecar-injector-6bdbc588fc-tzpm4   1/1     Running   0          3m45s

啓動了 5 個 Pod:dapr-dashboarddapr-operatordapr-placementdapr-sentrydapr-sidecar-injector

把 Dapr Dashboard 起來看看:

$ dapr dashboard -k

效果和 kubectl port-forward svc/dapr-dashboard 8080:8080 -n dapr-system 是一樣的。在瀏覽器裏輸入 http://localhost:8080

恭喜,Dapr Runtime 終於安裝完成了!

下面我們來部署一個 Dapr 的微服務的例子。

Dapr 程序的部署 (Standalone 模式)

上面 Dapr 的運行時環境已經安裝完成。今天我們在 Standalone 模式部署第一個 Dapr 程序。程序來自 Dapr 官方的 quickstarts 教程裏的 Hello World[7] ,我們用目前的最新版本 v1.0.0-rc.2。

$ git clone -b v1.0.0-rc.2 https://github.com/dapr/quickstarts.git
$ cd quickstarts

hello-world 下面是 Standalone 版的,進去看看。程序很簡單,一個 Node 的程序,提供 3 個 Endpoint(在 StateStore 裏保存,取得,刪除發過來的 OrderId)。還有一個 Python 的程序,每秒發 1 個 HTTP Request 給 Node 程序。

下面開始部署。Standalone 模式下首先要先安裝 Node 和 Python 的運行環境。不然 dapr run 的時候會報錯。Node 的運行環境:

$ sudo apt update
$ sudo apt install nodejs
$ sudo apt install npm
$ sudo npm install
$ npm list

裏面有 express 和 body-parser 的話就可以用 Dapr 啓動 node 的程序了。

$ 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: 43509
INFO[0000] starting Dapr Runtime -- version 1.0.0-rc.2 -- commit 196483d  app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] log level set to: info                        app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] metrics server started on :37057/             app_id=nodeapp instance=PC57-064 scope=dapr.metrics type=log ver=1.0.0-rc.2
INFO[0000] standalone mode configured                    app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] app id: nodeapp                               app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] mTLS is disabled. Skipping certificate request and tls validation  app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] local service entry announced: nodeapp -> 172.17.183.23:44399  app_id=nodeapp instance=PC57-064 scope=dapr.contrib type=log ver=1.0.0-rc.2
INFO[0000] Initialized name resolution to standalone     app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] component loaded. name: pubsub, type: pubsub.redis  app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] waiting for all outstanding components to be processed  app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] component loaded. name: statestore, type: state.redis  app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] all outstanding components processed          app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] enabled gRPC tracing middleware               app_id=nodeapp instance=PC57-064 scope=dapr.runtime.grpc.api type=log ver=1.0.0-rc.2
INFO[0000] enabled gRPC metrics middleware               app_id=nodeapp instance=PC57-064 scope=dapr.runtime.grpc.api type=log ver=1.0.0-rc.2
INFO[0000] API gRPC server is running on port 43509      app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] enabled metrics http middleware               app_id=nodeapp instance=PC57-064 scope=dapr.runtime.http type=log ver=1.0.0-rc.2
INFO[0000] enabled tracing http middleware               app_id=nodeapp instance=PC57-064 scope=dapr.runtime.http type=log ver=1.0.0-rc.2
INFO[0000] http server is running on port 3500           app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] enabled gRPC tracing middleware               app_id=nodeapp instance=PC57-064 scope=dapr.runtime.grpc.internal type=log ver=1.0.0-rc.2
INFO[0000] enabled gRPC metrics middleware               app_id=nodeapp instance=PC57-064 scope=dapr.runtime.grpc.internal type=log ver=1.0.0-rc.2
INFO[0000] internal gRPC server is running on port 44399  app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] application host: 127.0.0.1. application protocol: http. waiting on port 3000.  This will block until the app is listening on that port.  app_id=nodeapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
== APP == Node App listening on port 3000!

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

log 顯示 Node 程序已經跑起來了。APP 自己的 HTTP Endpoint 端口是 3000,Dapr(Sidecar) 的 HTTP 端口是 3500。現在再開另一個窗口,用 Dapr CLI 執行:

$ dapr invoke --app-id nodeapp --method neworder --data '{"data": { "orderId": "42" } }'
✅  App invoked successfully

成功了。再看看原來的窗口,多了下面兩條:

== APP == Got a new order! Order ID: 42

== APP == Successfully persisted state.

顯示持久化成功。用 Node 程序的 GET API 確認一下:

$ curl http://localhost:3000/order
{"orderId":42}

用 Dapr 的 API 確認一下:

$ curl http://localhost:3500/v1.0/invoke/nodeapp/method/order
{"orderId":"42"}

我們看看數據存在了哪裏。用 docker ps 看看 redis 跑在哪個 Container 裏:

$ docker ps
CONTAINER ID   IMAGE               COMMAND                  CREATED          STATUS                    PORTS                              NAMES
a51c086c8bbe   daprio/dapr         "./placement"            31 minutes ago   Up 31 minutes             0.0.0.0:50005->50005/tcp           dapr_placement
fdca40421094   redis               "docker-entrypoint.s…"   31 minutes ago   Up 31 minutes             0.0.0.0:6379->6379/tcp             dapr_redis
9dc60b00db4d   openzipkin/zipkin   "start-zipkin"           31 minutes ago   Up 31 minutes (healthy)   9410/tcp, 0.0.0.0:9411->9411/tcp   dapr_zipkin

Container 名是 dapr_redis。進去看看:

$ docker exec -it dapr_redis redis-cli
127.0.0.1:6379>

看看有什麼 Key:

127.0.0.1:6379> keys *
1) "nodeapp||order"
127.0.0.1:6379> type nodeapp||order
hash
127.0.0.1:6379> hgetall nodeapp||order
1) "data"
2) "{\"orderId\":\"42\"}"
3) "version"
4) "3"

OK,剛纔的 orderId(42) 找到了。除了用 Dapr CLI 保存 OrderId,我們還可以用 Dapr 的 API。

$ curl -XPOST -d @sample.json -H "Content-Type:application/json" http://localhost:3500/v1.0/invoke/nodeapp/method/neworder

其中 sample.json 的內容如下。調用 Dapr 的 invoke API 可以起到 Dapr CLI(dapr invoke) 一樣的效果。

{"data":{"orderId":"42"}}

當然我們也可以用 Node 程序自己的 Endpoint,這樣不通過 Dapr Sidecar。

$ curl -XPOST -d @sample.json -H "Content-Type:application/json" http://localhost:3000/neworder

你也許會問,爲什麼 Redis 直接就能用了呢?答案就是 Dapr Runtime 安裝的時候,自動就跑起來了 Redis 的服務用的 Docker Container(配置文件在~/.dapr/components / 下面的 statestore.yaml 和 pubsub.yaml),所以基於 Redis 的 statestore 和 pubsub 是開箱即用的。

OK。接下來部署 Python 的程序。Python 也得先裝運行環境:

$ sudo apt install python3-pip

用 Dapr CLI 啓動 Python 程序:

$ dapr run --app-id pythonapp --dapr-http-port 3501 python3 app.py
ℹ️  Starting Dapr with id pythonapp. HTTP Port: 3501. gRPC Port: 34093
ℹ️  Checking if Dapr sidecar is listening on HTTP port 3501
INFO[0000] starting Dapr Runtime -- version 1.0.0-rc.2 -- commit 196483d  app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] log level set to: info                        app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] metrics server started on :37435/             app_id=pythonapp instance=PC57-064 scope=dapr.metrics type=log ver=1.0.0-rc.2
INFO[0000] standalone mode configured                    app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] app id: pythonapp                             app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] mTLS is disabled. Skipping certificate request and tls validation  app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] local service entry announced: pythonapp -> 172.17.183.23:44129  app_id=pythonapp instance=PC57-064 scope=dapr.contrib type=log ver=1.0.0-rc.2
INFO[0000] Initialized name resolution to standalone     app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] component loaded. name: pubsub, type: pubsub.redis  app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] waiting for all outstanding components to be processed  app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] component loaded. name: statestore, type: state.redis  app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] all outstanding components processed          app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] enabled gRPC tracing middleware               app_id=pythonapp instance=PC57-064 scope=dapr.runtime.grpc.api type=log ver=1.0.0-rc.2
INFO[0000] enabled gRPC metrics middleware               app_id=pythonapp instance=PC57-064 scope=dapr.runtime.grpc.api type=log ver=1.0.0-rc.2
INFO[0000] API gRPC server is running on port 34093      app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] enabled metrics http middleware               app_id=pythonapp instance=PC57-064 scope=dapr.runtime.http type=log ver=1.0.0-rc.2
INFO[0000] enabled tracing http middleware               app_id=pythonapp instance=PC57-064 scope=dapr.runtime.http type=log ver=1.0.0-rc.2
INFO[0000] http server is running on port 3501           app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] enabled gRPC tracing middleware               app_id=pythonapp instance=PC57-064 scope=dapr.runtime.grpc.internal type=log ver=1.0.0-rc.2
INFO[0000] enabled gRPC metrics middleware               app_id=pythonapp instance=PC57-064 scope=dapr.runtime.grpc.internal type=log ver=1.0.0-rc.2
INFO[0000] internal gRPC server is running on port 44129  app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] actor runtime started. actor idle timeout: 1h0m0s. actor scan interval: 30s  app_id=pythonapp instance=PC57-064 scope=dapr.runtime.actor type=log ver=1.0.0-rc.2
WARN[0000] failed to read from bindings: app channel not initialized   app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] dapr initialized. Status: Running. Init Elapsed 4.716900000000001ms  app_id=pythonapp instance=PC57-064 scope=dapr.runtime type=log ver=1.0.0-rc.2
INFO[0000] placement tables updated, version: 0          app_id=pythonapp instance=PC57-064 scope=dapr.runtime.actor.internal.placement type=log ver=1.0.0-rc.2
ℹ️  Checking if Dapr sidecar is listening on GRPC port 34093
ℹ️  Dapr sidecar is up and running.
ℹ️  Updating metadata for app command: python3 app.py
✅  You're up and running! Both Dapr and your app logs will appear here.

啓動成功了。因爲 Python 自己不提供服務,所以 --app-port 不用指定。--dapr-http-port 是 3501,這是自己的 Sidecar 用的端口,不能跟別人的重了。再看看剛纔 Node 的窗口,不停的有新的 Request 過來,就是 Python 程序來的每隔一秒的 Request。最後看一下 dapr list 的結果:

$ dapr list
  APP ID     HTTP PORT  GRPC PORT  APP PORT  COMMAND         AGE  CREATED              PID
  nodeapp    3500       35485      3000      node app.js     41m  2020-12-27 00:54.54  18395
  pythonapp  40175      33349      0         python3 app.py  1m   2020-12-27 01:36.27  31185

要結束這兩個 APP,在各自窗口裏 Ctrl+c 就可以。如果在別的窗口,可以用 Dapr CLI 的命令:

$ dapr stop --app-id nodeapp
$ dapr stop --app-id pythonapp

至此在 Standalone 模式下的第一個 Dapr 程序部署成功!下一篇講講在 Kubernetes 模式下部署的方法。

Dapr 程序的部署 (Kubernetes 模式)

上一篇我們在 Standalone 模式部署了第一個 Dapr 程序。這一次我們換成在 Kubernetes 模式下部署同樣的程序。程序來自 Dapr 官方的 quickstarts 教程裏的 Hello Kubernetes[8] ,我們用目前的最新版本 v1.0.0-rc.2。

$ git clone -b v1.0.0-rc.2 https://github.com/dapr/quickstarts.git
$ cd quickstarts/hello-kubernetes

裏面包括一個 Python 的程序,每秒發 1 個 HTTP Request 給 Node 程序。一個 Node 程序,用來在 StateStore 裏保存發過來的 OrderId。

首先要先部署 Redis。跟 Standalone 模式不同,Kubernetes 模式的 Dapr Runtime 安裝的時候不會自動安裝 Redis, 需要手動安裝。

$ helm repo add bitnami https://charts.bitnami.com/bitnami
$ helm repo update
$ helm install redis bitnami/redis

NAME: redis
LAST DEPLOYED: Sun Jan  3 12:42:17 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
** Please be patient while the chart is being deployed **
Redis can be accessed via port 6379 on the following DNS names from within your cluster:

redis-master.default.svc.cluster.local for read/write operations
redis-slave.default.svc.cluster.local for read-only operations

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 server:

1. Run a Redis pod that you can use as a client:
   kubectl run --namespace default redis-client --rm --tty -i --restart='Never' \
    --env REDIS_PASSWORD=$REDIS_PASSWORD \
   --image docker.io/bitnami/redis:6.0.9-debian-10-r38 -- bash

2. Connect using the Redis CLI:
   redis-cli -h redis-master -a $REDIS_PASSWORD
   redis-cli -h redis-slave -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

安裝完成。看一下 Pod 裏多了什麼:

$ kubectl get pods
NAME             READY   STATUS    RESTARTS   AGE
redis-master-0   1/1     Running   0          2m5s
redis-slave-0    1/1     Running   0          2m5s
redis-slave-1    1/1     Running   0          1m7s

接下來部署 Statestore。

cd quickstarts/hello-kubernetes/deploy
$ kubectl apply -f redis.yaml
component.dapr.io/statestore created

redis.yaml 的內容如下:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  metadata:
  # These settings will work out of the box if you use `helm install
  # bitnami/redis`.  If you have your own setup, replace
  # `redis-master:6379` with your own Redis master address, and the
  # Redis password with your own Secret's name. For more information,
  # see https://docs.dapr.io/operations/components/component-secrets .
  - name: redisHost
    value: redis-master:6379
  - name: redisPassword
    secretKeyRef:
      name: redis
      key: redis-password
auth:
  secretStore: kubernetes

意思是部署一個名字叫 statestore 的 Component,類型爲 state.redis。

用 dapr cli 看一下 components:

$ dapr components -k
  NAME        TYPE         VERSION  SCOPES  CREATED              AGE
  statestore  state.redis                   2021-01-03 12:50.41  58s

接下來部署 Node 的 APP。

$ kubectl apply -f node.yaml
service/nodeapp created
deployment.apps/nodeapp created

其中 node.yaml 的內容如下:

kind: Service
apiVersion: v1
metadata:
  name: nodeapp
  labels:
    app: node
spec:
  selector:
    app: node
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3000
  type: LoadBalancer

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodeapp
  labels:
    app: node
spec:
  replicas: 1
  selector:
    matchLabels:
      app: node
  template:
    metadata:
      labels:
        app: node
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "nodeapp"
        dapr.io/app-port: "3000"
    spec:
      containers:
      - name: node
        image: dapriosamples/hello-k8s-node:latest
        ports:
        - containerPort: 3000
        imagePullPolicy: Always

看一下 service:

$ kubectl get svc
NAME             TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                               AGE
kubernetes       ClusterIP      10.96.0.1        <none>        443/TCP                               83m
nodeapp          LoadBalancer   10.96.83.185     <pending>     80:31324/TCP                          49m
nodeapp-dapr     ClusterIP      None             <none>        80/TCP,50001/TCP,50002/TCP,9090/TCP   49m
redis-headless   ClusterIP      None             <none>        6379/TCP                              60m
redis-master     ClusterIP      10.109.27.202    <none>        6379/TCP                              60m
redis-slave      ClusterIP      10.103.201.158   <none>        6379/TCP                              60m

啓動了一個 nodeap 的 service 和一個 nodeapp-dapr(就是 sidecar) 的 service。

因爲是運行在 minikube 上,所以 nodeapp 的 service 沒有 EXTERNAL IP(顯示爲 pending)。看一下 pod:

$ kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
nodeapp-5d5dc88584-kqr2v   2/2     Running   0          13m
redis-master-0             1/1     Running   0          142m
redis-slave-0              1/1     Running   0          142m
redis-slave-1              1/1     Running   0          141m

看一下 nodeapp 的 log:

$ kubectl logs nodeapp-5d5dc88584-kqr2v -c node
Node App listening on port 3000!
DAPR_HTTP_PORT: 3500
DAPR_GRPC_PORT: 50001

下面部署 python 的 APP:

$ kubectl apply -f python.yaml
deployment.apps/pythonapp created

python.yaml 的內容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pythonapp
  labels:
    app: python
spec:
  replicas: 1
  selector:
    matchLabels:
      app: python
  template:
    metadata:
      labels:
        app: python
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "pythonapp"
    spec:
      containers:
      - name: python
        image: dapriosamples/hello-k8s-python:latest

看一下 pod 裏多了什麼:

$ kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
nodeapp-5d5dc88584-kqr2v   2/2     Running   0          16m
pythonapp-fcb4f49b-n6ltf   2/2     Running   0          49s
redis-master-0             1/1     Running   0          145m
redis-slave-0              1/1     Running   0          145m
redis-slave-1              1/1     Running   0          144m

看一下 nodeapp 的 log:

$ kubectl logs nodeapp-5d5dc88584-kqr2v -c node
Node App listening on port 3000!
DAPR_HTTP_PORT: 3500
DAPR_GRPC_PORT: 50001
Got a new order! Order ID: 5
Successfully persisted state.
Got a new order! Order ID: 6
Successfully persisted state.
Got a new order! Order ID: 7
Successfully persisted state.
Got a new order! Order ID: 8
Successfully persisted state.

用 kubernetes 的端口映射查看最新的 orderid:

$ kubectl port-forward nodeapp-5d5dc88584-kqr2v 8080:3000

把 pod 裏的 nodeapp 的 http 端口 3000 映射到 localhost 的 8080。另開一個窗口,執行:

$ curl http://localhost:8080/order

或者

$ kubectl port-forward nodeapp-5d5dc88584-kqr2v 8080:3500

把 pod 裏的 dapr sidecar 的 http 端口 3500 映射到 localhost 的 8080。另開一個窗口,執行:

$ curl http://127.0.0.1:8080/v1.0/invoke/nodeapp/method/order/

如果想在 redis 裏查看最新的 orderid,執行下面的命令:

export REDIS_PASSWORD=$(kubectl get secret --namespace default redis -o jsonpath="{.data.redis-password}" | base64 --decode)
$ kubectl run --namespace default redis-client --rm --tty -i --restart='Never' \
     --env REDIS_PASSWORD=$REDIS_PASSWORD \
    --image docker.io/bitnami/redis:6.0.9-debian-10-r38 -- bash
If you don't see a command prompt, try pressing enter.
I have no name!@redis-client:/$ redis-cli -h redis-master -a $REDIS_PASSWORD
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
redis-master:6379> hgetall nodeapp||order

原文鏈接:https://www.cnblogs.com/thrillcattle/category/1906168.html

參考資料

[1]

Dapr 首次發佈: https://cloudblogs.microsoft.com/opensource/2019/10/16/announcing-dapr-open-source-project-build-microservice-applications/

[2]

Dapr 官方網站: https://dapr.io/

[3]

Actor Model: https://zh.wikipedia.org/wiki / 演員模型

[4]

How-To: Install Dapr CLI: https://docs.dapr.io/getting-started/install-dapr-cli/

[5]

Install Docker Engine on Ubuntu: https://docs.docker.com/engine/install/ubuntu/

[6]

minikube 官方文檔: https://minikube.sigs.k8s.io/docs/start/

[7]

Hello World: https://github.com/dapr/quickstarts/tree/v1.0.0-rc.2/hello-world

[8]

Hello Kubernetes: https://github.com/dapr/quickstarts/tree/v1.0.0-rc.2/hello-kubernetes

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/9QJfJmo2X5Na5gH3jgwguQ