Dapr 入門教程之中間件

Dapr 允許通過鏈接一系列中間件組件來定義自定義處理管道。一個請求在被路由到用戶代碼之前會經過所有定義的中間件組件,然後在返回到客戶端之前以相反的順序經過定義的中間件。

Dapr 中間件

當啓動的時候,Dapr sidecar 會構造一箇中間件處理管道。默認情況下,管道由 tracing 中間件和 CORS 中間件組成。通過 Dapr 配置添加的其他中間件會按定義順序添加到管道中,該管道適用於所有 Dapr API 端點,包括狀態、發佈 / 訂閱、服務調用、綁定、安全等。

如以下配置示例定義了一個使用 OAuth 2.0 中間件和大寫中間件組件的自定義管道。在這種情況下,所有請求都通過 OAuth 2.0 協議進行授權,並轉換爲大寫,然後再轉發給用戶代碼。

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
  name: pipeline
  namespace: default
spec:
  httpPipeline:
    handlers:
      - name: oauth2
        type: middleware.http.oauth2
      - name: uppercase
        type: middleware.http.uppercase

與其他構建塊組件一樣,中間件組件是可擴展的,目前 Dapr 已經支持的中間件如下表所示。

iAaXns

這些中間件的實現源碼位可以在 Github 倉庫 https://github.com/dapr/components-contrib/tree/master/middleware/http 中找到。

自定義中間件

Dapr 使用 FastHTTP 來實現其 HTTP 服務器,所以自定義的 HTTP 中間件需要編寫爲 FastHTTP handler,你的中間件需要實現一個如下所示的 Middleware 接口:

package middleware

import (
 "github.com/valyala/fasthttp"
)

// Middleware is the interface for a middleware.
type Middleware interface {
 GetHandler(metadata Metadata) (func(h fasthttp.RequestHandler) fasthttp.RequestHandler, error)
}

該接口定義了一個返回 fasthttp.RequestHandlererrorGetHandler 方法。我們自定義的中間件處理程序實現可以包括任何入站邏輯、出站邏輯:

func (m *customMiddleware) GetHandler(metadata Metadata) (func(fasthttp.RequestHandler) fasthttp.RequestHandler, error) {
  var err error
  return func(h fasthttp.RequestHandler) fasthttp.RequestHandler {
    return func(ctx *fasthttp.RequestCtx) {
      // 入站邏輯
      h(ctx)  // 調用下游處理程序
      // 出站邏輯
    }
  }, err
}

然後你可以將你自定義的中間件組件貢獻給 components-contrib 存儲庫。當 components-contrib 接受你的提交後,然後需要在 Dapr 運行時的倉庫中的 https://github.com/dapr/dapr/tree/master/cmd/daprd/components 下面添加一箇中間件註冊文件。

比如 uppercase 中間件的註冊如下代碼所示:

package components

import (
 "strings"

 "github.com/valyala/fasthttp"

 "github.com/dapr/components-contrib/middleware"
 httpMiddlewareLoader "github.com/dapr/dapr/pkg/components/middleware/http"
 httpMiddleware "github.com/dapr/dapr/pkg/middleware/http"
 "github.com/dapr/kit/logger"
)

func init() {
 httpMiddlewareLoader.DefaultRegistry.RegisterComponent(func(log logger.Logger) httpMiddlewareLoader.FactoryMethod {
  return func(metadata middleware.Metadata) (httpMiddleware.Middleware, error) {
   return func(h fasthttp.RequestHandler) fasthttp.RequestHandler {
    return func(ctx *fasthttp.RequestCtx) {
     body := string(ctx.PostBody())
     ctx.Request.SetBody([]byte(strings.ToUpper(body)))
     h(ctx)
    }
   }, nil
  }
 }, "uppercase")
}

不過我們也可以發現 Dapr 對於中間件的擴展並沒有完全放開,如果用戶有特定的需求需要將代碼在主倉庫中進行更新,這勢必也降低了靈活性,不過也可以避免因爲低質量的中間件造成 Dapr 各種問題。

OAuth 中間件示例

接下來我們配置一個 OAuth 中間件來說明下 Dapr 中中間件的使用方法。通過配置一個 OAuth 中間件,在不修改應用程序的情況下在 Web API 上啓用 OAuth 授權。這種設計將認證 / 授權問題從應用程序中分離出來,因此應用程序運維人員可以採用和配置認證 / 授權提供者而不影響應用程序代碼。

這裏我們在 K8s 集羣中使用 ingress-nginx 作爲 ingress 控制器,如果沒有安裝可以使用下面的 Helm chart 來快速安裝:

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install my-release ingress-nginx/ingress-nginx

首先我們編寫了一個使用 Node.js 開發的 echoapp,如下所示:

// app.js
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json());

const port = 3000;

app.get("/echo", (req, res) => {
  var text = req.query.text;
  console.log("Echoing: " + text);
  res.send("Access token: " + req.headers["authorization"] + " Text: " + text);
});

app.listen(port, () => console.log(`Node App listening on port ${port}!`));

然後編寫了一個 /echo 接口,獲取了請求 header 頭中的 authorization 信息和客戶端的請求文本信息。

接下來我們這裏使用 Github 來實現認證授權,首先前往 https://github.com/settings/developers 註冊一個 OAuth 應用,如下所示:

這裏我們指定的應用 URL 爲 http://echo.dapr.local,點擊 Register application 註冊新應用,註冊後在應用詳情頁面可以獲取到 clientId 信息,clientSecret 信息需要手動點擊 Generate a new client secret 按鈕獲取,將該兩個參數值記錄下來,後續會使用到。

然後將我們的 echoapp 應用部署到 Kubernetes 集羣中去,對應的資源清單如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: echoapp
  labels:
    app: echo
spec:
  selector:
    matchLabels:
      app: echo
  template:
    metadata:
      labels:
        app: echo
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "echoapp"
        dapr.io/app-port: "3000"
        dapr.io/config: "pipeline"
    spec:
      containers:
        - name: echo
          image: dapriosamples/middleware-echoapp:latest
          ports:
            - containerPort: 3000
          imagePullPolicy: Always
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-ingress
spec:
  ingressClassName: nginx
  rules:
    - host: echo.dapr.local
      http:
        paths:
          - backend:
              service:
                name: echoapp-dapr
                port:
                  number: 80
            path: /
            pathType: Prefix

注意上面我們創建了一個 Ingress 對象,用來暴露 echoapp 應用,需要注意的是我們並沒有主動創建 Service 對象,而是直接關聯的 echoapp-dapr 這個 Service,其 80 端口映射到 echoapp 應用的 dapr sidecar 中的 3500 端口。

另外注意上面爲應用添加的註解,其中有一個 dapr.io/config: "pipeline",這是用來指定使用的配置對象的,所以我們還需要創建一個名爲 pipelineConfiguration 對象:

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
  name: pipeline
spec:
  tracing:
    samplingRate: "1"
    zipkin:
      endpointAddress: "http://zipkin.default.svc.cluster.local:9411/api/v2/spans"
  httpPipeline:
    handlers:
      - type: middleware.http.oauth2
        name: oauth2

這裏我們爲 echoapp 應用配置了一個 middleware.http.oauth2 類型的中間件,對應處理器的名稱爲 oauth2,該處理器對應中 Dapr 中的一個 Component 組件,如下所示:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: oauth2
spec:
  type: middleware.http.oauth2
  version: v1
  metadata:
    - name: clientId
      value: "<client-id>"
    - name: clientSecret
      value: "<client-secret>"
    - name: scopes
      value: ""
    - name: authURL
      value: "https://github.com/login/oauth/authorize"
    - name: tokenURL
      value: "https://github.com/login/oauth/access_token"
    - name: redirectURL
      value: "http://echo.dapr.local"
    - name: authHeaderName
      value: "authorization"

要使用 Dapr OAuth 中間件,需要配置以下信息:

下表是一些比較熱門授權服務器的 Authorization/Token URLs:

t3Cfhb

我們這裏使用的 GitHub 的授權服務器配置,還要注意最後添加的 authHeaderName: authorization 屬性,上面我們代碼中就是從 Header 頭從獲取的 authorization 的值。

接下來創建上面的所有資源對象即可,創建完成後記得要將 echo.dapr.local 域名映射到 ingress 控制器。

$ kubectl get pods
NAME                              READY   STATUS    RESTARTS        AGE
echoapp-b7f5469cb-hzvth           2/2     Running   0               51s
$ kubectl get svc
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                               AGE
echoapp-dapr     ClusterIP      None            <none>         80/TCP,50001/TCP,50002/TCP,9090/TCP   69m
$ kubectl get ingress echo-ingress
NAME           CLASS   HOSTS             ADDRESS        PORTS   AGE
echo-ingress   nginx   echo.dapr.local   192.168.0.52   80      59m

然後接下來我們就可以在瀏覽器中輸入 http://echo.dapr.local/v1.0/invoke/echoapp/method/echo?text=hello 來訪問應用中的 echo 方法了,正常訪問的時候會出現 502 錯誤,不符合預期。

這是因爲從 Dapr 1.4 版本開始,daprd 進程就被鎖定爲只接受來自 pod 邊界的連接,以實現良好的安全措施。如果要啓用外部調用 Dapr,則需要在應用中添加以下註解:

dapr.io/sidecar-listen-addresses: "0.0.0.0"

將該註解添加到 Deployment 中,更新後再次訪問 http://echo.dapr.local/v1.0/invoke/echoapp/method/echo?text=hello 就正常了,第一次會跳轉到 GitHub 進行授權。

授權後會自動跳轉回來,正常就會顯示 echo 接口返回的數據,包括 Access Token 的數據和 text 參數的值。

到這裏我們就實現了在 Dapr 中爲應用啓用 OAuth 中間件,對原始應用沒有任何侵入性。

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