简介
Docker在1.9版本中引入了一整套docker network子命令和跨主机网络支持。这允许用户可以根据他们应用的拓扑结构创建虚拟网络并将容器接入其所对应的网络。
其实,早在Docker1.7版本中,网络部分代码就已经被抽离并单独成为了Docker的网络库,即libnetwork。在此之后,容器的网络模式也被抽像变成了统一接口的驱动。
为了标准化网络的驱动开发步骤和支持多种网络驱动,Docker公司在libnetwork中使用了CNM(Container Network Model)。CNM定义了构建容器虚拟化网络的模型。同时还提供了可以用于开发多种网络驱动的标准化接口和组件。
libnetwork和Docker daemon及各个网络驱动的关系可以通过下面的图进行形象的表示。
如上图所示,Docker daemon通过调用libnetwork对外提供的API完成网络的创建和管理等功能。libnetwrok中则使用了CNM来完成网络功能的提供。而CNM中主要有沙盒(sandbox)、端点(endpoint)、网络(network)这3种组件。
- 一个沙盒也包含了一个容器网络栈的信息。沙盒可以对容器的接口、路由和DNS设置等进行管理。沙盒的实现可以是Linux netwrok namespace、FreeBSD jail或者类似的机制。一个沙盒可以有多个端点和多个网络。
- 一个端点可以加入一个沙盒和一个网络。端点的实现可以是veth pair、Open vSwitch内部端口或者相似的设备。一个端点只可以属于一个网络并且只属于一个沙盒。
- 一个网络是一组可以直接互相联通的端点。网络的实现可以是Linux bridge、VLAN等。一个网络可以包含多个端点
网络模式
docker的网络模式大致可以分成五种类型,在安装完docker之后,宿主机上会创建三个网络,分别是bridge网络,host网络,none网络,可以使用docker network ls
命令查看。
1 | [root@master ~]# docker network ls |
可以使用 docker inspect bridge
来查看网络信息
none网络
这种网络模式下容器只有lo回环网络,没有其他网卡。none网络可以在容器创建时通过—network=none来指定。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。
1 | [root@master ~]# docker run -it --rm --network none busybox |
host网络
使用这种驱动的时候,libnetwork将不为Docker容器创建网络协议栈,即不会创建独立的network namespace。Docker容器中的进程处于宿主机的网络环境中,相当于Docker容器和宿主机共同用一个network namespace,使用宿主机的网卡、IP和端口等信息。
但是,容器其他方面,如文件系统、进程列表等还是和宿主机隔离的。host模式很好地解决了容器与外界通信的地址转换问题,可以直接使用宿主机的IP进行通信,不存在虚拟化网络带来的额外性能负担。但是host驱动也降低了容器与容器之间、容器与宿主机之间网络层面的隔离性,引起网络资源的竞争与冲突。因此可以认为host驱动适用于对于容器集群规模不大的场景。
1 | root@fdm:~# docker run -it --rm --network=host busybox |
host最大的优势就是网络性能比较好,不需要进行NAT,但是docker host上已经使用的端口就不能再用了,网络的隔离性不好。
bridge网络
查看bridge
这是容器的默认网络模式,docker在安装时会创建一个名为docker0的Linux bridge,在不指定--network
的情况下,创建的容器都会默认挂到docker0上面。
1 | [root@master ~]# brctl show |
使用brctl可以查看docker0上面有哪些容器,如上,就有5个容器使用了docker0这个bridge。可以把它想象成一个虚拟的交换机,所有的容器都是连到这台交换机上面的。
创建bridge
如果想做隔离,可以使用docker network create
来创建一下bridge,用来隔离其他的容器。
1 | root@fdm:~# docker network create -d bridge fdm |
可以看到新建的容器用的是172.21.0.0/16段的IP了。
link特性
link 参数的格式为 --link name:alias
,其中 name 是要连接的容器的名称,alias 是这个连接的别名。其目的可以是让2个容器连接起来,但是这样连接只是单向的。如下例子:
1 | [root@master ~]# docker run -itd --name a1 busybox |
就是可以使用容器名来通信了,这是由于docker会自动将容器名加到入host里面。但这个是单向的。
1 | [root@master ~]# docker exec -it a1 ping -c 1 -W 1 a2 |
此特性的应用场景是在数据库连接时,就可以使用这个方法,比如说有一个mysql容器,别人web类的应用都需要来访问mysql时,就可以使用mysql的容器名,不需要关心mysql容器的IP信息。
在生产环境下,强烈建议大家将容器加入自定义的 Docker 网络来连接多个容器,而不是使用 --link
参数。
其实在启动容器时,会自动挂载 /etc/hostname /etc/hosts /etc/resolv.conf
这三个配置文件,在修改这三个文件时,只在运行的容器中保留,容器终止或重启后并不会被保存下来,也不会被 docker commit
提交,这是因为这三个文件是mount进去的。如果用户想要手动指定容器的配置,可以在使用 docker run
命令启动容器时加入如下参数:
-h HOSTNAME
或者--hostname=HOSTNAME
设定容器的主机名,它会被写到容器内的/etc/hostname
和/etc/hosts
。但它在容器外部看不到,既不会在docker container ls
中显示,也不会在其他的容器的/etc/hosts
看到。--dns=IP_ADDRESS
添加 DNS 服务器到容器的/etc/resolv.conf
中,让容器用这个服务器来解析所有不在/etc/hosts
中的主机名。--dns-search=DOMAIN
设定容器的搜索域,当设定搜索域为.example.com
时,在搜索一个名为 host 的主机时,DNS 不仅搜索 host,还会搜索host.example.com
。
connect指定网络的连接
docker network connect命令会在所连接的容器中创建新的网卡,以完成其与所指定网络的连接。
1 | docker network create backend |
这时如果进入c2,是ping不通c3的,但是运行docker network connect frontend c2
之后,就是可以ping通了,如下:
1 | [root@master ~]# docker exec -it c2 ping -c 1 -W 1 172.19.0.2 |
这是因为使用connect之后,在c2上面可以看到其多分配了一个iP。也就是说将c2容器加入到frontend网络中了,再通俗一点来说,就是将C2这个节点接了一条网线到frontend交换机上。
1 | [root@master ~]# docker exec -it c2 ip -4 -o addr |
所以这也可以看出来了,由于只在c2上面connect了frontend这个bridge,所以在c3上面还是ping不通c2的。
container模式
创建容器时使用--network=container:NAME_or_ID这
个模式在创建新的容器的时候指定容器的网络和一个已经存在的容器共享一个Network Namespace,并不为docker容器进行任何网络配置,这个docker容器没有网卡、IP、路由等信息。
1 | [root@master ~]# docker run --name r1 -itd busybox |
如上r1的IP为172.17.0.7/16,另一个容器r2的IP同r1,这有点类似host类型,ip信息和网络端口等所有网络相关的信息都是共享的,但这两个容器的计算和存储资源还是隔离的。
user-defined模式
用户自定义模式主要可选的有三种网络驱动:bridge、overlay、macvlan。bridge驱动用于创建类似于前面提到的bridge网络;overlay和macvlan驱动用于创建跨主机的网络。
docker网络iptable分析
在安装完成docker之后,会自动生成docker0这个网卡,同时使用iptables-save这个命令来查看,会发现nat表里面会以下规则:1
2
3
4
5
6
7
8
9
10*nat
:PREROUTING ACCEPT [12470453:953811752]
:INPUT ACCEPT [3411641:193591030]
:OUTPUT ACCEPT [397756:24205156]
:POSTROUTING ACCEPT [397756:24205156]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
这四条规则的含义如下:
-m addrtype
表示匹配IP的类型,可以使用iptables -m addrtype --help
来查看支持的类型,比如LOCAL表示是本地网络地址,BROADCAST表示匹配广播地址等。所以这条规则的意思是:把目标地址类型匹配本机IP的数据包,在数据包进入NAT表PREROUTING链时,都让它们直接jump到一个名为DOCKER的链。- 把目标地址类型属于主机系统的本地网络地址、且目的IP不是127.0.0.0/8的数据包,在进入OUTPUT链时,再重定向到DOCKER链
- 数据包进入POSTROUTING时,源IP为172.17.0.0/16,且数据包不是从docker0发出时,进行MASQUERADE的SNAT地址伪装。也可以平时所说的SNAT上网,跟路由器的原理差不多。
- DOCKER链,来自docker0网卡的数据包直接退出这个链,再返回PREROUTING链直接匹配下一条规则。return的含义:从一个CHAIN里可以jump到另一个CHAIN, jump到的那个CHAIN是子CHAIN;从子CHAIN return后,回到触发jump的那条规则,从那条规则的下一条继续匹配;如果return不是在子CHAIN里,而是在main CHAIN,那么就以默认规则进行。
- 当新增一个有带端口信息的容器时,docker-proxy就会自动新建一条规则:
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
表示 对去往172.17.0.2这个目的IP的80端口的包进行放行。- -i, —in-interface [!] <网络接口name> :指定数据包的来自来自网络接口,”!” 表示取反。
- -o, —out-interface [!] <网络接口name> :指定数据包出去的网络接口。
- 结合iptables,为什么是docker0发出去的包呢?这是因为docer0其实是一个网桥,在PREROUTING规则,已经将本机IP的流量重定向DOCKER了,所以就类似将流量重定向给了docker0这个网桥了。
- 如下图所示,docker0是一个网桥,把他想像成一个交换机,所以当新建一个容器时,在容器上会生成一个eth0的网卡,同时,在docker0网桥上同样会新建一个以 vethxxx 命名的网卡,相当于交换机上的端口。