【Docker基础】深入解析 Docker 存储卷:管理、绑定与实战应用

文章目录

  • 一、什么是存储卷
  • 二、为什么需要存储卷
  • 三、存储卷分类
  • [四、管理卷 Volume](#四、管理卷 Volume)
    • [方式一:Volume 命令操作](#方式一:Volume 命令操作)
    • [方式二:使用 `-v` 或 `--mount` 参数指定卷](#方式二:使用 -v--mount 参数指定卷)
    • [方式三:Dockerfile 匿名卷](#方式三:Dockerfile 匿名卷)
  • 五、操作案例
    • [Docker 命令创建管理卷](#Docker 命令创建管理卷)
    • [Docker -v 创建管理卷](#Docker -v 创建管理卷)
    • [Docker 卷生命周期](#Docker 卷生命周期)
    • [Docker 卷共享](#Docker 卷共享)
  • [六、绑定卷 bind mount](#六、绑定卷 bind mount)
    • 创建卷
      • [使用 `-v` 参数创建卷](#使用 -v 参数创建卷)
      • [使用 `--mount` 参数创建绑定卷](#使用 --mount 参数创建绑定卷)
    • 操作案例
      • [使用 `--mount` 方式创建容器](#使用 --mount 方式创建容器)
      • [使用 `-v` 创建绑定卷](#使用 -v 创建绑定卷)
    • 绑定卷共享
  • [七、临时卷 `tmpfs`](#七、临时卷 tmpfs)
    • 创建临时卷
      • [方式一:使用 `--tmpfs` 参数创建](#方式一:使用 --tmpfs 参数创建)
      • [方式二:使用 `--mount` 参数创建](#方式二:使用 --mount 参数创建)
    • 操作案例
      • [`tmpfs` 参数创建临时卷](#tmpfs 参数创建临时卷)
      • [`mount` 创建临时卷](#mount 创建临时卷)
      • [`tmpfs` 失踪](#tmpfs 失踪)
  • [八、实战练习:Mysql 灾难恢复](#八、实战练习:Mysql 灾难恢复)
  • [九、关于 Volume 的一些问题](#九、关于 Volume 的一些问题)

一、什么是存储卷

存储卷是指将宿主机的本地文件系统中的某个目录与容器内部文件系统中的目录建立绑定关系。

具体来说,当我们在容器中的某个目录下写入数据时,容器会将数据直接写入宿主机上与该容器绑定的目录。

宿主机上与容器形成绑定关系的 目录 被称为存储卷。存储卷的本质是文件或目录,它绕过默认的联合文件系统,直接以文件或目录的形式存在于宿主机上。

例如,宿主机的 /data/web 目录与容器的 /container/data/web 目录形成绑定关系。当容器中的进程向该目录写入数据时,数据会直接写入宿主机的 /data/web 目录。通过这种方式,容器文件系统和宿主机文件系统建立了关联,实现了容器和宿主机之间的数据共享。

这种机制使得容器可以直接访问宿主机中的内容,同时宿主机也可以向容器写入内容。容器和宿主机的数据读写是同步的。


二、为什么需要存储卷

  1. 数据丢失问题

容器按照业务类型,总体可以分为两类:

  • 无状态的(数据不需要被持久化)
  • 有状态的(数据需要被持久化

容器更擅长无状态应用。因为未持久化数据的容器根目录的生命周期与容器的

生命周期一样,容器文件系统的本质是在镜像层上面创建的读写层,运行中的容器对

任何文件的修改都存在于该读写层,当容器被删除时,容器中的读写层也会随之消失

虽然容器希望所有的业务都尽量保持无状态,这样容器就可以开箱即用,并且可以任

意调度,但实际业务总是有各种需要数据持久化的场景,比如 MySQL、Kafka 等有状

态的业务。因此为了解决有状态业务的需求,Docker 提出了卷(Volume)的概念。

  1. 性能问题

UnionFS 在处理修改、删除等操作时,效率较低。对于 I/O 要求较高的应用,如 Redis,在实现持久化存储时,底层存储的性能要求非常高,UnionFS 可能无法满足这些需求。

  1. 宿主机和容器互访不方便

宿主机访问容器,或容器访问宿主机时,通常需要通过 docker cp 等命令来完成,这使得操作变得复杂且不便于应用程序直接访问文件系统。

  1. 容器和容器共享不方便

容器之间共享数据时,也存在不便之处。由于每个容器都有独立的文件系统,容器间的直接共享和数据交换变得复杂且不容易操作。


三、存储卷分类

Docker 提供了三种方式将数据从宿主机挂载到容器中:

  • Volume(卷)

    由 Docker 管理的卷,默认映射到宿主机的 /var/lib/docker/volumes 目录下。在容器内,只需指定挂载点,容器引擎会自动创建一个空目录,或使用已存在的目录与宿主机建立存储关系。这种方式解耦了用户与存储目录的关系,适合临时存储。缺点是用户无法指定具体使用的目录。

  • Bind Mount(绑定数据卷)

    绑定数据卷将宿主机的指定路径映射到容器。宿主机上的路径需要用户手动指定,并且容器中也需要指定对应路径。两个已知的路径建立关联关系。

  • Tmpfs Mount(临时数据卷)

    将数据映射到宿主机的内存中。一旦容器停止运行,tmpfs 卷会被移除,数据丢失。此方式用于高性能的临时数据存储。


四、管理卷 Volume

存储卷可以通过命令创建,也可以在创建容器时通过 -v--mount 进行指定。

方式一:Volume 命令操作

命令清单如下:

命令 别名 功能 备注
docker volume create --- 创建存储卷 ---
docker volume inspect --- 显示存储卷详细信息 显示卷的元数据和配置
docker volume ls docker volume list 列出存储卷 ---
docker volume prune --- 清理所有无用数据卷 删除未使用的卷
docker volume rm --- 删除存储卷 无法删除正在使用中的卷

Docker Volume 操作解释

  • docker volume create
    功能 :创建存储卷
    语法

    bash 复制代码
    docker volume create [OPTIONS] [VOLUME]

    关键参数

    • -d, --driver:指定驱动,默认是 local
    • --label:指定元数据
      示例
    bash 复制代码
    docker volume create my-vol
  • docker volume inspect
    功能 :查看卷详细信息
    语法

    bash 复制代码
    docker volume inspect [OPTIONS] VOLUME [VOLUME...]

    关键参数

    • -f:指定输出格式,如 json
      示例
    bash 复制代码
    docker volume inspect my-vol
  • docker volume ls
    功能 :列出卷
    语法

    bash 复制代码
    docker volume ls [OPTIONS]

    关键参数

    • --format:指定输出格式,如 json, table
    • --filter, -f:过滤
    • -q:仅显示卷名称
      示例
    bash 复制代码
    docker volume ls
  • docker volume rm
    功能 :删除卷,容器不使用时才能删除
    语法

    bash 复制代码
    docker volume rm [OPTIONS] VOLUME [VOLUME...]

    关键参数

    • -f, --force:强制删除
      示例
    bash 复制代码
    docker volume rm hello
  • docker volume prune
    功能 :删除未使用的本地卷
    语法

    bash 复制代码
    docker volume prune [OPTIONS]

    关键参数

    • --filter:过滤
    • -f, --force:不提示确认删除
      示例
    bash 复制代码
    docker volume prune

方式二:使用 -v--mount 参数指定卷

-v--mount 都可以用于创建和管理卷。以下是两种方式的详细说明:

-v 参数
功能 :完成目录映射
语法

bash 复制代码
docker run -v name:directory[:options] ...

参数说明

  • 第一个参数:卷名称
  • 第二个参数:卷映射到容器中的目录
  • 第三个参数 :选项,如 ro 表示只读模式

示例

bash 复制代码
docker run -d --name devtest -v myvol2:/app nginx:latest

通过 docker inspect 可以看到:

json 复制代码
"Mounts": [
  {
    "Type": "volume",
    "Name": "myvol2",
    "Source": "/var/lib/docker/volumes/myvol2/_data",
    "Destination": "/app",
    "Driver": "local",
    "Mode": "",
    "RW": true,
    "Propagation": ""
  }
]

-mount 参数
功能 :完成目录映射
语法

bash 复制代码
--mount '<key>=<value>,<key>=<value>'

关键参数

  • type :指定类型,可选 bindvolumetmpfs
  • source (src):对于命名卷,这是卷的名称;对于匿名卷,省略此字段
  • destination (dst, target):容器中挂载的文件或目录路径
  • ro, readonly:以只读方式挂载

示例

采用 --mount 创建卷

bash 复制代码
docker run -d --name devtest --mount source=myvol2,target=/app nginx:latest

通过 docker inspect 可以看到:

json 复制代码
"Mounts": [
  {
    "Type": "volume",
    "Name": "myvol2",
    "Source": "/var/lib/docker/volumes/myvol2/_data",
    "Destination": "/app",
    "Driver": "local",
    "Mode": "",
    "RW": true,
    "Propagation": ""
  }
]

方式三:Dockerfile 匿名卷

通过 Dockerfile 中的 VOLUME 指令,可以创建 Docker 管理的卷。

使用 VOLUME 指令可以在镜像中创建数据卷,确保每个通过该镜像创建的容器都会自动包含挂载点。但需要注意的是,通过 VOLUME 指令创建的挂载点无法指定主机上的具体目录,而是由 Docker 随机生成。


五、操作案例

Docker 命令创建管理卷

  1. 创建 Docker 管理卷

    使用以下命令创建一个名为 test_volume 的管理卷,并通过 Docker 容器挂载该卷:

bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker volume create test_volume
test_volume
   
ubuntu@VM-8-2-ubuntu:~$ docker container run --name nginx1 -d -p 8080:80 -v test_volume:/usr/share/nginx/html nginx:1.24.0
b2148264eed78ff979945bd7bec1ca14c9158c596f7e509261acdbda9ac358f4
  1. 查看管理卷

    使用以下命令查看所有 Docker 管理卷:

bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker volume ls
DRIVER    VOLUME NAME
local     1d853d8af50a81ef4f21a3984288cc19bb2beea754ebd997f6aab62e04bbc6a9
local     ebf76ebf9a2b50aa1cadfb4d9d3610b2172f13fb33d3ed52db755900daf3c5e1
local     myvol2
local     test_volume
  1. 查看卷的详细信息

    通过以下命令查看 test_volume 的详细信息,包括对应的宿主机目录:

bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker volume inspect test_volume
[
    {
        "CreatedAt": "2025-03-17T16:14:48+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/test_volume/_data",
        "Name": "test_volume",
        "Options": null,
        "Scope": "local"
    }
]
  1. 查看宿主机目录内容

    进入宿主机目录,查看卷挂载的数据:

bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ sudo bash
root@VM-8-2-ubuntu:/home/ubuntu# cd /var/lib/docker/volumes/test_volume/_data
root@VM-8-2-ubuntu:/var/lib/docker/volumes/test_volume/_data# ls
50x.html  index.html
  1. 查看容器的挂载信息

    通过以下命令查看容器的挂载信息,确认卷挂载到容器:

bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker inspect nginx1
[
    {
        "Id": "b2148264eed78ff979945bd7bec1ca14c9158c596f7e509261acdbda9ac358f4",
        "Created": "2025-03-17T08:27:12.908655805Z",
        "Path": "/docker-entrypoint.sh",
        "Args": [
            "nginx",
            "-g",
            "daemon off;"
        ],
        "State": {
        .....
  1. 修改文件

    修改 index.html 文件的内容:

bash 复制代码
   cd /data/var/lib/docker/volumes/test_volume/_data
   echo "Hello Docker" > index.html
  1. 通过浏览器查看

    访问容器的页面,通过浏览器查看内容,可以看到宿主机和容器中的数据是同步的。

  1. 停止容器并释放资源

    停止并删除容器以释放占用的资源:

    bash 复制代码
    docker stop nginx1
    docker rm nginx1

结论:

宿主机和容器之间的数据是同步的,修改宿主机目录中的文件,会实时反映到容器中的挂载位置。


Docker -v 创建管理卷

  1. -v 创建管理卷,并启动容器
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker container run --name nginx2 -d -p 8080:80 -v test_volume2:/usr/share/nginx/html:ro nginx:1.24.0
b4e93a64a5f7f9c278bc5d7b6e26efb8932f2387406fe66ec4a22dc80df1be35
  1. 进入卷目录:
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker inspect test_volume2
[
    {
        "CreatedAt": "2025-03-17T17:04:09+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/test_volume2/_data",
        "Name": "test_volume2",
        "Options": null,
        "Scope": "local"
    }
]
ubuntu@VM-8-2-ubuntu:~$ sudo bash
root@VM-8-2-ubuntu:/home/ubuntu# cd /var/lib/docker/volumes/test_volume/_data
root@VM-8-2-ubuntu:/var/lib/docker/volumes/test_volume/_data# 
  1. 修改 index.html,随后在浏览器进行查看:
bash 复制代码
root@VM-8-2-ubuntu:/var/lib/docker/volumes/test_volume/_data# echo "Hello Docker from test_volume2" > index.html
  1. 在容器中尝试修改 index.html,提示只读文件,无法修改:
bash 复制代码
root@VM-8-2-ubuntu:/var/lib/docker/volumes/test_volume/_data# docker exec -it nginx2 bash
root@b4e93a64a5f7:/# ls
bin  boot  dev	docker-entrypoint.d  docker-entrypoint.sh  etc	home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@b4e93a64a5f7:/# cd /usr/share/nginx/html/
root@b4e93a64a5f7:/usr/share/nginx/html# rm index.html
rm: cannot remove 'index.html': Read-only file system
  1. 清理释放空间
bash 复制代码
docker stop nginx2
docker rm nginx2

结论:

使用 :ro 的卷挂载选项会让容器的文件系统无法修改挂载卷中的内容,但是宿主机仍然能修改卷中的内容。

Docker 卷生命周期

Docker 卷的生命周期包括以下几个关键阶段:

  1. 创建

    使用 docker volume create 命令创建一个新的 Docker 卷。例如,创建名为 test_volume 的卷:

    bash 复制代码
    docker volume create test_volume

    这会在 Docker 引擎的存储位置(如 /var/lib/docker/volumes/)下创建一个新的卷。

  2. 挂载与使用

    卷创建后,可以在容器中挂载并使用。例如,通过以下命令将 test_volume 卷挂载到容器的 /usr/share/nginx/html 目录:

    bash 复制代码
    docker container run --name nginx1 -d -p 8080:80 -v test_volume:/usr/share/nginx/html nginx:1.24.0

    这样,卷 test_volume 就会与容器共享数据,并能够被容器中的应用程序访问。

  3. 数据读写

    在容器中对挂载的卷进行数据的读写操作,任何对卷中文件的修改都会实时反映到容器内的挂载位置。如果使用 :ro(只读)选项挂载卷,容器内的进程将无法修改卷中的数据,但宿主机仍然可以进行修改。

  4. 删除卷

    当不再需要卷时,可以使用 docker volume rm 命令删除卷:

    bash 复制代码
    docker volume rm test_volume

    删除卷时,如果卷仍被某个容器使用,则无法删除,需先停止并删除相关容器。

  5. 自动清理

    Docker 也提供了自动清理未使用卷的功能,通过以下命令删除所有未使用的卷:

    bash 复制代码
    docker volume prune

Docker 卷共享

Docker 卷共享允许多个容器共享同一个卷中的数据。这对于不同容器之间的数据共享和持久化存储非常有用。

  1. 共享卷的创建

    为了共享数据,可以在多个容器中使用同一个卷。例如,创建一个名为 shared_volume 的卷:

    bash 复制代码
    docker volume create shared_volume
  2. 挂载到多个容器

    可以将同一个卷挂载到多个容器中,从而共享数据。例如,将 shared_volume 同时挂载到两个容器:

    bash 复制代码
    docker container run --name container1 -d -v shared_volume:/usr/share/nginx/html nginx:1.24.0
    docker container run --name container2 -d -v shared_volume:/usr/share/nginx/html nginx:1.24.0

    这两个容器都将访问到 shared_volume 卷中的数据,因此它们的数据会同步共享。

  3. 数据同步

    如果容器中有应用程序修改了共享卷中的数据,这些更改会立即反映到所有使用该卷的容器中。例如,如果 container1 修改了 shared_volume 中的文件,container2 也能看到该文件的更改,因为它们都共享同一个卷。

  4. 共享卷的用途

    • 日志共享:多个容器可以共享卷来写入日志文件,便于集中管理。
    • 持久化存储:某些容器需要保存数据,例如数据库容器,可以将数据存储在卷中,以便容器重启后仍能保持数据。

六、绑定卷 bind mount

-v-mount 都可以完成绑定卷的创建

创建卷

使用 -v 参数创建卷

功能:完成卷的映射。

语法

bash 复制代码
docker run -v <宿主机目录>:<容器目录>[:options] ...

参数说明

  • 第一个参数:宿主机目录(与管理卷不同)。
  • 第二个参数:卷映射到容器的目录。
  • 第三个参数 (可选):挂载选项,例如 ro 表示只读模式。

示例

bash 复制代码
docker run -d \
-it \
--name devtest \
-v "$(pwd)"/target:/app \
nginx:latest

使用 --mount 参数创建绑定卷

功能:完成目录映射。

语法

bash 复制代码
docker run --mount '<key>=<value>,<key>=<value>' ...

关键参数

  • type :类型,可以是 bindvolumetmpfs
  • sourcesrc:宿主机目录(与管理卷不同)。
  • destinationdsttarget:容器中的文件或目录路径。
  • roreadonly:以只读方式挂载。

示例

bash 复制代码
docker run -d \
-it \
--name devtest \
--mount type=bind,source="$(pwd)"/target,target=/app \
nginx:latest

结论:

  • -v 参数用于创建并映射卷,通常用于指定宿主机目录与容器目录之间的关系。
  • --mount 参数提供了更详细的挂载选项,适用于绑定卷等更灵活的场景。

操作案例

使用 --mount 方式创建容器

  1. 创建 nginx 容器并挂载宿主机目录

    使用 --mount 方式创建一个 nginx 容器,并将宿主机的 /webapp1 目录挂载到容器的 /usr/share/nginx/html 目录。如果宿主机的 /webapp1 目录不存在,容器启动时会报错。

bash 复制代码
docker container run --name nginx_mount -d -p 8080:80 --mount type=bind,source=/data/myworkdir/fs/webapp1,target=/usr/share/nginx/html nginx:1.24.0

创建宿主机目录并启动容器:

bash 复制代码
root@VM-8-2-ubuntu:/home/ubuntu# mkdir -p /data/myworkdir/fs/webapp1
root@VM-8-2-ubuntu:/home/ubuntu# docker container run --name nginx_mount -d -p 8080:80 --mount type=bind,source=/data/myworkdir/fs/webapp1,target=/usr/share/nginx/html nginx:1.24.0

创建新的宿主机目录并启动另一个容器:

bash 复制代码
root@VM-8-2-ubuntu:/home/ubuntu# mkdir -p /data/myworkdir/fs/webapp1_new
root@VM-8-2-ubuntu:/home/ubuntu# docker run -d -p 8081:80 --name bind1 --mount type=bind,source=/data/myworkdir/fs/webapp1_new,target=/usr/share/nginx/html nginx:1.24.0
  1. 查看挂载信息

    使用 docker inspect 查看容器的挂载信息。

bash 复制代码
root@VM-8-2-ubuntu:/home/ubuntu# docker inspect bind1
[
    {
        "Id": "c184c9db30fe849ea7ed5f8cd945bb3e3c51ad55edf6176c3d22d23cc4ead5d9",
        "Created": "2025-03-18T07:20:17.73803812Z",
        "Path": "/docker-entrypoint.sh",
        "Args": [
            "nginx",
            "-g",
            "daemon off;"
        ],
        "State": {
        ...
  1. 进入容器查看挂载目录

    进入容器终端,查看挂载点 /usr/share/nginx/html,并在宿主机上查看挂载目录,发现文件并不存在。

bash 复制代码
root@VM-8-2-ubuntu:/home/ubuntu# docker exec -it bind1 ls /usr/share/nginx/html
root@VM-8-2-ubuntu:/data/myworkdir/fs# ls
webapp1  webapp1_new
root@VM-8-2-ubuntu:/data/myworkdir/fs# ll webapp1
total 8
drwxr-xr-x 2 root root 4096 Mar 18 15:12 ./
drwxr-xr-x 4 root root 4096 Mar 18 15:20 ../

可以发现容器中的文件已消失,这是 bind mountvolume 模式的主要区别。

  1. 宿主机文件更新,容器实时读取

    在宿主机 /webapp1 目录添加 index.html 文件,并通过浏览器访问容器,容器成功读取并显示宿主机的文件内容。

bash 复制代码
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# echo "Hello Bind Mount." >> index.html

通过浏览器访问容器时,可以看到宿主机的文件内容:

  1. 删除容器后,宿主机文件依然存在

    删除容器后,宿主机上的文件保持不变,说明 bind mount 的挂载不受容器生命周期的影响。

bash 复制代码
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# docker stop bind1
bind1
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# docker rm bind1
bind1
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# ll
total 12
drwxr-xr-x 2 root root 4096 Mar 18 15:32 ./
drwxr-xr-x 4 root root 4096 Mar 18 15:20 ../
-rw-r--r-- 1 root root   18 Mar 18 15:32 index.html

总结:

  • 使用 --mount 创建绑定卷时,容器会挂载宿主机的指定目录。
  • 容器中的挂载目录会覆盖容器内的原有内容,这是与 volume 模式的主要区别。
  • 宿主机文件的变化会立即反映到容器中,且容器删除后,宿主机文件仍然存在。

使用 -v 创建绑定卷

  1. 使用 -v 方式创建容器

    创建一个 nginx 容器,并将宿主机的 /webapp2 目录挂载到容器的 /usr/share/nginx/html 目录。需要注意的是,如果宿主机的 /webapp2 目录不存在,容器启动时不会报错。这是 -v--mount 方式的区别:-v 会自动创建挂载目录,而 --mount 需要宿主机目录存在。

bash 复制代码
docker run -d -p 8080:80 --name bind2 -v /data/myworkdir/fs/webapp2:/usr/share/nginx/html nginx:1.22.1
  1. 查看挂载信息

    使用 docker inspect 命令查看容器的挂载信息。

bash 复制代码
root@VM-8-2-ubuntu:/data/myworkdir/fs# docker inspect bind2
[
    {
        "Id": "b9631fa3b8a122012d2c876ffdd5826d80dbdb9336b967017d96061f98e5434a",
        "Created": "2025-03-18T10:20:52.43803812Z",
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/data/myworkdir/fs/webapp2",
                "Destination": "/usr/share/nginx/html",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
        ...
    }
]
  1. 进入容器终端查看挂载点

    进入容器终端,查看 /usr/share/nginx/html 目录,发现容器中的文件为空。同样,在宿主机上查看 /webapp2 目录,也没有文件。这是 bind mount 模式和 volume 模式的区别:容器内原有文件会被挂载目录的内容覆盖。

bash 复制代码
root@VM-8-2-ubuntu:/data/myworkdir/fs# docker exec -it bind2 ls /usr/share/nginx/html
root@VM-8-2-ubuntu:/data/myworkdir/fs# ll webapp2
total 8
drwxr-xr-x 2 root root 4096 Mar 18 15:20 ./
drwxr-xr-x 4 root root 4096 Mar 18 15:20 ../
  1. 在宿主机添加 index.html 文件

    在宿主机 /webapp2 目录下创建一个 index.html 文件,并写入内容。

bash 复制代码
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp2# echo "Hello Bind Mount in bind2" > index.html
  1. 通过浏览器访问

    通过浏览器访问容器时,能够看到容器已经读取并显示了宿主机的共享内容。

  2. 删除容器后,宿主机文件依然存在

    删除容器后,宿主机上的文件仍然存在,证明容器删除并不影响 bind mount 映射。

bash 复制代码
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp2# docker stop bind2
bind2
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp2# docker rm bind2
bind2
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp2# ll
total 12
drwxr-xr-x 2 root root 4096 Mar 18 15:32 ./
drwxr-xr-x 4 root root 4096 Mar 18 15:20 ../
-rw-r--r-- 1 root root   21 Mar 18 15:32 index.html

总结:

  • 使用 -v 创建绑定卷时,如果宿主机目录不存在,容器启动时不会报错。
  • 容器内的挂载目录内容会被宿主机目录内容覆盖,这是 bind mount 模式与 volume 模式的主要区别。
  • 宿主机文件的变化会实时反映到容器中,且容器删除后,宿主机的文件依然存在,bind mount 映射不会受到容器生命周期的影响。

绑定卷共享

  1. 启动两个绑定卷,绑定到宿主机的同一个目录:
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker run -d -p 8082:80 --name bind3 -v /data/myworkdir/fs/webapp1:/usr/share/nginx/html nginx:1.24.0
7e23e9cf779c21fa4c1cf1982953c79c13564876cd2bfce60fe1fefddb2d1ef5
ubuntu@VM-8-2-ubuntu:~$ docker run -d -p 8083:80 --name bind4 -v /data/myworkdir/fs/webapp1:/usr/share/nginx/html nginx:1.24.0
0794b4c804ca3ab938a5023756c59e515047821eaead81b69774a883e27b17a8
  1. 访问这两个页面可以查到相同的内容:
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ curl 127.0.0.1:8082
Hello Bind Mount.
ubuntu@VM-8-2-ubuntu:~$ curl 127.0.0.1:8083
Hello Bind Mount.
  1. 修改 index.html 后,再次访问两界面,可以发现 其index.html 都进行了修改,即实现了容器间的共享:
bash 复制代码
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# echo "changed bind mount" > index.html
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# curl 127.0.0.1:8082
changed bind mount
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# curl 127.0.0.1:8083
changed bind mount
  1. 最后不要忘记清理资源
bash 复制代码
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# docker stop bind2
bind2
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# docker stop bind3
bind3
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# docker rm bind2
bind2
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# docker rm bind3
bind3

七、临时卷 tmpfs

tmpfs 临时卷是位于内存中的卷,存储在容器和宿主机的外部。

tmpfs 的局限性:

  • 与卷和绑定挂载不同,tmpfs 挂载不能在多个容器之间共享。
  • 仅在 Linux 系统上运行 Docker 时支持此功能。

创建临时卷

方式一:使用 --tmpfs 参数创建

  • 功能:将临时卷挂载到容器指定目录。
  • 语法
bash 复制代码
  --tmpfs /app
  • 示例
bash 复制代码
  docker run -d \
  -it \
  --name tmptest \
  --tmpfs /app \
  nginx:1.22.1

方式二:使用 --mount 参数创建

  • 功能:完成目录的临时卷映射。
  • 语法
bash 复制代码
  --mount '<key>=<value>,<key>=<value>'
  • 关键参数

    • type:指定挂载类型,支持 bindvolumetmpfs
    • destinationdsttarget:容器中挂载的目标路径。
    • tmpfs-size:指定 tmpfs 挂载的大小(以字节为单位),默认没有限制。
    • tmpfs-mode:指定 tmpfs 的文件模式(以八进制表示),例如 7000770,默认 1777,全局可写。
  • 示例

bash 复制代码
  docker run -d \
  -it \
  --name tmptest \
  --mount type=tmpfs,destination=/app \
  nginx:latest

总结:

  • tmpfs 提供了一个高效的内存存储方式,适用于不需要持久化存储的临时数据。
  • 使用 --tmpfs--mount 都可以创建临时卷,选择方式依据需求而定。

操作案例

tmpfs 参数创建临时卷

  1. 创建临时卷并启动容器
bash 复制代码
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# docker container run --name tmpfs1 -d -p 8080:80 --tmpfs /usr/share/nginx/html nginx:1.24.0
a350249d8cf5efce441dac30059c8313aef9b6426ad4b8c9bb55a588a704f41a
  1. 进入容器可以发现 nginx 中的文件被覆盖了,即 tmpfs 会覆盖容器里的文件
bash 复制代码
root@VM-8-2-ubuntu:/data/myworkdir/fs/webapp1# docker exec -it tmpfs1 bash
root@a350249d8cf5:/# cd /usr/share/nginx/html/
root@a350249d8cf5:/usr/share/nginx/html# ls -l
total 0
  1. 添加首页并进行访问:
bash 复制代码
root@a350249d8cf5:/usr/share/nginx/html# echo "Hello docker from tmpfs" > index.html
root@a350249d8cf5:/usr/share/nginx/html# curl 127.0.0.1 :8080
Hello docker from tmpfs
  1. 停止容器并再次启动,进行查询后会发现tmpfs的内容完全消失了,即 tmpfs 的内容是存在内存中的:
bash 复制代码
root@VM-8-2-ubuntu:# docker stop tmpfs1
tmpfs1
root@VM-8-2-ubuntu:# docker start tmpfs1
tmpfs1
root@VM-8-2-ubuntu:# docker exec -it tmpfs1 bash
root@a350249d8cf5:/# ls -l /usr/share/nginx/html/
total 0
  1. 清理资源:
bash 复制代码
root@VM-8-2-ubuntu:/# docker stop tmpfs1
tmpfs1
root@VM-8-2-ubuntu:/# docker rm tmpfs1
tmpfs1

mount 创建临时卷

  1. 创建临时卷并启动容器
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker container run --name mtpfs1 -d -p 8080:80 --mount type=tmpfs,destination=/usr/share/nginx/html,tmpfs-size=1m nginx:1.24.0
817957a8e4bf52736f3e854efb3601a4b04f09be6d5247cff617ae6e14680d8e
  1. 进入容器可以发现nginx的内容被覆盖了,即 tmpfs 会覆盖容器
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker exec -it mtpfs1 bash
root@817957a8e4bf:/# ls
bin   dev		   docker-entrypoint.sh  home  lib64  mnt  proc  run   srv  tmp  var
boot  docker-entrypoint.d  etc			 lib   media  opt  root  sbin  sys  usr
root@817957a8e4bf:/# ls -l /usr/share/nginx/html
total 0
  1. 添加首页并进行访问:
bash 复制代码
root@817957a8e4bf:/usr/share/nginx/html# echo "hello mount from tmpfs" > index.html
root@817957a8e4bf:/usr/share/nginx/html# curl 127.0.0.1     
hello mount from tmpfs
  1. 剩下的步骤和 tmpfs 创建临时卷一样,就不再赘述了

tmpfs 失踪

  1. 创建一个普通的 docker 容器
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker run -d -it --name tmptest nginx:1.24.0
d4bb0cce7be8705d6e97d41e946b9c9e7d737416f6c8ff149e2d989f79ea7c2f
  1. 运行容器并写入文件:
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker exec -it tmptest bash
root@d4bb0cce7be8:/# mkdir -p /app
root@d4bb0cce7be8:/# echo 114 > /app/mylabel.txt
root@d4bb0cce7be8:/# exit
exit
  1. 在宿主机查找文件,是可以找到的,因为文件在容器的可写层
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ sudo find / -name mylabel.txt
/var/lib/docker/overlay2/c3cdefc3d23aa496e133c7a352028bcad779c1eebfa0f01e13097ae942bb24a7/merged/app/mylabel.txt
/var/lib/docker/overlay2/c3cdefc3d23aa496e133c7a352028bcad779c1eebfa0f01e13097ae942bb24a7/diff/app/mylabel.txt
  1. 创建一个临时卷并启动,
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker run -d --name tmptest2 --tmpfs /app nginx:1.24.0
10ce055a39d480fcdc00f00947c3eab4f981057021e61d9d8bfefe13bf59b4bc
ubuntu@VM-8-2-ubuntu:~$ docker exec -it tmptest2 bash
  1. 进入容器在 /app 目录下创建新文件 mynewlabel.txt
bash 复制代码
root@10ce055a39d4:/# ls app
root@10ce055a39d4:/# echo 514 > /app/mynewlabl.txt
root@10ce055a39d4:/# ls /app
mynewlabl.txt
root@10ce055a39d4:/# exit
exit
  1. 返回宿主机并查询 mynewlabel.txt ,发现找不到文件
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ sudo find / -name mynewlabel.txt
ubuntu@VM-8-2-ubuntu:~$ 
  1. 所以 tmpfs 的内容不是存储在容器的可写层里面的,最后释放容器
bash 复制代码
ubuntu@VM-8-2-ubuntu:~$ docker stop tmptest
tmptest
ubuntu@VM-8-2-ubuntu:~$ docker rm tmptest
tmptest
ubuntu@VM-8-2-ubuntu:~$ docker stop tmptest2
tmptest2
ubuntu@VM-8-2-ubuntu:~$ docker rm tmptest2
tmptest2

八、实战练习:Mysql 灾难恢复

练习目标:

本次练习旨在掌握挂载卷的使用,将 MySQL 的业务数据存储到外部。

操作步骤:

  1. 准备镜像

    首先,我们拉取 mysql:5.7 镜像。

    bash 复制代码
    ubuntu@VM-8-2-ubuntu:~$ docker pull mysql:5.7
    Using default tag: latest
    latest: Pulling from library/mysql
    7b36eddb0393: Pull complete
    9d96cf4d7877: Pull complete
    c06a2d8bb529: Pull complete
    7d30ccf68828: Pull complete
    169c64bfa9b4: Pull complete
    03f81c404364: Pull complete
    Digest: sha256:0b536be69f66e91e56ac8dbf45d5f55e8f393c19c22c6747e1ac9f4fbd37c2e0
    Status: Downloaded newer image for mysql:5.7
  2. 创建容器并挂载卷

    创建一个名为 mysql_demo 的容器,设置 MySQL 密码并将数据卷挂载到 /data/myworkdir/mysql-data 目录。

    bash 复制代码
    ubuntu@VM-8-2-ubuntu:~$ docker container run --name mysql_demo -e MYSQL_ROOT_PASSWORD=114514 -itd -v /data/myworkdir/mysql-data:/var/lib/mysql mysql:5.7

    解释

    • -e MYSQL_ROOT_PASSWORD=114514:设置 MySQL 根用户密码为 114514
    • -v /data/myworkdir/mysql-data:/var/lib/mysql:将宿主机的 /data/myworkdir/mysql-data 目录挂载到容器的 /var/lib/mysql,保存数据库数据。
  3. 查看容器挂载信息

    使用 docker container inspect 命令查看容器的挂载信息。

    bash 复制代码
    ubuntu@VM-8-2-ubuntu:~$ docker container inspect mysql_demo | grep "Mounts" -A 20
    "Mounts": [
        {
            "Type": "volume",
            "Name": "mysql-demo-mysql-data",
            "Source": "/data/myworkdir/mysql-data",
            "Destination": "/var/lib/mysql",
            "Mode": "rw",
            "RW": true,
            "Propagation": "rprivate"
        }
    ]
  4. 连接 MySQL 创建数据库和表

    连接到容器并进入 MySQL Shell,使用之前设置的密码 114514

    bash 复制代码
    ubuntu@VM-8-2-ubuntu:~$ docker container exec -it mysql_demo /bin/bash
    root@mysql_demo:/# mysql -u root -p
    Enter password: 114514
    Welcome to the MySQL monitor.  Commands end with ; or \g.
    Your MySQL connection id is 1
    Server version: 5.7.34 MySQL Community Server (GPL)

    创建 user 数据库和 student 表,并插入一些数据。

    bash 复制代码
    mysql> show databases;
    +--------------------+
    | Database           |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    +--------------------+
    4 rows in set (0.00 sec)
    
    mysql> create database user;
    Query OK, 1 row affected (0.01 sec)
    
    mysql> use user;
    Database changed
    
    mysql> create table student(sno char(3), sname varchar(20));
    Query OK, 0 rows affected (0.01 sec)
    
    mysql> insert into student values('1', '田所'),('2', '德川');
    Query OK, 2 rows affected (0.01 sec)
    
    mysql> select * from student;
    +-----+--------+
    | sno | sname  |
    +-----+--------+
    | 1   | 田所   |
    | 2   | 德川   |
    +-----+--------+
    2 rows in set (0.00 sec)
    
    mysql> exit
    Bye

    在上述步骤中,我们创建了一个 user 数据库,在其中创建了 student 表,并插入了两条数据。

  5. 查看宿主机上的数据卷内容

    查看挂载到宿主机的 mysql-data 目录,验证数据是否被持久化。

    bash 复制代码
    ubuntu@VM-8-2-ubuntu:~$ ll /data/myworkdir/mysql-data/
    total 96
    drwxr-xr-x  6 root root 4096 Mar 23 10:00 .
    drwxr-xr-x  3 root root 4096 Mar 23 09:50 ..
    drwxr-xr-x  2 root root 4096 Mar 23 10:00 3306
    drwxr-xr-x  2 root root 4096 Mar 23 10:00 lost+found
    -rw-r-----  1 root root  368 Mar 23 10:00 ib_logfile0
    -rw-r-----  1 root root  368 Mar 23 10:00 ib_logfile1
  6. 删除容器

    删除容器 mysql_demo,此时 MySQL 数据也会随容器一起清除。

    bash 复制代码
    ubuntu@VM-8-2-ubuntu:~$ docker stop mysql_demo
    mysql_demo
    ubuntu@VM-8-2-ubuntu:~$ docker rm mysql_demo
    mysql_demo
  7. 恢复数据

    通过再次运行容器并挂载相同的数据卷,数据可以恢复。

    bash 复制代码
    ubuntu@VM-8-2-ubuntu:~$ docker container run --name mysql_demo_new -e MYSQL_ROOT_PASSWORD=114514 -itd -v /data/myworkdir/mysql-data:/var/lib/mysql mysql:5.7
  8. 重新连接 MySQL 查看数据

    登录 MySQL,查看数据是否恢复。

    bash 复制代码
    ubuntu@VM-8-2-ubuntu:~$ docker exec -it mysql_demo_new mysql -u root -p
    Enter password: 114514
    Welcome to the MySQL monitor.  Commands end with ; or \g.
    Your MySQL connection id is 1
    Server version: 5.7.34 MySQL Community Server (GPL)
    
    mysql> show databases;
    +--------------------+
    | Database           |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    | user               |
    +--------------------+
    5 rows in set (0.00 sec)
    
    mysql> use user;
    Database changed
    
    mysql> show tables;
    +-----------------+
    | Tables_in_user  |
    +-----------------+
    | student         |
    +-----------------+
    1 row in set (0.00 sec)
    
    mysql> select * from student;
    +-----+--------+
    | sno | sname  |
    +-----+--------+
    | 1   | 田所   |
    | 2   | 德川   |
    +-----+--------+
    2 rows in set (0.00 sec)
    
    mysql> exit
    Bye

    数据成功恢复,student 表中的数据仍然存在。

  9. 释放空间

    删除新的容器以释放资源。

    bash 复制代码
    ubuntu@VM-8-2-ubuntu:~$ docker stop mysql_demo_new
    mysql_demo_new
    ubuntu@VM-8-2-ubuntu:~$ docker rm mysql_demo_new
    mysql_demo_new

九、关于 Volume 的一些问题

什么时候使用 Volume、Bind/Mount 和 tmpfs?

  • Volume

    Volume 是 Docker 容器与宿主机之间独立的存储区域,它位于宿主机的文件系统中。使用 Volume 时,Docker 会自动管理存储位置,不需要用户指定具体目录,适合不需要提前规划目录结构的场景。通常用于持久化容器数据,如数据库存储、日志文件等。

  • Bind Mount

    Bind Mount 完全依赖于宿主机的目录结构和操作系统。它将宿主机的指定目录直接挂载到容器中,因此需要提前规划好宿主机上的目录。适用于需要容器与宿主机之间共享数据,或需要直接访问宿主机文件系统的场景。例如,开发环境中容器需要访问主机代码目录,或者需要挂载特定配置文件时,使用 Bind Mount 是一个合适的选择。

  • tmpfs

    tmpfs 是一种将数据存储在内存中的临时存储方式,适用于不希望数据持久化到宿主机或容器可写层中的场景。使用 tmpfs 时,数据仅存在于容器生命周期内,当容器停止或删除时,数据会丢失。它适用于敏感数据存储、临时缓存等,不需要长期保存的数据。


存储卷在实际研发中的问题

  1. 跨主机使用问题

    Docker 存储卷依赖于宿主机的本地文件系统,每个宿主机上的存储卷只能在该主机上使用,无法跨主机共享。

    因此,当容器停止或删除后,虽然可以重新创建,但无法调度到其他主机。这是 Docker 默认存储卷的一个局限性。

    为了实现跨主机的存储共享,通常需要搭建共享存储(如 NFS),但这种解决方案对运维人员的能力有较高要求。为了解决存储和数据的分离问题,越来越多的分布式存储方案(如 S3、NFS 等)应运而生。

  2. 启动参数管理

    容器的启动选项相较于传统进程启动更加复杂,且容器在重启时常常无法记住上次启动时的参数。

    为了确保容器能够以相同的配置启动,通常需要将启动参数保存在配置文件中。这正是容器编排工具(如 Kubernetes)发挥作用的地方。

    虽然可以通过命令启动容器,并在文件中读取相关参数,但这种方式仅适用于单个容器的管理。对于需要管理几十或上百个容器的复杂应用,使用专业的容器编排工具(如 Kubernetes 或各大云厂商的企业版编排软件)是必不可少的。

  3. 复杂场景依赖运维经验

    对于有状态且需要持久化的集群化组件(如 MySQL 主从复制),其部署和维护往往需要丰富的运维知识和经验。

    例如,在部署、扩展、缩容或修复故障时,必须清楚集群规模、主从节点数量以及每个节点的具体配置。

    这种复杂场景的管理对运维人员提出了高要求,目前还没有完美的自动化工具能够完全替代人工干预。因此,在这些复杂场景中,运维经验仍然至关重要。

相关推荐
AugustShuai16 分钟前
服务器DNS失效
运维·服务器·dns·dns解析异常
code monkey.22 分钟前
【寻找Linux的奥秘】第四章:基础开发工具(下)
linux·运维·服务器
爬山算法28 分钟前
Dubbo(48)如何排查Dubbo的负载均衡问题?
运维·负载均衡·dubbo
还是鼠鼠1 小时前
Node.js 跨域 CORS 简单请求与预检请求的介绍
运维·服务器·vscode·中间件·node.js·express
alden_ygq7 小时前
k8s node inode被耗尽如何处理?
云原生·容器·kubernetes
爱知菜7 小时前
Windows安装Docker Desktop(WSL2模式)和Docker Pull网络问题解决
运维·docker·容器
月下雨(Moonlit Rain)9 小时前
Docker
运维·docker·容器
技术小甜甜10 小时前
[Dify] 使用 Docker 本地部署 Dify 并集成 Ollama 模型的详细指南
运维·docker·容器·dify
学习中的程序媛~10 小时前
主服务器和子服务器之间通过NFS实现文件夹共享
运维·服务器
小白也有IT梦10 小时前
解决 Ubuntu 上 Docker 安装与网络问题:从禁用 IPv6 到配置代理
ubuntu·docker·网络配置