有状态的应用程序,能够持久化数据,在云原生和微服务应用程序的世界中变得越来越重要。因此,在本章中,我们将关注Docker如何处理写入持久数据的应用程序。
我们将把本章分成三个通常的部分:
- 摘要
- 深入探讨
- 命令
卷和持久数据 - 概述
有两种主要的数据类别 — 持久数据和非持久数据。
持久数据是我们需要保留的数据。例如客户记录、财务数据、研究结果、审计日志,甚至某些类型的应用程序日志数据。非持久数据则是我们不需要保留的数据。
这两种数据都很重要,而Docker为这两种情况都提供了解决方案。
对于非持久数据,每个Docker容器都拥有自己的非持久存储。这会为每个容器自动创建,并与容器的生命周期紧密耦合。因此,删除容器将删除存储和其中的任何数据。
对于持久数据,容器需要将其存储在一个卷中。卷是独立的对象,其生命周期与容器分离。这意味着您可以独立创建和管理卷,它们不会在其容器被删除时被删除。
这就是概述。让我们更详细地了解一下。
卷和持久数据 - 深入探讨
一些人仍然认为容器不适合持久化数据的有状态应用程序。这在几年前确实是事实。然而,事情已经发生了变化,容器现在是创建持久数据的应用程序的优秀选择。
我们即将看到容器处理持久数据和非持久数据的一些方式,你会发现与虚拟机有很多相似之处。
我们将从非持久数据开始。
容器与非持久数据
容器被设计成不可变的。这是一个术语,意味着只读 —— 最佳实践是在部署容器后不更改容器的配置。如果发生故障或需要进行更改,您会创建一个全新的容器,包含修复或更新,并用这个新容器替换旧容器。您不应该登录到正在运行的容器并进行配置更改。
然而,许多应用程序需要一个可读写的文件系统才能运行 —— 即使在只读文件系统上它们都无法运行。这意味着将容器完全设置为只读并不是那么简单。为了解决这个问题,由Docker创建的容器在基于只读镜像的基础上具有薄的可读写层。图13.1显示了两个运行中的容器共享一个只读镜像。
每个可写容器层存在于Docker主机的文件系统中,你可能会听到它被称为不同的名称,包括本地存储、临时存储和图形驱动存储。它通常位于Docker主机上的以下位置:
-
Linux Docker主机:/var/lib/docker//...
-
Windows Docker主机:C:ProgramDataDockerwindowsfilter...
这个薄的可写层是许多容器的重要组成部分,它使所有读/写操作都成为可能。如果您或应用程序更新文件或添加新文件,它们将被写入这个层中。然而,它与容器的生命周期紧密耦合 —— 它在容器创建时创建,在容器删除时删除。由于它随着容器一起被删除,这意味着它不适用于您需要保留(持久化)的重要数据。
如果您的容器不创建持久数据,那么这个本地存储的薄可写层就足够了,您可以继续使用它。但是,如果您的容器需要持久化数据,您需要阅读下一节。
容器与持久数据
卷是在容器中持久化数据的推荐方式。有三个主要原因支持这一观点:
在高层次上,您创建一个卷,然后创建一个容器并将卷挂载到容器中。卷被挂载到容器文件系统中的一个目录中,任何写入该目录的数据都将存储在卷中。如果删除容器,卷及其数据仍将存在。
图13.2显示了一个Docker卷作为独立对象存在于容器之外。它被挂载到容器文件系统的/data目录中,任何写入/data目录的数据都将存储在卷中,并且在删除容器后仍将存在。
在图13.2中,/data目录是一个Docker卷,可以映射到外部存储系统或Docker主机上的一个目录。无论哪种方式,它的生命周期与容器分离。容器中的所有其他目录都使用Docker主机上本地存储区域中的薄可写容器层。
创建和管理Docker卷
卷在Docker中是一流的对象。这意味着它们在API中是独立的对象,并拥有自己的docker volume子命令。
使用以下命令创建一个名为myvol的新卷。
$ docker volume create myvol
myvol
默认情况下,Docker使用内置的本地驱动程序来创建新卷。正如名称所示,使用本地驱动程序创建的卷仅对与卷相同节点上的容器可用。您可以使用 -d 标志来指定不同的驱动程序。
第三方卷驱动程序以插件形式提供。它们为Docker提供了高级功能,并无缝访问外部存储系统,如云存储服务和包括SAN和NAS在内的本地存储系统。这在图13.3中有所示。
我们将在后面的部分看一个使用第三方驱动程序的示例。
现在卷已创建,您可以使用docker volume ls命令查看它,并使用docker volume inspect命令进行检查。
$ docker volume ls
DRIVER VOLUME NAME
local myvol
$ docker volume inspect myvol
[
{
"CreatedAt": "2023-05-23T10:00:18+01:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/myvol/_data",
"Name": "myvol",
"Options": null,
"Scope": "local"
}
]
请注意,驱动程序和作用域都是本地。这意味着卷是使用本地驱动程序创建的,只能在此Docker主机上的容器中使用。Mountpoint属性告诉我们卷存在于Docker主机文件系统的哪个位置。
使用本地驱动程序创建的所有卷都会在Linux上的/var/lib/docker/volumes或Windows上的C:ProgramDataDockervolumes下有自己的目录。这意味着您可以在Docker主机的文件系统中看到它们。您甚至可以直接从Docker主机访问它们,尽管这并不推荐。在Docker Compose章节中,我们展示了一个示例 —— 我们直接将文件复制到Docker主机上卷的目录中,文件立即出现在容器内的卷中。
现在卷已创建,它可以被一个或多个容器使用。我们将在一会儿看到使用示例。
有两种删除Docker卷的方法:
docker volume prune将删除未挂载到容器或服务副本中的所有卷,因此请谨慎使用!docker volume rm允许您明确指定要删除的卷。无论哪种命令都不会删除被容器或服务副本使用的卷。
由于myvol卷未被使用,请使用prune命令删除它。
$ docker volume prune
WARNING! This will remove all volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
myvol
Total reclaimed space: 0B
恭喜你,你已经创建、检查和删除了一个Docker卷。而且你做到了所有这些,都没有与容器进行交互。这展示了卷的独立性。
此时,你已经知道了创建、列出、检查和删除Docker卷的所有命令。然而,也可以通过Dockerfile使用VOLUME指令部署卷。其格式为VOLUME 。有趣的是,在Dockerfile中定义卷时,不能指定主机上的目录。这是因为主机目录取决于Docker主机运行的操作系统,如果在Dockerfile中指定了一个不存在的主机目录,可能会导致构建失败。因此,在Dockerfile中定义卷需要在部署时指定主机目录。
演示容器和服务中的卷
让我们看看如何在容器和服务中使用卷。
使用以下命令创建一个新的独立容器,该容器挂载了一个名为bizvol的卷。
$ docker run -it --name voltainer
--mount source=bizvol,target=/vol
alpine
该命令使用 --mount 标志将一个名为 "bizvol" 的卷挂载到容器的 /vol 目录中。尽管系统上不存在名为 "bizvol" 的卷,该命令仍然成功完成。这引发了一个有趣的观点:
- 如果您指定一个已经存在的卷,Docker 将使用现有的卷。
- 如果您指定一个不存在的卷,Docker 将为您创建它。
在这种情况下,bizvol 不存在,所以Docker创建了它并将其挂载到新的容器中。这意味着您可以使用 docker volume ls
命令看到它。
$ docker volume ls
DRIVER VOLUME NAME
local bizvol
尽管容器和卷具有独立的生命周期,但您不能删除正在被容器使用的卷。可以尝试一下。
$ docker volume rm bizvol
Error response from daemon: remove bizvol: volume is in use - [b44d3f82...dd2029ca]
这个卷是全新的,所以没有任何数据。让我们进入容器并向其中写入一些数据。
$ docker exec -it voltainer sh
/# echo "I promise to leave a review of the book on Amazon" > /vol/file1
/# ls -l /vol
total 4
-rw-r--r-- 1 root root 50 May 23 08:49 file1
/# cat /vol/file1
I promise to leave a review of the book on Amazon
输入"exit"以返回到Docker主机的shell界面,然后使用以下命令删除容器。
$ docker rm voltainer -f
voltainer
尽管容器已被删除,但卷仍然存在:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS
$ docker volume ls
DRIVER VOLUME NAME
local bizvol
由于卷仍然存在,您可以查看它在主机上的挂载点,以检查数据是否仍然存在。
从Docker主机的终端运行以下命令。第一个命令将显示文件仍然存在,第二个命令将显示文件的内容。您可能需要在命令前加上sudo。
如果您在Windows上进行操作,请确保使用C:ProgramDataDockervolumesbizvol_data目录。此外,请注意,如果您使用Docker Desktop,则此步骤将无法工作,因为Docker Desktop在VM内运行整个Docker环境。
$ ls -l /var/lib/docker/volumes/bizvol/_data/
total 4
-rw-r--r-- 1 root root 50 Jan 12 14:25 file1
$ cat /var/lib/docker/volumes/bizvol/_data/file1
I promise to leave a review of the book on Amazon
太好了,卷和数据仍然存在。
甚至可以将bizvol卷挂载到一个新的服务或容器中。以下命令创建一个新容器,将bizvol挂载到/vol目录。
$ docker run -it
--name hellcat
--mount source=bizvol,target=/vol
alpine sh
您的终端现在已连接到hellcat容器。
# cat /vol/file1
I promise to write a review of the book on Amazon
太棒了,这个卷保留了原始数据,并使其对新容器可用。
输入"exit"以退出容器,并跳转到亚马逊撰写书评吧! 😀
在集群节点之间共享存储
将外部存储系统与Docker集成,可以实现在集群节点之间共享卷的可能性。这些外部系统可以是云存储服务或您的本地数据中心中的企业存储系统。例如,可以将单个存储LUN或NFS共享呈现给多个Docker主机,从而允许它被容器和服务副本使用,无论它们运行在哪个Docker主机上。图13.4显示了一个外部共享卷被呈现给两个Docker节点的示例。然后,这些Docker节点可以使共享卷对容器中的一个或两个都可用。
构建这样的设置需要很多要素。您需要访问专门的存储系统以及了解它如何工作和提供存储。您还需要知道您的应用程序如何读取和写入共享存储中的数据。最后,您需要一个与外部存储系统配合使用的卷驱动程序插件。
卷驱动程序可作为容器运行的插件提供,并且最佳地方是Docker Hub。只需在浏览器中打开hub.docker.com,然后在“插件”视图上进行筛选。一旦找到适用于您的存储系统的合适插件,您可以使用docker plugin install
进行安装。
图13.5显示了Docker Hub上的NetApp Trident插件。请注意docker plugin install
命令。
潜在的数据损坏
在共享单个卷的多个容器之间的任何配置中,一个主要的担忧是数据损坏。
假设以下示例基于图13.4。
运行在node1上的ctr-1中的应用程序更新了共享卷中的一些数据。然而,它并没有直接将更新写入卷中,而是将其保留在本地缓冲区中以便更快地检索(这在许多操作系统中很常见)。此时,ctr-1中的应用程序认为数据已经写入卷中。然而,在ctr-1上的节点1刷新其缓冲区并将数据提交到卷之前,节点2上的ctr-2中的应用程序使用不同的值更新了相同的数据,并直接将其提交到卷中。此时,两个应用程序都认为它们已经更新了卷中的数据,但实际上只有ctr-2中的应用程序更新了数据。几秒钟后,ctr-1上的节点1刷新数据到卷中,覆盖了ctr-2中的应用程序所做的更改。然而,ctr-2中的应用程序完全不知情!这是数据损坏发生的一种方式。
为了防止这种情况发生,您需要以一种避免类似情况的方式编写您的应用程序。
卷和持久数据 - 命令
以下是一些与卷和持久数据相关的常用命令:
docker volume create
:创建新卷的命令。默认情况下,卷会使用本地驱动程序创建,但您可以使用-d
标志来指定不同的驱动程序。docker volume ls
:列出本地Docker主机上的所有卷。docker volume inspect
:显示详细的卷信息。使用此命令可以查看许多有关卷的有趣属性,包括卷在Docker主机文件系统中的位置。docker volume prune
:删除所有未被容器或服务副本使用的卷。请谨慎使用此命令!docker volume rm
:删除特定未被使用的卷。docker plugin install
:从Docker Hub安装新的卷插件。docker plugin ls
:列出Docker主机上安装的所有插件。