全部產品
Search
文件中心

Alibaba Cloud Service Mesh:使用Envoy External Processing對請求進行自訂處理

更新時間:Dec 26, 2024

Envoy External Processing是一種擴充能力,使得Envoy可以通過外部處理服務來增強其HTTP請求/響應處理功能,而不用編寫Wasm外掛程式或是其它處理指令碼,使得處理更加靈活和可擴充。本文介紹External Processing的實現機制和使用樣本。

前提條件

External Processing機制

Envoy External Processing對下遊服務要求以及上遊服務響應進行處理的過程如下圖:

  1. 下遊服務向上遊服務發送請求①,Envoy攔截請求並將請求資訊轉寄至External Processing服務②。

  2. External Processing服務對請求進行的處理,並將其返回給Envoy③。

  3. Envoy根據返回的結果對請求進行處理,並將處理完的請求轉寄至上遊服務④。

  4. 上遊處理完請求後向下遊服務發送響應⑤,Envoy攔截請求並將響應資訊轉寄至External Processing服務⑥。

  5. External Processing服務計對請求進行處理⑦,並將其返回Envoy⑧。

  6. Envoy根據返回的結果對回應標頭和響應體資訊進行處理,並將處理完的請求轉寄至下遊服務⑨。

步驟一:編寫External Processing服務處理邏輯

以下是部分核心邏輯代碼,完整範例程式碼請參見ext-proc-demo。更多設計細節,請參見Envoy官方文檔

展開查看核心邏輯代碼

func NewServer() *Server {
	return &Server{}
}

// Server 實現了 Envoy external processing server 介面
// 參見 https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/ext_proc/v3/external_processor.proto
type Server struct{}

func (s *Server) Process(srv extProcPb.ExternalProcessor_ProcessServer) error {
	klog.Infof("Processing")
	ctx := srv.Context()
	for {
		select {
		case <-ctx.Done():
			klog.Infof("context done")
			return ctx.Err()
		default:
		}

		req, err := srv.Recv()
		if err == io.EOF {
			// envoy has closed the stream. Don't return anything and close this stream entirely
			return nil
		}
		if err != nil {
			return status.Errorf(codes.Unknown, "cannot receive stream request: %v", err)
		}

		resp := &extProcPb.ProcessingResponse{}
		switch v := req.Request.(type) {
        // 對於要求標頭所需處理的請求
		case *extProcPb.ProcessingRequest_RequestHeaders:
			klog.Infof("Got RequestHeaders")
			h := req.Request.(*extProcPb.ProcessingRequest_RequestHeaders)
			resp = handleRequestHeaders(h)
        // 對於回應標頭所需處理的請求
        case *extProcPb.ProcessingRequest_ResponseHeaders:
			klog.Infof("Got ResponseHeaders")
			h := req.Request.(*extProcPb.ProcessingRequest_ResponseHeaders)
			resp = handleResponseHeaders(h)
        // 對於要求標頭所需處理的請求,暫未實現
		case *extProcPb.ProcessingRequest_RequestBody:
			klog.Infof("Got RequestBody (not currently handled)")
        // 對於請求 Trailers 所需處理的請求,暫未實現
		case *extProcPb.ProcessingRequest_RequestTrailers:
			klog.Infof("Got RequestTrailers (not currently handled)")
        // 對於響應體所需處理的請求,暫未實現
		case *extProcPb.ProcessingRequest_ResponseBody:
			klog.Infof("Got ResponseBody (not currently handled)")
        // 對於響應 Trailers 所需處理的請求,暫未實現
		case *extProcPb.ProcessingRequest_ResponseTrailers:
			klog.Infof("Got ResponseTrailers (not currently handled)")

		default:
			klog.Infof("Unknown Request type %v", v)
		}
        // 返回需要對請求作出的處理
		klog.Infof("Sending ProcessingResponse: %+v", resp)
		if err := srv.Send(resp); err != nil {
			klog.Infof("send error %v", err)
			return err
		}
	}
}

// 為要求標頭添加 x-ext-proc-header=hello-to-asm
func handleRequestHeaders(req *extProcPb.ProcessingRequest_RequestHeaders) *extProcPb.ProcessingResponse {
	klog.Infof("handle request headers: %+v\n", req)

	resp := &extProcPb.ProcessingResponse{
		Response: &extProcPb.ProcessingResponse_RequestHeaders{
			RequestHeaders: &extProcPb.HeadersResponse{
				Response: &extProcPb.CommonResponse{
					HeaderMutation: &extProcPb.HeaderMutation{
						SetHeaders: []*configPb.HeaderValueOption{
							{
								Header: &configPb.HeaderValue{
									Key:      "x-ext-proc-header",
									RawValue: []byte("hello-to-asm"),
								},
							},
						},
					},
				},
			},
		},
	}
    
	return resp
}

// 為回應標頭添加 x-ext-proc-header=hello-from-asm
func handleResponseHeaders(req *extProcPb.ProcessingRequest_ResponseHeaders) *extProcPb.ProcessingResponse {
	klog.Infof("handle response headers: %+v\n", req)

	resp := &extProcPb.ProcessingResponse{
		Response: &extProcPb.ProcessingResponse_ResponseHeaders{
			ResponseHeaders: &extProcPb.HeadersResponse{
				Response: &extProcPb.CommonResponse{
					HeaderMutation: &extProcPb.HeaderMutation{
						SetHeaders: []*configPb.HeaderValueOption{
							{
								Header: &configPb.HeaderValue{
									Key:      "x-ext-proc-header",
									RawValue: []byte("hello-from-asm"),
								},
							},
						},
					},
				},
			},
		},
	}

	return resp
}
說明

您需要編寫Dockerfile將External Processing服務代碼構建成鏡像並上傳到鏡像倉庫才可以進行部署。

步驟二:部署External Processing服務

本步驟使用ASM提供的External Processing服務樣本鏡像進行示範。該服務會在接收到的請求中添加要求標頭x-ext-proc-header: hello-to-asm,並在返回的響應中添加要求標頭x-ext-proc-header: hello-from-asm

  1. 使用以下內容,建立ext.yaml。

    apiVersion: v1
    kind: Service
    metadata:
      name: ext-proc
      labels:
        app: ext-proc
        service: ext-proc
    spec:
      ports:
      # External Processing 監聽連接埠
      - name: grpc
        port: 9002
        targetPort: 9002
      selector:
        app: ext-proc
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: ext-proc
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: ext-proc
          version: v1
      template:
        metadata:
          labels:
            app: ext-proc
            version: v1
        spec:
          containers:
          - image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/ext-proc:v0.2
            imagePullPolicy: IfNotPresent
            name: ext-proc
            ports:
            - containerPort: 9002
  2. 執行以下命令,查看Pod日誌以確認External Processing服務運行狀態。

    kubectl logs ext-proc-64c8xxxxx-xxxxx

    預期輸出:

    I1126 06:41:25.467033       1 main.go:52] Starting gRPC server on port :9002

    日誌中出現上述內容,說明External Processing服務運行狀態正常。

步驟三:配置EnvoyFilter

  1. 登入ASM控制台,在左側導覽列,選擇服務網格 > 網格管理

  2. 網格管理頁面,單擊目標執行個體名稱,然後在左側導覽列,選擇外掛程式擴充中心 > Envoy過濾器模板

  3. 使用以下內容,建立httpbin-ext-proc EnvoyFilter。

    apiVersion: networking.istio.io/v1alpha3
    kind: EnvoyFilter
    spec:
      configPatches:
        - applyTo: HTTP_FILTER
          match:
            context: SIDECAR_INBOUND
            listener:
              portNumber: 80
              filterChain:
                filter:
                  name: envoy.filters.network.http_connection_manager
            proxy:
              proxyVersion: ^MIN_VERSION-MAX_VERSION.*
          patch:
            operation: INSERT_BEFORE
            value:
              name: envoy.filters.http.ext_proc
              typed_config:
                '@type': >-
                  type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor
                grpc_service:
                  envoy_grpc:
                    cluster_name: outbound|9002||ext-proc.default.svc.cluster.local
                    authority: ext-proc.default.svc.cluster.local
                processing_mode:
                  request_header_mode: SEND
                  response_header_mode: SEND

步驟四:訪問httpbin應用進行驗證

執行以下命令,訪問httpbin應用,並查看回應標頭。

kubectl exec -it deploy/sleep -- curl httpbin:8000/headers -i

預期輸出:

HTTP/1.1 200 OK
server: envoy
date: Wed, 11 Dec 2024 06:47:59 GMT
content-type: application/json
content-length: 564
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 3
x-ext-proc-header: hello-from-asm

{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin:8000",
    "User-Agent": "curl/8.1.2",
    "X-B3-Parentspanid": "5c6dd2cc9312d6bb",
    "X-B3-Sampled": "1",
    "X-B3-Spanid": "1153a2737cee4434",
    "X-B3-Traceid": "baba86b696edc75a5c6dd2cc9312d6bb",
    "X-Envoy-Attempt-Count": "1",
    "X-Ext-Proc-Header": "hello-to-asm",
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=69d8f267c3c00b4396a83e12d14520acc9dadb1492d660e10f77e94dcad7cb06;Subject=\"\";URI=spiffe://cluster.local/ns/default/sa/sleep"
  }
}

可以看到,要求標頭中添加了x-ext-proc-header: hello-to-asm,回應標頭中添加了x-ext-proc-header: hello-from-asm