Docker做为目前最火的轻量级容器技术,有不少使人称道的功能,如Docker的镜像管理。然而,Docker一样有着不少不完善的地方,网络方面就是Docker比较薄弱的部分。所以,咱们有必要深刻了解Docker的网络知识,以知足更高的网络需求。本文首先介绍了Docker自身的4种网络工做方式,而后经过3个样例 —— 将Docker容器配置到本地网络环境中、单主机Docker容器的VLAN划分、多主机Docker容器的VLAN划分,演示了如何使用pipework帮助咱们进行复杂的网络设置,以及pipework是如何工做的。html
咱们在使用docker run建立Docker容器时,能够用--net选项指定容器的网络模式,Docker有如下4种网络模式:前端
host模式,使用--net=host指定。git
container模式,使用--net=container:NAME_or_ID指定。github
none模式,使用--net=none指定。web
bridge模式,使用--net=bridge指定,默认设置。docker
下面分别介绍一下Docker的各个网络模式。shell
相关厂商内容ubuntu
相关赞助商安全
全球软件开发大会,4月23-25日,北京,敬请期待!bash
众所周知,Docker使用了Linux的Namespaces技术来进行资源隔离,如PID Namespace隔离进程,Mount Namespace隔离文件系统,Network Namespace隔离网络等。一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable规则等都与其余的Network Namespace隔离。一个Docker容器通常会分配一个独立的Network Namespace。但若是启动容器的时候使用host模式,那么这个容器将不会得到一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出本身的网卡,配置本身的IP等,而是使用宿主机的IP和端口。
例如,咱们在10.10.101.105/24的机器上用host模式启动一个含有web应用的Docker容器,监听tcp80端口。当咱们在容器中执行任何相似ifconfig命令查看网络环境时,看到的都是宿主机上的信息。而外界访问容器中的应用,则直接使用10.10.101.105:80便可,不用任何NAT转换,就如直接跑在宿主机中同样。可是,容器的其余方面,如文件系统、进程列表等仍是和宿主机隔离的。
在理解了host模式后,这个模式也就好理解了。这个模式指定新建立的容器和已经存在的一个容器共享一个Network Namespace,而不是和宿主机共享。新建立的容器不会建立本身的网卡,配置本身的IP,而是和一个指定的容器共享IP、端口范围等。一样,两个容器除了网络方面,其余的如文件系统、进程列表等仍是隔离的。两个容器的进程能够经过lo网卡设备通讯。
这个模式和前两个不一样。在这种模式下,Docker容器拥有本身的Network Namespace,可是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。须要咱们本身为Docker容器添加网卡、配置IP等。
bridge模式是Docker默认的网络设置,此模式会为每个容器分配Network Namespace、设置IP等,并将一个主机上的Docker容器链接到一个虚拟网桥上。下面着重介绍一下此模式。
当Docker server启动时,会在主机上建立一个名为docker0的虚拟网桥,此主机上启动的Docker容器会链接到这个虚拟网桥上。虚拟网桥的工做方式和物理交换机相似,这样主机上的全部容器就经过交换机连在了一个二层网络中。接下来就要为容器分配IP了,Docker会从RFC1918所定义的私有IP网段中,选择一个和宿主机不一样的IP地址和子网分配给docker0,链接到docker0的容器就从这个子网中选择一个未占用的IP使用。如通常Docker会使用172.17.0.0/16这个网段,并将172.17.42.1/16分配给docker0网桥(在主机上使用ifconfig命令是能够看到docker0的,能够认为它是网桥的管理接口,在宿主机上做为一块虚拟网卡使用)。单机环境下的网络拓扑以下,主机地址为10.10.101.105/24。
Docker完成以上网络配置的过程大体是这样的:
在主机上建立一对虚拟网卡veth pair设备。veth设备老是成对出现的,它们组成了一个数据的通道,数据从一个设备进入,就会从另外一个设备出来。所以,veth设备经常使用来链接两个网络设备。
Docker将veth pair设备的一端放在新建立的容器中,并命名为eth0。另外一端放在主机中,以veth65f9这样相似的名字命名,并将这个网络设备加入到docker0网桥中,能够经过brctl show命令查看。
从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关。
网络拓扑介绍完后,接着介绍一下bridge模式下容器是如何通讯的。
在bridge模式下,连在同一网桥上的容器能够相互通讯(若出于安全考虑,也能够禁止它们之间通讯,方法是在DOCKER_OPTS变量中设置--icc=false,这样只有使用--link才能使两个容器通讯)。
容器也能够与外部通讯,咱们看一下主机上的Iptable规则,能够看到这么一条
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
这条规则会将源地址为172.17.0.0/16的包(也就是从Docker容器产生的包),而且不是从docker0网卡发出的,进行源地址转换,转换成主机网卡的地址。这么说可能不太好理解,举一个例子说明一下。假设主机有一块网卡为eth0,IP地址为10.10.101.105/24,网关为10.10.101.254。从主机上一个IP为172.17.0.1/16的容器中ping百度(180.76.3.151)。IP包首先从容器发往本身的默认网关docker0,包到达docker0后,也就到达了主机上。而后会查询主机的路由表,发现包应该从主机的eth0发往主机的网关10.10.105.254/24。接着包会转发给eth0,并从eth0发出去(主机的ip_forward转发应该已经打开)。这时候,上面的Iptable规则就会起做用,对包作SNAT转换,将源地址换为eth0的地址。这样,在外界看来,这个包就是从10.10.101.105上发出来的,Docker容器对外是不可见的。
那么,外面的机器是如何访问Docker容器的服务呢?咱们首先用下面命令建立一个含有web应用的容器,将容器的80端口映射到主机的80端口。
docker run -d --name web -p 80:80 fmzhen/simpleweb
而后查看Iptable规则的变化,发现多了这样一条规则:
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.5:80
此条规则就是对主机eth0收到的目的端口为80的tcp流量进行DNAT转换,将流量发往172.17.0.5:80,也就是咱们上面建立的Docker容器。因此,外界只需访问10.10.101.105:80就能够访问到容器中得服务。
除此以外,咱们还能够自定义Docker使用的IP地址、DNS等信息,甚至使用本身定义的网桥,可是其工做方式仍是同样的。
Docker自身的网络功能比较简单,不能知足不少复杂的应用场景。所以,有不少开源项目用来改善Docker的网络功能,如pipework、weave、flannel等。这里,就先介绍一下pipework的使用和工做原理。
pipework是由Docker的工程师Jérôme Petazzoni开发的一个Docker网络配置工具,由200多行shell实现,方便易用。下面用三个场景来演示pipework的使用和工做原理。
为了使本地网络中的机器和Docker容器更方便的通讯,咱们常常会有将Docker容器配置到和主机同一网段的需求。这个需求其实很容易实现,咱们只要将Docker容器和主机的网卡桥接起来,再给Docker容器配上IP就能够了。
下面咱们来操做一下,我主机A地址为10.10.101.105/24,网关为10.10.101.254,须要给Docker容器的地址配置为10.10.101.150/24。在主机A上作以下操做:
#安装pipework git clone https://github.com/jpetazzo/pipework cp ~/pipework/pipework /usr/local/bin/ #启动Docker容器。 docker run -itd --name test1 ubuntu /bin/bash #配置容器网络,并连到网桥br0上。网关在IP地址后面加@指定。 #若主机环境中存在dhcp服务器,也能够经过dhcp的方式获取IP #pipework br0 test1 dhcp pipework br0 test1 10.10.101.150/24@10.10.101.254 #将主机eth0桥接到br0上,并把eth0的IP配置在br0上。这里因为是远程操做,中间网络会断掉,因此放在一条命令中执行。 ip addr add 10.10.101.105/24 dev br0; \ ip addr del 10.10.101.105/24 dev eth0; \ brctl addif br0 eth0; \ ip route del default; \ ip route add default gw 10.10.101.254 dev br0
完成上述步骤后,咱们发现Docker容器已经可使用新的IP和主机网络里的机器相互通讯了。
那么容器到底发生了哪些变化呢?咱们docker attach到test1上,发现容器中多了一块eth1的网卡,而且配置了10.10.101.150/24的IP,并且默认路由也改成了10.10.101.254。这些都是pipework帮咱们配置的。经过查看源代码,能够发现pipework br0 test1 10.10.101.150/24@10.10.101.254是由如下命令完成的(这里只列出了具体执行操做的代码)。
#建立br0网桥 #若ovs开头,则建立OVS网桥 ovs-vsctl add-br ovs* brctl addbr $IFNAME #建立veth pair,用于链接容器和br0 ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU #找到Docker容器test1在主机上的PID,建立容器网络命名空间的软链接 DOCKERPID=$(docker inspect --format='{{ .State.Pid }}' $GUESTNAME) ln -s /proc/$NSPID/ns/net /var/run/netns/$NSPID #将veth pair一端放入Docker容器中,并设置正确的名字eth1 ip link set $GUEST_IFNAME netns $NSPID ip netns exec $NSPID ip link set $GUEST_IFNAME name $CONTAINER_IFNAME #将veth pair另外一端加入网桥 #若为OVS网桥则为 ovs-vsctl add-port $IFNAME $LOCAL_IFNAME ${VLAN:+"tag=$VLAN"} brctl addif $IFNAME $LOCAL_IFNAME #为新增长的容器配置IP和路由 ip netns exec $NSPID ip addr add $IPADDR dev $CONTAINER_IFNAME ip netns exec $NSPID ip link set $CONTAINER_IFNAME up ip netns exec $NSPID ip route delete default ip netns exec $NSPID ip route add $GATEWAY/32 dev $CONTAINER_IFNAME
首先pipework检查是否存在br0网桥,若不存在,就本身建立。若以"ovs"开头,就会建立OpenVswitch网桥,以"br"开头,建立Linux bridge。
建立veth pair设备,用于为容器提供网卡并链接到br0网桥。
使用docker inspect找到容器在主机中的PID,而后经过PID将容器的网络命名空间连接到/var/run/netns/目录下。这么作的目的是,方便在主机上使用ip netns命令配置容器的网络。由于,在Docker容器中,咱们没有权限配置网络环境。
将以前建立的veth pair设备分别加入容器和网桥中。在容器中的名称默认为eth1,能够经过pipework的-i参数修改该名称。
而后就是配置新网卡的IP。若在IP地址的后面加上网关地址,那么pipework会从新配置默认路由。这样容器通往外网的流量会经由新配置的eth1出去,而不是经过eth0和docker0。(若想彻底抛弃自带的网络设置,在启动容器的时候能够指定--net=none)
以上就是pipework配置Docker网络的过程,这和Docker的bridge模式有着类似的步骤。事实上,Docker在实现上也采用了相同的底层机制。
经过源代码,能够看出,pipework经过封装Linux上的ip、brctl等命令,简化了在复杂场景下对容器链接的操做命令,为咱们配置复杂的网络拓扑提供了一个强有力的工具。固然,若是想了解底层的操做,咱们也能够直接使用这些Linux命令来完成工做,甚至能够根据本身的需求,添加额外的功能。
pipework不只可使用Linux bridge链接Docker容器,还能够与OpenVswitch结合,实现Docker容器的VLAN划分。下面,就来简单演示一下,在单机环境下,如何实现Docker容器间的二层隔离。
为了演示隔离效果,咱们将4个容器放在了同一个IP网段中。但实际他们是二层隔离的两个网络,有不一样的广播域。
#在主机A上建立4个Docker容器,test一、test二、test三、test4 docker run -itd --name test1 ubuntu /bin/bash docker run -itd --name test2 ubuntu /bin/bash docker run -itd --name test3 ubuntu /bin/bash docker run -itd --name test4 ubuntu /bin/bash #将test1,test2划分到一个vlan中,vlan在mac地址后加@指定,此处mac地址省略。 pipework ovs0 test1 192.168.0.1/24 @100 pipework ovs0 test2 192.168.0.2/24 @100 #将test3,test4划分到另外一个vlan中 pipework ovs0 test3 192.168.0.3/24 @200 pipework ovs0 test4 192.168.0.4/24 @200
完成上述操做后,使用docker attach连到容器中,而后用ping命令测试连通性,发现test1和test2能够相互通讯,但与test3和test4隔离。这样,一个简单的VLAN隔离容器网络就已经完成。
因为OpenVswitch自己支持VLAN功能,因此这里pipework所作的工做和以前介绍的基本同样,只不过将Linux bridge替换成了OpenVswitch,在将veth pair的一端加入ovs0网桥时,指定了tag。底层操做以下:
ovs-vsctl add-port ovs0 veth* tag=100
上面介绍完了单主机上VLAN的隔离,下面咱们将状况延伸到多主机的状况。有了前面两个例子作铺垫,这个也就不难了。为了实现这个目的,咱们把宿主机上的网卡桥接到各自的OVS网桥上,而后再为容器配置IP和VLAN就能够了。咱们实验环境以下,主机A和B各有一块网卡eth0,IP地址分别为10.10.101.105/2四、10.10.101.106/24。在主机A上建立两个容器test一、test2,分别在VLAN 100和VLAN 200上。在主机B上建立test三、test4,分别在VLAN 100和VLAN 200 上。最终,test1能够和test3通讯,test2能够和test4通讯。
#在主机A上 #建立Docker容器 docker run -itd --name test1 ubuntu /bin/bash docker run -itd --name test2 ubuntu /bin/bash #划分VLAN pipework ovs0 test1 192.168.0.1/24 @100 pipework ovs0 test2 192.168.0.2/24 @200 #将eth0桥接到ovs0上 ip addr add 10.10.101.105/24 dev ovs0; \ ip addr del 10.10.101.105/24 dev eth0; \ ovs-vsctl add-port ovs0 eth0; \ ip route del default; \ ip route add default gw 10.10.101.254 dev ovs0 #在主机B上 #建立Docker容器 docker run -itd --name test3 ubuntu /bin/bash docker run -itd --name test4 ubuntu /bin/bash #划分VLAN pipework ovs0 test1 192.168.0.3/24 @100 pipework ovs0 test2 192.168.0.4/24 @200 #将eth0桥接到ovs0上 ip addr add 10.10.101.106/24 dev ovs0; \ ip addr del 10.10.101.106/24 dev eth0; \ ovs-vsctl add-port ovs0 eth0; \ ip route del default; \ ip route add default gw 10.10.101.254 dev ovs0
完成上面的步骤后,主机A上的test1和主机B上的test3容器就划分到了一个VLAN中,而且与主机A上的test2和主机B上的test4隔离(主机eth0网卡须要设置为混杂模式,链接主机的交换机端口应设置为trunk模式,即容许VLAN 100和VLAN 200的包经过)。拓扑图以下所示(省去了Docker默认的eth0网卡和主机上的docker0网桥):
除此以外,pipework还支持使用macvlan设备、设置网卡MAC地址等功能。不过,pipework有一个缺陷,就是配置的容器在关掉重启后,以前的设置会丢失。
经过上面的介绍,我相信你们对Docker的网络已经有了必定的了解。对于一个基本应用而言,Docker的网络模型已经很不错了。然而,随着云计算和微服务的兴起,咱们不能永远停留在使用基本应用的级别上,咱们须要性能更好且更灵活的网络功能。pipework正好知足了咱们这样的需求,从上面的样例中,咱们能够看到pipework的方便之处。可是,同时也应注意到,pipework并非一套解决方案,它只是一个网络配置工具,咱们能够利用它提供的强大功能,帮助咱们构建本身的解决方案。
冯明振,浙江大学SEL实验室硕士研究生,目前在云平台团队从事科研和开发工做。浙大团队对PaaS,Docker,大数据和主流开源云计算技术有深刻的研究和二次开发经验,团队现将部分技术文章贡献出来,但愿能对读者有所帮助。
感谢郭蕾对本文的策划和审校。
给InfoQ中文站投稿或者参与内容翻译工做,请邮件至editors@cn.infoq.com。也欢迎你们经过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注咱们,并与咱们的编辑和其余读者朋友交流。