TL; DR:
问题现象是集群运行一段时间后kube-proxy停止更新IPVS规则,这个问题是由一个存在于1.11.5/1.12.2/1.13.0版本中的bug造成的。并在以下版本中得到了修复,可以查看kubernetes/kubernetes#71071了解更多信息。
1.11.7
1.12.5
1.13.2
另一方面,也可以通过将未修复版本的kube-proxy设置为iptables模式来规避这个问题。
开始讲故事
最近有两个新平台部署,第一次在线上启用了很多新方案。包括Master高可用,LVS,持久化存储,Prometheus监控。本以为在测试环境已经正常跑了一个月的方案理当顺风顺水,结果PoC的第二天就出了一些问题。
问题最初的现象是局部网络不通,一些模块之间的连接出现了问题,exec到容器上,nmap扫描对端端口,发现竟然是filtered的,然而在对端容器里扫描自身却是Open的。显然是容器器网络出现了一些问题。
回想一下K8s中容器通信都会经过什么,在我的方案中,CNI插件采用了Calico,而kube-proxy则是开启了IPVS模式。在容器网络的构建中,CNI和kube-proxy两者都有参与。
CNI插件的任务是在容器开启时为容器分配IP,并为这个IP构建虚拟设备,另外,CNI插件也负责将容器间通信的协议包(如TCP/UDP等)从K8s集群中的任意一台机器转发到指定容器所在的机器上,再转发到指定容器上,在Calico方案中,这是通过N条主机路由(N=cali虚拟网卡数量)和M条tunl0上的直接路由(M=该K8s集群节点数量)实现的。
$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.130.29.1 0.0.0.0 UG 100 0 0 ens32
10.130.29.0 0.0.0.0 255.255.255.0 U 100 0 0 ens32
10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 *
10.244.0.137 0.0.0.0 255.255.255.255 UH 0 0 0 calid3c6b0469a6
10.244.0.138 0.0.0.0 255.255.255.255 UH 0 0 0 calidbc2311f514
10.244.0.140 0.0.0.0 255.255.255.255 UH 0 0 0 califb4eac25ec6
10.244.1.0 10.130.29.81 255.255.255.0 UG 0 0 0 tunl0
10.244.2.0 10.130.29.82 255.255.255.0 UG 0 0 0 tunl0
首先尝试了排除CNI的问题,CNI很好排查,只需要记录各个节点上容器的IP(每个节点记一个IP即可)然后在各个节点上Ping这些IP,如果都能Ping通,那CNI插件的工作就是完全正常的。
$ kubectl get pods -o wide -n kube-system|grep 10.244|awk '{print $6}'|xargs nmap -sP|grep up
Host is up (0.000032s latency).
Host is up (0.000033s latency).
Host is up (0.00063s latency).
Host is up (0.000038s latency).
Nmap done: 4 IP addresses (4 hosts up) scanned in 0.22 seconds
运行的结果是排除了CNI的问题。
随后,由于第一次使用nmap扫描对端端口时, 使用的是Cluster IP,也即是Service的IP,而ClusterIP到Pod IP之间只夹着一条LVS负载均衡规则(或者如果kube-proxy使用默认配置,则是iptables规则),既然Pod IP的连通性没有问题,那大概率是IPVS规则出现了问题。查询现有的IPVS规则,就发现了只有virtual server endpoint而没有real server的IPVS规则:
ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.0.1:443 rr
-> 10.130.29.80:6443 Masq 1 6 0
-> 10.130.29.81:6443 Masq 1 1 0
-> 10.130.29.82:6443 Masq 1 0 0
TCP 10.96.0.10:53 rr
-> 10.244.0.137:53 Masq 1 0 0
-> 10.244.0.138:53 Masq 1 0 0
TCP 10.99.182.13:80 rr
-> 10.130.29.80:8080 Masq 1 0 0
-> 10.130.29.81:8080 Masq 1 0 0
-> 10.130.29.82:8080 Masq 1 0 0
TCP 10.99.216.249:443 rr
-> 10.244.1.30:8443 Masq 1 0 0
TCP 10.102.174.22:443 rr
-> 10.244.0.140:443 Masq 1 2 0
TCP 10.108.62.55:5473 rr
UDP 10.96.0.10:53 rr
-> 10.244.0.137:53 Masq 1 0 0
-> 10.244.0.138:53 Masq 1 0 0
$ kubectl get service --all-namespaces|grep 10.108.62.55
kube-system calico-typha ClusterIP 10.108.62.55 <none> 5473/TCP 13d
$ kubectl get pods -n kube-system|grep calico
calico-node-ftrks 2/2 Running 0 13d
calico-node-gvtsp 2/2 Running 0 13d
calico-node-v6cx4 2/2 Running 0 13d
至此可以确定是由于缺少IPVS规则来将到达service的流量负载均衡到Pod上导致了这个问题。而IPVS规则是由kube-proxy维护的,打开kube-proxy的日志,发现了报错:
kubectl logs -n kube-system kube-proxy-cdsgl |tail
I0111 06:51:48.874607 1 graceful_termination.go:160] Trying to delete rs: 10.96.0.1:443/TCP/10.130.29.80:6443
I0111 06:51:48.874765 1 graceful_termination.go:160] Trying to delete rs: 10.96.0.1:443/TCP/10.130.29.81:6443
I0111 06:52:48.877668 1 graceful_termination.go:160] Trying to delete rs: 10.96.0.1:443/TCP/10.130.29.80:6443
I0111 06:52:48.877823 1 graceful_termination.go:160] Trying to delete rs: 10.96.0.1:443/TCP/10.130.29.81:6443
I0111 06:53:48.878217 1 graceful_termination.go:160] Trying to delete rs: 10.96.0.1:443/TCP/10.130.29.80:6443
I0111 06:53:48.878490 1 graceful_termination.go:160] Trying to delete rs: 10.96.0.1:443/TCP/10.130.29.81:6443
I0111 06:54:48.878874 1 graceful_termination.go:160] Trying to delete rs: 10.96.0.1:443/TCP/10.130.29.80:6443
I0111 06:54:48.879044 1 graceful_termination.go:160] Trying to delete rs: 10.96.0.1:443/TCP/10.130.29.81:6443
I0111 06:55:48.879356 1 graceful_termination.go:160] Trying to delete rs: 10.96.0.1:443/TCP/10.130.29.80:6443
I0111 06:55:48.879550 1 graceful_termination.go:160] Trying to delete rs: 10.96.0.1:443/TCP/10.130.29.81:6443
参考修复该问题的PR中的解释,原因是 kube-proxy中两个goroutine同时调用libipvs产生了死锁,新版本在对libipvs的调用上加了锁。
至于为什么直到在线上PoC部署的时候才遇到这个问题,主要原因还是在线下测试的不够多,使用量大的集群只更新了1.13.0,但从没有开启过IPVS,而同时升级了1.13.0并且开启IPVS的测试集群都非常的短命,也就没有机会等到这个问题出现。