overlay网络模型与flannel实践

这两天了解一些关于docker容器跨节点网络模型的知识,觉得收获还是挺大的,并且也动手尝试搭建了flannel容器网络,所以非常有必要把思考记录下来。

过往的知识

在讲容器的网络之前,先得讲讲服务器之间是怎么通讯的。

通过交换机连在一起的节点属于同一个网段,比如我有2台属于同网段的机器:

以及

它们的IP地址&掩码是相同的。

通过查看路由表,可以确定它俩连在一台交换机上,属于局域网关系:

以及

如果某个节点要向目标IP发包,那么先查自己的路由表。

  • 如果目标IP符合172.18.8.0网段,那么说明目标IP在局域网里,可以直接ARP广播问一下目标IP的MAC地址,然后直接从enp0s3网卡送出去就可以送达。
  • 否则就需要ARP问一下网关172.18.11.254的MAC地址,然后把包发给网关,让网关想办法去。

网关怎么想办法呢?网关一般带路由功能,它继续查看自己的路由表决定包往哪里送,就像刚刚我们机器做的那样,这是一个往往复复的过程。

容器带来的网络问题

我们最终希望容器技术可以像真的机器一样,每个容器有自己的IP并认为自己就是普通的机器,不同机器上的容器之间可以直接基于容器的IP通讯,完全不用需要在乎自己运行在物理机还是虚拟机上。

然而我们手里的机器只有一块网卡,也只分配了一个公网IP,这就是我们的现状,所以如何给每个容器不一样的IP呢?

overlay的本质

因为上述目标,所以出现了若干种实现容器跨节点网络通讯的技术方案。

overlay不是具体方案,它是一种实现思路。

我们可以通过linux虚拟化技术给每个容器虚拟化一个网卡,并且赋予一个独一无二的虚拟IP,这些IP与我们物理机所处的网络中的任何IP都不冲突。

这感觉就像自欺欺人,我们在容器里配了一些假的IP,然后还期望容器可以基于假IP调用到另外一个机器上的容器,但overlay的确就是要做这样一件事情。

我们知道物理机有真实IP,可以基于现有网络拓扑实现同网段或者跨网段的任意通讯,那么最直接的想法就是物理机把容器发出来的包封装一下,基于物理机所处的网络把包送到目标容器所处的物理机IP,然后目标物理机再进行解封得到原始容器的包,交给目标容器处理。

Overlay重叠网络就是这个意思,在现有的网络环境之上隐藏或者说虚拟一套假IP,并且可以用物理网络进行跨节点的运输,而一般这套封装协议就是vxlan协议。经过在现有网络上封装与解封,不仅可以实现虚拟IP之间的互通,而且容器自身完全无感知,就像真的有这些虚拟IP一样。

overlay实现原理

容器发出的包,源IP、目标IP、源MAC、目标MAC都是假的,经过宿主机的vxlan协议封装后,外层的源IP、目标IP、源MAC、目标MAC都变成真的,内层封装的仍旧是容器的包,这就是基本的overlay原理。

overlay只是一个思路,在实现上就有多种方案,原理有差异,但是思想与目标都和上述相同。

在实现overlay的时候,要搞定2个事情:

  • 确保容器的虚拟IP不重复,因为经过overlay封装后,容器之间通讯就和真实的网络环境一样,IP冲突就无法通讯了。
  • 物理机封装容器发出的包时,得知道目标容器IP在哪个物理机IP上。

实现方法一般就是搭建一套etcd来实现虚拟IP的唯一分配,以及维护虚拟IP与物理IP之间的关系。

因此,每台物理机上要有一个进程与etcd交互来申请虚拟IP分配到机器上的容器,以及获知虚拟IP与物理IP的关系,完成overlay包的封装。

不同overlay方案在实现虚拟IP分配时就会有不同,我大概发现了2种思路:

  1. etcd中的IP池子是同网段的,启动容器之前去etcd获取一个虚拟IP,因为同网段通讯只需要交换机转发即可,所以就在每台机器上虚拟一个交换机,这样容器的包就可以被虚拟交换机拿到,通过overlay封装发出。
  2. etcd中的IP池子是个大网段,每台物理机一次性获取其中的一个子网段占为己有,启动容器前只需要从占据的子网段中分配一个IP。同一台机器上的容器网段相同,所以需要一个交换机即可互通。不同机器上的容器网段不同,所以在每台机器上配置一个虚拟网关来协助路由,这样跨节点通讯就会被虚拟网关拿到,通过overlay发出。

上述总结的思路,其实就是想说明overlay虚拟化网络和物理世界的网络工作原理是一致的,如果虚拟了IP就要根据情况虚拟对应的中间设备。

以flannel为例

flannel是docker的一款网络插件,它实现了基于vxlan封装的overlay模型。

通过安装配置flannel,我们就可以打通容器之间的虚拟网络,对了,它属于第2种思路,也就是每台机器占据一个虚拟IP网段,不同机器的网段不同。

基本环境

我创建了3台virtualbox+ubuntu 16的虚拟机,配置他们的网络为桥接,这样它们的虚拟MAC地址就暴露到局域网了,可以直接从局域网中分配IP,可以和宿主机通讯,可以互相通讯,也可以访问外网。

接着安装docker,大家根据docker官网的步骤通过apt-get安装即可,然后需要给docker配置镜像加速器,大家注册https://www.daocloud.io/获取加速器,配置到3台ubuntu机器上即可,这些基础工作就不详细说了。

3台机器的情况如下,大家就把他们看做3台物理机就好了:

搭建etcd的机器 172.18.10.95
搭建docker和flannel 172.18.10.96
搭建docker和flannel 172.18.10.98

安装etcd

前面说过,etcd用来维护虚拟IP池以及物理IP与虚拟IP的映射关系。

登录95机器,先下载linux amd64版本的release二进制:https://github.com/etcd-io/etcd/releases

解压启动:

这样单机的etcd就可以工作了。

安装flannel

我以96机器为例,说明安装过程和原理。

下载linux amd64的flannel二进制:https://github.com/coreos/flannel/releases

解压后会有一个flanneld的二进制程序以及一个mk-docker-opts.sh的脚本。

负责给容器分配IP以及完成vxlan打包/解包的就是flanneld程序,因此它需要与etcd交互。

启动flanneld最基本的要求就是配置etcd的地址,其他参数保持默认一般是可以工作的:

启动后会有一些警告日志,因为我们没有在etcd配置flannel的虚拟IP池信息,所以flanneld无法获取虚拟IP网段。

配置ip池

回到95机器上,打开一个flannel.json文件,我们配置一下flannel的IP池:

这个意思是整个大IP池是10.x.x.x,每台机器可以拿走一个掩码为24的网段。

比如某台机器可以申请到一个这样的网段:10.0.5.0/24,那么这台机器上的容器可以分配的IP范围就是10.0.5.0~10.0.5.255,也就是最多部署255个容器IP就会用尽。

同样道理,大IP池总共可以划分出2^16个网段,即支持2^16台机器加入到flannel的集群中来。

我们使用etcd v2版本的API,把配置上传到flanneld的默认位置:

物理机网络配置

现在查看96机器的flanneld日志,会发现它已经分配到了IP段:10.0.5.0/24。

观察机器网络设备的变化,会发现多了一个叫做flannel1.1的虚拟网卡,这个网卡的分配到了一个IP地址是10.0.5.0:

就和物理网卡有自己的IP地址一样,虚拟网卡也可以有自己的虚拟IP,现在IP就是10.0.5.0。

这张虚拟网卡实际背后就是flanneld进程,任何发往这张网卡的数据都会被flanneld进程处理,从而实现vxlan封装,并通过物理网卡发出去,大家理解这一点即可。

配置docker

既然flanneld已经分配到了虚拟IP网段,接下来我们要让docker生成容器时从这个网段中分配IP,这一点只需要给docker改改配置就可以实现。

一旦flanned成功分配到网段,就会生成这样一个配置文件:

其中记录了分配到的subnet是10.0.5.1/24,之所以末尾是1,是因为需要预留出了10.0.5.0给flannel1.1虚拟网卡用。

我们现在执行伴随flannel下载的那个脚本:

可以生成一个docker的启动命令参数:

–bip参数就是用来告诉docker,创建容器时从这个IP段内分配IP,是不是很巧妙?docker本来就要给容器分配IP的呀。

现在修改docker的启动脚本即可:

修改如下:

然后让systemctl重新加载配置,然后重启docker:

物理机网络配置

现在可以看到,docker默认创建的docker0网桥,其IP分配到了10.0.5.1。不仅如此,后续创建的容器都会在这个网段下分配。

所有的容器都将连在docker0上,因为它们的IP段相同,所以本机容器之间可以直接基于docker0通讯,docker0网桥就是虚拟交换机的概念。

容器内网络

接下来,我们创建一个容器,看看容器内的网络配置:

docker给容器分配的IP:

查看容器内的路由:

可见,发往10.0.5.0/24的包属于同网段通讯,在docker0网桥上通过arp可以直接得到目标mac地址,经过docker0交换即可。

其他的包则直接发往默认网关docker0(10.0.5.1),可以为理解docker0既支持路由又支持交换,像家庭路由器一样。

从物理机上可以看出docker0虚拟机交换机上,拉了一根到容器eth0网口的网线:

这么描述大家应该更容易理解。

docker0继续路由

docker0是物理机上的网桥,所以使用的是物理机的路由表。

如果发往的目标IP是外网IP(例如:百度),那么只能走物理机的默认网关,不过这里会经过iptables的NAT转换,百度那边认为是我们的物理机在访问它。

我们关注的是容器访问另外一台机器上的容器,现在因为没有启动另外一台机器,所以没有我们想看到的路由规则。

配置98机器的flannel

配置过程与96机器一致,分配到的网段是:

观察96机器的路由表,发现多了一条路由规则:

也就是如果目标容器地址属于10.0.20.0/24网段,那么通过flannel1.1虚拟网卡发出,所以flanneld进程就可以有机会进行vxlan封装,并通过物理网卡发往目标物理机。

同样的,98机器的路由表也是类似的:

发往10.0.5.0的都交给flannel1.1处理,而flanneld进程要做的事情就是根据etcd中的记录获知目标物理机IP,然后vxlan封装送过去。

我们去看一下etcd中的记录就会更清晰的认识到这一点:

一目了然,虚拟网段与物理机IP的关系都记录在etcd中。

测试

现在可以让2台物理机上的容器互相ping通,并且是基于虚拟IP:

反过来也是一样的:

总结

以flannel为例,通过实践可以更加具体的感受虚拟化网络的玩法和思路。

不过flannel性能比较差劲,目前据说最好的选型是calico方案,但是理解和使用起来也会更复杂。

但是无论选用任何一种网络模型,最终实现的效果都是一样的,就是容器基于虚拟IP互通,这是所有网络插件的共同目标,所以真的要用起来并不需要把插件细节搞明白,理解原理就差不多了。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~