还以为是遇到鬼了,结果发现还是之间太年轻哇。进入正题
在进行 poc 测试过程中,随手使用ping
了下 svc 的名字,但是发现奇怪的错误
Destination Port Unreachable
明明之前还是好的哇,我记得之前是可以正常通信的哦,很奇怪,之前还是好好的,现在为啥不行了呢?抓狂(`Д´)
😫
环境简单了解一下后发现
root@debug:/# ping -c 1 192.168.110.66
PING 192.168.110.66 (192.168.110.66) 56(84) bytes of data.
64 bytes from 192.168.110.66: icmp_seq=1 ttl=62 time=0.271 ms
--- 192.168.110.66 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.271/0.271/0.271/0.000 ms
pod 到 pod 正常,理论这个肯定也是没问题的,顺手试了下
root@debug:/# telnet 192.168.110.66 389
Trying 192.168.110.66...
Connected to 192.168.110.66.
Escape character is '^]'.
都正常,就是 pod->svc
不对,那就是说 pod->pod
的四层网络是正常的,没问题,但是 pod 到 svc 的三层网络出现问题
如果你不太清楚三层四层说的是啥,再去了解在OSI 七层模型。[1]
那这里大概简单说下三层四层之间的区别吧:
三层
,即网络层,主要负责数据包的发送和接收,包括 IP 地址处理、路由选择等。在这一层,数据单位被称为“包”(Packet)。网络层的主要协议包括 IP(Internet Protocol)、ICMP(Internet Control Message Protocol)等。四层
,即传输层,主要负责提供端到端的通信服务,包括数据的分段、流量控制、错误检测等。在这一层,数据单位被称为“段”(Segment)或“数据报”(Datagram)。传输层的主要协议包括 TCP(Transmission Control Protocol)、UDP(User Datagram Protocol)等。以下是四层和三层的主要区别:
这里就基本清楚了他们的区别了,那猜想估计就是被防火墙 iptables 策略拦截了?
在 k8s pod 中,数据包会首先进入到 iptables 的 PREROUTING
链,然后经过一些列的规则转发到KUBE-SERVICES
链上
[root@work-A103 ~]# iptables -t nat -vnL
Chain PREROUTING (policy ACCEPT 832 packets, 57583 bytes)
pkts bytes target prot opt in out source destination
589M 39G KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
这个规则将所有数据包转发到 KUBE-SERVICES 链。这个规则的目的是处理所有目标地址是 Kubernetes Service 的数据包。
Chain KUBE-SERVICES (2 references)
pkts bytes target prot opt in out source destination
4 240 RETURN all -- * * 127.0.0.0/8 0.0.0.0/0
所有源地址是 127.0.0.0/8 的数据包会被返回,不会进行任何处理。这是因为这些数据包来自本地主机,不需要被转发。
2 120 KUBE-LOAD-BALANCER all -- * * 0.0.0.0/0 0.0.0.0/0 /* Kubernetes service lb portal */ match-set KUBE-LOAD-BALANCER dst,dst
所有目标地址在 KUBE-LOAD-BALANCER ipset 中的数据包会被转发到 KUBE-LOAD-BALANCER 链。这些数据包的目标是 Kubernetes 的 LoadBalancer Service。
0 0 KUBE-MARK-MASQ all -- * * !192.168.0.0/16 0.0.0.0/0 /* Kubernetes service cluster ip + port for masquerade purpose */ match-set KUBE-CLUSTER-IP dst,dst
所有目标地址不在 192.168.0.0/16 网段内,且在 KUBE-CLUSTER-IP ipset 中的数据包会被标记为 MASQUERADE。这是为了对这些数据包进行源地址伪装,以便它们可以被正确地路由回源 Pod。
48 3662 KUBE-NODE-PORT all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
所有目标地址是本地地址的数据包会被转发到 KUBE-NODE-PORT 链。这些数据包的目标是 Kubernetes 的 NodePort Service。
44 3422 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
所有目标地址在 KUBE-CLUSTER-IP ipset 中的数据包会被直接接受。这些数据包的目标是 Kubernetes 的 ClusterIP Service。
2 120 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 match-set KUBE-LOAD-BALANCER dst,dst
所有目标地址在 KUBE-LOAD-BALANCER ipset 中的数据包会被直接接受。这些数据包的目标是 Kubernetes 的 LoadBalancer Service。
从上面不同的匹配链上可以看出,到 svc 的流量都匹配到KUBE-CLUSTER-IP
这个规则上了,而 ipvs 模式在,所有服务的 clusterIP 都会出现在本机的 kube-ipvs0 网卡上,例如下方
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:16:3e:06:cc:83 brd ff:ff:ff:ff:ff:ff
inet 192.168.100.252/24 brd 192.168.100.255 scope global dynamic eth0
valid_lft 304538819sec preferred_lft 304538819sec
inet6 fe80::216:3eff:fe06:cc83/64 scope link
valid_lft forever preferred_lft forever
3: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether a2:02:dc:9e:e6:63 brd ff:ff:ff:ff:ff:ff
这里
4: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
link/ether ca:40:e8:2f:b5:ac brd ff:ff:ff:ff:ff:ff
inet 172.16.204.40/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 172.16.0.10/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 172.16.249.238/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 172.16.35.224/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 172.16.28.151/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 172.16.239.249/32 scope global kube-ipvs0
那数据包就必然走到INPUT
上
[root@work-A103 ~]# iptables -t filter -vnL
Chain INPUT (policy ACCEPT 23 packets, 2224 bytes)
pkts bytes target prot opt in out source destination
926M 265G KUBE-IPVS-FILTER all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes ipvs access filter */
所有数据包都会被转发到 KUBE-IPVS-FILTER 链。这个链的规则用于处理 IPVS 模式下的数据包。这个规则的目的是过滤掉那些目标地址不是 Kubernetes Service 的数据包,以防止它们被错误地发送到 Service 的后端 Pod。
926M 265G KUBE-PROXY-FIREWALL all -- * * 0.0.0.0/0 0.0.0.0/0 /* kube-proxy firewall rules */
所有数据包都会被转发到 KUBE-PROXY-FIREWALL 链。这个链的规则用于处理 kube-proxy 的防火墙规则。这个规则的目的是防止非法的网络流量进入或离开 Pod。
926M 265G KUBE-NODE-PORT all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes health check rules */
所有数据包都会被转发到 KUBE-NODE-PORT 链。这个链的规则用于处理 Kubernetes 的 NodePort Service 的健康检查规则。这个规则的目的是确保只有健康的 Pod 可以接收到流量。
926M 266G KUBE-FIREWALL all -- * * 0.0.0.0/0 0.0.0.0/0
所有数据包都会被转发到 KUBE-FIREWALL 链。这个链的规则用于处理 Kubernetes 的防火墙规则。这个规则的目的是防止非法的网络流量进入或离开节点。
此处,就可以专门关注一下KUBE-IPVS-FILTER
即可
[root@work-A103 ~]# iptables -t filter -vnL KUBE-IPVS-FILTER
Chain KUBE-IPVS-FILTER (1 references)
pkts bytes target prot opt in out source destination
9 1377 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 match-set KUBE-LOAD-BALANCER dst,dst
所有目标地址在 KUBE-LOAD-BALANCER ipset 中的数据包会被返回,不会进行任何处理。这些数据包的目标是 Kubernetes 的 LoadBalancer Service。
18 3740 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
所有目标地址在 KUBE-CLUSTER-IP ipset 中的数据包会被返回,不会进行任何处理。这些数据包的目标是 Kubernetes 的 ClusterIP Service。
0 0 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 match-set KUBE-EXTERNAL-IP dst,dst
所有目标地址在 KUBE-EXTERNAL-IP ipset 中的数据包会被返回,不会进行任何处理。这些数据包的目标是 Kubernetes Service 的外部 IP。
0 0 REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate NEW match-set KUBE-IPVS-IPS dst reject-with icmp-port-unreachable
所有新建连接的数据包,如果它们的目标地址在 KUBE-IPVS-IPS ipset 中,会被拒绝,并发送 ICMP Port Unreachable 消息。这个规则的目的是防止非 Kubernetes Service 的数据包被错误地发送到 Service 的后端 Pod。
到这里基本上距离真相也就不远了,那就是最后一个 ipset,当匹配到我们的 svc 的 ip 在这个 ipset 中的时候,我们的 icmp 就会被拒绝,然后反手送给我们一个Destination Port Unreachable
。
查看我们的目标 svc 的 IP 是否在KUBE-IPVS-IPS
中,可以直接通过
ipset list KUBE-IPVS-IPS |grep $IP
即可获取到。
所有在我们这个版本的 k8s 环境中,filter
表下的KUBE-IPVS-FILTER
链以及KUBE-IPVS-IPS
这个 ipset 导致 pod->svc 的 icmp 三层通信都会中断。
也有人说 svc 本身无实体 它是个虚拟 IP ping 它无意义的,你说你咋不早说呢?
在 k8s 版本 1.24 之前的可以 ping 通,之后的版本的就不可以了 😭
公众号菜单栏联系作者,或者公众号发送"加群",聊啥技术,就是混个脸熟
参考资料
[1]
OSI七层模型: https://mp.weixin.qq.com/s/ag-wVS-6PZjmNudf052GYA
[2]
kubernetes issue 119613: https://github.com/kubernetes/kubernetes/issues/119613