现象
mysql服务不是容器化的,是单独的几台服务器,然后使用一个VIP,将VIP作为externalIP注入到集群中,这时候如果在node上通过ssh连接vip,就可能会随机连接到某一台node上。
结论
当开启IPVS后,externalIP也会作为VIP被ipvs接管,因此如果在externalIp指定的是集群中具体Node节点的IP,在重启kube-proxy之前需要提前将externalIp替换成预先规划好的VIP,否则会出现VIP和Node节点IP冲突的问题。
使用ep+svc的方式代替externalIP。
原因
修改kube-proxy cm,将mode改为ipvs,删除kube-proxy pod。
查看kube-proxy 日志,确认使用的是ipvs模式。
部署mysql测试服务
apiVersion: v1
kind: Service
metadata:
name: mysql-mha
spec:
ports:
- port: 3306
targetPort: 3306
protocol: TCP
externalIPs:
- 172.50.60.105
查看ipvs0网卡地址
查看ipvs规则
ipvsadm -Ln
ipset list | grep -A 20 KUBE-EXTERNAL-IP
ipvs0网卡
ipvs0这块网卡和其他网卡有什么不同之处,可以发现ipvs0网卡有一个NOARP的标志,表示的是禁用arp,并且不管是down状态和up状态都有这个标志,说明这块网卡无论如何都是无法响应arp请求的。
ipvs0处于up状态时,内核会认为ipvs0这块网卡是可用的,但是因为禁用了arp,所以对这块网卡的arp请求都会直接丢弃掉,所以外部直接ping这块网卡的ip无法ping通。
当ipvs0这块网卡处于down状态时,因为arp请求的目标地址已经配在了ipvs0网卡上,所以内核当然会认为这次请求确实就是发给它的,但由于ipvs0这块网卡是down状态,所以内核会认为这块网卡不可用,并将同属于当前局域网的另一块网卡,也就是真实连接在交换机上的物理网卡的mac地址返回回去。
可以看到172.50.60.105的mac地址为node1 eth0网卡的mac地址。
为什么ssh会连到node
ip冲突一般是在多个节点上配置了同样的ip,首先肯定也是先通过arp请求获取到mac地址,然后通过mac地址最终定位到具体的节点,但是问题是所有配置了这个ip的节点都会将mac地址返回,我们并不能确定第一次返回的mac地址是哪个节点的mac地址,所以ssh或者http请求可能就会发往错误的节点。
externalip访问方式
但是在使用ipvs0这块网卡的时候,首先,我们不会通过ssh去连它,在k8s里就不是干这件事的,因为已经有真实的物理网卡可以连,然后就是对外提供服务,这个只有在给服务设置了externalip的时候,会直接在ipvs0网卡上配置一个外部能访问的ip,外部直接通过这个ip访问对应的服务,除externalip之外,ipvs0上配的都是内部的ip,只会在集群内部访问。
先看从外部访问,也就是externalip,通过externalip+port对外暴露服务,每个节点都能提供一样的服务,所以不管那个节点先返回arp请求,结果是一样的,所以外部用户需要知道访问的是哪个节点吗?不需要!
然后看内部访问,内部访问ipvs0上的ip等于访问localhost,本地就会将ip截获到,然后ipvs再根据负载均衡策略将流量转发到对应的pod。
clusterip访问方式
当创建 ClusterIP type 的 service 时,IPVS proxier 会执行以下三个操作:
- 确保本机已创建 dummy 网卡,默认为 kube-ipvs0。为什么要创建 dummy 网卡?因为 ipvs netfilter 的 DNAT 钩子挂载在 INPUT 链上,当访问 ClusterIP 时,将 ClusterIP 绑定在 dummy 网卡上为了让内核识别该 IP 就是本机 IP,进而进入 INPUT 链,然后通过钩子函数 ip_vs_in 转发到 POSTROUTING 链;
- 将 ClusterIP 绑定到 dummy 网卡;
- 为每个 ClusterIP 创建 IPVS virtual servers 和 real server,分别对应 service 和 endpoints;
数据包流向
- PREROUTING --> KUBE-SERVICES --> KUBE-CLUSTER-IP --> INPUT --> KUBE-FIREWALL --> POSTROUTING
- 首先进入 PREROUTING 链
- 从 PREROUTING 链会转到 KUBE-SERVICES 链,10.244.0.0/16 为 ClusterIP 网段
- 在 KUBE-SERVICES 链打标记
- 从 KUBE-SERVICES 链再进入到 KUBE-CLUSTER-IP 链
- KUBE-CLUSTER-IP 为 ipset 集合,在此处会进行 DNAT
- 然后会进入 INPUT 链
- 从 INPUT 链会转到 KUBE-FIREWALL 链,在此处检查标记
- 在 INPUT 链处,ipvs 的 LOCAL_IN Hook 发现此包在 ipvs 规则中则直接转发到 POSTROUTING 链
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A KUBE-SERVICES ! -s 10.244.0.0/16 -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ
// 执行完 PREROUTING
MARK --set-xmark 0x4000/0x4000
-A KUBE-SERVICES -m set --match-set KUBE-CLUSTER-IP dst,dst -j ACCEPT
// 进入 INPUT
-A INPUT -j KUBE-FIREWALL
-A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked packets" -m mark --mark 0x8000/0x8000 -j DROP
// 如果进来的数据带有 0x8000/0x8000 标记则丢弃,若有 0x4000/0x4000 标记则正常执行
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
nodePort访问方式
数据包流向
PREROUTING --> KUBE-SERVICES --> KUBE-NODE-PORT --> INPUT --> KUBE-FIREWALL --> POSTROUTING
- 首先进入 PREROUTING 链
- 从 PREROUTING 链会转到 KUBE-SERVICES 链
- 在 KUBE-SERVICES 链打标记
- 从 KUBE-SERVICES 链再进入到 KUBE-NODE-PORT 链
- KUBE-NODE-PORT 为 ipset 集合,在此处会进行 DNAT
- 然后会进入 INPUT 链
- 从 INPUT 链会转到 KUBE-FIREWALL 链,在此处检查标记
- 在 INPUT 链处,ipvs 的 LOCAL_IN Hook 发现此包在 ipvs 规则中则直接转发到 POSTROUTING 链