容器是短暂的
记住容器是临时的、短暂的。每当容器完成执行命令时,它就完全“关闭”了。
=> 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 实际上是一层保险,确保镜像运行可以更好的遵循最佳实践,不向容器存储层内进行写入操作。