竹径风声

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一、同一宿主机的两个容器间通信

通过 Veth Pair 设备 + 宿主机网桥的方式,实现了跟同一宿主机上其他容器通信。

访问流程:当容器1想要跟容器2通信时,IP包会被转发到容器1的eth0这个网卡上,通过二层网络直接发往目的地,但是二层交换设备需要MAC地址进行转发(这个 eth0 网卡,是一个 Veth Pair,一段在容器内一段在docker0网桥上,此时该虚拟网卡是docker0的一个从设备)。因此发送ARP数据包,docker0把 ARP 广播转发到其他被“插”在 docker0 上的虚拟网卡上,找到容器2的mac地址,然后docker0将mac地址返回给容器1,docker0根据mac地址,在它的 CAM 表(即交换机通过 MAC 地址学习维护的端口和 MAC 地址的对应表)里查到对应的端口(Port),然后将数据包转发到该端口上,此时数据包流入容器2的eth0上(容器2的网卡也是Veth Pair)

 

二、跟其他宿主机通信

当跟容器外的宿主机通信时,首先经过 docker0 网桥出现在宿主机上(类似上面的分析)。然后根据宿主机的路由表里的直连路由规则,对 10.168.0.3 的访问请求就会交给宿主机的 eth0去处理,然后将这个数据包转发到10.168.0.3宿主机的eth0上。

 

三、容器跨主机通信

Overlay Network(覆盖网络):通过软件的方式,创建一个整个集群公用的网桥,将所有容器连在这个网桥上。每个宿主机上都有一个特殊的网桥,能将数据包发到正确的宿主机上。

 

Flannel 

目前,Flannel 支持三种后端实现,分别是:

1、VXLAN;

2、host-gw;

3、UDP。

3.1 flannel UDP模式

当容器1和容器2通信时。容器1发起ip包,源地址为容器1的ip地址,目的地址为容器2的ip地址。而容器2的地址并不在docker0的网桥上,因此由默认路由规则处理,然后出现在宿主机

上。宿主机跟剧路由规则,将IP包转发到flannel0上,flannel0将这个ip包交给flannel进程也就是flanneld(flannel0是一个tunnel设备,这里内核态变为用户态)。flanneld将这个ip包封装成一个UDP包,UDP包的源地址为Node1的ip地址,目的地址为Node2的ip地址,每台宿主机上的 flanneld,都监听着一个 8285 端口,所以 flanneld 只要把 UDP 包发往 Node 2的8235端口即可。Node2的flanneld从UDP包里解析出容器1发给容器2的IP包,flanneld 会直接把这个 IP 包发送给它所管理的TUN 设备,即 flannel0 设备。Linux 内核网络栈就会负责处理这个 IP 包,具体的处理方法,就是通过本机的路由表来寻找这个 IP 包的下一步流向。Linux 内核就会按照这条路由规则,把这个 IP 包转发给docker0网桥,docker0 网桥会扮演二层交换机的角色,将数据包发送给正确的端口,进而通过 Veth Pair设备进入到容器2 的 Network Namespace 里。

这里如何知道UDP包的目的IP地址呢?

在flannel管理的网络里面,每个宿主机上的容器都属于分配给这个宿主机的子网。而这些子网和宿主机的对应关系存放在etcd数据库里面,所以,flanneld 进程在处理由 flannel0 传入ip包时,可以根据目的地址找到对应的子网,从而找到对应的宿主机,在etcd中找到对应宿主机的ip地址。

 

 UDP 模式有严重的性能问题:

相比于两台主机直接通信,flannel UDP多了一个步骤,flanneld的处理过程。由于使用了flannel0这个tunnel设备,仅在发送IP包的过程,就经历了三次内核态和用户态的数据拷贝。如下图:

 

3.2 flannel VXLAN模式

(解决flannel UDP内核态用户态互相转换效率问题)

VXLAN 可以完全在内核态实现上述封装和解封装的工作,从而通过与前面相似的“隧道”机制,构建出覆盖网络(Overlay Network)。

而为了能够在二层网络上打通“隧道”,XLAN 会在宿主机上设置一个特殊的网络设备作为“隧道”的两端。这个设备就叫作 VTEP,即:VXLAN Tunnel End Point。

而 VTEP 设备的作用,其实跟前面的 flanneld 进程非常相似。只不过,它进行封装和解封装的对象,是二层数据帧(Ethernet frame);而且这个工作的执行流程,全部是在内核里完成的(因为 VXLAN 本身就是 Linux 内核中的一个模块)。

 

与前面UDP模式类似,当容器1发出请求后,这个目的地址是容器2的ip地址,首先会出现在docker0网桥上,然后被路由到本机flannel.1设备进行处理,也就是来到了隧道的入口,我们把这个ip称为“原始ip包”。

为了能将原始ip包封装并发送到正确的宿主机,VXLAN就需要找到这条隧道的出口,即目的宿主机的VTEP设备。这个设备就是每台宿主机上的flanneld进程负责维护的。

比如,当 Node 2 启动并加入 Flannel 网络之后,在 Node 1(以及所有其他节点)上,flanneld 就会添加一条如下所示的路由规则:凡是发往 10.1.16.0/24 网段的 IP 包,都需要经过 flannel.1 设备发出,并且,它最后被发往的网关地址是:10.1.16.0。

然后根据ARP表(flanneld进程在Node2节点启动时,自动加在Node1上的),找到目的VTEP设备的ip地址对应的MAC地址。

 有了目的VTEP设备的MAC地址,Linux内核将原始ip包封装成“内部数据帧”,二层帧格式如下:

然后,Linux 内核会把这个数据帧封装进一个UDP 包里发出去。

此时,flannel.1扮演一个网桥的角色,在二层网络进行UDP包的转发。而在 Linux 内核里面,“网桥”设备进行转发的依据,来自于一个叫作 FDB(ForwardingDatabase)的转发数据库。这个 flannel.1“网桥”对应的 FDB 信息,也是 flanneld 进程负责维护的。它的内容可以通过 bridge fdb 命令查看到发往我们前面提到的“目的 VTEP 设备”的二层数据帧,应该通过 flannel.1 设备,发往 IP 地址为 10.168.0.3 的主机。显然,这台主机正是 Node 2,UDP 包要发往的目的地(目的宿主机ip)就找到了。

所以接下来的流程,就是一个正常的、宿主机网络上的封包工作。

Node2接收到数据帧后,内核网会发现这个数据帧有络栈里面有VXLAN Header,并且VNI=1.所以Linux内核会对它进行拆包,拿到内部数据帧,然后根据VIN的值,把它交给Node2上的flannel.1设备。flannel.1会进一步拆包,取出原始ip包。接下来回到上面讲的单机容器网络处理流程,最终ip包到容器2的network namespace中。

 

3.3 Flannel host-gw 模式

当你设置 Flannel 使用 host-gw 模式之后,flanneld 会在宿主机上创建这样一条规则,以Node 1 为例:目的 IP 地址属于 10.244.1.0/24 网段的IP 包,应该经过本机的 eth0 设备发出去(即:dev eth0);并且,它下一跳地址(next-hop)是 10.168.0.3(即:via 10.168.0.3)

 当容器1要跟容器2通信的时候,IP包会从网络层传到链路层,在数据帧上封装Node2的MAC地址,因此,该ip可以通过Node1的eth0发到Node2,然后再发送到容器2

 flannel host-gw 模式必须要求集群宿主机之间是二层联通的。

 

3.4 Calico

Calico 项目提供的网络解决方案,与 Flannel 的 host-gw 模式,几乎是完全一样的。Calico 也会在每台宿主机上,添加一个格式如下所示的路由规则:

Calico 项目使用BGP(边界网关协议)来自动地在整个集群中分发路由信息。BGP是一个 Linux 内核原生就支持的、专门用在大规模数据中心里维护不同的“自治系统”之间路由信息的、无中心的路由协议。AS 1 里面的主机 10.10.0.2,要访问AS 2 里面的主机 172.17.0.3 的话。它发出的ip包,就会先到达自治系统 AS 1 上的路由器 Router1。Router 1 的路由表里,有这样一条规则,即:目的地址是172.17.0.2 包,应该经过 Router 1 的的 C 接口,发往网关 Router 2(即:自治系统 AS 2 上的路由器)。

 负责把自治系统连接在一起的路由器,我们就把它形象地称为:边界网关。

 假如我们的网络拓扑结果非常复杂,可能是由多个自治系统组成的复合自治系统。这时就可以用BGP协议。使用BGP之后,可以认为在每个边界网关上都运行着一个小程序,他们会将各自的路由表信息,通过TCP传输给其他边界网关。而其他边界网关上的这个小程序会收到这些数据进行分析,然后将需要的信息添加到自己的路由表里。所谓 BGP,就是在大规模网络中实现节点路由信息共享的一种协议。

Calico 项目的架构就非常容易理解了。它由三个部分组成(它不会在宿主机上创建任何网桥设备。):

1、Calico 的 CNI 插件。这是 Calico 与 Kubernetes 对接的部分。

2、Felix。它是一个 DaemonSet,负责在宿主机上插入路由规则(即:写入 Linux 内核的 FIB 转发信息库),以及维护 Calico 所需的网络设备等工作。

3、BIRD。它就是 BGP 的客户端,专门负责在集群里分发路由规则信息。

Calico 的 CNI 插件会为每个容器设置一个 Veth Pair 设备,然后把其中的一端放置在宿主机上(它的名字以 cali 前缀开头)。此外,由于 Calico 没有使用 CNI 的网桥模式,Calico 的 CNI 插件还需要在宿主机上为每个容器的 Veth Pair 设备配置一条路由规则,用于接收传入的 IP 包。比如,宿主机 Node 2 上的 Container 4 对应的路由规则,如下所示:

即:发往 10.233.2.3 的 IP 包,应该进入 cali5863f3 设备。

有了这样的 Veth Pair 设备之后,容器发出的 IP 包就会经过 Veth Pair 设备出现在宿主机上。然后,宿主机网络栈就会根据路由规则的下一跳 IP 地址,把它们转发给正确的网关。接下来的流程就跟 Flannel host-gw 模式完全一致了。其中,这里最核心的“下一跳”路由规则,就是由 Calico 的 Felix 进程负责维护的。这些路由规则信息,则是通过 BGP Client 也就是 BIRD 组件,使用 BGP 协议传输而来的。

 

当Node1和Node2不在一个子网时,就需要为 Calico 打开 IPIP 模式

 

 当容器1要访问容器3时,路由规则的下一跳地址仍然是 Node 2 的 IP 地址,但这一次,要负责将 IP 包发出去的设备,变成了 tunl0。注意,是 T-U-N-L-0,而不是 Flannel UDP 模式使用的 T-U-N-0(tun0),这两种设备的功能是完全不一样的。Calico 使用的这个 tunl0 设备,是一个 IP 隧道(IP tunnel)设备。IP 包进入 IP 隧道设备之后,就会被 Linux 内核的 IPIP 驱动接管。IPIP 驱动会将这个 IP 包直接封装在一个宿主机网络的 IP 包中,如下所示:

其中,经过封装后的新的 IP 包的目的地址(图 5 中的 Outer IP Header 部分),正是原 IP 包的下一跳地址,即 Node 2 的 IP 地址:192.168.2.2。这样,

原先从容器到 Node 2 的 IP 包,就被伪装成了一个从 Node 1 到 Node 2 的 IP 包。由于宿主机之间已经使用路由器配置了三层转发,也就是设置了宿主机之间的“下一跳”。所以这个 IP 包在离开 Node 1 之后,就可以经过路由器,最终“跳”到 Node 2 上。

这时,Node 2 的网络内核栈会使用 IPIP 驱动进行解包,从而拿到原始的 IP 包。然后,原始 IP 包就会经过路由规则和 Veth Pair 设备到达目的容器内部。

 

小结:

三层和隧道的异同:
相同之处是都实现了跨主机容器的三层互通,而且都是通过对目的 MAC 地址的操作来实现的;不同之处是三层通过配置下一条主机的路由规则来实现互通,隧道则是通过通过在 IP 包外再封装一层 MAC 包头来实现。
三层的优点:少了封包和解包的过程,性能肯定是更高的。
三层的缺点:需要自己想办法维护路由规则。
隧道的优点:简单,原因是大部分工作都是由 Linux 内核的模块实现了,应用层面工作量较少。
隧道的缺点:主要的问题就是性能低。

posted on 2019-04-15 10:07  竹径风声  阅读(1048)  评论(0编辑  收藏  举报