Contents

Kubernetes教程(三)---纯三层网络方案

本文主要介绍了 Kubernetes 中的 Pure Layer 3 网络方案。其中的典型例子,莫过于 Flannel 的 host-gw 模式和 Calico 项目了,本文将大致分析 Flannel 的 host-gw 模式和 Calico 项目来探索Kubernetes 中的纯三层(Pure Layer 3)网络方案。

本文写于 2021-03-27

第二次更新于 2022-09-04,增加了一些自己的理解,并修复了一些错误

Kubernetes

1. Flannel host-gw 模式

数据流向如下图所示:

../../../img/kubernetes/network/layer3/k8s-network-pure-layer3.png

当你设置 Flannel 使用 模式之后,flanneld 会在宿主机上创建这样一条规则,以 Node 1 为例:

$ ip route
...
10.244.1.0/24 via 10.168.0.3 dev eth0

这条路由规则的含义是:目的 IP 地址属于 10.244.1.0/24 网段的 IP 包,应该经过本机的 eth0 设备发出去(即:dev eth0);并且,它下一跳地址(next-hop)是 10.168.0.3(即:via 10.168.0.3)。

所谓下一跳地址就是:如果 IP 包从主机 A 发到主机 B,需要经过路由设备 X 的中转。那么 X 的 IP 地址就应该配置为主机 A 的下一跳地址。

而从 host-gw 示意图中我们可以看到,这个下一跳地址对应的,正是我们的目的宿主机 Node 2。

一旦配置了下一跳地址,那么接下来,当 IP 包从网络层进入链路层封装成帧的时候,eth0 设备就会使用下一跳地址对应的 MAC 地址,作为该数据帧的目的 MAC 地址。显然,这个 MAC 地址,正是 Node 2 的 MAC 地址。

这样,这个数据帧就会从 Node 1 通过宿主机的二层网络顺利到达 Node 2 上。

而 Node 2 的内核网络栈从二层数据帧里拿到 IP 包后,会“看到”这个 IP 包的目的 IP 地址是 10.244.1.3,即 Infra-container-2 的 IP 地址。这时候,根据 Node 2 上的路由表,该目的地址会匹配到第二条路由规则(也就是 10.244.1.0 对应的路由规则),从而进入 cni0 网桥,进而进入到 Infra-container-2 当中。

可以看到,host-gw 模式的工作原理,其实就是将每个 Flannel 子网(Flannel Subnet,比如:10.244.1.0/24)的“下一跳”,设置成了该子网对应的宿主机的 IP 地址

也就是说,这台“主机”(Host)会充当这条容器通信路径里的“网关”(Gateway)。这也正是“host-gw”的含义。

而且 Flannel 子网和主机的信息,都是保存在 Etcd 当中的。flanneld 只需要 WACTH 这些数据的变化,然后实时更新路由表即可。

而在这种模式下,容器通信的过程就免除了额外的封包和解包带来的性能损耗。根据实际的测试,host-gw 的性能损失大约在 10% 左右,而其他所有基于 VXLAN“隧道”机制的网络方案,性能损失都在 20%~30% 左右。

host-gw 模式能够正常工作的核心,就在于 IP 包在封装成帧发送出去的时候,会使用路由表里的“下一跳”来设置目的 MAC 地址。这样,它就会经过二层网络到达目的宿主机。

所以说,Flannel host-gw 模式必须要求集群宿主机之间是二层连通的

问题来了,为什么需要二层连通?

如果二层不连通,那么只能通过三层传输(即:IP 包),到其他设备或者路由器的时候是不认识这个容器 IP 的,因此这个包肯定会丢。

注:Node1 和 Node2 认识是因为 Calico 在上面加了对应的路由规则。

因此必须要二层连通才行。

2. Calico BGP 模式

Calico 当前算是主流的 k8s CNI 实现。

实际上,Calico 项目提供的网络解决方案,与 Flannel 的 host-gw 模式,几乎是完全一样的。

也就是说,Calico 也会在每台宿主机上,添加一个格式如下所示的路由规则:

<目的容器IP地址段> via <网关的IP地址> dev eth0

其中,网关的 IP 地址,正是目的容器所在宿主机的 IP 地址。

而正如前所述,这个三层网络方案得以正常工作的核心,是为每个容器的 IP 地址,找到它所对应的、“下一跳”的网关

不过,不同于 Flannel 通过 Etcd 和宿主机上的 flanneld 来维护路由信息的做法,Calico 项目使用了一个“重型武器” BGP 来自动地在整个集群中分发路由信息

BGP(Border Gateway Protocol)即边界网关协议,是运行于TCP上的自治系(互联网AS)统路由协议。

简单理解:所谓 BGP,就是在大规模网络中实现节点路由信息共享的一种协议

BGP 在每个边界网关(路由器)上运行,彼此之间通信更新路由表信息,而 BGP 的这个能力,正好可以取代 Flannel 维护主机上路由表的功能。

而且,BGP 这种原生就是为大规模网络环境而实现的协议,其可靠性和可扩展性,远非 Flannel 自己的方案可比。

Calico 由三个部分组成:

  • 1)Calico 的 CNI 插件。这就是 Calico 与 Kubernetes 对接的部分。
  • 2)Felix。它是一个 DaemonSet,负责在宿主机上插入路由规则(即:写入 Linux 内核的 FIB 转发信息库),以及维护 Calico 所需的网络设备等工作。
  • 3)BIRD。它就是 BGP 的客户端,专门负责在集群里分发路由规则信息。

除了对路由信息的维护方式之外,Calico 项目与 Flannel 的 host-gw 模式的另一个不同之处,就是它不会在宿主机上创建任何网桥设备

二者只是不同的实现,功能上并没有差别。Calico 通过设置路由规则,将数据包直接路由到对应 veth 设备,Flannel 则是先路由到网桥,在从网桥转发到对应 veth 设备。

../../../img/kubernetes/network/layer3/k8s-network-bgp.jpg

Calico 的 CNI 插件会为每个容器设置一个 Veth Pair 设备,然后把其中的一端放置在宿主机上(它的名字以 cali 前缀开头)。

此外,由于 Calico 没有使用 CNI 的网桥模式,Calico 的 CNI 插件还需要在宿主机上为每个容器的 Veth Pair 设备配置一条路由规则,用于接收传入的 IP 包。比如,宿主机 Node 2 上的 Container 4 对应的路由规则,如下所示:

10.233.2.3 dev cali5863f3 scope link

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

基于上述原因,Calico 项目在宿主机上设置的路由规则,肯定要比 Flannel 项目多得多。

不过,Flannel host-gw 模式使用 CNI 网桥的主要原因,其实是为了跟 VXLAN 模式保持一致。否则的话,Flannel 就需要维护两套 CNI 插件了。

有了这样的 Veth Pair 设备之后,容器发出的 IP 包就会经过 Veth Pair 设备出现在宿主机上。然后,宿主机网络栈就会根据路由规则的下一跳 IP 地址,把它们转发给正确的网关。接下来的流程就跟 Flannel host-gw 模式完全一致了。

其中,这里最核心的“下一跳”路由规则,就是由 Calico 的 Felix 进程负责维护的。这些路由规则信息,则是通过 BGP Client 也就是 BIRD 组件,使用 BGP 协议传输而来的。

Calico 项目实际上将集群里的所有节点,都当作是边界路由器来处理,它们一起组成了一个全连通的网络,互相之间通过 BGP 协议交换路由规则。这些节点,我们称为 BGP Peer

需要注意的是,Calico 维护的网络在默认配置下,是一个被称为“Node-to-Node Mesh”的模式。这时候,每台宿主机上的 BGP Client 都需要跟其他所有节点的 BGP Client 进行通信以便交换路由信息。但是,随着节点数量 N 的增加,这些连接的数量就会以 N²的规模快速增长,从而给集群本身的网络带来巨大的压力。

所以,Node-to-Node Mesh 模式一般推荐用在少于 100 个节点的集群里。而在更大规模的集群中,你需要用到的是一个叫作 Route Reflector 的模式。

在这种模式下,Calico 会指定一个或者几个专门的节点,来负责跟所有节点建立 BGP 连接从而学习到全局的路由规则。而其他节点,只需要跟这几个专门的节点交换路由信息,就可以获得整个集群的路由规则信息了。

对于 Calico BGP 模式来说,同样要求集群宿主机之间是二层连通的。

举个例子,假如我们有两台处于不同子网的宿主机 Node 1 和 Node 2,对应的 IP 地址分别是 192.168.1.2 和 192.168.2.2。需要注意的是,这两台机器通过路由器实现了三层转发,所以这两个 IP 地址之间是可以相互通信的。

而我们现在的需求,还是 Container 1 要访问 Container 4。

按照我们前面的讲述,Calico 会尝试在 Node 1 上添加如下所示的一条路由规则:

10.233.2.0/16 via 192.168.2.2 eth0

上面这条规则里的下一跳地址是 192.168.2.2,可是它对应的 Node 2 跟 Node 1 却根本不在一个子网里,没办法通过二层网络把 IP 包发送到下一跳地址。

在这种情况下,你就需要为 Calico 打开 IPIP 模式

3. Calico IPIP 模式

Calico 的 IPIP 模式也是隧道中的一种。

../../../img/kubernetes/network/layer3/k8s-network-calico-ipip.jpg

在 Calico 的 IPIP 模式下,Felix 进程在 Node 1 上添加的路由规则,会稍微不同,如下所示:

10.233.2.0/24 via 192.168.2.2 tunl0

这一次,要负责将 IP 包发出去的设备,变成了 tunl0,Calico 使用的这个 tunl0 设备,是一个 IP 隧道(IP tunnel)设备。

P 包进入 IP 隧道设备之后,就会被 Linux 内核的 IPIP 驱动接管。IPIP 驱动会将这个 IP 包直接封装在一个宿主机网络的 IP 包中,如下所示:

../../../img/kubernetes/network/layer3/k8s-network-calico-ipip-wrap.webp

其中,经过封装后的新的 IP 包的目的地址(图中的 Outer IP Header 部分),正是原 IP 包的下一跳地址,即 Node 2 的 IP 地址:192.168.2.2,而原 IP 包本身,则会被直接封装成新 IP 包的 Payload。

这样,原先从容器到 Node 2 的 IP 包,就被伪装成了一个从 Node 1 到 Node 2 的 IP 包

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

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

以上,就是 Calico 项目主要的工作原理了。

额外的封包和解包工作会导致集群网络性能下降,在实际测试中,Calico IPIP 模式与 Flannel VXLAN 模式的性能大致相当。

4. 小结

三层网络主要通过维护路由规则,将数据包直接转发到对应的宿主机上

  • Flannel host-gw 主要通过 etcd 中的子网信息来维护路由规则;
  • Calico BGP 模式则通过 BGP 协议收集路由信息,由 Felix 进程来维护;
  • Calico IPIP 模式也是一直隧道模式,主要通过在 IP 包外再封装一层 IP 包来实现。

使用场景

在大规模集群里,三层网络方案在宿主机上的路由规则可能会非常多,这会导致错误排查变得困难。此外,在系统故障的时候,路由规则出现重叠冲突的概率也会变大。

  • 1)如果是在公有云上,由于宿主机网络本身比较“直白”,一般推荐更加简单的 Flannel host-gw 模式。
  • 2)但不难看到,在私有部署环境里,Calico 项目才能够覆盖更多的场景,并为你提供更加可靠的组网方案和架构思路。

三层和隧道的异同

相同之处是都实现了跨主机容器的三层互通,不同之处是三层通过配置下一条主机的路由规则来实现互通,因此需要二层连通,而隧道则是通过封包和解包来实现,因此不需要二层连通。

  • 三层的优点:少了封包和解包的过程,性能肯定是更高的。
  • 三层的缺点:需要自己想办法维护路由规则。
  • 隧道的优点:简单,原因是大部分工作都是由 Linux 内核的模块实现了,应用层面工作量较少。
  • 隧道的缺点:主要的问题就是性能低。

是否应用隧道的本质其实是在网络传输的中间过程中,只靠原本的数据包的路由信息(mac,ip)等,中间网络设备是否能够顺利识别并转发。

像二层不通的情况下,用 BGP 模式直接在宿主机设置路由是无法到达的,因此就需要该用 IPIP 模式走隧道了。

5. 参考

https://kubernetes.io/docs/concepts/cluster-administration/networking/

https://blog.laputa.io/kubernetes-flannel-networking-6a1cb1f8ec7c

https://feisky.gitbooks.io/kubernetes/content/network/flannel/flannel.html

深入剖析Kubernetes 专栏