主页 >> 程序猿的东西 >> K8s中使用Ingress转发gRPC

K8s中使用Ingress转发gRPC

背景

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,还可能遇到下面的问题:

  1. 通信证书同host关联。创建服务的同时,也要生成一个host,gRPC是否支持通配符证书尚未验证。
  2. 如果必须使用商用证书,通配符版本会更贵。
  3. 如果必须使用商业证书,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       # 替换为你的服务端口
滚动至顶部