目录
引言
什么是存储卷?
为什么需要存储卷?
存储卷分类
管理卷 Volume
创建卷
绑定卷 bind mount
临时卷
管理卷、绑定卷、临时卷的生命周期
管理卷(Managed Volumes)
绑定卷(Bind Mounts)
临时卷(Temporary Volumes)
MySQL 灾难恢复
常见问题
存储卷在实际研发中带来了哪些问题
引言
Docker Volume(存储卷)是Docker提供的一种用于数据持久化和共享的机制。它允许你将数据存储在Docker容器外部,从而在容器生命周期之外保持数据的持久性。Docker Volume可以绕过Union File System,直接管理卷数据,从而提高I/O性能。
什么是存储卷?
存储卷就是将宿主机的本地文件系统中存在的某个目录直接与容器内部的文件系统上的某一目录建立绑定关系。这就意味着,当我们在容器中的这个目录下写入数据时,容器会将其内容直接写入到宿主机上与此容器建立了绑定关系的目录。
在宿主机上的这个与容器形成绑定关系的目录被称作存储卷。卷的本质是文件或者目录,它可以绕过默认的联合文件系统,直接以文件或目录的形式存在于宿主机上。
宿主机的 /data/web 目录与容器中的/container/data/web 目录绑定关系,然后容器中的进程向这个目录中写数据时,是直接写在宿主机的目录上的,绕过容器文件系统与宿主机的文件系统建立关联关系,使得 可以在宿主机和容器内共享数据库内容 ,让容器直接访问宿主机中的内容,也可以宿主机向容器写入内容, 容器和宿主机的数据读写 是同步的。
为什么能提高性能呢?
看此图
绕过Union File System可以提高I/O性能的原因在于Union File System本身的工作机制。Union File System是一种分层文件系统,它可以将多个目录挂载到同一个虚拟文件系统下。在Docker中,每个容器的文件系统通常都是由多个层(layer)组成的,这些层是通过Union File System合并在一起的。当容器进行文件I/O操作时,如果使用的是Union File System,那么Docker需要通过这种分层结构来管理文件的读写。这意味着每次文件操作都需要在多个层之间进行查找和合并,这会增加额外的开销,从而降低I/O性能。相比之下,Docker Volume直接在主机文件系统上创建和管理数据卷,绕过了Union File System的层级结构。这种方式消除了在多个层之间查找和合并文件的开销,因此可以提高I/O性能。这并不是因为Docker自动管理卷的引擎进行了优化,而是因为避免了Union File System的额外开销。Docker Volume通过直接在主机文件系统上操作,提供了一种更直接、更高效的数据管理方式。
为什么需要存储卷?
1. 数据丢失问题
容器按照业务类型,总体可以分为两类:
无状态的(数据不需要被持久化)
有状态的(数据需要被持久化)
显然,容器更擅长无状态应用。因为未持久化数据的容器根目录的生命周期与容器的生命周期一样,容器文件系统的本质是在镜像层上面创建的读写层,运行中的容器对任何文件的修改都存在于该读写层,当容器被删除时,容器中的读写层也会随之消失。
虽然容器希望所有的业务都尽量保持无状态,这样容器就可以开箱即用,并且可以任意调度,但实际业务总是有各种需要数据持久化的场景,比如 MySQL 、 Kafka 等有状态的业务。因此为了解决有状态业务的需求, Docker 提出了卷( Volume )的概念。
2. 性能问题
UnionFS 对于修改删除等,一般效率非常低,如果对一于 I/O 要求比较高的应用,如redis 在实现持化存储时,是在底层存储时的性能要求比较高。
3. 宿主机和容器互访不方便
宿主机访问容器,或者容器访问要通过 docker cp 来完成,应用很难操作
4. 容器和容器共享不方便
存储卷分类
目前 Docker 提供了三种方式将数据从宿主机挂载到容器中
volume docker 管理卷 ,默认映射到宿主机的 /var/lib/docker/volumes 目录下, 只需要在容器内指定容器的挂载点 是什么,而被绑定宿主机下的那个目录,是 由 容器引擎 daemon 自行创建一个空的目录,或者使用一个已经存在的目录,与存储卷建立存储关系,这种方式极大解脱用户在使用卷时的耦合关系,缺陷是用户无法指定那些使用目录,临时存储比较适合 ;
bind mount 绑定数据卷 ,映射到宿主机指定路径下,在 宿主机上的路径要人工的指定一个特定的路径 , 在容器中也需要指定一个特定的路径 , 两个已知的路径建立关 联关系
tmpfs mount 临时数据卷 ,映射到于宿主机内存中,一旦容器停止运行,tmpfs mounts 会被移除,数据就会丢失,用于高性能的临时数据存储。
管理卷 Volume
创建卷
存储卷可以通过命令方式创建,也可以在创建容器的时候通过 -v and --mount 指定。
方式一: Volume 命令操作
命令清单如下
命令 | 功能 |
docker volume create | 创建存储卷 |
docker volume inspect | 显示存储卷详细信息 |
docker volume ls | 列出存储卷 |
docker volume prune | 清理所有无用数据卷 |
docker volume rm | 删除卷,使用中的无法删除 |
docker volume create
功能
○ 创建存储卷
•
语法
Shell
docker volume create [OPTIONS] [VOLUME]
•
关键参数
○ -d, --driver : 指定驱动,默认是 local
○ --label : 指定元数据
•
样例
Shell
docker volume create my-vol
docker volume inspect
•
功能
○ 查看卷详细信息
•
语法
Shell
docker volume inspect [OPTIONS] VOLUME [VOLUME...]
•
关键参数
○ -f : 指定相应个格式,如 json
•
样例
Shell
docker volume inspect my-vol
docker volume ls
•
功能
○ 列出卷
•
语法
Shell
docker volume ls [OPTIONS]
•
关键参数
--format : 指定相应个格式,如 json,table
○ --filter,-f: 过滤
○ -q: 仅显示名称
•
样例
Shell
docker volume ls
docker volume rm
•
功能
○ 删除卷,需要容器不使用。
•
语法
Shell
docker volume rm [OPTIONS] VOLUME [VOLUME...]
•
关键参数
○ -f,--force: 强制删除
•
样例
Shell
docker volume rm hello
docker volume prune
•
功能
○ 删除不使用的本地卷
•
语法
Shell
docker volume prune [OPTIONS]
•
关键参数
○ --filter: 过滤
○ -f, --force : 不提示是否删除
•
样例
Shell
docker volume prune
方式二: -v 或者 --mount 指定
-v 和 --mount 都可以完成管理卷的创建
-v 参数
•
功能:
完成目录映射
•
语法
Shell
docker run -v name:directory[:options] .........
•
参数
○ 第一个参数:卷名称
○ 第二个参数:卷映射到容器的目录
○ 第三个参数:选项,如 ro 表示 readonly
样例
Shell
docker run -d \
--name devtest \
-v myvol2:/app \
nginx:latest
通过 docker inspect 可以看到
"Mounts": [
{
"Type": "volume",
"Name": "myvol2",
"Source": "/var/lib/docker/volumes/myvol2/_data",
"Destination": "/app",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
--mount 参数
•
功能:
完成目录映射
•
语法
Shell
--mount '<key>=<value>,<key>=<value>'
•
关键参数
○ type : 类型表示 bind , volume , or tmpfs
○ source , src :对于命名卷,这是卷的名称。对于匿名卷,省略此字段。
○ destination , dst,target :文件或目录挂载在容器中的路径
○ ro,readonly: 只读方式挂载
样例
采用 mount 创建 volume
Shell
docker run -d \
--name devtest \
--mount source=myvol2,target=/app \
nginx:latest
通过 docker inspect 可以看到
Shell
"Mounts": [
{
"Type": "volume",
"Name": "myvol2",
"Source": "/var/lib/docker/volumes/myvol2/_data",
"Destination": "/app",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
方式三: Dockerfile 匿名卷
通过 Dockerfile 的 VOLUME 可以创建 docker 管理卷。这个我们后续在 Dockerfile的文章 中详细讲解。
我们也可以通过 dockerfile 的 VOLUME 指令在镜像中创建 Data Volume,这样只要通过该镜像创建的容器都会存在挂载点,但值得注意的是通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,而是由 docker 随机生成的
需要注意的是,多个容器可以挂接同一个卷进行通信
docker container run --name nginx2 -d -p 80:80 - v test_volume2 : /usr/share/nginx/html nginx:1.22.1
docker container run --name nginx3 -d -p 8080:80 - v test_volume2 : /usr/share/nginx/html nginx:1.22.1
可以看到,容器的卷目录一致(前提都是一个镜像所实例化的容器),这样修改某个容器的某个卷,可以实现同步修改。
这种方法的优点是可以方便地在多个容器之间共享数据,但它也有一些潜在的风险。例如,如果一个容器被破坏或受到安全威胁,那么其他共享同一个卷的容器也可能受到影响。因此,在使用共享卷时,需要确保所有容器的安全性,并定期备份数据以防止数据丢失。
绑定卷 bind mount
-v 和 -mount 都可以完成绑定卷的创建
创建卷
-v 参数创建卷
•
功能:
完成卷映射
•
语法
Shell
docker run -v name:directory[:options] .........
•
参数
○ 第一个参数: 宿主机目录,这个和管理卷是不一样的
○ 第二个参数:卷映射到容器的目录
○ 第三个参数:选项,如 ro 表示 readonly
样例
Shell
docker run -d -it --name devtest -v "(pwd)"/target:/app nginx:latest
--mount 参数创建绑定卷
•
功能:
完成目录映射
•
语法
Shell
--mount '<key>=<value>,<key>=<value>'
•
关键参数
○ type : 类型表示 bind , volume , or tmpfs
○ source , src : 宿主机目录,这个和管理卷是不一样的 。
○ destination , dst,target :文件或目录挂载在容器中的路径
○ ro,readonly: 只读方式挂载
样例
docker run -d \
-it \
--name devtest \
--mount type=bind,source="$(pwd)"/target,target=/app \
nginx:latest
需要注意的是
"Mounts": [
{
"Type": "bind",
"Source": "/data/myworkdir/fs/webapp1",
"Destination": "/usr/share/nginx/html",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
进入容器的终端,查看挂载点目录 , 和在宿主机上查看里面都是没有文件
root@139-159-150-152:/data/myworkdir/fs# docker exec -it bind1 ls
/usr/share/nginx/html
root@139-159-150-152:/data/myworkdir/fs# ll webapp1
total 8
drwxr-xr-x 2 root root 4096 Mar 16 13:39 ./
drwxr-xr-x 9 root root 4096 Mar 16 13:39 ../
可以发现容器该目录本身存在的文件消失不见, 这是 bind mount 模式和 volume 模
式最大的不同点
原因:
Bind mount是将宿主机上的一个目录或文件直接挂载到容器内的一个指定目录。当你使用bind mount时, 容器内的指定目录会被宿主机上的目录所覆盖。也就是说,容器启动时,容器内该目录下的任何现有文件都会被宿主机上的目录内容所替换。
临时卷
它使用宿主机的内存(包括/tmp目录或ramdisk)来存储数据,而不是使用宿主机或容器的文件系统。这种挂载方式通常用于存储临时文件,并且在容器停止时,这些数据不会被持久化
方式一:指定 --tmpfs 创建
•
功能:
完成临时卷映射
•
语法
Shell
--tmpfs /app
•
样例
Shell
docker run -d \
-it \
--name tmptest \
--tmpfs /app \
nginx:1.22.1.
在这个例子中,/app
是容器内的目录,它将被挂载到宿主机的内存中。这个目录在容器内部的表现就像一个普通的文件系统,但实际上它的数据是存储在宿主机的内存中的。
方式二: --mount 指定参数创建
功能:
完成目录映射
•
语法
Shell
--mount '<key>=<value>,<key>=<value>'
•
关键参数
○ type : 类型表示 bind , volume , or tmpfs
○ destination , dst,target :挂载在容器中的路径
○ tmpfs-size : tmpfs 挂载的大小(以字节为单位)。默认无限制。
○ tmpfs-mode : tmpfs 的八进制文件模式。例如, 700 或 0770 。默认为 1777 或全局可写。
•
样例
Shell
docker run -d \
-it \
--name tmptest \
--mount type=tmpfs,destination=/app \
nginx:latest
管理卷、绑定卷、临时卷的生命周期
在Docker中,卷(Volume)是用来持久化数据的重要机制。Docker提供了多种类型的卷,包括管理卷(Managed Volumes)、绑定卷(Bind Mounts)和临时卷(Temporary Volumes)。每种卷的生命周期管理方式不同,下面分别介绍:
管理卷(Managed Volumes)
管理卷是由Docker直接管理的卷,它们独立于容器的生命周期,具有以下特点:
- 创建:当你在容器启动时指定了一个管理卷,Docker会在宿主机上创建一个目录(通常在
/var/lib/docker/volumes/
目录下),并在容器内部挂载这个目录。 - 使用:管理卷可以在多个容器之间共享,即使其中一个容器停止或被删除,卷中的数据也不会丢失。
- 持久性:管理卷的数据是持久的,除非你显式地删除这个卷。
- 删除:你可以使用
docker volume rm
命令来删除不再需要的管理卷。如果你删除了一个容器,但未删除与之关联的管理卷,那么这个卷仍然存在,并且可以在其他容器中使用。
绑定卷(Bind Mounts)
绑定卷是将宿主机上的一个目录或文件挂载到容器内部。它们的生命周期与宿主机上的目录或文件紧密相关:
- 创建:绑定卷不需要在Docker中创建,因为它们直接使用宿主机上已有的目录或文件。
- 使用:当你启动容器并指定一个绑定卷时,Docker会将宿主机上的指定路径挂载到容器的指定路径。
- 持久性:绑定卷的持久性取决于宿主机上相应目录或文件的管理。如果宿主机上的目录或文件被删除,绑定卷也会失效。
- 删除:绑定卷本身没有“删除”的概念,因为它们只是宿主机上的目录或文件的映射。当你停止或删除容器时,绑定卷并不会被删除,除非你显式地在宿主机上删除了相应的目录或文件。
临时卷(Temporary Volumes)
临时卷是一种特殊的卷,它们在容器重启时不会被保留。临时卷的生命周期与容器的生命周期紧密相关:
- 创建:临时卷在容器启动时自动创建,并在容器内部挂载。
- 使用:临时卷只能在单个容器内部使用,不能在多个容器之间共享。
- 持久性:临时卷的数据在容器重启时会丢失。一旦容器停止,临时卷中的数据就会被删除。
- 删除:临时卷不需要显式删除。当容器停止时,Docker会自动清理临时卷。
MySQL 灾难恢复
通过卷的使用,将 mysql 的业务数据存储到外部
使用 MySQL 5.7 的镜像创建容器并创建一个普通数据卷 mysql-data 用来保存容器中产生的数据。
root@139-159-150-152:~# docker container run --name mysql-demo -e MYSQL_ROOT_PASSWORD=bite -itd -v /data/myworkdir/mysql-data:/var/lib/mysql mysql:5.7
重启,这个时候怎么恢复呢,我们再次启动我们的运行命令,确保目录映射一致就能找回我们的数据了
常见问题
1. 什么时候用 Volume ,什么时候用 bind 、 tmpfs ?
volume : volume 是 docker 的宿主机文件系统一部分,用于不需要规划具体目录的场景
bind : bind mount 完全是依赖于主机的目录结构和操作系统,用于目录需要提前规划, 比如 mysql 的目录需要个空间大的,其他服务有不占用的时候,用 volume 就不太合适了
tmpfs :用于敏感文件存储,文件不想存储的宿主机和容器的可写层之中
存储卷在实际研发中带来了哪些问题
1. 跨主机使用
docker 存储卷是使用其所在的宿主机上的本地文件系统目录,也就是宿主机有一块磁盘,这块磁盘并没有共享给其他的 docker 主机,容器在这宿主机上停止或删除,是可以重新再创建的,但是不能调度到其他的主机上,这也是 docker 本身没有解决的问题,所以 docker 存储卷默认就是 docker 所在主机的本地,但是自己搭建一个共享的 NFS来存储 docker 存储的数据,也可以实现, 但是这个过程强依赖于运维人员的能力。所以未来应用的存储和数据往往分离,越来越多的分布式存储方案出现,如 s3 系列,nfs 等。
2. 启动参数未知
容器有一个问题,一般与进程的启动不太一样,就是容器启动时选项比较多,如果下次再启动时,很容器会忘记它启动时的选项,所以最好有一个文件来保存容器的启动,这就是容器编排工具的作用。
一般情况下,是使用命令来启动操作 docker,但是可以通过文件来读,也就读文件来启动,读所需要的存储卷等,但是它也只是操作一个容器,如果要几十上百个容器操作,就需要 专业的容器编排工具 这种一般像开源的 k8s ,各个云厂商也有自己的企业版编排软件。
3. 复杂场景仍然需要运维
对于有状态要持久的集群化组件,如 mysql 的主从。部署维护一个 Mysql 主从需要运维知识、经验整合进去才能实现所谓的部署,扩展或缩容,出现问题后修复,必须要了解集群的规模有多大,有多少个主节点,有多少个从节点,主节点上有多少个库,这些都要一清二楚,才能修复故障,这些就 强依赖于运维经验 这种复杂的场景往往还是需要人力,很难有完美的工具出现。