Docker 数据存储

我们都知道 Docker 的数据可以存在容器的可写层,但是也存在以下几点不足:

  • 当该容器不再运行时,数据将不会持久存储,如果另一个进程需要它,就很难将数据从容器中取出。

  • 容器的可写层与 Docker Host 在容器运行时紧密耦合,你不能轻易地把数据移到别的地方。

  • 写入容器的可写层需要一个 storage driver 来管理。storage driver 使用 Linux 内核提供一个文件系统。与使用直接写入宿主文件系统的 volume 相比,这种额外的抽象降低了性能。

Docker 提供了三种不同的方式将数据从 Docker Host 挂载到 Docker 容器,并实现数据的读取和存储:volumes、bind mounts、tmpfs 。

无论我们选择使用哪种类型的挂载,数据从容器中看起来都一样的,它在容器的文件系统中作为目录或单个文件展示。

我们可以通过数据存储在 Docker Host 的方式来简单的了解这三种挂载方式的不同,如下图:

  • Volumes 存储在 Docker Host 文件系统的一个路径下,这个路径是由 Docker 来进行管理,路径默认是 /var/lib/docker/volumes/,非 Docker 的进程不能去修改这个路径下面的文件,所以说 Volumes 是持久存储数据最好的一种方式。

  • Bind mounts 可以存储在 Docker Host 文件系统的任何位置,它们甚至可能是重要的系统文件或目录,非 Docker 的进程或者 Docker 容器可能随时对其进行修改,存在潜在的安全风险。

  • Tmpfs 只存储在 Docker Host 的系统内存中,不会写入到系统的文件系统中,不会持久存储。

Bind mounts

bind mount自docker早期便开始为人们使用了,用于将host上的文件或目录被挂载到容器中。挂载时需要我们指定文件或目录在 host 上的完整路径。

如何使用

-v or --volume 语法

它有三部分组成,使用:进行分割,这些字段必须以正确的顺序排列,并且每个字段的含义不明显。

  • 第一个字段是 Docker Host 上的一个文件或者目录。
  • 第二个字段是将要挂载到容器上的一个文件或者目录。
  • 第三个字段是可选的,用来增加一些附加选项,比如 ro.

--mount 语法

由多个键值对组成,以逗号分隔,每个键=组由一个元组组成。--mount语法比-v或--volume更详细的,但键的顺序并不明显,并且标志的键值更容易理解。

key value
type bind, volume, or tmpfs
source/src Docker Host上的一个目录或者文件
destination/dst/target 被挂载容器上的一个目录或者文件
readonly 挂载为只读
option 其它选项

本地存在目录

比如我们想把 Docker Host 的目录source/html/挂载到 nginx 容器的/usr/share/nginx/html/,我们对 html 目录的更改希望可以立刻在容器 html 目录生效,这样我们就可以非常方便的修改网页的文件了。

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

使用命令docker inspect bindtest来查看挂载是否正确挂载。

"Mounts": [
            {
                "Type": "bind",
                "Source": "/root/html",
                "Destination": "/usr/share/nginx/html",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],

从里面可以出这是一个 bind mount,并且是读写挂载。

我们在 html 目录下创建一个 index.html,并且写入内容为“this is a bind test”。并且访问本地的 80 端口查看结果。

[root@tuling html]$ cat index.html       
this is a bind test
[root@tuling html]$ curl http://localhost
this is a bind test

从这里例子之中我们可以看出如果我们挂载到容器的目录中有文件,文件会被我们的源地址文件进行覆盖。

我们把容器销毁掉,查看一下我们创建的文件是否还存在。

docker stop bindtest
docker rm bindtest

我们发现文件还是存在的,可见,即使容器没有了,bind mount 也还在。

只读挂载

另外,bind mount 时还可以指定数据的读写权限,默认是可读可写,可指定为只读:

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

查看挂载详情,看看是不是只读模式。

"Mounts": [
            {
                "Type": "bind",
                "Source": "/root/html",
                "Destination": "/usr/share/nginx/html",
                "Mode": "ro",
                "RW": false,
                "Propagation": "rprivate"
            }
        ],

我们命令docker exec -ti bindtest bash进入到容器内部,修改文件测试一下。

[root@tuling ~]$ docker exec -ti bindtest bash
root@cb75eb777bf0:/$ ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@cb75eb777bf0:/$ cd /usr/share/nginx/html/
root@cb75eb777bf0:/usr/share/nginx/html$ ls
index.html
root@cb75eb777bf0:/usr/share/nginx/html$ echo 111> index.html 
bash: index.html: Read-only file system

readonly 设置了只读权限,在容器中是无法对 bind mount 数据进行修改的。只有 host 有权修改数据,提高了安全性。

单文件挂载 除了制定目录外,我们也可以指定单个文件进行覆盖,如下:

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

Volume

Volume 完全由 Docker 来进行管理,比如 volume 的创建,我们可以使用命令 docker volume create 来简单的创建一个 volume,当容器或者服务创建的时候,Docker 也可以自动的创建一个 volume。

我们首先需要创建一个 volume。

docker volume create my_vol

列出 volumes:

root@tuling:~$ docker volume ls
DRIVER              VOLUME NAME
local               my_vol

查看指定卷的详细信息:

root@tuling:~$ docker volume inspect my_vol
[
    {
        "CreatedAt": "2020-02-27T16:04:38+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my_vol/_data",
        "Name": "my_vol",
        "Options": {},
        "Scope": "local"
    }
]

删除卷:

docker volume rm my_vol

使用无数据 volume 启动容器

我们查看一下刚刚创建的 volume 里面是否有数据

root@tuling:~$ ll /var/lib/docker/volumes/my_vol/_data
total 0

我们看到里面并没有数据,那我们启动容器查看一下。

docker run -d \
  -it \
  -p 80:80 \
  --name bindtest \
  --mount source=my_vol,target=/usr/share/nginx/html \
  nginx:latest

使用命令docker inspect bindtest查看一下挂载详情。

 "Mounts": [
            {
                "Type": "volume",
                "Name": "my_vol",
                "Source": "/var/lib/docker/volumes/my_vol/_data",
                "Destination": "/usr/share/nginx/html",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],

我们再次查看一下 Source 目录。

root@tuling:~$ ll /var/lib/docker/volumes/my_vol/_data
total 8
-rw-r--r-- 1 root root 494 Jan 21 21:36 50x.html
-rw-r--r-- 1 root root 612 Jan 21 21:36 index.html

我们可以看到 volume 的内容跟容器原有 /usr/share/nginx/html 完全一样,因为我们挂载的 volume 是刚刚创建没有数据的,容器原有的数据会被复制到 volume 中,我们同样的可以对其进行修改操作,直接反映到容器中。

我们删掉容器查看一下 volume 的数据是否被删除。

docker stop bindtest
docker rm bindtest

再次进行查看。

root@tuling:~$ ll /var/lib/docker/volumes/my_vol/_data
total 8
-rw-r--r-- 1 root root 494 Jan 21 21:36 50x.html
-rw-r--r-- 1 root root 612 Jan 21 21:36 index.html

可以看到,数据没有被删除。

使用有数据 volume 启动容器

我们接着上个 volume 进行测试,我们知道里面已经存在文件 index.html,我们更改里面的内容,并且把 50x.html 删掉。

root@tuling:~$ ll /var/lib/docker/volumes/my_vol/_data
total 4
-rw-r--r-- 1 root root 612 Jan 21 21:36 index.html
root@tuling:~$ echo "this is volume test" > /var/lib/docker/volumes/my_vol/_data/index.html

确认好 volume 之后,我们启动容器。

docker run -d \
  -it \
  -p 80:80 \
  --name bindtest \
  --mount source=my_vol,target=/usr/share/nginx/html \
  nginx:latest

然后访问一下容器。

[root@tuling]$ curl http://localhost          
this is volume test

我们可以看到,当我们的 volume 里面有数据的时候,容器内的数据就被 volume 覆盖了,同样的,当我们删除容器之后,volume 里面的数据会依然存在的。

不提前创建 volume 启动容器

之前的情况都说我们提前创建好 volume 进行挂载的,这次我们不提前创建,直接指定,看看会出现什么情况。

docker run -d \
  -it \
  -p 80:80 \
  --name bindtest \
  --mount source=my_vol2,target=/usr/share/nginx/html \
  nginx:latest

我们可以看到,也创建成功了,这次我们对 volume 的命名为 my_vol2。

我们使用名称查看一下 volume 的情况。

root@tuling:~$ docker volume  ls
DRIVER              VOLUME NAME
local               1e97f99d822b0e377a5fbbd4c6e4183f618cff7da232a87610bfacf080b9776f
local               f1bbefc4edf045c093e1512f0fa5db3a95ca30da254125696b9b5fb673d3b40e
local               my_vol
local               my_vol2

我们可以看到 Docker 给我们自动创建了一个 volume,那我们使用docker inspect bindtest查看一下挂载详情。

 "Mounts": [
            {
                "Type": "volume",
                "Name": "my_vol2",
                "Source": "/var/lib/docker/volumes/my_vol2/_data",
                "Destination": "/usr/share/nginx/html",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],

我们猜测,这种情况应该和第一种空 volume 挂载类似,volume 里面的内容应该是容器复制过来的,我们查看一下是否这样的。

root@tuling:~$ ll /var/lib/docker/volumes/my_vol2/_data
total 8
-rw-r--r-- 1 root root 494 Jan 21 22:36 50x.html
-rw-r--r-- 1 root root 612 Jan 21 22:36 index.html

情况确实如我们所说,从这里我们可以看出,如果使用 bind mount,我们的源目录必须存在,不然docker 会报错,然而我们使用 volume,如果源不存在,docker 会为我们进行创建。

这是因为 bind mount 挂载的路径并不是 docker 进行管理的,他没有权限随便创建目录,然后 volume 是 docker 进行管理的,它可以在自己的存储目录下面创建 volume。

当我们想把容器内的数据导出来时,使用这种方式非常方便。

只读模式挂载 volume

在某些情况下,我们使用多容器进行挂载的时候,我们不允许容器对 volume 里面的数据进行修改,这样可以保证所有的容器挂载的是相同的 volume。

docker run -d \
  -it \
  -p 80:80 \
  --name=nginxtest \
  --mount source=nginx-vol,destination=/usr/share/nginx/html,readonly \
  nginx:latest

使用命令docker inspect nginxtest查看一下挂载情况。

"Mounts": [
            {
                "Type": "volume",
                "Name": "nginx-vol",
                "Source": "/var/lib/docker/volumes/nginx-vol/_data",
                "Destination": "/usr/share/nginx/html",
                "Driver": "local",
                "Mode": "z",
                "RW": false,
                "Propagation": ""
            }
        ],

同样的我们可以看到容器的数据被复制到了 volume 里面,我们进入容器,修改文件看看。

root@tuling:~$ docker exec -ti nginxtest bash
root@a4592bbbfa32:/$ echo "nginxtest" > /usr/share/nginx/html/index.html 
bash: /usr/share/nginx/html/index.html: Read-only file system

可以看到,容器内部是不能修改 volume 里面的数据的。

到这里我们简单对比一下 bind mount 和 volume 的区别。

区别 bind mount volume
source位置 可以任意指定 /var/lib/docker/volumes/....
source为空 覆盖掉容器的内容 容器内数据复制到volume
是否支持单个文件 支持 不支持,只能是目录
权限控制 读写或者只读 读写或者只读
移植性 弱,与hostpath 绑定 强,无需指定hostpath

tmpfs

tmpfs 不在磁盘上持久存储,也不在 Docker Host 容器里面存储,他存储在 host 的内存中,它可以在容器的整个生命周期内被容器所使用。

使用场景

当你不需要持久保留数据在 host 或容器内。这可能是出于安全原因,或者是提升容器的性能,比如我们的程序需要写入很多不需要存储的状态数据时,我们就会使用 tmpfs。

使用语法

同样的,我们可以在单容器的情况下使用--tmpfs,并且不能指定参数,在集群的情况下使用--mount,可以指定一些参数,具体如下:

  • type tmpfs
  • destination/dst/target

使用 tmpfs 启动容器。

docker run -d \
  -it \
  -p 80:80 \
  --name tmptest \
  --mount type=tmpfs,destination=/usr/share/nginx/html \
  nginx:latest

容器对目录所有的读写操作都在内存中。

我们也可以指定 tmpfs 的权限情况。

docker run -d \
  -it \
  -p 80:80 \
  --name tmptest \
  --mount type=tmpfs,destination=/usr/share/nginx/html,tmpfs-mode=1770 \
  nginx:latest

results matching ""

    No results matching ""