在Docker中,容器作为一种虚拟化形式,通常是与 Docker 主机以及其它容器隔离的系统,但是它们之间又需要进行数据交换。为了实现多个容器之间的协作和数据交换,我们需要将它们连接在一起,这就是网络的作用。

Docker网络模式

安装Docker后,会自动创建三种类型的网络,分别是:bridgehostnone,可通过如下命令查看:

1
2
3
4
5
docker network ls
NETWORK ID NAME DRIVER SCOPE
6d91571ddf34 bridge bridge local
5e7707e2e6f5 host host local
cb91085ce0cf none null local

bridge模式

这是Docker的默认模式,在Docker server启动时,会在宿主机上创建一个名为docker0的虚拟网桥。

Docker会从RFC1918所定义的私有IP网段中,选择一个和宿主机不同的IP地址和子网分配给docker0(通常是172.17.0.0/16这个网段)。可通过如下命令查看:

1
2
3
4
5
ip addr show docker0
7: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether f2:23:06:d7:38:9f brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever

此后,连接到docker0的容器就从这个子网中选择一个未占用的IP使用,同一桥接网络中的容器可以使用这些 IP 地址相互通信。

例如我运行一个redis,再运行一个mysql,可通过如下命令查看:

1
docker network inspect bridge|grep -aG20 Containers

此时就会打印容器的相关配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"Containers": {
"364b6f72202cf8aa529becfdc1e6f8cf17566b845b60c185976852c5e2612edc": {
"Name": "mysql",
"EndpointID": "9fe4a3c1921547450b12e95ae0632a3d39650734496beb11a4a4d67818ded287",
"MacAddress": "d2:05:70:df:77:97",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
},
"c08aaf6cd1de2816132305fab889354ba16ee78000f79db48ae65089faeb9c2e": {
"Name": "redis",
"EndpointID": "13635a0d6f6502753c82abf9f3ba3fbd374d661440f6f86278599f610f8d4786",
"MacAddress": "3e:40:72:36:92:2e",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
}

每当运行一个容器时,新建的容器会自动桥接到这个接口,同时Docker 守护进程会创建一对对等虚拟设备接口 veth pair,将其中一个接口设置为容器的 eth0 接口(容器的网卡),另一个接口放置在宿主机的命名空间中,以类似 vethxxx 这样的名字命名,从而将宿主机上的所有容器都连接到这个内部网络上。

由于Docker网桥是宿主机虚拟出来的,并不是真实的网络设备,那么就表示外部网络无法直接通过Container IP访问到容器内部。想要外部网络访问容器,需要通过-p参数来映射端口,然后访问宿主机:映射端口即可,例如:

1
docker run -d  --name mysql  -p 3306:3306 mysql/mysql-server:latest

容器内是可以访问外网的,和网络模式无关。

宿主机与容器之间是可以通信的,反之,容器与宿主机也是可以通信的:

1
2
3
4
# 宿主机 ping 容器
ping 172.17.0.3
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.368 ms

同一个宿主机上,容器之间也是可以互相通信的,因为每个容器都连接到一个共享的桥接网络。

None模式

启动容器时加上--net=none选项即可以创建None类型网络的容器,None类型的网络,即没有网络,Docker不会设置容器内网络的任何信息,不会对网络进行任何配置.

也就是说,这个网络模式下的Docker 容器没有网卡、IP、路由等信息。只有lo回环网络,因此这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。一些对安全性要求高并且不需要联网的应用可以考虑使用none网络模式。

host模式

宿主机ip地址:192.168.0.164 broadcast:192.168.0.255 指定容器使用host网络模式方式:只需在用docker run命令运行容器的时候加上 --network=host 选项即可。

需要注意的是启动时指定--network=host,如果还指定了-p或-P,那这个时候就会有以下警告,并且通过-p或-P设置的参数将不会起到任何作用,端口号会以主机端口号为主,重复时则递增。例如,如果容器要使用端口3306,而主机上已经有程序在使用3306,Docker会尝试使用3307。

host模式容器不会虚拟出自己的网卡,而是与物理机共享网络,容器没有自己的IP,直接用宿主机IP,下图可以看出在容器中执行ip addr看到的是其宿主机的所有网卡。甚至连hostname也是host的。

host 模式的优点是:容器可以使用宿主机的网卡和外界的通信,不需要转发拆包,性能好。

但 host 模式也有非常严重的缺点:直接占用宿主机端口,会与宿主机其他应用竞争宿主机的网络资源,因此无法用在生产环境。

host模式的网络互通:

  • 容器和外网:外部通过宿主机IP:容器端口即可访问容器。
  • 容器和宿主机:host模式下,宿主机的IP就是容器的IP,因此可以互访。
  • 同一个宿主机上的多个容器:同一个宿主机的多个容器的IP都是宿主机IP,相当于一台宿主机上部署的各类服务,当然是能访问的。

Docker自定义网络

在使用过程中,通常都会使用自定义网络。

创建网络

在docker中,创建网络如下所示:

1
docker network create app-network

查看创建的网络

1
2
3
4
5
6
docker network ls
NETWORK ID NAME DRIVER SCOPE
1faaf47c09da app-network bridge local
6d91571ddf34 bridge bridge local
5e7707e2e6f5 host host local
cb91085ce0cf none null local

如果想要使用这个网络,那么在创建容器时,使用--net app-network来指定。

连接网络

通过 docker network connect 网络名称 容器名称 为容器连接新的网络模式。

1
docker network connect app-network mysql

通过docker inspect 容器名称查看信息,发现mysql容器多绑定了一个网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
"Networks": {
"app-network": {
"IPAMConfig": {
"IPv4Address": "",
"IPv6Address": ""
},
"Links": null,
"Aliases": [],
"DriverOpts": {},
"GwPriority": 0,
"NetworkID": "1faaf47c09da803a8e380b47586c8fe570c109ed3e8b37302b058cbac8e4e507",
"EndpointID": "e70016b589db4150a5e97921922d122f0c222b9c1f19f22626ecddb01f61f3e2",
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.2",
"MacAddress": "2a:01:1e:ce:cf:a1",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DNSNames": [
"mysql",
"364b6f72202c"
]
},
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"DriverOpts": null,
"GwPriority": 0,
"NetworkID": "6d91571ddf34e72fd0c5c36ed15499bc9c989e919c1429ba9130a6ad7531eec5",
"EndpointID": "9fe4a3c1921547450b12e95ae0632a3d39650734496beb11a4a4d67818ded287",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3",
"MacAddress": "d2:05:70:df:77:97",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DNSNames": null
}
}

断开网络

通过docker network disconnect 网络名称 容器名称命令断开网络。例如:

1
docker network disconnect app-network mysql

再次查看容器,发现Networks少了刚才连接的网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"DriverOpts": null,
"GwPriority": 0,
"NetworkID": "6d91571ddf34e72fd0c5c36ed15499bc9c989e919c1429ba9130a6ad7531eec5",
"EndpointID": "9fe4a3c1921547450b12e95ae0632a3d39650734496beb11a4a4d67818ded287",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3",
"MacAddress": "d2:05:70:df:77:97",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DNSNames": null
}
}

删除网络

通过docker network rm 网络名称来删除自定义网络,删除成功后会返回网络模式名称

1
2
3
docker network rm app-network

app-network

容器之间的网络通信

连接在同一个网桥

这样是可以直接通信的。相当于手机和电脑连接到同一个路由器上, 互相可以通信。但是因为容器的IP并不是固定的,所以几乎不会使用容器的IP地址去通信。

使用容器名称通信

这个技术时Docker 1.10开始,在docker daemon中内嵌了一个DNS Server,使得容器可以直接通过容器名称去通信。需要在创建容器的时候指定--name

但是,Docker DNS有限制,只能在user-defined(自定义)网络中使用。默认的bridge是无法使用的。

在同属于一个自定义网桥的两个容器中,是可以直接通过容器名称去通信

不同网桥之间通信

如果需要默认的bridge的容器也要和自定义网桥例如app-network访问,那么就需要上述提到的命令docker network connect 网桥名称 容器名称来实现了。

本文转载自:小白快速上手 Docker 05 | Docker 中的网络 - 知乎