热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

浅谈docker学习之docker数据卷(volume)

浅谈docker学习之docker数据卷(volume)-1.什么是数据卷volume为了了解什么是DockerVolume,首先我们需要明确Docker内的文件系统是如何工作的。

1.什么是数据卷volume

为了了解什么是Docker Volume,首先我们需要明确Docker内的文件系统是如何工作的。Docker镜像被存储在一系列的只读层。当我们开启一个容器,Docker读取只读镜像并添加一个读写层在顶部。如果正在运行的容器修改了现有的文件,该文件将被拷贝出底层的只读层到最顶层的读写层。在读写层中的旧版本文件隐藏于该文件之下,但并没有被不破坏 - 它仍然存在于镜像以下。当Docker的容器被删除,然后重新启动镜像时,将开启一个没有任何更改的新的容器 - 这些更改会丢失。此只读层及在顶部的读写层的组合被Docker称为Union File System(联合文件系统)。
为了能够保存(持久)数据以及共享容器间的数据,Docker提出了Volumes的概念。很简单,volumes是目录(或者文件),它们是外部默认的联合文件系统或者是存在于宿主文件系统正常的目录和文件。

2.为什么使用数据卷volume

Docker的镜像是由一系列的只读层组合而来,当启动一个容器的时候,Docker加载镜像的所有只读层,并在最上层加入一个读写层。这个设计使得Docker可以提高镜像构建、存储和分发的效率,节省了时间和存储空间,然而也存在如下问题。

(1)容器中的文件在宿主机上存在形式复杂,不能在宿主机上很方便的对容器中的文件进行访问

(2)多个容器之间的数据无法共享

(3)当删除容器时,容器产生的数据将丢失

为了解决这些问题,Docker引入了数据卷(volume)机制。volume是存在一个或多个容器中的特定文件或文件夹,这个目录能够独立于联合文件系统的形式在宿主机中存在,并为数据的共享与持久提供一下便利。

(1)volume在容器创建时就初始化,在容器运行时就可以使用其中的文件

(2)volume能在不同的容器之间共享和重用

(3)对volume中的数据的操作会马上生效

(4)对volume中数据操作不会影响到镜像本身

(5)volume的生存周期独立于容器的生存周期,即使删除容器,volume仍然会存在,没有任何容器使用的volume也不会被Docker删除

3.如何使用数据卷 

3.1 从容器挂载volume(-v /path)

在使用docker run创建新容器的时候,可以使用-v 标签为容器添加数据卷volume,以下用法是从容器中的某个文件夹创建volume,如果容器中指定的文件夹不存在,会自动生成


在上面的概念中,有说道,宿主机应该会有一个文件夹绑定挂载到容器中的volume挂载点,那默认的宿主机上的文件夹在哪呢,使用docker inspect命令,查看下容器详情(CRT令起一个SSH终端)

[root@localhost ~]# docker inspect volume-test01 
[ 
 { 
  "Id": "81a74152e6f45a3f780ac7cdc37c9a089814f9a70aad1d27747093ca3c3dae3e", 
  "Created": "2016-08-25T07:48:55.942949334Z", 
  "Path": "/bin/bash", 
  "Args": [], 
  "State": { 
   "Status": "running", 
   "Running": true, 
   "Paused": false, 
   "Restarting": false, 
   "OOMKilled": false, 
   "Dead": false, 
   "Pid": 10199, 
   "ExitCode": 0, 
   "Error": "", 
   "StartedAt": "2016-08-25T07:48:56.777918888Z", 
   "FinishedAt": "0001-01-01T00:00:00Z" 
  }, 
  "Image": "sha256:4fd21defa24c8c07b3689b267a63d563ca0e26ef931b329fc3f3d46efb5bba2d", 
  "ResolvConfPath": "/var/lib/docker/containers/81a74152e6f45a3f780ac7cdc37c9a089814f9a70aad1d27747093ca3c3dae3e/resolv.conf", 
  "HostnamePath": "/var/lib/docker/containers/81a74152e6f45a3f780ac7cdc37c9a089814f9a70aad1d27747093ca3c3dae3e/hostname", 
  "HostsPath": "/var/lib/docker/containers/81a74152e6f45a3f780ac7cdc37c9a089814f9a70aad1d27747093ca3c3dae3e/hosts", 
  "LogPath": "", 
  "Name": "/volume-test01", 
  "RestartCount": 0, 
  "Driver": "devicemapper", 
  "MountLabel": "system_u:object_r:svirt_sandbox_file_t:s0:c47,c332", 
  "ProcessLabel": "system_u:system_r:svirt_lxc_net_t:s0:c47,c332", 
  "AppArmorProfile": "", 
  "ExecIDs": null, 
  "HostConfig": { 
   "Binds": null, 
   "ContainerIDFile": "", 
   "LogConfig": { 
    "Type": "journald", 
    "Config": {} 
   }, 
   "NetworkMode": "default", 
   "PortBindings": {}, 
   "RestartPolicy": { 
    "Name": "no", 
    "MaximumRetryCount": 0 
   }, 
   "VolumeDriver": "", 
   "VolumesFrom": null, 
   "CapAdd": null, 
   "CapDrop": null, 
   "Dns": [], 
   "DnsOptions": [], 
   "DnsSearch": [], 
   "ExtraHosts": null, 
   "GroupAdd": null, 
   "IpcMode": "", 
   "Links": null, 
   "OomScoreAdj": 0, 
   "PidMode": "", 
   "Privileged": false, 
   "PublishAllPorts": false, 
   "ReadonlyRootfs": false, 
   "SecurityOpt": null, 
   "UTSMode": "", 
   "ShmSize": 67108864, 
   "ConsoleSize": [ 
    0, 
    0 
   ], 
   "Isolation": "", 
   "CpuShares": 0, 
   "CgroupParent": "", 
   "BlkioWeight": 0, 
   "BlkioWeightDevice": null, 
   "BlkioDeviceReadBps": null, 
   "BlkioDeviceWriteBps": null, 
   "BlkioDeviceReadIOps": null, 
   "BlkioDeviceWriteIOps": null, 
   "CpuPeriod": 0, 
   "CpuQuota": 0, 
   "CpusetCpus": "", 
   "CpusetMems": "", 
   "Devices": [], 
   "KernelMemory": 0, 
   "Memory": 0, 
   "MemoryReservation": 0, 
   "MemorySwap": 0, 
   "MemorySwappiness": -1, 
   "OomKillDisable": false, 
   "PidsLimit": 0, 
   "Ulimits": null 
  }, 
  "GraphDriver": { 
   "Name": "devicemapper", 
   "Data": { 
    "DeviceId": "29", 
    "DeviceName": "docker-253:0-101330881-9cb32851050b1707022b475489686582a272d883a56a8ff52f3344f56b65639f", 
    "DeviceSize": "10737418240" 
   } 
  }, 
  "Mounts": [ 
   { 
    "Name": "5ddd734c53a38a78a9f739157c63074b4aff736d4045925616d7753402304137", 
    "Source": "/var/lib/docker/volumes/5ddd734c53a38a78a9f739157c63074b4aff736d4045925616d7753402304137/_data", 
    "Destination": "/opt/vol-data", 
    "Driver": "local", 
    "Mode": "", 
    "RW": true, 
    "Propagation": "" 
   } 
  ], 
  "Config": { 
   "Hostname": "81a74152e6f4", 
   "Domainname": "", 
   "User": "", 
   "AttachStdin": true, 
   "AttachStdout": true, 
   "AttachStderr": true, 
   "Tty": true, 
   "OpenStdin": true, 
   "StdinOnce": true, 
   "Env": null, 
   "Cmd": [ 
    "/bin/bash" 
   ], 
   "Image": "test/mycentos:v1.0", 
   "Volumes": { 
    "/opt/vol-data": {} 
   }, 
   "WorkingDir": "", 
   "Entrypoint": null, 
   "OnBuild": null, 
   "Labels": {} 
  }, 
  "NetworkSettings": { 
   "Bridge": "", 
   "SandboxID": "c73841812aab480cf8e6a02071e36951dceb002d7b36f7dc0b38ccd9db833ba5", 
   "HairpinMode": false, 
   "LinkLocalIPv6Address": "", 
   "LinkLocalIPv6PrefixLen": 0, 
   "Ports": {}, 
   "SandboxKey": "/var/run/docker/netns/c73841812aab", 
   "SecondaryIPAddresses": null, 
   "SecondaryIPv6Addresses": null, 
   "EndpointID": "3e492b611d9bd2c202c8a6c8fe4b4a755393545348b1aeb4e60995609dabb07c", 
   "Gateway": "172.17.0.1", 
   "GlobalIPv6Address": "", 
   "GlobalIPv6PrefixLen": 0, 
   "IPAddress": "172.17.0.2", 
   "IPPrefixLen": 16, 
   "IPv6Gateway": "", 
   "MacAddress": "02:42:ac:11:00:02", 
   "Networks": { 
    "bridge": { 
     "IPAMConfig": null, 
     "Links": null, 
     "Aliases": null, 
     "NetworkID": "54001aaff29a231c9b2fe83459805d99dce0c21d6e2719f9b11bc90d8fe2f9c9", 
     "EndpointID": "3e492b611d9bd2c202c8a6c8fe4b4a755393545348b1aeb4e60995609dabb07c", 
     "Gateway": "172.17.0.1", 
     "IPAddress": "172.17.0.2", 
     "IPPrefixLen": 16, 
     "IPv6Gateway": "", 
     "GlobalIPv6Address": "", 
     "GlobalIPv6PrefixLen": 0, 
     "MacAddress": "02:42:ac:11:00:02" 
    } 
   } 
  } 
 } 
]

注意看Mounts节点(Docker的版本用的是1.10.3),数据卷的使用,类似于 Linux 下对目录或文件进行 mount。

"Mounts": [ 
   { 
    "Name": "5ddd734c53a38a78a9f739157c63074b4aff736d4045925616d7753402304137", 
    "Source": "/var/lib/docker/volumes/5ddd734c53a38a78a9f739157c63074b4aff736d4045925616d7753402304137/_data", 
    "Destination": "/opt/vol-data", 
    "Driver": "local", 
    "Mode": "", 
    "RW": true, 
    "Propagation": "" 
   } 
  ]


     

当我们在容器的volume上操作时,宿主机上对应的文件是否也会跟着变动呢?测试一下,在容器的volume上创建一个文件test.txt,然后查看宿主机是不是也会同步存在



经测试,当容器上的volume有变动时,宿主机也会跟着变动,那反过来呢?经测试也是一样的。不管是容器挂载点发生变动还是宿主机对挂载目录进行操作,令一方都会跟着变动。

利用docker commit生成新镜像,然后docker run -it 运行新镜像,发现容器挂载目录下没有任何文件了。说明生成新镜像时,是不保存挂载文件的。

3.2从宿主机挂载volume(-v /host-path:/container-path)

将主机的文件或文件夹作为volume挂载时,可以用多个 -v标签为容器添加多个volume,还可以使用:ro指定该volume为只读。注意:如果容器中指定的挂载目录存在相同的文件时,会被宿主机覆盖掉


在宿主机上建立了/opt/vol-01和/opt/vol-02挂载点,分别和容器中的/opt/vol-test-1和/opt/vol-test-2对应,前者权限默认读写,后者只能读,用docker inspect


当在容器的vol-test-2上新建操作时,会提示只读.在宿主机上,2个挂载点新增,修改,删除操作都OK,但是在容器中居然2个都不行,按理说应用是第二个vol-test-2不行才对,不知是哪里出问题了。

利用docker commit生成新镜像,然后docker run -it 运行新镜像,发现容器挂载目录下没有任何文件了。说明生成新镜像时,是不保存挂载文件的。

3.3使用Dockerfile添加volume

使用VOLUME指令向容器添加volume

VOLUME /data

多个时VOLUME ["/data1","/data2"]

这种情况和第一个中情况docker run -v /data是一样的。注意,dockerfile中使用volume是不能和第二种方法那样挂载宿主机中指定的文件夹。这时为了保证Dockerfile的可移植性,因为不能保证所有的宿主机都有对应的文件夹。

需要注意的是,在Dockerfile中使用VOLUME指令后,如果尝试对这个volume进行修改,这些修改指令都不会生效,比如下面例子,尝试添加一个文件,并修改文件并改变文件所有权限

FROM test/mycent:v1.0 
RUN useradd foo 
VOLUME /data 
RUN touch /data/x 
RUN chown -R foo:foo /data

通过该Dockerfile创建镜像并启动容器后,该容器中存在用户foo,并且能看到在/data挂载的volume,但是/data文件夹的所有者并没有被改变为foo,而且/data下也没有/data/x文件。但是如果顺序反过来,先建文件,先授权,再挂载volume,那就得到期待的结果。

4.共享volume/数据卷容器(--volumes-from)

如果你有一些持续更新的数据需要在容器之间共享,最好创建数据卷容器。数据卷容器,其实就是一个正常的容器,专门用来提供数据卷供其它容器挂载的。

先创建一个名为dbdata的数据卷容器,专门共其他容器挂载。


     在/opt/dbdata下创建了一个文件db.properties。

     再启动2个容器,a,b都a--volumes-from dbdata,b --volumes-from a.



    可以看到vol_a,vol_b容器的/opt/dbdata下都有db.properties文件。在vol_b的/opt/dbdata下创建文件vol_b.txt时,容器dbdata,vol_a也都同时同步了。

    对dbdata,vol_a,vol_b使用命令docker inspect时,发现他们的Mounts下的Source都是一样的,说明它们都绑定到宿主机的同一个目录,所以当某个容器的挂载修改时,其他容器也看到了同样的效果


如果挂载源有多个时,可以使用多个--volumes-from,如

docker run -it --name vol_use --volumes-from vol_a --volumes-from vol_b test/mycentos:v1.0 /bin/bash.

   如果一个容器挂在了volume,即使容器停止了运行,该volume仍然存在,其他容器仍然可以继续--volumes-from它。


5.删除volume

如果创建容器时挂载了volume,

在/var/lib/docker/volumes/04b003b21b873157433deffbaf08bb0c89d234d3ec3c6576fdd7b61f5d41163e/_data下会生成相应的文件(路径,不同版本,不同操作系统会有所不同,具体可以用docker inspect查看容器具体信息),当删除容器时,宿主机上的挂载目录时不会删除的,并且目录名称是随机字符,不知意义,所以在删除容器时,需要妥善处理容器的volume。删除容器时一并删除volume有2中方法

(1)docker rm -v 删除容器。就是删除容器时,加上-v

(2)docker run --rm .就是启动容器的时候加上--rm,那么当容器运行停止时会自动删除容器以及容器所挂载的volume

上面创建了容器dbdata,vol_a,vol_b,vol_c,现在使用docker rm -v看看有什么效果。


vol_c是--volumes-from dbdata的,删除vol_c时,宿主机的挂载目录仍存在,没删掉,猜测那是因为还有其他容器在连着或者说是dbdata -v的时候创建的。那现在删除dbdata容器试试看(vol_a是--volumes-from dbdata的,vol_a还没删除,验证下能否删除dbdata)。


发现dbdata删除时,宿主机那目录仍然存在,同时也说明哪怕vol_a是--volumes-from dbdata的,vol_a还没删除,那也没影响。同时也说明只要还有一个容器在挂载这宿主机的目录,那宿主机的目录就不会删除。那接下来,把所有容器都删除,再看结果。


可以看到,当最后一个容器删除后,宿主机那volume目录终于删除了.

6.备份、恢复或迁移volume

上面有测试过,当使用docker commit等手段生成新镜像,然后再启动镜像生成新容器时,原先volume目录下的文件不见了,可以生成新镜像时,并没有把volume下的文件一起打包生成镜像。

volume作为数据的载体,在很多情况下需要对其中的数据进行备份、迁移,或是从已有数据恢复。一个很容易想到的方法就是用docker inspect命令查找到volume对应宿主机上对应的那个目录位置,然后复制其中内容或使用tar打包。当这些笨拙的做法并不值得推荐,因为查找主机上文件夹后再操作容易出错,也不适合脚本的自动化执行。

备份volume可以使用以下方法

这行指令启动了一个临时的容器,这个容器挂载了两个volume,第一个volume与要备份的volume共享,第二个volume将宿主机的当前目录(也可以绝对路径)挂载到容器的/backup下。容器运行后将要备份的容器(/data)备份到/backup/data.tar,然后删除容器,备份后的data.tar就留在了当前目录。操作验证一下


居然报错了,说没有权限。进入容器-it时,是docker随机生成一个用户的,至于怎样给该用户授权,以后再研究吧。


推荐阅读
  • 在本文中,我们将探讨如何在Docker环境中高效地管理和利用数据库。首先,需要安装Docker Desktop以确保本地环境准备就绪。接下来,可以从Docker Hub中选择合适的数据库镜像,并通过简单的命令将其拉取到本地。此外,我们还将介绍如何配置和优化这些数据库容器,以实现最佳性能和安全性。 ... [详细]
  • 为什么多数程序员难以成为架构师?
    探讨80%的程序员为何难以晋升为架构师,涉及技术深度、经验积累和综合能力等方面。本文将详细解析Tomcat的配置和服务组件,帮助读者理解其内部机制。 ... [详细]
  • vue引入echarts地图的四种方式
    一、vue中引入echart1、安装echarts:npminstallecharts--save2、在main.js文件中引入echarts实例:  Vue.prototype.$echartsecharts3、在需要用到echart图形的vue文件中引入:   importechartsfrom"echarts";4、如果用到map(地图),还 ... [详细]
  • 兆芯X86 CPU架构的演进与现状(国产CPU系列)
    本文详细介绍了兆芯X86 CPU架构的发展历程,从公司成立背景到关键技术授权,再到具体芯片架构的演进,全面解析了兆芯在国产CPU领域的贡献与挑战。 ... [详细]
  • 在 CentOS 7 环境中使用 MySQL 5.6 镜像启动数据库时遇到权限问题,本文将详细探讨并提供解决方案。 ... [详细]
  • Spring – Bean Life Cycle
    Spring – Bean Life Cycle ... [详细]
  • 解决Only fullscreen opaque activities can request orientation错误的方法
    本文介绍了在使用PictureSelectorLight第三方框架时遇到的Only fullscreen opaque activities can request orientation错误,并提供了一种有效的解决方案。 ... [详细]
  • 服务器部署中的安全策略实践与优化
    服务器部署中的安全策略实践与优化 ... [详细]
  • 本文介绍了如何利用Shell脚本高效地部署MHA(MySQL High Availability)高可用集群。通过详细的脚本编写和配置示例,展示了自动化部署过程中的关键步骤和注意事项。该方法不仅简化了集群的部署流程,还提高了系统的稳定性和可用性。 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
  • Hadoop的文件操作位于包org.apache.hadoop.fs里面,能够进行新建、删除、修改等操作。比较重要的几个类:(1)Configurati ... [详细]
  • 本文讨论了在进行 MySQL 数据迁移过程中遇到的所有 .frm 文件报错的问题,并提供了详细的解决方案和建议。 ... [详细]
  • 深入解析Struts、Spring与Hibernate三大框架的面试要点与技巧 ... [详细]
  • 在配置Nginx的SSL证书后,虽然HTTPS访问能够正常工作,但HTTP请求却会遇到400错误。本文详细解析了这一问题,并提供了Nginx配置的具体示例。此外,还深入探讨了DNS服务器证书、SSL证书的申请与安装流程,以及域名注册、查询方法和CDN加速技术的应用,帮助读者全面了解相关技术细节。 ... [详细]
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
author-avatar
北京盛世诚泰家具有限公司
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有