3

为什么ipvs下externalIPs乱设置会崩

 1 year ago
source link: https://zhangguanzhang.github.io/2023/07/13/k8s-externalIPs/#/ipvs-%E6%A8%A1%E5%BC%8F%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-externalIPs
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

总有小白不懂跟着网上文章设置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 <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

部署下下面的服务,

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-ipvs0dummy 接口上,而 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 机器上


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK