介绍
Docker 是一种流行的容器化工具,用于为软件应用程序提供一个文件系统,该文件系统包含它们运行所需的一切。 使用 Docker 容器可确保软件无论部署在何处都以相同的方式运行,因为它的运行时环境是一致的。
通常,Docker 容器是短暂的,运行时间与容器中发出的命令完成所需的时间一样长。 然而,有时,应用程序需要在容器被删除后共享数据访问或持久化数据。 数据库、用户为网站生成的内容和日志文件只是不切实际或不可能包含在 Docker 映像中但应用程序需要访问的数据的几个示例。 Docker Volumes 提供对数据的持久访问。
Docker 卷可以在创建容器的同一命令中创建和附加,也可以独立于任何容器创建并在以后附加。 在本文中,我们将研究在容器之间共享数据的四种不同方式。
先决条件
要阅读本文,您将需要一个 Ubuntu 20.04 服务器,其中包含以下内容:
- 具有 sudo 权限的非 root 用户。 Initial Server Setup with Ubuntu 20.04 指南解释了如何设置。
- 使用 How To Install and Use Docker on Ubuntu 20.04 的 Step 1 和 Step 2 中的说明安装 Docker
注意: 尽管先决条件给出了在 Ubuntu 20.04 上安装 Docker 的说明,但本文中用于 Docker 数据卷的 docker
命令应该可以在其他操作系统上运行,只要安装了 Docker 并且sudo 用户已添加到 docker
组。
第 1 步 — 创建独立卷
在 Docker 的 1.9 版本中引入的 docker volume create
命令允许您创建卷,而无需将其与任何特定容器相关联。 我们将使用此命令添加一个名为 DataVolume1
的卷:
docker volume create --name DataVolume1
显示名称,说明命令成功:
OutputDataVolume1
为了使用该卷,我们将从 Ubuntu 映像创建一个新容器,使用 --rm
标志在我们退出时自动删除它。 我们还将使用 -v
来挂载新卷。 -v
需要卷的名称、冒号,然后是卷应出现在容器内的绝对路径。 如果路径中的目录不作为映像的一部分存在,则将在命令运行时创建它们。 如果他们 do 存在,挂载的卷将隐藏现有的内容:
docker run -ti --rm -v DataVolume1:/datavolume1 ubuntu
在容器中,让我们将一些数据写入卷:
echo "Example1" > /datavolume1/Example1.txt
因为我们使用了 --rm
标志,所以我们的容器会在我们退出时被自动删除。 但是,我们的卷仍然可以访问。
exit
我们可以使用 docker volume inspect
验证系统上是否存在该卷:
docker volume inspect DataVolume1
Output[ { "CreatedAt": "2018-07-11T16:57:54Z", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/DataVolume1/_data", "Name": "DataVolume1", "Options": {}, "Scope": "local" } ]
注意:我们甚至可以查看主机上的数据,路径为Mountpoint
。 但是,我们应该避免更改它,因为如果应用程序或容器不知道更改,它可能会导致数据损坏。
接下来,让我们启动一个新容器并附加 DataVolume1
:
docker run --rm -ti -v DataVolume1:/datavolume1 ubuntu
验证内容:
cat /datavolume1/Example1.txt
OutputExample1
退出容器:
exit
在这个例子中,我们创建了一个卷,将它附加到一个容器上,并验证了它的持久性。
第 2 步 — 创建一个在容器被移除时仍然存在的卷
在我们的下一个示例中,我们将在创建容器的同时创建一个卷,删除该容器,然后将该卷附加到一个新容器。
我们将使用 docker run
命令使用基本 Ubuntu 映像创建一个新容器。 -t
将给我们一个终端,-i
将允许我们与之交互。 为清楚起见,我们将使用 --name
来识别容器。
-v
标志将允许我们创建一个新卷,我们将其称为 DataVolume2
。 我们将使用冒号将此名称与容器中应安装卷的路径分开。 最后,我们将指定基础 Ubuntu 映像,并依赖 Ubuntu 基础映像的 Docker 文件 、bash
中的默认命令将我们放入 shell:
docker run -ti --name=Container2 -v DataVolume2:/datavolume2 ubuntu
注意: -v
标志非常灵活。 它可以绑定或命名一个卷,只需稍微调整语法。 如果第一个参数以 /
或 ~/
开头,则您正在创建绑定挂载。 删除它,您将命名该卷。 例如:
-v /path:/path/in/container
挂载主机目录,/path
在/path/in/container
-v path:/path/in/container
创建一个名为path
的卷,与主机无关。
有关从主机绑定目录的更多信息,请参阅 如何在 Docker 容器和主机之间共享数据
在容器中,我们将向卷中写入一些数据:
echo "Example2" > /datavolume2/Example2.txt cat /datavolume2/Example2.txt
OutputExample2
让我们退出容器:
exit
当我们重新启动容器时,卷会自动挂载:
docker start -ai Container2
让我们验证卷确实已安装并且我们的数据仍然存在:
cat /datavolume2/Example2.txt
OutputExample2
最后,让我们退出并清理:
exit
如果容器引用了卷,Docker 不会让我们删除它。 让我们看看当我们尝试时会发生什么:
docker volume rm DataVolume2
该消息告诉我们该卷仍在使用中,并提供了容器 ID 的长版本:
OutputError response from daemon: unable to remove volume: remove DataVolume2: volume is in use - [d0d2233b668eddad4986313c7a4a1bc0d2edaf0c7e1c02a6a6256de27db17a63]
我们可以使用这个 ID 来移除容器:
docker rm d0d2233b668eddad4986313c7a4a1bc0d2edaf0c7e1c02a6a6256de27db17a63
Outputd0d2233b668eddad4986313c7a4a1bc0d2edaf0c7e1c02a6a6256de27db17a63
移除容器不会影响音量。 通过使用 docker volume ls
列出卷,我们可以看到它仍然存在于系统中:
docker volume ls
OutputDRIVER VOLUME NAME local DataVolume2
我们可以使用 docker volume rm
来删除它:
docker volume rm DataVolume2
在此示例中,我们在创建容器的同时创建了一个空数据卷。 在我们的下一个示例中,我们将探索当我们使用已经包含数据的容器目录创建卷时会发生什么。
第 3 步 — 从包含数据的现有目录创建卷
一般来说,使用docker volume create
独立创建卷和创建容器的同时创建卷是等价的,只有一个例外。 如果我们在创建容器 和 的同时创建卷,则我们提供包含基础映像中数据的目录的路径,该数据将被复制到卷中。
例如,我们将创建一个容器并在 /var
中添加数据卷,该目录包含基础映像中的数据:
docker run -ti --rm -v DataVolume3:/var ubuntu
基础镜像的 /var
目录中的所有内容都被复制到卷中,我们可以将该卷挂载到新容器中。
退出当前容器:
exit
这一次,我们将发出自己的 ls
命令,而不是依赖基础映像的默认 bash
命令,该命令将在不进入 shell 的情况下显示卷的内容:
docker run --rm -v DataVolume3:/datavolume3 ubuntu ls datavolume3
目录 datavolume3
现在具有基本映像的 /var
目录内容的副本:
Outputbackups cache lib local lock log mail opt run spool tmp
我们不太可能希望以这种方式挂载 /var/
,但如果我们制作了自己的映像并想要一种简单的方法来保存数据,这会很有帮助。 在我们的下一个示例中,我们将演示如何在多个容器之间共享一个卷。
第 4 步 — 在多个 Docker 容器之间共享数据
到目前为止,我们一次将一个卷附加到一个容器。 通常,我们希望多个容器附加到同一个数据卷。 这相对容易实现,但有一个关键警告:此时,Docker 不处理文件锁定。 如果您需要多个容器写入卷,则在这些容器中运行的应用程序 必须 设计为写入共享数据存储,以防止数据损坏。
创建 Container4 和 DataVolume4
使用 docker run
创建一个名为 Container4
的新容器,并附加一个数据卷:
docker run -ti --name=Container4 -v DataVolume4:/datavolume4 ubuntu
接下来我们将创建一个文件并添加一些文本:
echo "This file is shared between containers" > /datavolume4/Example4.txt
然后,我们将退出容器:
exit
这会将我们返回到主机命令提示符,我们将在其中创建一个新容器,用于从 Container4
装载数据卷。
创建 Container5 并从 Container4 挂载卷
我们将创建 Container5
,并从 Container4
挂载卷:
docker run -ti --name=Container5 --volumes-from Container4 ubuntu
让我们检查数据持久性:
cat /datavolume4/Example4.txt
OutputThis file is shared between containers
现在让我们从 Container5
中添加一些文本:
echo "Both containers can write to DataVolume4" >> /datavolume4/Example4.txt
最后,我们将退出容器:
exit
接下来,我们将检查我们的数据是否仍然存在于 Container4
中。
查看 Container5 中所做的更改
让我们通过重新启动 Container4
来检查 Container5
写入数据卷的更改:
docker start -ai Container4
检查更改:
cat /datavolume4/Example4.txt
OutputThis file is shared between containers Both containers can write to DataVolume4
现在我们已经验证了两个容器都能够从数据卷中读取和写入,我们将退出容器:
exit
同样,Docker 不处理任何文件锁定,因此应用程序 必须 自行考虑文件锁定。 可以通过添加 :ro
将 Docker 卷挂载为只读,以确保在容器需要只读访问时不会意外发生数据损坏。 让我们看看这是如何工作的。
启动 Container 6 并以只读方式挂载卷
一旦卷被挂载到容器中,而不是像使用典型的 Linux 文件系统那样卸载它,我们可以创建一个以我们想要的方式挂载的新容器,如果需要,删除以前的容器。 为了使卷只读,我们将 :ro
附加到容器名称的末尾:
docker run -ti --name=Container6 --volumes-from Container4:ro ubuntu
我们将通过尝试删除我们的示例文件来检查只读状态:
rm /datavolume4/Example4.txt
Outputrm: cannot remove '/datavolume4/Example4.txt': Read-only file system
最后,我们将退出容器并清理我们的测试容器和卷:
exit
现在我们已经完成了,让我们清理我们的容器和卷:
docker rm Container4 Container5 Container6 docker volume rm DataVolume4
在此示例中,我们展示了如何使用数据卷在两个容器之间共享数据,以及如何将数据卷挂载为只读。
结论
在本教程中,我们创建了一个数据卷,它允许数据通过删除容器来持久化。 我们在容器之间共享数据量,但需要注意的是应用程序需要设计为处理文件锁定以防止数据损坏。 最后,我们展示了如何以只读模式挂载共享卷。 如果您有兴趣了解容器和主机系统之间的数据共享,请参阅如何在 Docker 容器和主机之间共享数据。