Docker 存储管理

内容纲要

查看【Docker & Kubernetes】专题可浏览更多内容

默认情况下,在容器内创建的所有文件都存储在可写容器层上。这意味着:

  • 当该容器不再存在时,数据不会持久存在,并且如果另一个进程需要数据,则很难从容器中取出数据;
  • 容器的可写层与运行容器的主机紧密耦合,无法轻松地将数据移动到其他地方;
  • 写入容器的可写层需要存储驱动程序来管理文件系统。存储驱动程序使用 Linux 内核提供联合文件系统。与使用直接写入主机文件系统的数据卷相比,这种额外的抽象降低了性能;

Docker 有两个选项供容器在宿主机上存储文件,以便即使在容器停止后文件也能持久保存:宗卷(Volume) 和 绑定挂载(Bind mount)

Docker 还支持通过 tmpfs mount 将文件存储在主机内存中的容器。此类文件不会持久化。如果在 Linux 上运行 Docker,则 tmpfs mount 用于将文件存储在主机的系统内存中。如果在 Windows 上运行 Docker,则 named pipes 用于将文件存储在主机的系统内存中。

Docker 存储管理

Volume

Volume 存储在由 Docker 管理的宿主机文件系统的一部分中 (在 Linux 上为 /var/lib/docker/volumes/)。非 Docker 进程不应修改文件系统的这一部分。

Volume 是在 Docker 中持久保存数据的最佳方式。

Volume 相比 Bind mount 的几个优势:

  • Volume 相比 Bind mount 更易于备份或迁移;
  • Volume 可以使用 Docker CLI 命令或 Docker API 管理;
  • Volume 可以在 Linux 和 Windows 容器上运行;
  • Volume 可以在多个容器之间更安全地共享;
  • Volume driver 允许将 Volume 存储在远程主机或云提供商上,以加密卷的内容或添加其他功能;
  • 新 Volume 可以通过容器预先填充其内容;
  • 与 Mac 和 Windows 宿主机上的 Bind mount 相比,Docker Desktop 上的 Volume 具有更高的性能;

此外,Volume 通常是比将数据保存在容器的可写层中更好的选择,因为 Volume 不会增加使用它的容器的大小,并且卷的内容存在于给定容器的生命周期之外。

创建容器时使用 Volume

使用 Volume 可以在运行容器时加上 -v--mount 选项,Docker 官方推荐后者因为其更显示、更详细:

# -v, --volume
# <Volume 名称>:<容器内路径>
docker run -d -v html:/usr/share/nginx/html/ --name web nginx:stable

# --mount
docker run -d \
  --name web \
  --mount source=html,target=/usr/share/nginx/html/ \
  nginx:stable

在上面的命令中,通过 -v--mount 的方法,给 Volume 卷名为 html 并挂载到容器的 /usr/share/nginx/html/ 目录,这样日后当 NGINX 映像有了更新而删除重建 NGINX 容器,对 /usr/share/nginx/html/ 目录的更改就不会丢失了,这也就是数据持久化。

查看 Volume

docker volume ls
DRIVER    VOLUME NAME
local     html

通过 Volume 管理命令可以看到刚才在创建启动容器时同时创建在本地 (local) 上名为 html 的 Volume,如果在创建容器或 Volume 未指定名称 Docker 会随机给出一个名称

通过命令可以显示一个卷的详细信息:

# docker volume inspect <Volume 名称>
docker volume inspect html
[
    {
        "CreatedAt": "2023-01-06T04:19:54Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/html/_data",
        "Name": "html",
        "Options": null,
        "Scope": "local"
    }
]

其中的 Mountpoint 就是在宿主机上的具体路径,如果你是在 Linux 上安装的 Docker Engine 可以通过 cd 命令进入到该目录查看其具体内容

但如果是通过 Docker Desktop 使用的 Docker 就比较麻烦了,因为其是运行在一个虚拟机上并不能直接查看其目录 (包括使用 Docker Desktop 的Linux)

显示创建 Volume

# 创建 Volume
docker volume create <Volume 名称>

删除 Volume

当没有正在运行的容器正在使用某个 Volume 时,该 Volume 仍可供 Docker 使用,不会自动移除。可以使用 docker volume prune 删除未使用的Volume。

# 删除 Volume
docker volume rm <Volume 名称>
# 或删除本地所有未使用的 Volume
docker volume prune

设置 Volume 只读

有的时候需要容器对数据仅读取访问,那么还可以加上一些选项:

# -v, --volume
# <Volume 名称>:<容器内路径>
docker run -d -v html:/usr/share/nginx/html/:ro --name web nginx:stable

# --mount
docker run -d \
  --name web \
  --mount source="$(pwd)"/html,target=/usr/share/nginx/html/,readonly \
  nginx:stable

备份及恢复 Volume

如果需要对一个 Volume 的文件进行备份:

# --rm 表示容器运行完成后自动退出
# --volumes-from <容器名称> 这里是 web
# -v $(pwd):/backup 将宿主机当前目录挂载到容器的 /backup
# nginx:stable 使用的映像
# tar caf /backup/backup.tar /usr/share/nginx/html 命令,表示将 /usr/share/nginx/html 打包压缩到 /backup 目录下命名为 backup.tar
docker run --rm --volumes-from web -v $(pwd):/backup nginx:stable tar caf /backup/backup.tar /usr/share/nginx/html

换句话说就是挂载名为 web 的容器的 Volume,并通过 Bind mount 将(宿主机)终端当前的路径挂载到即将创建容器的 /backup 目录(不用担心容器中有没有这个目录,它会自动创建),使用映像为 nginx:stable 然后使用命令将 /usr/share/nginx/html 目录的内容通过 tar 打包到容器 /backup 目录下命名为 backup.tar,完成后退出该容器

因为通过 Bind mount 将终端当前路径挂载到容器的 /backup,所以你在终端当前路径的位置就可以看到容器中的 backup.tar 备份文件了

以上是备份了,那如果要将文件恢复到一个 Volume 中呢?

# 一个新的容器
docker run -d -v newhtml:/usr/share/nginx/html/:ro --name newweb nginx:stable

# 创建一个用于将备份文件恢复到 newweb 容器中的临时容器
docker run --rm --volumes-from newweb -v $(pwd):/backup nginx:stable bash -c "cd /usr/share/nginx/html && tar xvf /backup/backup.tar --strip 1"

Bind mount

Bind mount 可以存储在宿主机系统的任何位置。它们甚至可能是重要的系统文件或目录。 Docker 主机或 Docker 容器上的非 Docker 进程可以随时修改它们。

Bind mount 在 Docker 早期就已存在,与 Volume 相比 Bind mount 的功能有限,使用 Bind mount 时,宿主机上的文件或目录将挂载到容器中。文件或目录由其在主机上的完整路径引用。文件或目录不需要已经存在于 Docker 宿主机或容器上。如果它尚不存在,则按需创建它。

Bind mount 的性能非常好,但它们依赖于主机的文件系统,该文件系统具有可用的特定目录结构。如果您正在开发新的 Docker 应用程序,请考虑改用命名 Volume。但不能使用 Docker CLI 命令直接管理 Bind mount。

使用 Bind mount 的一个副作用是,无论是好是坏,都可以通过在容器中运行的进程更改宿主机文件系统,包括创建、修改或删除重要的系统文件或目录。这是一种强大的功能,可能会对安全造成影响,包括影响宿主机系统上的非 Docker 进程。

创建容器时使用 Bind mount

# -v, --volume
# <Volume 名称>:<容器内路径>
docker run -d -v "$(pwd)"/html:/usr/share/nginx/html/ -p 8080:80 --name web nginx:stable

# --mount
docker run -d \
  -it \
  --name web \
  --mount type=bind,source="$(pwd)"/html,target=/usr/share/nginx/html/ \
  -p 8080:80 \
  nginx:latest

如上命令,是将(宿主机)终端当前目录下的 html 目录挂载到容器的 /usr/share/nginx/html/ 目录,这样就可以对终端当前所在目录下的 html 目录进行修改,从而对应修改容器内 /usr/share/nginx/html/ 目录的内容

在这里 -v--mount 不同的是,如果宿主机上绑定挂载的文件或目录不存在,-v 会自动为其创建,而 --mount 会提示错误,如 docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist

还有一点需要注意的是,你可能已经注意到了使用上面的例子与不含 Bind mount 选项运行 NGINX 容器有所不同,不含 Bind mount 选项的 NGINX 运行后浏览器访问 http://localhost:8080 可以看到 NGINX 欢迎页面,而使用 Bind mount 选项选项后访问会显示 403

这是因为如果 Bind mount 所挂载到的容器的目录是个非空目录,会将容器上被挂载的目录所替换,而(宿主机)终端当前目录下的 html 目录是个空的,所以 NGINX 找不到 /usr/share/nginx/html/ 目录的 index.html 也就显示 403 了,通过在(宿主机)终端当前目录下的 html 目录添加 index.html 再在浏览器刷新访问 http://localhost:8080 就可以看到网页了

查看容器的 Bind mount 位置

使用命令可以查看其容器的挂载位置:

# docker inspect <容器名称>
docker inspect web
...
"Mounts": [
  {
    "Type": "bind",
    "Source": "/host_mnt/Users/toor/Developer/src/Docker/html",
    "Destination": "/usr/share/nginx/html",
    "Mode": "",
    "RW": true,
    "Propagation": "rprivate"
  }
]
...

设置 Bind mount 只读

与 Volume 类似,有时候希望容器只对 Bind mount 的内容进行只读而不可写入,那么在创建容器时可以使用相关选项:

# -v, --volume
# <Volume 名称>:<容器内路径>
docker run -d -v "$(pwd)"/html:/usr/share/nginx/html/:ro -p 8080:80 --name web nginx:stable

# --mount
docker run -d \
  -it \
  --name web \
  --mount type=bind,source="$(pwd)"/html,target=/usr/share/nginx/html/,readonly \
  -p 8080:80 \
  nginx:latest

tmpfs mount

tmpfs mount 仅存储在宿主机系统的内存中,永远不会写入宿主机系统的文件系统。

如果您的容器生成非持久状态数据,请考虑使用 tmpfs mount以避免将数据永久存储在任何地方,并通过避免写入容器的可写层来提高容器的性能。


小结
对 Docker 的数据存储管理一般主要是为了让容器的数据持久化,例如所使用的 NGINX 容器有了新的映像版本,在删除旧版容器然后使用基于新版映像的 NGINX 容器时,如何保留旧版容器的数据如 NGINX 所显示的 html 相关文件

Docker 有两个选项供容器在宿主机上存储文件,以便即使在容器停止后文件也能持久保存:宗卷(Volume) 和绑定挂载(Bind mount)

Volume 是在 Docker 中持久保存数据的最佳方式。

Volume 与 Bind mount 类似但是 Volume 是由 Docker 进行管理数据,且 Volume 功能更为丰富,Bind mount 虽然功能受限但性能更好 (但在 Docker Desktop 上 Volume 具有更高的性能)

Volume
在创建容器时可以通过选项 --mount source=<Volume 名称>,target=<容器内路径>[,readonly]-v <Volume 名称>:<容器内路径>[:ro] 来指定一个容器的 Volume,,readonly:ro 用于表示容器对于该 Volume 只有只读权限

管理 Volume 可以使用命令 docker voulme,选项有:ls 用于查看、create 用于创建、rm 用于删除、prune 用于删除所有未使用的本地 Volume

Bind mount
在创建容器时可以通过选项 -mount type=bind,source=<宿主机绝对路径>,target=<容器内路径>[,readonly]-v <宿主机绝对路径>:<容器内路径>[:ro] 来指定一个容器的Bind mount,,readonly:ro 用于表示容器对于该 Volume 只有只读权限

对于 Bind mount,-v 会创建不存在的文件或目录,而 --mount 会给出错误

另外容器内的非空目录,会挂载会覆盖其内容


小结

Bind mount 非常适合在主机和容器之间共享数据,而 Volume 可以让容器在运行之间保持状态,如果有一个正在运行的容器,且下次运行它时需要上次运行的结果,那么 Volume 将非常有帮助。

Volume 不仅可以在运行之间由相同的容器类型共享,而且可以在不同的容器之间共享。如果有两个容器并且希望将日志合并到一个位置,那么 Volume 可以帮助实现这一点。

而 Bind mount 是由主机管理的文件系统。它们只是主机中的普通文件,被装入一个容器中。Volume 之所以不同,是因为它们是由 Docker 管理的一个新文件系统,可以挂载到容器中。

这些 Docker 管理的文件系统对于主机系统是不可见的 (可以找到它们,但是设计上是这样设计的)