6. Docker 网络管理

6.1. Docker网络基础

在深入Docker内部的网络实现原理之前,先来直观感受一下Docker的网络架构与基本操作。

6.1.1. Docker网络基础

Docker在1.9版本中引入了一整套的docker network子命令和跨主机网络支持。这允许用户可以根据他们应用的拓扑结构创建虚拟网络并将容器接入其所对应的网络。 其实,早在Docker1.7版本中,网络部分代码就已经被抽离并单独成为了Docker的网络库,即libnetwork。在此之后,容器的网络模式也被抽象变成了统一接口的驱动。

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

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

Docker网络虚拟化架构

Docker网络虚拟化架构

Docker daemon通过调用libnetwork对外提供的API完成网络的创建和管理等功能。libnetwork中则使用了CNM来完成网络功能的提供。 而CNM中主要有沙盒(sandbox)、端点(endpoint)和网络(network)这3种组件。 libnetwork中内置的5种驱动则为libnetwork提供了不同类型的网络服务。下面分别对CNM中的3个核心组件和libnetwork中的5种内置驱动进行介绍。

CNM中的3个核心组件如下:

沙盒

一个沙盒包含了一个容器网络栈的信息。沙盒可以对容器的接口、路由和DNS设置等进行管理。沙盒的实现可以是Linux network namespace、FreeBSD Jail或者类似的机制。一个沙盒可以有多个端点和多个网络。

端点

一个端点可以加入一个沙盒和一个网络。端点的实现可以是veth pair、Open vSwitch内部端口或者相似的设备。一个端点只可以属于一个网络并且只属于一个沙盒。

网络

一个网络是一组可以直接互相联通的端点。网络的实现可以是Linux bridge、VLAN等。一个网络可以包含多个端点。

libnetwork中的5种内置驱动如下:

brige驱动

此驱动为Docker的默认设置,使用这个驱动的时候,libnetwork将创建出来的Docker容器连接到Docker网桥上(Docker网桥稍后会做介绍)。 作为最常规的模式,bridge模式已经可以满足Docker容器最基本的使用需求了。然而其与外界通信使用NAT,增加了通信的复杂性, 在复杂场景下使用会有诸多限制。

host驱动

使用这种驱动的时候,libnetwork将不为Docker容器创建网络协议栈,即不会创建独立的networknamespace。 Docker容器中的进程处于宿主机的网络环境中,相当于Docker容器和宿主机共用同一个networknamespace,使用宿主机的网卡、IP和端口等信息。 但是,容器其他方面,如文件系统、进程列表等还是和宿主机隔离的。host模式很好地解决了容器与外界通信的地址转换问题, 可以直接使用宿主机的IP进行通信,不存在虚拟化网络带来的额外性能负担。但是host驱动也降低了容器与容器之间、 容器与宿主机之间网络层面的隔离性,引起网络资源的竞争与冲突。因此可以认为host驱动适用于对于容器集群规模不大的场景。

overlay驱动

此驱动采用IETF标准的VXLAN方式,并且是VXLAN中被普遍认为最适合大规模的云计算虚拟化环境的SDN controller模式。 在使用的过程中,需要一个额外的配置存储服务,例如Consul、etcd或ZooKeeper。还需要在启动Docker daemon的的时候额外添加参数来指定所 使用的配置存储服务地址。

remote驱动

这个驱动实际上并未做真正的网络服务实现,而是调用了用户自行实现的网络驱动插件,使libnetwork实现了驱动的可插件化, 更好地满足了用户的多种需求。用户只要根据libnetwork提供的协议标准,实现其所要求的各个接口并向Docker daemon进行注册。

null驱动

使用这种驱动的时候,Docker容器拥有自己的network namespace,但是并不为Docker容器进行任何网络配置。也就是说, 这个Docker容器除了networknamespace自带的loopback网卡外,没有其他任何网卡、IP、路由等信息,需要用户为Docker容器添加网卡、 配置IP等。这种模式如果不进行特定的配置是无法正常使用的,但是优点也非常明显,它给了用户最大的自由度来自定义容器的网络环境。

6.1.1.1. libnetwork案例实验

在初步了解了libnetwork中各个组件和驱动后,为了更加深入地理解libnetwork中的CNM模型和熟悉dockernetwork子命令的使用, 这里介绍一个libnetwork官方GitHub上示例的搭建过程,并在搭建成功后对其中容器之间的连通性进行验证,如下图所示。

Docker CNM示意图

在这个例子中,使用Docker默认的bridge驱动进行演示。在此例中,会在Docker上组成一个网络拓扑的应用。

  • 它有两个网络,其中backend network为后端网络,frontend network则为前端网络,两个网络互不联通。

  • 其中container1和container3各拥有一个端点,并且分别加入到后端网络和前端网络中。而container2则有两个端点,它们两个分别加入到后端网络和前端网络中。

在开始实验之前,先来查看有什么默认的网络驱动类型:

1 $ docker network ls
2 NETWORK ID     NAME      DRIVER    SCOPE
3 12ea7ab05bda   bridge    bridge    local
4 930b2daaaf16   host      host      local
5 1587f8f9fe33   none      null      local

注解

这3个网络是Docker daemon默认创建的,分别使用了3种不同的驱动,而这3种驱动则对应了Docker原来的3种网络模式,这个在后面做详细讲解。 需要注意的是,3种内置的默认网络是无法使用docker network rm进行删除的。

接下来通过 docker network create 命令创建前后端两个网络

创建Docker网络
 1 $ docker network create frontend
 2 16e2b38d6bb184332580032c852b350d63e9348f51b1c663675ec1329522b359
 3 $ docker network create backend
 4 ebaba5717f786b3d1b8ac5d337dbe42756372d7c7f47dd006354cab69992c494
 5 $ docker network ls
 6 NETWORK ID     NAME       DRIVER    SCOPE
 7 ebaba5717f78   backend    bridge    local
 8 12ea7ab05bda   bridge     bridge    local
 9 16e2b38d6bb1   frontend   bridge    local
10 930b2daaaf16   host       host      local
11 1587f8f9fe33   none       null      local

在创建了所需要的两个网络之后,接下来创建3个容器,并使用如下命令将名为container1和container2的容器加入到backend网络中,将名为container3的容器加入到frontend网络中。

1 $ docker run --name container1 --rm -itd --network backend busybox sleep 600
2 ......
3 $ docker run --name container2 --rm -itd --network backend busybox sleep 600
4 ......
5 $ docker run --name container3 --rm -itd --network frontend busybox sleep 600
6 ......

分别在container1和container3中使用ping命令测试其与container2的连通性

 1 $ docker exec -it container1 ping container2
 2 PING container2 (172.19.0.3): 56 data bytes
 3 64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.101 ms
 4 64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.110 ms
 5 ^C
 6 --- container2 ping statistics ---
 7 2 packets transmitted, 2 packets received, 0% packet loss
 8 round-trip min/avg/max = 0.101/0.105/0.110 ms
 9
10 $ docker exec -it container3 ping container2
11 ping: bad address 'container2'
  • 因为container1与container2都在backend网络中,所以两者可以连通。

  • 但是,因为container3和container2不在一个网络中,所以两个之间并不能连通。

在container2中使用命令ifconfig来查看此容器中的网卡及其配置情况:

 1 $ docker exec -it container2 ifconfig
 2 eth0      Link encap:Ethernet  HWaddr 02:42:AC:13:00:03
 3         inet addr:172.19.0.3  Bcast:172.19.255.255  Mask:255.255.0.0
 4         UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 5         RX packets:13 errors:0 dropped:0 overruns:0 frame:0
 6         TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
 7         collisions:0 txqueuelen:0
 8         RX bytes:978 (978.0 B)  TX bytes:280 (280.0 B)
 9
10 lo        Link encap:Local Loopback
11         inet addr:127.0.0.1  Mask:255.0.0.0
12         UP LOOPBACK RUNNING  MTU:65536  Metric:1
13         RX packets:0 errors:0 dropped:0 overruns:0 frame:0
14         TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
15         collisions:0 txqueuelen:1000
16         RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

可以看到,除了回环网卡lo之外,此容器中只有一块以太网卡,其名称为eth0,

再来通过 docker network inspect 来查看backend网络的细节

查看docker网络
 1$ docker network  inspect backend
 2[
 3    {
 4        "Name": "backend",
 5        "Id": "ebaba5717f786b3d1b8ac5d337dbe42756372d7c7f47dd006354cab69992c494",
 6        "Created": "2021-05-22T08:57:55.535020461+08:00",
 7        "Scope": "local",
 8        "Driver": "bridge",
 9        "EnableIPv6": false,
10        "IPAM": {
11            "Driver": "default",
12            "Options": {},
13            "Config": [
14                {
15                    "Subnet": "172.19.0.0/16",
16                    "Gateway": "172.19.0.1"
17                }
18            ]
19        },
20        "Internal": false,
21        "Attachable": false,
22        "Ingress": false,
23        "ConfigFrom": {
24            "Network": ""
25        },
26        "ConfigOnly": false,
27        "Containers": {
28            "3176d88add17577c0c5c8e49cbc45eb3d6235568756ca363b06f4852eaf4556a": {
29                "Name": "container1",
30                "EndpointID": "8b96843a0998294bb54b84d52fea1e7f030c67fc82d43d9bd0e122230e63eb08",
31                "MacAddress": "02:42:ac:13:00:02",
32                "IPv4Address": "172.19.0.2/16",
33                "IPv6Address": ""
34            },
35            "cce37be32e8160f6ec387b5bac0311ed163ee2873df29a763af34f2de4ef4157": {
36                "Name": "container2",
37                "EndpointID": "ea3820c431454681ffbab3486df9d5fe7bc61b40b4cd91e22ee0eb20cd6a501a",
38                "MacAddress": "02:42:ac:13:00:03",
39                "IPv4Address": "172.19.0.3/16",
40                "IPv6Address": ""
41            }
42        },
43        "Options": {},
44        "Labels": {}
45    }
46]

可以发现Container2容器配置了和网桥backend同在一个IP段的IP地址,这个网卡eth0就是CNM模型中的 端点

最后,使用如下命令将container2加入到frontend网络中。

$ docker network connect frontend container2

再次,在container2中使用命令ifconfig来查看此容器中的网卡及其配置情况。

 1 $  docker exec -it container2 ifconfig
 2 eth0      Link encap:Ethernet  HWaddr 02:42:AC:13:00:03
 3 ......
 4
 5 eth1      Link encap:Ethernet  HWaddr 02:42:AC:12:00:03
 6           inet addr:172.18.0.3  Bcast:172.18.255.255  Mask:255.255.0.0
 7           UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 8           RX packets:8 errors:0 dropped:0 overruns:0 frame:0
 9           TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
10           collisions:0 txqueuelen:0
11           RX bytes:656 (656.0 B)  TX bytes:0 (0.0 B)
12
13 lo        Link encap:Local Loopback
14 ......
15
16 $ docker network inspect frontend
17 [
18     {
19         "Name": "frontend",
20         "Id": "16e2b38d6bb184332580032c852b350d63e9348f51b1c663675ec1329522b359",
21         "Created": "2021-05-22T08:57:47.412957638+08:00",
22         "Scope": "local",
23         "Driver": "bridge",
24         "EnableIPv6": false,
25         "IPAM": {
26             "Driver": "default",
27             "Options": {},
28             "Config": [
29                 {
30                     "Subnet": "172.18.0.0/16",
31                     "Gateway": "172.18.0.1"
32                 }
33             ]
34         },
35         "Internal": false,
36         "Attachable": false,
37         "Ingress": false,
38         "ConfigFrom": {
39             "Network": ""
40         },
41         "ConfigOnly": false,
42         "Containers": {
43             "2212ba15bba07a925a9a20860369fcf2c1cd186c62697089c9215db59f6b59e9": {
44                 "Name": "container3",
45                 "EndpointID": "53105b0ebc849f9bb6ce593dd0744f016bac8e89f4d32a5a422e0c06ce806bf1",
46                 "MacAddress": "02:42:ac:12:00:02",
47                 "IPv4Address": "172.18.0.2/16",
48                 "IPv6Address": ""
49             },
50             "e604c96796d6bb01c4c0f4fea9574d9222af5fb6ff46f7fcb1e6fd37826925b3": {
51                 "Name": "container2",
52                 "EndpointID": "c630c73de7cc8a6bc5ff7b52abe6d98fab6e07dddc453f3ec4577eaa07f80126",
53                 "MacAddress": "02:42:ac:12:00:03",
54                 "IPv4Address": "172.18.0.3/16",
55                 "IPv6Address": ""
56             }
57         },
58         "Options": {},
59         "Labels": {}
60     }
61 ]

发现多了一块名为eth1的以太网卡,并且其IP和网桥frontend同在一个IP段。

测试container2与container3的连通性

$ docker exec -it container3 ping container2
PING container2 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.106 ms
64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.101 ms
^C
--- container2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.101/0.103/0.106 ms

$ docker exec -it container2 ping container3
PING container3 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.085 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.113 ms
^C
--- container3 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.085/0.099/0.113 ms

可以看出, docker network connect 命令会在所连接的容器中创建新的网卡,以完成其与所指定网络的连接。

6.1.2. bridge驱动实现机制分析

前面我们演示了bridge驱动下的CNM使用方式,接下来将会分析bridge驱动的实现机制。

6.1.2.1. docker0网桥

当在一台未经特殊网络配置的RHEL机器上安装完Docker之后,在宿主机上通过使用ifconfig命令可以看到多了一块名为docker0的网卡,IP为172.17.0.1/16。

$ ifconfig
  ......

  docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
          inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
          inet6 fe80::42:45ff:fe4f:7917  prefixlen 64  scopeid 0x20<link>
          ether 02:42:45:4f:79:17  txqueuelen 0  (Ethernet)
          RX packets 25996766891  bytes 3338045520307 (3.0 TiB)
          RX errors 0  dropped 0  overruns 0  frame 0
          TX packets 25996762485  bytes 3701990436932 (3.3 TiB)
          TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

  ......

有了这样一块网卡,宿主机也会在内核路由表上添加一条到达相应网络的静态路由,可通过route -n命令查看。

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
......
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
......

此条路由表示所有目的IP地址为172.17.0.0/16的数据包从docker0网卡发出。

然后使用 docker run 命令创建一个Docker容器:

$ docker run -itd --rm --name con1 busybox sleep 600

查看该容器网卡:

$  docker exec -it con1 ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:656 (656.0 B)  TX bytes:0 (0.0 B)

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:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

在con1容器中可以看到它有两块网卡lo和eth0。lo设备不必多说,是容器的回环网卡;eth0即为容器与外界通信的网卡,eth0的IP为 172.17.0.2/16 , 和宿主机上的网桥docker0在同一个网段。

查看con1的路由表:

$  docker exec -it con1 route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

可以发现con1的默认网关正是宿主机的docker0网卡,通过测试,con1可以顺利访问外网和宿主机网络, 因此表明con1的eth0网卡与宿主机的docker0网卡是相互连通的。

这时查看宿主机的网络设备,会发现有一块以“veth”开头的网卡,

   $ ifconfig
   ......

   vetha311e81: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
           inet6 fe80::90a9:5bff:feb0:eb9c  prefixlen 64  scopeid 0x20<link>
           ether 92:a9:5b:b0:eb:9c  txqueuelen 0  (Ethernet)
           RX packets 8  bytes 608 (608.0 B)
           RX errors 0  dropped 0  overruns 0  frame 0
           TX packets 15  bytes 1271 (1.2 KiB)
           TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


``vetha311e81`` ,我们可以大胆猜测这块网卡肯定是veth设备了,

而veth pair总是成对出现的。前面介绍过,veth pair通常用来连接两个 network namespace ,那么另一个应该是Docker容器con1中的eth0了。 之前已经判断con1容器的eth0和宿主机的docker0是相连的,那么 vethe043f86 也应该是与docker0相连的,不难想到,docker0就不只是一个简单的网卡设备了,而是一个网桥。

真实情况正是如此,下图即为Docker默认网络模式(bridge模式)下的网络环境拓扑图,创建了docker0网桥,并以veth pair连接各容器的网络, 容器中的数据通过docker0网桥转发到eth0网卡上。

Docker网络brige模型

Docker网络brige模型示意图

这里网桥的概念等同于交换机,为连在其上的设备转发数据帧。网桥上的veth网卡设备相当于交换机上的端口,可以将多个容器或虚拟机连接在其上, 这些端口工作在二层,所以是不需要配置IP信息的。图中docker0网桥就为连在其上的容器转发数据帧, 使得同一台宿主机上的Docker容器之间可以相互通信。那么docker0既然是二层设备,其上怎么也配置了IP呢?

docker0是普通的Linux网桥,它是可以在上面配置IP的,可以认为其内部有一个可以用于配置IP信息的网卡接口 (如同每一个Open vSwitch网桥都有一个同名的内部接口一样)。在Docker的桥接网络模式中, docker0的IP地址作为连于之上的容器的默认网关地址存在。在Linux中,可以使用brctl命令查看和管理网桥(需要安装 bridge-utils 软件包) 如查看本机上的Linux网桥以及其上的端口:

$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242454f7917       no              veth1baf753

docker0网桥是在Docker daemon启动时自动创建的,其IP默认为172.17.0.1/16,之后创建的Docker容器都会在docker0子网的范围内选取一个未占用 的IP使用,并连接到docker0网桥上。Docker提供了如下参数可以帮助用户自定义docker0的设置。

  • --bip=CIDR

    设置docker0的IP地址和子网范围,使用CIDR格式,如192.168.100.1/24。注意这个参数仅仅是配置docker0的,对其他自定义的网桥无效。并且在指定这个参数的时候,宿主机是不存在docker0的或者docker0已存在且docker0的IP和参数指定的IP一致才行。

  • --fixed-cidr=CIDR

    限制Docker容器获取IP的范围。Docker容器默认获取的IP范围为Docker网桥(docker0网桥或者 --bridge 指定的网桥)的整个子网范围,此参数可将其缩小到某个子网范围内,所以这个参数必须在Docker网桥的子网范围内。如docker0的IP为 172.17.0.1/16 ,可将 --fxed-cidr 设为 172.17.1.1/24 ,那么Docker容器的IP范围将为 172.17.1.1~172.17.1.254

  • --mtu=BYTES

    指定docker0的最大传输单元( MTU )。

除了使用docker0网桥外,还可以使用自己创建的网桥,使用–bridge=BRIDGE参数指定。使用如下命令添加一个名为br0的网桥,并且为其配置IP。

$ brctl addbr br0

$ ifconfig br0 188.18.0.1

然后在启动Docker daemon的时候使用–bridge=br0指定使用br0网桥即可。

小心

–bridge参数若和–bip参数同时使用会产生冲突。

6.1.2.2. iptables规则

Docker安装完成后,将默认在宿主机系统上增加一些iptables规则,以用于Docker容器和容器之间以及和外界的通信,可以使用 iptables-save 命令查看 。其中nat表上的POSTROUTING链有这么一条规则:

-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

这条规则关系着Docker容器和外界的通信,含义是将源地址为 172.17.0.0/16 的数据包(即Docker容器发出的数据), 当不是从docker0网卡发出时做SNAT(源地址转换,将IP包的源地址替换为相应网卡的地址)。这样一来,从Docker容器访问外网的流量, 在外部看来就是从宿主机上发出的,外部感觉不到Docker容器的存在。那么,外界想要访问Docker容器的服务时该怎么办? 我们启动一个简单的Web服务容器,观察iptables规则有何变化。

首先启动一个Web容器,将其80端口映射到宿主机的80端口上。

$ docker run --name mynginx --rm -d -p 80:80 nginx:latest

然后查看iptables规则,省略部分无用信息。

*nat
......
$ iptables-save
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.2:80
......

*filter
......
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
......

可以看到,在nat和filter的DOCKER链中分别增加了一条规则,这两条规则将访问宿主机80端口的流量转发到 172.17.0.280 端口上 (真正提供服务的Docker容器IP端口),所以外界访问Docker容器是通过iptables做DNAT(目的地址转换)实现的。 此外,Docker的forward规则默认允许所有的外部IP访问容器,可以通过在filter的DOCKER链上添加规则来对外部的IP访问做出限制, 如只允许源IP为 8.8.8.8 的数据包访问容器,需要添加如下规则:

iptables -I DOCKER -i docker0 ! -s 8.8.8. -j DROP

不仅仅是与外界间通信,Docker容器之间互相通信也受到iptables规则限制。

通过前面的学习,我们了解到同一台宿主机上的Docker容器默认都连在docker0网桥上,它们属于一个子网,这是满足相互通信的第一步。 同时,Docker daemon会在filter的FORWARD链中增加一条ACCEPT的规则( --icc=true ):

-A FORWARD -i docker0 -o docker0 -j ACCEPT
这是满足相互通信的第二步。当Docker daemon启动参数 --icc (icc参数表示是否允许容器间相互通信)设置为false时,以上规则会被设置为DROP,

Docker容器间的相互通信就被禁止,这种情况下,想让两个容器通信就需要在docker run时使用 --link 选项。

在Docker容器和外界通信的过程中,还涉及了数据包在多个网卡间的转发(如从docker0网卡到宿主机eth0的转发), 这需要内核将ip-forward功能打开,即将ip_forward系统参数设为 1 。Docker daemon启动的时候默认会将其设为1( --ip-forward=true ), 也可以通过以下命令手动设置:

$ echo 1 > /proc/sys/net/ipv4/ip_forward
$ cat  /proc/sys/net/ipv4/ip_forward
1

以上过程中所涉及的Docker daemon启动参数如下:

  • --iptables

    是否允许Docker daemon设置宿主机的iptables规则,默认为true。当设为false时,Dockerdaemon将不会改变你宿主机上的iptables规则。

  • --icc

    是否允许Docker容器间相互通信,默认为true。true或false改变的是FORWARD链中相应iptables规则的策略(ACCEPT、DROP)。由于操作的是iptables规则,所以需要——iptables=true才能生效。

  • --ip-forward

    是否将ip_forward参数设为1,默认为true,用于打开Linux内核的ip数据包转发功能。

注解

这些参数也是在Docker daemon启动时进行设置的,所以可以设置在DOCKER_OPTS变量中。

6.1.2.3. Docker容器的DNS和主机名

一个Docker镜像可以启动很多个Docker容器,通过查看,它们的主机名并不一样,也即是说主机名并非是被写入镜像中的。 前面已经提及,实际上容器中/etc目录下有3个文件是容器启动后被虚拟文件覆盖掉的, 分别是 /etc/hostname/etc/hosts/etc/resolv.conf ,通过在容器中运行 mount 命令可以查看。

1$ docker exec -it mynginx mount
2......
3/dev/sdb on /etc/resolv.conf type xfs (rw,relatime,attr2,inode64,noquota)
4/dev/sdb on /etc/hostname type xfs (rw,relatime,attr2,inode64,noquota)
5/dev/sdb on /etc/hosts type xfs (rw,relatime,attr2,inode64,noquota)
6......

这样能解决主机名的问题,同时也能让DNS及时更新(改变resolv.conf)。由于这些文件的维护方法随着Docker版本演进而不断变化, 因此尽量不修改这些文件,而是通过Docker提供的参数进行相关设置,参数配置方式如下:

  • -h HOSTNAME或者–hostname=HOSTNAME:

    设置容器的主机名,此名称会写在/etc/hostname和/etc/hosts文件中,也会在容器的bash提示符中看到。但是在外部,容器的主机名是无法查看的,不会出现在其他容器的hosts文件中,即使使用docker ps命令也查看不到。此参数是docker run命令的参数,而非Dockerdaemon的启动参数。

  • --dns=IP_ADDRESS...

    为容器配置DNS,写在 /etc/resolv.conf 中。该参数既可以在Docker daemon启动的时候设置也可以在 docker run 时设置,默认为 8.8.8.88.8.4.4

注意对以上3个文件的修改不会被 docker commit 保存,也就是不会保存在镜像中,重启容器也会导致修改失效。另外,在不稳定的网络环境下使用需要特别注意DNS的设置。

6.3. 新的link介绍

相比于传统的link系统提供的名字和别名的解析、容器间网络隔离( --icc=false )以及环境变量的注入, Docker v1.9后为用户自定义网络提供了DNS自动名字解析、同一个网络中容器间的隔离、可以动态加入或者退出多个网络、 支持 --link 为源容器设定别名等服务。在使用上,可以说除了环境变量的注入,新的网络模型给用户提供了更便捷和更自然的使用方式而 不影响原有的使用习惯。

在新的网络模型中,link系统只是在当前网络给源容器起了一个别名,并且这个别名只对接收容器有效。 新旧link系统的另一个重要的区别是新的link系统在创建一个link时并不要求源容器已经创建或者启动。 比如我们使用bridge驱动创建一个自定义网络isolated_nw,再运行一个容器container1加入该网络并链接另一个容器container2, 虽然container2还并不存在,如下代码所示。

可以看到,创建并启动完container2后,就可以在container1里面pingcontainer2的别名c2了(同样也可以ping名字和ID)

$ docker exec -it container1 cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.20.0.2      cb983f6f3ff2

在查看 /etc/hosts 文件后,发现里面并没有container2的相关信息,这表示新的link系统的实现与原来的配置hosts文件的方式并不相同。实际上,Docker是通过DNS解析的方式提供名字和别名的解析,这很好地解决了在传统link系统中由于容器重启造成注入的环境变量更新不及时的问题。在新的link系统下,用户甚至可以实现一对容器之间相互link。