Docker存储卷篇

                                      作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

  

一.写时复制(COW)机制

Docker存储卷篇

所谓写时复制的效果如上图所示:
  Docker镜像由多个只读层叠加而成,启动容器时,Docker会加载只读镜像层并在镜像栈顶部添加一个读写层。
  如果运行中的容器修改了现有的一个已经存在的文件,那该文件将会从读写层下面的只读层复制到读写层,该文件的只读版本依然存在,只是已经被读写层中该文件的副本所隐藏,此即"读写复制(COW)"机制。

 

二.数据卷(Data Volume)概述

 1>.什么是存储卷(volume)

  关闭并重启容器,其数据不受影响;但删除Docker容器,则其更改将会全部丢失。原因就是我们上面提到的写时复制机制,这个写时复制机制存在于容器,数据默认都保存在容器内。

  Docker设计存在的问题:
    1>.存储于联合文件系统中,不易于宿主机访问;
    2>.容器间数据共享不便;
    3>.删除容器其数据会丢失;

  解决方案:"存储卷(volume)"
    如下图所示:"卷"是容器上的一个或多个"目录",此类目录可绕过联合文件系统,于宿主机上的某个目录"绑定(关联)"

 Docker存储卷篇

 2>.存储卷(volume)的作用

  1.数据卷为持久或共享数据提供了几个有用的特性:
    1>.Volume于容器(container)初始化之时即会创建,由base image提供的卷中的数据于此期间完成复制;
    2>.数据卷可以在容器之间共享和重用;
    3>.更新镜像(image)时不会包括对数据卷的更改;
    4>.即使容器(container)自身被删除,数据卷仍保持不变。

  2.Volume的初衷是独立于容器的生命周期实现数据持久化:
    1>.默认情况下删除容器之时既不会删除卷,也不会对哪怕未被引用的卷做垃圾回收操作;
    2>.如果我们删除容器时使用了特殊的选项(类似于linux的"userdel -r"选项)也可以一并删除容器的存储卷,但是生成环境我们不会这样做。

  3.卷为docker提供了独立于容器的数据管理机制(如下图所示):
    1>.可以把"镜像"想像成静态文件,例如"程序",把卷类比为动态内容,例如"数据";于是,镜像可以重用,而卷可以共享;
    2>.卷实现了"程序(镜像)"和"数据(卷)"分离,以及"程序(镜像)"和"制作镜像的主机"分离,用户制作镜像时无须再考虑镜像运行的容器所在的主机的环境;

Docker存储卷篇

 3>.docker存储卷类型(Volume types)

  如下图所示,Docker有两种类型的卷,每种类型都在容器中存在一个挂载点,但其在宿主机上的位置有所不同:
    1>.绑定挂载卷(Bind mount volume)
      指向主机文件系统上用户指定位置的卷。换句话说,在宿主机上的路径你需要人工指定一个特定路径,再容器上的路径也需要指定一个特定路径,让这两个已知路径建立关联关系。随着容器删除,存储卷数据并不会丢失。

    2>.docker管理的卷(Docker managed volume)
      Docker守护进程在Docker拥有的主机文件系统的一部分中创建管理卷。换句话说,我们只需要在容器内指定挂载点是什么,而绑定的是宿主机哪个路径下的目录我们不需要管,由容器引擎的docker daemon自行创建一个空目录或者使用一个已存在目录与你的存储卷路径建立关联关系。随着容器删除,存储卷数据也随着丢失了。
  

 Docker存储卷篇

 

三.在容器这种使用Volumes

1>.Docker managed volume案例

[root@node101.yinzhengjie.org.cn ~]# docker run --name c1 -it --rm busybox
/ # ls /
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # 
/ # exit
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# docker run --name c1 -it -v /data --rm busybox      #注意,我们使用"-v"选项来指定c1容器内docker管理卷存放为位置,而具体对应宿主机的哪个路径我们无需关心,而是由docker daemon进程自行创建,我们可以通过相关命令查看对应的具体路径。
/ # 
/ # ls /          #很上面的容器相比,很显然,咱们这个容器多出了一个"/data"目录,该目录即为docker managed volume实例。
bin   data  dev   etc   home  proc  root  sys   tmp   usr   var
/ # 
[root@node101.yinzhengjie.org.cn ~]# docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d574ca3e42a6 busybox "sh" 11 minutes ago Up 11 minutes c1
[root@node101.yinzhengjie.org.cn ~]#
[root@node101.yinzhengjie.org.cn ~]# docker inspect c1                           #查看c1容器的详细信息,注意观察keys为"Mounts""Source"属性对应的值,它所指向的路径就是c1容器"/data"目录所在宿主机的存储路径。
[
    {
        "Id": "d574ca3e42a6cc08e009ba44b3c7bcd467362ff10c71d9461211a7e9687e2c09",
        "Created": "2019-10-19T01:12:06.848411027Z",
        "Path": "sh",
        "Args": [],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 7124,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2019-10-19T01:12:07.402652338Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:19485c79a9bbdca205fce4f791efeaa2a103e23431434696cc54fdd939e9198d",
        "ResolvConfPath": "/var/lib/docker/containers/d574ca3e42a6cc08e009ba44b3c7bcd467362ff10c71d9461211a7e9687e2c09/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/d574ca3e42a6cc08e009ba44b3c7bcd467362ff10c71d9461211a7e9687e2c09/hostname",
        "HostsPath": "/var/lib/docker/containers/d574ca3e42a6cc08e009ba44b3c7bcd467362ff10c71d9461211a7e9687e2c09/hosts",
        "LogPath": "/var/lib/docker/containers/d574ca3e42a6cc08e009ba44b3c7bcd467362ff10c71d9461211a7e9687e2c09/d574ca3e42a6cc08e009ba4
4b3c7bcd467362ff10c71d9461211a7e9687e2c09-json.log",        "Name": "/c1",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "default",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": true,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "CapAdd": null,
            "CapDrop": null,
            "Capabilities": null,
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "private",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "ConsoleSize": [
                0,
                0
            ],
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": [],
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DeviceCgroupRules": null,
            "DeviceRequests": null,
            "KernelMemory": 0,
            "KernelMemoryTCP": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": null,
            "OomKillDisable": false,
            "PidsLimit": null,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0,
            "MaskedPaths": [
                "/proc/asound",
                "/proc/acpi",
                "/proc/kcore",
                "/proc/keys",
                "/proc/latency_stats",
                "/proc/timer_list",
                "/proc/timer_stats",
                "/proc/sched_debug",
                "/proc/scsi",
                "/sys/firmware"
            ],
            "ReadonlyPaths": [
                "/proc/bus",
                "/proc/fs",
                "/proc/irq",
                "/proc/sys",
                "/proc/sysrq-trigger"
            ]
        },
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/b91e241fd4070485ceb246e18c2d258df9666797453448b287e4253e6a5bc437-init/diff:/var/l
ib/docker/overlay2/ead7a29001855b8898300b67763a050eeb50e5c7988076f4777b56e85365169e/diff",                "MergedDir": "/var/lib/docker/overlay2/b91e241fd4070485ceb246e18c2d258df9666797453448b287e4253e6a5bc437/merged",
                "UpperDir": "/var/lib/docker/overlay2/b91e241fd4070485ceb246e18c2d258df9666797453448b287e4253e6a5bc437/diff",
                "WorkDir": "/var/lib/docker/overlay2/b91e241fd4070485ceb246e18c2d258df9666797453448b287e4253e6a5bc437/work"
            },
            "Name": "overlay2"
        },
        "Mounts": [
            {
                "Type": "volume",
                "Name": "3fc67ccb38ce00fb1257950e77ad71b5b01fc0d53f6959eda1e810e970153914",
                "Source": "/var/lib/docker/volumes/3fc67ccb38ce00fb1257950e77ad71b5b01fc0d53f6959eda1e810e970153914/_data",        #注意观察这里,这就是我们c1容器"/data"存储卷对应的宿主机路径。
                "Destination": "/data",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
        "Config": {
            "Hostname": "d574ca3e42a6",
            "Domainname": "",
            "User": "",
            "AttachStdin": true,
            "AttachStdout": true,
            "AttachStderr": true,
            "Tty": true,
            "OpenStdin": true,
            "StdinOnce": true,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "sh"
            ],
            "Image": "busybox",
            "Volumes": {
                "/data": {}
            },
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "7fcfdb3cbc2d42503fbd9f44d4c9606ee484cedc26d88f7c5c7a7934efb82be5",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {},
            "SandboxKey": "/var/run/docker/netns/7fcfdb3cbc2d",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "d81e297a392c3f6c73422ddb016b9c126a8f50ace798920947d331cea3c9baa0",
            "Gateway": "192.168.100.254",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "192.168.100.1",
            "IPPrefixLen": 24,
            "IPv6Gateway": "",
            "MacAddress": "02:42:c0:a8:64:01",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "7ad23e4ff4b3bbaa8cea6b89507921beb407568e021f3dad666a829ec817be7e",
                    "EndpointID": "d81e297a392c3f6c73422ddb016b9c126a8f50ace798920947d331cea3c9baa0",
                    "Gateway": "192.168.100.254",
                    "IPAddress": "192.168.100.1",
                    "IPPrefixLen": 24,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:c0:a8:64:01",
                    "DriverOpts": null
                }
            }
        }
    }
]
[root@node101.yinzhengjie.org.cn ~]# cd /var/lib/docker/volumes/3fc67ccb38ce00fb1257950e77ad71b5b01fc0d53f6959eda1e810e970153914/_data                #我们进入到c1容器对应"/data"目录在宿主机的路径,就可以实现c1容器和宿主机数据共享。
[root@node101.yinzhengjie.org.cn /var/lib/docker/volumes/3fc67ccb38ce00fb1257950e77ad71b5b01fc0d53f6959eda1e810e970153914/_data]# 
[root@node101.yinzhengjie.org.cn /var/lib/docker/volumes/3fc67ccb38ce00fb1257950e77ad71b5b01fc0d53f6959eda1e810e970153914/_data]# ls
[root@node101.yinzhengjie.org.cn /var/lib/docker/volumes/3fc67ccb38ce00fb1257950e77ad71b5b01fc0d53f6959eda1e810e970153914/_data]# 
[root@node101.yinzhengjie.org.cn /var/lib/docker/volumes/3fc67ccb38ce00fb1257950e77ad71b5b01fc0d53f6959eda1e810e970153914/_data]# echo "hello container" >> test.html
[root@node101.yinzhengjie.org.cn /var/lib/docker/volumes/3fc67ccb38ce00fb1257950e77ad71b5b01fc0d53f6959eda1e810e970153914/_data]# 
[root@node101.yinzhengjie.org.cn /var/lib/docker/volumes/3fc67ccb38ce00fb1257950e77ad71b5b01fc0d53f6959eda1e810e970153914/_data]# ls
test.html
[root@node101.yinzhengjie.org.cn /var/lib/docker/volumes/3fc67ccb38ce00fb1257950e77ad71b5b01fc0d53f6959eda1e810e970153914/_data]# 
[root@node101.yinzhengjie.org.cn ~]# docker inspect c1  #查看c1容器的详细信息,注意观察keys为"Mounts"的"Source"属性对应的值,它所指向的路径就是c1容器"/data"目录所在宿主机的存储路径。

相关文章: