使用 named volume 持久化容器数据

未匹配的标注

到目前为止,使用我们自己创建的镜像启动的容器,每次重启都会清除之前的待办事项数据。为什么会这样?让我们深入研究容器的工作方式。

容器的文件系统

在之前的章节介绍过,容器运行时使用的是隔离的文件系统,而这个特殊的文件系统就是由 镜像 提供的。在一个容器的文件系统上创建/更新/删除文件,都不会影响到另一个容器,即使它们使用的是同一个镜像。

动手验证

为了验证这一点,我们将启动两个容器,并在其中一个容器中创建一个文件,看看这个文件会不会出现在使用了同一个镜像启动的另一个容器中。

  1. 启动一个 Ubuntu 容器,在该容器中创建一个名为 /data.txt 的文件,文件内容是 1 到 10000 之间的随机数:

    docker run -d Ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"

    容器启动之后,开启一个 bash shell 并执行两个命令,&& 之前的第一个命令是选择一个 1 到 10000 的随机数,将其写入到 / data.txt 文件中。&& 之后的第二个命令只是监视文件以便让容器保持持续运行的状态。

  2. 通过 docker exec 命令查看容器中 /data.txt 文件的内容。

    # 执行上一条 `docker run ...` 命令时输出到控制台的内容就是 <container-id>
    # 也可以通过执行 `docker ps` 获取到 <container-id>
    docker exec <container-id> cat /data.txt

    你应该看到 /data.txt 文件的内容了

  3. 现在,让我们使用同一个镜像,启动另一个 ubuntu 容器,执行以下命令查看新容器的根目录下是否有 data.txt 文件:

    docker run -it Ubuntu ls /

    显然并没有看到 data.txt 文件,因为它仅被写入第一个容器的暂存空间中,不同容器直接彼此是隔离的。

  4. 验证了之后,就可以使用 Docker Desktop 或者使用 docker rm -f <container-id> 命令删除之前启动的第一个容器了。

容器的卷 Volume

备注:Volume 一般翻译成:卷。但是本文尽量不去翻译这种专有名词,保持英文原文。

在之前的实验中,我们看到每个容器每次启动都从镜像的定义中重新开始创建容器的文件系统。尽管容器可以创建、更新和删除文件,但是当容器被删除或者重新启动时,之前创建或修改的文件都将丢失。使用 Volume,可以改变目前的现状。

Volumes 提供将容器的特定文件系统路径连接回主机的功能。如果容器中的一个目录被挂载,那么这个目录的修改同样能够在主机上被看到。如果在重新启动容器时挂载了相同的目录,就能够看到相同的文件了。

备注:这里说的 主机 或者 本机 指的是运行 Docker 引擎的这台机器,也就是我们的 Mac、Windows 或者 Linux 机器。

volumes 有两种类型,先从开始 named volumes 介绍。同样的,这里的 named volumes 一般翻译成命名卷,但是在后续的章节中,我们也尽量不去翻译它。

保存我们的待办事项数据

默认情况下,我们的 todo-app 应用程序使用的是 SQLite 数据库 将数据存储在 /etc/todos/todo.db 文件中。如果你不熟悉 SQLite,不用担心!它跟 MySQL 类似都属于关系型数据库,但是 SQLite 更简单一些,它将所有数据都存储在一个文件中。虽然这对于大型应用程序不是最佳选择,但对于小型演示型应用却很适合。稍后我们会讨论如何将它切换到其他数据库引擎,比如 MySQL 数据库。

由于我们要用数据库是单个文件,因此我们可以将这个文件保存在主机上,然后创建一个 volume,将主机上的这个文件与容器中的某个位置关联在一起,这样就可以解决重启或者在新启动容器出现的数据丢失问题了。

如前所述,我们将使用 named volume 来实现这个功能,可以将 named volume 视为简单的数据桶。 Docker 维护主机上的物理位置,你只需要记住 volume 的名称即可。每次使用该 volume 时,Docker 将确保提供正确的数据。

  1. 使用 docker volume create 命令创建 volume
    # 也可以不用主动创建,当执行下方第 3 步的时候,Docker 会自动检测我们指定的 volume 是否存在,如果不存在会自动帮我们创建
    docker volume create todo-db
  2. 通过 Docker Dashboard 或者使用 docker rm -f <container-id> 命令停止并删除正在运行中的 todo-app 应用程序容器
  3. 再次启动容器, 但是这次要添加 -v 标志以指定要挂载的 volume。我们将使用 named volume 并将其挂载到容器上的 /etc/todos 路径下,这个 named volume 将会感知在这个路径下创建的所有文件。
    docker run -dp 3000:3000 -v todo-db:/etc/todos todo-app
  4. 容器启动后,访问我们的应用程序并添加一些待办事项
    Items added to todo list
  5. 参考第 2 步,再一次删除正在运行的容器
  6. 参考第 3 步,再一次启动容器
  7. 访问我们的应用,验证是否还能看到之前添加的待办事项

深入看一看 Volume

很多人经常问:“当我使用 named volume 时,Docker 实际上在哪里存储数据?”
如果你想知道答案,可以使用 docker volume inspect ... 命令查看 volume 的详细信息。

docker volume inspect todo-db

[
    {
        "CreatedAt": "2021-01-12T03:35:55Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": null,
        "Scope": "local"
    }
]

Mountpoint 的值就是主机上存储数据的实际位置。需要注意的是,在大多数计算机上,需要具有 root 访问权限的用户才能在主机上访问此目录。

回顾

这一节,我们学会了如何为容器持久化存储数据了。

下一节,将介绍如何使用另外一种 volume 类型:bind mounts 及时让容器感知到主机上源代码的变动。

原始资料:Persisting our DB

配套视频
www.bilibili.com/video/BV1sz4y117A...

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~