首发于运维实践

阿里云托管 K8S 集群内部无法访问 SLB 的问题

在使用阿里云的 Kubernetes 1.14 版「标准托管集群」和「标准专有集群」时,我们复用了创建 K8S 集群时自动创建的 ingress SLB 做其它「外部服务」的负载均衡,然后发现一个奇怪的问题:在这个 K8S 集群内部,无法访问 ingress SLB 上的监听端口!

对阿里云 SLB 有使用经验的朋友都会知道它有个限制:不能在 backend server 上通过 SLB 访问 backend server 自己。 但是我们的场景是 K8S 集群里的机器通过 ingress SLB 去访问集群外的机器,所以理应不会遇到那个限制。


这个 SLB 联通问题两个月前就遇到了,当时没有深究,今天又新建了一个同样的 K8S 1.14 + IPVS + Flannel 的「标准专有集群」,再次中招,于是费老劲调查了下。K8S 的网络牵涉到好几方:K8S、IPVS + KubeProxy,Flannel、nginx-ingress-controller、SLB、aliyun-cloud-controller:

  • 首先,K8S 的 CNI 以及 Flannel 是久经考验的东西,我司在其它集群用了很久,应该没问题;
  • 其次,IPVS 更是历史悠久,不可能出这个明显的问题,K8S 引入 KubeProxy 的 IPVS 模式也比较久了,不大像能出问题;
  • 第三, nginx-ingress-controller 只是根据 ingress rules 生成 nginx 配置文件,它不应该能阻断访问 SLB;
  • 第四,K8S 发展到 1.14 了,而且官方版本核心代码是不直接访问云服务商的东西,所以似乎也没这个能力干扰访问 SLB。

那么剩下的嫌疑就是阿里云 SLB 是否搞了什么新限制,或者 aliyun-cloud-controller 做了什么蹊跷的事情。


期间我组里同事发现在 K8S 节点上执行 ip route get SLB-IP 会发现走的 lo 网卡,很匪夷所思,当时 ip route show 看了下没找到原因,于是暂时搁置,他有点怀疑是 nginx ingress controller 搞的鬼,因为 ingress SLB 是为它创建的,或者是 SLB 和 K8S 集群使用的网段有冲突,然后我们重新购买 SLB,把 nginx-ingress-lb service 的 SLB 指向新买的 SLB,很郁闷的是问题必现:从 k8s 节点上 telnet SLB PORT 总是报告连接被拒绝,并不是 SLB 周知的那个不能环形通过 SLB 访问自己的限制表现——网络通讯卡住直至客户端超时。

郁闷中,回到上面说的奇怪路由问题,忽然想起来策略路由的事,于是 ip rule show,一看没啥特殊的,local, main, default 三种预定义的路由表,ip route show table default | grep SLB-IP 一查,意外的发现居然有记录!

# ip rule show
0:	from all lookup local 
32766:	from all lookup main 
32767:	from all lookup default 

# ip route show table local | grep 172.18.34.33
local 172.18.34.33 dev kube-ipvs0 proto kernel scope host src 172.18.34.33 

之前没注意到是因为 ip route show 显示的是 main 路由表,于是 ip addr show | less 查了下,豁然发现 SLB-IP 被绑定到了 kube-ipvs0 网卡上! 有点后悔之前没注意到这里,一是压根没想到阿里云会把 SLB-IP 绑定到虚拟机上,二是由于 k8s 缘故 ip addr show 输出特别长,没注意到这个 SLB IP 居然在其中。

这下就有眉目了,安装了 ipvsadm 软件包,用 ipvsadm -l -n 查看:

# ipvsadm -l -n | grep -C 9 172.18.34.33
TCP  172.18.22.71:31947 rr
  -> 172.26.2.141:14250           Masq    1      0          0         
  -> 172.26.3.17:14250            Masq    1      0          0         
TCP  172.18.22.71:32461 rr
  -> 172.26.2.141:14268           Masq    1      0          0         
  -> 172.26.3.17:14268            Masq    1      0          0         
TCP  172.18.22.71:32496 rr
TCP  172.18.22.71:32650 rr
TCP  172.18.22.71:32715 rr
TCP  172.18.34.33:80 rr          <--- 这里!!!
TCP  172.18.34.33:443 rr         <--- 这里!!!
TCP  172.26.1.1:30218 rr
TCP  172.26.1.1:30654 rr
  -> 172.26.2.141:14269           Masq    1      0          0         
  -> 172.26.3.17:14269            Masq    1      0          0         
TCP  172.26.1.1:30796 rr
TCP  172.26.1.1:30996 rr
  -> 172.26.2.141:14267           Masq    1      0          0         
  -> 172.26.3.17:14267            Masq    1      0          0         
TCP  172.26.1.1:31315 rr

# kubectl --kubeconfig=/etc/kubernetes/admin.conf get svc -n kube-system nginx-ingress-lb -o wide
NAME               TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                      AGE    SELECTOR
nginx-ingress-lb   LoadBalancer   172.31.63.129   172.18.34.33   80:31315/TCP,443:32715/TCP   4d7h   app=ingress-nginx

可以看到 nginx-ingress-lb 这个 LoadBalancer 类型的 K8S service,居然出现了一个 SLB IP 为 virtual server IP 的服务组,而且其 backend server 集合是空的! 这当然就解释了为什么 telnet SLB-IP PORT 时总是连接失败了!

进一步的对照其它阿里云托管 K8S-1.14 的集群,发现规律是这样的:

  1. LoadBalancer 类型的 K8S service,每个 K8S 节点上都会在 kube-ipvs0 网卡绑定 SLB IP,从而把集群内访问这个 SLB 劫持为「访问本机」;
  2. LoadBalancer 类型的 K8S service ,会有两个 LVS 虚拟服务组,一个是对应这个 K8S service 的 cluster IP 的,表现正常,LVS 虚拟服务组有正确的 backend server,一个是对应 SLB IP 的,表现不正常,它的 backend server 集合是「处于本机的」这个 K8S service 对应的 pod IP 集合,而且只有这个 K8S service 定义的服务端口,比如 nginx-ingress-lb 服务的 80 和 443 端口,完全没有 SLB 上手动额外定义的其它监听端口,这导致了本文开头的现象:
  • 从 K8S 节点上 telnet SLB-IP other-port 时连接被拒绝,因为压根没去连远程 SLB,而是直接连本机了,而本机恰好没监听这个 other-port;
  • 进一步的,从 K8S 节点上 telnet SLB-IP 80 or 443,连接也被拒绝,因为本机恰好没有被调度上 nginx-ingress-controller POD,于是 SLB-IP 对应的 LVS 虚拟服务组没有 backend server,从而连接被拒绝。

现象和直接原因清楚了,那么根源是哪里呢? 由于看不到阿里云托管 K8S 组件的源码(阿里云开源了一些,但怀疑版本落后了,还没来得及去 github 上扒),所以猜测 aliyun-cloud-controller 嫌疑居大,其次可能是阿里云把 K8S service 实现改出 bug——或者说 feature,这个 feature 可能的用意是让 K8S 集群内部去访问 LoadBalancer 类型 k8s svc 对应的 SLB 时(比如K8S集群内部去访问 ingress SLB,这是很可能发生的):

  1. 不会发生 K8s node -> SLB -> same K8s node 环形网络访问而挂住,这是文章开头说的阿里云 SLB 由来已久的限制;
  2. K8S 内部节点通过 SLB 访问自己集群服务时,不必绕道 SLB,而是本机非常「鸡贼」的劫持到本机 LVS 虚拟服务组,然后集群内部转发到对应 POD 上了,节约网络开销;

如果这确实是阿里云有意为之的 feature,那么尽管实现非常「鸡贼」,我还是要点个小小的赞,但是——但是——贵司这么重要的公有云容器服务不测试么?不测试么? 不测试么?当一个 K8S 节点没有这个 LB 类型 K8S service 对应的 POD 时,通过这个假冒 SLB IP 访问是不通的啊!!! 当一个 K8S 节点只有一个这个 LB 类型 K8S service 对应的 POD 时,通过这个假冒 SLB IP 访问是没有高可用的啊!!!

进一步,如果这确实是阿里云有意为之的 feature,那么,真的是太 XX 了,你们这么一弄,在 K8S 集群「里面」,就访问不到那个倒霉的 SLB 了,逼着阿里云用户创建更多的内网 SLB,不能复用被 LB 型 K8S service 引用的 SLB 了,穷啊,我们快用完阿里云免费的内网 SLB 配额了。。。

编辑于 2020-02-19 10:25