写点什么

K8S 探索之 Service+Flannel 本机及跨主机网络访问原理详解

作者:
  • 2022 年 7 月 08 日
  • 本文字数:9174 字

    阅读完需:约 30 分钟

简介

在上篇中,我们部署了我们的应用,但我们访问是直接在应用所在的容器,使用 IP+Port 的方式直接访问的,style 不够 k8s,本篇文章我们将使用 service 和跨主机访问

内容概览

目前我们的应用大致如下:



我们的应用目前是部署在工作节点 1 上面,如上篇所示,我们可以在工作节点 1 上面直接使用应用容器的 ip+port 进行访问


➜  ~ curl http://10.244.1.8:9000/app/versionCheck\?version\=1{"data":{"downloadUrl":null,"updateMsg":null,"latest":true},"code":200,"msg":null}#
复制代码


但上面有个问题,每次重启,我们的应用 ip 是会变化的,也就是不固定,这样对我们访问会造成麻烦,我们不可能每次重启都去替换我们的访问 ip


针对这种情况,k8s 提供的解决方案是:Service


Service 有一个固定的集群内访问 IP,能够感知其对应的 pod 的 ip 变化,自动对应上


Service 解决了 Pod 重启后的 IP 变化问题,但还有一个问题是跨主机访问问题:pod 重启后,不一定在原来的 node 节点上,这个时候对我们访问也会带来麻烦,需要访问节点是能跨节点访问


针对跨主机访问问题,目前有很多解决方案,比如:Flannel,本篇也是基于 Flannel 进行探索的


整个如下图所示:



在工作节点和主节点上,我们都能通过 service 的集群 IP 进行访问,如下:


➜  dashboard curl http://10.103.5.88:9000/app/versionCheck\?version\=1{"data":{"downloadUrl":null,"updateMsg":null,"latest":true},"code":200,"msg":null}
复制代码


目前的概况就如上所示,接下来的我们就开始探索其中的原理,主要是两方面:


  • 1.在工作节点中,如何通过 service 访问到具体的应用

  • 2.主节点上没有部署有应用,是如何通过 service 访问到具体应用的

准备工作

本篇文章基于下面的文章:



本篇中,将应用重新部署了下,将网络插件 contained 换成了 flanned,具体参考文章:[kubernetes] 跨云厂商使用公网IP搭建k8s v1.20.9集群


首先是参考链接配置 eth0:1 网卡


其他的也不复杂,我们先将之前部署的 k8s 卸载,然后初始化 k8s 集群即可


service 的生成也比较简单,在 Dashboard 中部署的我们的应用时,设置 service 即可,如下图所示:



部署应用的时候选择 service 为 Internal,即只集群内访问,不绑定主机节点端口,端口就填我们的应用对应端口接口:第一个端口是 service 的监听端口,第二个端口是 pod 应用的监听端口

本机内 service 访问原理解析

我们在工作节点 1-IP:106.55.227.160 上,使用 service 的方式访问应用:


➜ curl http://10.103.5.88:9000/app/versionCheck\?version\=1{"data":{"downloadUrl":null,"updateMsg":null,"latest":true},"code":200,"msg":null}
复制代码


通过查阅资料,其原理是使用 Iptables,具体如下:


我们首先看看 10.103.5.88 这个 ip 相关的 iptables 规则配置


➜  ~ iptables -S -t nat |grep 10.103.5.88-A KUBE-SERVICES -d 10.103.5.88/32 -p tcp -m comment --comment "xiuxian/auth-java:tcp-9000-9000-c8kgf cluster IP" -m tcp --dport 9000 -j KUBE-SVC-ATUXI35NVHBYDGFQ
复制代码


如上所示:-d 10.103.5.88/32 + --dport 9000 :表示将访问 10.103.5.88+9000 端口的请求,转到链路规则 KUBE-SVC-ATUXI35NVHBYDGFQ


所以我们接着看看 KUBE-SVC-ATUXI35NVHBYDGFQ


➜  ~ iptables -S -t nat |grep KUBE-SVC-ATUXI35NVHBYDGFQ-A KUBE-SVC-ATUXI35NVHBYDGFQ -m comment --comment "xiuxian/auth-java:tcp-9000-9000-c8kgf" -j KUBE-SEP-7ETKSBKARXRYVX3G
复制代码


如上所示,这个又将这个请求转到了:KUBE-SEP-7ETKSBKARXRYVX3G


我们接着看 KUBE-SEP-7ETKSBKARXRYVX3G


➜  ~ iptables -S -t nat |grep KUBE-SEP-7ETKSBKARXRYVX3G-A KUBE-SEP-7ETKSBKARXRYVX3G -p tcp -m comment --comment "xiuxian/auth-java:tcp-9000-9000-c8kgf" -m tcp -j DNAT --to-destination 10.244.1.8:9000
复制代码


如上所示,上面就行了目标地址转换:DNAT --to-destination 10.244.1.8:9000


将请求转到了我们的 pod 应用中


上面就是本机内应用访问的原理,就是通过配置 iptables 来达到目的,这些规则都是 kube-proxy 进行配置的


详细的细节可参考:《Kubernetes 网络权威指南:基础、原理与实践》--4.1 部分,这是本好书,网络出问题的时候,博主翻了这本书很多遍

service 跨主机访问原理解析

service 本机访问是通过 iptables,但如果是跨主机访问就麻烦了


在主节点 IP:121.4.190.84 上,使用命令,我们也是看到其配置了相同的 iptables 规则:


➜  ~ iptables -S -t nat |grep KUBE-SEP-7ETKSBKARXRYVX3G-A KUBE-SEP-7ETKSBKARXRYVX3G -p tcp -m comment --comment "xiuxian/auth-java:tcp-9000-9000-c8kgf" -m tcp -j DNAT --to-destination 10.244.1.8:9000
复制代码


也是去访问了 10.244.1.8,但是,在主节点上,是没有这个 pod 的


并且由于这个 ip:10.244.1.8 是个内网 ip,在不进行任何配置的情况下,在主节点主机上是没有版本进行访问的


针对这个问题,本文用的是 flannel 的 vxlan 方式进行解决(尝试过 calico,配置有点复杂,有些解决模式在公网 ip 集群有点麻烦,经过不断的尝试,终于使用 flannel 成功了),下面是整个请求的链路图,后面进行详细的解释:


上图就是一个完整的请求及响应链路,下面我们一步一步的具体解释:


1.service--10.103.5.88:9000 iptables 转换


在主节点上,和工作节点一样的方式,通过命令查看 iptables 规则,我们能得到请求最终转发到了 10.244.1.8:9000


-A KUBE-SERVICES -d 10.103.5.88/32 -p tcp -m comment --comment "xiuxian/auth-java:tcp-9000-9000-c8kgf cluster IP" -m tcp --dport 9000 -j KUBE-SVC-ATUXI35NVHBYDGFQ-A KUBE-SVC-ATUXI35NVHBYDGFQ -m comment --comment "xiuxian/auth-java:tcp-9000-9000-c8kgf" -j KUBE-SEP-7ETKSBKARXRYVX3G-A KUBE-SEP-7ETKSBKARXRYVX3G -p tcp -m comment --comment "xiuxian/auth-java:tcp-9000-9000-c8kgf" -m tcp -j DNAT --to-destination 10.244.1.8:9000
复制代码


2.请求路由到 flannel 网络接口


安装 flannel 网络插件后,就会生成其网络接口,用于跨主机的解包和封包,如下的 flannel.1


➜  ifconfigflannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450        inet 10.244.0.0  netmask 255.255.255.255  broadcast 10.244.0.0        inet6 fe80::f4f2:b0ff:fefa:7573  prefixlen 64  scopeid 0x20<link>        ether f6:f2:b0:fa:75:73  txqueuelen 0  (Ethernet)        RX packets 867  bytes 99948 (97.6 KiB)        RX errors 0  dropped 0  overruns 0  frame 0        TX packets 1508  bytes 212521 (207.5 KiB)        TX errors 0  dropped 8 overruns 0  carrier 0  collisions 0
复制代码


使用命令查看路由有下面的规则:


➜  ip route10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
复制代码


即将 10.244.1.0/24 的请求交给 flannel.1 处理


我们通过抓包看看:


➜  tcpdump -i flannel.1 -e -nn |grep 10.244.1.8tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on flannel.1, link-type EN10MB (Ethernet), capture size 262144 bytes10:36:22.935837 f6:f2:b0:fa:75:73 > da:a9:48:cb:15:3b, ethertype IPv4 (0x0800), length 74: 10.244.0.0.8741 > 10.244.1.8.9000: Flags [S], seq 1220826812, win 29200, options [mss 1460,sackOK,TS val 2079738924 ecr 0,nop,wscale 7], length 010:36:22.968258 da:a9:48:cb:15:3b > f6:f2:b0:fa:75:73, ethertype IPv4 (0x0800), length 74: 10.244.1.8.9000 > 10.244.0.0.8741: Flags [S.], seq 4079516162, ack 1220826813, win 27960, options [mss 1410,sackOK,TS val 1027012415 ecr 2079738924,nop,wscale 7], length 010:36:22.968294 f6:f2:b0:fa:75:73 > da:a9:48:cb:15:3b, ethertype IPv4 (0x0800), length 66: 10.244.0.0.8741 > 10.244.1.8.9000: Flags [.], ack 1, win 229, options [nop,nop,TS val 2079738957 ecr 1027012415], length 010:36:22.968387 f6:f2:b0:fa:75:73 > da:a9:48:cb:15:3b, ethertype IPv4 (0x0800), length 172: 10.244.0.0.8741 > 10.244.1.8.9000: Flags [P.], seq 1:107, ack 1, win 229, options [nop,nop,TS val 2079738957 ecr 1027012415], length 10610:36:23.002585 da:a9:48:cb:15:3b > f6:f2:b0:fa:75:73, ethertype IPv4 (0x0800), length 66: 10.244.1.8.9000 > 10.244.0.0.8741: Flags [.], ack 107, win 219, options [nop,nop,TS val 1027012447 ecr 2079738957], length 010:36:23.059860 da:a9:48:cb:15:3b > f6:f2:b0:fa:75:73, ethertype IPv4 (0x0800), length 268: 10.244.1.8.9000 > 10.244.0.0.8741: Flags [P.], seq 1:203, ack 107, win 219, options [nop,nop,TS val 1027012505 ecr 2079738957], length 20210:36:23.059897 f6:f2:b0:fa:75:73 > da:a9:48:cb:15:3b, ethertype IPv4 (0x0800), length 66: 10.244.0.0.8741 > 10.244.1.8.9000: Flags [.], ack 203, win 237, options [nop,nop,TS val 2079739048 ecr 1027012505], length 010:36:23.060196 da:a9:48:cb:15:3b > f6:f2:b0:fa:75:73, ethertype IPv4 (0x0800), length 71: 10.244.1.8.9000 > 10.244.0.0.8741: Flags [P.], seq 203:208, ack 107, win 219, options [nop,nop,TS val 1027012505 ecr 2079738957], length 510:36:23.060206 f6:f2:b0:fa:75:73 > da:a9:48:cb:15:3b, ethertype IPv4 (0x0800), length 66: 10.244.0.0.8741 > 10.244.1.8.9000: Flags [.], ack 208, win 237, options [nop,nop,TS val 2079739049 ecr 1027012505], length 010:36:23.060283 f6:f2:b0:fa:75:73 > da:a9:48:cb:15:3b, ethertype IPv4 (0x0800), length 66: 10.244.0.0.8741 > 10.244.1.8.9000: Flags [F.], seq 107, ack 208, win 237, options [nop,nop,TS val 2079739049 ecr 1027012505], length 010:36:23.094810 da:a9:48:cb:15:3b > f6:f2:b0:fa:75:73, ethertype IPv4 (0x0800), length 66: 10.244.1.8.9000 > 10.244.0.0.8741: Flags [F.], seq 208, ack 108, win 219, options [nop,nop,TS val 1027012540 ecr 2079739049], length 0^C24 packets captured24 packets received by filter0 packets dropped by kernel
复制代码


关键点是这个:10.244.0.0.8741 > 10.244.1.8.9000:


在上面的将请求路由到 flannel.1 后,flannel 进行封包处理


10.244.0.0 是 flannel.1 的 ip 地址,如上面使用 ifconfig 命令看到的


10.244.1.8.9000 就是目标 ip 了


3,4.封包后,请求发送到工作节点


我们监听下物理网卡 eth0


➜  tcpdump -i eth0 -e -nn |grep -E '10.244.1.8|106.55.227.160.8472'10:49:31.632150 52:54:00:f4:44:15 > fe:ee:b5:81:6a:26, ethertype IPv4 (0x0800), length 116: 172.17.16.14.51945 > 106.55.227.160.8472: OTV, flags [I] (0x08), overlay 0, instance 1
复制代码


在我们发起情况后,看到出现上面的包:172.17.16.14.51945 > 106.55.227.160.8472


请求由我们的物理网卡 eth0(ip 就是 172.17.16.14),发送到工作节点(公网 ip:106.55.227.160)的 8472 端口,8472 端口是 flannel 预定的接口


从上面可以看到,如果出现网络不通的情况,检查下 8741 端口,我们需要开发主机的 8741 端口


从上面 eth0 我们没有看到我们的目标地址 10.244.1.8 的痕迹,那是怎么对应起来的,不急,我们接着往下看


5,6flannel 应用受到请求包,解包后,请求 pod


看有的资料是配置的端口转发到 flannel,但没找到,更像是 flannel 的应用监听在 8472 端口,具体方式也不像监听,为了便于理解,暂时视为监听在 8472 端口吧,如下:


# 每个node节点都有kube-flannel  ➜  ~ crictl psCONTAINER           IMAGE               CREATED             STATE               NAME                        ATTEMPT             POD ID24bfdfc099e0a       8522d622299ca       23 hours ago        Running             kube-flannel                0                   4bd04cec2eeb2# 下面是相关的进程和端口情况,虽然PID为-,但在iptables没有找到转发规则,暂时看成flannel应用应该是监听在8472端口➜  ~ ps aux|grep flannelroot     22561  0.0  0.5 1269468 23136 ?       Ssl  Jul07   0:47 /opt/bin/flanneld --ip-masq --kube-subnet-mgr --public-ip=106.55.227.160 --iface=eth0➜  ~ netstat -ulnp|grep 8472udp        0      0 0.0.0.0:8472            0.0.0.0:*                           -
复制代码


抓下工作节点物理网卡的包,我们只抓 8472 端口的接口,因为上面也看到了主节点物理网卡是发到了工作节点的 8472 端口上的


# eth0物理网卡的➜  ~ tcpdump -i eth0 -e -nn port 847211:20:29.533872 fe:ee:b8:5a:5a:63 > 52:54:00:4f:72:ca, ethertype IPv4 (0x0800), length 124: 121.4.190.84.59814 > 10.0.20.11.8472: OTV, flags [I] (0x08), overlay 0, instance 1f6:f2:b0:fa:75:73 > da:a9:48:cb:15:3b, ethertype IPv4 (0x0800), length 74: 10.244.0.0.14571 > 10.244.1.8.9000: Flags [S], seq 2501773392, win 29200, options [mss 1460,sackOK,TS val 2082385506 ecr 0,nop,wscale 7], length 0
# flannel的➜ ~ tcpdump -i flannel.1 -e -nntcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on flannel.1, link-type EN10MB (Ethernet), capture size 262144 bytes11:26:46.822551 f6:f2:b0:fa:75:73 > da:a9:48:cb:15:3b, ethertype IPv4 (0x0800), length 74: 10.244.0.0.5326 > 10.244.1.8.9000: Flags [S], seq 2320302970, win 29200, options [mss 1460,sackOK,TS val 2082762796 ecr 0,nop,wscale 7], length 0
复制代码


在我们发送请求时看到:121.4.190.84.59814 > 10.0.20.11.8472


121.4.190.84 是我们的主节点公网 IP


10.0.20.11 是我们的工作节点的内网 ip,这个应该是腾讯云配置的从公网 ip 转内网 IP


eth0 的 10.244.0.0.14571 > 10.244.1.8.9000 和 flannel 的 10.244.0.0.5326 > 10.244.1.8.9000,应该是 flannel 解包后发起请求


10.224.0.0 是路由网卡,如下:


➜  ~ ip route10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink
复制代码


具体的解包,这个东西目前博主还不太清楚,等后面搞清楚了再来说说


目前是经过 flannel 解包后,得到了目标 pod,然后经过路由,请求到了 pod 中


到此请求链路就完成了


7,8,9 响应返回


在 flannel 的包中:


➜  ~ tcpdump -i flannel.1 -e -nn11:26:46.822637 da:a9:48:cb:15:3b > f6:f2:b0:fa:75:73, ethertype IPv4 (0x0800), length 74: 10.244.1.8.9000 > 10.244.0.0.5326: Flags [S.], seq 3013507867, ack 2320302971, win 27960, options [mss 1410,sackOK,TS val 1030036288 ecr 2082762796,nop,wscale 7], length 0
复制代码


可以看到我们的应用处理完成后,将请求回到了 flannel:10.244.1.8.9000 > 10.244.0.0.5326


工作节点的物理网卡中,在我们发起请求的时候,向我们的主节点的 8472 端口返回了响应


➜  ~ tcpdump -i eth0 -e -nn |grep 121.4.190.84.8472tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes11:43:33.880431 52:54:00:4f:72:ca > fe:ee:b8:5a:5a:63, ethertype IPv4 (0x0800), length 124: 10.0.20.11.40470 > 121.4.190.84.8472: OTV, flags [I] (0x08), overlay 0, instance 1
复制代码


10,11 主节点响应接收


下面是主节点物理网卡的


➜  tcpdump -i eth0 -e -nn port 847211:45:44.170343 fe:ee:b5:81:6a:26 > 52:54:00:f4:44:15, ethertype IPv4 (0x0800), length 116: 106.55.227.160.48943 > 172.17.16.14.8472: OTV, flags [I] (0x08), overlay 0, instance 1da:a9:48:cb:15:3b > f6:f2:b0:fa:75:73, ethertype IPv4 (0x0800), length 66: 10.244.1.8.9000 > 10.244.0.0.53794: Flags [.], ack 107, win 219, options [nop,nop,TS val 1031173621 ecr 2083900128], length 0
复制代码


106.55.227.160.48943 > 172.17.16.14.8472:工作节点的公网 ip,请求到主节点的内网 ip 的 8472 端口


下面是主节点的 flannel 的


➜ tcpdump -i flannel.1 -e -nn11:49:13.675481 da:a9:48:cb:15:3b > f6:f2:b0:fa:75:73, ethertype IPv4 (0x0800), length 74: 10.244.1.8.9000 > 10.244.0.0.54626: Flags [S.], seq 3933634312, ack 2648191468, win 27960, options [mss 1410,sackOK,TS val 1031383126 ecr 2084109635,nop,wscale 7], length 0
复制代码


响应的返回其实就是请求的逆向,理解了请求的链路,响应的链路大同小异:


请求:发起者的本地 ip+随机端口 --> 目标 IP+目标端口


响应:目标 IP+目标端口 --> 发起者的本地 ip+随机端口

总结

写本篇文章,耗费了很多精力,但也收获很大,感觉经过这次的问题解决,自己对网络部分又加深了理解


查询资料中,资料大部分是局域网进行集群部署的,但使用公网 ip 部署,有一些差异,有一些跨主机解决方案是用不了的,比如:


  • Flannel 的 Host Gateway

  • Calico 的 BGP


目前尝试下来,vxlan 还是可以,但其关键的一步就是公网 ip 网卡的设置


还有就是相关端口的开放,比如这里的 8472 端口


知道整个链路,后序排查问题就有思路,博主也希望这篇详细的链路分析能给各位一些问题排查上的帮助


这部分写了也四五天了,推荐两本好书,如果各位遇到了问题,可以去里面多看看,看看能不能给点启发


  • 《Kubernetes 网络权威指南:基础、原理与实践》:原理和实践都有,贼好,这周遇到问题就翻......

  • 《网络是怎样连接的》:基础原理的,如果局域网内的机器如何使用使用同一个公网 ip 进行网络访问,如果不了解上面的工作节点的公网 ip,请求到主节点的内网 ip 的 8472 端口,得看看这个


两本书在微信读书上面都有,不得不说,现在这个时代获取知识真方便,真好


后面有一些遇到问题的记录,如果遇到了相似的问题,也可以看看

问题记录与解决方法

重新安装 CNI 插件后,node 节点一直 notready

查看 kubelet 日志,一直报错如下:


m runtime service failed" err="rpc error: code = Unknown desc = failed to destroy network for sandbox \"f8b273d0b213af9a143f5729da56365efaf8fb322a1299ce2cfb8eef2cbd03c2\": cni plugin not initialized" podSandboxID="f8b273d0b213af9a14dbox before removing" err="rpc error: code = Unknown desc = failed to destroy network for sandbox \"f8b273d0b213af9a143f5729da56365efaf8fb322a1299ce2cfb8eef2cbd03c2\": cni plugin not initialized" sandboxID="f8b273d0b213af9a143f5729dm runtime service failed" err="rpc error: code = Unknown desc = failed to destroy network for sandbox \"e6a73ececa384f6946f9d764a093f639901fc647d84cd32f0175505a72b50f47\": cni plugin not initialized" podSandboxID="e6a73ececa384f6946dbox before removing" err="rpc error: code = Unknown desc = failed to destroy network for sandbox \"e6a73ececa384f6946f9d764a093f639901fc647d84cd32f0175505a72b50f47\": cni plugin not initialized" sandboxID="e6a73ececa384f6946f9d764ak not ready" networkReady="NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized"k not ready" networkReady="NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized"k not ready" networkReady="NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized"k not ready" networkReady="NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized"k not ready" networkReady="NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized"k not ready" networkReady="NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized"k not ready" networkReady="NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized"k not ready" networkReady="NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized"k not ready" networkReady="NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized"k not ready" networkReady="NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized"k not ready" networkReady="NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized"
复制代码


解决办法,重新启动下 contained:


systemctl daemon-reloadsystemctl restart containerd
复制代码

Readiness probe failed: calico/node is not ready: BIRD is not ready: Error querying BIRD: unable to connect to BIRDv4 socket: dial unix /var/run/calico/bird.ctl: connect: connection refused

确实一些组件导致的,下载后,重启 kubelet 即可


# 如果服务器内下载不了,访问这个页面去下最新的:https://github.com/containernetworking/plugins/releases,后面手动上传后解压wget https://github.com/containernetworking/plugins/releases/download/v1.0.1/cni-plugins-linux-arm64-v1.0.1.tgztar axvf ./cni-plugins-linux-arm64-v1.0.1.tgz  -C /opt/cni/bin/
systemctl restart kublet
复制代码

参考链接

发布于: 刚刚阅读数: 4
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
K8S探索之Service+Flannel本机及跨主机网络访问原理详解_网络_萧_InfoQ写作社区