Docker存储管理

一、Docker存储概念

1、容器本地存储与Docke存储驱动

容器本地存储:每个容器都被自动分配了内部存储,即容器本地存储。采用的是联合文件系统。通过存储驱动进行管理。

容器本地存储空间:分层结构构成,由一个可写容器层和若干只读的镜像层组成。

联合文件系统:Docker的一种底层技术,由存储驱动(Storage Driver)实现。相应的存储驱动有aufs、overlay、overlay2、devicemapper、btrfs、zfs、vfs等。

存储驱动:控制镜像和容器在 docker 主机上的存储和管理方式。

每个Docker主机只能选择一种存储驱动,不能为每个容器选择不同的存储驱动。

(1)查看主机支持的存储驱动

[root@hqs ~]# docker info
# 找到如下信息
 Storage Driver: overlay2           # 存储驱动是overlay2
  Backing Filesystem: xfs           # 底层文件系统是xfs
  Supports d_type: true             # 支持d_type
  Native Overlay Diff: true
  userxattr: false

(2)Linux发行版支持的存储驱动

您的操作系统和内核可能不支持每个存储驱动程序。
例如,aufs仅在Ubuntu和Debian上受支持,并且可能需要安装额外的软件包,而btrfs仅在您的系统使用btrfs作为存储时受支持。通常,以下配置适用于最新版本的Linux发行版:

Linux发行版 推荐存储驱动 可用驱动
Ubuntu overlay2 overlay,devicemapper,aufs,zfs,vfs
Debian overlay2 overlay,devicemapper,aufs,vfs
CentOS overlay2 overlay, devicemapper, zfs, vfs
Fedora overlay2 overlay, devicemapper, zfs, vfs
SLES 15 overlay2 overlay, devicemapper, vfs
RHEL overlay2 overlay, devicemapper, vfs

(3)可支持的文件系统

关于Docker,备份文件系统是/var/lib/doker/所在的文件系统。某些存储驱动程序仅适用于特定的备份文件系统。

存储驱动 可支持的后台文件系统
overlay2, overlay xfs with ftype=1, ext4
fuse-overlayfs any filesystem
aufs xfs, ext4
devicemapper direct-lvm
btrfs btrfs
zfs zfs
vfs any filesystem

(4)更改现有的存储驱动

可以根据需要修改现有的存储驱动。

注意:

  • 一个主机只能选择一个存储驱动。
  • 系统版本和存储驱动的兼容问题。
  • 更改存储驱动会使得现有的容器和镜像不可访问(原因:每种存储驱动存储镜像层的位置是不同的)
  • 但恢复存储驱动,可以再次访问之前的镜像和容器
# 修改daemon.json文件来更改存储引擎配置
[root@hqs ~]# vi /etc/docker/daemon.json 
{
  "registry-mirror":["https://registry.docker-cn.com"],
  "storage-driver":"overlay"             《————————添加这个信息,23.01版本似乎只能改vfs
}
# 重启docker生效
[root@hqs ~]# systemctl daemon-reload
[root@hqs ~]# systemctl restart docker
[root@hqs ~]# docker info
# 找到如下信息
 Storage Driver: overlay                 《————————修改生效
  Backing Filesystem: xfs
  Supports d_type: true
# 更改存储驱动会使得现有的容器和镜像不可访问
[root@hqs ~]# docker images
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE
# 恢复存储驱动,可以再次访问之前的镜像和容器
[root@hqs ~]# vi /etc/docker/daemon.json  
{
  "registry-mirror":["https://registry.docker-cn.com"]
}
[root@hqs ~]# systemctl daemon-reload
[root@hqs ~]# systemctl restart docker
[root@hqs ~]# docker images
REPOSITORY         TAG       IMAGE ID       CREATED        SIZE
ubuntu             latest    825d55fb6340   2 weeks ago    72.8MB
centos             latest    5d0da3dc9764   7 months ago   231MB

2、容器和非持久化数据

非持久化数据:不需要保存的数据。容器本地存储中的数据属于非持久化数据。

容器创建时会创建非持久化存储,非持久化存储自动创建,从属于容器,生命周期与容器相同。删除容器也会删除全部非持久化数据。

非持久化数据存在问题:

  1. 这类数据从属于容器,生命周期与容器相同,会随着容器的删除而被删除。(易丢失)
  2. 当该容器不再运行时,数据不会持久保存,如果另一个进程需要,则可能很难从该容器中获取数据。(难获取)
  3. 容器的可写层与运行容器的Docker主机紧密耦合,无法轻松地将数据转移到其他位置。(难转移)
  4. 写入容器的可写层需要Docker存储驱动管理文件系统。存储驱动使用Linux内核提供的联合文件系统,其性能不如直接写入主机文件系统的Docker卷。(读写性能差)

3、容器和持久化数据

持久化数据:需要保存的数据。例如:日志、业务数据、客户信息等有用的数据。

外部存储:Docker通过将主机中的文件系统挂载到容器中供容器存取,从而实现持久化数据存储。

容器持久化数据存储方式:Docker目前支持卷、绑定挂载,这两种挂载类型实现容器的持久化。

卷(数据卷):Docker中进行持久化数据存储的最佳方式。本质是Docker主机文件系统中的目录或文件直接挂载到容器的文件系统中。

卷和容器是解耦的,可以独立地创建并管理卷
卷不与容器的生命周期绑定(容器的停止删除和卷无关)
可以将任意数量的卷装入容器,多个容器也可以共享一个或多个卷(多对多关系)

4、挂载类型

往容器中挂载的外部文件系统主要有:卷、绑定挂载、tmpfs挂载。无论哪种挂载,对容器内部来说是一样的,都会显示为文件或目录。

(1)卷

卷存储在主机文件系统中由Docker管理的位置,在Linux主机上该位置默认就是/var/lib/docker/volumes目录。

卷是Docker持久化存储数据的最佳方式。卷支持使用卷驱动,可以让用户将数据存储在远程主机或云提供商处等。

可以以命名方式或匿名方式挂载卷:

  1. 匿名卷(Anonymous Volumes):首次挂载容器未指定名称,Docker为其随机指定一个唯一名称。
  2. 命名卷(Named Volumes):指定明确名称,和匿名卷其他特性相同。

卷由Docker创建并管理,卷适合以下应用场景。

  1. 在多个正在运行的容器之间共享数据。(数据共享)
  2. 当Docker主机不能保证具有特定目录结构时,卷有助于将Docker主机的配置与容器运行时解耦。(构建新目录与主机不同)
  3. 当需要将容器的数据存储到远程主机或云提供商处,而不是本地时。(可以远程挂载卷,公有云、灾备等场景)
  4. 当需要在两个Docker主机之间备份、恢复或迁移数据时。(主机间备份迁移)

(2)绑定挂载

绑定挂载可以存储到主机系统的任意位置,甚至会存储到一些重要的系统文件或目录中。

特点:

  1. 主机上进程或容器可以随时修改。
  2. 相比卷,功能更受限、性能更高。
  3. 绑定挂载运行访问敏感文件。

绑定挂载适合以下应用场景。

  1. 在主机和容器之间共享配置文件。
  2. 在Docker主机上的开发环境和容器之间共享源代码或构建工件。
  3. 当Docker主机上的目录结构保证与容器要求的绑定挂载一致时。

(3)tmpfs挂载

tmpfs挂载仅限于运行Linux操作系统的Docker主机使用,它只存储在主机的内存中,不会被写到主机的文件系统中,因此不能持久保存容器的应用数据。
在不需要将数据持久保存到主机或容器中时,tmpfs挂载最合适。
如果容器产生了非持久化数据,那么可以考虑使用tmpfs挂载避免将数据永久存储到任何位置,并且通过避免写入容器的可写层来提高容器的性能。

二、Docker卷管理

docker volume是Docker卷的管理命令。

创建卷的时候主机上的Docker根目录(linux默认是/var/lib/docker)下的volumes子目录内会生成一个以卷名命名的子目录,这个子目录里面再创建一个_data的子目录作为卷的数据存储路径。

卷名必须唯一,不同的卷驱动不能使用相同的卷名。

# 语法
[root@localhost volumes]# docker volume
Usage:  docker volume COMMAND
Manage volumes
Commands:
  create      Create a volume                                            # 创建卷
  inspect     Display detailed information on one or more volumes        # 查看卷的详细信息
  ls          List volumes                                               # 列出本地docker主机上的卷
  prune       Remove all unused local volumes                            # 删除未被使用的卷
  rm          Remove one or more volumes                                 # 删除指定卷

1、创建卷

使用 docker volume create命令创建卷。

# 语法
[root@localhost volumes]# docker volume create --help
Usage:  docker volume create [OPTIONS] [VOLUME]
Create a volume
Options:
  -d, --driver string   Specify volume driver name (default "local")     # 说明卷驱动名称(默认是'local')
      --label list      Set metadata for a volume                        # 设置卷元数据
  -o, --opt map         Set driver specific options (default map[])      # 设置驱动特殊选项


# 案例1:创建普通卷
[root@localhost volumes]# docker volume create test-vol01
test-vol01
[root@localhost volumes]# docker volume inspect test-vol01
[
    {
        "CreatedAt": "2022-04-27T17:02:27+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/test-vol01/_data",
        "Name": "test-vol01",
        "Options": {},
        "Scope": "local"
    }
]

# 案例2:创建tmpfs卷,大小100m,uid为1000
[root@localhost volumes]# docker volume create --driver local \
> --opt type=tmpfs \
> --opt device=tmpfs \
> --opt o=size=100m,uid=1000 \
> test-vol02
test-vol02
[root@localhost volumes]# docker volume inspect test-vol02
[
    {
        "CreatedAt": "2022-04-27T17:10:21+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/test-vol02/_data",
        "Name": "test-vol02",
        "Options": {
            "device": "tmpfs",
            "o": "size=100m,uid=1000",
            "type": "tmpfs"
        },
        "Scope": "local"
    }
]


# 案例3:设置元数据
[root@localhost volumes]# docker volume create --label city=wuhan test-vol03
test-vol03
[root@localhost volumes]# docker volume inspect test-vol03
[
    {
        "CreatedAt": "2022-04-27T17:07:44+08:00",
        "Driver": "local",
        "Labels": {
            "city": "wuhan"
        },
        "Mountpoint": "/var/lib/docker/volumes/test-vol03/_data",
        "Name": "test-vol03",
        "Options": {},
        "Scope": "local"
    }
]

# 案例4:创建匿名卷
[root@localhost volumes]# docker volume create
597f094e886b533e0a826b4e99ec01dad64e25b039ff84574869da5e26df1b3f
[root@localhost volumes]# docker volume ls
DRIVER    VOLUME NAME
local     597f094e886b533e0a826b4e99ec01dad64e25b039ff84574869da5e26df1b3f
local     test-vol01
local     test-vol02
local     test-vol03

2、查看卷列表

使用 docker volume ls 命令列出当前的卷。

format关键词:

Placeholder Description
.Name Volume name
.Driver Volume driver
.Scope Volume scope (local, global)
.Mountpoint The mount point of the volume on the host
.Labels All labels assigned to the volume
.Label Value of a specific label for this volume. For example {{.Label "project.version"}}
# 语法
[root@localhost volumes]# docker volume ls --help
Usage:  docker volume ls [OPTIONS]    《——————注意选项不能写在ls之前
List volumes
Aliases:
  ls, list
Options:
  -f, --filter filter   Provide filter values (e.g. 'dangling=true')      # 条件筛选
      --format string   Pretty-print volumes using a Go template          # 内容过滤输出
  -q, --quiet           Only display volume names                         # 只打印卷名

# 案例1:普通查看
[root@localhost volumes]# docker volume ls
卷驱动     卷名称
DRIVER    VOLUME NAME
local     test-vol01
local     test-vol02
local     test-vol03

# 案例2:条件筛选
# 没有被使用的卷
[root@localhost volumes]# docker volume ls -f dangling=true
DRIVER    VOLUME NAME
local     test-vol01
local     test-vol02
local     test-vol03
# 驱动为local的卷
[root@localhost volumes]# docker volume ls -f driver=local
DRIVER    VOLUME NAME
local     test-vol01
local     test-vol02
local     test-vol03
# 根据label元数据筛选
[root@localhost volumes]# docker volume ls --filter label=city
DRIVER    VOLUME NAME
local     test-vol03
# 根据名称筛选
[root@localhost volumes]# docker volume ls -f name=03
DRIVER    VOLUME NAME
local     test-vol03
[root@localhost volumes]# docker volume ls -f name=vol
DRIVER    VOLUME NAME
local     test-vol01
local     test-vol02
local     test-vol03

# 案例3:只打印卷名
[root@localhost volumes]# docker volume ls -q
test-vol01
test-vol02
test-vol03

# 案例4:内容过滤输出
[root@localhost volumes]# docker volume ls --format "{{.Name}}: {{.Driver}}"
test-vol01: local
test-vol02: local
test-vol03: local
[root@localhost volumes]# docker volume ls --format "{{.Name}}: {{.Mountpoint}}: {{.Labels}}"
test-vol01: /var/lib/docker/volumes/test-vol01/_data: 
test-vol02: /var/lib/docker/volumes/test-vol02/_data: 
test-vol03: /var/lib/docker/volumes/test-vol03/_data: city=wuhan

3、查看卷详细信息

使用 docker volume inspect 命令查看卷详细信息。包含卷在Docker主机文件系统中的具体位置。

# 语法
[root@hqs ~]# docker volume inspect --help
Usage:  docker volume inspect [OPTIONS] VOLUME [VOLUME...]
Display detailed information on one or more volumes
Options:
  -f, --format string   Format the output using the given Go template

# 案例
[root@hqs ~]# docker volume create --driver local \
> --opt type=tmpfs \
> --opt device=tmpfs \
> --opt o=size=100m,uid=1000 \
> --label city=wuhan \
> --label type=tmpfs \
> test-vol
70eefb6835df2daafcc3e678395af214dae4a98241fd6886e5822bbc9e7de920
[root@hqs ~]# docker volume inspect test-vol
[
    {
        "CreatedAt": "2022-04-28T00:36:35+08:00",
        "Driver": "local",                                           # 卷驱动
        "Labels": {
            "city": "wuhan",
            "type": "tmpfs"
        },
        "Mountpoint": "/var/lib/docker/volumes/test-vol/_data",      # 卷挂载点
        "Name": "test-vol",                                          # 卷名称
        "Options": {
            "device": "tmpfs",
            "o": "size=100m,uid=1000",
            "type": "tmpfs"
        },
        "Scope": "local"                                             # 卷作用域
    }
]
[root@hqs ~]# docker volume inspect --format '{{ .Mountpoint }}' test-vol
/var/lib/docker/volumes/test-vol/_data
[root@hqs ~]# docker volume inspect --format '{{ .Options }}' test-vol
map[device:tmpfs o:size=100m,uid=1000 type:tmpfs]
[root@hqs ~]# docker volume inspect --format '{{ .Options.o }}' test-vol
size=100m,uid=1000
[root@hqs ~]# docker volume inspect --format '{{ .Labels.city }}' test-vol
wuhan

4、删除卷

使用 docker volume rm 删除指定卷(不能删除正在被容器使用的卷)。

# 语法
[root@hqs ~]# docker volume rm --help
Usage:  docker volume rm [OPTIONS] VOLUME [VOLUME...]
Remove one or more volumes. You cannot remove a volume that is in use by a container.
Aliases:
  rm, remove
Options:
  -f, --force   Force the removal of one or more volumes   强制删除

# 案例
[root@hqs ~]# docker volume rm test-vol01
test-vol01
[root@hqs ~]# docker volume rm $(docker volume ls -q)
test-vol
test-vol02
test-vol03

使用 docker volume prune 删除未被容器或服务使用的所有卷。

# 语法
[root@hqs ~]# docker volume prune --help
Usage:  docker volume prune [OPTIONS]
Remove all unused local volumes
Options:
      --filter filter   Provide filter values (e.g. 'label=<label>')
  -f, --force           Do not prompt for confirmation

# 案例
[root@hqs ~]# docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
test-vol01
test-vol02
test-vol03
Total reclaimed space: 0B

三、容器文件系统挂载

使用 docker rundocker create 命令的相关选项将外部的文件系统挂载到容器中。

早期版本:

  1. 卷和绑定挂载都可以通过 -v--mount 选项挂载到容器中,主要是两者的语法的细微区别。
  2. -v用于独立容器,--mount用于集群服务。
  3. tmpfs挂载可以使用 --tmpfs选项。

docker 17.06或更高版本:

  1. 建议所有的容器或服务,绑定挂载、卷、tmpfs都使用--mount选项。(语法清晰、定制详细)
  2. --mount 选项也可以用于独立容器。

-v--mount的区别:

  1. -v语法是将选项组合在一个字段中,--mount的语法是将它们分开。
  2. --mount采用键值对写法支持更多的设置选项,-v 写法更加简洁。

1、-v(--volume)选项

选项包括冒号分隔的三个字段。必须按照正确顺序排序。

# 语法
[root@hqs ~]# docker run(create)
  -v, --volume=[host-src:]container-dest[:<options>]: Bind mount a volume.

第一个host-src字段表示挂载源,来自主机的文件系统,为绝对路径或名称变量。
第二个container-dest字段表示挂载目标,即容器的挂载点,可以是目录或文件路径(绝对路径)
第三个options字段可选,以逗号分隔选项列表,如:[rw|ro], [z|Z], [[r]shared|[r]slave|[r]private], and [nocopy].
   rw:读写模式??
   ro:只读模式
   nocopy:禁止自动复制到卷存储地址
   命名卷,copy是默认模式,copy模式不支持绑定挂载的卷。
   如果既没有指定rw又没有指定ro,卷挂载为读写模式。

# 案例:卷类型
[root@hqs ~]# docker volume create test-vol
test-vol
[root@hqs ~]# docker run -ti -v test-vol:/world ubuntu /bin/bash

# 案例:绑定挂载类型
[root@hqs ~]#  docker run -d \
>   -it \
>   --name devtest \
>   -v "$(pwd)"/target:/app \
>   nginx:latest
9c9bd6c4b7898f272b162683bf7370fe2fde2d04b30a361bda7443bc6a2d2bf4
[root@hqs ~]# docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS         PORTS     NAMES
9c9bd6c4b789   nginx:latest   "/docker-entrypoint.…"   10 seconds ago   Up 9 seconds   80/tcp    devtest
[root@hqs ~]# docker exec -ti 9c9bd6c4b789 /bin/bash
root@9c9bd6c4b789:/# ls
app  boot  docker-entrypoint.d	 etc   lib    media  opt   root  sbin  sys  usr
bin  dev   docker-entrypoint.sh  home  lib64  mnt    proc  run	 srv   tmp  var
root@9c9bd6c4b789:/# df -H
Filesystem               Size  Used Avail Use% Mounted on
overlay                   19G  5.5G   13G  30% /
tmpfs                     68M     0   68M   0% /dev
tmpfs                    1.1G     0  1.1G   0% /sys/fs/cgroup
shm                       68M     0   68M   0% /dev/shm
/dev/mapper/centos-root   19G  5.5G   13G  30% /app

# 案例:只读卷
[root@localhost volumes]# docker run -tid --name test-onlyread \
> -v test-vol:/world:ro \
> centos
f0fe5ba41e18321d261fb0c4bbdd3f2a423ac6cd965f013cd5d70736ae3ad9ef
# 登录容器
[root@localhost volumes]# docker exec -ti test-onlyread /bin/bash
# 尝试向卷中文件写入信息:报错只读文件
[root@f0fe5ba41e18 world]# echo 'test' >> /world/test.txt 
bash: /world/test.txt: Read-only file system

2、--mount选项

绑定挂载、卷、tmpfs类型都使用--mount选项。

--mount选项的参数由多个由逗号分隔的键值对组成。

特殊键如下:

  • type:指定要挂载的类型,值可以是bind(绑定挂载)、volume(卷)或tmpfs。
  • source(或src):指定挂载源
  • destination(或dst、target):指定挂载目标,即容器中的挂载点,必须采用绝对路径的形式
  • readonly:指定只读选项,表示源以只读方式挂载到容器中。
  • 其他键:可以被多次指定,由若干键值对组成。卷和绑定挂载有不同的键。

(1)卷案例

[root@hqs ~]# docker run(create)
      --mount <键>=<值>,<键>=<值>,...                Attach a filesystem mount to the container

# 案例1
[root@hqs ~]# docker run -d   --name devtest01   --mount source=myvol2,target=/app   nginx:latest
65e5132a4a64d40f688d2bcb80fe2f6d9a2fb504e092ca628b59398dd207b2a3
[root@hqs ~]# docker volume ls
DRIVER    VOLUME NAME
local     myvol2
[root@hqs ~]# docker volume inspect myvol2
[
    {
        "CreatedAt": "2022-04-28T02:45:45+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/myvol2/_data",
        "Name": "myvol2",
        "Options": null,
        "Scope": "local"
    }
]
[root@hqs ~]# docker exec -ti devtest01 /bin/bash
root@65e5132a4a64:/# cd /app/


# 案例2:只读卷
[root@localhost volumes]# docker run -tid --name devtest02 \
> --mount source=myvol3,target=/app,readonly \
> centos:latest
e021d47368bb127504af60cb3d01b6ef3060f148d23a290cdac0ce6865bb26ec
# 查看只读卷
[root@localhost volumes]# docker volume ls
DRIVER    VOLUME NAME
local     myvol3
# 在宿主机创建文件
[root@localhost _data]# pwd
/var/lib/docker/volumes/myvol3/_data
[root@localhost _data]# cp /etc/resolv.conf ./
# 登录容器尝试修改
[root@localhost volumes]# docker exec -ti devtest02 /bin/bash
[root@e021d47368bb app]# echo 'asdadad' >> /app/resolv.conf 
bash: /app/resolv.conf: Read-only file system

(2)绑定挂载示例

[root@localhost ~]# docker run -tid --name test01 --mount type=bind,source=/etc/localtime,target=/etc/localtime centos
c660c8241a5a5c6ba78998288aaa6c5e8c797a4df35b9ff31cd38a2d1b0046cf
[root@localhost ~]# docker exec -ti test01 /bin/bash
[root@c660c8241a5a /]# date +%z
+0800

(3)tmpfs挂载示例




四、卷应用示例

1、创建一个卷并让容器挂载

# 1.创建一个卷、查看当前卷列表、查看卷详细信息
[root@hqs ~]# docker volume create test-vol
test-vol
[root@hqs ~]# docker volume ls
DRIVER    VOLUME NAME
local     test-vol
[root@hqs ~]# docker volume inspect test-vol
[
    {
        "CreatedAt": "2022-04-28T03:06:05+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/test-vol/_data",
        "Name": "test-vol",
        "Options": {},
        "Scope": "local"
    }
]

# 2.启动容器,将卷挂载到容器/world目录,在容器中查看根目录、退出容器
[root@hqs ~]# docker run -ti -v test-vol:/world ubuntu /bin/bash
root@e6535eebe726:/# ls
bin   dev  home  lib32  libx32  mnt  proc  run   srv  tmp  var
boot  etc  lib   lib64  media   opt  root  sbin  sys  usr  world
root@e6535eebe726:/# ls /world/
root@e6535eebe726:/# exit
exit

# 3.查看容器信息,验证卷是否正确挂载
[root@hqs ~]# docker inspect --format='{{json .Mounts}}'   e6535
[{"Type":"volume","Name":"test-vol","Source":"/var/lib/docker/volumes/test-vol/_data","Destination":"/world","Driver":"local","Mode":"z","RW":true,"Propagation":""}]

# 4.删除卷
[root@hqs ~]# docker volume rm test-vol
Error response from daemon: remove test-vol: volume is in use - [e6535eebe726069b355567be0a562131844ad04b175b2a0ee870d990f72dca95]
# 使用中的卷不能直接删除,第二次是先删除容器,再删除卷
[root@hqs ~]# docker rm e6535
e6535
[root@hqs ~]# docker volume rm test-vol
test-vol

2、启动容器时自动创建卷

启动带有卷的容器时,如果卷尚不存在,则Docker会自动创建这个卷。
示例:将myvol卷挂载到容器testnovol的/app目录下。
--mount选项实现:

[root@hqs ~]# docker run -d --name testnovol01 --mount source=myvol,target=/app nginx
beafe21868168531d1eeb69c7b29e234f0ceb60cabc60ed8e5deae0c4c99f28d
[root@hqs ~]# docker inspect --format='{{json .Mounts}}' testnovol01
[{"Type":"volume","Name":"myvol","Source":"/var/lib/docker/volumes/myvol/_data","Destination":"/app","Driver":"local","Mode":"z","RW":true,"Propagation":""}]

-v选项实现:

[root@hqs ~]# docker run -d --name testnovol02 -v myvol02:/app nginx
d7728830916fd11dec8487ea9aaf7e6f1b306b3c4c072683cf8155f430f57887
[root@hqs ~]# docker inspect --format='{{json .Mounts}}' testnovol02
[{"Type":"volume","Name":"myvol02","Source":"/var/lib/docker/volumes/myvol02/_data","Destination":"/app","Driver":"local","Mode":"z","RW":true,"Propagation":""}]

3、使用容器填充卷

容器启动时挂载已经存在并包含数据的卷,则容器不会将其挂载点目录的数据复制到该卷,而是直接使用该卷的数据。

如果容器启动时挂载空白卷或自动创建新卷,而容器的挂载点目录有文件,则该挂载点的内容会复制到卷中。

# 1.执行以下命令启动一个nginx容器,并使用容器的/usr/share/nginx/html目录的内容填充新的卷nginx-vol。
[root@hqs ~]# docker run -d --name=nginxtest --mount source=nginx-vol,destination=/usr/share/nginx/html nginx
3f208b4cecc9bc98c0424dc891ae39314b9dab61985e1e957b294a799589fafb
[root@hqs ~]# docker ps 
CONTAINER ID   IMAGE          COMMAND                  CREATED             STATUS             PORTS     NAMES
3f208b4cecc9   nginx          "/docker-entrypoint.…"   20 seconds ago      Up 19 seconds      80/tcp    nginxtest

# 2.执行docker volume inspect nginx-vol命令查看该卷的详细信息,其中挂载点设置如下。
[root@hqs ~]# docker volume ls
DRIVER    VOLUME NAME
local     nginx-vol
[root@hqs ~]# docker volume inspect --format='{{json .Mountpoint}}' nginx-vol
"/var/lib/docker/volumes/nginx-vol/_data"

# 3.查看主机上该卷所在目录的内容,发现容器已经填充了卷。
[root@hqs ~]# cd /var/lib/docker/volumes/nginx-vol/_data/
[root@hqs _data]# ls
50x.html  index.html
[root@hqs _data]# cat index.html 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

# 4.启动另一个容器挂载该卷,以使用其中预先填充的内容。
[root@hqs _data]# docker run -ti --name=othercntr --mount source=nginx-vol,destination=/nginx ubuntu /bin/bash
root@a68b8352663b:/# ls /nginx/
50x.html  index.html
root@a68b8352663b:/# cd /nginx/
root@a68b8352663b:/nginx# cat 50x.html 
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>An error occurred.</h1>
<p>Sorry, the page you are looking for is currently unavailable.<br/>
Please try again later.</p>
<p>If you are the system administrator of this resource then you should check
the error log for details.</p>
<p><em>Faithfully yours, nginx.</em></p>
</body>
</html>

# 5.删除容器和卷
[root@hqs _data]# docker stop $(docker ps -aq)
a68b8352663b
3f208b4cecc9
d7728830916f
beafe2186816
65e5132a4a64
9c9bd6c4b789
[root@hqs _data]# docker rm $(docker ps -aq)
a68b8352663b
3f208b4cecc9
d7728830916f
beafe2186816
65e5132a4a64
9c9bd6c4b789
[root@hqs _data]# docker volume rm  $(docker volume ls -q)
myvol
myvol2
myvol02
nginx-vol

启动容器时指定一个不存在的卷,则自动创建一个空白卷。

如果是把空白卷挂载到容器已有文件的目录,则将这些文件复制到卷中。(常用于预填充容器所需数据)

4、使用只读卷

同一个卷可以由多个容器挂载,并且让容器执行读写操作。设置只读权限后,在容器中是无法对卷中数据修改的,可以提升安全性。

# --mount
[root@localhost _data]# docker run -dti --name=nginx-test --mount source=nginx-vol01,destination=/usr/share/nginx/html,readonly nginx
44bb1ee56819a71a4fafda36ab0ea05952d51b5d3c49dbfcc14a27fd2c84f740
[root@localhost _data]# docker exec -ti nginx-test /bin/bash
root@44bb1ee56819:/# cd /usr/share/nginx/html/
# 在路径中既不能添加文件,也不能修改文件
root@44bb1ee56819:/usr/share/nginx/html# touch sss
touch: cannot touch 'sss': Read-only file system
root@44bb1ee56819:/usr/share/nginx/html# echo 'adadadd' >> 50x.html 
bash: 50x.html: Read-only file system
[root@localhost _data]# docker inspect --format='{{json .Mounts}}'  nginx-test
[{"Type":"volume","Name":"nginx-vol01","Source":"/var/lib/docker/volumes/nginx-vol01/_data","Destination":"/usr/share/nginx/html","Driver":"local","Mode":"z","RW":false,"Propagation":""}]

# -v
[root@localhost _data]# docker run -dti --name=nginx-test02 -v nginx-vol02:/usr/share/nginx/html:ro nginx
5b909ace1f2533ecd637bcf5779baf8df5a90aafb9f18a21fcf1fe3573103641
[root@localhost _data]# docker exec -ti nginx-test02 /bin/bash
root@5b909ace1f25:/# cd /usr/share/nginx/html/
root@5b909ace1f25:/usr/share/nginx/html# echo 'sadad' >> index.html 
bash: index.html: Read-only file system
root@5b909ace1f25:/usr/share/nginx/html# touch asdad
touch: cannot touch 'asdad': Read-only file system
[root@localhost _data]# docker inspect --format='{{json .Mounts}}'  nginx-test02
[{"Type":"volume","Name":"nginx-vol02","Source":"/var/lib/docker/volumes/nginx-vol02/_data","Destination":"/usr/share/nginx/html","Driver":"local","Mode":"ro","RW":false,"Propagation":""}]

注意:-v--mount 选项,inspect查看输出的Mounts部分的信息有差异,主要是'Mode'显示的内容不同,一个表示读写模式,一个表示selinux的标签设置。

5、使用匿名卷

定义:匿名卷没有指定明确的名称。在创建或启动容器时可以创建匿名卷。

# --mount:启动容器时不定义source
[root@localhost ~]# docker run -it --mount destination=/app ubuntu /bin/bash
root@eacbc9629f0f:/# ls
app  boot  etc   lib    lib64   media  opt   root  sbin  sys  usr
bin  dev   home  lib32  libx32  mnt    proc  run   srv   tmp  var
root@eacbc9629f0f:/# exit
exit
[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     baa55a4c1cfc3aa117630efd47b2986fb9ad775be95d4f08735fc0495340ab0e
[root@localhost ~]# docker volume rm baa55a4c
Error: No such volume: baa55a4c     《————指定匿名卷必须使用完整的UUID
[root@localhost ~]# docker rm eacbc9629f0f
eacbc9629f0f
[root@localhost ~]# docker volume rm baa55a4c1cfc3aa117630efd47b2986fb9ad775be95d4f08735fc0495340ab0e
baa55a4c1cfc3aa117630efd47b2986fb9ad775be95d4f08735fc0495340ab0e

# -v:启动容器时省略第一个字段[host-src:]
[root@localhost ~]# docker run -ti -v /app ubuntu /bin/bash
root@3673f14ae07a:/# ls
app  boot  etc   lib    lib64   media  opt   root  sbin  sys  usr
bin  dev   home  lib32  libx32  mnt    proc  run   srv   tmp  var
root@3673f14ae07a:/# exit
exit
[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     db8dfec8d223d5eb3662c73249a9545ee1d982f00b52ca8b1a5e6314d843495d
# 删容器
[root@localhost ~]# docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
3673f14ae07a660a437d615eaca1aeb012673233010d5e7e0cbc0d30c7e601f0
Total reclaimed space: 8B
# 删空白卷
[root@localhost ~]# docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y     
Deleted Volumes:
db8dfec8d223d5eb3662c73249a9545ee1d982f00b52ca8b1a5e6314d843495d
Total reclaimed space: 0B

6、绑定挂载主机上的目录

可以通过绑定挂载将 Docker主机上现有的目录 挂载到容器的目录中,需要挂载的目录可以由 主机的绝对路径 引用。

$(pwd) 命令可以表示linux主机的当前目录,注意要用双引号包起来,否则报错。

绑定挂载的容器,不能强制删除,应该先停止后删除,否则会有报警。

(1) --mount选项

# 1.准备目录,切换到新目录的父目录
[root@localhost ~]# mkdir -p source/target
[root@localhost ~]# cd source/
[root@localhost source]# pwd
/root/source
# 2.启动容器,将主机的source/target目录挂载到容器的/app目录下
[root@localhost source]# docker run -dti --name devtest --mount type=bind,source="$(pwd)/target",target=/app nginx
497a3e9b17926b428e22c8b424a5d566a2edd8cc1211bbadd6416d65cbe13b86
# 失败的案例:绑定挂载必须使用已经存在的目录:
[root@localhost source]# docker run -dti --name devtest01 --mount type=bind,source="$(pwd)/test",target=/app nginx
docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /root/source/test.

# 3.查看容器的信息,验证绑定挂载是否创建
[root@localhost source]# docker inspect --format='{{json .Mounts}}'  devtest
[{"Type":"bind","Source":"/root/source/target","Destination":"/app","Mode":"","RW":true,"Propagation":"rprivate"}]
# type说明挂载方式为绑定挂载,源和目标路径都是正确的,Propagation(传播)方式是:rprivate(私有的)。
# Mode:表示与selinux标签有关的选项。
  # z选项表示绑定挂载的内容在多个容器之间共享。
  # Z选项表示绑定挂载的内容是私有的。

# 4.停止并删除容器
[root@localhost source]# docker rm -f devtest
devtest

# 5.启动只读绑定挂载容器
[root@localhost source]# docker run -dti --name devtest --mount type=bind,source="$(pwd)"/target,target=/app,readonly nginx
586c51f2b909903e4a755a85685bb4b1d9a7b5f2bb226f2bcc4c2f8fae1cf270

# 6.查看容器的信息
[root@localhost source]# docker inspect --format='{{json .Mounts}}' devtest
[{"Type":"bind","Source":"/root/source/target","Destination":"/app","Mode":"","RW":false,"Propagation":"rprivate"}]
# RW为false,说明为只读模式

(2)-v选项

# 1.准备目录,切换到新目录的父目录
[root@localhost ~]# mkdir -p source/target
[root@localhost ~]# cd source/
[root@localhost source]# pwd
/root/source
# 2.启动容器,将主机的source/target目录挂载到容器的/app目录下
[root@localhost source]# docker run -dti --name devtest -v "$(pwd)/target":/app nginx
9efef939c123a1b2e7f4b053008d4699c0dc4f1f879c40ea81614f24e9acec04

# 3.查看容器的信息,验证绑定挂载是否创建
[root@localhost source]# docker inspect --format='{{json .Mounts}}' devtest
[{"Type":"bind","Source":"/root/source/target","Destination":"/app","Mode":"","RW":true,"Propagation":"rprivate"}]
# type说明挂载方式为绑定挂载,源和目标路径都是正确的,Propagation(传播)方式是:rprivate(私有的)。

# 4.停止并删除容器
[root@localhost source]# docker stop devtest
devtest
[root@localhost source]# docker rm devtest
devtest

# 5.启动只读绑定挂载容器
[root@localhost source]# docker run -dti --name devtest -v "$(pwd)/target":/app:ro nginx
c56f97fd97247eaa3fe02edeef6e298944944575d023e16a60365edb53911aa3

# 6.查看容器的信息
[root@localhost source]# docker inspect --format='{{json .Mounts}}' devtest
[{"Type":"bind","Source":"/root/source/target","Destination":"/app","Mode":"ro","RW":false,"Propagation":"rprivate"}]
# Mode为"ro",为只读模式

7、绑定挂载主机上的文件

绑定挂载文件主要用于主机与容器之间共享配置文件。

配置文件置于Docker主机中,并挂载到容器,可以随时更改配置,将配置管理变简单。

# 1.查看主机的时区
[root@localhost source]# date +%z
+0800

# 2.启动一个ubuntu容器查看时区
[root@localhost source]# docker run -tid --rm --name test ubuntu  /bin/bash
a688f13a3d1eb1e2d252b68df657517f3325145823c9ce2c83cdb7e0fb6c889d
[root@localhost source]# docker exec -ti test /bin/bash
root@a688f13a3d1e:/# date +%z
+0000

# 3.启动一个绑定挂载文件的容器查看时区
[root@localhost source]# docker run -tid --rm --name test01 -v /etc/localtime:/etc/localtime  ubuntu  /bin/bash
bda7b3deca99bd39985df06a0c875114c3867f7411477ccbe220dcf08260e4a6
[root@localhost source]# docker exec -ti test01 /bin/bash
root@bda7b3deca99:/# date +%z
+0800

8、绑定挂载主机上不存在的目录或文件

绑定挂载Docker主机上不存在的目录或文件,--mount-v选项的表现不同。

(1)-v选项:自动创建目录

-v选项,对不存在的目录或文件,主机都会自动创建一个目录

# 启动容器,查看路径,并退出
[root@localhost ~]# docker run -ti --rm -v /doestnt/exist:/foo ubuntu bash
root@8fcdd361f44f:/# cd foo/
root@8fcdd361f44f:/foo# pwd
/foo
root@8fcdd361f44f:/foo# exit
exit

# 在docker主机查看新创建的路径
[root@localhost /]# ls
bin   dev      etc   lib    media  opt   root  sbin  sys  usr
boot  doestnt  home  lib64  mnt    proc  run   srv   tmp  var
[root@localhost /]# ls /doestnt/exist/

(2)--mount选项:报错

--mount选项,对不存在的目录或文件,主机不会自动创建目录,会报错。

[root@localhost /]# docker run -ti --rm --mount type=bind,source=/doesnt/exist,destination=/foo ubuntu bash
docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /doesnt/exist.

9、绑定挂载到容器中的非空目录

如果将主机上的目录绑定挂载到容器上的非空目录,则容器挂载的目录中的现有内容会被绑定挂载(主机上的目录)所遮盖。

  1. 被遮盖的目录和文件不会被删除或更改,但在使用绑定挂载时不可访问
  2. 无论主机上的目录是否为空,绑定挂载到容器中的非空目录都会发生被遮盖的情况。
# 案例1:主机目录有内容,会遮盖容器目录内容
[root@localhost /]# docker run -tid --name broken-test --mount type=bind,source=/tmp,target=/etc/yum.repos.d centos
cb7c523f56bd7ae4293bb83a27074f35f9abd319c9d08a94d9b17d072ddffad1
[root@localhost /]# docker exec -ti broken-test /bin/bash
[root@cb7c523f56bd /]# cd /etc/yum.repos.d/
[root@cb7c523f56bd yum.repos.d]# ls
vmware-root      《————遮盖了容器之前的内容

# 案例2:主机目录没有内容,也会遮盖容器目录内容
[root@localhost /]# docker run -tid --name broken-test02 --mount type=bind,source=/home,target=/etc/yum.repos.d centos    
76b23ec347b54f32789d2883ef5c0fd5ef37d1af3aac54d4b6a52858db2f3892
[root@localhost /]# docker exec -ti 76b23ec347b5 /bin/bash
[root@76b23ec347b5 /]# cd /etc/yum.repos.d/
[root@76b23ec347b5 yum.repos.d]# ls      《————————内容和/home一样为空

# 案例3:-v案例
[root@localhost /]# docker run -tid --name broken-test03 \
> -v /etc/yum.repos.d/:/etc/yum.repos.d/ centos 
2c3a8cf94b79db1c78551c4e63606aadc9dbe3c6010b995da1ba0962f2a8a739
[root@localhost /]# docker ps -a
CONTAINER ID   IMAGE     COMMAND       CREATED         STATUS         PORTS     NAMES
2c3a8cf94b79   centos    "/bin/bash"   2 seconds ago   Up 2 seconds             broken-test03
76b23ec347b5   centos    "/bin/bash"   8 minutes ago   Up 8 minutes             broken-test02
1e6f1d84a0f6   centos    "/bin/bash"   9 minutes ago   Up 9 minutes             broken-test
[root@localhost /]# docker exec -ti broken-test03 /bin/bash
[root@2c3a8cf94b79 /]# cd /etc/yum.repos.d/
[root@2c3a8cf94b79 yum.repos.d]# ls        《————————内容和宿主机目录内容一致
CentOS-Base.repo  docker-ce.repo  elrepo.repo  repo.bak

10、备份、恢复和迁移数据卷

可以通过卷容器(Volume Container)实现卷的备份、恢复和迁移。

卷容器(数据卷容器):一种特殊的容器,专门用来将卷(或绑定挂载)提供给其他容器挂载。

--volumes-from 选项,创建容器时使用这个选项:

  1. 可基于卷容器创建一个新的容器;
  2. 新容器挂载 卷容器 提供的卷。

(1)创建卷容器

# 创建卷容器dbstore,该卷容器挂载一个匿名卷
[root@hqs tmp]# docker create -v /dbdata --name dbstore busybox /bin/sh
e0bf42be22e492c1377112967dd113d6cfc6b0bd2483adef84f9f0da962c2806
# 查看新创建的匿名卷
[root@hqs tmp]# docker volume ls
DRIVER    VOLUME NAME
local     e3440f88e29fdb974db84419090554c2d61bd78819a86650446f3f69a5030783
# 查看卷容器详情中,卷信息
[root@hqs tmp]# docker inspect --format='{{json .Mounts}}' dbstore
[{"Type":"volume","Name":"e3440f88e29fdb974db84419090554c2d61bd78819a86650446f3f69a5030783","Source":"/var/lib/docker/volumes/e3440f88e29fdb974db84419090554c2d61bd78819a86650446f3f69a5030783/_data","Destination":"/dbdata","Driver":"local","Mode":"","RW":true,"Propagation":""}]

(2)备份卷容器

# 基于卷容器dbstore,创建新容器
# 此时新容器会挂载卷容器dbstore的匿名卷
# 另外还用-v绑定挂载了本地/tmp目录到容器的/backup目录
# 运行容器后,将匿名卷挂载目录/dbdata内容打包压缩存放在/backup/目录,也就是宿主机的/tmp目录
[root@hqs tmp]# docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvfP /backup/backup.tar /dbdata
/dbdata/
# 查看宿主机/tmp目录内容
[root@hqs tmp]# ls
backup.tar  vmware-root
[root@hqs tmp]# du -sh *
12K	backup.tar
0	vmware-root

# 执行命令会完成一下操作:
# 1. 启动一个新的容器并从dbstore容器中挂载卷。
# 2. 将本地主机的当前目录挂载为/backup。
# 3. 传送一个命令将dbdata卷的内容打包为/backup/backup.tar文件。

(3)从备份恢复卷容器

创建备份后,可以将它恢复到同一个容器,或另一个容器。

# 1. 创建卷容器dbstore2,也挂载了一个匿名卷
[root@hqs tmp]# docker run -v /dbdata --name dbstore2 ubuntu /bin/bash

# 2. 在新容器的数据卷中将备份文件解压
# 创建新容器,挂载卷容器dbstore2的匿名卷
# 绑定挂载宿主机的当前目录/tmp到容器的/backup目录
# 宿主机/tmp中包含dbstore卷中内容的压缩文件,运行容器时将其解压
[root@hqs tmp]# docker run -tid --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /backup && tar xvf backup.tar && /bin/bash"
e4a269b16d16e03e8e8fa4bd94940d03e7425c2671573bf570a3f42a2f2f29a2
# 登录容器查看/backup路径中内容
[root@hqs tmp]# docker exec -ti e4a269b16d16 /bin/bash
root@e4a269b16d16:/# cd /backup/
root@e4a269b16d16:/backup# ls
backup.tar  dbdata  vmware-root       《————dbdata为解压出的文件夹
posted @ 2022-04-26 13:27  休耕  阅读(1710)  评论(0编辑  收藏  举报