问题起源于我用 envoy 对 grpc 做 Layer 7 负载均衡的时候,发现 traffic 永远被转发到了一个特定的 Pod,显然是配置出错了。
环境如下:
同一个 namespace 下部署了 2 个 grpc server,一个 envoy
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
grpc-envoy-7684f49cb-9fv4h 1/1 Running 0 4h49m
grpc-server-668bdd6576-2bvkz 1/1 Running 0 4h51m
grpc-server-668bdd6576-tqzj4 1/1 Running 0 4h51m
envoy 的 service 配置如下
apiVersion: v1
kind: Service
metadata:
name: grpc-envoy
namespace: default
labels:
app: grpc-envoy
spec:
type: NodePort
ports:
- name: grpc
port: 8080
targetPort: grpc
nodePort: 30061
protocol: TCP
selector:
app: grpc-envoy
grpc-server 的 service 配置如下
apiVersion: v1
kind: Service
metadata:
name: grpc-server
namespace: default
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
protocol: TCP
selector:
app: grpc-server
其他的 deployment YAML file 省略。
这些都是非常简单的配置,看上去也不应该有问题,但是无论怎样,traffic 总是转发到一个 Pod,而无法实现负载均衡。
再一次又一次的检查 YAML 配置之后,发现原来需要明确的把 grpc server 的 clusterIP 设置为 None,这个叫 Headless Service
。
Headless Service
Headless Service 也是一种 Service,需要显式的在 YAML 中指定 spec:clusterIP: None
,也就是不需要 Cluster IP 的 Service。
我们知道,ClusterIP 的作用是在 kubernetes 内部提供一组 Pod 对外服务的统一 IP地址,并且配合 CoreDNS, 让我们在 Pod 内部能用 SVC-NAME.NAMESPACE.svc.cluster.local
形式的 FQDN 访问其他服务。
而如果把 clusterIP 设置为 None,则查询 FQDN 的时候, DNS 会返回所有符合条件的 Pod 的地址。
下面就来做个试验,
ClusterIP 不为 None
先把 grpc server 的 Service 配置改为默认的,也就是自带 ClusterIP
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grpc-envoy NodePort 10.97.207.187 <none> 8080:30061/TCP 4h33m
grpc-worker ClusterIP 10.108.192.173 <none> 8080/TCP 4s
然后随便进入 k8s 集群中其他某一个 Pod,用 nslookup 查询 DNS 记录
root@flask-server-786f96dd84-j4xqk:/home# nslookup grpc-server.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: grpc-server.default.svc.cluster.local
Address: 10.108.192.173
可以看到,FQDN grpc-server.default.svc.cluster.local
返回的是 ClusterIP。
ClusterIP 为 None
接着,删除旧的 grpc server Service, apply 下面这个 YAML。
注意: 无法在旧的 YAML 添加 clusterIP:None 然后 apply,必须删掉 k8s 中旧的 svc,然后 apply 新的。
apiVersion: v1
kind: Service
metadata:
name: grpc-worker
namespace: default
spec:
type: ClusterIP
clusterIP: None
ports:
- port: 8080
targetPort: 8080
protocol: TCP
selector:
app: grpc-worker
之后,我们看到 svc 里面的 clusterIP 为空
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grpc-envoy NodePort 10.97.207.187 <none> 8080:30061/TCP 4h42m
grpc-server ClusterIP None <none> 8080/TCP 3m32s
再次进入到某个 Pod 内,进行 DNS 查询,可以看到,这次 DNS 返回来所有的 Pod IP。
root@flask-server-786f96dd84-j4xqk:/home# nslookup grpc-server.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: grpc-server.default.svc.cluster.local
Address: 10.40.0.1
Name: grpc-server.default.svc.cluster.local
Address: 10.44.0.2
这样就可以由外部的 envoy 决定把 traffic 转发到哪个 Pod。
我们用 client 在外部调用 grpc,从返回结果来看确实实现了负载均衡。
$ ./client -s 192.168.31.54:30061
2020/09/21 04:43:43 resp from grpc-server-668bdd6576-2bvkz
2020/09/21 04:43:43 resp from grpc-server-668bdd6576-tqzj4
2020/09/21 04:43:43 resp from grpc-server-668bdd6576-tqzj4
2020/09/21 04:43:43 resp from grpc-server-668bdd6576-2bvkz
2020/09/21 04:43:43 resp from grpc-server-668bdd6576-2bvkz
2020/09/21 04:43:43 resp from grpc-server-668bdd6576-tqzj4
2020/09/21 04:43:43 resp from grpc-server-668bdd6576-2bvkz
2020/09/21 04:43:43 resp from grpc-server-668bdd6576-tqzj4
2020/09/21 04:43:43 resp from grpc-server-668bdd6576-2bvkz
2020/09/21 04:43:43 resp from grpc-server-668bdd6576-tqzj4