我们说了,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请求,从而打通二层网络