TL;DR docker 能流行起来,镜像是最大的原因,他把程序的依赖打包在一起,一次构建,处处运行
初识镜像
我们知道,docker 容器实际上与宿主机共享内核,只是宿主机上的一个普通进程,只不过用 mnt
, pid
, net
等等做了 namespace 隔离,然后再用 cgroup
做了资源限制。
如上图所示,左侧传统虚拟化出来的需要一层 Guest OS, 这就是本质的区别。
1. 查看内容
root@myali:~# docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
7ddbc47eeb70: Pull complete
c1bbdc448b72: Pull complete
8c3b70e39044: Pull complete
45d437916d57: Pull complete
Digest: sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest
使用 docker pull ubuntu
拉取最新的镜像
root@myali:~# mkdir ubuntu
root@myali:~# docker export $(docker create ubuntu) | tar -C ./ubuntu -xvf -
root@myali:~# ls ubuntu
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
将镜像内容解压后,我们发现,原来就是常用的文件系统内容而己。如果是其它定制化的,那么还会包含用户自定义的,比如 python 依赖,go 库等等
2. 制作镜像
基于上面拉取的 ubuntu,定制新的镜像。先写两个测试文件
root@myali:~# echo "out of docker" > config.json
root@myali:~# echo "out of ccccc" > ccccc
查看我们自写义的 Dockerfile
文件
root@myali:~# cat Dockerfile
FROM ubuntu
COPY config.json /root
COPY ccccc /
CMD /bin/sh
然后打包生成镜像
root@myali:~# docker build . -t myimage:latest
Sending build context to Docker daemon 68.39MB
Step 1/4 : FROM ubuntu
---> 775349758637
Step 2/4 : COPY config.json /root
---> 9c1a567cc02e
Step 3/4 : COPY ccccc /
---> c3f52d5158ab
Step 4/4 : CMD /bin/sh
---> Running in 540e2987bf4c
Removing intermediate container 540e2987bf4c
---> 4b9c8ca82e42
Successfully built 4b9c8ca82e42
Successfully tagged myimage:latest
root@myali:~# docker history myimage
IMAGE CREATED CREATED BY SIZE COMMENT
4b9c8ca82e42 4 hours ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/bin… 0B
c3f52d5158ab 4 hours ago /bin/sh -c #(nop) COPY file:414a288c3053d3f9… 13B
9c1a567cc02e 4 hours ago /bin/sh -c #(nop) COPY file:64632fffb953c191… 14B
775349758637 5 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 5 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 5 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 745B
<missing> 5 weeks ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 987kB
<missing> 5 weeks ago /bin/sh -c #(nop) ADD file:a48a5dc1b9dbfc632… 63.2MB
可以看到,镜像制做是分层的,每一个命令一层 layer, FROM ubuntu
表示基于哪个镜像制作的,最后 CMD /bin/sh
意思是容器启动后要运行的命令,也就是最终 runc
要执行的。
3. 分层设计
分层 layer,好处是多个镜像可以共享,拉取一个新镜像时,只需拉取不同的部份即可。一个容器典型分成三层:
- 容器层:镜像是静态的,启动时拥有一个完整的容器层,可读写。
- init 层:这一层是运行时动态生成的,只读,用来给每个容器生成不同的 dev etc 等等
- 镜像层:镜像不同 layer 的总称,内容是只读的。
root@myali:/var/lib/docker/overlay2# mount | grep -i overlay2
overlay on /var/lib/docker/overlay2/dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da/merged type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/SR34NROUREC6U2N6HMTAOH66CZ:/var/lib/docker/overlay2/l/K535YPMQVAVYMUJUJ5ONJSTV6M:/var/lib/docker/overlay2/l/AHPVAYNKIZTOHPVOYPD7BU7J7V:/var/lib/docker/overlay2/l/IAFCF667EV4HZWTDYKYVHFGX4D:/var/lib/docker/overlay2/l/DRDJ5SXKMKS5SAXLFA6SGQBRSW:/var/lib/docker/overlay2/l/FYPOP5ERVXC7OGFHRU5NLUEBT5:/var/lib/docker/overlay2/l/6LZLHYDHXDIJGNIKISCFFGA5BP,upperdir=/var/lib/docker/overlay2/dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da/diff,workdir=/var/lib/docker/overlay2/dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da/work)
overylay2
上面截图是查看当前挂载的 overlay2 文件系统,其中
-
lowerdir
是底层的镜像层 -
workdir
是 overlayfs 工作目录 -
upperdir
是容器层,所以容器里的对文件的更改都会放到这里 -
merged
是联合文件系统合并目录,最终合并展现给容器用户的
root@myali:~# ls /var/lib/docker/overlay2/dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da/merged
bin boot ccccc dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
layer
存储驱动
我们知道容器启动后,程序是要写本地文件的,这里分两种情况,一个是挂载 volume 写日志类的,一般 volume 都是本地磁盘,或是网络文件系统,另外一个是镜像容器层的读写,这个就是 docker 所谓的 Storage Driver, 可以通过 docker info
查看,最新默认是 overlay2
,backing fs 是 extfs,也就是 ex4.
root@myali:~# docker info
......
Server Version: 19.03.5
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
......
1. Driver 类型
基于文件实现的 aufs
, overlay
, overlay2
, 还有基于块实现的 devicemapper
, zfs
, btrfs
, 不同类型之间各有优缺点:基于文件的可以共享文件缓存,所以在内存使用上更高效一些。但是如果有改动,块的更高效一些。
2. overlay 与 overlay2
overlay/overlay2
属于联合文件系统,实现都是 copy-on-write,区别是 overlay
采用两层,硬链接文件来实现(需要递归创建目录,消耗更多的 inodes),而 overlay2
最多可以有 128 层,最新推荐使用后者
对于读:
- 读的文件不在容器层:如果读的文件不在容器层,则从镜像层进行读
- 读的文件只存在在容器层:直接从容器层读
- 读的文件在容器层和镜像层:读容器层中的文件,因为容器层隐藏了镜像层同名的文件
对于写:
- 写的文件不在容器层,在镜像层:由于文件不在容器层,因此overlay/overlay2存储驱动使用copy_up操作从镜像层拷贝文件到容器层,然后将写入的内容写入到文件新的拷贝中。
- 删除文件和目录:删除镜像层的文件,会在容器层创建一个whiteout文件来隐藏它;删除镜像层的目录,会创建opaque目录,它和whiteout文件有相同的效果
- 重命名目录:对一个目录调用rename(2)仅仅在资源和目的地路径都在顶层时才被允许,否则返回EXDEV。
4. 文件结构
不同存储数型的目录是不同的,以 overlay2
为例默认是 /var/lib/docker/overlay2
root@myali:/var/lib/docker/overlay2# ls -rlt
total 44
drwx------ 3 root root 4096 Dec 11 10:52 81d3cf5ac6d993c9768bb3bf6352b0b8e86752ce5c8d6aba23cb0430d07aee56
drwx------ 4 root root 4096 Dec 11 10:52 58bbbb798d7548cedcb3d0c3367b8aed391534c2fef7bec980ed6337281eca76
drwx------ 4 root root 4096 Dec 11 10:52 9fd5371cc3852d951116f38f0fed873a23019edf0d730701b5501ec3ba64e1a4
drwx------ 4 root root 4096 Dec 11 11:00 98a0ef116d91098c578781085cc183cf977ef449a671d3b92134e303ca85b76f
drwx------ 4 root root 4096 Dec 11 12:43 3ac3321ec3c39ee8e9ca202ff9f47f2c45adcdd58970b363c8007ae48f936d04
drwx------ 4 root root 4096 Dec 11 12:43 6068909bbebb79f5e3d3f21d196a0a0ee542daef6379c7702805bb3bbad01dbe
drwx------ 4 root root 4096 Dec 11 12:57 5c4545f510fa94188705e27b6cbaff00b6bda6c4a69f89a6365eee0c85a90bf2-init
drwx------ 4 root root 4096 Dec 11 12:57 5c4545f510fa94188705e27b6cbaff00b6bda6c4a69f89a6365eee0c85a90bf2
drwx------ 2 root root 4096 Dec 11 12:57 l
drwx------ 4 root root 4096 Dec 11 12:57 dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da-init
drwx------ 5 root root 4096 Dec 11 12:57 dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da
注意这个文件夹有个 l
目录,这里面存放不同 layer 的软连接,但是名字很短,主要是用于 mount 挂载时能显示出来,其中 dcfb38888.....
就是我们的容器层,dcfb38888.....-init
是临时生成的只读层。
root@myali:/var/lib/docker/overlay2# ls -l dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da
total 20
drwxr-xr-x 3 root root 4096 Dec 11 15:44 diff
-rw-r--r-- 1 root root 26 Dec 11 12:57 link
-rw-r--r-- 1 root root 202 Dec 11 12:57 lower
drwxr-xr-x 1 root root 4096 Dec 11 15:44 merged
drwx------ 3 root root 4096 Dec 11 12:57 work
看下容器层里面的内容,diff
目录是保存运行时的改动,初始为空。link
文件里的内容是当前层的软连接短名,merged
就是合并后展现组容器的统一统目,work
是工作目录。
root@myali:/var/lib/docker/overlay2# cat dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da/lower
l/SR34NROUREC6U2N6HMTAOH66CZ:l/K535YPMQVAVYMUJUJ5ONJSTV6M:l/AHPVAYNKIZTOHPVOYPD7BU7J7V:l/IAFCF667EV4HZWTDYKYVHFGX4D:l/DRDJ5SXKMKS5SAXLFA6SGQBRSW:l/FYPOP5ERVXC7OGFHRU5NLUEBT5:l/6LZLHYDHXDIJGNIKISCFFGA5BP
再看下 lower
目录,里面都是依赖的所有下层 layer
5. 读写测试
测试在容器里分别写不存在的文件,修改文件,删除文件及目录
# touch bbbbbb
# echo "inner" > ccccc
# cat ccccc
inner
# rm root/config.json
# rm -fr mnt
查看容器层的 diff
内容
root@myali:/var/lib/docker/overlay2# tree -L 2 dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da/diff
dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da/diff
├── bbbbbb
├── ccccc
├── mnt
└── root
└── config.json
1 directory, 4 files
root@myali:/var/lib/docker/overlay2# file dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da/diff/mnt
dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da/diff/mnt: character special (0/0)
root@myali:/var/lib/docker/overlay2# file dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da/diff/root/config.json
dcfb388887b481f92add6fb4b0ac3b98d76ac9ebeebcaf26e19f1a9de8d5b9da/diff/root/config.json: character special (0/0)
可以看到,新增与改动都在 diff 下面,并且删除了文件和目录变成了 character special file
小结
镜像这一块比较复杂,还涉及到仓库,暂时先不看了
网友评论