笔者近期遇到了Kubernetes网络问题,在处理过程中加深了对Linux网络协议栈的理解。
在此记录分享。
Kubernetes环境:双网卡的实体机 * 2
Master节点双网卡:192.168.33.67/24【enp26s0网卡】 71.11.33.67/21【eno1网卡】
Work节点双网卡: 192.168.33.208/24【eno5网卡】 71.11.33.208/21【eno1网卡】
kube-apiserver组件: hostnetwork方式运行在Master节点,监听196.168.33.67:5444,且拥有svc-clusterip地址 10.247.0.1:443。
在部署calico时(crosssubnet模式),Master节点上的calico-node正常,Worker节点上的calico-node报错。
在两个节点上分别进行telnet,Master节点正常,Worker节点不通。
但奇怪的是,svc-iptables规则正常,从Worker节点直接请求apiserver-pod-ip 竟然是通的。
.
经过telnet,已经定位是网络不通,于是进行抓包。
抓包结果显示发包成功,但是没有回包。
5.1 分析iptables日志
从抓包中可以看到,源地址是71.11.33.208,而目的地址是192.168.33.67。很明显不在同一网段,现象比较奇怪。使用同网段的192.168.33.208岂不是逻辑上更通顺?
因为此问题是请求svc-clusterip(10.247.0.1),于是猜测和kubernetes-proxy的iptables规则有关。
日志内容如下:
从日志中可以看到:请求经过DNAT,最终从eno5网卡发出,这是符合预期的。
可为什么这个网络包从最开始就被赋予了71.11.33.208这个源地址IP?
主机拥有双网卡 拥有两个IP地址,源IP为什么是71.11.33.208 而不是 192.168.33.208?
5.2 分析Linux网络协议栈处理过程
Linux网络协议栈在网上可以查到很多资料,下图是笔者搜到的信息。
结合网络协议栈和iptables日志,我们可以得出网络包的处理过程:
发请求,DST为10.247.0.1:443
到路由表,根据路由规则,此网络包从eno1发出。因为此时没有SRC-IP,把eno1的ip设置为网络包的SRC-IP,即71.11.33.208。
执行iptables规则,进行了DNAT,DST改为192.168.33.67:5444
再到路由表,匹配规则,此包从eno5发出。因为此时已经有SRC-IP了,不做修改。
网络包从eno5发出。
因此,看似奇怪的源IP竟然是正常的。
可是从路由层分析,依然很奇怪。
Worker节点上,"请求发出的网卡" 竟然不是 ”响应接收的网卡“。
5.3 Master节点添加路由
本着:网络数据包的发出网卡和响应接收网卡理论上应该是同一个。
来到master节点添加一条路由后,竟然通了!
6.1 在redhat官网中找到了答案
在Linux内核中,`rp_filter`(Reverse Path Filtering)功能可以防止中间人攻击和其他形式的IP欺骗攻击。
RPF查看数据包到达的接口是否与内核用于将数据包发送到该 IP 的接口相同。如果接口相同,则数据包已通过严格过滤测试,并正常处理。如果接口不同,则数据包将被丢弃,无需任何进一步处理。
此时已经得出异常现象的理论原因:。Worker节点的RPF功能拦截了响应网络数据包。
6.2 查看Worker主机的RPF设置,实践验证理论结果
删掉5.3中添加的路由,将Worker主机eno5网卡的RPF设置为0后,请求测试成功。
若主机有很多网卡需要修改,可以通过net.ipv4.conf.all.rp_filter进行"一键修改"。