前提知识

  1. TCP五层模型:物理层、数据链路层、网络层、传输层、应用层。

  2. Linux容器能看到的“网络栈”是被隔离到自己的Network NameSpace中的。所谓网络栈包括:网卡(Network Interface)、路由表(Route Table)、回环设备(Loopback Device)、网络规则(iptable规则),以上是一个进程发起和响应网络请求的基本环境。

  3. ipconfig查看网络设备、route查看路由表、brctl show查看网桥、forward转发。

  4. 网桥
    网关
    网卡

  5. 如果你想要实现两台主机之间的通信,最直接的办法,就是把它们用一根网线连接起来;而如果你想要实现多台主机之间的通信,那就需要用网线,把它们连接在一台交换机上。在 Linux 中,能够起到虚拟交换机作用的网络设备,是网桥(Bridge)。它是一个工作在数据链路层(Data Link)的设备,主要功能是根据 MAC 地址学习来将数据包转发到网桥的不同端口(Port)上。

  6. 路由规则上的网关。0.0.0.0意味着这是个直连规则。网关上面走的是IP包。网卡走的是MAC包,通过ARP协议通过IP地址找MAC地址。

  7. 网卡是“插”在网桥上的,这时候会被降级成输入输出端口。这时候网桥扮演的就是网卡,把来自不同网络的IP包,通过MAC地址传到不同网络中去。宿主机看docker0是网卡,容器看docker0是网桥。

Docker单机网络方案

容器要与外部通信就一定要把IP包发到宿主机上,host就直接用宿主机方案,bridge是用一套Veth-Pair把容器内的IP包发到网桥/网卡上来与本机的其他容器或者外网进行交互。

none

host

直接用主机网络栈,优点快、方便,可以用容器修改本机配置。缺点占用端口了,可能会产生端口冲突。

bridge

默认会建一个docker0的网桥。那怎么把容器接到网桥上,又没有网线?
--> Veth Pair的虚拟设备,这种设备的特点是:成对创建,以两张虚拟网卡(Veth Peer)的形式使用,并且一张“网卡”发出的数据会同时出现在对应“网卡”上,哪怕这对网卡在不同namespace。OH my goder,简直就是虚拟的网线。
但是当Veth Pair被插到docker0的网卡上,就相当于降级成了一个端口,而同一网卡,即docker0上的容器是可以相互ping通的。docker0相当于一个二层交换机。

docker0相当于2层交换机,把数据包发给正确的端口,并把数据包通过veth-pair传到容器的网络里。
既然docker0承担了所有默认容器的网络,那么不同主机的docker0只要再做一个集成,创建一整个集群公用的“网桥”连接所有容器即可。这种在已有宿主机网络上再建一层网络的就叫overlay(覆盖网络)。在以有宿主机网络上再构建一个连接所有容器的虚拟网络。

user-def

bridege overlay macvlan

Docker跨主机网络方案

容器跨主网络的主流实现方法:1.vxlan 2.host-gw 3.UDP
flannnel提供了一种flannel0设备,这是一种TUN(Tunnel设备),是一种Linux中工作在三层网络层的虚拟设备。作用是在操作系统内核和用户应用程序之间传递IP包。这样就能把内核态的IP包传给Flannel进程,由这个进程把IP包传到另一个机子,再在另一个机子上把IP包从进程传到操作系统的docker0里。

那么flannnel是怎么知道要发给哪个机子的哪?
——->答案是 子网(Subnet)。子网和宿主机关系被存在etc中,这样,每个容器的IP包就能知道是哪个机子,哪个IP地址,然后也能根据目的IP地址找到要发给的机子。

UDP

最早支持、最早废弃,性能最差,但也是最直接的实现。

如上分析,通过一个简单的、宿主机之间的UDP通信,一个UDP包就从node1到了node2,然后flannel就能解析这个UDP包,拿到容器发出的原IP包。

用户态和内核态切换太多,导致性能低下,于是后来vxlan成为主流。

Vxlan

VXLAN,即 Virtual Extensible LAN(虚拟可扩展局域网),是 Linux 内核本身就支持的一种网络虚似化技术。所以说,VXLAN 可以完全在内核态实现上述封装和解封装的工作,从而通过与前面相似的“隧道”机制,构建出覆盖网络(Overlay Network)。即只要在vxlan上的主机就能通信,而不必非要打包成UDP。

而为了能够在二层网络上打通“隧道”,VXLAN 会在宿主机上设置一个特殊的网络设备作为“隧道”的两端。这个设备就叫作 VTEP,即:VXLAN Tunnel End Point(虚拟隧道端点)。而 VTEP 设备的作用,其实跟前面的 flanneld 进程非常相似。只不过,它进行封装和解封装的对象,是二层数据帧(Ethernet frame);而且这个工作的执行流程,全部是在内核里完成的(因为VXLAN 本身就是 Linux 内核中的一个模块)。当然最后也是打了个UDP包。

使用该技术后,每台宿主机上名叫 flannel.1 的设备,就是 VXLAN 所需的 VTEP 设备,它既有 IP地址,也有 MAC 地址。

以上两种都可以称为“隧道机制”,这也是很多其他容器网络插件的基础,但是它的缺陷就是,可以保证三层网络(IP地址)之间的连通性,无法保证二层网络(MAC地址)之间的连通性。是用户的容器都连接在 docker0 网桥上。而网络插件则在宿主机上创建了一个特殊的设备(UDP 模式创建的是 TUN 设备,VXLAN 模式创建的则是VTEP 设备),docker0 与这个设备之间,通过 IP 转发(路由表)进行协作。网络插件真正要做的事情,则是通过某种方法,把不同宿主机上的特殊设备连通,从而达到容器跨主机通信的目的。

host-gw

纯三层(Pure Layer 3)网络方案

flanneld直接给你配好下一跳地址。host-gw 模式的工作原理,其实就是将每个 Flannel 子网(Flannel Subnet,比如:10.244.1.0/24)的“下一跳”,设置成了该子网对应的宿主机的 IP 地址。也就是说,这台“主机”(Host)会充当这条容器通信路径里的“网关”(Gateway)。这也正是“host-gw”的含义。

优点在于性能损失低,不用封包解包了,,host-gw 的性能损失大约在 10% 左右,而其他所有基于 VXLAN“隧道”机制的网络方案,性能损失都在 20%~30% 左右。
但问题是host-gw 模式能够正常工作的核心,就在于 IP 包在封装成帧发送出去的时候,会使用路由表里的“下一跳”来设置目的 MAC 地址。这样,它就会经过二层网络到达目的宿主机。所以说,Flannel host-gw 模式必须要求集群宿主机之间是二层连通的。

缺点是只能在二层玩,过了二层路由被重写就GG了。于是就需要K8s进行网络方案的设置。

宿主机之间二层不连通的情况也是广泛存在的。比如,宿主机分布在了不同的子网(VLAN)里。但是,在一个 Kubernetes 集群里,宿主机之间必须可以通过 IP 地址进行通信,也就是说至少是三层可达的。否则的话,你的集群将不满足上一篇文章中提到的宿主机之间IP 互通的假设(Kubernetes 网络模型)。当然,“三层可达”也可以通过为几个子网设置三层转发来实现。

K8s集群网络方案

K8s做的也是类似的事,把不同宿主机上的特殊设备连通,达到跨主机通信的目的。K8s用的是一个叫CNI的接口,维护了一个单独的网桥来代替docker0,即CNI网桥,默认宿主机名为cni0. K8s会为网桥分配子网范围,而之后提供的服务都会在这个范围里。

需要注意的是docker0和cni0网桥是同时存在的,并不是cni0代替了docker0。CNi的设计思想就是K8s创建pod时,在启动infra容器后可以直接调用CNI插件,为其配置符合预期的网络栈。

所以如果我想实现一个网络方案,只要做两步:1.实现这个网络方案本身,如创建路由设备、配置相关路由信息。 2.实现该方案对应的CNI插件,让其能被放到pod中。
某些方案,如Flannel已经内置了CNI插件,对于Weave、Caliao等项目来说,安装插件时必须把相关插件放在对应目录下,再启动网络方案(fanneld)本身,网络方案会去找插件并安装运行。

归纳其网络特点就是:
1. 所有容器都可以直接使用 IP 地址与其他容器通信,而无需使用 NAT。
2. 所有宿主机都可以直接使用 IP 地址与所有容器通信,而无需使用 NAT。反之亦然。
3. 容器自己“看到”的自己的 IP 地址,和别人(宿主机或者容器)看到的地址是完全一样的。
k8s集群里只关心最终网络可以连通,而不需要在内部去实现各种复杂的网络模块,使用CNI可以方便灵活地自定义网络插件,网络可以独立。

龙头老大Calico 项目

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

如前所述,这个三层网络方案得以正常工作的核心,是为每个容器的 IP 地址,找到它所对应的、“下一跳”的网关。不过,不同于 Flannel 通过 Etcd 和宿主机上的 flanneld 来维护路由信息的做法,Calico 项目使用了一个“重型武器”来自动地在整个集群中分发路由信息。这个“重型武器”,就是 BGP。

BGP 的全称是 Border Gateway Protocol,即:边界网关协议。它是一个 Linux 内核原生就支持的、专门用在大规模数据中心里维护不同的“自治系统”之间路由信息的、无中心的路由协议。 两个自主网络想用IP地址通信就必须用路由器,这种路由器就叫边界网关,该路由器里有其他网络的主机路由信息。而在巨大的隔离网络之中,就需要要BGP协议,就是要在大规模网络中实现节点路由信息共享。

在了解了 BGP 之后,Calico 项目的架构就非常容易理解了。它由三个部分组成:
1. Calico 的 CNI 插件。这是 Calico 与 Kubernetes 对接的部分。我已经在上一篇文章中,和你详细分享了 CNI 插件的工作原理,这里就不再赘述了。
2. Felix。它是一个 DaemonSet,负责在宿主机上插入路由规则(即:写入 Linux 内核的 FIB转发信息库),以及维护 Calico 所需的网络设备等工作。
3. BIRD。它就是 BGP 的客户端,专门负责在集群里分发路由规则信息。