Container 本来也不是什么新技术,为什么 Docker 就火了,而前辈们如 lxc、OpenVZ 等没这么火。其实一部分原因得益于 Docker 的 Image 管理。Docker 借鉴了 vm 的方式,让用户像管理 vm 一样的管理他们的 container 镜像,并且也同样叫做 Image。在实现上,Docker 利用 container 的 Rootfs 是从 host 上挂载的、并且能挂载多个目录这个特点,将 Docker Image 分成多个小块(这是按照 vm Image 的思维来说的,实际上这多个小块,每个都是一个 Image,最终使用的是一个 Image 组合),方便管理与共享。
我们已经了解了 Container 是什么,那么 Image 是怎么转换为 Container 的 Rootfs 的? Image 本身是怎么在磁盘上存储的?带着这些疑问,我们一起来看看 Docker 的实现。
磁盘上的 image
先来看看磁盘中存储的 image 是什么样子的,这里以 aufs 为例, devicemapper 的存储形式和 aufs 还不太一样,后续有空再来分析。
现在我们有一个 image:
【plain】1.ubuntu@ubuntu:~$ sudo docker images
2.[sudo] password for ubuntu:
3.REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
4. ubuntu-12.04 latest 212e4aaf49e7 25 minutes ago 172.7 MB
对应的 /var/lib/docker 目录下就有这些东西:
image
【plain】
1. ubuntu@ubuntu:~$ sudo ls -lh /var/lib/docker
2. total 48K
3. drwxr-xr-x 2 root root 4.0K Apr 16 22:26 apparmor
4. drwxr-xr-x 5 root root 4.0K Jun 21 16:14 aufs
5. drwx------ 3 root root 4.0K Jun 22 13:01 containers
6. drwx------ 5 root root 4.0K Jun 22 12:59 devicemapper
7. drwx------ 3 root root 4.0K Apr 16 22:16 execdriver
8. drwx------ 6 root root 4.0K Jul 13 11:32 graph
9. drwx------ 2 root root 4.0K Jul 13 11:40 init
10. -rw-r--r-- 1 root root 5.0K Jun 22 13:01 linkgraph.db
11. -rw------- 1 root root 111 Jul 13 11:32 repositories-aufs
12. -rw------- 1 root root 180 Apr 16 22:56 repositories-devicemapper
13. drwx------ 2 root root 4.0K Apr 16 22:16 volumes
抛开其他的不管,现在只关心里面的 aufs, graph 目录和 repositories-aufs 文件(如果是 devicemapper,则是 devicemapper 目录和 repositories-devicemapper 文件,graph 为共用的目录)。
aufs 目录的结构如下:
【plain】
1.ubuntu@ubuntu:~$ sudo tree /var/lib/docker/aufs -L 3
2. /var/lib/docker/aufs
3. |-- diff
4. | `-- 212e4aaf49e7cde3c280d05f1a6bd74ecf66ad83917fa757137deb0dae82806d
5. | |-- bin
6. | |-- boot
7. | |-- dev
8. | |-- etc
9. | |-- home
10. | |-- lib
11. | |-- lib64
12. | |-- media
13. | |-- mnt
14. | |-- opt
15. | |-- proc
16. | |-- root
17. | |-- run
18. | |-- sbin
19. | |-- selinux
20. | |-- srv
21. | |-- sys
22. | |-- tmp
23. | |-- usr
24. | `-- var
25. |-- layers
26. | `-- 212e4aaf49e7cde3c280d05f1a6bd74ecf66ad83917fa757137deb0dae82806d
27. `-- mnt
28. `-- 212e4aaf49e7cde3c280d05f1a6bd74ecf66ad83917fa757137deb0dae82806d
layers 目录和 mnt 目录里面目前还只有一个空目录,后面再来看。这里主要是 diff 目录下对应 image 的目录,image 内部的所有文件都在这。
而 graph 目录的结构如下:
【plain】
1. ubuntu@ubuntu:~$ sudo tree /var/lib/docker/graph -L 3
2. /var/lib/docker/graph
3. |-- 212e4aaf49e7cde3c280d05f1a6bd74ecf66ad83917fa757137deb0dae82806d
4. | |-- json
5. | `-- layersize
6. |-- 317c1f4475ad860bdcf0e529fd03f181b2c6b64e5b78358051efc94c8f728cd7
7. | |-- json
8. | `-- layersize
9. |-- bcd86fdd0ba0b84f10f4539f99e8730958fc1028d35495f8dd11ae1913370e42
10. | |-- json
11. | `-- layersize
12. `-- _tmp
这里只看 212e 这个 image,另外两个 image 其实是 devicemapper 下的 image,所有 image 的描述都放在 graph 目录下。json 文件为 image 的描述文件,主要是创建这个 image 的 container 配置信息, layersize 文件内为该 image 的大小。
最后看看 repositories-aufs 文件:
[plain]
1. ubuntu@ubuntu:~$ sudo cat /var/lib/docker/repositories-aufs |python -m json.tool
2. {
3. "Repositories": {
4. "ubuntu-12.04": {
5. "latest": "212e4aaf49e7cde3c280d05f1a6bd74ecf66ad83917fa757137deb0dae82806d"
6. }
7. }
8. }
其实就是描述在 aufs 模式下有哪些 image 可用。
了解了 image 包括哪些东西,接下来再看看这些东西是怎么用的。
从 Image 到 Container
当我们通过命令行 docker run 创建一个 container 并运行时,其中就经历了从 image 到 container rootfs 的转化,蓝图如下(省略了其他操作):
上图横轴为执行流程,纵轴是对每个过程的解释。
因为 docker run 命令实际的执行主要分两步:create 和 start。在 create 过程中,主要是将 image 挂载到 /var/lib/docker/aufs/mnt/:id-init 目录 (:id 为container id ),并且取消掉对某些文件/文件夹的挂载,重新创建一份新的。这里的文件/文件夹主要为:
[plain]
1. "/dev/pts": "dir",
2. "/dev/shm": "dir",
3. "/proc": "dir",
4. "/sys": "dir",
5. "/.dockerinit": "file",
6. "/.dockerenv": "file",
7. "/etc/resolv.conf": "file",
8. "/etc/hosts": "file",
9. "/etc/hostname": "file",
10. "/dev/console": "file",
11. "/etc/mtab": "/proc/mounts",
然后在 start 流程中,主要是生成 container 真正的 rootfs 目录 /var/lib/docker/aufs/mnt/:id, 然后又将几个 /host 上的目录挂载到 rootfs,主要是 /etc/hosts, /etc/hostname, /etc/resolv.conf 等。
(其实这里没看明白为啥这几个文件要这样处理,也没看明白为什么要用:id-init 目录隔一层。主要是对 aufs 不熟悉,后面再学习一下。)
现在来看看,当我们创建了一个 container 后,aufs 目录有什么变化:
[plain]
1. ubuntu@ubuntu:~$ sudo tree -L 3 /var/lib/docker/aufs
2.[sudo] password for ubuntu:
3. /var/lib/docker/aufs
4. |-- diff
5. | |-- 09b1be94a4444076375b7cb386609990622a1746802858ea85f1de607c1e5167
6. | |-- 09b1be94a4444076375b7cb386609990622a1746802858ea85f1de607c1e5167-init
7. | | |-- dev
8. | | `-- etc
9. | `-- 212e4aaf49e7cde3c280d05f1a6bd74ecf66ad83917fa757137deb0dae82806d
10. | |-- bin
11. | |-- boot
12. | |-- dev
13. | |-- etc
14. | |-- home
15. | |-- lib
16. | |-- lib64
17. | |-- media
18. | |-- mnt
19. | |-- opt
20. | |-- proc
21. | |-- root
22. | |-- run
23. | |-- sbin
24. | |-- selinux
25. | |-- srv
26. | |-- sys
27. | |-- tmp
28. | |-- usr
29. | `-- var
30. |-- layers
31. | |-- 09b1be94a4444076375b7cb386609990622a1746802858ea85f1de607c1e5167
32. | |-- 09b1be94a4444076375b7cb386609990622a1746802858ea85f1de607c1e5167-init
33. | `-- 212e4aaf49e7cde3c280d05f1a6bd74ecf66ad83917fa757137deb0dae82806d
34. `-- mnt
35. |-- 09b1be94a4444076375b7cb386609990622a1746802858ea85f1de607c1e5167
36. | |-- bin
37. | |-- boot
38. | |-- dev
39. | |-- etc
40. | |-- home
41. | |-- lib
42. | |-- lib64
43. | |-- media
44. | |-- mnt
45. | |-- opt
46. | |-- proc
47. | |-- root
48. | |-- run
49. | |-- sbin
50. | |-- selinux
51. | |-- srv
52. | |-- sys
53. | |-- tmp
54. | |-- usr
55. | `-- var
56. |-- 09b1be94a4444076375b7cb386609990622a1746802858ea85f1de607c1e5167-init
57. `-- 212e4aaf49e7cde3c280d05f1a6bd74ecf66ad83917fa757137deb0dae82806d
这里创建了一个 id 为 09b1 的 container,其中 /aufs/mnt/09b1xxx 为 rootfs, /aufs/layers 目录下的文件,主要存储 image 的 parents image id。
小结:
Docker 的 image 管理涉及到不少 linux 文件系统使用的细节,对 linux 文件系统掌握得不深的话,这部分就只能看个大概的流程。而且这只是其中的 aufs 部分,其他的 devicemapper、btrfs、vfs 部分的都不太一样。下来先补习一下 linux 文件系统知识,再回过头来看看 docker 的 image 管理,可能会稍微轻松些。