天天看点

掌握Docker:绑定挂载、匿名卷、命名卷

掌握Docker:绑定挂载、匿名卷、命名卷

容器是短暂的

记住容器是临时的、短暂的。每当容器完成执行命令时,它就完全“关闭”了。

=> docker run node ls

bin
boot
dev
etc
home
lib
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
           

它使用镜像node启动一个新容器并在容器内执行命令(列出文件和目录)。

该命令给出一个输出并完成。命令完成后,容器将关闭并且其所有数据都将丢失。

执行 Javascript 程序

假设我们有一个包含非常基本的 Javascript 程序的文件:

helloWorld.js

greeting = (message) => {
  console.log(message)
}

greeting("Hello, world")
           

我们如何使用容器运行这个程序?可以尝试做类似的事情:

docker run node node helloWorld.js
           

得到错误:

Error: Cannot find module '/app/helloWorld.js'
...
           

那是因为容器是隔离的,不能共享同一个主机文件系统。

绑定挂载:在主机和容器之间同步数据

我们必须将文件helloWorld.js与容器同步。Docker 提供了一种挂载卷的方法,这意味着:

从Host 挂载一个目录或文件到 Container,这样在 Host 中所做的每一个更改都会被镜像到 Container,反之亦然。

您更改主机中的文件/目录,容器将看到更改。您更改容器中的文件/目录,主机将看到更改。

让我们与容器同步:

docker run 
  -v $(pwd)/docker-101/helloWorld.js:/app/helloWorld.js 
  node 
  node /app/helloWorld.js
           
Hello, world
           

解释:

  • pwd是指当前目录的完整路径名
  • -v {hostPath}:{containerPath}将文件/目录挂载到容器
  • node是镜像
  • node /app/helloWorld.js使用容器内定义的path执行命令

一个真实的项目

假设我们有一个这样的项目目录:

/docker-101
  /src
    /components
    components.js
  index.js
           

从docker-101目录内部,执行node

docker run 
  -v $(pwd):/app
  node
  node /app/index.js
           

当前目录中的所有文件都将挂载到容器中的/app。

如果我们想进入容器并从那里操作文件怎么办?

docker run 
  -it
  -v $(pwd):/app
  node
  bash
           

.这里:

  • -it指示 Docker 保持容器终端(bash/shell)打开
  • bash在容器内打开一个新的 bash/shell

执行这个docker run命令,保持容器终端打开,在容器内部执行更多命令并创建新文件:

root@8dcbfa6d777c:/# cd /app

root@8dcbfa6d777c:/app# ls
index.js  src

root@8dcbfa6d777c:/app# touch new-file.js

root@8dcbfa6d777c:/app# ls
index.js  new-file.js src

root@8dcbfa6d777c:/app# exit
exit
           

现在,退出容器后,在主机执行ls,我们会发现,相应的新创建的文件都同步在host上了:

index.js    new-file.js src           

这种类型的卷称为Path Volume。

使用mount参数

另一种挂载卷的方法是使用选项--mount,它指定挂载类型、源和目标。

docker run 
  --mount type=bind,source=$(pwd),target=/app
  node
  node /app/helloWorld.js
           

mount更明确,但在大多数情况下使用-v已经足够了。

命名卷与匿名卷

绑定加载是由程序员自己维护的,而数据卷是由 Docker 引擎维护的存储方式,使用 docker volume create 命令创建,可以利用卷驱动支持多种存储方案。其默认的驱动为 local,也就是本地卷驱动。本地驱动支持命名卷和匿名卷。

docker volume create my-volume

docker volume inspect my-volume
           
[
    {
        "CreatedAt": "2022-02-05T00:47:59Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-volume/_data",
        "Name": "my-volume",
        "Options": {},
        "Scope": "local"
    }
]
           

Mountpoint指示的路径是主机中的确切路径。因此,每个使用此卷的容器都会将其数据直接同步到此挂载点。

而匿名卷就是没名字的卷,一般是 docker run -v /data 这种不指定卷名的时候所产生,或者 Dockerfile 里面的定义直接使用的。

如果容器启动时使用了 --rm 选项,容器停止时,容器被自动删除,匿名卷也会自动被删除。

反之,如果没有--rm选项,即使使用命名 docker container rm my_container 删除了容器,匿名卷不会被自动删除。每次创建新的容器,都会重新创建新的匿名卷。 重启容器则使用已有附加的匿名卷。

要删除匿名卷,使用命令 docker volume rm 或者 prune

因此,一般而言,匿名卷只存放无关紧要的临时数据,随着容器消亡,这些数据将失去存在的意义。

在 Dockerfile 中定义的挂载,是指匿名数据卷。无法在Dockerfile 内创建命名卷。

最佳实践

不应该在容器存储层内进行数据写入操作,所有写入应该使用卷。如果定制镜像的时候,就可以确定某些目录会发生频繁大量的读写操作,那么为了避免在运行时由于用户疏忽而忘记指定卷,导致容器发生存储层写入的问题,就可以在 Dockerfile 中使用 VOLUME 来指定某些目录为匿名卷。这样即使用户忘记了指定卷,也不会产生不良的后果。

比如,Dockerfile 中说 VOLUME /data,那么如果直接 docker run,其 /data 就会被挂载为匿名卷,向 /data 写入的操作不会写入到容器存储层,而是写入到了匿名卷中。但是如果运行时 docker run -v mydata:/data,这就覆盖了 /data 的挂载设置,要求将 /data 挂载到名为 mydata 的命名卷中。所以说 Dockerfile 中的 VOLUME 实际上是一层保险,确保镜像运行可以更好的遵循最佳实践,不向容器存储层内进行写入操作。