配置 gRPC 代理
API7 企业版支持代理 gRPC 流量,让你可以使用与 HTTP 服务相同的流量管理、安全和可观测性能力来管理 gRPC 服务。你还可以将 RESTful HTTP 请求转码为 gRPC,并为浏览器客户端启用 gRPC-Web 支持。
前置条件
- API7 企业版实例正在运行。
- 已创建网关组,并且网关实例正在运行。
- 从控制台获取 Token。
- 在运行示例上游的主机上安装 Docker。
- 安装
grpcurl,用于验证原生 gRPC。 - 如果使用 ADC 配置,须安装 ADC 并将其连接到目标网关组。
启动示例 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 中保留既有设置,并添加监听器:
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。
- Admin API
- ADC
创建服务:
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"
}'
创建服务和路由:
services:
- name: grpc-native-service
labels:
docs-example: grpc-native
upstream:
scheme: grpc
type: roundrobin
nodes:
- host: grpc-service
port: 50051
weight: 100
routes:
- name: grpc-native-route
uris:
- /helloworld.Greeter/SayHello
methods:
- POST
预览由本示例管理的变更,确认差异中没有非预期的更新或删除:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=grpc-native
同步已审查的服务配置:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=grpc-native
通过 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 @-
配置转码路由
为转码示例创建独立的服务和路由:
- Admin API
- ADC
创建服务:
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"
}
}
}'
ADC 无法创建 Proto 资源。同步此服务和路由之前,请先运行上面的 Admin API Proto 命令。
创建服务和路由:
services:
- name: grpc-transcode-service
labels:
docs-example: grpc-transcode
upstream:
scheme: grpc
type: roundrobin
nodes:
- host: grpc-service
port: 50051
weight: 100
routes:
- name: grpc-transcode-route
uris:
- /hello
methods:
- POST
plugins:
grpc-transcode:
_state: enabled
proto_id: helloworld-proto
service: helloworld.Greeter
method: SayHello
预览由本示例管理的变更,确认差异中没有非预期的更新或删除:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=grpc-transcode
同步已审查的服务配置:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=grpc-transcode
向转码路由发送 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 预检请求。
- Admin API
- ADC
创建服务:
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"
}
}
}'
创建服务和路由:
services:
- name: grpc-web-service
labels:
docs-example: grpc-web
upstream:
scheme: grpc
type: roundrobin
nodes:
- host: grpc-service
port: 50051
weight: 100
routes:
- name: grpc-web-route
uris:
- /helloworld.Greeter/SayHello
methods:
- POST
- OPTIONS
plugins:
grpc-web:
_state: enabled
cors_allow_headers: "content-type,x-grpc-web,x-user-agent"
预览由本示例管理的变更,确认差异中没有非预期的更新或删除:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=grpc-web
同步已审查的服务配置:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=grpc-web
创建浏览器客户端
安装 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
创建浏览器客户端:
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();
});
创建用于显示结果的页面:
<!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
下一步
- 配置 WebSocket 代理:了解如何处理长连接 WebSocket。
- 配置 GraphQL 代理:了解面向 GraphQL 的流量管理 。