Kubernetes 网络问题排查
安全组
当一个网络包进入网卡的时候,首先拿下 mac
头看看是不是当前网卡的。
- 如果是,则拿下
IP
头,得到了IP
之后,就开始进行路由判断。在路由判断之前这个节点称为PREROUTING
。 - 如果发现ip是当前网卡
IP
,包就应该发给上面的传输层,这个节点叫做INPUT
。 - 如果发现
IP
不是当前网卡的,就需要进行转发,这个节点就叫FORWARD
。 - 如果ip是当前网卡的
IP
,则发送到上层处理。处理完一般会返回一个结果,把处理结果发出去,这个节点称为OUTPUT
。 - 无论是
FORWARD
和OUTPUT
,都是在路由判断之后发生的,最有一个节点是POSTROUTING
。
iptables
模块
在 Linux
内核中,有一个框架叫 Netfilter
。可以在上面的几个节点放一个hook函数,这些函数可以对数据包进行干预。如果接受就是 ACCEPT
;如果需要过滤掉就是 DROP
;如果需要发送给用户态进程处理,就是 QUEUE
。
iptables
就是实现了 Netfilter
框架,在上面五个节点上都放了hook函数,按照功能可以分为:
- conntrack: 连接跟踪
- filter: 数据包过滤
- nat: 网络地址转换
- mangle: 数据包修改
iptables
在用户态,有一个客户端程序 iptables
,用命令行来干预内核的规则,内核的功能对于 iptables
来说,就是表和链的概念。
表
- raw
- mangle
- nat
- filter
优先级: raw
> mangle
> nat
> filter
raw 不常用,主要功能都在剩下的几个表里面
filter
表
处理过滤功能
INPUT
链: 过滤所有目标地址是本机的数据包FORWARD
链: 过滤所有路过本机的数据包OUTPUT
链: 过滤所有由本机产生的数据包
nat
表
PREROUTING
链: 可以在数据包到达防火墙之前改变目标地址(DNT)OUTPUT
链: 可以改变本地产生的数据包的目标地址POSTROUTING
链: 在数据包离开防火墙时改变数据包的源地址(SNAT)
mangle
表
PREROUTING
链INPUT
链FORWARD
链OUTPUT
链POSTROUTING
链
raw
表
PREROUTING
链OUTPUT
链
将 iptables
的表和链整合起来就形成了下面的图和过程。
Kubernetes
里的 Service
** Service
是由 kube-proxy
组件加上 iptables
来共同实现的**
iptables
模式
当我们创建的 service
提交到 Kubernetes
的时候,kube-proxy
就可以通过 Service
的 Informer
感知到 Service
对象添加。从而对这个事件进行响应,它会在宿主机上创建一条 iptables
规则。可以使用 iptables-save
看见这样一条规则:
1 | -A KUBE-SERVICES -d 10.27.248.11/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3 |
凡是目的地址是 10.27.248.11
,端口号是80的包,都使用 KUBE-SVC-NWV5X2332I4OT4T3
iptables
链处理,而这个 10.27.248.11
这个就是 Service
的 clusterIP
,在查看 KUBE-SVC-NWV5X2332I4OT4T3
规则,实际上是一组规则集合:
1 | -A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ |
这实际上是一组随机模式(–mode random)的 iptables
链,而 KUBE-SEP-(hash)
链指向的最终地址就是代理的三个 Pod
(就是 Endpoint
)。所以这一组规则就是 Service
实现负载均衡的位置。查看上述三条链明细,就能理解 Service
转发的具体原理:
1 | -A KUBE-SEP-57KPRZ3JQVENLNBR -s 10.28.1.123/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000 |
这三条链其实就是三条 DNAT
规则。iptables
对流入的 IP
包还设置了一个标志(--set-xmark
),在 PREROUTING
检查之前将流入 IP
包的目的地址和端口改成 --to-destination
所指定的新的地址和端口
IPVS
模式
基于 iptables
的 Service
实现,都是制约 Kubernetes
项目承载更多量级 Pod
的主要障碍,IPVS
并不需要在主机上为每个 Pod
设置 iptables
规则,而是把这些规则放到了内核态,从而极大地降低了维护这些规则的代价。IPVS
模式的工作原理,和 iptables
类似。当创建了 Service
后,kube-proxy
首先会在宿主机上创建一个虚拟网卡 kube-ipvs0
。并为他分配 Service VIP
作为 IP
地址:
1 | ip addr |
然后 kube-proxy
就会通过 Linux
的 IPVS
模块,为这个IP地址设置三个 IPVS
虚拟主机,可以通过 ipvsadm
查看:
1 | ipvsadm -ln |
这三个 IPVS
虚拟主机的 IP
地址和端口对应的就是被代理的 Pod
,之间使用轮询模式(rr)来作为负载均衡。
IPVS
模块只负责负载均衡和代理功能,而一个完整的 Service
流程正常工作需要的包过滤,SNAT
,DNAT
等操作,还是要靠 iptables
来实现。只不过这些辅助性的 iptables
规则数量有限,也不会随着 Pod
数量增加而增加
查看 iptables
nat
表
1 | -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES |
倒数第 3
条规则表示来源 IP
不是 10.27.248.0/22
,则进入 KUBE-MARK-MASQ
,打上 0x4000
标。倒数第 5
条规则就是在 POSTROUTING
的时候做 SNAT
,如果看到有 0x4000
这个标志的,就修改来源 IP
(MASQUERADE
)。
SNAT
需要指定一个或多个 IP
,MASQUERADE
不需要指定,直接获取网卡的 IP
作为来源 IP
问题和解决思路
Service
没办法通过 DNS
访问
区分到底是 Service
本身的配置问题还是集群的 DNS
除了问题,检查的有效方式就是 Kubernetes
自己的 master
节点的 Service
DNS
是否正常:
1 | 在pod里面执行 |
如果执行有问题,那么就应该检查 kube-dns
的运行状态和日志。否则的话就应该检查 Service
定义是不是有问题。
Service
没办法通过 ClusterIP
访问
首先查看时候有 Endpoint
(kubectl get endpoint xxxx
),如果 Pod
的 readniessProbe
没有通过,也不会出现在 Endpoint
列表里面。如果 Endpoint
正常,就需要确认一下 kube-proxy
是否正确运行。如果 kube-proxy
一切正常,就应该查看宿主机的 iptables
。
iptables
模式的话检查:
KUBE-SERVICE
或者KUBE-NODEPORTS
规则对应的入口链,这个规则应该和VIP
和Service
端口一一对应KUBE-SEP-(hash)
规则对应的DNAT
链,和Endpoint
一一对应KUBE-SVC-(hash)
规则对应负载均衡链,这些规则的数目应该和Endpoint
数目对应- 如果是
NodePort
模式的话,还有POSTROUTING
的SNAT
链
Pod
没办法通过 Service
访问自己
这往往就是因为 kubelet
的 hairpin-mode
没有正确被设置(Haripin Mode 发夹模式,在默认情况下,网桥设备是不允许一个数据包从一个端口进来,再从这个端口发出去,开启这个模式从而取消这个限制)
调试技巧
使用 nsenter
来进入 Pod
容器
1 | function e() { |
在需要调试的 Pod
的宿主机上先执行这个命令,然后使用 e pod-name namespaces
来进入 Pod
进行调试
真实案例复盘
现象
在集群内任意一个节点上访问 Service
的 cluster ip
,如果这个 Service
的 Pod
运行在当前节点,则能访问成功,否则访问不成功
分析
在运行有 Pod
的宿主机上使用 tcpdump
抓 cni0
的包(cni0
和 eth0
做了桥接),发现包正常请求,正常返回
1 | tcpdump -nn -i cni0 tcp and host 10.28.248.11 |
分析:服务端是正常处理了请求的,应该是在返回的时候包被丢弃了(因为没有返回结果)。通过上面的 IPVS
模式的介绍,查看 iptables
的配置,发现了问题:
1 | pod ClusterCIDR: 10.28.0.0/16 |
而 iptables
处理的是非 10.28.0.0/16
的包才进行 SNAT
,所以导致包回不去,被丢弃了。
解决办法
- 修改
ServiceCIDR
为另外一个网段(推荐) - 每个宿主机上手动添加一条规则:
iptables -t nat -A POSTROUTING -s 10.28.0.0/16 -j SNAT --to-source 10.28.252.241(node ip)