跳到主要内容
版本:3.10.x

配置 gRPC 代理

API7 企业版支持代理 gRPC 流量,让你可以使用与 HTTP 服务相同的流量管理、安全和可观测性能力来管理 gRPC 服务。你还可以将 RESTful HTTP 请求转码为 gRPC,并为浏览器客户端启用 gRPC-Web 支持。

前置条件​

启动示例 gRPC 服务器​

将 GATEWAY_CONTAINER 设置为正在运行的 API7 网关容器。创建专用网络并将网关连接到该网络:

export GATEWAY_CONTAINER=replace-with-gateway-container-name

docker network create gateway-grpc-net
docker network connect gateway-grpc-net "$GATEWAY_CONTAINER"

在共享网络中启动持续维护的示例 gRPC 服务器:

docker run -d \
--name grpc-service \
--network gateway-grpc-net \
-p 50051:50051 \
api7/grpc-server-example:1.0.2

下载配套的协议定义:

curl -fLo helloworld.proto \
"https://raw.githubusercontent.com/api7/grpc_server_example/release/1.0.2/proto/helloworld.proto"

服务器公开 helloworld.Greeter 服务和 SayHello 方法。直接验证上游:

grpcurl -plaintext \
-proto helloworld.proto \
-d '{"name":"API7"}' \
127.0.0.1:50051 \
helloworld.Greeter.SayHello

如果网关不是通过 Docker 部署,请在网关可访问的位置运行示例服务器,并将上游配置中的 grpc-service 替换为该地址。

为原生 gRPC 启用 HTTP/2 监听器​

原生 gRPC 客户端需要一个可访问的 HTTP/2 监听器。REST 到 gRPC 和 gRPC-Web 客户端可以使用普通 HTTP 监听器,因为网关会先转换这些请求,再调用原生 gRPC 上游。

在网关部署中公开一个额外的监听端口,例如 9081。在部署使用的完整 config.yaml 中保留既有设置,并添加监听器:

config.yaml
apisix:
node_listen:
- port: 9080
- port: 9081
enable_http2: true

通过该部署的常规流程重启或重新创建网关,然后使用端口 9081 验证原生 gRPC。

对于 Helm 部署,请在 values 的 gateway.http.additionalContainerPorts 中添加对应监听器,然后执行 Helm 升级;不要直接修改 Pod 内由 Chart 生成的配置文件。

重新创建 Docker 网关会移除之前手动添加的网络连接。继续操作前,将 GATEWAY_CONTAINER 设置为新容器名称,并重新连接网络:

export GATEWAY_CONTAINER=replace-with-recreated-gateway-container-name
docker network connect gateway-grpc-net "$GATEWAY_CONTAINER"

配置 Admin API 访问​

独立验证各示例

以下配置是相互独立的示例,请在专用测试网关组中每次只启用一个。切换示例前,停用或删除前一示例的路由和服务,并确保 Admin API 与 ADC 操作的是同一目标网关组。原生 gRPC 与 gRPC-Web 示例匹配相同的 POST 路径,9081 与 9080 两个监听端口本身不能隔离路由;同时启用可能让原生 gRPC 请求误入 gRPC-Web 路由而失败。

Admin API 示例使用控制台 Token。导出该 Token,并将各请求 URL 中的 {gateway_group_id} 替换为目标网关组 ID。快速入门中的网关组 ID 为 default。

export API_KEY=replace-with-dashboard-token

代理原生 gRPC​

创建一个 scheme 为 grpc 的服务,并创建指向该服务的路由。如果上游通过 TLS 接收 gRPC,请改用 grpcs。

创建服务:

curl -k "https://localhost:7443/apisix/admin/services/grpc-native-service?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "grpc-native-service",
"upstream": {
"type": "roundrobin",
"scheme": "grpc",
"nodes": [
{
"host": "grpc-service",
"port": 50051,
"weight": 100
}
]
}
}'

创建路由:

curl -k "https://localhost:7443/apisix/admin/routes/grpc-native-route?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "grpc-native-route",
"methods": ["POST"],
"paths": ["/helloworld.Greeter/SayHello"],
"service_id": "grpc-native-service"
}'

通过 HTTP/2 监听器发送原生 gRPC 请求:

grpcurl -plaintext \
-proto helloworld.proto \
-d '{"name":"API7"}' \
127.0.0.1:9081 \
helloworld.Greeter.SayHello

响应应包含以下内容:

{
"message": "Hello API7"
}

配置 REST 到 gRPC 转码​

grpc-transcode 插件向 HTTP 客户端公开 gRPC 方法,并在 JSON 和 Protocol Buffers 之间进行转换。

上传 Proto 定义​

必须通过 Admin API 创建 Proto 资源。安装 jq,将下载的文件编码为请求体:

jq -Rs '{content: .}' helloworld.proto | \
curl -k "https://localhost:7443/apisix/admin/protos/helloworld-proto?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d @-

配置转码路由​

为转码示例创建独立的服务和路由:

创建服务:

curl -k "https://localhost:7443/apisix/admin/services/grpc-transcode-service?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "grpc-transcode-service",
"upstream": {
"type": "roundrobin",
"scheme": "grpc",
"nodes": [
{
"host": "grpc-service",
"port": 50051,
"weight": 100
}
]
}
}'

创建路由:

curl -k "https://localhost:7443/apisix/admin/routes/grpc-transcode-route?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "grpc-transcode-route",
"methods": ["POST"],
"paths": ["/hello"],
"service_id": "grpc-transcode-service",
"plugins": {
"grpc-transcode": {
"proto_id": "helloworld-proto",
"service": "helloworld.Greeter",
"method": "SayHello"
}
}
}'

向转码路由发送 HTTP 请求:

curl "http://127.0.0.1:9080/hello" -X POST \
-H "Content-Type: application/json" \
-d '{"name":"API7"}'

网关应返回解码后的 gRPC 响应:

{
"items": [],
"message": "Hello API7",
"gender": "GENDER_UNKNOWN"
}

配置 gRPC-Web​

grpc-web 插件让浏览器客户端可以调用原生 gRPC 上游。为浏览器流量创建独立的服务和路由。浏览器客户端运行在不同端口,因此路由必须同时接受 POST 请求和 OPTIONS 预检请求。

创建服务:

curl -k "https://localhost:7443/apisix/admin/services/grpc-web-service?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "grpc-web-service",
"upstream": {
"type": "roundrobin",
"scheme": "grpc",
"nodes": [
{
"host": "grpc-service",
"port": 50051,
"weight": 100
}
]
}
}'

创建路由:

curl -k "https://localhost:7443/apisix/admin/routes/grpc-web-route?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "grpc-web-route",
"methods": ["POST", "OPTIONS"],
"paths": ["/helloworld.Greeter/SayHello"],
"service_id": "grpc-web-service",
"plugins": {
"grpc-web": {
"cors_allow_headers": "content-type,x-grpc-web,x-user-agent"
}
}
}'

创建浏览器客户端​

安装 Node.js、Protocol Buffers 编译器和 protoc-gen-grpc-web,以生成客户端。最后一步使用 Python 3在本地提供页面。

为路由使用的同一个 helloworld.Greeter 契约生成浏览器客户端。

创建客户端目录,并安装固定版本的浏览器依赖:

mkdir grpc-web-client
cp helloworld.proto grpc-web-client/
cd grpc-web-client

npm init -y
npm install --save-exact grpc-web@2.1.1 google-protobuf@4.0.2
npm install --save-dev --save-exact \
@protocolbuffers/protoc-gen-js@4.0.2 \
esbuild@0.25.9

生成 JavaScript 消息类和客户端桩代码:

protoc -I=. \
--plugin=protoc-gen-js=./node_modules/.bin/protoc-gen-js \
--js_out=import_style=commonjs:. \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:. \
helloworld.proto

创建浏览器客户端:

client.js
const { GreeterClient } = require('./helloworld_grpc_web_pb');
const { HelloRequest } = require('./helloworld_pb');

const output = document.querySelector('#output');
const client = new GreeterClient('http://127.0.0.1:9080');
const request = new HelloRequest();
request.setName('API7');

client.sayHello(request, {}, (error, response) => {
output.textContent = error
? `error: ${error.message}`
: response.getMessage();
});

创建用于显示结果的页面:

index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>API7 Gateway gRPC-Web example</title>
</head>
<body>
<pre id="output"></pre>
<script src="bundle.js"></script>
</body>
</html>

打包并提供客户端:

./node_modules/.bin/esbuild client.js --bundle --outfile=bundle.js
python3 -m http.server 8088

在浏览器中打开 http://127.0.0.1:8088。页面应显示:

Hello API7

下一步​