分佈式應用運行時 Dapr:萬物皆可 API

Dapr[1] 分佈式應用運行時 Distributed Application Runtime 的首字母縮寫。有關多運行時,可以看下 Bilgin Ibryam 的 Multi-Runtime Microservices Architecture[2],不想看英文的可以看下我之前的翻譯。

Dapr 是一個分佈式系統工具包,通過提供 API 實現應用程序與外圍組件的解耦合,讓開發人員更加聚焦於業務邏輯的研發。解耦也是與傳統 SDK 的很大區別,能力不再是通過應用程序中加入庫的方式提供,而是通過應用附近的邊車(sidecar)運行時提供(sidecar 不是廣爲人知的服務網格 sidecar - pod 中的容器,而是廣泛使用在系統軟件設計中的一種模式,比如操作系統的 initd、日誌採集組件,甚至是 Java 中的多線程。)。因此這裏說的 Dapr sidecar 可能是個獨立的進程,也可能是 pod 中的一個容器。

在 Dapr 中我們可以看到很多常見 SDK 的能力:

以上能力都是通過 HTTP 和 gRPC API 暴露給應用,這些 API 在 Dapr 中被叫做 構建塊 [10](building blocks),並且也 僅提供抽象,也就是說你可以隨意替換底層實現(Dapr 中也叫做 組件 [11])而無需修改任何應用代碼。

比如你的應用需要在存儲中保存狀態,在開發時可以使用 內存 [12] 作爲存儲組件,其他環境中可以使用 Mysql[13]、Redis[14] 等持久化組件。

接下來,就藉助官方的入門指南體驗 Dapr 的。Dapr 提供了 多種入門指南 [15],這裏我選了其中的 hello-kubernetes[16],但實際操作可能與官方有些許差異,也正式這些差異能讓(坑)我對 Dapr 有更多的瞭解。

環境

安裝 Dapr CLI

Dapr CLI 是操作 Dapr 的工具,對可以用來安裝、管理 Dapr 實例,以及進行 debug。參考官方的 安裝文檔 [17],我使用的是 macOS 選擇 homebrew 來安裝。

brew install dapr-cli

目前最新的版本是 1.9.1。

dapr version
CLI version: 1.9.1
Runtime version: n/a

創建 Kubernetes 集羣

使用 k3s v1.23.8+k3s2 作爲實驗環境集羣。

export INSTALL_K3S_VERSION=v1.23.8+k3s2
curl -sfL https://get.k3s.io | sh -s - --disable traefik --disable servicelb  --write-kubeconfig-mode 644 --write-kubeconfig ~/.kube/config

安裝 Dapr

執行下面的命令將 Dapr 安裝到集羣中。

dapr init --kubernetes  --wait

檢查組件是否正常運行。在 Kubernetes 環境下,我們的很多命令都要使用 --kubernetes 或者 -k 參數。

dapr status -k
  NAME                   NAMESPACE    HEALTHY  STATUS   REPLICAS  VERSION  AGE  CREATED
  dapr-dashboard         dapr-system  True     Running  1         0.11.0   47s  2023-02-11 08:30.25
  dapr-sentry            dapr-system  True     Running  1         1.9.6    47s  2023-02-11 08:30.25
  dapr-sidecar-injector  dapr-system  True     Running  1         1.9.6    47s  2023-02-11 08:30.25
  dapr-operator          dapr-system  True     Running  1         1.9.6    47s  2023-02-11 08:30.25
  dapr-placement-server  dapr-system  True     Running  1         1.9.6    47s  2023-02-11 08:30.25

示例應用

環境部署好之後,我們來看下要用的示例應用。

git clone https://github.com/dapr/quickstarts
cd quickstarts/tutorials/hello-kubernetes

示例中包含了 2 個應用 pythonapp 和 nodeapp,以及 Redis。

用到了 Dapr 的兩個功能:服務調用和狀態存儲。

創建應用命名空間

應用將部署在 dpar-test 命名空間下。

kubectl create namespace dapr-test

狀態存儲

狀態存儲使用 Redis,先部署 Redis 到命名空間 store 下。簡單起見,只使用單 master 節點,並設置密碼 changeme

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install redis bitnami/redis --namespace store --create-namespace \
  --set replica.replicaCount=\
  --set auth.password=changeme

創建組件

由於 Redis 設置了密碼,需要爲 Dapr 提供訪問 Redis 的密碼,通過 Secret 來傳遞。Secret 保存在 dapr-test 下。

kubectl create secret generic redis -n dapr-test --from-literal=redis-password=changeme

根據 Redis store 規範 [18] 在 dapr-test 下創建組件 statetore

kubectl apply -n dapr-test -f - <<EOF
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: redis-master.store:6379
  - name: redisPassword
    secretKeyRef:
      name: redis
      key: redis-password
auth:
  secretStore: kubernetes
EOF

訪問狀態存儲

通過 Dapr API 訪問狀態存儲 [20],請求格式:POST http://localhost:<daprPort>/v1.0/state/<storename>

下面截取了 nodeapp 中的部分代碼,stateStoreName 就是上面創建的 statestore。應用和組件位於同一命名空間下,直接只用 statestore;否則,就要代碼組件所在的命名空間 storeName.storeNamespace(由於代碼中硬編碼了組件名 statestore,所以在同命名空間下創建組件)。

const stateStoreName = `statestore`;
const stateUrl = `http://localhost:${daprPort}/v1.0/state/${stateStoreName}`;

const state = [{
    key: "order",
    value: data
}];

const response = await fetch(stateUrl, {
    method: "POST",
    body: JSON.stringify(state),
    headers: {
        "Content-Type""application/json"
    }
});

服務調用

調用方 pythonapp 的代碼。

dapr_port = os.getenv("DAPR_HTTP_PORT", 3500)
dapr_url = "http://localhost:{}/neworder".format(dapr_port)

n = 0
while True:
    n += 1
    message = {"data"{"orderId": n}}

    try:
        response = requests.post(dapr_url, json=message, timeout=5, headers = {"dapr-app-id""nodeapp"} )
        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 -n dapr-test -f deploy/node.yaml
kubectl wait --for=condition=ready pod -n dapr-test -l app=node --timeout=60s
kubectl apply -n dapr-test -f deploy/python.yaml
kubectl wait --for=condition=ready pod -n dapr-test -l app=python --timeout=60s

檢查 node 容器的日誌,可以接收到了來自 pythonapp 的請求,併成功持久化存儲了訂單。

kubectl logs -f -n dapr-test -l app=node -c node

Successfully persisted state for Order ID: 1
Got a new order! Order ID: 1
Successfully persisted state for Order ID: 2
Got a new order! Order ID: 2
Successfully persisted state for Order ID: 3
Got a new order! Order ID: 3
Successfully persisted state for Order ID: 4
Got a new order! Order ID: 4

Debug

原本官方的指南是將 Redis 和應用部署在同一個命名空間中,加上 nodeapp 中硬編碼了存儲組件名。而我實驗的時候講 Redis 部署在了另一個空間下,檢查 node 容器日誌時看到的是:

Got a new order! Order ID: 1
Failed to persist state.

daprd 容器中,只有下面的日誌。

time="2023-02-11T02:55:38.166259509Z" level=info msg="HTTP API Called: POST /v1.0/state/statestore" app_id=nodeapp instance=nodeapp-857cf6f985-jnmzw scope=dapr.runtime.http-info type=log useragent="node-fetch/1.0 (+https://github.com/bitinn/node-fetch)" ver=1.9.6

通過爲 nodeapp 的 pod 添加註解 dapr.io/log-level="debug" 讓 daprd 容器輸出 debug 日誌。

time="2023-02-11T03:05:07.663028821Z" level=debug msg="{ERR_STATE_STORE_NOT_CONFIGURED state store is not configured}" app_id=nodeapp instance=nodeapp-59b754ff54-c4x4s scope=dapr.runtime.http type=log ver=1.9.6

更多 Debug 方式,參考官方的 Troubleshooting 文檔 [22]。

總結

Dapr 提供了與傳統 SDK 方式完成不同的方法來實現系統集成,讓開發者可以專注於業務邏輯,而無需考慮底層的實現;對組織來說,應用變得更加便攜,可以使用不同的雲環境。

但是 Dapr 本身無法跨雲跨集羣,社區正在考慮與服務網格集成來實現混合多雲環境下的服務調用,大家可以期待一下。

參考資料

[1] 

Dapr: https://dapr.io

[2] 

Multi-Runtime Microservices Architecture: https://www.infoq.com/articles/multi-runtime-microservice-architecture/

[3] 

服務調用: https://docs.dapr.io/developing-applications/building-blocks/service-invocation/

[4] 

彈性策略: https://docs.dapr.io/operations/resiliency/policies/

[5] 

狀態存儲: https://docs.dapr.io/developing-applications/building-blocks/state-management/

[6] 

發佈 / 訂閱: https://docs.dapr.io/developing-applications/building-blocks/pubsub/

[7] 

綁定: https://docs.dapr.io/developing-applications/building-blocks/bindings/

[8] 

分佈式鎖: https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/

[9] 

名稱解析: https://docs.dapr.io/reference/components-reference/supported-name-resolution/

[10] 

構建塊: https://docs.dapr.io/concepts/building-blocks-concept/

[11] 

組件: https://docs.dapr.io/concepts/components-concept/

[12] 

內存: https://docs.dapr.io/reference/components-reference/supported-state-stores/setup-inmemory/

[13] 

Mysql: https://docs.dapr.io/reference/components-reference/supported-state-stores/setup-mysql/

[14] 

Redis: https://docs.dapr.io/reference/components-reference/supported-state-stores/setup-redis/

[15] 

多種入門指南: https://github.com/dapr/quickstarts/tree/master/tutorials

[16] 

hello-kubernetes: https://github.com/dapr/quickstarts/tree/master/tutorials/hello-kubernetes

[17] 

安裝文檔: https://docs.dapr.io/getting-started/install-dapr-cli/

[18] 

Redis store 規範: https://docs.dapr.io/reference/components-reference/supported-state-stores/

[19] 

Kubernetes: https://docs.dapr.io/reference/components-reference/supported-secret-stores/kubernetes-secret-store/

[20] 

Dapr API 訪問狀態存儲: https://docs.dapr.io/reference/api/state_api/#save-state

[21] 

文檔: https://docs.dapr.io/reference/arguments-annotations-overview/

[22] 

Troubleshooting 文檔: https://docs.dapr.io/operations/troubleshooting/

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