实验二:Docker存储配置与管理
容器与非持久化数据
非持久化数据是不需要保存的那些数据,容器本地存储中的数据就属于这种类型。容器创建时会创
建非持久化存储,这是容器全部文件和文件系统保存的地方。
默认情况下,在容器内创建的所有文件都存储在可写容器层,文件系统的改动都发生在容器层,这
意味着存在以下问题:
非持久化数据从属于容器,生命周期与容器相同,会随着容器的除而被删除
当该容器不再运行时,数据不会持久保存,如果另一个进程需要,则可能很难从该容器中获取数据
容器的可写层与运行容器的 Docker主机紧密耦合,无法轻松地将数据转移到其他位置
写入容器的可写层需要Docker存储驱动管理文件系统。存储驱动使用Linux内核提供的联合文件系统,其性能不如直接写入主机文件系统的Docker卷
容器与持久化数据
持久化数据是需要保存的数据,如客户信息、财务、计划、审计日志,以及某些应用日志数据,docker 通过将主机中的文件系统挂载到容器中供容器访问,从而实现持久化数据存储,这就是容器的外存储。即使容器删除之后,这些文件仍然存在。Docker目前支持卷和绑定挂载这两种挂载类型来实现容器的持久化数据存储。
卷是在Docker中进行持久化数据存储的最佳方式。如果希望自己的容器数据持久化保留下来,可以将数据存储在卷上。卷又称数据卷,本质上是 Docker 主机文件系统中的目录或文件,它能够直接被挂载到容器的文件系统中。卷与容器是解耦的,因此可以独立地创建并管理卷,并且卷并未与任意容器生命周期绑定。用户可以停止或删除一个关联了卷的容器,但是卷并不会被删除。可以将任意数量卷装入容器,多个容器也可以共享一个或多个卷。
绑定挂载是Docker 早期版本就支持的挂载类型。绑定挂载性能高,但它们需要指定主机文件系统指定路径,从而限制了容器的可移植性
卷和绑定挂载这两种外部存储都绕过了联合文件系统,其读写操作会绕过存储驱动,并以本地主机存取速度运行。这里以绑定挂载为例说明外部存储与本地存储的关系,其中一个docker主机运行两个容器,每个容器都位于 Docker 主机本地存储区(/var/lib/docker/.… )各自的空间,由存储驱动支持。Docker 主机上的/data 目录绑定挂载到两个容器中,可以被两个容器共享。容器挂载点目录与主机上的/data 目录之间采用虚线连接,这是为了表明它们之间是非耦合的关系。外部存储位于 Docker主机本地存储区域之外,进一步增强了它们不受存储驱动控制的独立性。当容器被删除时,外部存储中的任何数据都会保留在Docker主机上
挂载类型
往容器中挂载的外部文件系统有多种类型。除了卷和绑定挂载之外,如果在 Linux 系统上运行
Docker,也可以使用tmpfs挂载,只息这种类型仅支持非持久化数据,不能用于持久化数据存储。无论选择哪种挂载类型,从容器内部角度看,数据并没有什么不同,这些数据在容器的文件系统中都会显示为目录或文件
卷、绑定挂载和 tmpfs 挂载这3种挂载类型的区别是数据在 Docker 主机中存放的位置不同:
卷
卷存储在主机文件系统中由 Docker 管理的位置,在 Linux 主机上该位置默认就是var/lib/docker/ volumes目录,它受到保护,非Docker进程是不能修改该部分的。卷是Docker中持久存储容器的应用数据的最佳方式。卷也支持使用卷驱动,卷驱动可以让用户将数据存储在远程主机或云提供商处,以及其他可能的位置
可以以命名方式或匿名方式挂载卷,匿名卷(Anonymous Volumes)在首次挂载到容器中时没有指定明确的名称,因此 Docker 会为其随机指定一个在当前 Docker 主机中唯一的名称,除了名称外,命名卷(Named Volumes)和匿名卷的其他特性相同。
卷由 Docker 创建并管理,卷适合以下应用场景:
在多个正在运行的容器之间共享数据,如果没有显式创建卷,则卷会在首次被挂载到容器上时创建。当容器被删除时,卷依然会存在。多个容器可以同时挂载同一个卷,挂载模式可以是读写模式或只读模式,只有显式删除卷时,卷才会被删除
当Docker主机不能保证具有特定目录结构时,卷有助于将Docker主机的配置与容器运行时解耦。
当需要将容器的数据存储到远程主机或云提供商处,而不是本地时
当需要在两个 Docker主机之间备份、恢复或迁移数据时。可以在停止使用卷的容器之后,备份卷所在的目录(如varlib/docker/volumes/<卷名>)
绑定挂载
绑定挂载可以存储到主机系统的任意位置,甚至会存储到一些重要的系统文件或目录中。Docker
主机上的非Docker进程或Docker容器都可以随时对它们进行修改
与卷相比,绑定挂载功能更受限,绑定挂载性能高,但它们依赖于具有特定目录结构的主机文件系
统,不能使用Docker命令直接管理绑定挂载。绑定挂载还允许访问敏感文件。
绑定挂载适合以下应用场景:
在主机和容器之间共享配置文件。Docker 向容器提供 DNS 解折时默认采用的就是这种方式,即将主机上的/etc/resolv.conf 文件挂载到每个容器中
在Docker 主机上的开发环境和容器之间共享源代码或构建工件(Artifacts)。例如,可以将项目管理工具Maven的target目录挂载到容器中,每次在 Docker 主机上构建 Maven 项目时,容器会访问重新构建的工件。以这种方式使用 Docker进行开发时,生产环境中的Dockerfile会直接将生产就绪的工件复制到镜像中,而不是依赖一个绑定挂载
当Docker主机上的目录结构保证与容器要求的邦定挂载一致时
如果正在开发新的Docker化应用程序,则应考虑使用命名卷,而不要使用绑定挂载
tmpfs挂载
tmpfs 挂载仅限于运行 Linux 操作系统的 Dooker 主机使用,它只存储在主机的内存中,不会被写
到主机的文件系统中,因此不能持久保存容器的应用数据。在不需要将数据持久保存到主机或容器中时tmpfs 挂载最合适。出于安全考虑,或者要保证容器的性能,应用程序需要写入大量非持久化数据时,这种挂载很适用。
如果容器产生了非持久化数据,那么可以考虑使用tmpfs挂载避免将数据永久存储到任何位置,并目通过避免写入容器的可写层来提高容器的性能
Docker 卷管理语法
docker volume 是Docker卷的管理命令,基本语法如下:
docker volume 子命令
子命令用于完成具体的卷管理任务,docker volume 命令列举如下:
docker volume create:创建一个新的卷
docker volume Is:列出本地 Docker 主机上的卷
docker volume inspect:查看卷的详细信息,包括卷在Docker 主机文件系统中的具体位置
docker volume prune:删除未被容器或者服务副本使用的全部卷
docker volume rm:删除未被使用的指定卷
容器的文件系统挂载语法
使用 docker run 或 docker create 命令的相关选项将外部文件系统挂载到容器中
早期的 Docker 版本中,-v(--volume)选项用于独立容器,而--mount选项用于集群服务。卷和绑定挂载都可以通过这两个选项挂载到容器中,只是二者的语法存在细微差异。对于 tmpfs挂载,可以使用--tmpfs 选项
在Docker 17.06 或更高版本中,建议对于所有的容器或服务的绑定挂载、卷或tmpfs挂载都使用
--mount选项,因为其语法更清晰、定制更详细。从 Docker 17.06 开始,也可以将--mount 选项用于独立容器。--mount与-V最大的不同在于:-v的语法是将所有选项组合在一个字段中,而--mount 的语法是将它们分开,--mount 采用若干键值对的写法以支持更多的设置选项。-v 写法更加简洁,目前仍然被广泛使用
-V选项的基本语法:
-v[主机中的源:] 容器中的目标[:<选项>]
该选项包括由冒号(:)分隔的3个字段。这些字段必须按照正确的顺序排列,第1个字段表示挂载源(来自主机的文件系统),第2个字段是挂载目标(容器中的挂载点,可以是目录或文件路径,必须采用绝对路径的形式),第3个字段是可选的,是一个以逗号分隔的选项列表,如ro表示只读
--mount选项的基本语法
--mount<键>=<值>,<键>=<值>,.…
该选项的参数由多个由逗号分隔的键值对组成。--mount 选项的语法比-V 选项更冗长,但键的排
列顺序并不重要,并且键值更易于理解。其主要的键列举如下:
type:指定要挂载的类型,值可以是bind(绑定挂载) volume(卷)或tmpfs。默认使用 volume
source(或src):指定挂载源。对于卷,这里是卷名;对于绑定挂载,则为主机上的目录或文件
destination(或dst、target):指定挂载目标,即容器中的挂载点,必须采用绝对路径的形式
readonly:指定只读选项,表示源以只读方式挂载到容器中
其他键:可以被多次指定,由若干键值对组成。卷和绑定挂载有不同的键
实现
创建一个卷并让容器挂载
创建一个卷:
默认情况下,Docker创建新卷时采用内置的local驱动,使用该驱动的本地卷只能被所在主机上的容器使用
列出卷驱动和卷名称:
查看该卷的详细信息:
创建卷时会在主机上的Docker根目录(Linux主机上默认为/var/lib/docker)下的volumes子目录中生成一个以卷名命名的子目录,在该子目录中再创建名为_data的子目录作为卷的数据存储路径
启动一个容器,并将test-vol卷挂载到容器中的/world目录:
docker并不支持在容器中使用相对路径的挂载点目录,挂载点目录必须从根目录开始
在容器中列出目录:
会发现容器有一个名为world的目录,这个目录实际指向的是上述的test-vol卷
退出容器:
执行下列命令验证卷被正确挂载到容器中:
查看Mounts(挂载)部分信息:
这表明挂载的是一个卷,显示了正确的源和目标,并且是可读写的
删除该卷:
报错,因为卷正在被使用,虽然容器停止了,但是仍然处于容器的生命周期内,会占用卷
先删除容器,再删除该卷即可成功
挂载NFS文件系统:
使用-o,--opt等选项定制卷的创建
本地驱动 文件类型为NFS NFS服务器地址,可读写 NFS目录路径
多个容器可以使用同一个卷,这对需要访问共享数据的容器特别有用
启动容器时自动创建卷
启动带有卷的容器时,如果卷尚不存在,则docker会自动创建这个卷,即在docker根目录下的volumes子目录中生成相应的目录结构
使用容器填充卷
如果容器启动时挂载已经存在并拥有包含数据的卷,则容器不会将其挂载点目录的数据复制到该卷,而是直接使用该卷中的数据。如果容器启动时挂载空白卷(卷已存在但没有任何数据)或者自动创建新卷,而容器在挂载点目录中已有文件或目录,则该挂载点目录的内容会被传播(复制)到卷中,也就是将容器中挂载点目录的数据填充到卷中。其他容器挂载并使用该卷时可以访问其中预先填充的内容。下面给出一个实例验证容器填充卷
执行以下命令启动一个 Nginx 容器,并使用容器的usr/share/nginx/html目录(Nginx服务器存储其网页内容的默认位置)的内容填充新的卷 nginx-vol:
docker run -d --name=ngintest --mount source=nginx-vol,destination=/usr/share/nginx/html nginx
查看该卷的详细信息:
执行以下命令查看主机上该卷所在目录的内容,可以发现容器已经填充了卷:
启动另一个容器挂载该卷,以使用其中预先填充的内容
docker run -it --name=othercntr --mount source=nginx-vol,destination=/nginx ubuntu /bin/bash
依次执行下面的命令删除容器和卷:
使用只读卷
同一个卷可以由多个容器挂载,并且可以让某些容器执行读写操作,而让另一部分容器只能执行读操作,设置只读操作后,是没有权限对卷中的数据进行修改的,只有Docker主机有权修改数据
通过在容器中挂载点后面的选项列表(默认为空)中添加只读参数,将卷以只读模式挂载到容器的目录中去:
使用--mount选项:
docker run -d --name nginxtest --mount source=nginx-vol,destination=/usr/share/nginx/html,readonly nginx:latest
使用docker inspect nginxtest验证卷挂载是否成功:
停止并删除nginxtest容器,然后删除nginx-vol卷:
使用-v:
docker run -d --name=nginxtest -v nginx-vol:/usr/share/nginx/html:ro nginx
”ro“表示只读,”RW“为false,表示只读
使用匿名卷
使用--mount:
不定义source
docker run -it --mount destination=/world ubuntu/bin/bash
匿名卷不是没有名称,而是Docker自动为匿名卷生成一个UUID(通用唯一识别码)作为名称
使用docker volume rm命令删除匿名卷,必须指定匿名卷的完整的UUID
要自动删除匿名卷,应在创建容器时使用--rm
使用-v:
docker run -it -v /world ubuntu /bin/bash
绑定挂载主机上的目录
通过绑定挂载可以将Docker 主机上现有的目录挂载到容器的目录中,需要挂载的目录可以由主机上的绝对路径引用
这里给出一个使用绑定挂载构建源代码的示例。假如源代码保存在 source 目录中,当构建源代码时,工件保存到另一个目录 source/target中。要求工件在容器的/app目录中可用,且每次在开发主机上构建源代码时容器都可以访问新的工件。可以使用以下命令将 target/目录绑定挂载到容器的/app/目录中。从 source 目录中运行此命令,$(pwd)子命令表示Linux 主机上的当前工作目录
准备源代码目录并切换到 source 目录,这里只是作为示范,没有添加具体源代码内容:
docker run -it -d --name devtest --mount type=bind,source="$(pwd)"/target,target=/app nginx
--mount指明挂载类型bind,挂载源和挂载目标必须使用绝对路径
-v选项:
docker run -d -it --name devtest -v "$(pwd)"/target:/app nginx
使用docker inspect devtest查看容器的详细信息:
停止并删除容器:
使用--mount实现只读绑定挂载:
docker run -it -d --name devtest --mount type=bind,source="$(pwd)"/target,target=/app,readonly nginx
绑定挂载主机上的文件
绑定挂载文件主要用于主机与容器之间共享配置文件
将主机上的/etc/localtime挂载到容器中,可以让容器的时区设置与主机保持一致:
docker run --rm -it -v /etc/localtime ubuntu /bin/bash
绑定挂载主机上不存在的目录或文件
-v:会在主机上自动创建一个目录,对于不存在的文件,创建的也是一个目录
Docker会在启动容器之前在主机上创建一个/doesnt/exist目录:
docker run --rm -v/doesnt/exist:/foo -w /foo -it ubuntu bash
--mount:Docker非但不会自动创建目录,反而会报错
docker run --rm --mount type=bind,source=/doesnt/exist:/foo,target=/foo -w /foo -it ubuntu bash
绑定挂载到容器中的非空目录
如果将主机上的目录绑定挂载到容器上的非空目录,则容器挂载的目录中的现有内容会被绑定挂载
(主机上的目录)所遮盖,被遮盖的目录和文件不会被删除或更改,但在使用绑定挂载时不可访问,这就像将文件保存到Linux主机上的/mnt 目录中,然后将USB驱动器挂载到mnt目录中,在知载USB 驱动器之前,USB 驱动器的内容会遮盖/mnt目录中的内容,访问/mnt 目录存取的是USB 驱动器的内容。卸载USB 驱动器之后,访问/mnt目录看到的是该目录本身的内容
无论主机上的目录是否为空,绑定挂载到容器中的非空目录都会发生遮盖的情况。一定要注意,这
种方式与 Docker 卷是完全不同的。对于卷来说,只有卷中存在内容,挂载卷的容器目录才会被遮盖而使用该卷中的内容
这里给出比较极端的示例,用主机上的/tmp目录替换容器的/usr目录的内容,如下所示。在大多数
情况下,这会产生一个没有用处的容器
容器虽然创建了,但是无法工作,删除这个容器
备份、恢复和迁移数据卷
可以通过卷容器( Volume Container)实现卷的备份、恢复和迁移。卷容器又称数据卷容器,是一
种特殊的容器,专门用来将卷(也可以是绑定挂载)提供给其他容器挂載。使用docker run 或dooker create 命令创建容器时可通过--volumes-from 选项基于卷容器创建一个新的容器, 并挂载卷容器提供的卷
创建卷容器:
创建一个名为dbstore的卷容器,挂在一个匿名卷(/dbdata)
docker create -v /dbdata --name dbstore busybox /bin/sh
备份卷容器:
docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
启动一个新的容器并从dbstore容器中挂载卷
将本地主机的当前目录挂载为/backup
传送一个命令,将dbdata卷的内容打包为/backup/backup.tar文件
从备份中恢复卷容器:
docker run -v /dbdata --name dbstore2 ubuntu /bin/bash
在新容器的数据卷中将备份文件解压缩:
docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar--strip 1"