运行环境情况千差万别,所以容器网络方案也是五花八门,官方都列出一堆网络模型:https://kubernetes.io/zh/docs/concepts/cluster-administration/networking/

最常见的网络插件就是flanneld和calico,最常见的模式是隧道模式或者三层网络模型,下面来从CNI插件来分析下容器网络模型

Vxlan

UDP模式因为性能问题被废弃,因为其仅在发出包的过程中,就要经过三次用户态和内核态的数据拷贝。目前使用较多的就是vxlan模式。

VXLAN,即 Virtual Extensible LAN(虚拟可扩展局域网),是 Linux 内核本身就支持的一种网络 虚似化技术。所以说,VXLAN 可以完全在内核态实现封装和解封装的工作,从而通过相似的“隧道”机制,构建出覆盖网络(Overlay Network)。

以flanneld为例通信流程:

                 ┌───────────────────────────────────────────┐                     
                 │                                           │                     
                 │ 10.168.0.2/24                             ▼  10.168.0.3/24                   
┌───────────┬─────────┬──────────────┐      ┌───────────┬─────────┬───────────────┐
│           │  eth0   │              │      │           │  eth0   │               │
│           └─────────┘              │      │           └─────────┘               │
│                ▲                   │      │                │                    │
│                │                   │      │                ▼                    │
│         ┌─────────────┐            │      │         ┌─────────────┐             │
│   VTEP  │  flannel.1  │10.1.15.0/32│      │  VTEP   │  flannel.1  │10.1.16.0/32 │
│         └─────────────┘            │      │         └─────────────┘             │
│                ▲                   │      │                │                    │
│                │                   │      │                ▼                    │
│         ┌─────────────┐            │      │         ┌─────────────┐             │
│         │     cni0    │10.1.15.1/24│      │         │     cni0    │10.1.16.1/24 │
│         └─────────────┘            │      │         └─────────────┘             │
│                ▲                   │      │                │                    │
│        ┌───────┴──────────┐        │      │        ┌───────┴──────────┐         │
│        │                  │        │      │        ▼                  ▼         │
│  ┌──────────┐       ┌──────────┐   │      │  ┌──────────┐       ┌──────────┐    │
│  │container1│       │container2│   │      │  │container1│       │container2│    │
│  └──────────┘       └──────────┘   │      │  └──────────┘       └──────────┘    │
│   10.1.15.1                        │      │                       10.1.16.3     │
│              Node1                 │      │              Node2                  │
└────────────────────────────────────┘      └─────────────────────────────────────┘

flanneld会在宿主机上新建一个VTEP设备flannel.1,既有IP又有MAC地址,当一个节点加入集群的时候,flanneld会把上面节点定义的子网网段写入每个节点的路由,以上图来举例,node2加入集群的时候,node1上会写入一条路由

1
2
3
4
5
6
$ route -n 
Kernel IP routing table 
Destination  Gateway  Genmask  Flags Metric Ref  Use Iface
...

10.1.16.0 10.1.16.0  255.255.255.0  UG  0 0  0 flannel.1

这样node1就知道了凡是发往 10.1.16.0/24 网段的 IP 包,都需要经过 flannel.1 设备发出,并且,它最后被发往的网关地址是:10.1.16.0, 并且10.1.16.0 正是 Node 2 上的 VTEP 设 备(也就是 flannel.1 设备)的 IP 地址

那vxlan又是怎么模拟二层通信的呢?

node2的flanneld启动的时候,会在node1的ARP表上加一条数据

1
2
3
# 在 Node 1 上 
$ ip neigh show dev flannel.1 
10.1.16.0 lladdr 5e:f8:4f:00:e3:37 PERMANENT

这条记录的意思非常明确,即:IP 地址 10.1.16.0,对应的 MAC 地址是 5e:f8:4f:00:e3:37,

然后去一个叫作 FDB(Forwarding Database)的转发数据库查找MAC地址和目的宿主机IP地址的关系

1
2
3
# 在 Node 1 上,使用“目的 VTEP 设备”的 MAC 地址进行查询 
$ bridge fdb show flannel.1 | grep 5e:f8:4f:00:e3:37 
5e:f8:4f:00:e3:37 dev flannel.1 dst 10.168.0.3 self permanent

最后node1的内核就会把node2的mac地址、IP地址和一个vxlan表示VNI封到数据包头,伪装成一个二层包,然后内核把这个数据帧封装到UDP包里发出去

目的宿主机收到包之后根据VNI进行解包操作,然后宿主机拿到目的容器的MAC地址之后,在本身的CAM表(即交换机通过 MAC 地址学习维护的端口和 MAC 地址的对应表)里面找到端口对应的veth pair,将数据发往这个veth pair,veth pair往往是一对,另一端到容器内部,所以数据就发到了容器内部

三层网络

vxlan模式通用性强,但是因为要封包解包,存在网络消耗,所以不少网络插件提供三层网络路由直通方式

flanneld有host-gw模式,calico有BGP模式

flannel host-gw

当你设置 Flannel 使用 host-gw 模式之后,flanneld 会在宿主机上创建这样一条规则,以上面的node 1 为例

1
2
3
$ ip route 
...
10.1.16.1/24 via 10.168.0.3 dev eth0

就是设置对应网段的IP包,经过本机的eth0,发到下一跳的IP地址(其实就是node2的eth0的IP)

当 IP 包从网络层进入链路层封装成帧的时候,eth0 设备 就会使用下一跳地址对应的 MAC 地址,这个数据帧就会从 Node 1 通过宿主机的二层网络顺利到达 Node 2 上

Node 2 的内核网络栈从二层数据帧里拿到 IP 包后,会“看到”这个 IP 包的目的Pod IP 地址,然后转发到CNI0网桥

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

Flannel 子网和主机的信息,都是保存在 Etcd 当中的。flanneld 只需要 WACTH 这些数 据的变化,然后实时更新路由表即可,当然用host-gw模式要求二层是联通的。

calico

calico也是比较类似的,不过calico设置这个路由是通过BGP来做的

每个node上运行一个软路由软件bird,并且被设置成BGP Speaker,与其它node通过BGP协议交换路由信息。

可以简单理解为,每一个node都会向其它node通知这样的信息:

我是X.X.X.X,某个IP或者网段在我这里,它们的下一跳地址是我。

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

calico与Flannel 的 host-gw 模式的另一个不同之处, 就是它不会在宿主机上创建任何网桥设备。

                 ┌─────────────────────────────────────────────┐                    
                 │                                             │                    
                 │                                             ▼                    
┌───────────┬─────────┬──────────────┐        ┌───────────┬─────────┬──────────────┐
│           │  eth0   │              │        │           │  eth0   │              │
│           └─────────┘              │        │           └─────────┘              │
│                ▲                   │        │                │                   │
│                │                   │        │                ▼                   │
│         ┌─────────────┐            │        │         ┌─────────────┐            │
│         │   routes    │            │        │         │   routes    │            │
│         └─────────────┘            │        │         └─────────────┘            │
│                ▲                   │        │                │                   │
│        ┌───────┴──────────┐        │        │        ┌───────┴──────────┐        │
│        │                  │        │        │        ▼                  ▼        │
│  ┌───────────┐      ┌───────────┐  │        │  ┌───────────┐      ┌───────────┐  │
│  │cali9c02e56│      │cali20949C │  │        │  │caliacsdfe5│      │cali20234g │  │
│  └───────────┘      └───────────┘  │        │  └───────────┘      └───────────┘  │
│        ▲                  ▲        │        │        │                  │        │
│        │                  │        │        │        │                  │        │
│        │                  │        │        │        ▼                  ▼        │
│  ┌──┬────┬───┐      ┌──┬────┬──┐   │        │  ┌──┬────┬───┐      ┌──┬────┬──┐   │
│  │  │eth0│   │      │  │eth0│  │   │        │  │  │eth0│   │      │  │eth0│  │   │
│  │  └────┘   │      │  └────┘  │   │        │  │  └────┘   │      │  └────┘  │   │
│  │           │      │          │   │        │  │           │      │          │   │
│  └───────────┘      └──────────┘   │        │  └───────────┘      └──────────┘   │
│              Node1                 │        │              Node2                 │
└────────────────────────────────────┘        └────────────────────────────────────┘

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

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

10.1.16.3 dev cali20234g scope link

即发往 10.233.2.3 的 IP 包,应该进入cali20234g设备,这样数据就能通了

注意Calico也要求二层联通,如果不能联通就需要打开IPIP模式,IPIP模式设置的路由也和BGP有所不同

10.233.2.0/24 via 192.168.2.2 tunl0

尽管这条规则的下一跳地址仍然是 Node 2 的 IP 地址,但这一次,要负责将 IP 包 发出去的设备,变成了 tunl0,Calico 使用的这个 tunl0 设备,是一个 IP 隧道(IP tunnel)设备。IP 包进入 IP 隧道设备之后,就会被 Linux 内核的 IPIP 驱动接管。IPIP 驱动 会将这个 IP 包直接封装在一个宿主机网络的 IP 包中,这样原先从容器到 Node 2 的 IP 包,就被伪装成了一个从 Node 1 到 Node 2 的 IP 包

node2拿到之后内核通过IPIP驱动进行解包,从而拿到原始的 IP 包。然后,原始 IP 包就会经过路由规则和 Veth Pair 设备到达目的容器内部。