ASM入口网关支持协议转码的能力,使用户及其客户端可以使用HTTP/JSON访问服务网格内的gRPC服务。本文介绍如何使用通过ASM入口网关实现HTTP请求网格内gRPC服务。

前提条件

背景信息

Envoy作为服务网格ASM数据平面的Proxy组件,内置了多种HTTP扩展过滤器,包括HTTP到gRPC的转码器。为了启用这个过滤器,Envoy定义了相应的过滤器协议,关于过滤器协议的详细信息,请参见过滤器协议。为此,ASM的控制平面需要定义一个EnvoyFilter来声明某个阶段启用这个过滤器,然后下发这个EnvoyFilter在指定环节启用转码器。

转码流程

ASM入口网关可将HTTP/JSON转码为gRPC。下图为完整的HTTP请、gRPC转码以及gRPC请求流程。请求流程
序号 说明
ASM控制平面下发用于gRPC转码的EnvoyFilter,用于路由到gRPC服务端口的规则配置Gateway和VirtualService到ASM入口网关,入口网关接收后即时加载生效。
入口网关收到用户或其客户端HTTP协议的请求后,将进行路由规则匹配和协议转换,然后以gRPC协议请求服务网格内的gRPC服务。
入口网关收到后端服务的gRPC响应,再将其转换为HTTP的响应返回给请求方。

步骤一:补充转码声明

gRPC服务是从protobuf格式的gRPC服务协议文件.proto的定义开始的。grpc-service-project封装gRPC接口,后续步骤包括构建镜像,编写Deployment,最终通过ASM将gRPC服务以Pod的形式部署到ACK集群。gRPC
为了支持HTTP转码gRPC,您需要在.proto文件的实践方法中补充以下支持转码的声明。
option(google.api.http) = {
  get: "/v1/talk/{data}/{meta}"
};
以hello-servicemesh-rpc示例中的.proto为例,以下为补充了转码声明后的.proto。关于hello-servicemesh-grpc的详细信息,请参见hello-servicemesh-grpc
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;
}

步骤二:生成Proto Descriptors文件

在Protocol工具上执行以下命令,使用Protoc命令从landing.proto生成landing.proto-descriptor文件。
# 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

步骤三:创建EnvoyFilter的YAML文件

在本地的运行窗口输入以下调用接口的请求,将自动使用grpc-transcoder工具生成EnvoyFlter的YAML文件。
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实例的版本。
  • service_port:gRPC服务端口。
  • service_name:gRPC服务名称。
  • proto_pkg:gRPC服务.proto包名的定义。
  • proto_svc:gRPC服务.proto中服务名的定义。
  • descriptorProto Descriptors文件本地路径。
使用上述请求自动生成以下EnvoyFilter,将以下内容复制到grpc-transcoder-envoyfilter.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

步骤四:在ASM控制台创建EnvoyFilter

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 在网格详情页面左侧导航栏选择流量管理 > Envoy过滤器(EnvoyFilter),然后在右侧页面单击新建
  5. 新建面板选择命名空间,将步骤三:创建EnvoyFilter的YAML文件grpc-transcoder-envoyfilter.yaml内容复制到文本框,然后单击确定

步骤五:验证Envoy配置

依次执行以下命令,验证Envoy动态配置中是否包含解码器GrpcJsonTranscoder。
#获取入口网关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?dynamic_listeners" >dynamic_listeners-"$timestamp".json
#确认配置中存在GrpcJsonTranscoder
grep -B3 -A7 GrpcJsonTranscoder dynamic_listeners-"$timestamp".json
输出配置中应包含以下内容,说明Envoy动态配置中包含解码器GrpcJsonTranscoder。
{
  "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
    },
    ...

步骤六:验证HTTP请求网格内gRPC服务

.proto文件定义了gRPC服务的请求接口和响应声明,根据请求接口调用gRPC服务,将返回定义的响应声明。以下为.proto文件定义的请求接口和响应声明:
  • .proto文件定义的请求接口
    rpc talk (TalkRequest) returns (TalkResponse) {
      option(google.api.http) = {
        get: "/v1/talk/{data}/{meta}"
      };
    }
  • .proto文件定义的响应声明
    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<string, string> kv = 3;
    }
    
    enum ResultType {
      OK = 0;
      FAIL = 1;
    }
执行以下命令,通过HTTP请求入口网关调用gRPC服务。
#获取入口网关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"
   }
  }
 ]
}

通过HTTP请求入口网关,调用gRPC服务,返回结果与预期定义相同。说明调用gRPC服务成功,转码成功。