docker入门教程五:网络模式

简介

Docker在1.9版本中引入了一整套docker network子命令和跨主机网络支持。这允许用户可以根据他们应用的拓扑结构创建虚拟网络并将容器接入其所对应的网络。

其实,早在Docker1.7版本中,网络部分代码就已经被抽离并单独成为了Docker的网络库,即libnetwork。在此之后,容器的网络模式也被抽像变成了统一接口的驱动

为了标准化网络的驱动开发步骤和支持多种网络驱动,Docker公司在libnetwork中使用了CNM(Container Network Model)。CNM定义了构建容器虚拟化网络的模型。同时还提供了可以用于开发多种网络驱动的标准化接口和组件。

libnetwork和Docker daemon及各个网络驱动的关系可以通过下面的图进行形象的表示。

image

如上图所示,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 是这个连接的别名。

可以通过link的方式直接让容器包含起来,但是这样只是单向的。如下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
[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
[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 命名的网卡,相当于交换机上的端口。

参考消息

docker的五种网络模式总结

“深入浅出”来解读Docker网络核心原理

Docker高级网络实践之 玩转Linux network namespace & pipework

Docker 网络之进阶篇

官文文档

Docker的网络模式

iptables

  • 本文作者: wumingx
  • 本文链接: https://www.wumingx.com/k8s/docker-network-mode.html
  • 本文主题: docker入门教程五:网络模式
  • 版权声明: 本博客所有文章除特别声明外,转载请注明出处!如有侵权,请联系我删除。
0%