​​Linux开源存储漫谈(8)容器及容器存储

容器

2013年,dotCloud公司开源容器项目Docker,开启了容器时代。但早在2013年之前,容器相关技术就已然出现并趋于完善,GOOGLE公司的Process Container,后更名为cgroup,并于2007年加入Linux 2.6.24内核版本,2008年,出现第一个比较完善的容器技术,基于cgroup和Linux Namesapces实现的LXC。之后,陆续出现了一些容器的实现,如CoreOS的rkt,再如Redhat开源了更加安全的容器实现Podman,但时至今日,似乎docker仍然是容器的代名词

容器解决了什么问题

容器之前,应用程序的部署、运维管理总是基于进程的,直接面对宿主机的操作系统,程序的发布、部署、运维管理更是五花八门,如发布部署格式,包括rpm、deb、压缩包,还有像IIM(IBM Installation Manager);运维管理采用supervisor、systemd服务、crontab等等,不一而足。标准化盛行的当代技术圈,必然会出现一种革命性的解决方案,一统发布、部署、运维管理的困境,docker和kubernetes便站在了浪潮之巅,docker解决了应用程序规范化发布、部署及沙盒的运行环境问题,kubernetes则解决分布式应用的容器集群编排及调度管理

容器技术框架

容器的技术框架如图:

应用层,指运行于容器的应用程序及辅助系统(监控、日志、安全等);集群管理层,负责容器应用的调度、编排,如Google开源的容器集群管理系统Kubernetes,也是Google多年大规模容器管理技术 Borg的开源版本,主要功能包括,基于容器的应用部署调度、维护和滚动升级,负载均衡和服务发现、自动伸缩等;运行引擎层指常见的容器系统,包括docker、rkt、基于CRI-O的容器实现等等,负责启动、运行、管理容器实例;资源管理层的核心目标是对服务器操作系统的管理;服务器层就是物理机或虚拟机

2013年到现在的10年时间里,得益于容器技术的发展演进,以及CNCF推动下云原生技术生态的蓬勃发展,而Kubernetes更是一骑绝尘逐渐成为容器集群管理系统的事实标准,而围绕着容器和Kubernetes生态的发展起来的诸如容器网络、容器存储等技术相关创新层次不穷,相关技术规范也日臻完善,2017年12月,标准化容器接口规范(CSI)发布,CSI的主要目的是使存储提供商只需要编写一个插件,就能够适配大部分容器编排系统。

Kubernetes的存储插件是内置的,也就是说这些插件随Kubernetes核心部件一起发布。但是对于大多数插件开发者来说,跟随Kubernetes的发布流程是一件很痛苦的事情。Flex Volume Plugin通过向外部插件暴露基于exec的API的方式来解决存储插件同Kubernetes核心部件一起发布的问题,Flex Volume Plugin虽然避开了写入Kubernetes核心代码的风险,但是需要访问工作节点或Master节点的root文件系统。另外,Flex Volume Plugin也未能解决依赖问题,插件经常需要外部依赖,如mount或文件系统工具,而安装这些依赖需要root文件权限。另一个存储插件的开发方式是CSI,CSI允许插件完全独立于Kubernetes核心代码,只是规定了发布CSI插件的最低要求,用户使用标准的Kubernetes存储原语,包括物理卷、PVC、StorageClass,来进行容器化部署。

Docker

从2013年dotCloud开源Docker,到2019年Docker公司拆分,Docker和Docker公司的传奇故事如今已经称为过去,但是容器技术的发展和演变却从未停止,大多容器实现与Docker基于相同的技术原理(限于Linux OS)

Docker架构(摘自Docker官网

1)Docker守护进程,docker daemon

docker daemon是docker常驻在后台的系统进程,负责监听来自docker client发送来的命令或API请求,当某个命令或API被调用时,这些调用或请求将会被封装成请求发送后台守护进程进行处理。后台守护进程管理各种Docker对象,包括镜像、容器、网络、卷等等

2)Docker客户端,docker client

Docker客户端,如docker命令,是docker用户与docker daemon进行交互的主要方式。当command窗口输入docker命令时,docker程序会将命令封装成消息发送给守护进程,守护进程根据消息的类型执行不同的操作 

3)Docker仓库,docker registry

docker 仓库,存储docker镜像。docker客户端从docker仓库同步docker镜像同步到本地后备用

root@nvme:~# docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox
325d69979d33: Pull complete
Digest: sha256:560af6915bfc8d7630e50e212e08242d37b63bd5c1ccf9bd4acccf116e262d5b
Status: Downloaded newer image for busybox:latest
docker.io/library/busybox:latest
root@nvme:~# docker images
REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
busybox      latest    8135583d97fe   4 days ago   4.86MB
root@nvme:~#

4)docker示例

root@nvme:~# docker run -it busybox /bin/sh
/ #
/ # ls
bin    etc    lib    proc   sys    usr
dev    home   lib64  root   tmp    var
/ #


# 打开另一个command,执行docker ps查看容器信息
root@nvme:~# docker ps
CONTAINER ID   IMAGE     COMMAND     CREATED          STATUS          PORTS     NAMES
909bf11417fd   busybox   "/bin/sh"   57 seconds ago   Up 55 seconds             stoic_leavitt
root@nvme:~#

示例展示了最重要的一个docker操作,即docker run启动一个容器实例。-it 参数告诉了 Docker 在启动容器后,分配一个文本输入/输出环境,即TTY,与容器的标准输入相关联,通过这个途径或者说是技术手段,就可以和这个docker容器进行交互了。而 /bin/sh则h是docker 容器里运行的应用程序

容器和镜像

docker通过运行一个镜像来产生一个容器,Docker镜像所解决的问题,就是应用程序打包问题。本质上,Docker 镜像就是一个压缩包,压缩包的内容,不仅仅包括应用可执行文件 + 启停脚本的组合,还包括应用程序完整的运行环境(操作系统的所有文件和目录),所以,通过使用Docker镜像整体打包本地环境,完成之后,再把这个镜像上传到云端运行,这样,就实现了应用程序本地环境和云端环境的高度一致,而Docker镜像是Docker最主要的创新,开创了软件交付的新标准,也是docker在2013年脱颖而出的不二法宝

Docker 镜像的原理并不十分复杂,其中最主要的是镜像层(下图中的Image),Dockerfile中每一条命令都会生成一个新的镜像层(只读), 见:Dockerfile,而镜像就像是一层层只读层叠加在一起,除底层(即原始只读层)以外,每一层都有一个指针指向它的下一层。Docker通过联合文件系统技术将不同的层整合成一个文件系统,屏蔽了多层的存在。

与镜像类似,容器也是一堆层的叠加,唯一的区别在于容器的最上层是可读写的。在容器的生命周期内任何修改都被写在最上层,包括新建、删除文件等

容器存储

个人认为,容器存储可分为两个方面来说,一方面是容器所占用的存储资源,主要镜像本身所占用的资源;另一方面是容器运行时产生的数据资源

容器的rootfs

rootfs,根文件系统,是Linux系统的概念,百度百科对根文件系统的定义:“根文件系统首先是内核启动时所mount的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行”。相比于Linux的根文件系统,容器的rootfs只包括了操作系统的文件和目录结构,并不包括内核,因为同一台机器上的所有容器是共享宿主机内核的,如下图,容器的rootfs包括蓝色的4个层

容器的rootfs总体上分三层,最上为容器层,也称作可写层,在没有写入文件之前,这个目录是空的,当容器生命周期结束后随之消失,有一点需要注意,容器rootfs的所有的层会通过联合文件系统挂载在一起;中间为init层,是 Docker 项目单独生成的一个内部层,专门用来存放 /etc/hosts、/etc/resolv.conf 等信息。需要这样一层的原因是,这些文件本来属于只读的 镜像的一部分,但是用户往往需要在启动容器时写入一些指定的值比如 hostname,所以就需要在可写层对它们进行修改。可是,这些修改往往只对当前的容器有效,并不希望执行 docker commit 时,把这些信息连同可写层一起提交掉;余下的都是只读层

那么,rootfs的内容是如何高效的保存在宿主机操作系统中的呢?做一个示例来看一下

# 创建first镜像
root@nvme:~/docker# cat > Dockerfile <<EOF
>FROM ubuntu
>ADD first.sh /
>EOF
root@nvme:~/docker# cat > first.sh <<EOF
>echo "first"
>EOF
root@nvme:~/docker# chmod a+x first.sh
root@nvme:~/docker# docker build -t first .
Sending build context to Docker daemon  3.072kB
Step 1/2 : FROM ubuntu
latest: Pulling from library/ubuntu
dbf6a9befcde: Pull complete
Digest: sha256:dfd64a3b4296d8c9b62aa3309984f8620b98d87e47492599ee20739e8eb54fbf
Status: Downloaded newer image for ubuntu:latest
 ---> 3b418d7b466a
Step 2/2 : ADD first.sh /
 ---> 0b90bc610ada
Successfully built 0b90bc610ada
Successfully tagged first:latest


# 创建second镜像,second & first都 FROM ubuntu,
root@nvme:~/docker# cat > Dockerfile.sec <<EOF
>FROM ubuntu
>ADD second.sh /
>EOF
root@nvme:~/docker# cat > second.sh <<EOF
>echo "second"
>EOF
root@nvme:~/docker# chmod a+x second.sh
root@nvme:~/docker# docker build -t second -f Dockerfile.sec .
Sending build context to Docker daemon   5.12kB
Step 1/2 : FROM ubuntu
 ---> 3b418d7b466a
Step 2/2 : ADD second.sh /
 ---> a96a73b0e9c9
Successfully built a96a73b0e9c9
Successfully tagged second:latest

# 新开两个command窗口,分别用于启动first和second,再容器first中touch first-tmp.sh文件
root@nvme:~# docker run -it first /bin/sh
# touch first-tmp.sh

root@nvme:~# docker run -it second /bin/sh
#

# 再打开一个command窗口
root@nvme:~# cd /var/lib/docker/overlay2
root@nvme:/var/lib/docker/overlay2# ls
1a24f6184d31a47a381d2d536ac6135c435f407e6cba56fb02a37c6d90eeae14
424fc002d09597a8003d155e3cb87b63f96fbfc979d48809b526f6e483bd7d7f
5b0086641475a8e6703c45d95adb86789389e69c7ea69e6b32e3d5347c0e0dfc
5b0086641475a8e6703c45d95adb86789389e69c7ea69e6b32e3d5347c0e0dfc-init
5d403b8e432eaeacb9f28e9f6ad354b3339e75483e0a9169e1c9c934511e281c
ce994829fe8d5c0393e49bb61120fe2f7a064ff2addba1f647f05545515263a2
ce994829fe8d5c0393e49bb61120fe2f7a064ff2addba1f647f05545515263a2-init
cf6e0c2358e7e73e7149adfe0716028e2fee995a44ce2cd905acc64ff46c1db8

root@nvme:/var/lib/docker/overlay2# docker ps
CONTAINER ID   IMAGE     COMMAND     CREATED         STATUS         PORTS     NAMES
788d973dcda1   second    "/bin/sh"   9 minutes ago   Up 9 minutes             silly_goldwasser
6c5614ff6d6f   first     "/bin/sh"   9 minutes ago   Up 9 minutes             serene_williams
root@nvme:/var/lib/docker/overlay2# docker inspect 6c5614ff6d6f
......
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/5b0086641475a8e6703c45d95adb86789389e69c7ea69e6b32e3d5347c0e0dfc-init/diff:/var/lib/docker/overlay2/1a24f6184d31a47a381d2d536ac6135c435f407e6cba56fb02a37c6d90eeae14/diff:/var/lib/docker/overlay2/5d403b8e432eaeacb9f28e9f6ad354b3339e75483e0a9169e1c9c934511e281c/diff",
                "MergedDir": "/var/lib/docker/overlay2/5b0086641475a8e6703c45d95adb86789389e69c7ea69e6b32e3d5347c0e0dfc/merged",
                "UpperDir": "/var/lib/docker/overlay2/5b0086641475a8e6703c45d95adb86789389e69c7ea69e6b32e3d5347c0e0dfc/diff",
                "WorkDir": "/var/lib/docker/overlay2/5b0086641475a8e6703c45d95adb86789389e69c7ea69e6b32e3d5347c0e0dfc/work"
            },
            "Name": "overlay2"
        },
......

root@nvme:/var/lib/docker/overlay2# docker inspect 788d973dcda1
......
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/ce994829fe8d5c0393e49bb61120fe2f7a064ff2addba1f647f05545515263a2-init/diff:/var/lib/docker/overlay2/424fc002d09597a8003d155e3cb87b63f96fbfc979d48809b526f6e483bd7d7f/diff:/var/lib/docker/overlay2/5d403b8e432eaeacb9f28e9f6ad354b3339e75483e0a9169e1c9c934511e281c/diff",
                "MergedDir": "/var/lib/docker/overlay2/ce994829fe8d5c0393e49bb61120fe2f7a064ff2addba1f647f05545515263a2/merged",
                "UpperDir": "/var/lib/docker/overlay2/ce994829fe8d5c0393e49bb61120fe2f7a064ff2addba1f647f05545515263a2/diff",
                "WorkDir": "/var/lib/docker/overlay2/ce994829fe8d5c0393e49bb61120fe2f7a064ff2addba1f647f05545515263a2/work"
            },
            "Name": "overlay2"
        },

......

# 运行find查找first.sh first-tmp.sh
root@nvme:/var/lib/docker/overlay2# find . -name first.sh
./1a24f6184d31a47a381d2d536ac6135c435f407e6cba56fb02a37c6d90eeae14/diff/first.sh
./5b0086641475a8e6703c45d95adb86789389e69c7ea69e6b32e3d5347c0e0dfc/merged/first.sh
root@nvme:/var/lib/docker/overlay2# find . -name first-tmp.sh
./5b0086641475a8e6703c45d95adb86789389e69c7ea69e6b32e3d5347c0e0dfc/diff/first-tmp.sh
./5b0086641475a8e6703c45d95adb86789389e69c7ea69e6b32e3d5347c0e0dfc/merged/first-tmp.sh
root@nvme:/var/lib/docker/overlay2#



frist和second镜像都是FROM ubuntu,也就是他们有相同Base Image层,容器6c5614ff6d6f和788d973dcda1就都挂载了5d403b8e43...281c。示例的最后的两个find命令,find first.sh的结果表明,Dockerfile里的“ADD first.sh /”生成的镜像层是1a24f6184d...ae14,find first-tmp.sh的结果则表明first镜像启动的容器6c5614ff6d6f的容器层是5b00866414...0dfc,init层就是5b00866414...0dfc-init,只读层为5d403b8e43...281c和1a24f6184d...ae14,这些层叠加在一起,并基于overlayfs合成容器6c5614ff6d6f的rootfs

容器存储

默认情况下,容器内产生的临时文件都被保存在可写层(容器层),保存在容器内部,当容器生命周期结束,这些数据会随之消失,这将产生3个问题,数据的持久化问题、耦合问题(数据与容器实例)、性能不佳(容器通过调用内核模块实现联合文件系统,这种通过更高层地抽象而得到的文件系统对IO影响较大),解决这些问题的技术途径就是通过数据卷插件,将保存的数据写入持久化数据卷

临时存储,默认情况下,容器内产生的临时文件都被保存在容器层,数据流经过联合文件系统,保存在容器内部。Docker通过模块化机制,提供了多种存储方案,可以根据场景不同选择合适的存储驱动。Docker使用存储驱动管理镜像层和容器层的内容,并定义了相关的存储驱动的接口,实现相应的方法,就可以扩展出一种存储引擎,目前,已支持的引擎包括device mapper、VFS、overlayer等。那么docker如何选择存储驱动,如何构建和存储镜像以及不同存储驱动背后的实现机制是什么呢?

type ProtoDriver interface {
    String() string
    Create(id, parent string)  error
    Remove(id string) error
    Get(id, mountLabel string) (dir string, err error)
    ......
}
type Driver interface {
    ProtoDriver
    Diff(id, parent string) error
    Change(id, parent string) ([]archive.Change, err error)
    ApplyDiff(id , parent string, diff archive.ArchiveReader) (size int64, err error)
    DiffSize(id, parent string) (size int64, err error)
}

不同的存储驱动实现细节各不相同,但是都基于一个原理、两个策略:镜像的分层原理、写时复制策略和用时分配策略

镜像的分层原理,镜像是由Dockerfile中的一条条指令生成,一条指令生成一个镜像层,如rootfs示例代码,first镜像的Dockerfile有两条指令

FROM ubuntu
ADD first.sh /

构建镜像使用docker build命令

root@nvme:~/docker# docker build -t first .
Sending build context to Docker daemon  3.072kB
Step 1/2 : FROM ubuntu
latest: Pulling from library/ubuntu
dbf6a9befcde: Pull complete
Digest: sha256:dfd64a3b4296d8c9b62aa3309984f8620b98d87e47492599ee20739e8eb54fbf
Status: Downloaded newer image for ubuntu:latest
 ---> 3b418d7b466a
Step 2/2 : ADD first.sh /
 ---> 0b90bc610ada
Successfully built 0b90bc610ada
Successfully tagged first:latest

first镜像两个镜像层,第一层对应FROM指令,sha256: 3b418d7b466a;第二层对应ADD指令,sha256:0b90bc610ada

写时复制可以最大化复制文件的效率。加入用同一个镜像启动多个容器,如果每个容器都申请一个容器文件系统,那么重复数据将会占用大量的存储空间。而写时复制技术可以让所有容器共享同一个镜像文件系统,所有的数据都从同一个镜像中读取,只有对文件进行写操作时,才复制到自己的文件系统中,示例中的first和second同时加载5d403b8e43...281c,也是这种场景的应用之一

用时分配策略是指只有在写入一个新的文件时才分配空间,从而提高存储资源的利用率

OverlayFS

OverlayFS同AUFS一样,也是一种联合文件系统,不同的是AUFS一直未能合并如Linux内核,而OverlayFS在2014年正式加入Linux内核主线。相比于AUFS,OverlayFS实现更加简单,也效率更高。当前最新版本的Docker,使用更新的OverlayFS存储驱动Overlay2,Overlays具有更高的性能和更高的索引节点利用率

OverlayFS建立在其它文件系统之上,并且不直接参与存储,其主要功能是合并底层文件系统,然后提供统一的文件系统供上层使用。

# 查看docker文件系统
root@nvme:~# docker info
Client:
 Context:    default
 Debug Mode: false
......

 Server Version: 20.10.21
 Storage Driver: overlay2

......

Overlay的存储机制,如图:

 lowerdir也称为镜像层,upperdir称为容器层,Overlay将镜像层和容器层的内容组合,然后提供统一视角,称为merged层,示例:

root@nvme:~/overlay# mkdir udir
root@nvme:~/overlay# mkdir ldir
root@nvme:~/overlay# mkdir wdir
root@nvme:~/overlay# mkdir mdir
root@nvme:~/overlay# echo "lower file #ldir only" > ldir/lfile
root@nvme:~/overlay# echo "delete lower file # LOWER" > ldir/del-lfile
root@nvme:~/overlay# echo "both lower and upper file #LOWER" > ldir/lufile

root@nvme:~/overlay# echo "upper file #udir only" > udir/ufile
root@nvme:~/overlay# echo "delete upper file # UPPER" > udir/del-ufile
root@nvme:~/overlay# echo "both lower and upper file #UPPER" > udir/lufile
root@nvme:~/overlay# tree
.
├── ldir
│   ├── del-lfile
│   ├── lfile
│   └── lufile
├── mdir
├── udir
│   ├── del-ufile
│   ├── lufile
│   └── ufile
└── wdir
    └── work

5 directories, 6 files



root@nvme:~/overlay# mount -t overlay overlay -o lowerdir=/root/overlay/ldir,upperdir=/root/overlay/udir,workdir=/root/overlay/wdir /root/overlay/mdir/
root@nvme:~/overlay# tree
.
├── ldir
│   ├── del-lfile
│   ├── lfile
│   └── lufile
├── mdir
│   ├── del-lfile
│   ├── del-ufile
│   ├── lfile
│   ├── lufile
│   └── ufile
├── udir
│   ├── del-ufile
│   ├── lufile
│   └── ufile
└── wdir
    └── work

5 directories, 11 files

root@nvme:~/overlay# cat mdir/lufile
both lower and upper file #UPPER

# create a new file in mdir
root@nvme:~/overlay# touch mdir/new-mfile
root@nvme:~/overlay# rm -f mdir/del-lfile
root@nvme:~/overlay# rm -f mdir/del-ufile
root@nvme:~/overlay# tree
.
├── ldir
│   ├── del-lfile
│   ├── lfile
│   └── lufile
├── mdir
│   ├── lfile
│   ├── lufile
│   ├── new-mfile
│   └── ufile
├── udir
│   ├── del-lfile
│   ├── lufile
│   ├── new-mfile
│   └── ufile
└── wdir
    └── work
        └── #1b

5 directories, 12 files
root@nvme:~/overlay# cat udir/del-lfile
cat: udir/del-lfile: No such device or address

通过简单的测试,可以看出OverlayFS的读写流程,如果文件只存在于某一层,如只存在于ldir或udir,则直接读取;如果文件两个层都存在,则lower层的文件会被upper层的覆盖掉,如cat mdir/lufile的内容可以看出,ldir/lufile被udir/lufile覆盖掉。修改文件(指直接修改merged里文件,示例即修改mdir内的文件),upper层的文件会被直接修改,并同时体现在upper和merged层;lower层的文件,可以理解为先被copy到upper层,然后按upper层文件处理;删除文件的本质是whiteout文件来屏蔽再次访问

持久化存储

针对临时存储存在的问题(紧耦合、持久化、效率),docker提供了数据卷(volume)的功能来进行数据的持久化存储。数据卷将宿主机的某个一文件夹挂载在容器内部,绕开分层的文件系统,因此数据卷的IO性能与主机一致,并脱离了容器的生命周期,获得了持久化能力。

docker volume

docker支持两种Volume的挂载机制,如下示例,"-v /firstv"和"-v /root/hostdir:/hostdir"

root@nvme:~# mkdir hostdir
root@nvme:~# touch hostdir/file1
root@nvme:~# docker run -it  -v /firstv -v /root/hostdir:/hostdir first /bin/sh
# ls | grep -E "firstv|hostdir"
firstv
hostdir
# ls hostdir
file1
#

# 容器内查看 firstv 和 hostdir的inode信息
# ls -i | grep -E "first|hostdir"
9966450 first.sh
9966545 firstv
9571055 hostdir
#

在开一个窗口,运行docker inspect,在宿主机上查看容器的信息

# 查看容器的ID信息
root@nvme:~# docker ps
CONTAINER ID   IMAGE     COMMAND     CREATED         STATUS         PORTS     NAMES
3f6a91591cda   first     "/bin/sh"   7 minutes ago   Up 7 minutes             strange_cannon

# 运行docker inspect获取overlayFS的merged目录信息,并进入
root@nvme:~# docker inspect 3f6a91591cda --format '{{.GraphDriver.Data.MergedDir}}'
/var/lib/docker/overlay2/4304b4b53c0a1b28d254ea3835ec1a72bf37ef0be7dc07dcfd85cd7e635e7c1e/merged
root@nvme:~# cd /var/lib/docker/overlay2/4304b4b53c0a1b28d254ea3835ec1a72bf37ef0be7dc07dcfd85cd7e635e7c1e/merged

# 查看firstv和hostdir(容器内挂载点)的inode
root@nvme:/var/lib/docker/overlay2/4304b4b53c0a1b28d254ea3835ec1a72bf37ef0be7dc07dcfd85cd7e635e7c1e/merged# ls -i | grep -E "first|hostdir"
9966450 first.sh
9966554 firstv
9966553 hostdir
root@nvme:/var/lib/docker/overlay2/4304b4b53c0a1b28d254ea3835ec1a72bf37ef0be7dc07dcfd85cd7e635e7c1e/merged#

对比容器内部和宿主机查看firstv和hostdir的inode信息是不同的,但是first.sh的inode却是相同的,这说明,first.sh在容器内看到的和在宿主机"mount -t overlay overlay ..."的merged目录里看到的是指向同一个inode的目录项,而firstv和hostdir则是不同的,那么,firstv和hostdir分别又是哪里呢?继续使用docker inspect 查看容器的Mounts信息

root@nvme:~# docker inspect 3f6a91591cda
{
......
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/root/hostdir",
                "Destination": "/hostdir",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },
            {
                "Type": "volume",
                "Name": "7668699e31c37d61720ea093db9b08d35ca704bfc5cc7483fad7850244357d29",
                "Source": "/var/lib/docker/volumes/7668699e31c37d61720ea093db9b08d35ca704bfc5cc7483fad7850244357d29/_data",
                "Destination": "/firstv",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
......
}

root@nvme:~# ls -i /var/lib/docker/volumes/7668699e31c37d61720ea093db9b08d35ca704bfc5cc7483fad7850244357d29
9966545 _data

root@nvme:~# ls -i | grep hostdir
9571055 hostdir
root@nvme:~#

# Linux绑定挂载测试
root@nvme:~# mkdir mount.bind
root@nvme:~# cd mount.bind/
root@nvme:~/mount.bind# mkdir sdir
root@nvme:~/mount.bind# mkdir tdir
root@nvme:~/mount.bind# ls -i
9571058 sdir  9571059 tdir
root@nvme:~/mount.bind# mount --bin /root/mount.bind/sdir /root/mount.bind/tdir
root@nvme:~/mount.bind# ls -i
9571058 sdir  9571058 tdir
root@nvme:~/mount.bind#

从以上输出可以得出结论,docker数据卷,使用了Linux的绑定挂载机制(inode替换,通过mount --bind source target命令实现,把target的inode指向source的inode),"-v /firstv",docker先创建了一个Volume,然后,是一个inode替换,即把overlayFS的merged里的firstv和hostdir分别替换为"/var/lib/docker/volumes/7668699e31c37d61720ea093db9b08d35ca704bfc5cc7483fad7850244357d29/_data"和"/root/hostdir"的inode,所以,在一个正确的时机,进行一次绑定挂载,Docker 就可以成功地将一个宿主机上的目录或文件,不动声色地挂载到容器中。

欢迎转载,请注明出处

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值