LXX的网络日志
人因梦想而伟大
Docker-05-数据管理

数据管理

在容器管理中数据主要有两种方式:

  • 数据卷(Volumes)
  • 挂载绑定(Bind mounts)

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

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

数据卷

数据卷是一个可供一个或者多个容器使用的特殊目录,可以提供很多有用的特性:

  • 在多个运行容器之间共享数据。如果未显式创建它,则会在第一次将其装入容器时创建卷。当该容器停止或被移除时,该卷仍然存在。多个容器可以同时安装相同的卷,可以是读写也可以是只读。仅在我们明确删除卷时才会删除卷。
  • 当Docker主机不能保证具有给定的目录或文件结构时。Volumes可帮助我们将Docker主机的配置与容器运行时分离。
  • 如果要将容器的数据存储在远程主机或云提供程序上,而不是本地存储。
  • 当我们需要备份,还原或将数据从一个Docker主机迁移到另一个Docker主机时,卷是更好的选择。我们可以使用卷停止容器,然后备份卷的目录(例如/var/lib/docker/volumes/)。

创建一个数据卷

xin@xin:~$ docker volume create my-vol

查看所有的数据卷:

xin@xin:~$ docker volume ls
local               my-vol
...

在主机里使用以下命令可以查看指定数据卷的信息:

xin@xin:~$ docker volume inspect my-vol 
[
    {
        "CreatedAt": "2019-01-03T23:09:12+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": {},
        "Scope": "local"
    }
]

删除卷:

xin@xin:~$ docker volume rm my-vol

数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker不会再容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。

无主的数据卷可能会占据很多空间,要清理可以使用下面的命令:

xin@xin:~$ docker volume prune

启动一个具有数据卷的容器

如果启动具有尚不存在的卷容器,Docker会自动创建。下面我们将启动一个挂载数据卷为my-vol2的容器,并加载一个数据卷到容器的/app目录。

xin@xin:~$ docker run -d --name nginx --mount source=my-vol2,target=/app nginx:latest

查看数据卷的具体信息

使用docker inspect nginx验证创建的数据卷是否正确,找到Mounts部分:

"Mounts": [
    {
        "Type": "volume",
        "Name": "my-vol2",
        "Source": "/var/lib/docker/volumes/my-vol2/_data",
        "Destination": "/app",
        "Driver": "local",
        "Mode": "z",
        "RW": true,
        "Propagation": ""
    }
]

停止容器并移除卷

这里需要注意的是,删除卷是一个单独的步骤:

xin@xin:~$ docker container stop nginx
nginx
xin@xin:~$ docker container rm nginx 
nginx
xin@xin:~$ docker volume rm my-vol2 
my-vol2

使用只读数据卷

对于某些开发应用程序,容器需要写入和绑定装入。有些时候,容器只需要对数据进行读取。注意,多个容器可以挂载相同的数据卷,并且可以为其中一些容器以读写方式挂载,同时为其他容器以只读方式挂载。

xin@xin:~$ docker run -d --name=nginx --mount source=my-vol,destination=/usr/share/nginx/html,readonly nginx:latest

xin@xin:~$ docker exec -it nginx bash

root@b552ca54bdd8:/# cd /usr/share/nginx/html/

root@b552ca54bdd8:/usr/share/nginx/html# echo>>index.html h
bash: index.html: Read-only file system

可以看到,当我向index.html写入文件的时候,提示是只读的。

绑定挂载

自Docker早期以来,绑定挂载一直存在。与数据卷相比,绑定挂载具有有限的功能。使用绑定挂载时,主机上的文件或目录将装入容器中。文件或目录由其在主机上的完整路径或相对路径引用。相反,当我们使用卷时,会在主机上的Docker存储目录中创建一个新目录,Docker会管理该目录的内容。

通常,我们应该尽可能的使用卷。绑定适用于以下类型的用例:

  • 将配置文件从主机共享到容器。这就是Docker默认通过/etc/resolv.conf从主机安装到每个容器中来为容器提供DNS解析的方式 。
  • 在Docker主机上的开发环境和容器之间共享源代码或构建工件。例如,您可以将Maven target/ 目录安装到容器中,每次在Docker主机上构建Maven项目时,容器都可以访问重建的项目。 如果以这种方式使用Docker进行开发,生产Dockerfile会将生产就绪工件直接复制到映像中,而不是依赖于绑定装载。
  • 当Docker主机的文件或目录结构保证与容器所需的绑定挂载一致时。

使用绑定挂载启动容器

考虑到有一个source目录的情况,当我们构建代码的是,构建好的项目会被另存为到source/target/目录中。我们希望构建好的项目可以用于容器/app/目录。

xin@xin:$ docker run -d -it --name devtest --mount type=bind,source="$(pwd)"/target,target=/app ubuntu:18.04

进入容器查看:

xin@xin:$ docker exec -it devtest bash

root@7d5ffb294615:/# ls
app  boot  etc   lib    media  opt   root  sbin  sys  usr
bin  dev   home  lib64  mnt    proc  run   srv   tmp  var

root@7d5ffb294615:/# cd app/
root@7d5ffb294615:/app# ls
classes                 halo-latest.jar           maven-status
generated-sources       halo-latest.jar.original  test-classes
generated-test-sources  maven-archiver

可以看到,将项目打包后绑定目录source/target,到/app目录中去。

当我们删除/app目录中的文件,宿主机的文件也会消失:

root@7d5ffb294615:/app# rm halo-latest.jar.original

root@7d5ffb294615:/app# exit

...

xin@xin:$ ls
classes            generated-test-sources  maven-archiver  test-classes
generated-sources  halo-latest.jar         maven-status

挂载到容器上的非空目录中

如果将bind-mount绑定到容器上的非空目录中,则绑定挂载会隐藏目录的现有内容。在某种情况下,这可能是有益的。例如当我们想要测试新版本的应用程序而不想构建新的image时。

如果把容器/usr目录的内容替换为/tmp/,大多数情况下,这会导致容器无法运行。

xin@xin:$ docker run -d -it --name broken-container --mount type=bind,source=/tmp,target=/usr nginx:latest

docker: Error response from daemon: oci runtime error: container_linux.go:262:
starting container process caused "exec: \"nginx\": executable file not found in $PATH".

容器已创建但它无法运行,我们删掉它:

xin@xin:$ docker container rm broken-container

使用只读绑定挂载

对于某些开发应用程序,容器需要写入,因此更改会传播回Docker主机。在其他时候,容器只需要读访问权限。

xin@xin:~$ docker run -d -it --name devtest --mount type=bind,source="$(pwd)"/target,target=/app,readonly ubuntu:18.04

进入容器:

xin@xin:~$ docker exec -it devtest bash

root@900e0e8ec0af:/# ls
app  boot  etc   lib    media  opt   root  sbin  sys  usr
bin  dev   home  lib64  mnt    proc  run   srv   tmp  var

进入/app目录,并且尝试删除文件:

root@900e0e8ec0af:/# cd /app/
root@900e0e8ec0af:/app# ls
hello.text
root@900e0e8ec0af:/app# rm hello.text
rm: cannot remove 'hello.text': Read-only file system

可以看到,当我们尝试删除文件时,提示我们该文件是只读的无法删除。

小结

数据卷存储在由Docker(/var/lib/docker/volumes/在Linux上)管理的主机文件系统的一部分中。非Docker进程不应修改文件系统的这一部分。数据卷是在Docker中保留数据的最佳方式。

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