![Kubernetes网络权威指南:基础、原理与实践](https://wfqqreader-1252317822.image.myqcloud.com/cover/281/27741281/b_27741281.jpg)
1.3 连接你我他:Linux bridge
两个network namespace可以通过veth pair连接,但要做到两个以上network namespace相互连接,veth pair就显得捉襟见肘了。这就轮到本节的主角Linux bridge出场了。
我们在计算机网络课本上学的网桥正如其字面含义所描述的,有“牵线搭桥”之意,用于连接两个不同的局域网,是网线的延伸。网桥是二层网络设备,两个端口分别有一条独立的交换信道,不共享一条背板总线,可隔离冲突域。网桥比集线器(hub)性能更好,集线器上各端口都是共享同一条背板总线的。后来,网桥被具有更多端口、可隔离冲突域的交换机(switch)所取代。
顾名思义,Linux bridge就是Linux系统中的网桥,但是Linux bridge的行为更像是一台虚拟的网络交换机,任意的真实物理设备(例如eth0)和虚拟设备(例如,前面讲到的veth pair和后面即将介绍的tap设备)都可以连接到Linux bridge上。需要注意的是,Linux bridge不能跨机连接网络设备。
Linux bridge与Linux上其他网络设备的区别在于,普通的网络设备只有两端,从一端进来的数据会从另一端出去。例如,物理网卡从外面网络中收到的数据会转发给内核协议栈,而从协议栈过来的数据会转发到外面的物理网络中。Linux bridge则有多个端口,数据可以从任何端口进来,进来之后从哪个口出去取决于目的MAC地址,原理和物理交换机差不多。
1.3.1 Linux bridge初体验
我们先用iproute2软件包里的ip命令创建一个bridge:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_44.jpg?sign=1738927283-zwFfVzKNg1UBwSy1sEb0w0yM2hLLjq0y-0-98fe3e16f0e3cf902a3b06de9324fd45)
除了ip命令,我们还可以使用bridge-utils软件包里的brctl工具管理网桥,例如新建一个网桥:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_45.jpg?sign=1738927283-UfF6H7TAcFCONYqDatcYK2RibPTsOgXt-0-a8a5f552d2e1fa089a020c4ba76981d4)
刚创建一个bridge时,它是一个独立的网络设备,只有一个端口连着协议栈,其他端口什么都没连接,这样的bridge其实没有任何实际功能,如图1-4所示。
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_46.jpg?sign=1738927283-81DjPFU6WAd3nK6S7TquJ5hDGwGQNZvV-0-1e79c5ba43636adbf9d38706b6daa5d2)
图1-4 独立的bridge设备示意图
假设eth0是我们的物理网卡,IP地址是1.2.3.4,并且假设实验室环境网关地址是1.2.3.1(下文会用到)。
为了充分发挥Linux bridge的作用,我们特将它和前文介绍的veth pair配合起来使用。我们将创建一对veth设备,并配置IP地址:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_47.jpg?sign=1738927283-AWydgcBrIG4Kqv7q6AodQcrobJqmiRXq-0-b68dddb5c4ba088ab864ba8c080b51a3)
然后,通过下面的命令将veth0连接到br0上:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_48.jpg?sign=1738927283-9IrjD4UvxebNXxxXljLyEfMSq5tUKFBE-0-9f089edde39fc5e92db7b615fd068fe9)
同样,可以使用brctl命令添加一个设备到网桥上:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_49.jpg?sign=1738927283-QQnWqpupjhp3Yuxvow3AVtZweJ9kYCQ9-0-57a7b16e23ec281b882531429c15dd00)
成功对接后,可以通过bridge link(bridge也是iproute2的组成部分)命令查看当前网桥上都有哪些网络设备:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_50.jpg?sign=1738927283-I3NjcgEcQRSK8G4wfTiAJbF9XFwFCN9Q-0-6cf28c9aa673379b3cb5e2d9ca00452f)
也可以使用brctl命令显示当前存在的网桥及其所连接的网络端口,这个命令的输出和bridge link的输出有所区别,命令如下所示:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_51.jpg?sign=1738927283-rN5lg7m6zOoKWVAoOJQwpBNbeYICgraw-0-9acfde0a507ce128990762a7b4405599)
执行完以上命令后,连接veth pair的bridge设备的网络拓扑如图1-5所示。
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_52.jpg?sign=1738927283-mPyjKoMNgASwrlJi7X7v31c3SWJA7Ev9-0-a55995cb4efb8835118a5cd8d6f05fb8)
图1-5 连接veth pair的bridge设备的网络拓扑
br0和veth0相连之后发生了如下变化:
·br0和veth0之间连接起来了,并且是双向的通道;
·协议栈和veth0之间变成了单通道,协议栈能发数据给veth0,但veth0从外面收到的数据不会转发给协议栈;
·br0的MAC地址变成了veth0的MAC地址。
这就好比Linux bridge在veth0和协议栈之间做了一次拦截,在veth0上面做了点小动作,将veth0本来要转发给协议栈的数据拦截,全部转发给bridge。同时,bridge也可以向veth0发数据。
让我们做个小实验来验证以上观点。
首先,从veth0 ping veth1:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_53.jpg?sign=1738927283-hPh8JdSvfk72Anih42lKj02ZaE1ITXJu-0-b3695fa44ea215f0858b4e107b29488f)
如上所示,veth0 ping veth1失败。为什么veth0加入bridge之后,就ping不通对端的veth1了呢?1.2.3.102原本应该是能ping通的,让我们通过抓包深入分析。先抓veth1网卡上的报文:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_54.jpg?sign=1738927283-bwzcgDzdCDw41TkBE73YbeZHIpmuRaUv-0-08dd8ac868dc8798767f98f950e543bf)
如上所示,由于veth0的ARP缓存里没有veth1的MAC地址,所以ping之前先发ARP请求。veth1抓取的报文显示,veth1收到了ARP请求,并且返回了应答。
再抓veh0网卡上的报文:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_55.jpg?sign=1738927283-nFP9VppKJKBVshNmbyZuhGqJSjedzmfr-0-10c91d7edae60d9854b32dc955c7c745)
如上所示,veth0上的数据包都发出去了,而且也收到了响应。
再看br0上的数据包,发现只有应答,如下所示:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_56.jpg?sign=1738927283-7SjY3hLnzidPPEkPK0J4uphwI102f2X5-0-b6bcecdef5b8a2d4671ffa02f0889011)
通过分析以下报文可以看出,包的去和回的流程都没有问题,问题就出在veth0收到应答包后没有给协议栈,而是给了br0,于是协议栈得不到veth1的MAC地址,导致通信失败。
1.3.2 把IP让给Linux bridge
通过上面的分析可以看出,给veth0配置IP没有意义,因为就算协议栈传数据包给veth0,回程报文也回不来。这里我们就把veth0的IP地址“让给”Linux bridge:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_57.jpg?sign=1738927283-8FJGisAvHXLpwuDKy8GoiEqaazoiO8RT-0-74bf733718abab7d2259b3d7e65c1239)
以上命令将原本分配给veth0的IP地址配置到br0上。于是,绑定IP地址的bridge设备的网络拓扑如图1-6所示。
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_58.jpg?sign=1738927283-x5AQ8jbsQ1hbtDm7kl3jEFuguB1qQL3D-0-d5d2d245f61c711dae3424ef4ffc42a6)
图1-6 绑定IP地址的bridge设备的网络拓扑
图1-6将协议栈和veth0之间的联系去掉了,veth0相当于一根网线。实际上,veth0和协议栈之间是有联系的,但由于veth0没有配置IP,所以协议栈在路由的时候不会将数据包发给veth0。就算强制要求数据包通过veth0发送出去,由于veth0从另一端收到的数据包只会给br0,协议栈还是没法收到相应的ARP应答包,同样会导致通信失败。
这时,再通过br0 ping veth1,结果成功收到了ICMP的回程报文:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_59.jpg?sign=1738927283-vRIyKwp5ji1olcChNULzcpuwBxyu9adu-0-3311134154e0603ea67bce08297d5ef8)
但ping网关还是失败:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_60.jpg?sign=1738927283-YjsQaO1ZpixCf73qjNiTYDkH8qy1FU9K-0-5dc0e27970b19bcda14c0e1af0a3de6a)
因为这个br0上只有192.168.3.101和192.168.3.102这两个网络设备,不知道1.2.3.1在哪儿。
1.3.3 将物理网卡添加到Linux bridge
下面,我们演示如何将主机上的物理网卡eth0添加到Linux bridge:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_61.jpg?sign=1738927283-j287nDNTe28cA4wioWdgWWz0YiEuga1R-0-475322a1ffb3822310f8fbf1cd29cbe2)
Linux bridge不会区分接入进来的到底是物理设备还是虚拟设备,对它来说没有区别。因此,eth0加入br0后,落得和上面veth0一样的“下场”,从外面网络收到的数据包将无条件地转发给br0,自己变成了一根网线。
这时,通过eth0 ping网关失败。因为br0通过eth0这根网线连上了外面的物理交换机,所以连在br0上的设备都能ping通网关,这里连上的设备就是veth1和br0自己,veth1是通过eth0这根网线连上去的,而br0有一块自带的网卡。
通过br0 ping网关成功:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_62.jpg?sign=1738927283-o1jVNK7rcX9bsrwN3HhVIpXoAWUsBdpT-0-b50fa9c3aa6dd4df6669052be5405807)
通过veth1 ping网关成功:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_63.jpg?sign=1738927283-kwEGlG0FDmTbsGXFEwSC6KyDMBLy59Gu-0-1ce3abd76e67a22b90f2c04d91387cb2)
通过eth0 ping网关失败:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_64.jpg?sign=1738927283-lZRksLKOUP4b6htgjDPEMZ0NoG2Op9ni-0-934134a2a36f4a9fd9c78a5826cfacce)
因为eth0的功能已经和网线差不多,所以在eth0上配置IP没有意义,还会影响协议栈的路由选择。例如,如果ping的时候不指定网卡,则协议栈有可能优先选择eth0,导致ping不通。因此,需要将eth0上的IP去掉。在以上测试过程中,由于eth0上有IP,在访问1.2.3.0/24网段时,会优先选择eth0。可以通过查看主机路由表来验证我们的判断:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_65.jpg?sign=1738927283-vWPqYpS8oESTiXLjgKygfbO1k2vsWu1F-0-8c7d1f11bcfe5fc0ed81724225b72e5a)
eth0接入了br0,因此它收到的数据包都会转发给br0,于是协议栈收不到ARP应答包,导致ping失败。
让我们将eth0上的IP删除:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_66.jpg?sign=1738927283-WWX06JO1VJO0ryiUKvXW1cerXVCVHM6q-0-9d891293727758d8e1c9ee8079816b1b)
这时,再从eth0 ping一次网关,成功收到ICMP响应报文:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_67.jpg?sign=1738927283-32JNcjItyh1u6zgn5wGOdX7IRS4E3kBP-0-ad2f9b5747c7e2f0f8084f66bf2127e6)
当我们删除eth0的IP后,路由表里就没有它了,于是数据包会从veth1出去。可以通过查看主机路由表来验证我们的判断。
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_68.jpg?sign=1738927283-mPqUP3XxuTrSez7xbiFsUAIUwWIY5kDF-0-885d6e524a07ccea16fb1cf14d3b2e97)
通过观察以上路由表信息可以看出:原来的默认路由进过eth0,eth0的IP被删除后,默认路由不见了,想要连接1.2.3.0/24以外的网段,需要手动将默认网关加回来。
添加默认网关:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_69.jpg?sign=1738927283-W6dGp5hfCUgpLbnzw9ijsTl6J0psVuSF-0-e7faa6696989f5f012dd5aed7af551ca)
再ping外网,成功返回ICMP报文:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_70.jpg?sign=1738927283-APa425zcVaiIMd23qcRnhPjO5m7gJNVW-0-6179601540de9d420f4122ac326abc9c)
经过上面一系列的操作,将物理网卡添加到bridge设备的网络拓扑如图1-7所示。
注:要完成以上所有实验步骤,需要打开eth0网卡的混杂模式(下文会详细介绍Linux bridge的混杂模式),不然veth1的网络会不通。当eth0不在混杂模式时,只会接收目的MAC地址是自己的报文,丢掉目的MAC地址是veth1的数据包。
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_71.jpg?sign=1738927283-DBSnxFmTH5tl0zqaePgFBO0irbOo0AVY-0-e58514517fb3b272090eba5bf93c0a25)
图1-7 将物理网卡添加到bridge设备的网络拓扑
1.3.4 Linux bridge在网络虚拟化中的应用
以上例子是为了阐述Linux bridge的底层机制而设计的,下面将通过Linux bridge的两种常见的部署方式说明其在现代网络虚拟化技术中的地位。
1. 虚拟机
虚拟机通过tun/tap或者其他类似的虚拟网络设备,将虚拟机内的网卡同br0连接起来,这样就达到和真实交换机一样的效果,虚拟机发出去的数据包先到达br0,然后由br0交给eth0发送出去,数据包都不需要经过host机器的协议栈,效率高,如图1-8所示。如果有多个虚拟机,那么这些虚拟机通过tun/tap设备连接到网桥。tun/tap设备的详细介绍将在1.4节展开。
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_72.jpg?sign=1738927283-0rY0ZNC7FAkrtepj6gqUlBQ5tjjDlVt7-0-b04a0b5372d675e5ac8ed0f93d21bae2)
图1-8 Linux bridge在虚拟机中的应用
2. 容器
容器运行在自己单独的network namespace里,因此都有自己单独的协议栈。Linux bridge在容器场景的组网和上面的虚拟机场景差不多,但也存在一些区别。例如,容器使用的是veth pair设备,而虚拟机使用的是tun/tap设备。在虚拟机场景下,我们给主机物理网卡eth0分配了IP地址;而在容器场景下,我们一般不会对宿主机eth0进行配置。在虚拟机场景下,虚拟器一般会和主机在同一个网段;而在容器场景下,容器和物理网络不在同一个网段内。Linux bridge在容器中的应用如图1-9所示。
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_73.jpg?sign=1738927283-96N5jPyz9UvEurIf43iQqISnQZZZ3wND-0-7f10dbd95800ebfc5a25f6bcfaf43e30)
图1-9 Linux bridge在容器中的应用
在容器中配置其网关地址为br0,在我们的例子中即1.2.3.101(容器网络网段是1.2.3.0/24)。因此,从容器发出去的数据包先到达br0,然后交给host机器的协议栈。由于目的IP是外网IP,且host机器开启了IP forward功能,数据包会通过eth0发送出去。因为容器所分配的网段一般都不在物理网络网段内(在我们的例子中,物理网络网段是10.20.30.0/24),所以一般发出去之前会先做NAT转换(NAT转换需要自己配置,可以使用iptables,1.5节会介绍iptables)。
1.3.5 网络接口的混杂模式
前文提到过网桥的混杂模式,网络接口的混杂模式在Kubernetes网络也有应用,本节将重点讨论网络接口的混杂模式。
混杂模式(Promiscuous mode),简称Promisc mode,俗称“监听模式”。混杂模式通常被网络管理员用来诊断网络问题,但也会被无认证的、想偷听网络通信的人利用。根据维基百科的定义,混杂模式是指一个网卡会把它接收的所有网络流量都交给CPU,而不是只把它想转交的部分交给CPU。在IEEE 802定的网络规范中,每个网络帧都有一个目的MAC地址。在非混杂模式下,网卡只会接收目的MAC地址是它自己的单播帧,以及多播及广播帧;在混杂模式下,网卡会接收经过它的所有帧!
我们可以使用ifconfig或者netstat-i命令查看一个网卡是否开启了混杂模式。
·ifconfig eth0,查看eth0的配置,包括混杂模式。当输出包含PROMISC时,表明该网络接口处于混杂模式。
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_74.jpg?sign=1738927283-13eLYlcLnkvmUMORbUYYhAg1mlRsAqrs-0-d42ce71ef8c672a77b224944082f4724)
启用网卡的混杂模式,可以使用下面这条命令:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_75.jpg?sign=1738927283-KqtkdpnRZEXw996284xTYclztXjmnczL-0-e7bf8940d156dbde1f298fdee5d436d6)
使网卡退出混杂模式,可以使用下面这条命令:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_76.jpg?sign=1738927283-cCf3Ivq4Beavb6KZmHWmhlHfTFkkbztW-0-e8e6243c08fae36c59c61ed641bfec3e)
将网络设备加入Linux bridge后,会自动进入混杂模式。可以通过下面的小实验来说明:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_77.jpg?sign=1738927283-GvnEaS0U7k6aFDEBEhe2bqVZz5VwVYbS-0-e77abcfe0213b5bde2f71dca0e3820bc)
如上所示,veth设备加入Linux bridge后,可以通过查看内核日志看到veth0自动进入混杂模式,而且无法退出,直到将veth0从Linux bridge中移除。
即使手动将网卡设置为非混杂模式,实际上还是没有退出混杂模式,一边操作ifconfig veth0-promisc,一边观察内核日志(内核并不会真正处理)便可看出。有兴趣的读者可以自行验证,这里不再赘述。
网络设备离开Linux bridge后,会自动退出混杂模式,如下所示:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_78.jpg?sign=1738927283-iBMHh5YAlAE9xQI4VLG2Ex3oSGduicUs-0-0b434f071ca2c9d0d01ae6e0784df77d)