前两篇文章中我们介绍了pod怎么和宿主机通信以及pod怎么访问外网,这两种通信是理解pod间通信的基础。
关于pod间的相互访问,这里还需要细化一下。回想一下pod在k8s节点中的分布,两个pod可能分布在同一台宿主机上,也可能分布在不同宿主机上。这两种不同的拓扑结构,其组网模型也不一样。
本文先介绍第一种,同宿主内pod间如何访问
同宿主内pod间如何访问
关于同主机下的pod如何访问,有多种技术可以实现,因为这个pod发送的数据包,无论如何流转,始终不会出宿主机。
正如前文所说,要分析网络,总是离不开网络设备和路由表。
veth pair
既然之前文章我们说过,veth pair可以实现跨network namespace通信,最简单的我们可以用veth pair来做这件事情。
回想第一篇文章,我们的veth pair一端在pod的network namespace,另外一端在宿主机上,也就是root network namespace。如果我们将另外一端放到要通信pod的network namespace中不就可以了吗?
没错,这种模型完全可以。并且也很简单,但是要维护它却很困难。
因为这种模型类似一个点对点的结构,它要求pod两两之间都需要有veth pair。
试想,如果同一个主机内存在N个Pod。那么每个pod ns中都会存在N-1个veth设备,以及N-1条路由规则,整个主机上存在N*N个veth pair。
那你可能会说了,node节点默认可运行的pod数目上限是110个,也不是天文数字,似乎也能接受
确实,但再试想一个场景,当一个pod的IP发生变化的时候,你需要做什么?这时候可能就需要修改其他所有pod的路由规则。
这样一想,似乎就不太好接受了,veth pair的数量和路由表的维护是一个麻烦事。
那如果有个角色,能够将这些veth pair 束口呢?
bridge
接着刚才那个问题,熟悉linux设备的同学可能马上会想到,Liunx网桥不就能干这件事情吗?
确实可以,Linux bridge可以让这些网络设备作为一个端口连接到网桥上来,如下图所示:
在这里,Linux bridge充当了一个二层交换机的角色。负责将来自不同veth设备的流量进行交换和转发。
网桥的定义:网桥是一个工作在数据链路层的网络设备,可以连接多个局域网段,它能够根据MAC地址来转发帧,使得多个网络段上的设备能够像在同一个网络中一样进行通信。
Linux网桥:在Linux系统中,网桥是通过软件模拟实现的。它允许系统管理员创建一个逻辑上的交换机,可以绑定多个物理或虚拟网络接口。
这个模型很简单,它解决了第一种模型下veth pair数量多和需要维护路由的问题。
还是以同一个主机下N个Pod为例,在这个模型下,每个Pod的network namespace下只需要存在一个veth设备,整个主机也只需要N个veth pair。同时pod间的路由规则不需要任何路由表来记录。
没了路由表,Linux bridge怎么知道发送给谁呢?
答案是广播和转发表
广播的含义是当Linux bridge一个端口收到报文,而转发表中没有目标地址的MAC时,就会广播所有连接到bridge上的网络接口。
转发表的含义是bridge会记录所有经过的报文的MAC地址,它是自学习的。
有了linux bridge,大大减少了管理的复杂度,下面通过一个小实验,感受一个pod间如何通信。
实验
本实验用到的命令在Kubernets(k8s) 网络原理一:Pod与宿主机通信文章中有简单介绍
如无特殊说明,我们实验都用network namespace代指pod
创建pod-1和pod-2
# ip netns add pod-1
# ip netns add pod-2
创建eth0和veth1,并将eth0加入pod-1 ns
# ip link add eth0 type veth peer name veth1
# ip link set eth0 netns pod-1
创建eth0和veth2,并将eth0加入pod-2 ns
# ip link add eth0 type veth peer name veth2
# ip link set eth0 netns pod-2
分别给pod-1和pod-2中的eth0配置ip
# ip netns exec pod-1 ip addr add 10.10.1.10/32 dev eth0
# ip netns exec pod-2 ip addr add 10.10.1.11/32 dev eth0
启动pod-1中veth pair,并添加路由,为什么添加路由请查看前一篇文章
# ip netns exec pod-1 ip link set eth0 up
# ip link set veth1 up
# ip netns exec pod-1 ip ro add default dev eth0
启动pod-2中veth pair,并添加路由
# ip netns exec pod-2 ip link set eth0 up
# ip link set veth2 up
# ip netns exec pod-2 ip ro add default dev eth0
创建网桥cbr0并启动,需要安装bridge-utils
# brctl addbr cbr0
# ip link set cbr0 up
将vteh1和veth2 添加进网桥
# brctl addif cbr0 veth1
# brctl addif cbr0 veth2
查看vteh1和veth2是否正确添加到网桥
# brctl show
bridge name bridge id STP enabled interfaces
cbr0 8000.d6821b81f256 no veth1
veth2
准备工作就绪,此时网络拓扑如下
我们尝试在pod-1中ping pod-2来模拟通信
# ip netns exec pod-1 ping -c 1 10.10.1.11
PING 10.10.1.11 (10.10.1.11) 56(84) bytes of data.
64 bytes from 10.10.1.11: icmp_seq=1 ttl=64 time=0.033 ms
--- 10.10.1.11 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.033/0.033/0.033/0.000 ms
一切顺利,可以ping通,我们抓包看看这个过程发生了什么
# ip netns exec pod-1 tcpdump -pne -i eth0
12:20:24.329781 12:35:c3:bb:69:bc > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 10.10.1.11 tell 10.10.1.10, length 28
12:20:24.329812 7e:55:17:b0:dc:bb > 12:35:c3:bb:69:bc, ethertype ARP (0x0806), length 42: Reply 10.10.1.11 is-at 7e:55:17:b0:dc:bb, length 28
12:20:24.329814 12:35:c3:bb:69:bc > 7e:55:17:b0:dc:bb, ethertype IPv4 (0x0800), length 98: 10.10.1.10 > 10.10.1.11: ICMP echo request, id 48389, seq 1, length 64
12:20:24.329827 7e:55:17:b0:dc:bb > 12:35:c3:bb:69:bc, ethertype IPv4 (0x0800), length 98: 10.10.1.11 > 10.10.1.10: ICMP echo reply, id 48389, seq 1, length 64
12:20:29.340282 7e:55:17:b0:dc:bb > 12:35:c3:bb:69:bc, ethertype ARP (0x0806), length 42: Request who-has 10.10.1.10 tell 10.10.1.11, length 28
12:20:29.340285 12:35:c3:bb:69:bc > 7e:55:17:b0:dc:bb, ethertype ARP (0x0806), length 42: Reply 10.10.1.10 is-at 12:35:c3:bb:69:bc, length 28
查看MAC地址与端口的映射关系
# brctl showmacs cbr0
port no mac addr is local? ageing timer
1 12:35:c3:bb:69:bc no 2.30
2 7e:55:17:b0:dc:bb no 2.30
1 d6:82:1b:81:f2:56 yes 0.00
2 fe:c5:af:3f:03:b6 yes 0.00
值得说明的是,is local为no表示不是这个MAC地址不在root network namespace,上面回显中d6:82:1b:81:f2:56和fe:c5:af:3f:03:b6是veth1和veth2的mac地址,他们的is local为yes是因为veth1和veth2在root namespace。同时还可以看到相同端口号对应的正好是一对veth pair。
因此我们可以根据这两个信息总结关键流程如下:
- pod-1发起ICMP请求,因为无pod-2的MAC地址,于是先发起ARP请求
- pod-1 eth0发起ARP请求,希望获取10.1.10.11的mac地址
- cbr0收到广播的ARP请求,在转发表中记录MAC地址和端口
- 此时crb0中不存在pod-2中eth0的MAC地址,于是向所有将广播转发到其所绑定的所有端口,除了源veth
- pod-2 发现ieth0的ip刚好匹配,于是响应ARP请求,告诉自己的MAC地址
- crb0收到pod-2的ARP响应,在转发表中记录MAC地址和端口
- pod-1 向pod-2发起ICMP请求
- pod-2 响应ICMP请求
实验过程非常简单,这个模型能工作的原理就是利用了Linux bridge在二层通信。
实验环境清理
执行以下命令清理上文配置的设备
# ip netns del pod-1
# ip netns del pod-2
# ip link set cbr0 down
# brctl delbr cbr0
小结
整个模型非常简单,完全是利用Linux Bridge的特性,没有什么好总结的,既然不总结,那就提几个问题
- veth1和veth2为什么不需要配置IP?
- bridge模型下主机内pod通信需要主机的eth0参与吗?
- 如果主机内pod通信不需要eth0参与,那什么时候需要它参与呢?
- cbr0网桥什么时候需要配置IP?
这些问题可能没有一个唯一的答案,但是能够触发我们思考。
直接点就是想说,要分析一种网络模型,你必须要知道这个网络下的各种网络设备作用。以及当网络诉求变化时,相关网络设备应该如何变化。
route table
bridge可以充当束口的角色,但是如果不想引入Linux bridge这个网络设备,又该如何处理呢?
我们知道一个主机打开ip_forward转发的时候,他自己就可以转发报文。
转发报文也就意味着从一个网络接口,转给另外一个网络接口,这个好理解,也就是说只要将veth pair的另一端连接到root network namesapce,让主机就能看到这个网络设备就可以做到。
问题在于第二个,如何转发?
话都到这了,肯定会想到路由表嘛
没错,在这个模型下,就是让主机承担这个转发的任务,同时将这些veth pair收拢到root network namesapce里面来。
通信模型如下。
在这个模型下,省去了网桥概念,通信模型更为简单,但是需要维护root network namespace中的route规则。不过好在这不会太多。
还是以同一个主机下N个Pod为例,在这个模型下,每个Pod的network namespace下只需要存在一个veth设备,整个主机也只需要N个veth pair,此外root network namespace中还需要有N条路由规则。
同理,我们也进行一个小实验来模拟
实验
建议先查看Kubernets(k8s) 网络原理一:Pod与宿主机通信,理解network namespace怎么和主机通信
老规矩,我们创建pod-1和pod-2 network namespace代表两个pod
以路由的方式配置nework namespace在上一篇文章中已经介绍了每一条命令的作用,因此这里只罗列命令
配置pod-1
# ip netns add pod-1
# ip link add eth0 type veth peer name veth1
# ip link set eth0 netns pod-1
# ip netns exec pod-1 ip addr add 10.10.1.10 dev eth0
# ip netns exec pod-1 ip link set dev eth0 up
# ip netns exec pod-1 ip route add 169.254.1.1 dev eth0
# ip netns exec pod-1 ip route add default via 169.254.1.1 dev eth0
# ip link set dev veth1 up
# echo 1 > /proc/sys/net/ipv4/conf/veth1/proxy_arp
# echo 1 > /proc/sys/net/ipv4/ip_forward
配置pod-2
# ip netns add pod-2
# ip link add eth0 type veth peer name veth2
# ip link set eth0 netns pod-2
# ip netns exec pod-2 ip addr add 10.10.1.11 dev eth0
# ip netns exec pod-2 ip link set dev eth0 up
# ip netns exec pod-2 ip route add 169.254.1.1 dev eth0
# ip netns exec pod-2 ip route add default via 169.254.1.1 dev eth0
# ip link set dev veth2 up
# echo 1 > /proc/sys/net/ipv4/conf/veth2/proxy_arp
# echo 1 > /proc/sys/net/ipv4/ip_forward
然后在主机上配置路由表
# ip route add 10.10.1.10 dev veth1 scope link
# ip route add 10.10.1.11 dev veth2 scope link
路由表的意思是说,所有发给10.10.1.10的报文都从veth1出去,同理,所有发给10.10.1.11的报文都从veth2 出去。
此时网络拓扑如下图所示:
此时我们尝试在pod-1中ping pod-2来模拟通信
# ip netns exec pod-1 ping -c 1 10.10.1.11
PING 10.10.1.11 (10.10.1.11) 56(84) bytes of data.
64 bytes from 10.10.1.11: icmp_seq=1 ttl=63 time=0.035 ms
--- 10.10.1.11 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.035/0.035/0.035/0.000 ms
没有问题,但是整个过程发生了什么呢,我们来抓包看一下
在pod-1中抓包
# ip netns exec pod-1 tcpdump -n -i eth0
09:53:29.297355 ARP, Request who-has 169.254.1.1 tell 10.10.1.10, length 28
09:53:29.382379 ARP, Reply 169.254.1.1 is-at be:7c:fa:0e:81:25, length 28
09:53:29.382386 IP 10.10.1.10 > 10.10.1.11: ICMP echo request, id 36618, seq 1, length 64
09:53:29.541810 IP 10.10.1.11 > 10.10.1.10: ICMP echo reply, id 36618, seq 1, length 64
这个过程其实我们已经在pod与宿主机通信文章中分析过了,这里简单梳理以下流程
- pod-1中执行ping命令,通过socket调用给到pod-1协议栈
- pod-1协议栈准备发起ICMP报文,查找路由表,获知从eth0出去,下一跳是169.254.1.1
- pod-1协议栈查找Arp表,没有169.254.1.1的MAC地址,于是先发起Arp请求
- veth1收到pod-1中eth0发来的Arp请求,因为配置了proxy_arp,于是响应自己的MAC地址
- pod-1协议栈收到响应,组装ICMP报文,发送给veth-1
- veth-1收到ICMP报文,交给自己的协议栈,即host协议栈
- host协议栈因开启了ip_forward,于是查找本地路由表,发现应该从veth2设备发送
- veth2和pod-2中eth0为veth pair关系,于是eth0收到pod-1发送的ICMP报文,交给pod-2协议栈
- pod-2协议栈处理报文,按照原路返回响应报文。
小结
路由规则关心的是IP,而不是MAC地址,换句话说当跨物理网络时,路由模式的好处就显示出来了。虽然我们一台主机内不会存在跨网段,但是在后面我们总会用到的。
此外还值得说的一点是使用bridge的方式,Linux bridge会根据目的地址进行广播,所有设备都能收到,而L3 路由的方式则会隔离广播,请求的目的地址是固定的,因此广播限定在指定的广播域中,其它设备不会收到广播。
总结
本文分析两种主机内的组网模型,他们都可以达到主机内Pod相互通信的目的。只是采用不同的技术去实现,这两种方式没有严格的好坏之分,只是适用的场景不一样。
例如fiannel和docker就是采用bridge这种方式,依赖于一个网桥cni0(docker0)
进行二层数据的转发。而calico就使用的L3路由的方式。
理解了主机内pod如何通信,在下一篇文章中,我们将分析整个容器组网最复杂的一部分,不同主机上的pod如何通信。