简介
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 2 3 4 5 6 7 |
[root@master ~]# docker network ls NETWORK ID NAME DRIVER SCOPE 4c3f6c8e4ed3 bridge bridge local 6f54912fe44b host host local 099101cd7797 none null local [root@master ~]# docker inspect bridge -f "{{json .IPAM.Config}}" [{"Subnet":"172.17.0.0/16","Gateway":"172.17.0.1"}] |
可以使用 docker inspect bridge
来查看网络信息
none网络
这种网络模式下容器只有lo回环网络,没有其他网卡。none网络可以在容器创建时通过—network=none来指定。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。
1 2 3 4 5 6 7 8 9 |
[root@master ~]# docker run -it --rm --network none busybox / # ifconfig 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) |
host网络
使用这种驱动的时候,libnetwork将不为Docker容器创建网络协议栈,即不会创建独立的network namespace。Docker容器中的进程处于宿主机的网络环境中,相当于Docker容器和宿主机共同用一个network namespace,使用宿主机的网卡、IP和端口等信息。
但是,容器其他方面,如文件系统、进程列表等还是和宿主机隔离的。host模式很好地解决了容器与外界通信的地址转换问题,可以直接使用宿主机的IP进行通信,不存在虚拟化网络带来的额外性能负担。但是host驱动也降低了容器与容器之间、容器与宿主机之间网络层面的隔离性,引起网络资源的竞争与冲突。因此可以认为host驱动适用于对于容器集群规模不大的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
root@fdm:~# docker run -it --rm --network=host busybox / # ip addr show ens33 2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel qlen 1000 link/ether 00:0c:29:17:eb:aa brd ff:ff:ff:ff:ff:ff inet 192.168.254.131/24 brd 192.168.254.255 scope global dynamic ens33 valid_lft 1487sec preferred_lft 1487sec inet6 fe80::20c:29ff:fe17:ebaa/64 scope link valid_lft forever preferred_lft forever / # netstat -tunpl Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN - tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN - tcp 0 0 :::3306 :::* LISTEN - tcp 0 0 :::6379 :::* LISTEN - tcp 0 0 :::8080 :::* LISTEN - tcp 0 0 :::80 :::* LISTEN - tcp 0 0 :::8081 :::* LISTEN - tcp 0 0 :::22 :::* LISTEN - tcp 0 0 :::443 :::* LISTEN - |
host最大的优势就是网络性能比较好,不需要进行NAT,但是docker host上已经使用的端口就不能再用了,网络的隔离性不好。
bridge网络
查看bridge
这是容器的默认网络模式,docker在安装时会创建一个名为docker0的Linux bridge,在不指定--network
的情况下,创建的容器都会默认挂到docker0上面。
1 2 3 4 5 6 7 |
[root@master ~]# brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242de1d30e9 no veth40db10a veth651f0eb veth7776d69 veth98cbea9 vetha3ce824 |
使用brctl可以查看docker0上面有哪些容器,如上,就有5个容器使用了docker0这个bridge。可以把它想象成一个虚拟的交换机,所有的容器都是连到这台交换机上面的。
创建bridge
如果想做隔离,可以使用docker network create
来创建一下bridge,用来隔离其他的容器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
root@fdm:~# docker network create -d bridge fdm fb0a51e7e720f01c33293c58815e31814863ec4081d4bb3221c133d87fc640b7 root@fdm:~# docker network ls NETWORK ID NAME DRIVER SCOPE 26cea62c4cc8 bridge bridge local fb0a51e7e720 fdm bridge local 1a7301e3c89d host host local a4fd1a294acb none null local root@fdm:~/dnmp# docker inspect fdm |sed -n '/Config"/,/}/p' "Config": [ { "Subnet": "172.21.0.0/16", "Gateway": "172.21.0.1" } root@fdm:~# docker run -it --rm --network=fdm busybox / # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:AC:15:00:02 inet addr:172.21.0.2 Bcast:172.21.255.255 Mask:255.255.0.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 |
可以看到新建的容器用的是172.21.0.0/16段的IP了。
link特性
link 参数的格式为 --link name:alias
,其中 name 是要连接的容器的名称,alias 是这个连接的别名。其目的可以是让2个容器连接起来,但是这样连接只是单向的。如下例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[root@master ~]# docker run -itd --name a1 busybox bf41cfdedc430616be570a5fc9da99ffd962ad4ad9d454a7b5210c5de49394a8 [root@master ~]# docker run --name a2 --link a1 -itd busybox 38ccd1f90a6fc0557719dfb6accb6c2f97a4b03f80631d6f5c49a1d7d7aede3f [root@master ~]# docker exec -it a2 ping -c 1 -W 1 a1 PING a1 (172.17.0.7): 56 data bytes 64 bytes from 172.17.0.7: seq=0 ttl=64 time=0.289 ms --- a1 ping statistics --- 1 packets transmitted, 1 packets received, 0% packet loss round-trip min/avg/max = 0.289/0.289/0.289 ms # 可以ping通的原因是在a2的 hosts 里面有加入了a1的指向IP [root@master ~]# docker exec -it a2 cat /etc/hosts |grep a1 172.17.0.7 a1 bf41cfdedc43 |
就是可以使用容器名来通信了,这是由于docker会自动将容器名加到入host里面。但这个是单向的。
1 2 |
[root@master ~]# docker exec -it a1 ping -c 1 -W 1 a2 ping: bad address '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 2 3 4 5 6 |
docker network create backend docker network create frontend docker run -itd --name c1 --net backend busybox # 172.18.0.2 docker run -itd --name c2 --net backend busybox # 172.18.0.3 docker run -itd --name c3 --net frontend busybox # 172.19.0.2 |
这时如果进入c2,是ping不通c3的,但是运行docker network connect frontend c2
之后,就是可以ping通了,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[root@master ~]# docker exec -it c2 ping -c 1 -W 1 172.19.0.2 PING 172.19.0.2 (172.19.0.2): 56 data bytes --- 172.19.0.2 ping statistics --- 1 packets transmitted, 0 packets received, 100% packet loss [root@master ~]# docker network connect frontend c2 [root@master ~]# docker exec -it c2 ping -c 1 -W 1 172.19.0.2 PING 172.19.0.2 (172.19.0.2): 56 data bytes 64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.284 ms --- 172.19.0.2 ping statistics --- 1 packets transmitted, 1 packets received, 0% packet loss round-trip min/avg/max = 0.284/0.284/0.284 ms |
这是因为使用connect之后,在c2上面可以看到其多分配了一个iP。也就是说将c2容器加入到frontend网络中了,再通俗一点来说,就是将C2这个节点接了一条网线到frontend交换机上。
1 2 3 4 |
[root@master ~]# docker exec -it c2 ip -4 -o addr 1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever 250: eth0 inet 172.18.0.3/16 brd 172.18.255.255 scope global eth0\ valid_lft forever preferred_lft forever 254: eth1 inet 172.19.0.3/16 brd 172.19.255.255 scope global eth1\ valid_lft forever preferred_lft forever |
所以这也可以看出来了,由于只在c2上面connect了frontend这个bridge,所以在c3上面还是ping不通c2的。
container模式
创建容器时使用--network=container:NAME_or_ID这
个模式在创建新的容器的时候指定容器的网络和一个已经存在的容器共享一个Network Namespace,并不为docker容器进行任何网络配置,这个docker容器没有网卡、IP、路由等信息。
1 2 3 4 5 6 7 8 9 10 11 12 |
[root@master ~]# docker run --name r1 -itd busybox 8f12e170917e015bc2a4011b91f393884e6c160df0a01b70c72fe6c9e295c3d3 [root@master ~]# docker run --name r2 --network container:r1 -itd busybox 06fc8e2293b3a079916b3096e9aa6547ff0d5b27a373065ed305bd7e3a1122ec [root@master ~]# [root@master ~]# docker exec -it r2 ip -4 -o addr 1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever 262: eth0 inet 172.17.0.7/16 brd 172.17.255.255 scope global eth0\ valid_lft forever preferred_lft forever [root@master ~]# [root@master ~]# docker exec -it r1 ip -4 -o addr 1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever 262: eth0 inet 172.17.0.7/16 brd 172.17.255.255 scope global eth0\ valid_lft forever preferred_lft forever |
如上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 命名的网卡,相当于交换机上的端口。
参考消息
一、如果您发现本站侵害了相关版权,请附上本站侵权链接和您的版权证明一并发送至邮箱:yehes#qq.com(#替换为@)我们将会在五天内处理并断开该文章下载地址。
二、本站所有资源来自互联网整理收集,全部内容采用撰写共用版权协议,要求署名、非商业用途和相同方式共享,如转载请也遵循撰写共用协议。
三、根据署名-非商业性使用-相同方式共享 (by-nc-sa) 许可协议规定,只要他人在以原作品为基础创作的新作品上适用同一类型的许可协议,并且在新作品发布的显著位置,注明原作者的姓名、来源及其采用的知识共享协议,与该作品在本网站的原发地址建立链接,他人就可基于非商业目的对原作品重新编排、修改、节选或者本人的作品为基础进行创作和发布。
四、基于原作品创作的所有新作品都要适用同一类型的许可协议,因此适用该项协议, 对任何以他人原作为基础创作的作品自然同样都不得商业性用途。
五、根据二〇〇二年一月一日《计算机软件保护条例》规定:为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可不经软件著作权人许可,无需向其支付报酬!
六、鉴此,也望大家按此说明转载和分享资源!本站提供的所有信息、教程、软件版权归原公司所有,仅供日常使用,不得用于任何商业用途,下载试用后请24小时内删除,因下载本站资源造成的损失,全部由使用者本人承担!