背景
https://zhuanlan.zhihu.com/p/41958018
本系列Docker笔记将以基于TensorFlow的模型的训练与部署的具体场景为例,总结Docker的基本原理、安装、镜像制作、容器部署等。事实上,在学校实验室和自己的开源项目推进Docker使用已经快1年了,这篇文章之所以现在才开始写,纯粹是拖太久。
安装docker与nvidia-docker
首先,请以官网教程安装docker:
How to install docker on Ubuntu 16.04
如果你打算制作或者使用涉及到cuda或者cuddn等与GPU相关的镜像或容器,你需要安装nvidia-docker,请按照repo里的步骤安装nvidia-docker:
How to install nvidia-docker on Ubuntu 16.04
在完成两步安装后,需要更改docker守护进程默认的runtime参数,请将/etc/docker/daemon.json
文件中键default-runtime
对应的值修改为nvidia
,然后通过sudo service docker restart
重启docker服务,这一步操作是为了避免之后运行与cuda或者cudnn库相关的容器时每次都要指定runtime
参数的情况。
请务必确认docker被正确安装,接下来我们将从基本概念开始介绍docker,一直到模型部署。
基本概念
首先引用百度百科的定义:
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。
其次是官网的定义:
Docker is a platform for developers and sysadmins to develop, deploy, and run applications with containers.
其实百度百科的总结已经足够好了,更概括地,Docker是一个轻量级虚拟机制作、分发、部署工具。
Docker有两个非常重要的概念,他们分别是镜像(image)与容器(container),直观地,我们可以将容器类比为虚拟机,这个虚拟机可能是正在运行的,也可能是已经停止的,而镜像则是像配置文件一样定义了这些虚拟机如何运行。
而事实上事情要比上面的例子复杂很多,镜像和容器的本质一个文件系统:
在计算机中,文件系统(File System)是命名文件及放置文件的逻辑存储和恢复的系统。
我们首先将以Overlay2为例,详细介绍镜像与容器的区别和联系。
Overlay2
接下来将简要的介绍文件存储驱动overlay2,以便于更好的理解容器与镜像的关系。overlay2是Ubuntu上最新的Docker CE版本18.06.0上的默认存储驱动。上段提到,本质上镜像与容器都是文件系统,它们唯一的不同,就是镜像是只读的,而容器是可读可写的。
举个例子,我们通过以下命令获取Ubuntu的镜像:
➜ overlay2 docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
c64513b74145: Pull complete
01b8b12bad90: Pull complete
c5d85cf7a05f: Pull complete
b6b268720157: Pull complete
e12192999ff1: Pull complete
Digest: sha256:3f119dc0737f57f704ebecac8a6d8477b0f6ca1ca0332c7ee1395ed2c6a82be7
Status: Downloaded newer image for ubuntu:latest
➜ overlay2 docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 735f80812f90 2 weeks ago 83.5MB
可以看出,Ubuntu镜像具有5层,这5层都是只读的,我们可以在这个目录看到他们:
➜ overlay2 pwd
/var/lib/docker/overlay2
➜ overlay2 ll
total 24K
drwx------ 4 root root 4.0K 8月 13 19:52 0ba50fa3b79a5dc66ebb8f2939e77128b0ab7c3989fc776bd4268af366bd148 5
drwx------ 3 root root 4.0K 8月 13 19:52 225c757add2a395c0cfc47e1bc4472bf8fccf9dedd42f76f99b21c7637cb2a7 6
drwx------ 4 root root 4.0K 8月 13 19:53 40b4fd1a0dea16978cffe5f26deee9a5834c76752db8c3b2a86057037a12b5f 5
drwx------ 4 root root 4.0K 8月 13 19:52 7bb93daf624b3fc798554d36b75940fced7713f0d165631131432e230718555 9
drwx------ 4 root root 4.0K 8月 13 19:52 ea274fcefed09dd48b0c6baa45e66bcd00887d4abbddbee1ef804c9dc7cfba4 e
drwxr-xr-x 2 root root 4.0K 8月 13 19:53 l
以及这个文件夹:
➜ l pwd
/var/lib/docker/overlay2/l
➜ l ll
total 20K
lrwxrwxrwx 1 root root 72 8月 13 19:52 IBZJKDU6Z76YR2ZDYWHDVUVHEZ -> ../0ba50fa3b79a5dc66ebb8f2939e77128 b0ab7c3989fc776bd4268af366bd1485/diff
lrwxrwxrwx 1 root root 72 8月 13 19:52 NRJEUKQPNXJQSQBLGCULIHRT77 -> ../7bb93daf624b3fc798554d36b75940fc ed7713f0d165631131432e2307185559/diff
lrwxrwxrwx 1 root root 72 8月 13 19:52 UXY6233J7FGSMGWJ2KJKU4Z6U3 -> ../225c757add2a395c0cfc47e1bc4472bf 8fccf9dedd42f76f99b21c7637cb2a76/diff
lrwxrwxrwx 1 root root 72 8月 13 19:52 VYQ3FKAYCIQNRXABOBPQ3ACEEH -> ../ea274fcefed09dd48b0c6baa45e66bcd 00887d4abbddbee1ef804c9dc7cfba4e/diff
lrwxrwxrwx 1 root root 72 8月 13 19:53 ZFT2GFUH6ZW3BMC3A4VY7S6HZV -> ../40b4fd1a0dea16978cffe5f26deee9a5 834c76752db8c3b2a86057037a12b5f5/diff
可以发现,全部都是到各层diff
之间的软链接,以IBZJKDU6Z76YR2ZDYWHDVUVHEZ
为例,我们观察一下这个链接目录:
➜ l cd IBZJKDU6Z76YR2ZDYWHDVUVHEZ
➜ IBZJKDU6Z76YR2ZDYWHDVUVHEZ ll
total 4.0K
drwxr-xr-x 3 root root 4.0K 7月 27 06:20 etc
发现除了etc
文件夹之外空空如也,再以NRJEUKQPNXJQSQBLGCULIHRT77
为例,观察一下这个符号链接目录的内容:
➜ NRJEUKQPNXJQSQBLGCULIHRT77 ll
total 16K
drwxr-xr-x 4 root root 4.0K 7月 27 06:20 etc
drwxr-xr-x 2 root root 4.0K 7月 27 06:20 sbin
drwxr-xr-x 3 root root 4.0K 7月 25 04:51 usr
drwxr-xr-x 3 root root 4.0K 7月 25 04:53 var
可以发现以上内容。事实上,每层的diff
即是文件系统在统一挂载时的挂载点,我们可以再进一步地观察下一层,UXY6233J7FGSMGWJ2KJKU4Z6U3
的内容:
➜ UXY6233J7FGSMGWJ2KJKU4Z6U3 ll
total 76K
drwxr-xr-x 2 root root 4.0K 7月 25 04:53 bin
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 boot
drwxr-xr-x 4 root root 4.0K 7月 25 04:51 dev
drwxr-xr-x 29 root root 4.0K 7月 25 04:53 etc
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 home
drwxr-xr-x 8 root root 4.0K 7月 25 04:51 lib
drwxr-xr-x 2 root root 4.0K 7月 25 04:52 lib64
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 media
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 mnt
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 opt
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 proc
drwx------ 2 root root 4.0K 7月 25 04:53 root
drwxr-xr-x 4 root root 4.0K 7月 25 04:51 run
drwxr-xr-x 2 root root 4.0K 7月 25 04:53 sbin
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 srv
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 sys
drwxrwxrwt 2 root root 4.0K 7月 25 04:53 tmp
drwxr-xr-x 10 root root 4.0K 7月 25 04:51 usr
drwxr-xr-x 11 root root 4.0K 7月 25 04:53 var
可以发现这一层仿佛就是一个Ubuntu了。到这里我们可以知道,镜像是由多个层组织并定义的,这些层本质上是文件,这些文件是只读的,每层具体的文件存放在层标识符下的diff
目录下。接下来我们将介绍他们是如何被组织起来的。
回过头来,我们继续观察层标识符目录:
➜ overlay2 pwd
/var/lib/docker/overlay2
➜ overlay2 ll
total 24K
drwx------ 4 root root 4.0K 8月 13 19:52 0ba50fa3b79a5dc66ebb8f2939e77128b0ab7c3989fc776bd4268af366bd148 5
drwx------ 3 root root 4.0K 8月 13 19:52 225c757add2a395c0cfc47e1bc4472bf8fccf9dedd42f76f99b21c7637cb2a7 6
drwx------ 4 root root 4.0K 8月 13 19:53 40b4fd1a0dea16978cffe5f26deee9a5834c76752db8c3b2a86057037a12b5f 5
drwx------ 4 root root 4.0K 8月 13 19:52 7bb93daf624b3fc798554d36b75940fced7713f0d165631131432e230718555 9
drwx------ 4 root root 4.0K 8月 13 19:52 ea274fcefed09dd48b0c6baa45e66bcd00887d4abbddbee1ef804c9dc7cfba4 e
drwxr-xr-x 2 root root 4.0K 8月 13 19:53 l
接着我们进入225c757add2a395c0cfc47e1bc4472bf8fccf9dedd42f76f99b21c7637cb2a7
这个目录,观察一下目录结构:
➜ 225c757add2a395c0cfc47e1bc4472bf8fccf9dedd42f76f99b21c7637cb2a76 tree . -L 2
.
├── diff
│ ├── bin
│ ├── boot
│ ├── dev
│ ├── etc
│ ├── home
│ ├── lib
│ ├── lib64
│ ├── media
│ ├── mnt
│ ├── opt
│ ├── proc
│ ├── root
│ ├── run
│ ├── sbin
│ ├── srv
│ ├── sys
│ ├── tmp
│ ├── usr
│ └── var
└── link
20 directories, 1 file
好像没什么特别的,接着我们进入0ba50fa3b79a5dc66ebb8f2939e77128b0ab7c3989fc776bd4268af366bd148
这个目录,观察一下目录结构:
➜ overlay2 cd 0ba50fa3b79a5dc66ebb8f2939e77128b0ab7c3989fc776bd4268af366bd1485
➜ 0ba50fa3b79a5dc66ebb8f2939e77128b0ab7c3989fc776bd4268af366bd1485 tree .
.
├── diff
│ └── etc
│ └── apt
│ └── sources.list
├── link
├── lower
└── work
4 directories, 3 files
➜ 0ba50fa3b79a5dc66ebb8f2939e77128b0ab7c3989fc776bd4268af366bd1485 cat lower
l/VYQ3FKAYCIQNRXABOBPQ3ACEEH:l/NRJEUKQPNXJQSQBLGCULIHRT77:l/UXY6233J7FGSMGWJ2KJKU4Z6U3#
➜ 0ba50fa3b79a5dc66ebb8f2939e77128b0ab7c3989fc776bd4268af366bd1485 cat link
IBZJKDU6Z76YR2ZDYWHDVUVHEZ#
可以看出,link
文件描述了该层标识符的精简版,而lower
文件描述了层序的组织关系。接着我们通过以下命令启动一个容器:
➜ 0ba50fa3b79a5dc66ebb8f2939e77128b0ab7c3989fc776bd4268af366bd1485 docker run -it ubuntu
然后通过以下命令观察overlay2联合挂载情况:
root@7d01751deb92:/# mount | grep overlay
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/FSK5KQSBSQH67GQ5IEWQKL4YPF:/var/lib/docker/overlay2/l/ZFT2GFUH6ZW3BMC3A4VY7S6HZV:/var/lib/docker/overlay2/l/IBZJKDU6Z76YR2ZDYWHDVUVHEZ:/var/lib/docker/overlay2/l/VYQ3FKAYCIQNRXABOBPQ3ACEEH:/var/lib/docker/overlay2/l/NRJEUKQPNXJQSQBLGCULIHRT77:/var/lib/docker/overlay2/l/UXY6233J7FGSMGWJ2KJKU4Z6U3,upperdir=/var/lib/docker/overlay2/3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da/diff,workdir=/var/lib/docker/overlay2/3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da/work)
root@7d01751deb92:/#
我们可以观察到一些关键信息,例如lowerdir
,可以看到是这些层标识符:
FSK5KQSBSQH67GQ5IEWQKL4YPF
ZFT2GFUH6ZW3BMC3A4VY7S6HZV
IBZJKDU6Z76YR2ZDYWHDVUVHEZ
VYQ3FKAYCIQNRXABOBPQ3ACEEH
NRJEUKQPNXJQSQBLGCULIHRT77
UXY6233J7FGSMGWJ2KJKU4Z6U3
这时我们再观察overlay2
文件夹,发现在该文件夹和l
文件夹都多出了2个标识符:
➜ overlay2 ls | grep 389
3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da
3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da-init
➜ l ll | grep 389
lrwxrwxrwx 1 root root 77 8月 13 20:26 FSK5KQSBSQH67GQ5IEWQKL4YPF -> ../3895f4ddbd45f65e509ed996d39536d1 737647bf1b70c2b9c82b6765b2e376da-init/diff
lrwxrwxrwx 1 root root 72 8月 13 20:26 O5NQ7PKEES3VHMHNZAZHE54M2C -> ../3895f4ddbd45f65e509ed996d39536d1 737647bf1b70c2b9c82b6765b2e376da/diff
这一层是动态生成的,观察其目录结构:
➜ 3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da-init tree . -L 4
.
├── diff
│ ├── dev
│ │ └── console
│ └── etc
│ ├── hostname
│ ├── hosts
│ ├── mtab -> /proc/mounts
│ └── resolv.conf
├── link
├── lower
└── work
└── work
5 directories, 7 files
可以看出,它主要是一些配置文件构成的层。而不带init
后缀的3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da
的情况就比较特殊了:
➜ 3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da tree . -L 2
.
├── diff
├── link
├── lower
├── merged
│ ├── bin
│ ├── boot
│ ├── dev
│ ├── etc
│ ├── home
│ ├── lib
│ ├── lib64
│ ├── media
│ ├── mnt
│ ├── opt
│ ├── proc
│ ├── root
│ ├── run
│ ├── sbin
│ ├── srv
│ ├── sys
│ ├── tmp
│ ├── usr
│ └── var
└── work
└── work
23 directories, 2 files
➜ 3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da cat lower
l/FSK5KQSBSQH67GQ5IEWQKL4YPF:l/ZFT2GFUH6ZW3BMC3A4VY7S6HZV:l/IBZJKDU6Z76
回忆一下225c757add2a395c0cfc47e1bc4472bf8fccf9dedd42f76f99b21c7637cb2a7
这个标识符:
➜ 225c757add2a395c0cfc47e1bc4472bf8fccf9dedd42f76f99b21c7637cb2a76 tree . -L 2
.
├── diff
│ ├── bin
│ ├── boot
│ ├── dev
│ ├── etc
│ ├── home
│ ├── lib
│ ├── lib64
│ ├── media
│ ├── mnt
│ ├── opt
│ ├── proc
│ ├── root
│ ├── run
│ ├── sbin
│ ├── srv
│ ├── sys
│ ├── tmp
│ ├── usr
│ └── var
└── link
20 directories, 1 file
可以看出,3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da
这标识符多了一个文件夹merged
,与225c757add2a395c0cfc47e1bc4472bf8fccf9dedd42f76f99b21c7637cb2a76
的diff
文件夹相似,它正是容器的可读可写层。讲到这里,我们在回头来观察overlay2联合挂载情况:
root@7d01751deb92:/# mount | grep overlay
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/FSK5KQSBSQH67GQ5IEWQKL4YPF:/var/lib/docker/overlay2/l/ZFT2GFUH6ZW3BMC3A4VY7S6HZV:/var/lib/docker/overlay2/l/IBZJKDU6Z76YR2ZDYWHDVUVHEZ:/var/lib/docker/overlay2/l/VYQ3FKAYCIQNRXABOBPQ3ACEEH:/var/lib/docker/overlay2/l/NRJEUKQPNXJQSQBLGCULIHRT77:/var/lib/docker/overlay2/l/UXY6233J7FGSMGWJ2KJKU4Z6U3,upperdir=/var/lib/docker/overlay2/3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da/diff,workdir=/var/lib/docker/overlay2/3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da/work)
root@7d01751deb92:/#
我们可以看出,overlay2将lowerdir
、upperdir
、workdir
联合挂载,形成最终的merged
挂载点,其中lowerdir
是镜像只读层,upperdir
是容器可读可写层,workdir
是执行涉及修改lowerdir
执行copy_up
操作的中转层(例如,upperdir
中不存在,需要从lowerdir
中进行复制,该过程暂未详细了解,遇到了再分析),接着我们可以做一个实验,我们在容器中通过以下命令创建一个文件:
root@7d01751deb92:/# touch test.txt
接下来我们观察容器的可读写层,与镜像的只读层:
➜ 3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da ll diff
total 0
-rw-r--r-- 1 root root 0 8月 13 20:54 test.txt
➜ 3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da ll merged
total 76K
drwxr-xr-x 2 root root 4.0K 7月 25 04:53 bin
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 boot
drwxr-xr-x 1 root root 4.0K 8月 13 20:26 dev
drwxr-xr-x 1 root root 4.0K 8月 13 20:26 etc
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 home
drwxr-xr-x 8 root root 4.0K 7月 25 04:51 lib
drwxr-xr-x 2 root root 4.0K 7月 25 04:52 lib64
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 media
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 mnt
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 opt
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 proc
drwx------ 2 root root 4.0K 7月 25 04:53 root
drwxr-xr-x 1 root root 4.0K 7月 27 06:20 run
drwxr-xr-x 1 root root 4.0K 7月 27 06:20 sbin
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 srv
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 sys
-rw-r--r-- 1 root root 0 8月 13 20:54 test.txt
drwxrwxrwt 2 root root 4.0K 7月 25 04:53 tmp
drwxr-xr-x 1 root root 4.0K 7月 25 04:51 usr
drwxr-xr-x 1 root root 4.0K 7月 25 04:53 var
➜ 3895f4ddbd45f65e509ed996d39536d1737647bf1b70c2b9c82b6765b2e376da ll ../225c757add2a395c0cfc47e1bc4472bf8fccf9dedd42f76f99b21c7637cb2a76/diff
total 76K
drwxr-xr-x 2 root root 4.0K 7月 25 04:53 bin
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 boot
drwxr-xr-x 4 root root 4.0K 7月 25 04:51 dev
drwxr-xr-x 29 root root 4.0K 7月 25 04:53 etc
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 home
drwxr-xr-x 8 root root 4.0K 7月 25 04:51 lib
drwxr-xr-x 2 root root 4.0K 7月 25 04:52 lib64
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 media
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 mnt
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 opt
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 proc
drwx------ 2 root root 4.0K 7月 25 04:53 root
drwxr-xr-x 4 root root 4.0K 7月 25 04:51 run
drwxr-xr-x 2 root root 4.0K 7月 25 04:53 sbin
drwxr-xr-x 2 root root 4.0K 7月 25 04:51 srv
drwxr-xr-x 2 root root 4.0K 4月 24 16:34 sys
drwxrwxrwt 2 root root 4.0K 7月 25 04:53 tmp
drwxr-xr-x 10 root root 4.0K 7月 25 04:51 usr
drwxr-xr-x 11 root root 4.0K 7月 25 04:53 var
可以发现,新创建的文件被存在了上述位置,而此时如果我们通过以下命令:
docker commit CONTAINER_ID
提交容器更改,则会将该容器的当前可读可写层转化为只读层,更新镜像。总结一下,镜像大体上,可以认为是多个只读层通过某些特定的方式组织起来,而容器则是在其之上的一个可读写层,我们可以保存一个可读写层的更改,将它转化为一个只读层。
以上是对镜像与容器,及其存储驱动overlay2的一个简要概述。