专栏首页charlierorodocker storage driver

docker storage driver

  • docker默认有2种方式用于持久化数据,volumes和bind mounts,也可以使用tmpfs,其中使用volume是持久化数据的最好方式,volume由docker控制管理,使用docker volume create创建一个volume时,其目录会生成到/var/lib/docker/volumes目录下。volumes和bind mounts用于将数据持久化到硬盘中,tmpfs的数据只存在于内存中,主要用于存储容器运行过程中的临时数据。容器运行中如果会产生大量无需持久化的数据,建议将数据存放在tempfs中,这样会提高性能
  • volumes和bind mounts类似,但volume不依赖host机器的系统文件,因此使用上兼容性更大,权限隔离性更高。volume由docker管理,限制了文件的访问范围(用户和组),增强安全性。如果容器需要与host使用同一个文件目录(为了使用host的配置文件,如/etc/resolv.conf,或不同docker之间共享编译件等),可以考虑使用bind mount。当mount到一个容器中的非空目录时,volume会保留该目录的内容,而bind mount会覆盖该目录。
  • volumes和bind mounts默认使用rprivate类型的bind propagation,将命名空间中所有的mount point变为private propagation类型。

使用docker目录创建一个volume,并将该volume挂载到容器的/my_Cvol目录下

# docker volume create my_vo
# docker run -itd --rm --mount source=my_vol,target=/my_Cvol busybox:latest /bin/sh

查看该volume,其源目录实际在/var/lib/docker/volumes/my_vol/_data下

# docker volume inspect my_vol
[
    {
        "CreatedAt": "2018-12-24T22:42:18+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/my_vol/_data",
        "Name": "my_vol",
        "Options": null,
        "Scope": "local"
    }
]

查看使用docker inspect容器相关信息,可以看到volume的挂载信息,挂载到容器中的目录是可读写的。这样在容器的/my_Cvol目录下的操作也会同步到host的/var/lib/docker/volumes/my_vol/_data目录中。

"Mounts": [
    {
        "Type": "volume",
        "Name": "my_vol",
        "Source": "/var/lib/docker/volumes/my_vol/_data",
        "Destination": "/my_Cvol",
        "Driver": "local",
        "Mode": "z",
        "RW": true,
        "Propagation": ""
    }

查看host上/var/lib/docker/volumes/my_vol/_data的MAC属性可以看到它们变为了容器的MAC属性,这样也防止了容器操作不属于其权限范围的文件

# ls -Z
-rw-------. root root system_u:object_r:container_var_lib_t:s0 metadata.db
drwxr-xr-x. root root system_u:object_r:container_var_lib_t:s0 my_vol

使用tmpfs主要用于存储临时数据,由于tmpfs使用的是共享内存方式,所以其效率比较高。使用tmpfs时有如下2个选项用于指定tmpfs的大小和访问权限:

tmpfs-size:指定tmpfs的大小

tmpfs-mode:指定mount的目录权限

  • docker有很多插件可以实现不同的需求,如实现NFS卷共享,使用云提供商的存储介质等(更多查看Volume plugins)。下面使用ssh在不同node节点间共享卷

首先安装docker插件

# docker plugin install --grant-all-permissions vieux/sshfs

在node1节点上创建位于node2节点的卷,登陆的ssh密码为root,对端ip为192.168.80.161

# docker volume create --driver vieux/sshfs -o sshcmd=root@192.168.80.161:/home/sshvolume -o password=root sshvolume

在host上查看容器进程的挂载信息,可以看到其实际使用了fuse.sshfs的方式挂载了来自的root@192.168.80.161:/home/sshvolume目录

# cat /proc/19574/mountinfo |grep 176
502 399 0:49 / /sshvolume rw,nosuid,nodev,relatime master:176 - fuse.sshfs root@192.168.80.161:/home/sshvolume rw,user_id=0,group_id=0

node1上查看docker卷信息,可以看到新增了drive为vieux/sshfs:latest,名字为sshvolume的卷

# docker volume ls
DRIVER               VOLUME NAME
vieux/sshfs:latest   sshvolume

在node1上创建一个容器,并将上一步的卷挂载到容器,在容器内部创建2个文件夹,登陆到node2的/home/sshvolume,可以看到该目录下有node1的容器创建的文件夹

docker run -itd --mount source=sshvolume,target=/sshvolume busybox:latest /bin/sh

docker storage driver

storage driver负责不同layer之间的交互,它允许在容器的读写层创建数据,读写层数据不会被持久化,且读写效率较低。如下图所示,容器镜像的layer是只读的,当创建一个容器时,会新增一个读写层,称为”container layer“,对容器的所有修改都在该layer上进行。当容器删除后,该读写层也会被删除。不同的storage driver实现不同,但所有的storage driver都使用了如下栈式镜像结构以及CoW(copy-on-write)策略。这是对CoW的描述

而CoW技术可以让所有的容器共享image的文件系统,所有数据都从image中读取,只有当要对文件进行写操作时,才从image里把要写的文件复制到自己的文件系统进行修改。所以无论有多少个容器共享同一个image,所做的写操作都是对从image中复制到自己的文件系统中的复本上进行,并不会修改image的源文件,且多个容器操作同一个文件,会在每个容器的文件系统里生成一个复本,每个容器修改的都是自己的复本,相互隔离,相互不影响。使用CoW可以有效的提高磁盘的利用率。

使用docker ps -s可以查看镜像大小,可以看到"SIZE"有2个值,如container id为5b22377a773d的容器中,38B表示容器读写层的数据总和;virtual表示只读的镜像层加上读写层的大小,不同的容器可能会共用部分或全部的镜像层,因此计算容器占用空间大小不能简单地对virtual进行叠加

# docker ps -s
CONTAINER ID        IMAGE               COMMAND                 CREATED             STATUS              PORTS               NAMES               SIZE
5b22377a773d        echo:v1             "/bin/sh"               2 hours ago         Up 2 hours                              practical_darwin    38B (virtual 1.2MB)
803ee1eb5acf        echo:v1             "sh -c /home/echo.sh"   2 hours ago         Up 2 hours                              hungry_hertz        66B (virtual 1.2MB)

当在5b22377a773d中手动创建一个非空文件之后,可以看到size变为了95B

# docker ps -s
CONTAINER ID        IMAGE               COMMAND                 CREATED             STATUS              PORTS               NAMES               SIZE
5b22377a773d        echo:v1             "/bin/sh"               2 hours ago         Up 2 hours                              practical_darwin    95B (virtual 1.2MB)
803ee1eb5acf        6d495122f721        "sh -c /home/echo.sh"   2 hours ago         Up 2 hours                              hungry_hertz        66B (virtual 1.2MB)

当使用docker pull拉取一个容器镜像时,会在/var/lib/docker/<storage-driver>/layers/下面保存各个layer。

容器的读写层只保存修改过的变动,而未修改的文件或目录等则不会被保存在读写层。当修改容器中已经存在的文件时,会执行CoW操作,此时在镜像层中逐层搜索该文件,当找到该文件时,会将文件拷贝到容器的读写层(容器的镜像只读层可共享,但读写层不可以共享,CoW技术可以最大化减小容器占用的磁盘,提高磁盘利用率)。当CoW的读写效率比较低,可能会影响IO效率,需要注意以下2点:

  • 如果程序运行时需要大量修改存在于镜像只读层的文件,可以考虑将这些需要大量修改的文件单独放在独立于容器运行的volume中,可以提高IO
  • 如果镜像的layer比较多或需要修改的文件的目录比较深也会影响CoW的效率

ocker的storage driver使用插件方式提供功能。插件的选择取决于docker的版本以及使用的系统等,官方对storage driver的选择有如下建议,可以看出目前主要推荐overlay2。overlay和devicemapper已经在docker 18.09版本中被废除

Linux distribution

Recommended storage drivers

Alternative drivers

Docker Engine - Community on Ubuntu

overlay2 or aufs (for Ubuntu 14.04 running on kernel 3.13)

overlay, devicemapper, zfs, vfs

Docker Engine - Community on Debian

overlay2 (Debian Stretch), aufs or devicemapper (older versions)

overlay, vfs

Docker Engine - Community on CentOS

overlay2

overlay, devicemapper, zfs, vfs

Docker Engine - Community on Fedora

overlay2

overlay, devicemapper, zfs, vfs

不同storage driver所需要的文件系统如下:

Storage driver

Supported backing filesystems

overlay2, overlay

xfs with ftype=1, ext4

aufs

xfs, ext4

devicemapper

direct-lvm

btrfs

btrfs

zfs

zfs

vfs

any filesystem

不同的storage driver各有优缺点:

  • overlay2,aufs和overlay工作在文件级别(非块级别),该模式下内存使用会更有效,但在对容器的读写层进行大量文件的修改时会导致读写层变大;
  • devicemapper,btrfs和zfs工作在块级别,它们在进行大量文件的修改时则比上述更好,btrfs和zfs会消耗更多内存;
  • 在对小而多的文件进行修改或文件系统层级比较深的情况下,overlay比overlay2更有效,但会消耗更多inode资源;
  • zfs在高密集操作时更适用;
  • overlay2,aufs,overlay和devicemapper的可靠性更高

在对storage driver修改时需要注意

Important: When you change the storage driver, any existing images and containers become inaccessible. This is because their layers cannot be used by the new storage driver. If you revert your changes, you can access the old images and containers again, but any that you pulled or created using the new driver are then inaccessible.

下面讲解下overlay2的文件结构和特点,首先下载一个centos镜像,查看改镜像有如下3个layer

# docker history centos:latest
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
1e1148e4cc2c        2 months ago        /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           2 months ago        /bin/sh -c #(nop)  LABEL org.label-schema.sc…   0B
<missing>           2 months ago        /bin/sh -c #(nop) ADD file:6f877549795f4798a…   202MB

使用docker inspect查看该镜像可以看到其文件系统

"GraphDriver": {
            "Data": {
                "MergedDir": "/var/lib/docker/overlay2/0bb8525e901534d5c3884a9e47b91d27f887278d0433506126c45081a2a482b8/merged",
                "UpperDir": "/var/lib/docker/overlay2/0bb8525e901534d5c3884a9e47b91d27f887278d0433506126c45081a2a482b8/diff",
                "WorkDir": "/var/lib/docker/overlay2/0bb8525e901534d5c3884a9e47b91d27f887278d0433506126c45081a2a482b8/work"
            },
            "Name": "overlay2"
        },

使用docker run -itd centos:latest /bin/sh启动一个centos的容器,此时会自动创建overlay需要的lowerdir,upperdir,merged和workdir,使用docker inspect命令,可以看到该容器的overlay2使用情况

 "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/7aa485418eedcd1443f76018b94b76870de074d732f6e0d5b3e4305a6d896f0d-init/diff:/var/lib/docker/overlay2/0bb8525e901534d5c3884a9e47b91d27f887278d0433506126c45081a2a482b8/diff",
                "MergedDir": "/var/lib/docker/overlay2/7aa485418eedcd1443f76018b94b76870de074d732f6e0d5b3e4305a6d896f0d/merged",
                "UpperDir": "/var/lib/docker/overlay2/7aa485418eedcd1443f76018b94b76870de074d732f6e0d5b3e4305a6d896f0d/diff",
                "WorkDir": "/var/lib/docker/overlay2/7aa485418eedcd1443f76018b94b76870de074d732f6e0d5b3e4305a6d896f0d/work"
            },
            "Name": "overlay2"
        },

merged,upperdir和lowerdir的定义如下,upperdir为容器的读写层,lowerdir为容器的镜像只读层,merged为二者的合集

在容器创建后在/var/lib/docker/overlay2下面会生成2个新的目录,其中7aa485418eedcd1443f76018b94b76870de074d732f6e0d5b3e4305a6d896f0d-init用于设置容器的初始环境

drwx------. 5 root root     69 Feb 13 22:17 7aa485418eedcd1443f76018b94b76870de074d732f6e0d5b3e4305a6d896f0d
drwx------. 4 root root     55 Feb 13 22:17 7aa485418eedcd1443f76018b94b76870de074d732f6e0d5b3e4305a6d896f0d-init

在7aa485418eedcd1443f76018b94b76870de074d732f6e0d5b3e4305a6d896f0d目录下可以看到如下文件和目录:diff为该容器的UpperDir,对于容器的读写层,在容器中创建的文件或目录都会体现在该目录中(如下图,在容器的/home下创建一个名为newfile的文件和一个名为newfoler的目录,在diff/home下面也会同步体现该变化)。

# tree -L 1
  .
  ├── diff
  ├── link
  ├── lower
  ├── merged
  └── work
# pwd
  /var/lib/docker/overlay2/7aa485418eedcd1443f76018b94b76870de074d732f6e0d5b3e4305a6d896f0d/diff/home
# ll
  total 0
  -rw-r--r--. 1 root root 0 Feb 13 23:52 newfile
  drwxr-xr-x. 2 root root 6 Feb 13 23:54 newfolder
# cat link
  SKDGVP5O54VJTAXE7CQNUMIVLQ

link中包含了一个指向本目录diff文件夹的索引SKDGVP5O54VJTAXE7CQNUMIVLQ,可以在/var/lib/docker/l目录下找到其定义,其实是个系统链接(l目录存在的意义是防止挂载时符号超出页大小限制--默认4k)。

# ll ../l |grep SKDGVP5O54VJTAXE7CQNUMIVLQ
lrwxrwxrwx. 1 root root 72 Feb 13 22:17 SKDGVP5O54VJTAXE7CQNUMIVLQ -> ../7aa485418eedcd1443f76018b94b76870de074d732f6e0d5b3e4305a6d896f0d/diff

lower的内容如下,其实就是上述的GraphDriver.Data.LowerDir,对应容器镜像的只读层

# cat lower
l/G6URSFRXVFKZI5ESH2BGXG7LFS:l/T6OXBIDQ2GU523P5CXINFO3VH5

merged为lower和link的合集,work为OverlayFS内部使用的文件夹。

overlayFS读文件时使用时有如下特性:

  • 文件不在容器层:此时会从镜像查找并读取该文件,该操作会影响一部分性能
  • 文件存在于容器层:直接读取即可
  • 同时存在于镜像层和容器层:读取容器层文件,忽略镜像层

overlayFS写文件或目录时有如下特性:

  • 当修改的文件不在容器层时,会执行copy_up操作,将该文件从镜像层(lowerdir)拷贝到容器层(upperdir),需要注意的是,如果该文件很大时会影响性能
    • 对同一个文件的copy_up只会进行一次,后续对该文件的操作都会基于该文件的副本

由于overlayFS的CoW特性,在容器中需要注意以下2点(详情参见Use the OverlayFS storage driver):

  • 如使用fd1=open("foo", O_RDONLY) 后调用 fd2=open("foo", O_RDWR).打开镜像层的文件时,原意是打开并返回同一个文件的2个描述符,但实际上由于此时触发了copy_up机制,fd1指向的是镜像层的文件,而fd2指向容器层的文件,一种解决办法是在调用open之前触发copy_up,如使用touch命令
  • overlayFS不完全支持rename系统调用

TIPS:

  • overlayFS使用了2层结构(lower和upper),相比aufs提高了执行效率
  • 可以通过在/var/lib/docker/overlay2下面直接查看容器的镜像只读层和容器读写层的信息,但容器异常退出后,容器读写层会被删除,只能查看镜像只读层的文件信息
  • overlay在部署多个容器时会出现inode资源占用过大问题,建议使用overlay2

参考:

https://docs.docker.com/storage/storagedriver/

https://arkingc.github.io/2017/05/05/2017-05-05-docker-filesystem-overlay/

https://docs.docker.com/storage/storagedriver/overlayfs-driver/

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • docker “no space left on device”问题定位解决

    docker info查看docker的根路径,可以看到为/opt/docker:

    charlieroro
  • prometheus使用postgresql-adapter连接postgresql

    Prometheus使用postgresql需要使用postgresql-adapter进行数据转换。在安装postgresql-adapter之前需要安装2个...

    charlieroro
  • 使用共享内存和信号量模拟实现多进程会话

    linux手册中有详细讲解使用的接口的用法:http://man7.org/linux/man-pages/man2/semop.2.html

    charlieroro
  • docker学习(3) 容器的启动过程

    这一节我们来稍微了解下docker原理性的东西 docker run -i -t ubuntu /bin/bash 输入上面这行命令,启动一个ubuntu容器时...

    菩提树下的杨过
  • Docker系列之RabbitMQ安装部署教程

    因为学习RabbitMQ需要,需要安装RabbitMQ,网上找资料,RabbitMQ官方提供了window版、Linux版、Docker版的管理页面,为了方便,...

    SmileNicky
  • 群晖Docker安装chevereto图床

    mysql 数据库 (我目前是使用的是MariaDB 10,安装数据库不多做叙述,可以看我之前的教程)

    IT小马哥
  • Docker 入门(二):从实例来看 Dockerfile 和 Compose

    上一文中提到,每次对容器做了改动,如果不把它 commit 成新的镜像,在删除容器后,数据也会丢失,有没有什么更好的办法来保存数据呢?使用数据卷就可以解决这个问...

    zhisheng
  • 微服务链路监控系统-Pinpoint

    随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求 往往需要涉及到多个服务。这些服务可能不同编程语言开发,不同 团队开发,可能部署很多副本。因此,就需要...

    yuezhimi
  • Docker 入门到实战教程(十三)Docker Compose

    这里构建一个运行在Docker Compose上的简单Python Web应用程序。该应用程序使用Flask框架,并在Redis中维护一个计数器。尽管该示例使用...

    小东啊
  • HTML中百度地图的使用

    越陌度阡

扫码关注云+社区

领取腾讯云代金券