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

通过 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)

k8s网络

 

二、跟其他宿主机通信

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

k8s网络

 

三、容器跨主机通信

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

k8s网络

 

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地址。

k8s网络

 k8s网络

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

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

 k8s网络

3.2 flannel VXLAN模式

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

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

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

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

 k8s网络

与前面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。

k8s网络

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

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

k8s网络

然后,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)就找到了。

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

k8s网络

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)

k8s网络

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

k8s网络

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

 

3.4 Calico

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

k8s网络

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 上的路由器)。

k8s网络

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

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

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

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

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

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

k8s网络

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

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

k8s网络

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

 

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

 k8s网络

 当容器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 包中,如下所示:

k8s网络

其中,经过封装后的新的 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 竹径风声 阅读(...) 评论(...) 编辑 收藏

相关文章: