背景
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安装,详情参考官方文档
错误排查
许多K8s发行版都会配置Traefik为默认的Ingress控制器,占用80/443端口。这时我们查看kube-system命名空间下的pod会看到nginx-controller的pod一直在Pending状态,describe这些pod会看到有下面的报错:
default-scheduler 0/3 nodes are available: 1 node(s) didn't have free ports for the requested pod ports. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod.
我们禁用或者删掉Traefik即可(生产环境谨慎操作)。
配置示例
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这样的值的时候又报错了,好像在Ingress的配置中,这样带“.”的path是不合法的。
我在参考ChatGPT和GitHub中的其他代理gRPC项目的时候,好像又可以看到/service1,/service2这样的值,但是我没有成功。这个有待日后验证,或者有成功的朋友请不吝赐教。
测试
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
如果无法调通,请执行kubectl get ingress -A
查看你创建的Ingress的Address一栏是否是空的,如果是的话,请检查Nginx Controller的安装是否正常。
可能存在的问题
这个例子没有用到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 # 替换为你的服务端口