管理容器文件系统

DockerDockerBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

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

  • 如果容器不再存在,数据将会丢失。
  • 容器的可写层与主机紧密耦合。
  • 要管理文件系统,你需要一个使用Linux内核提供联合文件系统的存储驱动程序。与直接写入文件系统的「数据卷」相比,这种额外的抽象会降低性能。

Docker提供了两种将文件存储在主机中的选项:「卷」(volumes)和「绑定挂载」(bind mounts)。如果你在Linux上运行Docker,还可以使用「临时文件系统挂载」(tmpfs mount);在Windows上运行Docker时,也可以使用「命名管道」(named pipe)。

挂载类型
  • 「卷」存储在由Docker管理的主机文件系统中。
  • 「绑定挂载」可以存储在主机系统的任何位置。
  • 「临时文件系统挂载」仅存储在主机内存中。

最初,--mount标志用于Docker Swarm服务,--volume标志用于独立容器。从Docker 17.06及更高版本开始,你也可以将--mount用于独立容器,并且它通常比--volume更明确、更详细。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL docker(("`Docker`")) -.-> docker/ContainerOperationsGroup(["`Container Operations`"]) docker(("`Docker`")) -.-> docker/VolumeOperationsGroup(["`Volume Operations`"]) docker(("`Docker`")) -.-> docker/SystemManagementGroup(["`System Management`"]) docker/ContainerOperationsGroup -.-> docker/run("`Run a Container`") docker/ContainerOperationsGroup -.-> docker/stop("`Stop Container`") docker/ContainerOperationsGroup -.-> docker/rm("`Remove Container`") docker/ContainerOperationsGroup -.-> docker/exec("`Execute Command in Container`") docker/ContainerOperationsGroup -.-> docker/inspect("`Inspect Container`") docker/VolumeOperationsGroup -.-> docker/volume("`Manage Volumes`") docker/SystemManagementGroup -.-> docker/prune("`Remove Unused Docker Objects`") subgraph Lab Skills docker/run -.-> lab-148984{{"`管理容器文件系统`"}} docker/stop -.-> lab-148984{{"`管理容器文件系统`"}} docker/rm -.-> lab-148984{{"`管理容器文件系统`"}} docker/exec -.-> lab-148984{{"`管理容器文件系统`"}} docker/inspect -.-> lab-148984{{"`管理容器文件系统`"}} docker/volume -.-> lab-148984{{"`管理容器文件系统`"}} docker/prune -.-> lab-148984{{"`管理容器文件系统`"}} end

「数据卷」(data volume)或「卷」(volume)是绕过Docker的「联合文件系统」的目录。

卷有三种类型:

  • 匿名卷
  • 命名卷
  • 主机卷

匿名卷

让我们创建一个流行的开源NoSQL数据库CouchDB的实例,并使用「匿名卷」来存储数据库的数据文件。

要运行CouchDB实例,请使用Docker Hub上的CouchDB镜像,网址为https://hub.docker.com/_/couchdb。文档中提到,CouchDB的默认设置是「使用其自己的内部卷管理将数据库文件写入主机系统的磁盘」。

运行以下命令:

docker run -d -p 5984:5984 --name my-couchdb -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=passw0rd1 couchdb:3.1

CouchDB将创建一个匿名卷并生成一个哈希名称。检查主机系统上的卷:

labex:~/ $ docker volume ls
DRIVER VOLUME NAME
local 1d292aca855adb9de9be7acea88f6d3f8e6a08eef5bfd986a81f073f1906b82f

设置一个环境变量VOLUME,其值为生成的名称:

export VOLUME=<VOLUME NAME>

然后检查创建的卷,使用为该卷生成的哈希名称:

$ docker volume inspect $VOLUME
[
{
  "CreatedAt": "2020-09-24T14:10:07Z",
  "Driver": "local",
  "Labels": null,
  "Mountpoint": "/var/lib/docker/volumes/f543c5319ebd96b7701dc1f2d915f21b095dfb35adbb8dc851630e098d526a50/_data",
  "Name": "f543c5319ebd96b7701dc1f2d915f21b095dfb35adbb8dc851630e098d526a50",
  "Options": null,
  "Scope": "local"
}
]

你会看到Docker已经在Docker主机文件系统的/var/lib/docker/volumes/$VOLUME_NAME/_data下创建并管理了一个卷。请注意,这不是主机上的路径,而是Docker管理的文件系统的一部分。

创建一个新数据库mydb,并插入一条带有「hello world」消息的新文档。

curl -X PUT -u admin:passw0rd1 http://127.0.0.1:5984/mydb
curl -X PUT -u admin:passw0rd1 http://127.0.0.1:5984/mydb/1 -d '{"msg": "hello world"}'

停止容器并再次启动:

docker stop my-couchdb
docker start my-couchdb

检索数据库中的文档,以测试数据是否已持久保存。

curl -X GET -u admin:passw0rd1 http://127.0.0.1:5984/mydb/_all_docs
curl -X GET -u admin:passw0rd1 http://127.0.0.1:5984/mydb/1

输出:

## $ curl -X GET -u admin:passw0rd1 http://127.0.0.1:5984/mydb/_all_docs
{"total_rows":1,"offset":0,"rows":[
{"id":"1","key":"1","value":{"rev":"1-c09289617e06b96bc747fb1201fea7f1"}}
]}

## $ curl -X GET -u admin:passw0rd1 http://127.0.0.1:5984/mydb/1
{"_id":"1","_rev":"1-c09289617e06b96bc747fb1201fea7f1","msg":"hello world"}

共享卷

你可以使用--volumes-from选项与另一个容器共享匿名卷。

创建一个busybox容器,将一个匿名卷挂载到容器中的/data目录,并使用 shell 命令将一条消息写入日志文件。

$ docker run -it --name busybox1 -v /data busybox sh
/ ## echo "hello from busybox1" > /data/hi.log
/ ## ls /data
hi.log
/ ## exit

确保容器busybox1已停止但未删除。

labex:~/ $ docker ps -a | grep busybox1
f4dbf9ee7513   busybox                               "sh"                     2 minutes ago   Exited (0) About a minute ago                                                                                                                                          busybox1

然后使用--volumes-from选项创建第二个名为busybox2busybox容器,以共享busybox1创建的卷:

$ docker run --rm -it --name busybox2 --volumes-from busybox1 busybox sh
/ ## ls -al /data
total 12
drwxr-xr-x 2 root root 4096 Jan 23 07:20.
drwxr-xr-x 1 root root 4096 Jan 23 07:24..
-rw-r--r-- 1 root root 20 Jan 23 07:20 hi.log
/ ## cat /data/hi.log
hello from busybox1
/ ## exit

Docker创建了你能够使用--volumes-from选项共享的匿名卷,并创建了一个新的匿名卷。

labex:~/ $ docker volume ls
DRIVER VOLUME NAME
local 0f971b2477d5fc0d0c2b31fc908ee59d6b577b4887e381964650ce6853890dc9
local 1d292aca855adb9de9be7acea88f6d3f8e6a08eef5bfd986a81f073f1906b82f

清理现有的卷和容器。

docker stop my-couchdb
docker rm my-couchdb
docker rm busybox1
docker volume rm $(docker volume ls -q)
docker system prune -a
clear

命名卷

「命名卷」和「匿名卷」的相似之处在于,Docker管理它们的位置。然而,「命名卷」在挂载到容器目录时可以通过名称引用。如果你想在多个容器之间共享一个卷,这会很有帮助。

首先,创建一个「命名卷」:

docker volume create my-couchdb-data-volume

验证卷是否已创建:

$ docker volume ls
DRIVER VOLUME NAME
local my-couchdb-data-volume

现在使用「命名卷」创建名为my-couchdb-name-vol的CouchDB容器:

docker run -d -p 59840:5984 --name my-couchdb-name-vol -v my-couchdb-data-volume:/opt/couchdb/data -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=passw0rd1 couchdb:3.1

等待CouchDB容器运行并实例可用。

创建一个新数据库mydb,并插入一条带有「hello world」消息的新文档。

curl -X PUT -u admin:passw0rd1 http://127.0.0.1:59840/mydb
curl -X PUT -u admin:passw0rd1 http://127.0.0.1:59840/mydb/1 -d '{"msg": "hello world"}'

现在很容易与另一个容器共享该卷。例如,使用busybox镜像读取卷的内容,并通过将卷挂载到busybox容器中的一个目录来共享my-couchdb-data-volume卷。

labex:~/ $ docker run --rm -it --name busybox -v my-couchdb-data-volume:/myvolume busybox sh
/ ## / ## ls -al /myvolume
total 40
drwxr-xr-x 4 5984 5984 4096 Jan 23 07:30.
drwxr-xr-x 1 root root 4096 Jan 23 07:31..
drwxr-xr-x 2 5984 5984 4096 Jan 23 07:29.delete
-rw-r--r-- 1 5984 5984 8388 Jan 23 07:30 _dbs.couch
-rw-r--r-- 1 5984 5984 8385 Jan 23 07:29 _nodes.couch
drwxr-xr-x 4 5984 5984 4096 Jan 23 07:30 shards
/ ## exit

你可以通过以特权权限运行busybox容器并将进程ID设置为host来检查Docker管理的文件系统中的卷,以检查主机系统,并浏览到Docker管理的目录。

docker run -it --privileged --pid=host busybox nsenter -t 1 -m -u -n -i sh
/ ## ls -l /var/lib/docker/volumes
total 28
-rw------- 1 root root 32768 Nov 10 15:54 metadata.db
drwxr-xr-x 3 root root 4096 Nov 10 15:54 my-couchdb-data-volume
/ ## exit

清理:

docker stop my-couchdb
docker rm my-couchdb
docker volume rm my-couchdb-data-volume
docker system prune -a
docker volume prune
clear

主机卷

当你希望直接从主机轻松访问卷目录,而不是使用Docker管理的目录时,可以创建一个「主机卷」。

让我们使用当前工作目录(通过命令pwd指示)中的一个名为data的目录,或者在主机上选择你自己的数据目录,例如/home/couchdb/data。如果$(pwd)/data目录尚不存在,我们让Docker创建它。我们将CouchDB容器内的「主机卷」挂载到容器目录/opt/couchdb/data,这是CouchDB的默认数据目录。

运行以下命令:

cd /home/labex/project
docker run -d -p 5984:5984 --name my-couchdb -v $(pwd)/data:/opt/couchdb/data -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=passw0rd1 couchdb:3.1

验证是否创建了一个data目录:

$ ls -al
total 20
drwxrwxr-x 3 labex labex 4096 Aug 29 14:14.
drwxr-x--- 25 labex labex 4096 Aug 29 14:14..
drwxr-xr-x 3 5984 5984 4096 Aug 29 14:14 data

并且CouchDB已在此处创建数据文件:

$ ls -al data
total 32
drwxr-xr-x 3 5984 5984 4096 Aug 29 14:14.
drwxrwxr-x 3 labex labex 4096 Aug 29 14:14..
-rw-r--r-- 1 5984 5984 4257 Aug 29 14:14 _dbs.couch
drwxr-xr-x 2 5984 5984 4096 Aug 29 14:14.delete
-rw-r--r-- 1 5984 5984 8385 Aug 29 14:14 _nodes.couch

还要检查现在Docker没有创建管理卷,因为我们现在使用的是「主机卷」。

docker volume ls

以及:

docker run -it --privileged --pid=host busybox nsenter -t 1 -m -u -n -i sh
sh-5.1## ls -l /var/lib/docker/volumes
total 28
brw------- 1 root root 252, 3 Jan 23 15:15 backingFsBlockDev
-rw------- 1 root root 32768 Jan 23 15:33 metadata.db
drwx-----x 3 root root 4096 Jan 23 15:26 my-couchdb-data-volume
sh-5.1## exit

创建一个新数据库mydb,并插入一条带有「hello world」消息的新文档。

curl -X PUT -u admin:passw0rd1 http://127.0.0.1:5984/mydb
curl -X PUT -u admin:passw0rd1 http://127.0.0.1:5984/mydb/1 -d '{"msg": "hello world"}'

请注意,CouchDB创建了一个shards文件夹:

$ ls -al data
total 40
drwxr-xr-x 4 5984 5984 4096 Aug 29 14:15.
drwxrwxr-x 3 labex labex 4096 Aug 29 14:14..
-rw-r--r-- 1 5984 5984 8388 Aug 29 14:15 _dbs.couch
drwxr-xr-x 2 5984 5984 4096 Aug 29 14:14.delete
-rw-r--r-- 1 5984 5984 8385 Aug 29 14:14 _nodes.couch
drwxr-xr-x 4 5984 5984 4096 Aug 29 14:15 shards

列出shards目录的内容:

$ ls -al data/shards
total 16
drwxr-xr-x 4 5984 5984 4096 Aug 29 14:15.
drwxr-xr-x 4 5984 5984 4096 Aug 29 14:15..
drwxr-xr-x 2 5984 5984 4096 Aug 29 14:15 00000000-7fffffff
drwxr-xr-x 2 5984 5984 4096 Aug 29 14:15 80000000-ffffffff

以及第一个分片:

$ ls -al data/shards/00000000-7fffffff/
total 20
drwxr-xr-x 2 5984 5984 4096 Aug 29 14:15.
drwxr-xr-x 4 5984 5984 4096 Aug 29 14:15..
-rw-r--r-- 1 5984 5984 8346 Aug 29 14:15 mydb.1693289721.couch

分片是数据库中数据的水平分区。将数据划分为分片并将每个分片的副本分布到集群中的不同节点,可以使数据在节点丢失时具有更高的耐久性。CouchDB会自动对数据库进行分片,并在节点之间分布文档子集。

清理:

docker stop my-couchdb
docker rm my-couchdb
sudo rm -rf $(pwd)/data
docker system prune -a

绑定挂载

与「卷」语法相比,Docker推荐使用「挂载」(mount)语法。与卷相比,绑定挂载的功能有限。当一个文件或目录挂载到容器中时,它是通过主机上的完整路径来引用的。绑定挂载依赖于主机文件系统具有特定的目录结构,并且你不能使用Docker CLI来管理绑定挂载。请注意,绑定挂载可以通过容器中运行的进程来更改主机文件系统。

与使用由冒号分隔符(:)分隔的三个字段的-v语法不同,「挂载」语法更详细,使用多个「键值」对:

  • type:bind、volume或tmpfs,
  • source:主机上文件或目录的路径,
  • destination:容器中的路径,
  • readonly,
  • bind-propagation:rprivate、private、rshared、shared、rslave、slave,
  • consistency:consistent、delegated、cached,
  • mount。
cd /home/labex/project
mkdir data
docker run -it --name busybox --mount type=bind,source="$(pwd)"/data,target=/data busybox sh

在容器中输入命令:

echo "hello busybox" > /data/hi.txt
exit

验证文件是否在主机上创建。

cat data/hi.txt

[可选] OverlayFS

OverlayFS是Linux的一种「联合挂载文件系统」实现。要理解什么是Docker卷,有助于了解层和文件系统在Docker中是如何工作的。

要启动一个容器,Docker会获取只读镜像并在其之上创建一个新的读写层。为了将这些层视为一个整体,Docker使用联合文件系统或OverlayFS(覆盖文件系统),具体来说是overlay2存储驱动程序。

要查看由Docker主机管理的文件,你需要访问Docker进程文件系统。使用--privileged--pid=host标志,你可以从像busybox这样的容器内部访问主机的进程ID命名空间。然后,你可以浏览到Docker的/var/lib/docker/overlay2目录,查看由Docker管理的已下载层。

要查看Docker中当前的层列表:

$ docker run -it --privileged --pid=host busybox nsenter -t 1 -m -u -n -i sh

/ ## ls -l /var/lib/docker/overlay2
total 16
drwx------ 3 root root 4096 Sep 25 19:44 0e55ecaa4d17c353191e68022d9a17fde64fb5e9217b07b5c56eb4c74dad5b32
drwx------ 5 root root 4096 Sep 25 19:44 187854d05ccd18980642e820b0d2be6a127ba85d8ed96315bb5ae37eb1add36d
drwx------ 4 root root 4096 Sep 25 19:44 187854d05ccd18980642e820b0d2be6a127ba85d8ed96315bb5ae37eb1add36d-init
drwx------ 2 root root 4096 Sep 25 19:44 l

/ ## exit

拉取ubuntu镜像并再次检查:

docker pull ubuntu
docker run -it --privileged --pid=host busybox nsenter -t 1 -m -u -n -i sh

再次输入命令查看层列表:

ls -l /var/lib/docker/overlay2/ & exit

你会看到拉取ubuntu镜像时,隐式地拉取了4个新层:

  • a611792b4cac502995fa88a888261dfba0b5d852e72f9db9e075050991423779
  • d181f1a41fc35a45c16e8bfcb8eee6f768f3b98f82210a43ea65f284a45fcd65
  • dac2f37f6280a076836d39b87b0ae5ebf5c0d386b6d8b991b103aadbcebaa7c6
  • f3e921b440c37c86d06cd9c9fb70df50edad553c36cc87f84d5eeba734aae709

overlay2存储驱动程序本质上是将主机上的不同目录分层,并将它们呈现为一个单一目录。

  • 基础层或lowerdir
  • diff层或upperdir
  • 覆盖层(用户视图)
  • work目录

OverlayFS将较低的目录称为lowerdir,其中包含基础镜像和拉取下来的只读(R/O)层。

上层目录称为upperdir,是读写(R/W)容器层。

统一视图或overlay层称为merged

最后,workdir是必需的,它是overlay用于内部使用的空目录。

overlay2驱动程序最多支持128个较低的OverlayFS层。l目录包含作为符号链接的缩短层标识符。

Overlay2存储驱动程序

清理:

docker system prune -a
clear

总结

在本实验中,你学习了如何使用「卷」(volumes)和「绑定挂载」(bind mounts)来管理容器中的数据。你还了解了overlay2存储驱动程序以及它如何使用「联合文件系统」来管理层。

您可能感兴趣的其他 Docker 教程