内容简介:这两天了解一些关于docker容器跨节点网络模型的知识,觉得收获还是挺大的,并且也动手尝试搭建了flannel容器网络,所以非常有必要把思考记录下来。在讲容器的网络之前,先得讲讲服务器之间是怎么通讯的。通过交换机连在一起的节点属于同一个网段,比如我有2台属于同网段的机器:
这两天了解一些关于 docker 容器跨节点网络模型的知识,觉得收获还是挺大的,并且也动手尝试搭建了flannel容器网络,所以非常有必要把思考记录下来。
过往的知识
在讲容器的网络之前,先得讲讲服务器之间是怎么通讯的。
通过交换机连在一起的节点属于同一个网段,比如我有2台属于同网段的机器:
enp0s3 Link encap:Ethernet HWaddr 08:00:27:ac:f0:7f inet addr:172.18.10.96 Bcast:172.18.11.255 Mask:255.255.252.0
以及
enp0s3 Link encap:Ethernet HWaddr 08:00:27:17:0e:a9 inet addr:172.18.10.98 Bcast:172.18.11.255 Mask:255.255.252.0
它们的IP地址&掩码是相同的。
通过查看路由表,可以确定它俩连在一台交换机上,属于局域网关系:
172.18.8.0 * 255.255.252.0 U 0 0 0 enp0s3 default 172.18.11.254 0.0.0.0 UG 0 0 0 enp0s3
以及
172.18.8.0 * 255.255.252.0 U 0 0 0 enp0s3 default 172.18.11.254 0.0.0.0 UG 0 0 0 enp0s3
如果某个节点要向目标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种思路:
- etcd中的IP池子是同网段的,启动容器之前去etcd获取一个虚拟IP,因为同网段通讯只需要交换机转发即可,所以就在每台机器上虚拟一个交换机,这样容器的包就可以被虚拟交换机拿到,通过overlay封装发出。
- etcd中的IP池子是个大网段,每台物理机一次性获取其中的一个子网段占为己有,启动容器前只需要从占据的子网段中分配一个IP。同一台机器上的容器网段相同,所以需要一个交换机即可互通。不同机器上的容器网段不同,所以在每台机器上配置一个虚拟网关来协助路由,这样跨节点通讯就会被虚拟网关拿到,通过overlay发出。
上述总结的思路,其实就是想说明overlay虚拟化网络和物理世界的网络工作原理是一致的,如果虚拟了IP就要根据情况虚拟对应的中间设备。
以flannel为例
flannel是docker的一款网络插件,它实现了基于vxlan封装的overlay模型。
通过安装配置flannel,我们就可以打通容器之间的虚拟网络,对了,它属于第2种思路,也就是每台机器占据一个虚拟IP网段,不同机器的网段不同。
基本环境
我创建了3台virtualbox+ubuntu 16的虚拟机,配置他们的网络为host-only,这样它们的虚拟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 。
解压启动:
nohup ./etcd --listen-client-urls http://0.0.0.0:2379 -advertise-client-urls http://0.0.0.0:2379 &
这样单机的etcd就可以工作了。
安装flannel
我以96机器为例,说明安装过程和原理。
下载linux amd64的flannel二进制: https://github.com/coreos/flannel/releases 。
解压后会有一个flanneld的二进制程序以及一个mk-docker-opts.sh的脚本。
负责给容器分配IP以及完成vxlan打包/解包的就是flanneld程序,因此它需要与etcd交互。
启动flanneld最基本的要求就是配置etcd的地址,其他参数保持默认一般是可以工作的:
nohup ./flanneld -etcd-endpoints http://172.18.10.95:2379 &
启动后会有一些警告日志,因为我们没有在etcd配置flannel的虚拟IP池信息,所以flanneld无法获取虚拟IP网段。
配置ip池
回到95机器上,打开一个flannel.json文件,我们配置一下flannel的IP池:
{ "Network": "10.0.0.0/8", "SubnetLen": 24, "Backend": {"Type": "vxlan"} }
这个意思是整个大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的默认位置:
./etcdctl set /coreos.com/network/config < ~/flannel.json
物理机网络配置
现在查看96机器的flanneld日志,会发现它已经分配到了IP段:10.0.5.0/24。
观察机器网络设备的变化,会发现多了一个叫做flannel1.1的虚拟网卡,这个网卡的分配到了一个IP地址是10.0.5.0:
flannel.1 Link encap:Ethernet HWaddr 36:ba:b9:5c:8d:d6 inet addr:10.0.5.0 Bcast:0.0.0.0 Mask:255.255.255.255 inet6 addr: fe80::34ba:b9ff:fe5c:8dd6/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1 RX packets:53 errors:0 dropped:0 overruns:0 frame:0 TX packets:53 errors:0 dropped:8 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:4452 (4.4 KB) TX bytes:4452 (4.4 KB)
就和物理网卡有自己的IP地址一样,虚拟网卡也可以有自己的虚拟IP,现在IP就是10.0.5.0。
这张虚拟网卡实际背后就是flanneld进程,任何发往这张网卡的数据都会被flanneld进程处理,从而实现vxlan封装,并通过物理网卡发出去,大家理解这一点即可。
配置docker
既然flanneld已经分配到了虚拟IP网段,接下来我们要让docker生成容器时从这个网段中分配IP,这一点只需要给docker改改配置就可以实现。
一旦flanned成功分配到网段,就会生成这样一个配置文件:
root@ubuntu:~# cat /run/flannel/subnet.env FLANNEL_NETWORK=10.0.0.0/8 FLANNEL_SUBNET=10.0.5.1/24 FLANNEL_MTU=1450 FLANNEL_IPMASQ=false
其中记录了分配到的subnet是10.0.5.1/24,之所以末尾是1,是因为需要预留出了10.0.5.0给flannel1.1虚拟网卡用。
我们现在执行伴随flannel下载的那个脚本:
sh mk-docker-opts.sh -c
可以生成一个docker的启动命令参数:
root@ubuntu:~# cat /run/docker_opts.env DOCKER_OPTS=" --bip=10.0.5.1/24 --ip-masq=true --mtu=1450"
–bip参数就是用来告诉docker,创建容器时从这个IP段内分配IP,是不是很巧妙?docker本来就要给容器分配IP的呀。
现在修改docker的启动脚本即可:
/etc/systemd/system/multi-user.target.wants/docker.service
修改如下:
[Service] Type=notify # the default is not to use systemd for cgroups because the delegate issues still # exists and systemd currently does not support the cgroup feature set required # for containers run by docker EnvironmentFile=/run/docker_opts.env ExecStart=/usr/bin/dockerd -H unix:// $DOCKER_OPTS
然后让systemctl重新加载配置,然后重启docker:
root@ubuntu:~# systemctl daemon-reload root@ubuntu:~# systemctl restart docker
物理机网络配置
root@ubuntu:~# ifconfig docker0 Link encap:Ethernet HWaddr 02:42:2c:3b:dd:2b inet addr:10.0.5.1 Bcast:10.0.5.255 Mask:255.255.255.0 inet6 addr: fe80::42:2cff:fe3b:dd2b/64 Scope:Link UP BROADCAST MULTICAST MTU:1500 Metric:1 RX packets:11025 errors:0 dropped:0 overruns:0 frame:0 TX packets:11742 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:803316 (803.3 KB) TX bytes:31400158 (31.4 MB)
现在可以看到,docker默认创建的docker0网桥,其IP分配到了10.0.5.1。不仅如此,后续创建的容器都会在这个网段下分配。
所有的容器都将连在docker0上,因为它们的IP段相同,所以本机容器之间可以直接基于docker0通讯,docker0网桥就是虚拟交换机的概念。
容器内网络
接下来,我们创建一个容器,看看容器内的网络配置:
root@ubuntu:~# docker run -it busybox
docker给容器分配的IP:
/ # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:0A:00:05:02 inet addr:10.0.5.2 Bcast:10.0.5.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1 RX packets:7 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:598 (598.0 B) TX bytes:0 (0.0 B)
查看容器内的路由:
/ # route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default 10.0.5.1 0.0.0.0 UG 0 0 0 eth0 10.0.5.0 * 255.255.255.0 U 0 0 0 eth0
可见,发往10.0.5.0/24的包属于同网段通讯,在docker0网桥上通过arp可以直接得到目标mac地址,经过docker0交换即可。
其他的包则直接发往默认网关docker0(10.0.5.1),可以为理解docker0既支持路由又支持交换,像家庭路由器一样。
从物理机上可以看出docker0虚拟机交换机上,拉了一根到容器eth0网口的网线:
root@ubuntu:~# brctl show bridge name bridge id STP enabled interfaces docker0 8000.02422c3bdd2b no veth43dc82c
这么描述大家应该更容易理解。
docker0继续路由
docker0是物理机上的网桥,所以使用的是物理机的路由表。
/ # root@ubuntu:~# route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default 172.18.11.254 0.0.0.0 UG 0 0 0 enp0s3 10.0.5.0 * 255.255.255.0 U 0 0 0 docker0 172.18.8.0 * 255.255.252.0 U 0 0 0 enp0s3
如果发往的目标IP是外网IP(例如:百度),那么只能走物理机的默认网关,不过这里会经过iptables的NAT转换,百度那边认为是我们的物理机在访问它。
我们关注的是容器访问另外一台机器上的容器,现在因为没有启动另外一台机器,所以没有我们想看到的路由规则。
配置98机器的flannel
配置过程与96机器一致,分配到的网段是:
root@ubuntu:~# cat /run/flannel/subnet.env FLANNEL_NETWORK=10.0.0.0/8 FLANNEL_SUBNET=10.0.20.1/24 FLANNEL_MTU=1450 FLANNEL_IPMASQ=false
观察96机器的路由表,发现多了一条路由规则:
/ # root@ubuntu:~# route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default 172.18.11.254 0.0.0.0 UG 0 0 0 enp0s3 10.0.5.0 * 255.255.255.0 U 0 0 0 docker0 10.0.20.0 10.0.20.0 255.255.255.0 UG 0 0 0 flannel.1 172.18.8.0 * 255.255.252.0 U 0 0 0 enp0s3
也就是如果目标容器地址属于10.0.20.0/24网段,那么通过flannel1.1虚拟网卡发出,所以flanneld进程就可以有机会进行vxlan封装,并通过物理网卡发往目标物理机。
同样的,98机器的路由表也是类似的:
root@ubuntu:~# route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default 172.18.11.254 0.0.0.0 UG 0 0 0 enp0s3 10.0.5.0 10.0.5.0 255.255.255.0 UG 0 0 0 flannel.1 10.0.20.0 * 255.255.255.0 U 0 0 0 docker0 172.18.8.0 * 255.255.252.0 U 0 0 0 enp0s3
发往10.0.5.0的都交给flannel1.1处理,而flanneld进程要做的事情就是根据etcd中的记录获知目标物理机IP,然后vxlan封装送过去。
我们去看一下etcd中的记录就会更清晰的认识到这一点:
root@ubuntu:~/etcd-v3.3.10-linux-amd64# ./etcdctl ls /coreos.com/network/subnets/ /coreos.com/network/subnets/10.0.5.0-24 /coreos.com/network/subnets/10.0.20.0-24 root@ubuntu:~/etcd-v3.3.10-linux-amd64# ./etcdctl get /coreos.com/network/subnets/10.0.5.0-24 {"PublicIP":"172.18.10.96","BackendType":"vxlan","BackendData":{"VtepMAC":"36:ba:b9:5c:8d:d6"}} root@ubuntu:~/etcd-v3.3.10-linux-amd64# ./etcdctl get /coreos.com/network/subnets/10.0.20.0-24 {"PublicIP":"172.18.10.98","BackendType":"vxlan","BackendData":{"VtepMAC":"9e:94:df:d8:67:c3"}}
一目了然,虚拟网段与物理机IP的关系都记录在etcd中。
测试
现在可以让2台物理机上的容器互相ping通,并且是基于虚拟IP:
root@ubuntu:~# docker run -it busybox / # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:0A:00:05:02 inet addr:10.0.5.2 Bcast:10.0.5.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1 RX packets:9 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:758 (758.0 B) TX bytes:0 (0.0 B) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) / # ping 10.0.20.3 PING 10.0.20.3 (10.0.20.3): 56 data bytes 64 bytes from 10.0.20.3: seq=0 ttl=62 time=0.626 ms 64 bytes from 10.0.20.3: seq=1 ttl=62 time=0.946 ms
反过来也是一样的:
/ # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:0A:00:14:03 inet addr:10.0.20.3 Bcast:10.0.20.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1 RX packets:15 errors:0 dropped:0 overruns:0 frame:0 TX packets:7 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:1278 (1.2 KiB) TX bytes:574 (574.0 B) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) / # ping 10.0.5.2 PING 10.0.5.2 (10.0.5.2): 56 data bytes 64 bytes from 10.0.5.2: seq=0 ttl=62 time=0.602 ms 64 bytes from 10.0.5.2: seq=1 ttl=62 time=0.559 ms
总结
以flannel为例,通过实践可以更加具体的感受虚拟化网络的玩法和思路。
不过flannel性能比较差劲,目前据说最好的选型是calico方案,但是理解和使用起来也会更复杂。
但是无论选用任何一种网络模型,最终实现的效果都是一样的,就是容器基于虚拟IP互通,这是所有网络插件的共同目标,所以真的要用起来并不需要把插件细节搞明白,理解原理就差不多了。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 自动化优秀实践(一):从纺锤模型到金字塔模型
- 【语言模型系列】实践篇:ALBERT在房产领域的实践
- 自动化测试最佳实践(一):从纺锤模型到金字塔模型
- 应用XGboost实现多分类模型实践
- 智能搜索模型预估框架的建设与实践
- 实践篇 | 推荐系统之矩阵分解模型
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。