Bootstrap

Docker:存储卷


存储卷

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

在这里插入图片描述

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

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

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

在这里插入图片描述

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

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

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

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

在这里插入图片描述

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

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


命令

docker volume ls

  • 功能:列出存储卷

语法:

docker volume ls [option]

选项:

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

示例:

在这里插入图片描述

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

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

使用-f进行过滤:

在这里插入图片描述

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

=


docker volume create

  • 功能:创建存储卷

语法:

docker volume create [option] [volume]

选项:

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

创建一个匿名存储卷:

在这里插入图片描述

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

创建命名卷:

在这里插入图片描述

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

指定元数据:

在这里插入图片描述

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


docker volume inspect

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

语法:

docker volume inspect [option] volume

在这里插入图片描述

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


docker volume rm

  • 功能:删除存储卷

语法:

docker volume rm [option] volume [volume...]

选项:

  • -f:强制删除

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

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


docker volume prune

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

语法:

docker volume prune [option]

选项:

  • -f:强制删除

在这里插入图片描述

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


分类

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

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

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

数据卷

-v语法:

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语法:

docker run --mount key=value [key=value ...]

参数:

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

多个参数以逗号,分隔。

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

在这里插入图片描述

存储卷挂载命令如下:

--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语法:

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

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

nginx容器创建一个绑定卷:

在这里插入图片描述

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

-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语法:

docker run --mount key=value [key=value ...]

参数:

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

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

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

在这里插入图片描述

绑定卷命令:

--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语法:

docker run --tmpfs path

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

创建一个临时卷:

在这里插入图片描述

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

--mount语法:

docker run --mount key=value [key=value ...]

参数:

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

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

在这里插入图片描述

临时卷命令:

--mount type=tmpfs,dst=/usr/share/nginx/html,tmpfs-size=1m

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

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


;