入口网关支持HTTP请求ServiceMesh内的GRPC服务实践

概述

本文将讲述ServiceMesh的入口网关(ingressgateway)支持协议转码的能力,以支持使用HTTP/JSON访问服务网格内的gRPC服务。

image.png
  1. ServiceMesh控制平面下发用于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生成EnvoyFilter。调用接口的示意请求如下。

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文件本地路径。

使用上述请求自动生成的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

4 入口网关启用转码器

通过工具生成EnvoyFilter后,我们通过执行命令kubectl --kubeconfig mesh_config -f grpc-transcoder-envoyfilter.yaml -n istio-system,使转码器即时生效。到此,配置的步骤就完成了,完整的流程示意图如下。

image.png

5 envoy配置校验

接下来我们校验入口网关的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?resource=dynamic_listeners" >dynamic_listeners-"$timestamp".json
#确认配置中存在GrpcJsonTranscoder
grep -B3 -A7 GrpcJsonTranscoder dynamic_listeners-"$timestamp".json

envoy动态配置中应包含类似如下的配置:

{
  "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服务接口的定义:

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;
}

依次执行如下命令,验证这个grpc服务接口可以通过HTTP请求入口网关实现调用:

#获取入口网关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"
   }
  }
 ]
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容