为什么ipvs下externalIPs乱设置会崩

由来

今天中午又有小白不懂在 ipvs 模式下,跟着网上文章设置 externalIPs 为 k8s 的机器 IP 后集群崩了,本文讲解下原因和自救手段。

externalIPs

下面是 官方文档 externalIPs 的描述:

如果有外部 IP 能够路由到一个或多个集群节点上,则 Kubernetes 服务可以暴露在这些 externalIPs 上。 当网络流量到达集群时,如果外部 IP(作为目的 IP 地址)和端口都与该 Service 匹配,Kubernetes 配置的规则和路由会确保流量被路由到该 Service 的端点之一。

如果你第一次看不懂没关系,后面看完本文就懂了这部分文字。

iptables 模式

先看看 iptables 的模式下原理,iptables 模式集群信息为:

NAME            STATUS   ROLES         AGE   VERSION    INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                                  KERNEL-VERSION                    CONTAINER-RUNTIME
10.xxx.xx.211   Ready    master,node   14d   v1.20.11   10.xxx.xx.211           Kylin Linux Advanced Server V10 (Sword)   4.19.90-24.4.v2101.ky10.aarch64   docker://20.10.22
10.xxx.xx.213   Ready    master,node   14d   v1.20.11   10.xxx.xx.213           Kylin Linux Advanced Server V10 (Sword)   4.19.90-24.4.v2101.ky10.aarch64   docker://20.10.22
10.xxx.xx.214   Ready    master,node   14d   v1.20.11   10.xxx.xx.214           Kylin Linux Advanced Server V10 (Sword)   4.19.90-24.4.v2101.ky10.aarch64   docker://20.10.22

部署下下面的服务,

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: zgz
spec:
  containers:
  - name: nginx
    image: nginx:stable
    ports:
      - containerPort: 80
        name: http-web-svc
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: zgz
  ports:
  - name: name-of-service-port
    protocol: TCP
    port: 80
    targetPort: http-web-svc

部署后,选个节点导出下 iptables nat 表规则,然后增加 externalIPs 设置为第一个节点的 IP 后再导出对比下

$ iptables -t nat -S >  before-nat-pod.txt
$ kubectl patch svc nginx-service -p '{"spec":{"externalIPs":["10.xxx.xx.211"]}}'
$ iptables -t nat -S >  after-nat-pod.txt
# 对比
$ diff <(sort before-nat-pod.txt) <(sort after-nat-pod.txt)
149a150,152
> -A KUBE-SERVICES -d 10.xxx.xx.211/32 -p tcp -m comment --comment "default/nginx-service:name-of-service-port external IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
> -A KUBE-SERVICES -d 10.xxx.xx.211/32 -p tcp -m comment --comment "default/nginx-service:name-of-service-port external IP" -m tcp --dport 80 -m addrtype --dst-type LOCAL -j KUBE-SVC-IQGXNJVVP26VHMIN
> -A KUBE-SERVICES -d 10.xxx.xx.211/32 -p tcp -m comment --comment "default/nginx-service:name-of-service-port external IP" -m tcp --dport 80 -m physdev ! --physdev-is-in -m addrtype ! --src-type LOCAL -j KUBE-SVC-IQGXNJVVP26VHMIN
# 不 sort 对比就多了因为顺序乱了的重复内容,sort 对比又会导致上面的规则顺序乱了,所以直接正则匹配
$ grep -w 'name-of-service-port external IP' after-nat-pod.txt
-A KUBE-SERVICES -d 10.xxx.xx.211/32 -p tcp -m comment --comment "default/nginx-service:name-of-service-port external IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.xxx.xx.211/32 -p tcp -m comment --comment "default/nginx-service:name-of-service-port external IP" -m tcp --dport 80 -m physdev ! --physdev-is-in -m addrtype ! --src-type LOCAL -j KUBE-SVC-IQGXNJVVP26VHMIN
-A KUBE-SERVICES -d 10.xxx.xx.211/32 -p tcp -m comment --comment "default/nginx-service:name-of-service-port external IP" -m tcp --dport 80 -m addrtype --dst-type LOCAL -j KUBE-SVC-IQGXNJVVP26VHMIN
# KUBE-SVC-IQGXNJVVP26VHMIN 则是 svc 的 iptables 负载入口链

diff 发现多了三条 iptables 规则,三条规则的前面属性都是 目标 IP 和端口为 10.xxx.xx.211:80 的

  • 进入动态伪装 IP 的处理链
  • -m physdev ! --physdev-is-in 意思为不是从桥接接口进来,也就是外部的物理网卡进来的,-m addrtype ! --src-type LOCAL 意思是来源地址不是本机上的 IP,最后是到 svc 的 DNAT 到 podIP
  • 第三个结尾的 -m addrtype --dst-type LOCAL 是目标 IP 在本机,也就是针对 externalIPs 设置成机器上的 网卡 IP 的,此刻拦截发到 svc。

因为前面都是针对 ip:80 的,所以 iptables 模式针对的都是纯四层,设置为集群机器 IP 这样没啥问题,问题是 ipvs 模式,也就是接下来讲的

ipvs 模式

清理掉上面的svc:

kubectl delete pod nginx
kubectl delete svc nginx-service

集群 kube-proxy 切换为 ipvs 模式后。在非容器环境上实现散装的 IPVS SVC。

非 k8s IP

切换模式后,先找个不是 k8s 节点但是和 k8s 节点机器是同一个二层局域网的没使用的 IP 作为 externalIPs,然后部署上面的步骤。

ipvs 下,kube-proxy 会把 svcIP/32 配置在 kube-ipvs0 的 dummy 接口上,而 externalIPs 也会把它配置在 kube-ipvs0 上。然后我们做一个实验,假设此刻 externalIPs 设置为 10.xxx.xx.250 可以任何一台k8s机器:

$ docker run --rm -d --net host --name test nginx:alpine

$ curl -I localhost
HTTP/1.1 200 OK
$ curl -I 10.xxx.xx.250
HTTP/1.1 200 OK

你会发现也能通,这是因为 kube-proxy 把 externalIPs/32 配置在 kube-ipvs0 的 dummy 上了,因为 IP 在本机上,而且是32位掩码,又因为 nginx 进程 bind 的 0.0.0.0 ,所以访问 externalIPs:80 也能到 nginx。

k8s 机器 IP

你可能在想,我 nginx 设置监听指定的本机节点网卡 IP ,这样 externalIPs:80 不就无法访问了吗,确实,但是 externalIPs:其他端口 也会定向到本机上,由于没端口监听访问最终会超时。假设你把 externalIPs 设置为第一个master 节点:

  • 第一个 master 节点路由到本机 IP 都会到 kube-ipvs0(因为掩码32最大),也就会和外界断联
  • 其他 master 访问 master1:6443 和 etcd 的 2379 都被定向到本机的,信息就乱了,包含其他端口都会定向到本机上,有端口监听就信息乱,没端口监听就超时
  • 然后非第一个 master 上的 kubelet 和 kubectl 只要流量最终负载到第一个 master 上就都会超时

假设已经发生:

  • 每台机器从 tty 或者虚拟化 vnc 进去后停止 kube-proxy,ip link delete <externalIPs>/32 dev kube-ipvs0
  • 如果操作后能使用 kubectl 就 edit 或者删除那个 svc
  • 如果不能就 etcdctl 删除掉这个 svc
  • 最后再启动停止的进程

ipvs 模式如何使用 externalIPs

可以设置外部没使用的 IP,然后网络设备添加路由把这个 IP 导向 k8s 的机器。或者有其他进程/轮子宣告 externalIPs 的 arp,让外部机器访问 externalIPs 能到 k8s 机器上

参考

声明:本网站刊载的所有内容,包括文字、图片、音频、视频、软件、程序、以及网页版式设计等如无特殊说明或标注,均在网上搜集。仅供访问者个人学习、研究或欣赏,禁止商业性或盈利性用途,访问者应遵守著作权法的规定,在使用时征得本站和原著作权人的同意并支付许可使用费。本网站刊登内容,如有侵权请权利人予以告知,本站将立即予以删除。
(0)
保哥保哥黄金会员
上一篇 2023年9月7日
下一篇 2023年9月11日

相关推荐

发表回复

登录后才能评论