[docker] 容器镜像
容器镜像
[TOC]
联合文件系统UnionFS
作用是将不同文件系统下的文件mount到一个指定的挂载点上。
UnionFS 有多种实现方法,我这里以aufs(Advance Union File System)
为例说明原理,实际中docker client可能会使用别的实现,比如overlay2
等。你可以通过docker info
查看。
在进行联合的过程中,可以对各个分支规定不同的读写权限。
比如我们这样做:
创建两个目录 fruits 和 vegetables,在里面放上文件,并且创建两个空目录 mergeDir 和 workingDir,形成如下结构:
$ tree
.
├── fruits
│ ├── apple
│ └── tomato
├── mergeDir
├── vegetables
│ ├── carrots
│ └── tomato
└── workingDir
4 directories, 4 files
接下来我们将fruits, vegetable, workingDir 三个目录联合挂载到 mergeDir 目录下,同时规定 workingDir 为可读写目录, fruits 和 vegetables 为只读目录。
sudo mount -t aufs -o br=./workingDir=rw:./fruits=ro:./vegetables=ro none ./mergeDir
查看联合挂载之后的情况:
$ tree .
.
├── fruits
│ ├── apple
│ └── tomato
├── mergeDir
│ ├── apple
│ ├── carrots
│ └── tomato
├── vegetables
│ ├── carrots
│ └── tomato
└── workingDir
4 directories, 7 files
也就是说,我们在mergeDir中可以看到所有分支中的内容。而在联合挂载时fruits目录在vegetables之前,所以这里的tomato将是fruits目录中的tomato。
尝试进行文件修改:
hzq0@ceph0:~/docker-learn/new$ cd mergeDir/
hzq0@ceph0:~/docker-learn/new/mergeDir$ ls
apple carrots tomato
hzq0@ceph0:~/docker-learn/new/mergeDir$ echo "mergeDir: tomato" > tomato
hzq0@ceph0:~/docker-learn/new/mergeDir$ cat tomato
mergeDir: tomato
hzq0@ceph0:~/docker-learn/new/mergeDir$ cat ../fruits/tomato
hzq0@ceph0:~/docker-learn/new/mergeDir$ cat ../vegetables/tomato
hzq0@ceph0:~/docker-learn/new/mergeDir$ cat ../workingDir/tomato
mergeDir: tomato
可以看到由于fruits 和 vegetables 目录是作为只读分支被联合挂载的,所以在mergeDir中对文件的修改只能体现在workingDir中。
现在尝试删除操作:
hzq0@ceph0:~/docker-learn/new$ cd mergeDir/
hzq0@ceph0:~/docker-learn/new/mergeDir$ ls
apple carrots tomato
hzq0@ceph0:~/docker-learn/new/mergeDir$ rm carrots
hzq0@ceph0:~/docker-learn/new/mergeDir$ cd ..
hzq0@ceph0:~/docker-learn/new$ tree .
.
├── fruits
│ ├── apple
│ └── tomato
├── mergeDir
│ ├── apple
│ └── tomato
├── vegetables
│ ├── carrots
│ └── tomato
└── workingDir
└── tomato
4 directories, 7 files
我们在 mergeDir 中删除了文件 carrots,但是由于 carrots 本身的目录 vegetables 是作为只读分支挂载的,所以我们无法在 mergeDir 中真正删除 vegetables 中的 carrots,只能在 mergeDir 的“视图”中删除 carrtos。那么是怎么做到这一点的呢?
hzq0@ceph0:~/docker-learn/new$ cd workingDir/
hzq0@ceph0:~/docker-learn/new/workingDir$ ls
tomato
hzq0@ceph0:~/docker-learn/new/workingDir$ ls -a
. .. tomato .wh.carrots .wh..wh.aufs .wh..wh.orph .wh..wh.plnk
答案在 workingDir 中。workingDir 中新产生了一个隐藏文件.wh.carrots
。该文件的作用是白障whiteout
掉 carrtos 文件,使得我们并没有在分支中真正删除该文件,但却在挂载点中看不到该文件,产生一种该文件被删除的错觉。
联合文件系统UnionFS还有其他使用方式,我上面展示的这种用法正是docker产生它的容器镜像时的使用方法。
docker 如何利用UnionFS
当执行docker run
将一个容器运行起来之后,该容器的容器镜像作为一个只读的rootfs
和一个可读可写的workingDir
被联合挂载到/var/lib/docker/aufs/mnt
下面,而该目录将会作为容器运行之后的根目录。
mount -t aufs -o br=<upperDir>=rw:<lowerDir>=ro+wh none <mnt>
在联合挂载时,容器镜像作为lowerDir
,workingDir
作为upperDir
。
在没有对lowerDir
进行写操作之前,upperDir
里面是空的,而当对只读的lowerDir
中的文件进行修改之后,就会触发UnionFS
的copy-on-write
机制,进行一个copyup
的操作,将lowerDir
中被修改后的文件保存在upperDir
中。
而当需要删除只读的lowerDir
中的名为foo
的文件时,UnionFS
则是在可读写的upperDir
之中创建一个叫做.wh.foo
的文件,该文件的作用是“遮蔽”lowerDir
中的foo
文件,这样在mntDir
之中就看不到被删除的文件了(实际上该文件依然存在)。以上便是docker中增量rootfs的体现。
docker 镜像的分层
只读层
就是容器的运行镜像(rootfs
)。逻辑上可以将rootfs理解为一个完整的操作系统的根文件系统。但是实际上该容器镜像本身也是分层的,多个层叠加在一起形成一个完整的rootfs
。
比如前面例子中的 fruits 和 vegetables 目录。
可读写层
该层在容器运行时被创建,作用是保存增量(增量包括删除、修改和增加)。如前面例子中的 workingDir
特殊的init层
init
层是 docker 使用的一个内部层,用来存放/etc/hosts
,/etc/resolv.conf
等内容。需要这样一层的原因是,这些文件本来属于只读的 Ubuntu 镜像的一部分,但是用户往往需要在启动容器时写入一些指定的值比如hostname,所以就需要将这些文件放入可读写层。但是这些属性往往只想规定对当次容器运行的参数,不希望在执行docker commit
之后将这些设置写入新创建的容器镜像,所以就将这些文件放入一个可读写的init
层,在docker commmit
时只会提交可读写层。