157:eth0:<BROADCAST,MULTICAST,UP,LOWER_UP>mtu1500qdisc fq_codel state UNKNOWN mode DEFAULTgroup defaultqlen1000
link/ether a6:99:c3:0d:14:93brd ff:ff:ff:ff:ff:ff
158:eth1:<BROADCAST,MULTICAST,UP,LOWER_UP>mtu1500qdisc fq_codel state UNKNOWN mode DEFAULTgroup defaultqlen1000
link/ether82:e2:5d:48:67:fc brd ff:ff:ff:ff:ff:ff
我们怎么配置这两个网卡呢?
首先,假设我们给两个 interface 分别配置一个 IP 地址,这样的话,在交换机和其他设备看来,这个服务器其实是两个机器,因为它是两个不同的 IP,已经失去了高可用的意义了。因为软件是需要基于 IP 来部署和交互的,拿 Etcd 来讲,假设我们部署 Etcd 在这个机器上,它就只能 listen 其中的一个 IP。如果部署两个实例分别 listen 一个 IP,那在其他实例看起来,好像是有两个不同的实例,但其实是部署在一个物理机上,这样,反而降低了可用性。因为物理机一挂就会导致两个 Etcd 实例一起失败。综上,我们要让服务器对外看起来还是一个 IP 才行。
我们在考虑:假设给这两个网卡配置成一样的 MAC 地址行不行?这样,对外看起来只有一个 IP 和一个 MAC 地址,发送数据的时候可以用两条线,接收数据的时候,无论哪一条线收到都可以。看起来完美,但是忽略了一个重要的角色——交换机。
还记得交换机是怎么工作的吗?它从每一个收到的包学习 MAC 地址。两条线接到交换机上,对于交换机来说,这就是两个客户端。假设这两个网卡(客户端)的 MAC 地址一样,都是 MAC AA,那么当网卡 A 从交换机端口 1 发送数据出去的时候,交换机学习到:「MAC AA 对应端口1,发往 MAC AA 的数据都转发给端口 1」;但是当网卡 B 从端口 2 发送出去的时候,来源 MAC 地址也是 AA,交换机就学习到:「OK,现在 MAC AA 是在端口 2 了,让我修改我的 MAC 地址表,之后如果发给 MAC AA 我就往端口 B转发就好了」。过了一会,交换机的端口 1 又收到了 MAC AA 发来的数据,交换机迷惑了——「怎么这个 MAC AA 一会插在端口 1 上,一会插在端口 2 上,切换如此频繁,到底是谁的手速这么快?」。
这就是 MAC flapping 问题,指的就是同一个 MAC 地址在不同的交换机端口之间来回切换。
它的坏处有几个:
显而易见它会给交换机带来困扰,交换机来回修改 MAC 地址表,性能会下降;
对目标地址为 MAC AA 的,交换机会时而发给端口 1,时而发给端口 2,可能会造成同一个 TCP 连接的乱序问题,从而造成性能下降。
综上,我们不能对两个网卡使用相同的 MAC 地址。
但是我们可以把两张卡配置成一样的 MAC 地址,却不同时使用呀。
最简单的方案是:我们总是使用一张物理卡,当这个网卡不可用的时候,我们转而无缝切换到另一张网卡。这就是 Linux bonding 的 active-backup 模式。
配置 bonding active-backup 模式(mode 1)
从命令行配置 bonding 模式非常直观,大体的步骤是,把要配置的网卡先 down 掉,然后创建一个 bond 模式的 interface,将物理网卡设置为 bond 的 slave,最后把 bond 设置为 up,完事。(不需要手动设置物理网卡为 up,会自动 up。)
它是干什么的呢?还记得理解网络的分层模型中我们提到的「沙漏模型」吗?假设没有 MII,网卡中的 MAC 硬件直接连接物理媒介,那么每出现一种新的物理媒介,比如千兆以太网,万兆以太网,各种光纤,都要去适配所有的 MAC 硬件,是 M x N 的工作量。如果有 MII,MAC 硬件对接 MII,然后每出现一种新的媒介,只需要开发一次新的 MII 接口即可。
MII Monitoring 就是通过 MII 来检查物理物理网卡的状态。如果物理网卡挂了,通过 MII 就可以检测到。但是 MII 检查通过不能代表网络是通的。
(之前我有一个误解,我觉得 MII 只能检测网卡自身问题,无法检测像是网线不良,对端是否插线,对端设备挂了,对端设备端口挂了等问题,但是后来发现 PHY 芯片是可以检测到这些问题的,这里稍微展开一下。)
明确一下网络高可用的目标。服务器最终的目标是提供服务,服务通过应用层网络协议提供出去:HTTP,webRTC 等等。这些协议都是基于 TCP 或者 UDP,由于四层的高可用技术主要是四层负载均衡解决了,四层负载均衡一般使用的是商用服务器,在这些服务器前面的网络设备主要工作在二层和三层,对于这些设备来说,四层网络就是这些设备要传输的数据。这些设备要做的就是:确保四层数据的传输是高可用的,或者说,尽量在三层 IP 协议上保证可用性,这也是 IP 协议的设计:Best effort.
流量都是通过网络单元的形式发送出去的,在三层叫做包,二层叫做帧。服务器拿 Linux 来距离,在发送数据的时候,无论是 TCP 还是 UDP,都是先封装成三层数据包,再封装成二层数据帧,最后通过物理层让网卡发送数据。不是所有的数据都是基于三层协议发送的,但是数据一定是通过二层协议发送出去的,因为二层是连接物理层的电信号和数字信号的地方。数据中心的二层协议一般就是 Ethernet 了。
数据封装成三层包很简单,因为应用发送数据的时候已经指定了 IP 地址,kernel 根据这些信息封装添加 I P header 就完成了。
封装成二层也很简单,主要是添加二层 Header 和计算 CRC。Header 中最重要的是放上自己的 MAC 地址和目标的 MAC 地址。怎么知道目标 MAC 地址应该写啥呢?首先根据要发送的目标 IP 和自己的子网判断目标 IP 是否和自己在同一个子网,如果在同一个子网,那么目标 MAC 地址就写目标 IP 的 MAC 地址;如果不在同一个子网,就写默认网关 (default gateway) 的 MAC 地址。那又怎么知道目标 IP 或者默认网关的 MAC 地址是什么呢?机器本地是没有办法知道的,所以要通过网络协议询问,即 ARP 协议。
想知道一个 IP 对应的 MAC 地址,就设置一个问题,问「谁有 IP xxx 的 MAC 地址?如果知道,请回复给 IP yyy,yyy 的 MAC 地址是 B。」前面提到 ARP 也是基于二层的,我们就得把这个问题封装到二层中,来源 MAC 就是自己的 MAC,B,目标 MAC 地址是 FF:FF:FF:FF:FF:FF,即广播给所有的人。这时候所有人会收到这个 ARP 问题,但是只有 IP 是 xxx 的会回复,告知其 MAC 地址。其他人虽然不回复,但是也会收到这个 ARP 问题,通过这个 ARP 问题,所有的人都知道了:「哦,虽然不需要我回答,但是我也获得了 yyy 的 MAC 地址,我先记下来,说不定以后用得到呢。」
通过 ARP 协议拿到了 MAC 地址,就可以完成二层的封装了,最后通过物理层发送出去。ARP 协议的目的就是找到 IP 和 MAC 地址的对应,这个记录会在机器上缓存一段时间,减少 ARP 查询量,也能减少 ARP 带来的延迟。
接收流量的行为很简单,网卡从物理层收到网络数据,解析成二层数据帧,判断目标 MAC 地址是否是发给我的,如果不是就丢弃,如果是,就把数据交给 kernel。如果是广播报,就根据协议判断是否需要回复,如上面提到的 ARP。
MAC 地址表中,一个 MAC 地址只能对应唯一一个端口,不能对应多个端口。即,表示这个 MAC 转发到这个端口;但是一个端口可以对应多个 MAC 地址,或者说,多个 MAC 地址可以对应到同一个端口,因为一个端口后面不仅仅可以是服务器,也可以是交换机。
路由器
路由器工作在三层,但是三层必须要依靠二层承载才能工作,所以路由器里面也有一个二层实现。在每次收发数据包的时候,它的工作方式是和服务器一样的,都是通过 ARP 询问 MAC 地址,然后把要发送的三层包添加二层 header(主要是 MAC 地址),发送出去。对于交换机的视角来说,路由器和服务器一样,都是它的「客户」。
在转发数据包的时候,路由器每次拿到一个 IP 包,就检查自己的路由表,找到一个出口端口,然后从这个端口发送出去。
它和交换机一样,主要的工作是「转发」,只不过交换机参考的是 MAC 与端口的对应表,路由器参考的是 IP 与端口的对应表。
电脑转给路由器,设置 MAC 地址看起来很没必要?是的,这种相当于点对点的网络,没有地址也不会发错的。也确实存在一些其他的二层技术,比如 Frame Relay,二层的包头就没有 MAC 地址(但是有类似 MAC 地址作用的东西)。
但是下面这个结构,MAC 地址就很有用了。比如电脑 1 要发送给电脑 2 内容,DST MAC 地址写 MAC 2,然后交给交换机,交换机就会转发给电脑2。如果没有 MAC 的话,交换机收到这个包,就不知道应该发给 2 还是 3 了。
如果有了 MAC 地址,交换机就可以把这个包从 MAC 地址对应的端口转发出去。
如今的大部分交换机都是非常「智能」的设备,可以通过查询 MAC 地址查找对应的端口进行转发。四十年前的设备不这么智能——它从一个端口收到包,就直接转发给其他所有的端口,就是 Hub(集线器)。所有的主机都会收到这个包,但是会检查 MAC 地址是否属于自己,如果不是,就丢弃。Ethernet 本身的设计就是将包转发到所有的端口,按广播的设计来的,所以它有冲突检测等设计,如今已经很少用到了。
三层转发同理,它只关心三层的内容,最重要的是 IP 地址。检查目标 IP,然后根据自己的路由表,在多个接口(路由器一般有多个接口,连接不同的网络,家用路由器可以理解为有两个接口,一个连接家里的内网,一个连接 ISP 那边的网络)选择其中的一个,然后通过这个接口将数据发送出去。路由表可以理解成是三层设备的一个转发数据库,是它最重要的依据,它的功能就是给定一个 目标 IP,可以从中查询出一个物理出口,然后就可以使用这个物理出口转发出去。
这个时候,二层网络就成了它的载体:二层用什么技术不重要,只要能给三层把封装好的三层包发到对面就可以,Ethernet 可以,Wi-Fi 可以,Frame Relay 也可以。如果是 Ethernet 的话,就把三层包封装到一个二层里面,即在外面贴上 DST MAC 地址是对面的设备,SRC MAC 是自己的 MAC,最后用物理层发送。
所有上层协议都可以依赖 IP 协议发送自己的数据,TCP, UDP, ICMP 等等,都是将 IP 作为载体。
所有的设备之间能够互相通讯,这就要求这些设备之间都运行了某种一样的协议,才能懂彼此的语言。这个协议就是 IP。IP 的上层有很多协议,下层也可以依赖各种协议,但是中间必须是 IP。每一个网络的自治域内可能使用非 IP 协议(但是我好像没听说过),但是自治域之间,都是用 IP 交换信息的,交换 IP 路由信息的协议都是用 BGP。就像一个沙漏一样。
二层设备主要是按照 MAC 地址转发,它不修改包的内容。不过上面的例子不知读者有没有发现一个问题——交换机怎么知道哪一个接口对应 MAC2 呢?如何将收到的包发送出去?其实很简单,如果它不知道,就把这个包发送给所有的接口。和「集线器」不同的是,交换机可能一开始不知道 MAC 和端口的对应,但是它可以学习。学习主要通过收到的包的来源 MAC 地址,比如这个包是从接口 1 收到的,而且包里面有一个 SRC MAC 是 MAC1,那二层设备就记住了,下次发给 MAC1 的时候,我就直接发送给接口 1,不用发给所有人了。这就叫「MAC 地址学习」,学习到的内容叫做 MAC 地址表,存储在内存中。
三层设备就是按照 IP 转发了。三层设备的负担更重一些,因为无法像二层那样简单地从要转发的包里面「学习」路由信息。因为发送者也不知道怎么转发到终点 IP 呀,人家指望着你呢,已经把包交给到你的手上了。三层设备根据自己的路由表转发,路由表可以通过静态配置。比如配置成往 xx 这个网段发送的话,就走接口1,如果往 yy 那个网段发送的话,就走接口2,如果找不到的话,就走接口3,类似这种。但是机房内设备众多,不可能一一手动配置,一般都是动态生成的。即,所有的路由器都知道自己的直连网络,然后在路由器之间交换彼此的信息,这样,大家彼此依靠,共同描绘一个转发地图。具体的路由协议内容非常复杂,就不展开了。总之,三层设备之间在交换两种信息:一种是路由信息,这部分我们也叫「控制面」;另一种就是实际要转发的数据了,我们叫「数据面」。