2-5-Docker数据卷管理

熊 0 阅读 容器

1. 存储卷介绍

1.1 存储卷基础

Docker镜像由多个文件系统 ( 只读层 ) 叠加而成。Docker 会加载只读镜像层 , 并在镜像栈顶部添加一个读写层。当运行容器后 , 如果修改了某个已存在的文件 , 那么该文件将会从下面的只读层复制到上面的读写层 , 同时 , 该文件在只读层中仍然存在 , 此即“写时复制(COW)”机制。当我们删除 Docker容器 , 并通过镜像重新启动容器时 , 之前的更改的文件将会丢失。

于是 , Docker提出了Volume数据卷的概念。

简单来说 , 数据卷是一个可供一个或多个容器使用的特殊目录 , 用于持久化数据以及共享容器间的数据 , 它以正常的文件或目录的形式存在于宿主机上。 另外 , 其生命周期独立于容器的生命周期 , 即当你删除容器时 , 数据卷并不会被删除。

1769223-20200116170812619-1095240313

描述 : 如果一个文件在最底层是可见的 , 如果在layer1上标记为删除 , 最高的层是用户看到的Layer2的层 , 在layer0上的文件 , 在layer2上可以删除 , 但是只是标记删除 , 用户是不可见的 , 总之在到达最顶层之前 , 把它标记来删除 , 对于最上层的用户是不可见的 , 当标记一删除 , 只有用户在最上层建一个同名一样的文件 , 才是可见的。

类似于挂载一样 , 宿主机的/data/web目录与容器中的/container/data/web目录绑定关系 , 然后容器中的进程向这个目录中写数据时 , 是直接写在宿主机的目录上的 , 绕过容器文件系统与宿主机的文件系统建立关联关系 , 使得可以在宿主机和容器内共享数据库内容 , 让容器直接访问宿主机中的内容 , 也可以宿主机向容器供集内容 , 两者是同步的。

mount名称空间本来是隔离的 , 可以让两个本来是隔离的文件系统 , 在某个子路径上建立一定程度的绑定关系 , 从而使得在两个容器之间的文件系统的某个子路径上不再是隔离的 , 实现一定程度上共享的效果。

在宿主机上能够被共享的目录(可以是文件)就被称为volume。

对于修改删除等这类的操作 , 一般效率非常低 , 如果对于I/O要求比较高的应用 , 如redis在实现持化存储时 , 是在底层存储时的性能要求比较高。

假设底层运行一个存储库mysql ,mysql本来对于I/O的要求就比较高 , 如果mysql又是运行在容器中自己的文件系统之上时 , 也就是容器在停止时 , 就意味着删除 , 其实现数据存取时效率比较低 , 要避免这个限制要使用存储卷来实现。

容器原本存在的问题 :

  • 存储于联合文件系统中 , 不易于宿主机访问 ;

  • 容器间数据共享不便

  • 删除容器其数据会丢失

1.2 存储卷作用

优点是容器中进程所生成的数据 , 都保存在存储卷上 , 从而脱离容器文件系统自身后 , 当容器被关闭甚至被删除时 , 都不用担心数据被丢失, 实现数据可以脱离容器生命周期而持久 , 当再次重建容器时 , 如果可以让它使用到或者关联到同一个存储卷上时 , 再创建容器 , 虽然不是之前的容器 , 但是数据还是那个数据 , 特别类似于进程的运行逻辑 , 进程本身不保存任何的数据 , 数据都在进程之外的文件系统上 , 或者是专业的存储服务之上 , 所以进程每次停止 , 只是保存程序文件 , 对于容器也是一样 ; 容器就是一个有生命周期的动态对象来使用 , 容器关闭就是容器删除的时候 , 但是它底层的镜像文件还是存在的 , 可以基于镜像再重新启动容器。

但是容器有一个问题 , 一般与进程的启动不太一样 , 就是容器启动时选项比较多 , 如果下次再启动时 , 很容器会忘记它启动时的选项 , 所以最好有一个文件来保存容器的启动 , 这就是容器编排工具的作用。一般情况下 , 是使用命令来启动操作docker , 但是可以通过文件来读 , 也就读文件来启动 , 读所需要的存储卷等 , 但是它也只是操作一个容器 , 这也是需要专业的容器编排工具的原因。

另一个优势就是容器就可以不置于启动在那台主机之上了 , 如几台主机后面挂载一个NFS , 在各自主机上创建容器 , 而容器上通过关联到宿主机的某个目录上 , 而这个目录也是NFS所挂载的目录中 , 这样容器如果停止或者是删除都可以不限制于只能在原先的宿主机上启动才可以 , 可以实现全集群范围内调试容器的使用 , 当再分配存储、计算资源时 , 就不会再局限于单机之上 , 可以在集群范围内建立起来 , 基本各种docker的编排工具都能实现此功能 , 但是后面严重依赖于共享存储的使用。

1.3 配合各服务应用状态分析

考虑到容器应用是需要持久存储数据的 , 可能是有状态的 , 如果考虑使用NFS做反向代理是没必要存储数据的 , 应用可以分为有状态和无状态 , 有状态是当前这次连接请求处理一定此前的处理是有关联的 , 无状态是前后处理是没有关联关系的 , 大多数有状态应用都是数据持久存储的 , 如mysql,redis有状态应用 , 在持久存储 , 如nginx作为反向代理是无状态应用 , tomcat可以是有状态的 , 但是它有可能不需要持久存储数据 , 因为它的session都是保存在内存中就可以的 , 会导致节点宕机而丢失session , 如果有必要应该让它持久 , 这也算是有状态的。

应用状态象限 : 是否有状态或无状态 , 是否需要持久存储 , 可以定立一个正轴坐标系 , 第一象限中是那些有状态需要存储的 , 像mysql ,redis等服务 , 有些有有状态但是无需进行存储的 , 像tomcat把会话保存在内存中时 , 无状态也无需要存储的数据 , 如各种反向代理服务器nginx ,lvs请求连接都是当作一个独立的连接来调度 , 本地也不需要保存数据 , 第四象限是无状态 , 但是需要存储数据是比较少见。

1216496-20190108094703624-1465343520

运维起来比较难的是有状态且需要持久的 , 需要大量的运维经验和大量的操作步骤才能操作起来的 , 如做一个Mysql主从需要运维知识、经验整合进去才能实现所谓的部署 , 扩展或缩容 , 出现问题后修复 , 必须要了解集群的规模有多大 , 有多少个主节点 , 有多少个从节点 , 主节点上有多少个库 , 这些都要一清二楚 , 才能修复故障 , 这些就强依赖于运维经验 , 无状态的如nginx一安装就可以了 , 并不复杂 , 对于无状态的应用可以迅速的实现复制 , 在运维上实现自动化是很容易的 , 对于有状态的现状比较难脱离运维人员来管理 , 即使是k8s在使用上也暂时没有成熟的工具来实现。

总之 : 对于有状态的应用的数据 , 不使用存储卷 , 只能放在容器本地 , 效率比较低 , 而导致一个很严重问题就是无法迁移使用 , 而且随着容器生命周期的停止 , 还不能把它删除 , 只能等待下次再启动状态才可以 , 如果删除了数据就可能没了 , 因为它的可写层是随着容器的生命周期而存在的 , 所以只要持久存储数据 , 存储卷就是必需的。

docker存储卷难度 : 对于docker存储卷运行起来并不太麻烦 , 如果不自己借助额外的体系来维护 , 它本身并没有这么强大 , 因为docker 存储卷是使用其所在的宿主机上的本地文件系统目录 , 也就是宿主机有一块磁盘 , 然后容器所使用的目录 , 只是关联到宿主机磁盘上的某个目录而已 , 也就是容器在这宿主机上停止或删除 , 是可以重新再创建的 , 但是不能调度到其他的主机上 , 这也是docker本身没有解决的问题 , 所以docker存储卷默认就是docker所在主机的本地文件系统 , 但是自己搭建一个共享的NFS来存储docker存储的数据 , 也可以实现 , 但是这个过程强依赖于运维人员的能力。

2. 数据卷类型

Docker提供了3种不同的方式将数据从宿主机挂载到容器中。

docker-volume-1

2.1 volume

volume : Docker管理宿主机文件系统的一部分 , 默认位于 /var/lib/docker/volumes 目录下, 也是最常用的方式。

docker-volume-2

看上图 , 所有的Docker容器数据都保存在 /var/lib/docker/volumes 目录下。若容器运行时未指定数据卷 , Docker创建容器时会使用默认的匿名卷 ( 名称为一堆很长的ID ) 。

注意 : Mac系统中 , Docker是基于虚拟机的 , 必须登录到虚拟机里面 , 登录后在目录 /var/lib/docker/volumes/ 下即可找到数据卷。

2.2 bind mount

bind mount : 意为可以存储在宿主机中的任意位置 , 在宿主机上的路径要人工的指定一个特定的路径 , 在容器中也需要指定一个特定的路径 , 两个已知的路径建立关联关系。需要注意的是 , bind mount在不同的宿主机系统时不可移植的 , 比如Windows和 Linux的目录结构是不一样的 , bind mount所指向的host目录也不一样。这也是为什么bind mount不能出现在Dockerfile 中的原因所在 , 因为这样Dockerfile就不可移植了。

1216496-20190108094753761-99085082

2.3 tmpfs mount

tmpfs mount : 挂载存储在宿主机的内存中 , 而不会写入宿主机的文件系统 , 一般不用此种方式。

如果在Linux上运行Docker , 那么还有第三种选择 : tmpfs挂载。不会持久化到宿主机。目的是为了避免写入数据到容器存储层还有一个方案。

与卷和绑定挂载不同 , tmpfs挂载是临时的 , 只存留在主机内存中。当容器停止时 , tmpfs 挂载将被删除 , 在那里写入的文件不会被持久化。

  1. tmpfs 挂载的局限性

    • 不同于卷和绑定挂载 , 不能在容器之间共享tmpfs挂载。

    • 这个功能只有在Linux上运行Docker时才可用。

  2. --tmpfs和--mount行为之间的差异

    • --tmpfs标记不允许指定任何可配置选项。

    • --tmpfs标记不能用于集群服务。对于集群服务 , 您必须使用--mount。

  3. 在容器中使用 tmpfs 挂载

    要在容器中使用tmpfs挂载 , 请使用--tmpfs标记, 或者使用带有type=tmpfs和destination选项的--mount标记。没有用于 tmpfs挂载的源(source)。

    示例1 : (--mount) , 在Nginx容器中的/usr/share/nginx/html创建一个tmpfs挂载

    $ docker run -d \
      -it \
      --name tmptest \
      --mount type=tmpfs,destination=/usr/share/nginx/html \
      nginx:latest
    $ docker exec -it tmptest /bin/bash
    $ df -h

    示例2 : (--tmpfs )

    $ docker run -d \
      -it \
      --name tmptest2 \
      --tmpfs /usr/share/nginx/html \
      nginx:latest
    docker-volume-11

    通过运行docker inspect tmptest来验证挂载是否是tmpfs挂载 , 查看Mounts部分:

    $ docker inspect tmptest|grep -i -C3 Tmpfs
    docker-volume-12
  4. 指定 tmpfs 选项

    tmpfs挂载允许两个配置选项 , 两个选项都不是必需的。 如果需要指定这些选项 , 则必须使用--mount标记 , 因为--tmpfs标记不支持。

    tmpfs-size  tmpfs 挂载的大小(以字节为单位)。默认无限制。
    tmpfs-mode  tmpfs 的八进制文件模式。例如 , 700 或 0770。默认为 1777 或全局可写。

    示例:下面的示例将tmpfs-mode设置为1770 , 因此在容器中它不是全局可读的。

    $ docker run -d \
      -it \
      --name tmptest3 \
      --mount type=tmpfs,destination=/usr/share/nginx/html,tmpfs-mode=1770 \
      nginx:latest
    ​
    docker volume create --driver local \
        --opt type=tmpfs \
        --opt device=tmpfs \
        --opt o=size=100m,uid=1000 \
        foo

3. 数据卷使用

3.1 命令使用

Usage:  docker volume COMMAND
​
Manage volumes
​
Commands:
  create      Create a volume
  inspect     Display detailed information on one or more volumes
  ls          List volumes
  prune       Remove unused local volumes
  rm          Remove one or more volumes
  update      Update a volume (cluster volumes only)

3.1.1 创建存储卷

创建一个名为vol_simple的存储卷 :

docker volume create --name vol_simple

查看数据卷的信息 :

docker volume inspect vol_simple

示例 :

fly@fly:~$ docker volume create --name vol_simple
vol_simple
fly@fly:~$ docker volume inspect vol_simple 
[
    {
        "CreatedAt": "2023-01-04T11:59:24Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/vol_simple/_data",
        "Name": "vol_simple",
        "Options": {},
        "Scope": "local"
    }
]

Docker run和docker create 通过指定-v参数可以为容器挂载一个数据卷。

#没有指定volume卷 , 只指定了容器中的挂载点/data 
docker run -d -it -v /data ubuntu /bin/bash 
# 查看随机创建的volume位置 , 查看mounts的内容 
docker inspect {ID}
# 创建一个指定名字的volume , 并挂载到容器中的/data目录 
docker run -d -v vol_simple:/data ubuntu

示例 :

fly@fly:~$ docker run -dit -v vol_simple:/data ubuntu
e86204b7a02ccb62e6cf4f00b7246ac0d70aadf0d9915df5418ad73f0bf8f00a
fly@fly:~$ docker ps
CONTAINER ID   IMAGE        COMMAND                  CREATED         STATUS          PORTS                                       NAMES
e86204b7a02c   ubuntu       "bash"                   4 seconds ago   Up 2 seconds                                                charming_bhaskara
fly@fly:~$ docker inspect charming_bhaskara 
...
"Mounts": [
            {
                "Type": "volume",
                "Name": "vol_simple",
                "Source": "/var/lib/docker/volumes/vol_simple/_data",
                "Destination": "/data",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],
...

测试数据持久性 :

fly@fly:~$ docker exec -it charming_bhaskara bash
root@e86204b7a02c:/# ls
bin  boot  data  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@e86204b7a02c:/# echo abcd >> /data/1.txt
root@e86204b7a02c:/# cat data/1.txt 
abcd
root@e86204b7a02c:/# exit
exit
fly@fly:~$ docker stop charming_bhaskara 
charming_bhaskara
fly@fly:~$ docker rm charming_bhaskara 
charming_bhaskara
fly@fly:~$ docker ps
CONTAINER ID   IMAGE        COMMAND                  CREATED       STATUS          PORTS                                       NAMES
22634e72e7a3   registry:2   "/entrypoint.sh /etc…"   3 weeks ago   Up 21 minutes   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   registry
fly@fly:~$ sudo cat /var/lib/docker/volumes/vol_simple/_data/1.txt
abcd

3.1.2 挂载数据卷

3.1.2.1 基础使用

使用docker run或者docker create创建新容器是 , 可以使用-v标签为容器添加volume。可以将自行创建或者有Docker创建的volume挂载到容器中 , 也可以将宿主机上的目录或者文件作为volume挂载到容器中。

将宿主机中指定目录作为volume挂载到容器中的/data目录下 , 文件夹必须使用绝对路径 , 如果宿主机中不存在指定的目录 , 则会创建一个空文件夹 ; 如果宿主机文件夹已经存在 , 容器可以通过访问挂载点/data从而访问宿主机文件夹中的所有内容。如果容器下原本已经存在/data文件夹 , 且不为空 , 那么容器中文件夹下原有的内容将被隐藏 , 来保持与宿主机中的文件夹一致。

挂载方式 :

  1. 创建名为vol_simple的数据卷 , 并挂载到容器中的/data目录下 :

    docker volume create --name vol_simple 
    docker run -d -v vol_simple:/data ubuntu /bin/bash
  2. 创建一个随机的ID的volume , 并将其挂载到容器中的/data目录下 :

    docker run -d -v /data ubuntu /bin/bash
  3. 关联宿主机目录 :

    docker run -d -v $HOME/data:/data ubuntu /bin/bash
  4. 使用--mount挂载数据卷

    docker run -d -it --name=test-nginx -p 8011:80 --mount source=test-vol,target=/usr/share/nginx/html nginx:1.13.12

    --mount source=test-vol,target=/usr/share/nginx/html : 将 test-vol 数据卷挂载到容器中的/usr/share/nginx/html目录上;

  5. 挂载权限 ,ro表示只读 , rw表示读写 , 默认为rw :

    # ro表示只读 , rw表示读写 , 默认为rw 
    docker run -it -v $HOME/data:/data:ro ubuntu /bin/bash 
    # 通过修改文件验证 
    echo 123456abcdefg >> /data/test

    同时挂载多个数据卷 :

    docker run -it -v $HOME/data:/data:ro -v /data1 -v /data2 ubuntu /bin/bash
  6. 通过dockerfile指定数据卷挂载 :

    # dockerfile 添加指令 
    VOLUME /data 
    # 指定多个数据卷的挂载点 
    VOLUME ["/data1","/data2"]

    使用dockerfile VOLUME指令 , 与docker run -v 不同的是 , dockerfile指令不能挂载主机中指定的文件夹。这时为了保证Dockerfile的可移植性 , 因为不能保证所有的宿主机都有对应的文件夹。如果镜像中存才/data文件夹 , 这个文件夹中的内容将全部被复制到宿主机上对应文件夹中 , 并且根据容器中的文件设置合适的权限和所有者。 注意 : 在Dockerfile中使用VOLUME指令后的代码 , 如果尝试对这个volume进行修改 , 这些修改都不会生效。因为 : volume是独立于rootfs的存储 , 镜像构建过程 , 每一层类似docker commit提交临时镜像为镜像层 , docker commit不会对挂载的volume进行保存。

示例1 : VOLUME指定了挂载点之后 , 再对挂载点进行操作 , 由于docker commit提交临时镜像不会对volume进行保存 , 所以该data下的file并没有被保存 , 更不会同步到宿主机。

FROM ubuntu 
RUN useradd fly2
VOLUME /data 
RUN touch /data/file 
RUN chown -R fly2:fly2 /data

构建 :

docker build -t test1:v1 -f Dockerfile . 
docker run -d -it test1:v1 /bin/bash

查看挂载点信息 :

docker inspect 容器ID

查看宿主机存储卷目录 :

sudo ls 宿主机存储卷目录

示例2 : 由于挂载volume时 , /data目录已经存在 , 所以/data中的文件以及它们的权限和所有者设置都会被复制到volume中。

FROM ubuntu 
RUN useradd fly2 
RUN mkdir /data && touch /data/file 
RUN chown -R fly2:fly2 /data 
VOLUME /data

构建 :

docker build -t test1:v2 -f Dockerfile1 . 
docker run -d -it test1:v2 /bin/bash

查看挂载点信息 :

docker inspect 容器ID
image-20240306141224072

查看宿主机存储卷目录 :

sudo ls 宿主机存储卷目录

[root@harbor ubuntu]# ls /var/lib/docker/volumes/5a03cd48d24dab9c8b82df7579b0f76135b0b0e282e6a67f79a7ea3dcc9ed913/_data
file

示例3 : 通过CMD和ENTRYPOINT指令 , 在容器启动时执行挂载点下文件的初始化。

FROM ubuntu 
RUN useradd fly2
VOLUME /data 
CMD touch /data/file && chown -R fly2:fly2 /data

3.1.2.2 -v与--mount使用区别

都是挂载命令 , 使用 -v 挂载时 , 如果宿主机上没有指定文件不会报错 , 会自动创建指定文件;当使用 --mount时 , 如果宿主机中没有这个文件会报错找不到指定文件 , 不会自动创建指定文件。

容器运行成功后 , 进入到 /var/lib/docker/volumes 目录下 , 验证数据是否挂载成功 :

docker-volume-3

可以看到已经有了 50x.html 、 index.html 两个Nginx页面相关数据 , 说明数据卷挂载成功了。挂载成功后 , 我们不论是修改 /var/lib/docker/volumes 下的数据 , 还是进入到容器中修改 /usr/share/nginx/html 下的数据 , 都会同步修改对应的挂载目录 , 类似前端开发中双向绑定的作用。

下面 , 我们停止并删除刚刚运行的Nginx容器, 看看数据卷中的数据是否会跟着被删除 :

docker-volume-4

可以发现数据卷相关数据都还在 , 表明数据卷的生命周期独立于容器。另外 , 若下次再创建Nginx容器 , 还可以复用这个数据卷 , 复用性以及扩张性都非常不错。

  • -v|--volume[=[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]]

    创建bind装载。如果指定-v /HOST-DIR:/CONTAINER-DIR , Docker将主机中的/HOST-DR绑定到Docker容器中的 /COCONTAINER-DIR 。如果省略了“HOST-DIR” , Docker会自动在主机上创建新的volume卷。

    OPTIONS是一个逗号分隔的列表

    • [rw|ro]

    • [z|Z]

    • [[r]shared|[r]slave|[r]private]

    • [delegated|cached|consistent]

    • [nocopy]

    CONTAINER-DIR必须是绝对路径 , 例如/src/docs。HOST-DIR可以是绝对路径或名称值。名称值必须以字母数字开头 , 后面跟着A -z0-9、_(下划线)、.(句号)或-(连字符)。绝对路径以/(正斜杠)开头。

    如果你提供的HOST-DIR是一个绝对路径 , Docker会绑定挂载到你指定的路径。如果您提供了一个名称 , Docker将以该名称创建一个volume卷。例如 , 您可以为HOST-DIR值指定/foo或foo。如果你提供了/foo值 , Docker会创建一个bind挂载。如果你提供了foo规范 , Docker会创建一个命名卷。

    您可以指定多个-v选项来将一个或多个挂载挂载到容器。要在其他容器中使用相同的挂载 , 可以使用--volumes-from选项。

    您可以在附加冒号后面为每个绑定挂载提供附加选项。A:ro或:rw后缀分别以只读或读写方式挂载卷。默认情况下 , 卷以读写方式挂载。您还可以指定挂载的一致性要求 , 可以是:consistent(默认)、:cached或:delegated。多个选项之间用逗号分隔 , 例如:ro,cached。

    像SELinux这样的标签系统要求在安装到容器中的卷内容上放置适当的标签。如果没有标签 , 安全系统可能会阻止在容器内运行的进程使用内容。默认情况下 , Docker不修改操作系统设置的标签。

    要在容器上下文中更改标签 , 可以向卷挂载添加两个后缀:z或:Z之一。这些后缀告诉Docker重新标记共享卷上的文件对象。z选项告诉Docker两个容器共享卷内容。因此 ,Docker用共享内容标签来标记内容。共享卷标签允许所有容器读/写内容。Z选项告诉Docker用一个私有的非共享标签来标记内容。只有当前容器可以使用私有卷。

    默认情况下,绑定挂载的卷是私有的。这意味着在容器内完成的任何挂载在主机上都不可见,反之亦然。可以通过指定卷挂载传播属性来更改此行为。在容器内的卷下创建卷共享挂载将在主机上可见,反之亦然。创建从属卷只支持单向挂载传播,即在该卷下的主机上进行的挂载将在容器内可见,而不是其他方式。

    要控制卷的挂载传播属性,可以使用:[r]shared、:[r]slave或:[r]private传播标志。传播属性只能为bind挂载的卷指定,不能为内部卷或命名卷指定。要使挂载传播工作,源挂载点(源目录挂载的挂载点)必须具有正确的传播属性。对于共享卷,源挂载点必须是共享的。对于从卷,源挂载要么是共享的,要么是从卷。

    使用df <source-dir>找出源挂载,然后使用findnt -o TARGET,PROPAGATION <source-mount- dir>找出源挂载的传播属性。如果findnt实用程序不可用,那么可以在/proc/self/mountinfo中查看源挂载点的挂载条目。查看可选字段并查看是否指定了任何传播属性。shared:X表示挂载是共享的,master:X表示挂载是从属的,如果没有,则表示挂载是私有的。

    要更改挂载点的传播属性,请使用mount命令。例如,如果想绑定挂载源目录/foo,可以执行mount --bind /foo /foo和mount --make-private --make-shared /foo。这将把/foo转换为一个共享挂载点。或者,可以直接更改源挂载的传播属性。假设/是/foo的源挂载,然后使用mount --make-shared /将/转换为共享挂载。

    注意:当使用systemd来管理Docker守护进程的启动和停止时,在systemd单元文件中有一个选项来控制Docker守护进程本身的挂载传播,称为MountFlags。此设置的值可能导致Docker看不到挂载点上的挂载传播更改。例如,如果该值为slave,则可能无法在卷上使用shared或rshared传播。

    要禁用数据从容器路径自动复制到卷,请使用nocopy标志。nocopy标志可以在绑定挂载和命名卷上设置。

  • --mount type=TYPE,TYPE-SPECIFIC-OPTION[,...]

    将文件系统挂载到容器上

    现在支持的挂载类型由三种:bind、volume和tmpfs

    type=bind,source=/path/on/host,destination=/path/in/container
    
    type=volume,source=my-volume,destination=/path/in/container,volume-label="color=red",volume-label="shape=round"
    type=tmpfs,tmpfs-size=512M,destination=/path/in/container
    • 共有参数:

      • src, source : 挂载源地址。bind类型必选

      • dst, destination, target: 挂载目的地

      • ro, readonly : true or false (default).

      注意:根据内核版本的不同,将绑定挂载设置为readonly可能不会使其子挂载变为只读。参见bind-recursive。

    • bind类型相关选项

      • bind-propagation : shared,slave, private, rshared, rslave, or rprivate(default)

      • consistency : consistent(default), cached, or delegated. 仅适用于MAC版docker

      • bind-recursive : enabled (default), disabled, writable, or readonly:

        • read-only.

          如果设置为enabled , 子挂载将递归绑定挂载,并尝试递归创建子挂载只读的。

          如果设置为disabled, 子挂载不会递归绑定挂载

          如果设置为writable , 子挂载将递归绑定挂载,但递归不会设置为只读

          如果设置为readonly,子挂载将被递归绑定挂载,并强制递归只读。

      • bind-nonrecursive(已弃用): true或false(默认)。设置true等同于bind-recursive=disabled。 设置false等同于bind-recursive=enabled。

    • volume类型相关参数

      • volume-driver : volume-driver插件名称

      • volume-lable : 卷的元数据信息

      • volume-nocopy : true(默认)或false。如果设置为false,则引擎将挂载路径下的现有文件和目录复制到卷中,允许主机访问它们。

      • volume-opt : 特定于给定的卷驱动程序

    • tmpfs 类型参数:

      • tmpfs-size : 以字节为单位的tmpfs挂载大小。在Linux中默认是无限的。

      • tmpfs-mode : tmpfs权限模式(8进制)。(例如:700或0700。)Linux默认为1777。

3.1.3 共享数据卷

在使用docker run或docker create创建新容器时 , 可以使用–volumes-from标签使得容器与已有容器共享volume。可以使用多个–volumes-from标签 , 使得容器与多个已有容器共享volume。

一个容器挂载了一个volume , 即使这个容器停止运行 , 该volume仍然存在 , 其他容器也可以使用–volumes-from与这个容器共享volume。

共享数据卷作用 : 如果有一些数据 , 比如配置文件、数据文件等 , 要在多个容器之间共享 , 一种常见的做法是创建一个数据容器 , 其他的容器与之共享volume。

示例 : 创建一个带数据卷的容器。

创建目录和数据卷 :

mkdir $HOME/data 
mkdir $HOME/readOlny_data 
docker volume create vol_simple 
docker volume create vol_simple1

创建一个数据容器 包含已创建数据卷 , 只读数据卷 , 私有数据卷等 :

docker run -d -it -v vol_simple:/vol_simple -v $HOME/data:/data -v $HOME/readOlny_data:/readOlny_data:ro --name share_data ubuntu /bin/bash 
docker run -d -it -v vol_simple1:/vol_simple1 --name share_data1 ubuntu /bin/bash

进入容器查看 :

docker exec -it 容器ID /bin/bash

运行新的容器(volume_from1)通过数据容器共享数据卷 容器名 :

docker run -d -it --volumes-from share_data --volumes-from share_data1 --name volume_from1 ubuntu /bin/bash

运行新的容器(volume_from2 )通过数据容器共享数据卷 容器名 :

docker run -d -it --volumes-from share_data --volumes-from share_data1 --name volume_from2 ubuntu /bin/bash
  1. 只读共享点不能被写入。

  2. 可以同时共享多个容器的数据卷。

  3. docker inspect查看容器元数据。

3.1.4 删除数据卷

如果创建容器时从容器中挂载了volume , 在/var/lib/docker/volumes下会生产与volume对应的目录 ( 可使用docker inspect命令查看容器信息找到对应的信息 ) 。 使用docker rm删除容器并不会删除与volume对应的目录 , 这些目录会占据不必要的存储空间。

删除volume的三种方式 :

  1. 使用docker volume rm <volume_name>删除数据卷。

  2. 使用docker rm -v <container_name>删除容器时一并删除它挂载的数据卷。

  3. 在运行容器时使用docker run --rm标签会在容器停止运行时删除容器以及容器所挂载的volume。

注意 :

  1. 在使用第一种docker volume rm删除时 , 只有当没有任何容器使用该volume的时候 , 才能被删除成功。

  2. 另外两种删除方式 , 只会对挂载在该容器上的未指定名称 ( 匿名的 ) 的volume进行删除 , 而会对用户指定名称的 ( 具名的 )volume进行保留。

  3. 如果volume是在创建容器时从宿主机中挂载的 , 无论对容器进行任何操作都不会导致其在宿主机中被删除 , 如果不需要这些文件 , 只能手动删除。 ( 从宿主机中挂载是指docker run -v dir:dir这种模式 ) 。

4. 数据卷类型的使用说明

4.1 bind mount使用

4.1.1 基础使用

通过bind mount模式可以挂载到宿主机的任意位置 , 示例如下 :

docker run -d -it --name=test-nginx -p 8011:80 -v /docker/nginx1:/usr/share/nginx/html nginx:1.13.12

参数说明 :

  • -v /docker/nginx1:/usr/share/nginx/html : 将宿主机中的 /docker/nginx1 目录挂载到容器中的 /usr/share/nginx/html 目录;

容器运行成功后 , 进入容器中 :

docker exec -it test-nginx /bin/bash
docker-volume-5

从上图可以看到 , 与volume不同 , bind mount这种方式会隐藏目录中的内容 ( 非空情况下 ) , 这里的 /usr/share/nginx/html 目录下的html文件被隐藏了 , 所以我们看不到。

但是 , 我们可以将宿主机中该目录中的文件立刻挂载到容器中 , 下面验证一下 :

  1. 新建一个 index.html:

    docker-volume-6
  2. 再次进入容器 , 查看挂载目录内容 :

    docker-volume-7

4.1.2 使用示例

4.1.2.1 将宿主机的代码映射到tomcat容器指定的目录下

  1. 先查看tomcat容器里边的代码内容

    [root@centos-7 app1]``# docker run -it -p 8080:8080 tomcat-app1:v1 # 启动tomcat-app1:v1容器
  2. 到运行的tomcat-app1:v1的容器中查看代码内容

    [root@centos-7 ~]# docker ps  #查看运行的容器
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    21f9df7830a5 tomcat-app1:v1 "/apps/tomcat/bin/ru…" About a minute ago Up About a minute 0.0.0.0:8080->8080/tcp, 8443/tcp nervous_grothendieck
     
    [root@centos-7 ~]# docker exec -it 21f9df7830a5 bash  # 进入正在运行的容器内部
    [root@21f9df7830a5 /]# ls
    apps  bin  data  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
    [root@21f9df7830a5 /]# cd /data/tomcat/webapps/
    [root@21f9df7830a5 webapps]# cat app1/index.html  #查看容器缺省的代码内容
    tomcat web app1 page
  3. 访问网页也可以看到此时的内容

    1769223-20200116172516866-26938152
  4. 在宿主机上先创建一个存放代码的目录

    [root@centos-7 ~]# cd  /data/tomcat/app1/
    [root@centos-7 app1]# echo 20200116 welcome to Shanghai > index.html
  5. 将宿主机的代码数据映射到tomcat访问的数据目录下 , 并映射端口 , 此时volume映射的优先级高

    [root@centos-7 app1]# docker run -it -v /data/tomcat/app1/:/data/tomcat/webapps/app1 -p 8080:8080 tomcat-app1:v1
  6. 查看网页效果 , 此时就是宿主机的代码覆盖了容器内部的数据代码

    1769223-20200116173119741-468189518

4.1.2.2 实现宿主机配置文件挂载到容器中

  1. 在宿主机修改catalina.sh脚本文件 , 并存放到指定的目录下

    [root@centos-7 bin]# mkdir /data/tomcat/bin
    [root@centos-7 bin]#cd /data/tomcat/bin  # 在此目录下将脚本文件存放在此

    创建一个挂载容器log日志的目录

    [root@centos-7 bin]``# mkdir /data/tomcat/logs

    将目录和catalina.sh脚本分别加上权限

    # chmod 777 /data/tomcat/logs # 目录先加上777权限``# chmod +x /data/tomcat/bin/catalina.sh # 加上执行权限
  2. 查看catalina.sh配置文件内容 , 最主要的部分在下面

    1769223-20200116174951245-343157572
  3. 开始挂载此文件到容器中 , 并设置为只读文件 , 避免在容器中进行修改此文件 , 且将容器中的log日志进行挂载 , 避免容器坏掉而log日志在容器中 , 导致数据丢失

    [root@centos-7 logs]# docker run -it -v /data/tomcat/bin/catalina.sh:/apps/tomcat/bin/catalina.sh -v  /data/tomcat/logs/:/apps/tomcat/logs/  -p 8282:8080 tomcat-app1:v1
  4. 此时就可以在宿主机查看容器里边的log日志

    [root@centos-7 bin]# ll /data/tomcat/logs/
    total 16
    -rw-rw-r-- 1 2020 2020 7355 Jan 16 18:03 catalina.2020-01-16.log
    -rw-rw-r-- 1 2020 2020 7866 Jan 16 18:03 catalina.out
    -rw-rw-r-- 1 2020 2020    0 Jan 16 18:03 host-manager.2020-01-16.log
    -rw-rw-r-- 1 2020 2020    0 Jan 16 18:03 localhost.2020-01-16.log
    -rw-rw-r-- 1 2020 2020    0 Jan 16 18:03 localhost_access_log.2020-01-16.txt
    -rw-rw-r-- 1 2020 2020    0 Jan 16 18:03 manager.2020-01-16.log
  5. 此时catalina.sh的文件已经存放到文件中

    [root@centos-7 bin]# docker ps
    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                              NAMES
    95d4f7d917bc        tomcat-app1:v1      "/apps/tomcat/bin/ru…"   56 seconds ago      Up 55 seconds       8443/tcp, 0.0.0.0:8282->8080/tcp   wizardly_hypatia
    [root@centos-7 bin]# docker exec -it 95d4f7d917bc bash 
    [root@95d4f7d917bc /]# cd /apps/tomcat/bin/
    [root@95d4f7d917bc bin]# ls
    bootstrap.jar       commons-daemon-native.tar.gz  daemon.sh  setclasspath.bat  startup.bat       tool-wrapper.bat
    catalina-tasks.xml  commons-daemon.jar        digest.bat     setclasspath.sh   startup.sh        tool-wrapper.sh
    catalina.bat        configtest.bat        digest.sh  shutdown.bat      tomcat-juli.jar   version.bat
    catalina.sh     configtest.sh         run_tomcat.sh  shutdown.sh       tomcat-native.tar.gz  version.sh
    [root@95d4f7d917bc bin]# vim catalina.sh

4.2 数据卷容器

4.2.1 基础使用

  • 创建数据卷容器

    运行一个容器 , 并创建一个名为 dbdata 的数据卷 :

    docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres
    docker-volume-8

    容器运行成功后 , 会发现该数据卷容器处于停止运行状态 , 这是因为数据卷容器并不需要处于运行状态 , 只需用于提供数据卷挂载即可。

  • 挂载数据卷

    --volumes-from 命令支持从另一个容器挂载容器中已创建好的数据卷。

    docker run -d --volumes-from dbdata --name db1 training/postgres
    docker run -d --volumes-from dbdata --name db2 training/postgres
    docker ps
    CONTAINER ID       IMAGE                COMMAND                CREATED             STATUS              PORTS               NAMES
    7348cb189292       training/postgres    "/docker-entrypoint.   11 seconds ago      Up 10 seconds       5432/tcp            db2
    a262c79688e8       training/postgres    "/docker-entrypoint.   33 seconds ago      Up 32 seconds       5432/tcp            db1

    还可以使用多个 --volumes-from 参数来从多个容器挂载多个数据卷。也可以从其他已经挂载了数据卷的容器来挂载数据卷。

    如果删除了挂载的容器 ( 包括dbdata、db1和db2 ) , 数据卷并不会被自动删除。如果想要删除一个数据卷 , 必须在删除最后一个还挂载着它的容器时使用 docker rm -v 命令来指定同时删除关联的容器。

4.2.2 使用示例

  • 创建共享存储卷容器

    创建一个volume-server的容器名 , 并将宿主机的访问页面index.html、catalina.sh、logs日志都映射到此日志中

    [root@centos-7 ~]# docker run -it -d --name volume-server  -v /data/tomcat/bin/catalina.sh:/apps/tomcat/bin/catalina.sh:ro -v /data/tomcat/logs:/apps/tomcat/logs  -v /data/tomcat/app1/:/data/tomcat/webapps/app1/    tomcat-app1:v1
    230d219e035c3fec1145c32060fed37e1e12154df0d010b34f614740f31ef9bd  # 添加一个server服务器的容器 , 并将宿主机的文件映射到此容器中
  • --volume-from选项来复制容器中存储卷

    1. 创建volume-client客户端的容器 , 用--volume-from选项复制上一个volume-server容器里边的存储卷

      [root@centos-7 ~]# docker run -it -d --name volume-client --volumes-from volume-server -p 8282:8080 tomcat-app1:v1
      1f40e1b7b4eb9192260557ebef78aec51f239e66f994b2ae42b1c18a9849843b  #此时映射的volume-client容器内容都是从colume-server容器来的
    2. 进入colume-client的容器内 , 也可以看到宿主机的文件内容

      [root@centos-7 ~]# docker exec -it 1f40e1b7b4eb9192260557ebef78aec51f239e66f994b2ae42b1c18a9849843b bash
      [root@1f40e1b7b4eb /]# cat  /data/tomcat/webapps/index.html
      20200116 welcome to Shanghai  # 可以看到是宿主机的index.html文件
      [root@1f40e1b7b4eb /]# ls -l  /apps/tomcat/logs/
      total 92
      -rw-rw-r-- 1 tomcat tomcat 42631 Jan 16 19:21 catalina.2020-01-16.log
      -rw-rw-r-- 1 tomcat tomcat 45697 Jan 16 19:21 catalina.out
      -rw-rw-r-- 1 tomcat tomcat     0 Jan 16 18:03 host-manager.2020-01-16.log
      -rw-rw-r-- 1 tomcat tomcat     0 Jan 16 18:03 localhost.2020-01-16.log
      -rw-rw-r-- 1 tomcat tomcat     0 Jan 16 18:03 localhost_access_log.2020-01-16.txt
      -rw-rw-r-- 1 tomcat tomcat     0 Jan 16 18:03 manager.2020-01-16.log
    3. 此时访问volume-client的页面 , 实际也是来自于volume-server容器的内容

      1769223-20200116200517088-140030268
  • 再启动一个容器复制volume-server容器的存储卷

    1. 此时新建一个volume-client容器也能访问宿主机的代码网页 

      [root@centos-7 ~]# docker run -it -d --name volume-client1 --volumes-from volume-server -p 8383:8080 tomcat-app1:v1 # 只需要修改映射端口 , 客户端的名称即可
      ce0e8bd7e33249b8b9ec59e8c7c7678086958b62cac4bb16c43e92577aa0d77f
    2. 进入到volume-client2的容器中 , 也可以看到宿主机的访问页面

      [root@centos-7 ~]# docker exec  -it ce0e8bd7e33249b8b9ec59e8c7c7678086958b62cac4bb16c43e92577aa0d77f bash #进入volume-client2容器中
       
      [root@ce0e8bd7e332 /]# cat /data/tomcat/webapps/index.html # 查看此客户端的页面
      20200116 welcome to Shanghai

      此时映射的端口 : 8383照样也能访问网页

      1769223-20200116200920822-1697061412
    3. 此时挂载后的client客户端不会受server端容器的宕机而导致不能访问 , 但是新的容器就不能基于volume-server服务容器进行创建新的容器。

      [root@centos-7 ~]# docker ps  # 查看image镜像ID
      CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                              NAMES
      b4044ab23dc0        tomcat-app1:v1      "/apps/tomcat/bin/ru…"   38 seconds ago       Up 37 seconds       8443/tcp, 0.0.0.0:8383->8080/tcp   volume-client1
      80556d0c9945        tomcat-app1:v1      "/apps/tomcat/bin/ru…"   About a minute ago   Up About a minute   8443/tcp, 0.0.0.0:8282->8080/tcp   volume-client
      54dd95b8d95c        tomcat-app1:v1      "/apps/tomcat/bin/ru…"   About a minute ago   Up About a minute   8080/tcp, 8443/tcp                 volume-server
      [root@centos-7 ~]# docker rm -f 54dd95b8d95c  #删除server服务端的ID
      54dd95b8d95c

      此时客户端的网页还是可以访问 , 此时 , 实验就实现了一个容器去共享存储卷 , 用--volume-from选项来复制此volume-server的文件 , 实现存储卷共享的作用。

      1769223-20200116201104623-1231384643

总结

  1. 在当前环境下 , 即使把提供卷的容器Server删除 , 已经运行的容器Client依然可以使用挂载的卷 , 因为容器是通过挂载访问数据的 , 但是无法创建新的卷容器客户端 , 但是再把卷容器Server创建出来后即可创建新的卷容器Client

    此方式可以用于线上数据库 、共享数据目录等环境 , 即使数据卷容器被删除了 , 其他已经运行的容器依然可以挂载使用。

  2. 数据卷容器可以作为共享的方式为其他容器提供文件共享 , 类似于NFS共享 , 可以在生产中启动一个实例挂载本地的目录 , 然后其他的容器分别挂载此容器的目录 , 即可保证各容器之间的数据一致性。


参考链接

看谁还不懂 Docker 数据卷 ! (qq.com)

Docker之七---Docker存储卷与容器卷详解 - 一叶知秋~~ - 博客园 (cnblogs.com)

一篇文章教你轻松玩转Docker数据卷 (qq.com)

Docker数据卷—Volumes (qq.com)