Kong-gRPC

3,505 阅读4分钟

从版本1.3开始,kong开始原生支持gRPC了。本篇文章会讨论如何使用kong管理gRPC服务。

主要讨论2个步骤,单独的gRPC Service和Route,单独的gRPC Service和多个Route。前者配置一个能捕获全部请求的Route,它清理全部的gRPC请求到制定的upstream gRPC service;后者标明如何Route每一个gRPC方法。

注意:想要使用该功能需要开启kong的http2的代理,如果使用docker启动命令如下:

// 其中8990端口用于grpc
docker run -d --name kong \
     --network=kong-net \
     -e "KONG_DATABASE=cassandra" \
     -e "KONG_PG_HOST=kong-database" \
     -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
     -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
     -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
     -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
     -e "KONG_PROXY_LISTEN=0.0.0.0:8000 reuseport backlog=16384, 0.0.0.0:8443 ssl reuseport backlog=16384, 0.0.0.0:8990 http2 reuseport backlog=16384" \
     -p 8000:8000 \
     -p 8443:8443 \
     -p 8001:8001 \
     -p 8444:8444 \
     -p 8990:8990 \
     kong:latest


1. 单gRPC Service & Route

创建一个Service(假设gRPC服务启动在你本地的15002端口)

// localhost上要部署有grpc服务,如果是docker部署,grpc在宿主机上,要填写宿主机地址
curl -XPOST localhost:8001/services \
  --data name=grpc \
  --data protocol=grpc \
  --data host=localhost \
  --data port=15002

然后创建一个Route

$ curl -XPOST localhost:8001/services/grpc/routes \
  --data protocols=grpc \
  --data name=catch-all \
  --data paths=/

然后可以使用 github.com/grpc/grpc-j… 中的example验证该功能。(把client地址改为kong的地址)

使用上述例子中使用CustomHeaderServer验证,会发现client中收到的返回会在头文件中增加如下内容:

x-kong-upstream-latency=232,x-kong-proxy-latency=2,via=kong/2.0.0

2. 单gRPC Service、多Route

基于上一个例子,创建多个route,每个用于一个gRPC方法。假设protobuf文件如下:

syntax = "proto2";

package hello;

service HelloService {
  rpc SayHello(HelloRequest) returns (HelloResponse);
  rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
  rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
  rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
}

message HelloRequest {
  optional string greeting = 1;
}

message HelloResponse {
  required string reply = 1;
}

我们希望为SayHello和LotsOfReplies单独创建route,方法如下:

// SayHello
curl -XPOST localhost:8001/services/grpc/routes \
  --data protocols=grpc \
  --data paths=/hello.HelloService/SayHello \
  --data name=say-hello

//LotsOfReplies
curl -XPOST localhost:8001/services/grpc/routes \
  --data protocols=grpc \
  --data paths=/hello.HelloService/LotsOfReplies \
  --data name=lots-of-replies

建议直接用上面git中的RouteGuideServer测试。

3. plugins

kong的插件如何开发和集成 zhuanlan.zhihu.com/p/52402537 这篇博客里写的很清楚,除了版本有点老以外别的没问题。写插件时遇到lua语法问题可以参考:www.lua.org/manual/5.1/… 遇到PDK(kong插件开发工具包)的问题可以参考:docs.konghq.com/2.0.x/pdk/

为了调研使用插件鉴权grpc协议,我写了一个简单的demo,下载(https://github.com/Kong/kong-plugin.git),按照上面博客的步骤修改文件结构,结果如下:

├── LICENSE
├── README.md
├── kong
│   └── plugins
│       └── wangqi
│           ├── handler.lua
│           └── schema.lua
├── kong-plugin-wangqi-0.1.0-1.rockspec

修改schema.lua文件内容,增加插件的config字段,这里我们增加了一个字段叫my_keys,是个string类型。

return {  no_consumer = false, 
-- this plugin is available on APIs as well as on Consumers,  f
ields = {  my_keys = {type = "string"}  },  
self_check = function(schema, plugin_t, dao, is_updating)    
-- perform any custom verification    
return true  
end}

随后修改handler.lua增加鉴权逻辑

function plugin:access(plugin_conf)
 local token = kong.request.get_header("mytoken")
   if token == nil then
       return kong.response.exit(400, "mytoken empty")
   end
   if token ~= plugin_conf.my_keys then
      return kong.response.exit(400, "mytoken error")
   end
end --]]

因为是demo,这里鉴权逻辑很简单,如果请求header中的mytoken信息等于插件配置的my_keys则鉴权通过。

上面的插件集成的博客中kong是物理机部署的,当使用docker时集成方式类似:

docker run -d --name kong \
     --network=kong-net \
     -e "KONG_DATABASE=cassandra" \
     -e "KONG_PG_HOST=kong-database" \
     -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
     -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
     -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
     -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
     -e "KONG_PROXY_LISTEN=0.0.0.0:8000 reuseport backlog=16384, 0.0.0.0:8443 ssl reuseport backlog=16384, 0.0.0.0:8990 http2 reuseport backlog=16384" \
     -e "KONG_PLUGINS=bundled,wangqi" \
     -e "KONG_LUA_PACKAGE_PATH=/usr/local/kong-plugin-wangqi/?.lua;./?.lua;./?/init.lua;;" \
     -v /Users/qiwang/workspace/kong-plugin-wangqi:/usr/local/kong-plugin-wangqi \
     -p 8000:8000 \
     -p 8443:8443 \
     -p 8001:8001 \
     -p 8444:8444 \
     -p 8990:8990 \
     kong:latest

这里我选择的是:

  1. 把本地的插件源代码路径挂载到docker内部。
  2. 使用KONG_PLUGINS环境变量制定启动的插件名称,其中bundled是自带的插件,wangqi是刚刚新建的插件。
  3. 使用KONG_LUA_PACKAGE_PATH制定插件所在目录。

也可以使用博客中laurocks编译的方式集成插件,方法类似。插件集成完成后,即可验证是否满足期望。

当前待解决问题:插件开发方式不友好,只能写完插件后restart镜像看效果,后续期待更友好的开发方式。