Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support gRPCS #3411

Merged
merged 1 commit into from
Jan 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,6 @@ t/lib/dubbo-backend/dubbo-backend-provider/target/
/conf/config-*.yaml
!/conf/config-default.yaml
/conf/debug-*.yaml
/build-cache/
# release tar package
*.tgz
22 changes: 11 additions & 11 deletions .travis/linux_openresty_common_runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,17 @@ do_install() {
cp .travis/ASF* .travis/openwhisk-utilities/scancode/

ls -l ./
if [ ! -f "build-cache/grpc_server_example" ]; then
wget https://github.com/iresty/grpc_server_example/releases/download/20200901/grpc_server_example-amd64.tar.gz
if [ ! -f "build-cache/grpc_server_example_20210122" ]; then
wget https://github.com/api7/grpc_server_example/releases/download/20210122/grpc_server_example-amd64.tar.gz
tar -xvf grpc_server_example-amd64.tar.gz
mv grpc_server_example build-cache/
fi

if [ ! -f "build-cache/proto/helloworld.proto" ]; then
if [ ! -f "grpc_server_example/main.go" ]; then
git clone https://github.com/iresty/grpc_server_example.git grpc_server_example
fi

cd grpc_server_example/
git clone --depth 1 https://github.com/api7/grpc_server_example.git grpc_server_example
pushd grpc_server_example/ || exit 1
mv proto/ ../build-cache/
cd ..
popd || exit 1

touch build-cache/grpc_server_example_20210122
fi

if [ ! -f "build-cache/grpcurl" ]; then
Expand All @@ -101,7 +98,10 @@ script() {
export_or_prefix
openresty -V

./build-cache/grpc_server_example &
./build-cache/grpc_server_example \
-grpc-address :50051 -grpcs-address :50052 \
-crt ./t/certs/apisix.crt -key ./t/certs/apisix.key \
&

./bin/apisix help
./bin/apisix init
Expand Down
6 changes: 0 additions & 6 deletions .travis/linux_tengine_runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,6 @@ do_install() {
cp .travis/ASF* .travis/openwhisk-utilities/scancode/

ls -l ./

if [ ! -f "build-cache/grpc_server_example" ]; then
wget https://github.com/iresty/grpc_server_example/releases/download/20200901/grpc_server_example-amd64.tar.gz
tar -xvf grpc_server_example-amd64.tar.gz
mv grpc_server_example build-cache/
fi
}

script() {
Expand Down
49 changes: 48 additions & 1 deletion apisix/cli/ngx_tpl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,52 @@ http {
}
}

{% if use_or_1_15 then %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have to support openresty 1.15?
I think we can only support 1.19 and 1.17. we can update cli.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think? @spacewander

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, 1.15 is released in 2019, and Kong still supports it. Maybe we can give it one more chance.

# hack for OpenResty before 1.17.8, which doesn't support variable inside grpc_pass
location @1_15_grpc_pass {
access_by_lua_block {
apisix.grpc_access_phase()
}

grpc_set_header Content-Type application/grpc;
grpc_socket_keepalive on;
grpc_pass grpc://apisix_backend;

header_filter_by_lua_block {
apisix.http_header_filter_phase()
}

body_filter_by_lua_block {
apisix.http_body_filter_phase()
}

log_by_lua_block {
apisix.http_log_phase()
}
}

location @1_15_grpcs_pass {
access_by_lua_block {
apisix.grpc_access_phase()
}

grpc_set_header Content-Type application/grpc;
grpc_socket_keepalive on;
grpc_pass grpcs://apisix_backend;

header_filter_by_lua_block {
apisix.http_header_filter_phase()
}

body_filter_by_lua_block {
apisix.http_body_filter_phase()
}

log_by_lua_block {
apisix.http_log_phase()
}
}
{% else %}
location @grpc_pass {

access_by_lua_block {
Expand All @@ -562,7 +608,7 @@ http {

grpc_set_header Content-Type application/grpc;
grpc_socket_keepalive on;
grpc_pass grpc://apisix_backend;
grpc_pass $upstream_scheme://apisix_backend;

header_filter_by_lua_block {
apisix.http_header_filter_phase()
Expand All @@ -576,6 +622,7 @@ http {
apisix.http_log_phase()
}
}
{% end %}

{% if enabled_plugins["dubbo-proxy"] then %}
location @dubbo_pass {
Expand Down
29 changes: 17 additions & 12 deletions apisix/cli/ops.lua
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,23 @@ Please modify "admin_key" in conf/config.yaml .
util.die("ERROR: Admin API can only be used with etcd config_center.\n")
end

local or_ver = util.execute_cmd("openresty -V 2>&1")
local or_ver = get_openresty_version()
if or_ver == nil then
util.die("can not find openresty\n")
end

local use_or_1_15 = true
local need_ver = "1.15.8"
if not check_version(or_ver, need_ver) then
util.die("openresty version must >=", need_ver, " current ", or_ver, "\n")
end
if check_version(or_ver, "1.17.8") then
use_or_1_15 = false
end

local or_info = util.execute_cmd("openresty -V 2>&1")
local with_module_status = true
if or_ver and not or_ver:find("http_stub_status_module", 1, true) then
if or_info and not or_info:find("http_stub_status_module", 1, true) then
stderr:write("'http_stub_status_module' module is missing in ",
"your openresty, please check it out. Without this ",
"module, there will be fewer monitoring indicators.\n")
Expand Down Expand Up @@ -245,6 +259,7 @@ Please modify "admin_key" in conf/config.yaml .

-- Using template.render
local sys_conf = {
use_or_1_15 = use_or_1_15,
lua_path = env.pkg_path_org,
lua_cpath = env.pkg_cpath_org,
os_name = util.trim(util.execute_cmd("uname")),
Expand Down Expand Up @@ -365,16 +380,6 @@ Please modify "admin_key" in conf/config.yaml .
if not ok then
util.die("failed to update nginx.conf: ", err, "\n")
end

local op_ver = get_openresty_version()
if op_ver == nil then
util.die("can not find openresty\n")
end

local need_ver = "1.15.8"
if not check_version(op_ver, need_ver) then
util.die("openresty version must >=", need_ver, " current ", op_ver, "\n")
end
end


Expand Down
12 changes: 11 additions & 1 deletion apisix/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ local upstream_util = require("apisix.utils.upstream")
local ctxdump = require("resty.ctxdump")
local ipmatcher = require("resty.ipmatcher")
local ngx = ngx
local ngx_version = ngx.config.nginx_version
local get_method = ngx.req.get_method
local ngx_exit = ngx.exit
local math = math
Expand Down Expand Up @@ -511,6 +512,10 @@ function _M.http_access_phase()
run_plugin("access", plugins, api_ctx)
end

if route.value.service_protocol == "grpc" then
api_ctx.upstream_scheme = "grpc"
end

local code, err = set_upstream(route, api_ctx)
if code then
core.log.error("failed to set upstream: ", err)
Expand All @@ -519,8 +524,13 @@ function _M.http_access_phase()

set_upstream_host(api_ctx)

if route.value.service_protocol == "grpc" then
local up_scheme = api_ctx.upstream_scheme
if up_scheme == "grpcs" or up_scheme == "grpc" then
ngx_var.ctx_ref = ctxdump.stash_ngx_ctx()
if ngx_version < 1017008 then
return ngx.exec("@1_15_" .. up_scheme .. "_pass")
end

return ngx.exec("@grpc_pass")
end

Expand Down
4 changes: 3 additions & 1 deletion apisix/plugins/proxy-rewrite.lua
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ end

do
local upstream_vars = {
scheme = "upstream_scheme",
host = "upstream_host",
upgrade = "upstream_upgrade",
connection = "upstream_connection",
Expand All @@ -140,6 +139,9 @@ function _M.rewrite(conf, ctx)
ctx.var[upstream_vars[name]] = conf[name]
end
end
if conf["scheme"] then
ctx.upstream_scheme = conf["scheme"]
end

local upstream_uri = ctx.var.uri
if conf.uri ~= nil then
Expand Down
4 changes: 4 additions & 0 deletions apisix/schema_def.lua
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,10 @@ local upstream_schema = {
description = "the key of chash for dynamic load balancing",
type = "string",
},
scheme = {
default = "http",
enum = {"grpc", "grpcs", "http"}
},
labels = {
description = "key/value pairs to specify attributes",
type = "object",
Expand Down
17 changes: 17 additions & 0 deletions apisix/upstream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ local error = error
local tostring = tostring
local ipairs = ipairs
local pairs = pairs
local is_http = ngx.config.subsystem == "http"
local upstreams
local healthcheck

Expand Down Expand Up @@ -122,6 +123,17 @@ local function fetch_healthchecker(upstream)
end


local function set_upstream_scheme(ctx, upstream)
-- plugins like proxy-rewrite may already set ctx.upstream_scheme
if not ctx.upstream_scheme then
-- the old configuration doesn't have scheme field, so fallback to "http"
ctx.upstream_scheme = upstream.scheme or "http"
end

ctx.var["upstream_scheme"] = ctx.upstream_scheme
end


function _M.set_by_route(route, api_ctx)
if api_ctx.upstream_conf then
core.log.warn("upstream node has been specified, ",
Expand Down Expand Up @@ -175,11 +187,16 @@ function _M.set_by_route(route, api_ctx)
return 502, "no valid upstream node"
end

if not is_http then
return
end

if nodes_count > 1 then
local checker = fetch_healthchecker(up_conf)
api_ctx.up_checker = checker
end

set_upstream_scheme(api_ctx, up_conf)
return
end

Expand Down
2 changes: 1 addition & 1 deletion doc/admin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@
|upstream |False |Upstream|Enabled Upstream configuration, see [Upstream](architecture-design.md#upstream) for more||
|upstream_id|False |Upstream|Enabled upstream id, see [Upstream](architecture-design.md#upstream) for more ||
|service_id|False |Service|Binded Service configuration, see [Service](architecture-design.md#service) for more ||
|service_protocol|False|Upstream protocol type|only `grpc`|Must set `grpc` if using `gRPC proxy` or `gRPC transcode`. |
|labels |False |Match Rules|Key/value pairs to specify attributes|{"version":"v2","build":"16","env":"production"}|
|enable_websocket|False|Auxiliary| enable `websocket`(boolean), default `false`.||
|status |False|Auxiliary| enable this route, default `1`.|`1` to enable, `0` to disable|
Expand Down Expand Up @@ -525,6 +524,7 @@ In addition to the basic complex equalization algorithm selection, APISIX's Upst
|desc |optional|upstream usage scenarios, and more.|
|pass_host |optional|`pass` pass the client request host, `node` not pass the client request host, using the upstream node host, `rewrite` rewrite host by the configured `upstream_host`.|
|upstream_host |optional|This option is only valid if the `pass_host` is `rewrite`.|
|scheme|optional |The scheme used when talk with the upstream. The value is one of ['http', 'grpc', 'grpcs'], default to 'http'.|
|labels|optional |Key/value pairs to specify attributes|{"version":"v2","build":"16","env":"production"}|
|create_time|optional| epoch timestamp in second, like `1602883670`, will be created automatically if missing|
|update_time|optional| epoch timestamp in second, like `1602883670`, will be created automatically if missing|
Expand Down
29 changes: 24 additions & 5 deletions doc/grpc-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@
# grpc-proxy

proxying gRPC traffic:
gRPC client -> APISIX -> gRPC server
gRPC client -> APISIX -> gRPC/gRPCS server

## Parameters

* `service_protocol`: the route's option `service_protocol` must be `grpc`
* `uri`: format likes /service/method, Example:/helloworld.Greeter/SayHello
* `scheme`: the `scheme` of the route's upstream must be `grpc` or `grpcs`.
* `uri`: format likes /service/method, Example:/helloworld.Greeter/SayHello

### Example

#### create proxying gRPC route

Here's an example, to proxying gRPC service by specified route:

* attention: the route's option `service_protocol` must be `grpc`
* attention: the `scheme` of the route's upstream must be `grpc` or `grpcs`.
* attention: APISIX use TLS‑encrypted HTTP/2 to expose gRPC service, so need to [config SSL certificate](https.md)
* the grpc server example:[grpc_server_example](https://github.com/iresty/grpc_server_example)

Expand All @@ -44,8 +44,8 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13
{
"methods": ["POST", "GET"],
"uri": "/helloworld.Greeter/SayHello",
"service_protocol": "grpc",
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:50051": 1
Expand All @@ -66,3 +66,22 @@ $ grpcurl -insecure -import-path /pathtoprotos -proto helloworld.proto -d '{"n
```

This means that the proxying is working.

### gRPCS

If your gRPC service encrypts with TLS by itself (so called `gPRCS`, gPRC + TLS), you need to change the `scheme` to `grpcs`. The example above runs gRPCS service on port 50052, to proxy gRPC request, we need to use the configuration below:

```shell
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"methods": ["POST", "GET"],
"uri": "/helloworld.Greeter/SayHello",
"upstream": {
"scheme": "grpcs",
"type": "roundrobin",
"nodes": {
"127.0.0.1:50052": 1
}
}
}'
```
Loading