Docker 容器数据卷

什么是容器数据卷

数据卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性:

Docker容器产生的数据,如果不备份,那么当容器实例删除后,容器内的数据自然也就没有了。数据卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。

特点:

  • 数据卷可在容器之间共享或重用数据
  • 卷中的更改可以直接实时生效
  • 数据卷中的更改不会包含在镜像的更新中
  • 数据卷默认会一直存在,即使容器被删除
  • 数据卷的生命周期一直持续到没有容器使用它为止

容器中的管理数据主要有两种方式:

  • 数据卷:Data Volumes 容器内数据直接映射到本地主机环境

  • 数据卷容器:Data Volume Containers 使用特定容器维护数据卷

注:cp命令也是管理数据的方式,但是基本不会用到。

数据卷类型

有三种数据卷类型:

  1. 宿主机数据卷:直接在宿主机的文件系统中但是容器可以访问(bind mount)

  2. 命名数据卷:磁盘上Docker管理的数据卷,但是这个卷指定了名字。

  3. 匿名数据卷:磁盘上Docker管理的数据卷,因为没有指定名字,所以想要找到不容易,Docker来管理这些文件。

数据卷其实都在(如果没有网络文件系统等情况下)宿主机文件系统里面的,只是第一种是在宿主机内的特定目录下,而后两种则在docker管理的目录下,这个目录一般是 /var/lib/docker/volumes/xxxx/_data

推荐使用 宿主机数据卷 方式持久化数据

数据覆盖问题

如果挂载一个空的数据卷到容器中的一个非空目录中,那么这个目录下的文件会被复制到数据卷中。

如果挂载一个非空的数据卷到容器中的一个目录中,那么容器中的目录会显示数据卷中的数据。如果原来容器中的目录有数据,那么原始数据会被隐藏掉。

如何使用

docker run -v /宿主机绝对路径目录:/容器内目录 镜像名

建议先创建好目录后再进行数据挂载

基本使用

1
docker run -itd --name mysql --privileged=true -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -v /data/mysql:/var/lib/mysql mysql:5.7.31 

匿名&具名数据卷

匿名数据卷

匿名挂载就是在指定数据卷的时候,不指定容器路径对应的主机路径,这样对应映射的主机路径就是默认的路径/var/lib/docker/volumes/中自动生成一个随机命名的文件夹。

1
2
[root@localhost /]# docker run -d -P --name nginx01 -v /etc/nginx nginx
bc96b3eeda762d7f3ed0990b97dffbaffde3e76d7b5f7c67051c06cd4d4d4e06

查看所有的数据卷volume的情况, VOLUME NAME这里的值是真实存在的目录(一般在/var/lib/docker/volumes/中)。

1
2
3
4
5
6
[root@localhost /]# docker volume ls
DRIVER VOLUME NAME
local 3d36eef7142c28c563e6945d703086101efb9aac31fbe401db1f9a06869bfd69
local 6e35689a5f472a67e2fd3943b9ee5945501fa49aed255bf93d7d7d558422638b
local 1914bdf6c087393c99a470dc137bba2c1880ad7fbb359857ac8818e5e643e119
local 79153f67654c09e5b4c3f4cd19957bce78b82ae7f5ae17e9b6d02989faf32f84

因为没有指定名字,所以想要找到不容易,但是我们可以通过如下命令查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost /]# docker inspect nginx01
[
....
"Mounts": [
{
"Type": "volume",
"Name": "1914bdf6c087393c99a470dc137bba2c1880ad7fbb359857ac8818e5e643e119",
"Source": "/var/lib/docker/volumes/1914bdf6c087393c99a470dc137bba2c1880ad7fbb359857ac8818e5e643e119/_data",
"Destination": "/etc/nginx",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
....
]

**命名(**具名)数据卷

就是指定文件夹名称,区别于指定路径挂载,这里的指定文件夹名称是在Docker指定的默认数据卷路径下的。通过docker volume ls命令可以查看当前数据卷的目录情况。

1
2
3
4
5
6
7
8
9
[root@localhost /]# docker run -d -P --name nginx02 -v test-nginx:/etc/nginx nginx
b549334cfd1107010ec858feb5c94a79b1b1062f4be7a9f9593cdab58b01fb49
[root@localhost /]# docker volume ls
DRIVER VOLUME NAME
local 3d36eef7142c28c563e6945d703086101efb9aac31fbe401db1f9a06869bfd69
local 6e35689a5f472a67e2fd3943b9ee5945501fa49aed255bf93d7d7d558422638b
local 1914bdf6c087393c99a470dc137bba2c1880ad7fbb359857ac8818e5e643e119
local 79153f67654c09e5b4c3f4cd19957bce78b82ae7f5ae17e9b6d02989faf32f84
local test-nginx

具名数据卷查看数据卷信息就比较简单了:docker volume inspect 数据卷名称

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost /]# docker volume inspect test-nginx
[
{
"CreatedAt": "2022-07-07T14:07:23+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/test-nginx/_data",
"Name": "test-nginx",
"Options": null,
"Scope": "local"
}
]

容器目录权限

通过 -v 容器内路径: ro rw 改变读写权限

ro:readonly 只读
rw:readwrite 可读可写

1
2
3
4
5
docker run -it -v /宿主机绝对路径目录:/容器内目录:ro 镜像名 
docker run -it -v /宿主机绝对路径目录:/容器内目录:rw 镜像名
例如:
docker run -d -P --name nginx01 -v /opt/nginx:/etc/nginx:ro nginx
docker run -d -P --name nginx01 -v /opt/nginx:/etc/nginx:rw nginx

只要看到ro就说明这个路径只能通过宿主机来操作,容器内部无法操作

其它

  • 创建数据卷
1
docker volume create [OPTIONS] [VOLUME]
  • 删除数据卷
1
docker volume rm [OPTIONS] VOLUME [VOLUME...]
  • 无主的数据卷可能会占据很多空间,要清理请使用以下命令
1
docker volume prune [OPTIONS]

e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost /]# docker volume create my-vol
my-vol
[root@localhost /]# docker volume ls
DRIVER VOLUME NAME
local my-vol
[root@localhost /]# docker volume rm my-vol
my-vol
[root@localhost /]# docker volume ls
DRIVER VOLUME NAME
[root@localhost /]# docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B
  • 删除容器之时删除相关的卷

数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷 。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令

1
docker rm -v 容器id或名字

Docker 网络

什么是docker0?

介绍

当未启动docker时,我们使用ifconfig查看网络可以看到如下的内容:

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
[root@localhost ~]# ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.26.131 netmask 255.255.255.0 broadcast 192.168.26.255
inet6 fe80::f872:cd45:fd96:9ae3 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:e9:08:b6 txqueuelen 1000 (Ethernet)
RX packets 768 bytes 74945 (73.1 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1085 bytes 298038 (291.0 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

virbr0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.122.1 netmask 255.255.255.0 broadcast 192.168.122.255
ether 52:54:00:85:fc:07 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

注:有的机器会出现eth0的网络,如果你安装并使用过docker,那么还可能还会有br或者vethe开头的虚拟网桥,但是这些貌似都不重要,重要的是安装完docker以后再使用ifconfig命令查看,可以看到多了一个docker0的虚拟网桥。

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
[root@localhost ~]# ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:63:59:22:bb txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.26.131 netmask 255.255.255.0 broadcast 192.168.26.255
inet6 fe80::f872:cd45:fd96:9ae3 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:e9:08:b6 txqueuelen 1000 (Ethernet)
RX packets 768 bytes 74945 (73.1 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1085 bytes 298038 (291.0 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

virbr0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.122.1 netmask 255.255.255.0 broadcast 192.168.122.255
ether 52:54:00:85:fc:07 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

从上面我们得知了,当Docker 启动时,会自动在主机上创建一个 docker0 虚拟网桥,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机。它会在挂载到它的网口之间进行转发。

测试

我们以nginx测试下主机和容器以及容器和容器之间能不能ping通:

  1. 因为容器里一般没有ping命令,所以我们可以用Dockerfile先简单定制一个镜像
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
[root@localhost ~]# cd /opt/dockerfile/nginx
[root@localhost nginx]# ls
Dockerfile
[root@localhost nginx]# docker build -t cheng-nginx:1.0 .
Sending build context to Docker daemon 2.048kB
Step 1/5 : FROM nginx:latest
---> 670dcc86b69d
Step 2/5 : MAINTAINER cheng<chave_z@163.com>
---> Running in 20c3ab3f0da1
Removing intermediate container 20c3ab3f0da1
---> 2a913ad1a5fc
Step 3/5 : RUN apt-get update
---> Running in d2c54691ac48
Get:1 http://deb.debian.org/debian bullseye InRelease [116 kB]
---> 5ef39efc718a
Step 4/5 : RUN apt -y install iputils-ping
---> Running in 573723109c34
Step 5/5 : RUN apt -y install iproute2
---> Running in d021ada4b6b4
// ....
Successfully built a9450932c5c8
Successfully tagged cheng-nginx:1.0
[root@localhost nginx]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
cheng-nginx 1.0 a9450932c5c8 9 seconds ago 162MB
nginx latest 670dcc86b69d 38 hours ago 142MB
mysql latest 33037edcac9b 8 days ago 444MB
tomcat latest 451d25ef4583 3 weeks ago 483MB

上面/opt/dockerfile/nginx的内容为:

1
2
3
4
5
6
7
8
9
# 文件内容如下
FROM nginx:latest

MAINTAINER cheng<chave_z@163.com>

RUN apt-get update
RUN apt -y install iputils-ping
# 安装 ip addr命令
RUN apt -y install iproute2
  1. 创建容器,测试是否能ping通
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@localhost nginx]# docker run -d -P --name nginx01 cheng-nginx:1.0
5dd776cf75477cb34c187c2a67fbaf29fbf277179fe2e2078d9aaf8b04e4ce04
[root@localhost nginx]# docker run -d -P --name nginx02 cheng-nginx:1.0
ea71e5b80ba7411d16eea6b229afb3e236dc241fc66d0d74316d3f67369bdf43
[root@localhost nginx]# docker exec -it nginx01 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
5: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
[root@localhost nginx]# docker exec -it nginx02 ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.114 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.061 ms
[root@localhost nginx]# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.060 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.054 ms

从上面的结果我们能看到 主机和容器 以及 容器和容器 之间都是能相互访问的。那他是什么原理呢?

docker 网络原理

介绍

上文我们知道每一个安装了Docker的linux主机都有一个docker0的虚拟网卡。这是个桥接网卡,使用了veth-pair技术,而每启动一个容器,linux主机就会多了一个虚拟网卡。例如上面我们启动nginx01 执行ip addr 命令我们看到多了一个 5: eth0@if6,我们在主机上执行ip addr 发现主机也会多一个虚拟网卡:

6: vethf660ce3@if5,同样的,我们再启动一个nginx03 也会发现一样的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@localhost ~]# docker run -d -P --name nginx03 cheng-nginx:1.0
0102fe078ea2530cb1cf359140733ce71dbb7dd4c681a8810a4f55c3d2178c6b
[root@localhost ~]# ip addr
# .....
8: vethdc49038@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether d2:86:54:c9:4f:91 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::d086:54ff:fec9:4f91/64 scope link
valid_lft forever preferred_lft forever
[root@localhost ~]# docker exec -it nginx03 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
7: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

我们可以看到: 主机上多了一个网卡 8: vethdc49038@if7 容器内也多了一个 7: eth0@if8,反观nginx01看到的现象:

  • nginx01与主机:6: vethf660ce3@if5 对应 5: eth0@if6

  • nginx03与主机:8: vethdc49038@if7 对应 7: eth0@if8

也就是说没启动一个容器,都会有一对网卡出现,veth-pair 就是一对的虚拟设备接口,它都是成对出现的。一端连着协议栈,一端彼此相连着。

正因为有这个特性,它常常充当着一个桥梁,连接着各种虚拟网络设备! “Bridge、OVS 之间的连接”,“Docker 容器之间的连接” 等等,以此构建出非常复杂的虚拟网络结构,比如 OpenStack Neutron。 上面那个nginx的网络模型图如下:

image-20220725132226276

结论

Docker 随机分配一个本地未占用的私有网段(在 RFC1918 (opens new window)中定义)中的一个地址给 docker0 接口。比如典型的 172.17.42.1,掩码为 255.255.0.0。此后启动的容器内的网口也会自动分配一个同一网段(172.17.0.0/16)的地址。

当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。

Docker中的网络接口默认都是虚拟的接口。虚拟接口的优势就是转发效率极高(因为Linux是在内核中进行数据的复制来实现虚拟接口之间的数据转发,无需通过外部的网络设备交换),对于本地系统和容器系统来说,虚拟接口跟一个正常的以太网卡相比并没有区别,只是他的速度快很多。

image-20220721144345229

–Link命令(了解)

我们编写一个服务,数据库连接地址原来是使用ip的,如果ip变化就不行了,那我们能不能使用服务名访问呢?jdbc:mysql://mysql:3306,这样的话哪怕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
43
44
45
46
47
48
49
50
# 先停止nginx03,保留nginx01与nginx02
[root@localhost ~]# docker stop nginx03
nginx03
[root@localhost ~]# docker rm nginx03
nginx03

# 我们先尝试用容器名ping一下看能不能ping通
[root@localhost ~]# docker exec -it nginx02 ping nginx01
ping: nginx01: Temporary failure in name resolution # 发现ping不通

# 此时我们启动一个nginx03并使用--link命令
[root@localhost ~]# docker run -d -P --name nginx03 --link nginx02 cheng-nginx:1.0
e36c94df18313671674fbb81602e3b3081ea9b3f3971ca867f0a4cc9576e188c

# 这样nginx03就能ping通nginx02了
[root@localhost ~]# docker exec -it nginx03 ping nginx02
PING nginx02 (172.17.0.3) 56(84) bytes of data.
64 bytes from nginx02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.076 ms
64 bytes from nginx02 (172.17.0.3): icmp_seq=2 ttl=64 time=0.053 ms
^C
--- nginx02 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 0.053/0.064/0.076/0.011 ms

# 但是nginx03就能pingnginx01会失败,这个正常,因为上面的--link命令并没有涉及到nginx01
[root@localhost ~]# docker exec -it nginx03 ping nginx01
ping: nginx01: Temporary failure in name resolution

# 但是nginx02就能pingnginx03也会失败了,这是为什么呢?
[root@localhost ~]# docker exec -it nginx02 ping nginx03
ping: nginx03: Temporary failure in name resolution

# 我们查看一下nginx03和nginx02的host配置
[root@localhost ~]# docker exec -it nginx03 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.17.0.3 nginx02 335e2a9c76c6
172.17.0.4 e36c94df1831
[root@localhost ~]# docker exec -it nginx02 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.17.0.3 335e2a9c76c6

从上面的host文件配置可以看出,**–link命令其实就是直接把需要link的主机的域名和ip直接配置到了当前容器的hosts文件中**,所以只有nginx03能ping通nginx02,而且–link早都过时了,我们不推荐使用,可以使用自定义网络的方式来替代它。

docker网络模式

Linux 平台下,Docker容器网络资源通过内核的 Network Namespace 机制实现隔离,不同的 Network Namespace 有各自的网络设备、协议栈、路由表、防火墙规则等,反之,同一个 Network Namespace 下的进程共享同一网络试图。通过 Network Namespace 的灵活操纵,Docker 提供了五种容器网络模式。

模式 配置 简介
bridge –net=bridge(或不写) (默认为该模式)此模式会为每一个容器分配、设置IP等,并将容器连接到一个docker0虚拟网桥,通过docker0网桥以及Iptables nat表配置与宿主机通信。
host –net=host 容器不会虚拟出自己的网卡,配置自己的IP等,它与宿主机共享Network namespace。
container –net=container:NAME_or_ID 新建的容器不会虚拟出自己的网卡以及配置自己的IP,而是和一个指定的容器共享IP、端口范围等。
none –net=none 容器有独立的Network namespace,但是没有任何网络设置。
用户自定义 –net=自定义网络 用户自己使用network相关命令定义网络,创建容器的 时候可以指定为自己定义的网络

安装Docker后,它会自动创建三个网络模式,可以使用以下命令列出当前已有的网络模式:

1
2
3
4
5
[root@localhost /]# docker network ls
NETWORK ID NAME DRIVER SCOPE
399fe5094d70 bridge bridge local
a6ab68c3e731 host host local
cd26a41fdaa7 none null local

bridge模式

默认的网络模式。bridge模式下容器没有一个公有ip,只有宿主机可以直接访问,外部主机是不可见的,但容器通过宿主机的NAT规则后可以访问外网。

Bridge 桥接模式主要实现步骤

  • Docker Daemon 利用 veth pair 技术,在宿主机上创建两个虚拟网络接口设备,假设为veth0 和 veth1。而veth pair 技术的特性可以保证无论哪一个 veth 接收到网络报文,都会将报文传输给另一方。

  • Docker Daemon 将 veth0 附加到 Docker Daemon 创建的 docker0网桥上。保证宿主机的网络报文可以发往 veth0;

  • Docker Daemon 将 veth1 添加到 Docker Container 所属的 namespace 下,并被改名为eth0。如此一来,保证宿主机的网络报文若发往 veth0,则立即会被 eth0 接收,实现宿主机到DockerContainer网络的联通性;同时,也保证 Docker Container 单独使用 eth0,实现容器网络环境的隔离性。

Bridge桥接模式的缺陷

  1. 最明显的是,该模式下 Docker Container 不具有一个公有 IP,即和宿主机的 eth0 不处于同一个网段。导致的结果是宿主机以外的世界不能直接和容器进行通信。

  2. 虽然 NAT 模式经过中间处理实现了这一点,但是 NAT 模式仍然存在问题与不便,如:容器均需要在宿主机上竞争端口,容器内部服务的访问者需要使用服务发现获知服务的外部端口等。

  3. 另外 NAT 模式由于是在三层网络上的实现手段,故肯定会影响网络的传输效率。

注意:

veth设备是成双成对出现的,一端是容器内部命名为eth0,一端是加入到网桥并命名的veth(通常命名为veth),它们组成了一个数据传输通道,一端进一端出,veth设备连接了两个网络设备并实现了数据通信

image-20220725115535030

host模式

host网络模式需要在容器创建时指定–network=host

相当于Vmware中的NAT模式,与宿主机在同一个网络中,但没有独立IP地址。如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。

使用host模式的容器可以直接使用宿主机的IP地址与外界通信,容器内部的服务端口也可以使用宿主机的端口,不需要进行NAT,host最大的优势就是网络性能比较好,但是docker host上已经使用的端口就不能再用了,网络的隔离性不好。

host 模式是 bridge 桥接模式很好的补充。采用 host 模式的 Docker Container,可以直接使用宿主机的 IP地址与外界进行通信,若宿主机的 eth0 是一个公有 IP,那么容器也拥有这个公有IP。同时容器内服务的端口也可以使用宿主机的端口,无需额外进行 NAT 转换。

host模式可以让容器共享宿主机网络栈,这样的好处是外部主机与容器直接通信,但是容器的网络缺少隔离性。

Host网络模式的缺陷

最明显的是 Docker Container 网络环境隔离性的弱化。即容器不再拥有隔离、独立的网络环境。

另外,使用 host 模式的 Docker Container 虽然可以让容器内部的服务和传统情况无差别、无改造的使用,但是由于网络隔离性的弱化,该容器会与宿主机共享竞争网络栈的使用;

另外,容器内部将不再拥有所有的端口资源,原因是部分端口资源已经被宿主机本身的服务占用,还有部分端口已经用以 bridge 网络模式容器的端口映射。

Container网络模式

在容器创建时使用–network=container:vm1指定。(vm1指定的是运行的容器名)

一种特殊host网络模式

Container 网络模式是 Docker 中一种较为特别的网络的模式。

处于这个模式下的 Docker 容器会共享一个网络环境,这样两个容器之间可以使用localhost高效快速通信。

缺陷:

它并没有改善容器与宿主机以外世界通信的情况(和桥接模式一样,不能连接宿主机以外的其他设备)。这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。

同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过lo 网卡设备通信。

none模式

none模式可以在容器创建时通过–network=none来指定

使用none模式,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。

这种网络模式下容器只有lo回环网络,没有其他网卡。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。

自定义网络模式

默认情况下,docker容器之间是不能通过容器名来访问的,但是我们能通过自定义网络来解决这个问题,演示如下:

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
# 创建一个名为mynet的网络
[root@localhost ~]# docker network create mynet
01c9a9709b545f7fd286973b328cb937b619f5a2847f9cac51f38cd5c2514c56
[root@localhost ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
c1850efcce5b bridge bridge local
299055be968e host host local
01c9a9709b54 mynet bridge local
f993cb2bafbf none null local

# 我们来启动两个容器用于测试,使用自己的自定义网络 mynet
[root@localhost ~]# docker run -d -P --name nginx01 --net mynet cheng-nginx:1.0
c089ea53564213a2107e871e462a381049a81b3aa13169d0fc52e4be3f37b4c5
[root@localhost ~]# docker run -d -P --name nginx02 --net mynet cheng-nginx:1.0
73529f983d3e97421af3d2c75108324d6b84f7e74a25410507677b0aae3f8007
# 测试在 nginx01 上通过服务名是否能联通 nginx02
[root@localhost ~]# docker exec -it nginx01 ping nginx02
PING nginx02 (172.18.0.4) 56(84) bytes of data.
64 bytes from nginx02.mynet (172.18.0.4): icmp_seq=1 ttl=64 time=0.101 ms
64 bytes from nginx02.mynet (172.18.0.4): icmp_seq=2 ttl=64 time=0.057 ms
^C
--- nginx02 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.057/0.079/0.101/0.022 ms
# 测试在 nginx02 上通过服务名是否能联通 nginx01
[root@localhost ~]# docker exec -it nginx02 ping nginx01
PING nginx01 (172.18.0.3) 56(84) bytes of data.
64 bytes from nginx01.mynet (172.18.0.3): icmp_seq=1 ttl=64 time=0.064 ms
64 bytes from nginx01.mynet (172.18.0.3): icmp_seq=2 ttl=64 time=0.057 ms
^C
--- nginx01 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.057/0.060/0.064/0.003 ms

从上面的结果能发现,docker自定义的网络本身就会帮我们维护好主机名和ip的对应关系(ip和域名都能ping通) ,所以我们平时都可以这样使用网络。

如何跨网络访问

如果要跨网络操作别人,就需要使用docker network connect [OPTIONS] NETWORK CONTAINER连接,操作如下:

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
43
44
# 先排除干扰
[root@localhost ~]# docker rm -f $(docker ps -aq)
73529f983d3e
c089ea535642
5202e4a761dc
# 在docker0网络上创建nginx01、nginx02
[root@localhost ~]# docker run -d -P --name nginx01 cheng-nginx:1.0
9fe601a78444962d5eadf7b961709c11350ae9a6bb7e8555f48e06a51d0fbd70
[root@localhost ~]# docker run -d -P --name nginx02 cheng-nginx:1.0
d0f2ca7c64d564a3f88c2950bd4905d513e6ce4583f29e06e944740a4155e403

# 在自定义网络 mynet 上创建nginx_mynet_01
[root@localhost ~]# docker run -d -P --name nginx_mynet_01 --net mynet cheng-nginx:1.0
d0813810affa2a5128e82c636b2cd5ab80da6e45a163fbf1c2ff069588d4c13b

# 测试docker0上的 nginx01 是否能ping通 mynet 上的 nginx_mynet_01
[root@localhost ~]# docker exec -it nginx01 ping nginx_mynet_01
ping: nginx_mynet_01: Temporary failure in name resolution # 发现不能ping通

# 打通mynet-docker0 的网络
# 命令 docker network connect [OPTIONS] NETWORK CONTAINER
[root@localhost ~]# docker network connect mynet nginx01

# 继续测试是否能ping 通,发现 nginx_mynet_01 和 nginx01 已经能连通了
[root@localhost ~]# docker exec -it nginx01 ping nginx_mynet_01
PING nginx_mynet_01 (172.18.0.2) 56(84) bytes of data.
64 bytes from nginx_mynet_01.mynet (172.18.0.2): icmp_seq=1 ttl=64 time=0.087 ms
^C
--- nginx_mynet_01 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.087/0.087/0.087/0.000 ms
[root@localhost ~]# docker exec -it nginx_mynet_01 ping nginx01
PING nginx01 (172.18.0.4) 56(84) bytes of data.
64 bytes from nginx01.mynet (172.18.0.4): icmp_seq=1 ttl=64 time=0.110 ms
64 bytes from nginx01.mynet (172.18.0.4): icmp_seq=2 ttl=64 time=0.055 ms
64 bytes from nginx01.mynet (172.18.0.4): icmp_seq=3 ttl=64 time=0.075 ms
^C
--- nginx01 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.055/0.080/0.110/0.022 ms

# nginx_mynet_01 和 nginx02 仍旧不通
[root@localhost ~]# docker exec -it nginx02 ping nginx_mynet_01
ping: nginx_mynet_01: Temporary failure in name resolution

docker网络主要作用

docker网络的主要作用如下:

  • 容器间的互联和通信以及端口映射

容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P-p 参数来指定端口映射。当使用 -P 标记时,Docker 会随机映射一个端口到内部容器开放的网络端口。

  • 容器IP变动时候可以通过服务名直接网络通信而不受到影响

我们开发的web项目基本都需要连接数据库,那么配置文件里就需要指定数据库的url地址。但是我们用Docker创建数据库事,假设数据库出问题了,我们重新启动运行一个,这个时候数据库容器的地址就会发生变化,因为docker会给每个容器都分配一个ip,且容器和容器之间是可以互相访问的。

参考:

Docker 从入门到实践

《Docker进阶与实战》