大數據

通過ASM入口網關實現HTTP請求網格內GRPC服務

概述

本文將介紹ASM入口網關(ingressgateway)支持協議轉碼的能力,使用戶及其客戶端可以使用HTTP/JSON訪問服務網格內的gRPC服務。

ASM入口網關可將HTTP/JSON轉碼為gRPC。完整的http請求、grpc轉碼,以及grpc請求流程,如下圖所示。

image.png

  1. ASM控制平面下發用於grpc轉碼的EnvoyFilter、用於路由到gRPC服務端口的規則配置Gateway和VirtualService到ASM入口網關,入口網關接收後即時加載生效。
  2. 入口網關收到用戶或其客戶端http協議的請求後,將進行路由規則匹配和協議轉換,然後以grpc協議請求服務網格內的gRPC服務。
  3. 入口網關收到後端服務的grpc響應,再將其轉換為http的響應返回給請求方。

原理和目標

Envoy作為ServiceMesh數據平面的proxy組件,內置了多種http擴展過濾器,本文涉及的是其中的http到grpc的轉碼器。為了啟用這個過濾器,Envoy定義了相應的過濾器協議config.filter.http.transcoder.v2.GrpcJsonTranscoder。為此,ServiceMesh的控制平面需要定義一個EnvoyFilter來聲明在什麼地方哪個階段啟用這個過濾器,然後下發這個EnvoyFilter在指定環節啟用轉碼器。另一方面,這個轉碼器需要知道grpc服務的具體協議,包括描述grpc服務的Proto Descriptors文件、grpc服務全名列表。

Envoy是ServiceMesh入口網關的核心組件。因此,實現入口網關轉碼的關鍵任務就是生成這個用於grpc轉碼的EnvoyFilter。

由於EnvoyFilter的定義較為複雜,從頭寫一個這樣的EnvoyFilter比較費時費力且容易出錯,本文提供了自動化生成EnvoyFilter的工具grpc-transcoder(https://github.com/AliyunContainerService/grpc-transcoder),一方面保證EnvoyFilter的定義正確,另一方面可以節省你的時間以及省去編寫EnvoyFilter的痛苦。

實戰

1 gRPC服務

如下圖所示,通常用戶的gRPC服務是從protobuf格式的gRPC服務協議文件.proto的定義開始的。grpc-service-project封裝grpc接口並完成實現,後續步驟包括構建鏡像,編寫Deployment,最終通過ASM,將grpc服務以POD的形式部署到ACK集群。
image.png

為了支持http轉碼grpc,需要確認.proto中是否定義了支持轉碼的聲明。這裡以hello-servicemesh-grpc示例中的proto為例,示意如下。

import "google/api/annotations.proto";
service LandingService {
  //Unary RPC
  rpc talk (TalkRequest) returns (TalkResponse) {
    option(google.api.http) = {
      get: "/v1/talk/{data}/{meta}"
    };
  }
...
}

message TalkRequest {
  string data = 1;
  string meta = 2;
}

其中,5-7行是為了支持轉碼新增的聲明,第1行是引入相應能力的.proto聲明。如果你的.proto中沒有相關聲明,請補充。這裡需要提示的是,補充這個.proto只是為了生成pb(Proto Descriptors)文件,補充後並不影響grpc-service-project及後續步驟。

2 生成Proto Descriptors文件

這裡以hello-servicemesh-grpc為例,使用protoc命令從landing.proto生成landing.proto-descriptor文件。如果沒有安裝protoc請在此處下載。

# https://github.com/AliyunContainerService/hello-servicemesh-grpc
proto_path={path/to/hello-servicemesh-grpc}/proto
# https://github.com/grpc-ecosystem/grpc-gateway/tree/master/third_party/
proto_dep_path={path/to/third_party}
protoc \
    --proto_path=${proto_path} \
    --proto_path=${proto_dep_path} \
    --include_imports \
    --include_source_info \
    --descriptor_set_out=landing.proto-descriptor \
    "${proto_path}"/landing.proto
```


#### 3 生成EnvoyFilter

此時,我們已經有了Proto Descriptors文件`landing.proto-descriptor`。接下來,通過[**grpc-transcoder**](https://github.com/AliyunContainerService/grpc-transcoder)生成EnvoyFilter。調用接口的示意請求如下。

```bash
grpc-transcoder \
--version 1.7 \
--service_port 9996 \
--service_name grpc-server-svc \
--proto_pkg org.feuyeux.grpc \
--proto_svc LandingService \
--descriptor landing.proto-descriptor
```

參數說明:

- `version`:由於ASM是託管集群,因此需要保證版本升級對EnvoyFilter的影響。因此這個參數是必選值。
- `service_port`:對應的grpc服務(詳見下圖)端口。
- `service_name`:對應的grpc服務(詳見下圖)名稱。
- `proto_pkg`:對應的grpc服務`.proto`中包名的定義。
- `proto_svc`:對應的grpc服務`.proto`中服務名的定義。
- `descriptor`:Proto Descriptors文件本地路徑。


![image.png](https://ucc.alicdn.com/pic/developer-ecology/dd09bc78ccbf4cb6a2db0e28abcdfa5d.png)

使用上述請求自動生成的EnvoyFilter(`grpc-transcoder-envoyfilter.yaml`)如下。

```yaml
#Generated by ASM(http://servicemesh.console.aliyun.com)
#GRPC Transcoder EnvoyFilter[1.7]
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: grpc-transcoder-grpc-server-svc
spec:
  workloadSelector:
    labels:
      app: istio-ingressgateway
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: GATEWAY
        listener:
          portNumber: 9996
          filterChain:
            filter:
              name: "envoy.filters.network.http_connection_manager"
              subFilter:
                name: "envoy.filters.http.router"
        proxy:
          proxyVersion: ^1\.7.*
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.grpc_json_transcoder
          typed_config:
            '@type': type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder
            proto_descriptor_bin: Ctl4ChVnb29nbGUvYXBpL2h0dHAucHJ...
            services: 
            - org.feuyeux.grpc.LandingService
            print_options:
              add_whitespace: true
              always_print_primitive_fields: true
              always_print_enums_as_ints: false
              preserve_proto_field_names: false
```

#### 4 ASM入口網關啟用轉碼器

通過工具生成EnvoyFilter後,我們通過ASM管控臺提交生成的EnvoyFilter(`grpc-transcoder-envoyfilter.yaml`),轉碼器將即時生效。

![image.png](https://ucc.alicdn.com/pic/developer-ecology/81fd0cffdea94bd78795ead39159dcec.png)

上述操作相當於執行命令`kubectl  --kubeconfig mesh_config -f grpc-transcoder-envoyfilter.yaml -n istio-system`。到此,配置的步驟就完成了,完整的流程示意圖如下。

![image.png](https://ucc.alicdn.com/pic/developer-ecology/c3bc93160bd0464982060c24bce56c1d.png)

#### 5 envoy配置校驗

接下來我們校驗入口網關的envoy配置是否生效。依次執行如下命令,驗證envoy動態配置中是否包含解碼器GrpcJsonTranscoder的配置:

```bash
#獲取入口網關POD名稱
ingressgateway_pod=$(kubectl get pod -l app="istio-ingressgateway" -n istio-system -o jsonpath='{.items[0].metadata.name}')
#時間戳
timestamp=$(date "+%Y%m%d-%H%M%S")
#獲取envoy動態配置並保存到dynamic_listeners-"$timestamp".json
kubectl -n istio-system exec $ingressgateway_pod \
  -c istio-proxy \
  -- curl -s "http://localhost:15000/config_dump?resource=dynamic_listeners" >dynamic_listeners-"$timestamp".json
#確認配置中存在GrpcJsonTranscoder
grep -B3 -A7 GrpcJsonTranscoder dynamic_listeners-"$timestamp".json
```

envoy動態配置中應包含類似如下的配置:

```json
{
  "name": "envoy.grpc_json_transcoder",
  "typed_config": {
    "@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder",
    "services": [
      "org.feuyeux.grpc.LandingService"
    ],
    "print_options": {
      "add_whitespace": true,
      "always_print_primitive_fields": true
    },
    ...
```

#### 6 功能校驗

最後,我們進行功能校驗,驗證HTTP請求網格內GRPC服務。

我們再次給出grpc服務接口的定義:

```protobuf
rpc talk (TalkRequest) returns (TalkResponse) {
  option(google.api.http) = {
    get: "/v1/talk/{data}/{meta}"
  };
}
```

為了驗證響應信息,這裡給出`.proto`中定義的響應聲明:

```protobuf
message TalkResponse {
  int32 status = 1;
  repeated TalkResult results = 2;
}

message TalkResult {
  //timestamp
  int64 id = 1;
  //enum
  ResultType type = 2;
  // id:result uuid
  // idx:language index
  // data: hello
  // meta: serverside language
  map kv = 3;
}

enum ResultType {
  OK = 0;
  FAIL = 1;
}
```

依次執行如下命令,驗證這個grpc服務接口可以通過HTTP請求入口網關實現調用:

```bash
#獲取入口網關IP
INGRESS_IP=$(k -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
#http請求入口網關的9996端口,/v1/talk/{data}/{meta}路徑
curl http://$INGRESS_IP:9996/v1/talk/0/java

我們應該得到如下響應:

{
 "status": 200,
 "results": [
  {
   "id": "699882576081691",
   "type": "OK",
   "kv": {
    "data": "Hello",
    "meta": "JAVA",
    "id": "8c175d5c-d8a3-4197-a7f8-6e3e0ab1fe59",
    "idx": "0"
   }
  }
 ]
}

Leave a Reply

Your email address will not be published. Required fields are marked *