我们说了,Linux容器网络的实现原理,在一般默认配置下,不同宿主机上的容器通过分配的IP 地址进行访问是做不到的

为了解决这个容器的跨宿主机通信的问题,社区出现了容器网络方案

那么现在比较火的容器间通信项目,当属Flannel项目

这是CoreOS公司主推的容器网络方案,Flannel 项目只是一个框架,真正提供网络实现的,是Flannel的后端实现,目前,Flannel支持三种后端的实现

VXLAN host-gw UDP

这三种不同的后端实现,代表了三种容器跨主网络的主流实现方式

先说UDP模式,是Flannel最早支持的一种方式,也是性能较差的一种方式,这个模式已经被废弃,不过Flannel使用UDP也是因为这是最容易实现和理解的容器跨主网络实现

我们首先说,有两台宿主机,一台宿主Node 1上有一个容器 container-1,ip地址是100.96.1.2对应的网桥是100.96.1.1/24

Node-2上也有一个容器container-2,ip地址是100.96.2.3,对应的网桥是100.96.2.1/24

现在需要container-1访问container-2

这样container-1发起的IP包,源地址是100.96.1.2,目的地址是100.96.2.3.目的地址100.96.2.3不在Node1的网桥的网段里,这个IP包会被交给默认的路由规则,通过网桥发往宿主上的网卡

交给宿主机指定的路由规则进行转发,所以Flannel会在宿主上创建出一系列的路由规则

# 在Node 1上

$ ip route

default via 10.168.0.1 dev eth0

100.96.0.0/16 dev flannel0  proto kernel  scope link  src 100.96.1.0

100.96.1.0/24 dev docker0  proto kernel  scope link  src 100.96.1.1

10.168.0.0/24 dev eth0  proto kernel  scope link  src 10.168.0.2

上面说Flannel让宿主机将所有发往100.96.0.0的网端的ip包,都发往flannel0的设备,这个设备实际上是一个TUN设备

Linux中,TUN设备是一种工作在三层Network Layer 的虚拟网络设备,TUN设备的功能非常简单,在操作系统内核用户应用程序之间传递IP包

这就是内核态和用户态的沟通

而具体的流程就是从docker0发出IP包,就会根据路由表进入到flannel0设备,宿主机的flanneld进程就能收到这个IP包,然后flanneld看到这个IP包的目的地址是100.96.2.3,就将其发送给Node2的宿主机

flanneld如何知道这个IP地址对应的容器节点的呢?

这就是Flannel中一个非常重要的概念,子网Subnet,在Flannel管理的容器网络中,一台宿主机上的所有容器,都属于该宿主机被分配的一个子网,Node1的子网是100.96.1.0/24 container-1的具体IP就是这个网段下的 100.96.1.2,Node2的网段是100.96.2.0/24,container-2的IP地址是100.96.2.3

这些子网和宿主机对应的关系,正是保存Etcd当中

$ etcdctl ls /coreos.com/network/subnets

/coreos.com/network/subnets/100.96.1.0-24

/coreos.com/network/subnets/100.96.2.0-24

/coreos.com/network/subnets/100.96.3.0-24

flanneld进程在处理经由flannel0传入的IP包时候,就可以根据目的IP的地址,匹配到对应的子网.从Etcd中找到这个子网对应的宿主机的IP地址是10.168.0.3,如下所示

$ etcdctl get /coreos.com/network/subnets/100.96.2.0-24

{“PublicIP”:”10.168.0.3″}

这样对于Flannel来说,只要Node1和Node2是互通的,那么就可以进行彼此的访问

在flanneld收到container-1发给container-2的IP包之后,就会将这个IP包封装在一个UDP包中,然后进行发送给Node2

所以说,flanneld在收到container-1发给container-2的IP包之后,就会将IP包,直接封装在一个UDP包上,发给Node-2

而宿主机间的通信,通过的是每个宿主机上的8285端口,这样flanneld只需要将UDP发往Node2的8285端口就可以了

这样的一个普通的宿主机间的UDP通信,就可以进行了,但本质上是flanneld设备间的通信

这样,node2的flannel设备收到了这个包,就会解包并发给对应的TUN设备,flannel0设备

这样,我们讲解的TUN设备的原理,正是一个从用户态往内核态的流动方向,所以Linux内核网络栈就会负责处理这个IP包,具体的处理方式,就是通过本机的路由表来查找这个IP包的下一个流向

于是Node2宿主机就会读取到对应的路由规则,然后按照这个对着转给对应的网桥

接下来的流程,就是讲数据包发往正确的端口,然后发给container-2的Network Namespace中

这样,就是Flannel在进行UDP模式下的通信

而Flannel除了负责转发这些IP,就是需要在宿主机上分配网段,并且在宿主机上进行相关的网络配置

基本的原理图如下所示

图片

这样,UDP模式就是一个三层的Overlay网络,对IP包进行UDP的封装,然后进行接收端的解封包,然后转发给目标容器,就好比打通了一个隧道,而且用的是原生的网络配置

但是缺点在于多次内核态和用户态的数据拷贝

图片

容器的ip包经由docker0网桥进入内核

IP包通过路由表进入TUN设备,然后回到用户态的flanneld进程

flanneld进行UDP封包后进入内核态,然后将UDP的包通过宿主机的eth0发出去

这样,进行UDP的封包和解封包,进行了上下文的切换和用户态的转换,这就是Flannel UDP性能不好的原因

在进行系统级别的编程的时候,有一个非常重要的优化原则,减少从用户态到内核态的切换,将核心的处理逻辑都放在内核态进行,这就是Flannel不再使用UDP的原因

VXLAN,Virtual Extensible LAN 虚拟可扩展局域网,是Linux内核本身支持的网络虚拟化技术,所以VXLAN,可以在内核态上实现封装和解封装的过程,并且是一种Overlay的网络覆盖技术

VXLAN的网络设计思想,在现有的三层网络上,负载上一层自我维护的二层网络,可以在一个局域网内一样,自由通信

为了打通二层的隧道,VXLAN会在宿主机上设置一个特殊的网络设备作为隧道的两端,就是VTEP,VXLAN Tunnel End Point

这个设备的作用,就是进行解封装 二层数据帧,这个工作流程,在内核态内完成的

图片

可以看出来,图中每台宿主机上名为flannel.1的设备,就是VXLAN所需要的VTEP的设备,既有IP,也有MAC,所以我们Container-1的IP地址是10.1.15.2,访问的地址是10.1.16.3

那么和前面的流程基本一致,先发出请求,目的是10.1.16.3的IP包,然后出现在docker0网桥,然后被路由到本机flannel.1设备处理,到了隧道入口,就是原始的IP包,然后找到对应目标的宿主机的VTEP

那么这个不同宿主机之间的关系,是由每台宿主机上的flanneld进程负责维护的

每当我们有一个节点加入到这个Flannel网络后,就会有一条新的路由规则

$ 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

这个规则的意思是,凡事发往10.1.16.0/24网段的IP包,经过flannel.1的包,最后发往网关的地址是10.1.16.0

这样就发往了Node2上的VTEP设备

这样就通过VTEP设备之间,就可以组成一个虚拟的二层网络,既通过二层数据帧进行通信

所以在这种,我们收到了原始包,就需要将原始的IP包进行包装,封装为了一个二层数据帧,然后发给了目的VTEP设备,我们需要解决目的的VTEP设备的MAC地址是什么呢

此时,根据前面的路由记录,已经知道了目的VTEP设备的IP地址,而根据三层IP地址查询对应的二层MAC地址,正是ARP表的功能

有了这个目的VTEP的MAC地址,就可以进行二层封包工作了

这样,我们就会把VTEP设备的MAC地址,填写在图中Inner Ethernet Header字段,得到了二层数据帧

那么我们就加了一个新的二层头,新封装出的数据帧,并不能在我们宿主机二层网络传输,我们将其称为内部数据帧

所以,Linux内核还需要将内部数据帧,进一部分封装出外部数据帧

我们外面加一个特殊的VXLAN头,这个头里面是一个重要的标志,叫做VNI,是VTEP设备识别某个数据帧是不是交给自己处理的重要表示

Linxu将这个数据帧封装一个UDP包中发出去

所以跟UDP模型类型,只会意味自己的flannel.1设备支持向另一个宿主机的flannel.1设备,发起了一个普通的UDP连接,但是这个UDP包中,是一个完整的二层数据帧

这个UDP该发给哪个宿主机呢,这时候flannel.1会扮演一个网桥的角色,网桥转发的一句,来源于一个Forwarding Database的转发数据库,而且flannel,1网桥对应的FDB,正是flanneld进程负责维护的,可以通过bridge fdb进行查看到

这个里面存储了对应宿主机的外部IP

所以整体的封包就完成了

UDP是一个四层数据包,所以Linux内核会在前面加上一个IP头,Outer IP Header,组成一个IP包,然后加上目的主机的IP,然后加上对应的MAC地址,这个MAC地址,需要Node1的ARP表进行学习的,完全无需Flannel 维护,这个时候,封装外部帧如下

图片

这样Node1就可以将数据帧从自身的eth0网卡发出去了,这个帧会到达宿主机网络来到Node2的网卡,然后Node2先收进来,然后解包,然后判断VXLAN Header,并且VNI=1,所以Linux内核对VNI的值进行判定,交给了对应的flannel设备

然后进一步拆包,获取到了原始IP包,进行相关的转发到Network Namespace

我们本次了解了Flannel UDP和VXLAN模式的工作模式,都可以称为隧道机制,也是很多网络插件的模式,Weave也是这两种机制

然后,VXLAN组件的网络,就是不同宿主机上的VTEP设备,flannel设备组成的虚拟二层网络,对于VTEP设备来说,发出的内部数据帧,在整体的二层网络上流动

我们根据隧道机制,实现了三层网络的联通,但是关于二层网络的联通,能够保证吗

关于这个问题,flannel维护的必然是子网,也就是三层上的概念,想要直接打通二层的网路联通,那么就需要考虑不同宿主机之间容器如何知道彼此的MAC地址呢?可以参考VXLAN的解决方案,每一个VTEP都通过IGMP,加入一个组播组需要VTEP都挂载到一个组内,方便进行广播arp请求,从而打通二层网络

发表评论

邮箱地址不会被公开。 必填项已用*标注