Docker数据持久化

本章将和大家分享Docker中如何实现数据的持久化。废话不多说,下面我们直接进入主题。

一、什么是数据卷

我们都知道在Docker中,容器的数据读写默认发生在容器的存储层,当容器被删除时其上的数据将会丢失。如果想实现数据的持久化,就需要将容器和宿主机建立联系(将数据从宿主机挂载到容器内),通俗的说,数据卷就是在容器和宿主机之间实现数据共享。

数据卷是一个位于宿主机上的目录或文件,可以被Docker容器挂载使用。它允许容器和宿主机之间,或者容器和容器之间共享数据。数据卷的设计目的是为了数据的持久化,即数据不会随着容器的删除而丢失,完全独立于容器的生存周期。

数据卷是由 Docker 管理的持久化数据存储区域,用于在容器之间共享和持久化数据。Docker 会在主机的特定位置(通常是 /var/lib/docker/volumes/ 目录下)创建数据卷目录。

1、使用方式

数据卷可以通过Docker命令行工具进行创建、挂载、查看和管理。常用的命令包括:

  • docker run -v /宿主机路径:/容器内路径 ...:在创建容器时挂载数据卷。
  • docker volume create 数据卷名称:显式创建一个数据卷。
  • docker volume ls:列出所有数据卷。
  • docker volume inspect 数据卷名称:查看数据卷的详细信息。
  • docker volume rm 数据卷名称:删除数据卷。

2、匿名挂载和具名挂载

  • 匿名挂载:没有指定数据卷名称,只指定了容器内路径。
  • 具名挂载:在-v或--mount标志中指定了数据卷的名称和容器内路径。

二、Docker支持的三种数据挂载方式

Docker提供了三种不同的方式将数据从宿主机挂载到容器中:卷挂载(Volume Mounts)、绑定挂载(Bind Mounts)和临时文件系统挂载(tmpfs Mounts)。

  • volume:Docker管理宿主机文件系统的一部分(/var/lib/docker/volumes),是Docker默认存储数据方式。
  • bind mounts:可以存储在宿主机系统的任意位置。将宿主机的任意位置的文件或者目录挂载到容器中。
  • tmpfs mounts:挂载存储在宿主机系统的内存中,不会写入宿主机的文件系统。

三、卷挂载(Volume Mounts)

卷挂载是Docker提供的一种持久化存储方式,它将容器内的数据存储在宿主机上的一个目录里。这个目录由Docker管理,通常位于 /var/lib/docker/volumes/ 这个路径下。当容器被删除后,数据卷中的数据不会被删除,实现了数据的持久化。卷挂载支持具名数据卷挂载(Named Volumes)和匿名数据卷挂载(Anonymous Volumes)这两种方式。

1、具名数据卷挂载

特点:具名数据卷在创建时会被分配一个用户指定的名称,这个名称在Docker主机上是唯一的。这使得数据卷的管理变得更为直观和方便。

创建方式:可以通过docker volume create命令显式创建具名数据卷,或者在docker run命令中通过-v或--mount选项并指定一个名称(而非路径)来隐式创建。

示例:

  • 显式创建:docker volume create my-named-volume
  • 隐式创建并挂载:docker run -d --name my-container -v my-named-volume:/app/data nginx 或 docker run -d --name my-container --mount source=my-named-volume,target=/app/data nginx

优势:具名数据卷独立于容器的生命周期,即使容器被删除,数据卷也会保留在Docker主机上,除非显式删除数据卷。这使得数据持久化和容器迁移变得更加容易。

语法示例:

# 创建一个名为nginx_volume的数据卷(命名卷)
docker volume create nginx_volume

# 查看当前所有数据卷
docker volume ls

# 查看数据卷的详细信息
docker volume inspect nginx_volume

# 启动容器时将数据卷挂载到容器内的 /usr/share/nginx/html 目录
docker run -d -p 8090:80 --mount type=volume,source=nginx_volume,target=/usr/share/nginx/html --name nginx-container nginx:latest
或
docker run -d -p 8090:80 -v nginx_volume:/usr/share/nginx/html --name nginx-container nginx:latest

# 查看docker容器列表
docker ps -a  #所有容器列表(包含存活和退出容器)
docker ps -l   #列出当前运行的最后一个容器

# 查看容器具体信息
docker inspect nginx-container  #容器ID或名称

演示:

[root@localhost ~]# docker volume create nginx_volume
nginx_volume
[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     nginx_volume
[root@localhost ~]# docker volume inspect nginx_volume
[
    {
        "CreatedAt": "2024-09-06T23:28:09+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/nginx_volume/_data",
        "Name": "nginx_volume",
        "Options": null,
        "Scope": "local"
    }
]
[root@localhost ~]# docker run -d -p 8090:80 -v nginx_volume:/usr/share/nginx/html --name nginx-container nginx:latest
eab6771135908196d70df10b604c5566b14571b52d806b205c8607737a85b370
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE          COMMAND                   CREATED          STATUS          PORTS                                   NAMES
eab677113590   nginx:latest   "/docker-entrypoint.…"   12 seconds ago   Up 11 seconds   0.0.0.0:8090->80/tcp, :::8090->80/tcp   nginx-container
[root@localhost ~]# docker inspect nginx-container
......

其中 "Mountpoint": "/var/lib/docker/volumes/nginx_volume/_data" 表示这个卷在宿主机上的挂载点(即:数据卷的工作目录)。Docker会将这个目录下的数据挂载到容器内的指定位置,供容器使用。这里的 _data 目录是Docker在创建卷时自动创建的,用于存储实际的数据。

验证数据卷是否挂载成功:

[root@localhost ~]# cd /var/lib/docker/volumes/nginx_volume/_data
[root@localhost _data]# echo "hello world" >> test.html
[root@localhost _data]# ll
总用量 12
-rw-r--r--. 1 root root 497 8月  12 22:21 50x.html
-rw-r--r--. 1 root root 615 8月  12 22:21 index.html
-rw-r--r--. 1 root root  12 9月   6 23:42 test.html
[root@localhost _data]# curl 192.168.4.250:8090/test.html
hello world
[root@localhost _data]# docker ps -l
CONTAINER ID   IMAGE          COMMAND                   CREATED          STATUS          PORTS                                   NAMES
eab677113590   nginx:latest   "/docker-entrypoint.…"   19 minutes ago   Up 19 minutes   0.0.0.0:8090->80/tcp, :::8090->80/tcp   nginx-container
[root@localhost _data]# docker exec -it eab677113590 /bin/bash
root@eab677113590:/# cd /usr/share/nginx/html
root@eab677113590:/usr/share/nginx/html# ls
50x.html  index.html  test.html

我们在宿主机数据卷里新增了一个test.html文件,进入到Docker容器内部的指定目录下也可以看到该文件,并且通过浏览器也能正常访问到该文件,证明我们的数据卷挂载成功了。

命名卷是数据卷的一种特殊形式,具有明确的名称,可以更容易地管理和引用。

2、匿名数据卷挂载

特点:匿名数据卷在创建时没有明确的名称,Docker会自动为其分配一个随机ID作为标识。这使得它们的管理相对复杂,因为需要通过其他方式(如docker volume ls)来查找和识别。

创建方式:通常是在docker run命令中使用-v或--mount选项时,只指定容器内的挂载点,而不指定数据卷的名称或主机路径。Docker会自动为这样的挂载点创建一个匿名数据卷。

示例:docker run -d --name my-container -v /app/data nginx 或 docker run -d --name my-container --mount type=volume,target=/app/data nginx(注意,这里虽然使用了--mount但没有指定source,因此会创建匿名卷)

注意:由于匿名数据卷没有明确的名称,因此在管理和维护上可能会更加困难。特别是当需要删除不再使用的数据卷时,可能需要额外的步骤来识别和删除它们。因此非必要不推荐使用该方式挂载。

语法示例:

# 使用匿名数据卷挂载启动一个nginx容器,Docker会自动创建一个匿名数据卷并将其挂载到容器内的 /usr/share/nginx/html 这个路径上
docker run -d -p 8091:80 --mount type=volume,target=/usr/share/nginx/html --name nginx-container-2 nginx:latest
或
docker run -d -p 8091:80 -v /usr/share/nginx/html --name nginx-container-2 nginx:latest

演示:

[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     nginx_volume
[root@localhost ~]# docker run -d -p 8091:80 -v /usr/share/nginx/html --name nginx-container-2 nginx:latest
dfbe28444ded76b621e4cf61df67336b2ce3d837445e5244ed0de3b9e928de5f
[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     161adf8452b212bbd9a41a2715f6152bd0d9963c011d4d505311b4beddee102b
local     nginx_volume
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE          COMMAND                   CREATED          STATUS                     PORTS                                   NAMES
dfbe28444ded   nginx:latest   "/docker-entrypoint.…"   29 seconds ago   Up 28 seconds              0.0.0.0:8091->80/tcp, :::8091->80/tcp   nginx-container-2
eab677113590   nginx:latest   "/docker-entrypoint.…"   20 hours ago     Exited (255) 2 hours ago   0.0.0.0:8090->80/tcp, :::8090->80/tcp   nginx-container

上面那个名字很长的数据卷就是匿名数据卷。

四、绑定挂载(Bind Mounts)

Docker 的绑定挂载(Bind Mounts)允许你将宿主机上的一个目录或文件挂载到容器内部的一个目录或文件上。这种方式非常适合在开发环境中使用,因为它允许你在容器外编辑文件,同时容器内可以看到文件的变化。

语法示例:

# 启动nginx容器,将宿主机的 /opt/nginx 目录挂载到容器内的 /usr/share/nginx/html 目录
docker run -d -p 8090:80 --mount type=bind,source=/opt/nginx,target=/usr/share/nginx/html --name nginx-container nginx:latest
或
docker run -d -p 8090:80 -v /opt/nginx:/usr/share/nginx/html --name nginx-container nginx:latest

演示:

在宿主机 /opt 目录下创建一个 nginx 文件夹,并在 nginx 文件夹里面添加一个 test.html 文件:

[root@localhost ~]# cd /opt
[root@localhost opt]# mkdir nginx
[root@localhost opt]# cd ./nginx/
[root@localhost nginx]# echo "hello world" >> test.html
[root@localhost nginx]# ll
总用量 4
-rw-r--r--. 1 root root 12 9月   7 23:48 test.html

启动nginx容器,将宿主机的 /opt/nginx 目录挂载到容器内的 /usr/share/nginx/html 目录:

[root@localhost nginx]# docker run -d -p 8092:80 --mount type=bind,source=/opt/nginx,target=/usr/share/nginx/html --name nginx-container-2 nginx:latest
468e94b76c55b4df2ccf4d0039940e1aa03e74e9d031dfcc0b1e6f03471024c1

进入Docker容器内部,查看容器内 /usr/share/nginx/html 目录下的文件列表:

[root@localhost nginx]# docker ps -a
CONTAINER ID   IMAGE          COMMAND                   CREATED         STATUS                     PORTS                                   NAMES
468e94b76c55   nginx:latest   "/docker-entrypoint.…"   2 minutes ago   Up 2 minutes               0.0.0.0:8092->80/tcp, :::8092->80/tcp   nginx-container-2
eab677113590   nginx:latest   "/docker-entrypoint.…"   25 hours ago    Exited (255) 6 hours ago   0.0.0.0:8090->80/tcp, :::8090->80/tcp   nginx-container
[root@localhost nginx]# docker exec -it nginx-container-2 /bin/bash
root@468e94b76c55:/# cd /usr/share/nginx/html
root@468e94b76c55:/usr/share/nginx/html# ls
test.html

可以发现并没有 nginx 默认的 index.html 和 50x.html 文件,只有来自宿主机上面的 test.html 文件。

可以使用 exit 命令退出容器:

root@468e94b76c55:/usr/share/nginx/html# exit
exit
[root@localhost nginx]#

注意:

1、如果你使用 Bind mounts 挂载宿主机目录到一个容器中的非空目录,那么此容器中的非空目录中的文件会被隐藏,容器访问这个目录时能够访问到的文件均来自于宿主机目录下的文件。

2、-v 宿主机目录路径必须以 / 或 ~/ 开头,否则Docker会将其当成是 Volume Mounts 而不是 Bind Mounts 。

五、临时文件系统挂载(tmpfs Mounts)

tmpfs挂载(也称为内存文件系统挂载)是一种特殊的挂载类型,它将数据存储在容器的内存中而不是磁盘上。tmpfs主要用于需要快速读写操作的场景,或者是那些需要在容器启动时清空数据的场景。

tmpfs mount只在Linux主机内存中持久化,是临时性的。当容器停止,tmpfs mount会被移除,通常用于临时存放敏感文件。

语法示例:

# 运行容器并绑定临时数据卷
docker run -d -p 8090:80 --mount type=tmpfs,target=/usr/share/nginx/html --name nginx-container nginx:latest

演示:

[root@localhost ~]# docker run -d -p 8093:80 --mount type=tmpfs,target=/usr/share/nginx/html --name nginx-container-3 nginx:latest
cb971be9d9316c300e74155f3069de5a7639ece74d5cb63638c0f018b4b9f0bd
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE          COMMAND                   CREATED         STATUS                      PORTS                                   NAMES
cb971be9d931   nginx:latest   "/docker-entrypoint.…"   6 seconds ago   Up 5 seconds                0.0.0.0:8093->80/tcp, :::8093->80/tcp   nginx-container-3
eab677113590   nginx:latest   "/docker-entrypoint.…"   38 hours ago    Exited (255) 19 hours ago   0.0.0.0:8090->80/tcp, :::8090->80/tcp   nginx-container
[root@localhost ~]# docker exec -it nginx-container-3 /bin/bash
root@cb971be9d931:/# cd /usr/share/nginx/html
root@cb971be9d931:/usr/share/nginx/html# ls
root@cb971be9d931:/usr/share/nginx/html# echo "hello world" >> test.html
root@cb971be9d931:/usr/share/nginx/html# ls
test.html
root@cb971be9d931:/usr/share/nginx/html# exit
exit
[root@localhost ~]# curl 192.168.4.250:8093/test.html
hello world
[root@localhost ~]#

注意事项:

  • tmpfs 中的数据在容器停止或重启时会被清除。
  • tmpfs 的大小受到宿主机可用内存的限制。
  • tmpfs 适用于需要高速读写操作的场景,如缓存或临时文件存储。
  • 由于 tmpfs 存储在内存中,它有助于提高安全性,因为数据不会永久驻留在磁盘上。
  • 如果 tmpfs 中的数据量很大,可能会导致容器启动时加载较慢。

六、三种存储方式适用场景

volume mount:

  • 多个容器间共享数据。
  • 宿主机不保证存在固定目录结构。
  • 持久化数据到远程主机或者云存储而非本地。
  • 需要备份、迁移、合并数据时。停止container,将volume整体复制,用于备份、迁移、合并等。

bind mount:

  • 主机与容器共享配置文件,如docker将宿主机文件/etc/resov.conf文件bind mount到容器上,两者会使用相同的DNS服务器。
  • 共享源代码或build artifacts(比如将Maven的target/目录挂载到容器中,每次在Docker主机中build Maven工程时,容器能够访问到那些rebuilt artifacts)。
  • 当 docker主机中的文件或目录结构和容器需要一致时。

tmpfs mount:

  • 既不想将数据存于主机,又不想存于容器中时(这可以是出于安全的考虑,或当应用需要写大量非持久性的状态数据时为了保护容器的性能)。

 

至此本文就全部介绍完了,如果觉得对您有所启发请记得点个赞哦!!!