zhangguanzhang's Blog

为什么ipvs下externalIPs乱设置会崩

字数统计: 1.6k阅读时长: 7 min
2023/07/13

总有小白不懂跟着网上文章设置externalIPs后集群崩了

由来

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

externalIPs

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

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

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

iptables 模式

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

1
2
3
4
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 <none> 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 <none> 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 <none> Kylin Linux Advanced Server V10 (Sword) 4.19.90-24.4.v2101.ky10.aarch64 docker://20.10.22

部署下下面的服务,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 后再导出对比下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ 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:

1
2
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-ipvs0dummy 接口上,而 externalIPs 也会把它配置在 kube-ipvs0 上。然后我们做一个实验,假设此刻 externalIPs 设置为 10.xxx.xx.250 可以任何一台k8s机器:

1
2
3
4
5
6
$ 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 机器上

参考

CATALOG
  1. 1. 由来
    1. 1.1. externalIPs
    2. 1.2. iptables 模式
    3. 1.3. ipvs 模式
      1. 1.3.1. 非 k8s IP
      2. 1.3.2. k8s 机器 IP
      3. 1.3.3. ipvs 模式如何使用 externalIPs
  2. 2. 参考