Docker 进阶实战:数据管理、网络

本文介绍了Docker中数据管理的三种方式:Volume、Bindmounts和tmpfs,以及如何创建、挂载、共享和删除数据卷。此外,还详细讲解了Docker的网络管理,包括端口映射、容器互联的方法如Dockerlink和DockerNetworking,强调了它们在网络通信中的作用和优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Docker 进阶实战:数据管理、网络

数据管理

默认情况下,容器中数据的读写发生在容器的存储层,当容器被删除时其上的数据将会丢失。为了实现数据的持久化存储,Docker 为我们提供了以下几种方案:

  • Volumes
  • Bind mounts
  • tmpfs mounts

如下图,无论是哪种方式,其实对于容器来说它们都没有任何区别,都是存储在宿主机上的一个文件或者目录中,唯一的不同就是存储的位置或是文件系统、内存、Docker area。

Docker 数据管理方式

Volume

卷(volume)是在一个或者多个容器内被选定的目录,可以绕过分层的联合文件系统(Union File System),为 Docker 提供持久数据或者共享数据,它具有以下特点:

  • 对卷的修改会直接生效,并绕过镜像。
  • 当提交或者创建镜像时,卷不被包含在镜像里。
  • 卷可以在容器间共享。
  • 容器停止,卷里的内容依旧存在。
创建数据卷

我们可以使用 docker volume create 命令来创建一个数据卷,例如:

docker volume create test

接着可以使用 docker volume inspect 命令查看该数据卷的信息:

[
    {
        "CreatedAt": "2022-07-31T22:04:29+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/test/_data",
        "Name": "test",
        "Options": {},
        "Scope": "local"
    }
]

Mountpoint 中的路径 /var/lib/docker/volumes/test/_data 就是卷在宿主机上存储数据的目录。


挂载数据卷

那我们该怎么为容器挂载数据卷呢?可以在启动/创建容器的时候加上 -v 参数来挂载数据卷,例如:

docker run -d -v test:/data --name webserver orekilee/demo:nginx

这里我们将数据卷 test 挂载到了容器的 /data 目录下。

需要注意的是,即使我们在上一步没有创建数据卷,这里 docker 也会自动的去帮我们创建。如果我们没有指定数据卷的名字,docker 就会随机生成一个 id 来作为代替。


共享数据卷

如果想要让几个容器之间共享数据,可以在启动容器的时候用 --volumes-from 选项指定想要共享数据卷的容器,例如:

docker run -d --volumes-from webserver --name webserver1 orekilee/demo:nginx

此时我们新创建的容器 webserver1 就可以共享 webserver 所使用的所有数据卷,并且它们挂载的目录都是相同的。


删除数据卷

由于数据卷是用来为容器持久化数据的,所以其生命周期独立于容器之外,即使我们将容器销毁,docker 也并不会将它删除,而由于 docker 并不具备垃圾回收机制,这些无用的数据卷就会浪费我们的空间。

因此当一个数据卷没用之后,可以使用 docker volume rm 命令将其删除。

docker volume rm test


Bind mounts

除了挂载 docker area 中的数据卷,我们也可以将宿主机上的目录挂载到容器中,例如:

docker run -d -v /data/test:/data --name webserver2 orekilee/demo:nginx

上面的命令将宿主机中的 /data/test (必须使用绝对路径)目录作为一个卷挂载到容器中的 /data。如果宿主机中不存在该目录,将会直接创建一个。容器可以通过访问 /data 来直接操作宿主机中 /data/test 中的数据。


tmpfs mounts

与前面提到的两种方式不同,tmpfs mounts 所挂载的数据是临时存储于宿主机内存中的,即非持久化存储。当容器停止时,其中的数据也会随之删除。

我们可以在启动容器时使用 --tmpfs 参数来将数据挂载到内存中,例如:

docker run -d --tmpfs /data --name webserver3 orekilee/demo:nginx

此时我们可以使用 docker inspect 命令来查看其 mount 信息:

"Mounts": [
            {
                "Type": "tmpfs",
                "Source": "",
                "Destination": "/app",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ]

本节提到的三种方式都可以使用 –mount 选项来完成,其可以通过指定类型 type=[volume、bind、tmpfs] 以及宿主机目录 source=[宿主机目录] target=[容器挂载目录] 来完成,例如:

–mount type=bind,source=/data,target=/data

由于其语法冗余,本文就没有过多介绍,想要了解 –mount 与 -v、–tmpfs 区别的小伙伴可以自行查看 docker 官方文档。


网络

端口映射

当我们在容器中部署一些服务时(例如 MySQL、Tomcat、Nginx 等),如果想让外部也能够访问到这些应用,就需要在创建容器时加上 -p 或者 -P (不指定端口,随机映射到一个开放的端口上)参数来指定端口映射:

# -p格式有以下三种 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
docker run -p 8080:80 -d --name webserver1 orekilee/demo:nginx

这里我们将本地宿主机的 8080 端口绑定到了容器的 80 端口上,此时我们可以通过浏览器访问宿主机的 8080 端口,就可以访问到 nginx 服务,如下图:

访问容器中的nginx

如果我们使用 -P 来随机映射,可以使用 docker port 命令来查看其对应的端口号:

# 使用-P随机映射一个端口
docker run -P -d --name webserver222 orekilee/demo:nginx

# 查看映射的端口号
docker port webserver222

80/tcp -> 0.0.0.0:49158

那么它是如何实现的呢?其实很简单,只是在本地 iptable 的 nat 表中添加了相应的规则,我们可以使用 iptables -t nat -nL 2 命令来查看刚刚配置的几个端口:

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.17.0.8:80
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.17.0.9:80
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:49158 to:172.17.0.10:80


容器互联

在 Docker 中,如果想要让各个容器之间通过内网进行网络连接,通常有以下三种方法:

  1. docker 内部网络
  2. Docker link
  3. Docker Networking
Docker 内部网络

docker 为了能够连接容器与本地宿主网络,构建了一个虚拟的以太网桥 docker0,它会为每一个 docker 容器分配一个 ip 地址,我们可以通过 ip a 命令来查看它的接口信息:

3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:c9:f4:67:6d 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
    inet6 fe80::42:c9ff:fef4:676d/64 scope link
       valid_lft forever preferred_lft forever
5: veth6e15dc8@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether fa:c2:79:89:57:bb brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::f8c2:79ff:fe89:57bb/64 scope link
       valid_lft forever preferred_lft forever
7: vethaeec631@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether a6:08:87:69:93:be brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::a408:87ff:fe69:93be/64 scope link
       valid_lft forever preferred_lft forever

在这里我们发现当前所运行的容器都有一个对应的 veth 接口,那它有什么作用呢?

这里就先简单说明一下它的作用(原理在后续的章节会写),veth 其实是一个虚拟出来的网络接口,我们可以将其比作一个全双工的管道。管道的一端连接着 docker0,另一端则连接着我们的容器,两者可以通过这个管道来进行通信。通过为每一个容器创建一个这样的管道,docker 就构建出了一个虚拟的子网,这个子网由宿主机和所有的Docker容器共享。

Docker 网络 bridge 模式示意图

内部网络作为 docker 默认的方案,虽然看起来不错,但是其有一个致命的问题:每当容器重启时,ip 地址就会重新分配,如果我们在服务中将 ip 写死,则会导致服务不可用,需要重新配置。


Docker link

在 docker 1.9 之前,我们通常使用 Docker link 来解决上面的问题。它的使用方法非常简单,只需要在启动容器的时候,加上一个 –link 的参数即可。

这里我两个容器为例子:

# 首先启动一个容器
docker run -d --name webserver1 orekilee/demo:nginx


# 接着启动第二个容器,将其link到第一个容器上容器上
docker run -d --name webserver2 --link webserver1 orekilee/demo:nginx

此时,我们进入 webserver2 中,直接通过 ping 容器名来尝试连接 webserver1:

PING webserver1 (172.17.0.8) 56(84) bytes of data.
64 bytes from webserver1 (172.17.0.8): icmp_seq=1 ttl=64 time=0.065 ms
64 bytes from webserver1 (172.17.0.8): icmp_seq=2 ttl=64 time=0.066 ms
64 bytes from webserver1 (172.17.0.8): icmp_seq=3 ttl=64 time=0.068 ms

那么它是如何做到的呢?首先我们查看一下 /etc/hosts 文件:

::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.2	nginx1 91c559efadf8
172.17.0.3	d0e819e71e15

我们发现在 hosts 文件中,docker 直接为其加上了 ip 地址与容器名的映射。

接着使用 env 查看环境变量:

LANG=en_US.UTF-8
HOSTNAME=8f1d5dc8d58a
WEBSERVER1_PORT_80_TCP_PORT=80
WEBSERVER1_NAME=/webserver2/webserver1
which_declare=declare -f
PWD=/
HOME=/root
WEBSERVER1_PORT_80_TCP_ADDR=172.17.0.8
TERM=xterm
WEBSERVER1_PORT=tcp://172.17.0.8:80
WEBSERVER1_PORT_80_TCP=tcp://172.17.0.8:80
WEBSERVER1_PORT_80_TCP_PROTO=tcp
SHLVL=1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LESSOPEN=||/usr/bin/lesspipe.sh %s
BASH_FUNC_which%%=() {  ( alias;
 eval ${which_declare} ) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot "$@"
}
_=/usr/bin/env

在环境变量中,同样也写入了 webserver1 的连接信息。

随着容器的重启,这些网络信息也会自动进行更新。相比较于 docker 内部网络,我们在代码中不必去写死 ip 地址,而是可以直接使用容器名来进行通信。

除此之外,通过把容器 link 在一起,可以让客户容器直接访问任意服务容器的公开端口(只有通过 --link 才能连接到这个端口),容器的端口不需要对本地宿主机公开。使得我们可以限制容器化应用程序被攻击面,减少应用暴露的网络。

注意:Docker link 只能工作于同一台 Docker 宿主机中,不能链接位于不同 Docker 宿主机上的容器。


Docker Networking

在 docker 1.9 版本中推出了 Docker Networking,其允许用户创建自己的网络,容器可以通过这个网络互相通信,同时容器可以跨越不同的宿主机来通信,且网络配置可以更灵活地定制。

相比较于 Docker link,它有以下两个优点:

  1. Docker Networking 可以将容器连接到不同宿主机上的容器。
  2. 通过 Docker Networking 连接的容器可以在无需更新连接的情况下,对停止、启动或者重启容器。而使用 Docker link,则可能需要更新一些配置,或者重启相应的容器来维护 Docker 容器之间的链接。
  3. Docker Networking 不必事先创建容器再去连接它。同样,也不必关心容器的运行顺序。

除了默认的运行在单个主机上的 bridge 桥接网络,还有允许跨多台宿主机进行通信的 overlay 网络,只需要在创建时加上 -d overlay 参数即可。

接下来我们就看看它的用法,首先我们先创建一个桥接网络 test:

docker network create test

那我们该如何该如何让容器加入这个网络呢?只需要在创建容器的时候加上 --net 参数即可:

docker run -d --net=test --name webserver1 orekilee/demo:nginx
docker run -d --net=test --name webserver2 orekilee/demo:nginx

接着使用 docker network inspect 命令查看这个网络的信息:

[
    {
        "Name": "test",
        "Id": "4480ede291b6db5ead834dfcda0c35a4130028694a0151b35b9d4108d8f0a2d2",
        "Created": "2022-07-31T18:30:05.518031987+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "0e859754b01b674cc28b2a14e9ca25fd7bbbb27b5ef4f17b821408616561c8b9": {
                "Name": "webserver1",
                "EndpointID": "b4d3491f6abbe80c61f79588251b9c5ed36bae40238f6dd9a76d5ca91be40201",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "28b8cf143b840eaea720dca4591804d22b983ca0e6a19a365ff16338226669fc": {
                "Name": "webserver2",
                "EndpointID": "18b08b0441674ec966640adfd1410e7a31cd28e59746604f23e816df6ca818b6",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

此时我们看到我们的容器已经加入该网络,并且其 ip 地址,端口号都被记录其中。

接着我们来到 webserver1 上,尝试去连接 webserver2:

PING webserver2 (172.18.0.3) 56(84) bytes of data.
64 bytes from webserver2.test (172.18.0.3): icmp_seq=1 ttl=64 time=0.071 ms
64 bytes from webserver2.test (172.18.0.3): icmp_seq=2 ttl=64 time=0.071 ms
64 bytes from webserver2.test (172.18.0.3): icmp_seq=3 ttl=64 time=0.068 ms

此时我们发现,它们已经能够达成互联,并且与 docker link 相同,它们的 hosts 信息也会随着重启而自动更新。

除了在创建的时候指定 network,我们能不能让已经运行的容器加入 network 呢?

这时就需要使用 docker network connect 来将运行中的容器加入到已有的网络:

docker run -d --name webserver3 orekilee/demo:nginx
docker network connect test webserver3

此时再查看 docker network inspect,就可以发现容器已经加入:

[
    {
        "Name": "test",
        "Id": "4480ede291b6db5ead834dfcda0c35a4130028694a0151b35b9d4108d8f0a2d2",
        "Created": "2022-07-31T18:30:05.518031987+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "0e859754b01b674cc28b2a14e9ca25fd7bbbb27b5ef4f17b821408616561c8b9": {
                "Name": "webserver1",
                "EndpointID": "b4d3491f6abbe80c61f79588251b9c5ed36bae40238f6dd9a76d5ca91be40201",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "28b8cf143b840eaea720dca4591804d22b983ca0e6a19a365ff16338226669fc": {
                "Name": "webserver2",
                "EndpointID": "18b08b0441674ec966640adfd1410e7a31cd28e59746604f23e816df6ca818b6",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "bf0b77de8064434af64598a51ed62ca1915b47cc218ed3f354149b5a916b64d7": {
                "Name": "webserver3",
                "EndpointID": "3271ea7fb1b88ef9d2a80f284ac21f0408078b811ce0544e9770e2fbba14dd46",
                "MacAddress": "02:42:ac:12:00:04",
                "IPv4Address": "172.18.0.4/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

同理,我们可以使用 docker network disconnect 命令将容器从网络中删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌桓丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值