Docker 数据卷学习笔记

1. 什么是数据卷

数据卷(volume)是一个虚拟目录,作为容器内目录与宿主机目录之间映射的桥梁。

1.1 数据卷的作用(以 Nginx 为例)

Nginx 中有两个关键目录:

  • html:放置静态资源
  • conf:放置配置文件

容器运行的 Nginx 所有文件都在容器内部,需利用数据卷将这两个目录与宿主机目录关联,方便操作。

1.2 数据卷的工作原理

  • 创建两个数据卷:conf、html
  • Nginx 容器内部的 conf 目录和 html 目录分别与两个数据卷关联
  • 数据卷 conf 和 html 分别指向宿主机的 /var/lib/docker/volumes/conf/_data 目录和 /var/lib/docker/volumes/html/_data 目录

通过这种关联(称为挂载),操作宿主机的对应目录就等同于操作容器内的对应目录。例如,将静态资源放入宿主机的 /var/lib/docker/volumes/html/_data 目录,就可被 Nginx 代理。

1.3 数据卷的默认存储路径

/var/lib/docker/volumes 是默认存放所有容器数据卷的目录,其下再根据数据卷名称创建新目录,格式为 /数据卷名/_data

1.4 为什么使用数据卷而非直接指向宿主机目录

  • 直接指向宿主机目录会与宿主机强耦合,若切换环境,宿主机目录可能改变,而容器一旦创建,目录挂载无法修改,会导致容器无法正常工作。
  • 容器指向数据卷(一个逻辑名称),数据卷再指向宿主机目录,可避免强耦合。若宿主机目录改变,只需修改数据卷与宿主机目录之间的映射关系即可。

不过,由于数据卷目录较深,不好寻找,通常也允许让容器直接与宿主机目录挂载而不使用数据卷。

2. 数据卷命令

命令 说明 文档地址
docker volume create 创建数据卷 docker volume create
docker volume ls 查看所有数据卷 docs.docker.com
docker volume rm 删除指定数据卷 docs.docker.com
docker volume inspect 查看某个数据卷的详情 docs.docker.com
docker volume prune 清除数据卷 docker volume prune

2.1 注意事项

容器与数据卷的挂载要在创建容器时配置,对于创建好的容器,不能设置数据卷。且创建容器的过程中,数据卷会自动创建。

3. 教学演示环节

3.1 Nginx 的 html 目录挂载

  1. 首先创建容器并指定数据卷,通过 -v 参数来指定数据卷:

    1
    docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx
  2. 查看数据卷:

    1
    docker volume ls

    结果:

    1
    2
    3
    DRIVER    VOLUME NAME
    local 29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f
    local html
  3. 查看数据卷详情:

    1
    docker volume inspect html

    结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [
    {
    "CreatedAt": "2024-05-17T19:57:08+08:00",
    "Driver": "local",
    "Labels": null,
    "Mountpoint": "/var/lib/docker/volumes/html/_data",
    "Name": "html",
    "Options": null,
    "Scope": "local"
    }
    ]
  4. 查看 /var/lib/docker/volumes/html/_data 目录:

    1
    ll /var/lib/docker/volumes/html/_data

    可以看到与 nginx 的 html 目录内容一样,结果如下:

    1
    2
    3
    总用量 8
    -rw-r--r--. 1 root root 497 12月 28 2021 50x.html
    -rw-r--r--. 1 root root 615 12月 28 2021 index.html
  5. 进入该目录,并随意修改 index.html 内容:

    1
    2
    cd /var/lib/docker/volumes/html/_data
    vi index.html
  6. 打开页面,查看效果。

  7. 进入容器内部,查看 /usr/share/nginx/html 目录内的文件是否变化:

    1
    docker exec -it nginx bash

3.2 MySQL 的匿名数据卷

  1. 查看 MySQL 容器详细信息:

    1
    docker inspect mysql

    关注其中 .Config.Volumes 部分和 .Mounts 部分。

    • .Config.Volumes 部分:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      {
      "Config": {
      // ... 略
      "Volumes": {
      "/var/lib/mysql": {}
      }
      // ... 略
      }
      }

      可以发现这个容器声明了一个本地目录,需要挂载数据卷,但是数据卷未定义,这就是匿名卷。

    • .Mounts 部分:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      {
      "Mounts": [
      {
      "Type": "volume",
      "Name": "29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f",
      "Source": "/var/lib/docker/volumes/29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f/_data",
      "Destination": "/var/lib/mysql",
      "Driver": "local",
      }
      ]
      }

      其中几个关键属性:

      • Name:数据卷名称。由于定义容器未设置容器名,这里是匿名卷自动生成的名字,一串 hash 值。
      • Source:宿主机目录
      • Destination:容器内的目录

      上述配置是将容器内的 /var/lib/mysql 目录,与数据卷 29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f 挂载。于是在宿主机中就有了 /var/lib/docker/volumes/29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f/_data 目录。这就是匿名数据卷对应的目录,其使用方式与普通数据卷没有差别。

  2. 查看该目录下的 MySQL 的数据文件:

    1
    ls -l /var/lib/docker/volumes/29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f/_data

注意:每一个不同的镜像,创建容器后内部有哪些目录可以挂载,可参考 DockerHub 对应的页面。

4. 挂载本地目录或文件

数据卷的目录结构较深,操作不便。很多情况下,会直接将容器目录与宿主机指定目录挂载。

4.1 挂载语法

  • 挂载本地目录:-v 本地目录:容器内目录
  • 挂载本地文件:-v 本地文件:容器内文件

4.2 注意事项

本地目录或文件必须以 /./ 开头,如果直接以名字开头,会被识别为数据卷名而非本地目录名。

  • 例如:-v mysql:/var/lib/mysql 会被识别为一个数据卷叫 mysql,运行时会自动创建这个数据卷。
  • 例如:-v ./mysql:/var/lib/mysql 会被识别为当前目录下的 mysql 目录,运行时如果不存在会创建目录。

4.3 教学演示:MySQL 容器本地目录挂载

删除并重新创建 mysql 容器,完成本地目录挂载:

  • 挂载 /root/mysql/data 到容器内的 /var/lib/mysql 目录
  • 挂载 /root/mysql/init 到容器内的 /docker-entrypoint-initdb.d 目录(初始化的 SQL 脚本目录)
  • 挂载 /root/mysql/conf 到容器内的 /etc/mysql/conf.d 目录(MySQL 配置文件目录)

课前已准备好 mysql 的 init 目录和 conf 目录,以及对应的初始化 SQL 脚本(hmall.sql)和配置文件(hm.cnf,配置了 MySQL 的默认编码为 utf8mb4),并将整个 mysql 目录上传至虚拟机的 /root 目录下。

操作步骤:

  1. 删除原来的 MySQL 容器:

    1
    docker rm -f mysql
  2. 进入 root 目录:

    1
    cd ~
  3. 创建并运行新 mysql 容器,挂载本地目录:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    docker run -d \
    --name mysql \
    -p 3306:3306 \
    -e TZ=Asia/Shanghai \
    -e MYSQL_ROOT_PASSWORD=123 \
    -v ./mysql/data:/var/lib/mysql \
    -v ./mysql/conf:/etc/mysql/conf.d \
    -v ./mysql/init:/docker-entrypoint-initdb.d \
    mysql
  4. 查看 root 目录,发现 ~/mysql/data 目录已经自动创建好了:

    1
    ls -l mysql

    结果:

    1
    2
    3
    4
    总用量 4
    drwxr-xr-x. 2 root root 20 5月 19 15:11 conf
    drwxr-xr-x. 7 polkitd root 4096 5月 19 15:11 data
    drwxr-xr-x. 2 root root 23 5月 19 15:11 init

    查看 data 目录,会发现里面有大量数据库数据,说明数据库完成了初始化:bash

    1
    ls -l data
  5. 查看 MySQL 容器内数据:

    • 进入 MySQL:

      1
      docker exec -it mysql mysql -uroot -p123
    • 查看编码表:

      1
      show variables like "%char%";

      结果(编码是 utf8mb4):plaintext

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      +--------------------------+--------------------------------+
      | Variable_name | Value |
      +--------------------------+--------------------------------+
      | character_set_client | utf8mb4 |
      | character_set_connection | utf8mb4 |
      | character_set_database | utf8mb4 |
      | character_set_filesystem | binary |
      | character_set_results | utf8mb4 |
      | character_set_server | utf8mb4 |
      | character_set_system | utf8mb3 |
      | character_sets_dir | /usr/share/mysql-8.0/charsets/ |
      +--------------------------+--------------------------------+
  6. 查看数据:

    • 查看数据库:

      1
      show databases;

      结果(hmall 是黑马商城数据库):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      +--------------------+
      | Database |
      +--------------------+
      | hmall |
      | information_schema |
      | mysql |
      | performance_schema |
      | sys |
      +--------------------+
      5 rows in set (0.00 sec)
    • 切换到 hmall 数据库:

      1
      use hmall;
    • 查看表:

      1
      show tables;

      结果:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      +-----------------+
      | Tables_in_hmall |
      +-----------------+
      | address |
      | cart |
      | item |
      | order |
      | order_detail |
      | order_logistics |
      | pay_order |
      | user |
      +-----------------+
    • 查看 address 表数据:

      1
      select * from address;

      结果:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      +----+---------+----------+--------+----------+-------------+---------------+-----------+------------+-------+
      | id | user_id | province | city | town | mobile | street | contact | is_default | notes |
      +----+---------+----------+--------+----------+-------------+---------------+-----------+------------+-------+
      | 59 | 1 | 北京 | 北京 | 朝阳区 | 13900112222 | 金燕龙办公楼 | 李佳诚 | 0 | NULL |
      | 60 | 1 | 北京 | 北京 | 朝阳区 | 13700221122 | 修正大厦 | 李佳红 | 0 | NULL |
      | 61 | 1 | 上海 | 上海 | 浦东新区 | 13301212233 | 航头镇航头路 | 李佳星 | 1 | NULL |
      | 63 | 1 | 广东 | 佛山 | 永春 | 13301212233 | 永春武馆 | 李晓龙 | 0 | NULL |
      +----+---------+----------+--------+----------+-------------+---------------+-----------+------------+-------+
      4 rows in set (0.00 sec)