背景
gRPC需要对公网提供服务。这个场景虽然也可以用Service实现,但是Service的服务是基于端口的,端口是有限资源,且有产生冲突的场景,一旦服务多起来管理就是个很大的问题。
Ingress是K8s的一个入口网关,默认在80/443端口上提供服务。并且可以设置路由转发到集群内部的Service上,用它实现gRPC的转发,可以避免端口管理的问题,同时也实现了端口复用,所有的服务都通过一个入口来对外提供服务。
但是Ingress是基于HTTP的,虽然gRPC也可以是基于HTTP2但是仍然有许多坑会踩到。本文提供一个实验通过的可行配置。
解决方案
Nginx Controller
Ingress可以由各种控制器提供服务,内置的控制器是traefik。还有一个常用的控制器是Nginx控制器,本例中使用Nginx控制器实现。
Nginx控制器的安装命令如下,如果终端不能联外网的可以把这个文件下载下来。
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.12.0-beta.0/deploy/static/provider/cloud/deploy.yaml
也可以使用Helm安装,详情参考官方文档
配置示例
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: grpc-ingress
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
ingressClassName: nginx
rules:
- host: ingress1.0x0f.tech # 替换为你的域名
http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: go1-service # 替换为你的服务名称
port:
number: 8080 # 替换为你的服务端口
- host: ingress2.0x0f.tech # 替换为你的域名
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: go2-service # 替换为你的服务名称
port:
number: 8080 # 替换为你的服务端口
这个Ingress代理了两个gRPC服务,并用两个host来指定转发。关键是在配置中指定ingressClassName和annotations。我最早的设想是使用不同path,但是path的方案我一直没有成功,另外Nginx在代理gRPC的时候对path会有一些限制。比如下面这个Nginx代理gRPC的示例,我们可以看出Nginx在代理gRPC的时候指定了path,但这个值是跟API的定义关联的,无法任意指定。
location /demo.DemoSvc {
grpc_pass grpcs://192.168.20.5:54882;
keepalive_timeout 1800s;
}
但是我在Ingress中设置path为/demo.DemoSvc这样的值的时候又报错了,好像这样带“.”的path是不合法的。
我在参考ChatGPT和GitHub中的其他代理gRPC项目的时候,好像又可以看到/service1,/service2这样的值,但是我没有成功。这个有待日后验证,或者有成功的朋友请不吝赐教。
可能存在的问题
这个配置的另一个问题是host过多好像也是一个麻烦的事情,比如可能需要把host解析到IP,这个问题比较好解决。发请求的时候全部指定到IP:port,然后用authority参数来指定。比如上面的例子,就用下面两个命令发起请求
grpcurl -plaintext --authority ingress1.0x0f.tech 127.0.0.1:80 demo.Demo/Ping
grpcurl -plaintext --authority ingress2.0x0f.tech 127.0.0.1:80 demo.Demo/Ping
另外,这个例子没有用到ssl,如果要开启ssl,还可能遇到下面的问题:
- 通信证书同host关联。创建服务的同时,也要生成一个host,gRPC是否支持通配符证书尚未验证。
- 如果必须使用商用证书,通配符版本会更贵。
- 如果必须使用商业证书,host需要是持有域名。
附录(完整Demo配置):
################################################## USAGE ##############################################
#
# Install:
# kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.12.0-beta.0/deploy/static/provider/cloud/deploy.yaml
# kubectl apply -f mgrpc.yaml
# Test:
# grpcurl -plaintext --authority ingress1.0x0f.tech 127.0.0.1:80 demo.Demo/Ping
# grpcurl -plaintext --authority ingress2.0x0f.tech 127.0.0.1:80 demo.Demo/Ping
#
#######################################################################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: go1-service
labels:
app: go1-service
spec:
replicas: 1 # 设置服务副本数
selector:
matchLabels:
app: go1-service
template:
metadata:
labels:
app: go1-service
spec:
containers:
- name: go1-service
image: laotie255/pingpong:go1
ports:
- containerPort: 8080 # Go Zero 服务的端口
---
apiVersion: v1
kind: Service
metadata:
name: go1-service
labels:
app: go1-service
spec:
selector:
app: go1-service
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: grpc
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: go2-service
labels:
app: go2-service
spec:
replicas: 1 # 设置服务副本数
selector:
matchLabels:
app: go2-service
template:
metadata:
labels:
app: go2-service
spec:
containers:
- name: go2-service
image: laotie255/pingpong:go2
ports:
- containerPort: 8080 # Go Zero 服务的端口
---
apiVersion: v1
kind: Service
metadata:
name: go2-service
labels:
app: go2-service
spec:
selector:
app: go2-service
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: grpc
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: grpc-ingress
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
ingressClassName: nginx
rules:
- host: ingress1.0x0f.tech # 替换为你的域名
http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: go1-service # 替换为你的服务名称
port:
number: 8080 # 替换为你的服务端口
- host: ingress2.0x0f.tech # 替换为你的域名
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: go2-service # 替换为你的服务名称
port:
number: 8080 # 替换为你的服务端口