Docker:存储卷

Docker:存储卷

    • 存储卷
    • 命令
      • [docker volume ls](#docker volume ls)
      • [docker volume create](#docker volume create)
      • [docker volume inspect](#docker volume inspect)
      • [docker volume rm](#docker volume rm)
      • [docker volume prune](#docker volume prune)
    • 分类

存储卷

Docker中,容器的文件存储结构如下:

容器时基于镜像产生的,一个镜像可以实例化出多个容器,为了节省镜像的存储消耗,并不是每个容器都会拷贝一份镜像,而是所有容器共用一份镜像。

为此,Docker采用联合文件系统来存储文件,在镜像内部,所有文件分为多层存储,每个容器创建时,创建一个容器层,在容器层内部修改文件,不会影响镜像。

比如说容器删除一个文件,容器不会去镜像内部删除镜像的某一层,而是新开一层标记某个文件已经删除。

上图中,容器删除了镜像中的文件,但是其实log.txt没有真的被删除,只是对于这个容器来说,被标记为删除了,其他容器依然可以看到log.txt

这种存储结构,可以减少很多的存储损耗,但是任何事物都有两面性,没有完美的解决方案。

  1. 持久化:如果容器被删除,那么整个容器层都直接销毁,数据无法持久化保存到操作系统
  2. 性能:每次容器读写,都要经过联合文件系统,性能极大降低

为此,Docker提供了另一种存储方式存储卷,用于存储需要持久化,或者IO频率高,对性能有要求的文件。

存储卷可以绕过联合文件系统,直接读写操作系统的文件系统。因为绕过联合文件系统,所以IO效率可以显著提高,另一方面,这些数据并不存储在容器层,而是存储在操作系统,就算容器被删除,容器层被销毁,存储卷也不会被销毁,可以持久化保存。

对于联合文件系统的更多详细信息,会在其他博客有专门讲解,本博客不再深入联合文件系统。


命令

docker volume ls

  • 功能:列出存储卷

语法:

bash 复制代码
docker volume ls [option]

选项:

  • -f:依据条件过滤
  • -q:仅显示名称

示例:

此处有三个存储卷,其中my_volume是有名称的,而前两个是没有名称的。DRIVER这一栏,是卷的存储驱动,绝大部分存储卷的驱动都是local,其可以满足大部分场景的需求。

如果加上-q选项,不会显示DRIVER这一列,只显示名称。

使用-f进行过滤:

-f可以对查询进行过滤,并且支持正则表达式,例如-f name=my*表示查询卷名以my开头的存储卷。

=


docker volume create

  • 功能:创建存储卷

语法:

bash 复制代码
docker volume create [option] [volume]

选项:

  • -d --driver:指定驱动,默认为local
  • --label:指定元数据

创建一个匿名存储卷:

创建卷时,如果不指定名称,那么创建的卷就是没有名字的匿名卷。

创建命名卷:

如果需要命名,那么命令的最后一个参数就是卷名。

指定元数据:

创建卷时,可以通过--label指定元数据,以键值对的形式。元数据可以帮助快速查询容器,比如通过-f选项可以指定元数据。


docker volume inspect

  • 功能:查看存储卷的详细信息

语法:

bash 复制代码
docker volume inspect [option] volume

此处test_vm_2的信息中,可以看到刚才指定的元数据TEST=2,以及卷名,存储驱动等等。


docker volume rm

  • 功能:删除存储卷

语法:

bash 复制代码
docker volume rm [option] volume [volume...]

选项:

  • -f:强制删除

对于有卷名的存储卷,可以通过卷名删除,如果是匿名的,只能复制一大串随机字符串来指定删除。

如果这个卷正在被容器使用,那么无法直接删除,此时需要通过-f强制删除。


docker volume prune

  • 功能:删除所有不使用的匿名存储卷

语法:

bash 复制代码
docker volume prune [option]

选项:

  • -f:强制删除

第一次查询,主机中有三个匿名卷,通过prune删除后,所有匿名卷都被删除了。过程中会进行一次确认[y/N],使用-f参数,则不会进行这次确认。


分类

以上所有操作,都没有把存储卷和容器关联起来,其实存储卷分为三种类型,不同的存储卷创建方式不同。

  • 数据卷:默认映射到宿主机的/var/lib/docker/volumes目录下,只需要在容器内部指定挂载点,而在宿主机的挂载点,docker自动完成
  • 绑定卷:由用户自己指定宿主机的一个文件或目录,共享到容器中
  • 临时卷:前两者在容器销毁后,都可以持久化保存。但是临时卷并不会持久化保存,容器删除后临时卷也会销毁。主要用于高性能IO存储临时数据的场景。

创建以上三种卷,主要在docker run运行容器时,通过-v或者--mount选项进行指定。

数据卷

-v语法:

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

参数:

  • name:绑定的现有卷的名称,可以省略,此时会创建一个新的匿名卷
  • directory:数据卷在容器内部的路径
  • option:可以指定ro表示read only,此时只有宿主机可以修改数据卷,容器只读

后续操作,都基于nginx镜像,在nginx镜像内部,路径/usr/share/nginx/html存放着ngxin的首页文件,以该目录为存储卷位置。

使用现有卷test_vm_1作为数据卷:

通过-v test_vm_1:/usr/share/nginx/html,表示把存储卷test_vm_1这个存储卷,挂载到容器的/usr/share/nginx/html路径下。

随后查询test_vm_1,找到其在宿主机的存储路径,该路径下多出两个文件50x.htmlindex.html,这就是nginx内部创建的文件,通过数据卷共享到宿主机了。

就算删除掉这个容器,存储卷内的内容依然存在。

创建匿名数据卷:

如果容器创建时,只指定挂载路径,但是没有指定存储卷名,那么就会创建一个匿名卷。以上例子中,创建了一个fb48...的匿名卷,进入其存储目录后,也出现了两个共享的html文件。

另外的,如果指定存储卷名时,存储卷不存在,那么也会自动创建一个新的命名卷。

--mount语法:

bash 复制代码
docker run --mount key=value [key=value ...]

参数:

  • type:存储卷类型,指定volume表示数据卷
  • stc:对于命名卷,表示卷名,对于匿名卷,此字段省略
  • dst:文件在容器的挂载路径
  • ro:只读方式挂载

多个参数以逗号,分隔。

通过--volume创建一个命名卷:

存储卷挂载命令如下:

bash 复制代码
--mount type=volume,src=test_vm_3,dst=/usr/share/nginx/html,ro

其中type=volume表示使用数据卷,src=test_vm_3表示使用的存储卷,dst=/usr/share/nginx/html指定容器中的挂载路径,ro表示只读,容器内部无法修改该文件。

进入容器内部修改文件:

通过docker exec进入容器后,尝试修改存储卷挂载的目录,此时提示Read-only file system,即这个容器对该目录只读,没有权限进行写入。

不论是-v还是--volume效果是一样的。


绑定卷

对于先前的数据卷,在宿主机的存储路径都是由Docker指定的,如果想要把宿主机的指定文件作为存储卷,共享到容器,就需要绑定卷

-v语法:

bash 复制代码
docker run -v path:directory[:option]
  • name:绑定卷在宿主机的路径
  • directory:绑定卷在容器内部的路径
  • option:可以指定ro表示read only,此时只有宿主机可以修改绑定卷,容器只读

除了path这个参数,其它的和数据卷相同。在数据卷中,使用name:来指派一个存储卷,或者不填写该选项,直接创建一个匿名卷。如果填入的是一个有效的宿主机路径path:,那么就会把宿主机的指定目录或文件,作为绑定卷与容器共享。

nginx容器创建一个绑定卷:

首先在当前目录创建一个index.html文件写入hello world,随后将该文件绑定到容器中,命令:

bash 复制代码
-v ./index.html:/usr/share/nginx/html/index.html

此处./index.html是一个有效的本机路径,/usr/share/nginx/html/index.html是容器内部的挂载点。与先前不同,先前指定的是.../html/这个目录,这次指定的是一个具体的.html文件,存储卷既可以共享目录,也可以共享文件。

随后通过docker exec进入容器,查看index.html,发现其内容是hello world。按理来说nginx的这个文件应该初始化为一个有效的html文件,但是最后被宿主机绑定卷的内容给覆盖了。这就是绑定卷与数据卷的区别之一。

绑定卷会把宿主机的文件覆盖容器的文件,哪怕容器的文件原先就有内容。如果宿主机中,绑定卷的内容为空,那么会把容器中的文件也覆盖为空,这会导致一定的数据丢失。但是对于数据卷来说,如果宿主机中内容为空,宿主机会把容器的内容拷贝到宿主机。

之前就可以看出,在数据卷中,把容器内部的50x.htmlindex.html拷贝到宿主机了。但是如果让绑定卷也绑定这个目录,那么宿主机不仅不会拷贝50x.htmlindex.html,还会删掉容器内部的50x.htmlindex.html

绑定卷创建完成后,宿主机和容器内部的文件就同步了,虽然刚创建时宿主机的文件优先级高,但是创建完成后,容器与宿主机对这个文件的操作权限是相同的,只要不指定readonly

--volume语法:

bash 复制代码
docker run --mount key=value [key=value ...]

参数:

  • type:存储卷类型,指定bind表示绑定卷
  • stc:宿主机的有效路径
  • dst:文件在容器的挂载路径
  • ro:只读方式挂载

多个参数以逗号,分隔。此处的type与数据卷不同,数据卷type=volume,绑定卷type=bind

通过--volume创建一个绑定卷:

绑定卷命令:

bash 复制代码
--mount type=bind,src=./html,dst=/usr/share/nginx/html

把宿主机的./html目录,绑定到容器的/usr/share/nginx/html目录中。ls查看宿主机的./html目录,发现内容为空,进入容器后查看/usr/share/nginx/html目录,依然为空。

原先这个目录应该有两个html文件,但是由于宿主机绑定卷目录为空,所以这两个文件被覆盖删除了。

另外的,绑定卷是无法通过docker volume ls进行查询的。但是可以在容器中查询到,通过docker inspect

mounts这一栏,可以看到存储卷的信息,类型为绑定卷bind,宿主机目录source,容器目录Destination"RW": true表示可读写。


临时卷

存储卷有两个功能,一个是进行持久化的保存,另一个是进行高效的IO。有的时候只需要高效IO,并不需要持久化,那么就可以使用临时卷。

临时卷为了进一步提高IO效率,对文件存储进行了进一步优化。数据卷和绑定卷都是存储在硬盘中的,但是临时卷存储在内存中,是一个内存级的文件,IO效率极高。但是由于内存不是持久化存储的,所以临时卷在容器停止时就会销毁,注意是停止stop时,而不是删除rm时。

注意点:

  1. 临时卷是Linux专有的,Windows等其它操作系统上的Docker,无法使用临时卷。
  2. 临时卷只能挂载目录,不能挂载文件

--tmpfs语法:

bash 复制代码
docker run --tmpfs path

只需要指定容器中的一个目录为临时卷即可,由于并不于主机互通,无需指定主机路径或者卷名。

创建一个临时卷:

此处把/usr/share/nginx/html目录指定为了临时卷,进入目录后,原先存在的两个html文件也被覆盖了,说明临时卷也会清空目录内的原有内容。

--mount语法:

bash 复制代码
docker run --mount key=value [key=value ...]

参数:

  • type:存储卷类型,指定tmpfs表示临时卷
  • dst:文件在容器的挂载路径
  • tmpfs-size:临时卷的限制大小
  • tmpfs-mode:挂载模式,以八进制形式指定目录权限,默认为1777

创建一个内存限制不超过1m的临时卷:

临时卷命令:

bash 复制代码
--mount type=tmpfs,dst=/usr/share/nginx/html,tmpfs-size=1m

进入容器后,创建一个5m的文件index.html

bash 复制代码
dd if=/dev/zero of=index.html bs=1M count=5

此处的dd命令,用于创建一个5m的空文件。

显示dd: error writing 'index.html': No space left on device表示创建失败,没有空间了。因为限制了临时卷最多使用1m,此处却创建了5m的文件。

最后通过ls -l查看文件,发现index.html的大小是1048576,也就是1024 * 1024 byte = 1m


相关推荐
时迁2471 小时前
【k8s】k8s是怎么实现自动扩缩的
云原生·容器·kubernetes·k8s
诡异森林。4 小时前
Docker--Docker网络原理
网络·docker·容器
ALex_zry4 小时前
Docker Macvlan网络配置实战:解决“network already exists“错误
网络·docker·php
IT小辉同学4 小时前
Docker如何更换镜像源提高拉取速度
spring cloud·docker·eureka
matrixlzp5 小时前
K8S Service 原理、案例
云原生·容器·kubernetes
GnixAij6 小时前
Docker SSH端口转发
docker·ssh
angushine6 小时前
让Docker端口映射受Firewall管理而非iptables
运维·docker·容器
玄明Hanko7 小时前
生产环境到底能用Docker部署MySQL吗?
后端·mysql·docker
玄明Hanko8 小时前
Quarkus+Docker最全面完整教程:手把手搞定Java云原生
后端·docker·云原生
SimonLiu0098 小时前
清理HiNas(海纳斯) Docker日志并限制日志大小
java·docker·容器