前言
docker容器有四种网络模式可供选择,在有些书中,也会把这四种容器的网络模式称之为网络容器原型(例如在《docker in action》中),所有的docker容器都要属于这四种容器网络模式中的一种。
不同的容器网络模式对应着不同程度的隔离,按隔离程度由高到低的顺序排列是:none模式、bridged模式、container模式、host模式。其中,bridged模式是docker默认的网络模式,用户启动一个容器时,如果不特别规定使用哪一种网络模式,那么该容器使用bridged模式。
下图是《docker in action》中,描绘四种网络容器模式的原理以及与docker主机拓扑关系的图:
1.none模式
该模式下的容器,不允许任何的网络流量。启动该模式下的容器的方式是在docker run命令后加上–net none选项。执行命令
docker container run -dit --name testnone --net=none alpine
启动一个名为testnone的容器,采用none网络模式。容器启动完成之后,使用docker exec -it testnone sh
命令进入容器中执行ifconfig结果如下图所示:
none模式下容器中的进程,只可以与自身通信或者与本地进程通信(也就是容器中的其他进程)。容器外的进程无法与容器内的进程通信,容器内的进程也无法与容器外的进程通信。另外还需注意,none模式下的容器可以被扩展它的网络功能以适应不同的情景。
2.bridge模式
bridge模式,是docker默认的容器启动模式,该模式下的容器有两个接口,一个是本地回环接口(127.0.0.1、localhost),另一个接口连接到docker引擎创建的桥接网络上(注意与Linux网桥docker0的区别,稍后会讲),执行命令
docker container run -dit --name testbridge --net=bridge alpine
创建一个bridge模式的容器testbridge(其中‘–net=bridge’可省略),然后执行docker exec -it testbridge sh
进入容器中,ifconfig结果如下:
bridge模式下的网桥
docker daemon启动时会建立一个docker0网桥,这是一个Linux虚拟网桥。连接在该网桥上的所有容器属于同一个虚拟子网,它们之间能够相互通信,且可以通过docker0与外部的网络进行通信。该网桥一端连接到主机网络接口上,另一端和容器相连。
此外,还有一个桥接网络的概念,容器可以分属于不同的桥接网络,桥接网络由docker引擎管理,会被映射到Linux的网桥上。例如,默认的bridge网络被映射到docker0网桥上,可以通过命令docker network ls
查看主机上的桥接网络。
其中,(NAME列)bridge网络、host网络、none网络是默认创建的,localnet网络是我用
docker network create -d bridge localnet
创建的。
执行docker network inspect bridge
命令可以查看bridge网络的相信信息,可以看到有哪些容器连接到了该网络上。如下图所示:
可以看到有一个名为testbridge的容器连接到了该网络中。同理,可以使用docker network inspect none
查看none网络的详细信息,使用docker network inspect host
查看host网络的详细信息。默认情况下,bridge模式的容器自动连接到bridge网络中,host模式的容器自动连接到host网络中,none模式的容器自动连接到none网络中,container模式的容器与其他容器共享网络接口,所以不需要再单独创建一个container网络。
在使用docker run命令运行容器时,可以通过–network参数,指定容器连接到哪个网络中。例如,执行docker run -dit --name testlocalnet --network localnet alpine
启动一个bridge模式的容器testlocalnet,该容器不能与testbridge通信,因为属于两个不同的桥接网络。
下面进行一个实验,执行docker run -dit --name testlocalnet2 --network localnet alpine
再启动一个bridge模式的容器testlocalnet2,与容器testlocalnet属于同一个桥接网络localnet。执行docker exec -it testlocalnet2 sh
进入容器testlocalnet2中执行ping testlocalnet
的结果如下所示:
注意:该实验如果要在bridge网络中进行的话,ping命令后面不能用容器的名字做参数,而要用容器的IP地址做参数,因为Linux系统中默认的bridge网络不支持通过docker DNS服务进行域名解析的,而自定义的桥接网络可以。
接下来从原理上解释一下为什么不属于同一个桥接网络的容器不能互相通信
在使用docker network create -d bridge localnet
命令创建localnet网络时,内核也建立了一个新的虚拟网桥,也就是说在docker0网桥之外,建立了另一个网桥,localnet网络就映射到该网桥(br-9d5636f71221)上。可以通过brctl show
命令查看。
每一个网桥都有自己的虚拟子网,不同的网桥的虚拟子网不同。在我的主机上,bridge网络的子网是172.17.0.0/16,localnet网络的子网是172.18.0.0/16,所以在容器testbridge里ping容器testlocalnet是ping不通的。
下面这张图参考《Docker Deep Dive》解释了Linux网桥,docker桥接网络,和docker容器的关系。
3.container模式
container模式下的容器,与另外一个运行中的容器共享同一个Network Namespace(网络命名空间,也可以说是网络栈),即两个容器在网络层面没有任何的隔离,它们的网络接口是相同的。第一个实验,执行
docker container run -dit --name testcontainer --net=container:testnone alpine
启动一个container模式下的容器,与第一节中的none模式的容器进行共享网络栈。注意,如果testnone容器不处于运行状态,则无法完成共享网络栈操作,testcontainer容器被创建,但是不会被运行。接着执行docker exec -it testcontainer sh
命令,进入testcontainer容器中执行ifconfig结果如下所示:
第二个实验,我们启动一个新的容器,让它与第二节中的bridge模式的容器共享网络栈。执行命令:docker container run -dit --name testcontainer2 --net=container:testbridge alpine
,接着执行命令docker exec -it testcontainer2 sh
进入testcontainer2容器中执行ifconfig结果如下所示:
可以看到,testcontainer2容器eth0接口的IP地址也是172.17.0.2,与testbridge的一致。此外,还可以使用cat /etc/hosts
命令,会看到两个容器的hosts文件中172.17.0.2地址对应的是同一个hostname。
container模式下的两个容器,拥有各自的文件系统和内存,共享的仅仅是网络组件。该模式的一个应用场景是扩展none模式下容器的网络功能,即我们的第一个实验的内容,位于两个none模式下的容器中的进程可以相互通信了。
4.host模式
在大多数的资料中都会提醒用户,该模式下的容器安全性极差,告诫用户谨慎使用该模式。host模式下的容器,在网络层面没有任何的隔离,对主机的网络有完全的访问权,共享主机的网络接口。执行
docker run -dit --name testhost --net=host alpine
启动一个host模式下的容器,然后执行docker exec -it testhost sh
命令进入容器中,接着执行ifconfig命令,结果如下:
可以看到主机上所有的网络接口,包括docker0。
5.docker run中与网络相关的一些参数
--hostname +参数
用来指定容器的名字,仅用于DNS解析,设置该参数后会在容器内的/etc/hostname文件中看到结果。注意与–name选项的区别,–name选项可用于DNS解析,也可用于其他情景,比如docker stop 的参数等。
--dns +参数
用来设置主DNS服务器,设置该参数后会在容器内的/etc/resolv.conf文件中看到结果。
--dns-search +参数
用来指定进行DNS解析时的后缀,也叫DNS查找域。该参数被设定之后,DNS查询时,不包括顶级域名的主机名都会自动加上该后缀。例如--dns-search docker.com
,那么nslookup registry.hub
命令会解析registry.hub.docker.com的IP地址。
--add-host +参数
用来自定义从主机名到IP地址的映射关系,例如,--add-host test:172.19.0.2