Docker是如何工作的?这是一个简单的问题,但答案却是出乎意料的复杂。你可能很多次地听说过“守护进程(daemon)”和“运行时(runtime)”这两个术语,但可能从未真正理解它们的含义以及它们是如何配合在一起的。如果你像我一样,涉过源头去发现真相,那么,在你沉溺于代码之海时,你并不孤单。
2013年,dotCloud公司的Solomon Hykes在那年的Python大会上发表了Linux容器的未来的演讲,第一次将Docker带入了公众的视线。让我们将他的git代码库回溯到2013年1月,这个Docker开发更轻松的时间。
Docker由两个主要组件组成,一个供用户使用的命令行应用程序和一个管理容器的守护进程。这个守护进程依赖两个子组件来执行它的任务:在宿主主机文件系统上用来存储镜像和容器数据的存储组件;以及用于抽象原始内核调用来构建Linux容器的LXC接口。
命令行应用程序
Docker命令行应用程序是管理你的Docker运行副本已知的所有镜像和容器的人工界面。它相对简单,因为所有的管理都是由守护进程完成的。应用程序开始于一个main 函数:
它会立即建立一个TCP连接,指向存储在DOCKER这个环境变量中的地址,这是Docker守护进程的地址。用户提供的参数发送给守护进程,然后命令行应用程序等待打印成功答复的结果。
dockerd
Docker守护进程的代码存放在同一个代码库中,这个进程称为dockerd。它运行在后台来处理用户请求和容器清理工作。启动后,dockerd将监听在8080端口上传入的HTTP连接和在4242端口上的TCP连接。
一旦命令接收到后,dockerd将使用反射机制查找并调用要运行的函数。
docker run命令
其中一个运行的函数是CmdRun,它对应这个docker run(注:这个命令创建一个新的容器并运行一个命令)命令。
用户通常会提供一个镜像和一个命令,以便dockerd运行。当它们被省略时,将默认使用镜像base和命令/bin/bash。
查找镜像
然后,我们通过将名称(或ID)映射到文件系统上的一个位置来找到指定的镜像(假设前面已经执行过docker pull (注:这个命令从镜像仓库中拉取或者更新指定镜像)命令,镜像已经生成)。
在这个版本的Docker中,所有镜像都存储在/var/lib/docker/images文件夹中。想进一步了解Docker镜像中有什么,请参阅文章(https://cameronlonsdale.com/2018/11/26/whats-in-a-docker-image/)。
创建容器
接着我们开始创建容器。dockerd创建了一个结构(struct)来保存与这个容器相关的所有元数据,并将它存储在一个列表中以便于访问。
当创建这个struct时,dockerd 会在下列路径为容器创建下面这个唯一目录:/var/lib/docker/containers/。在这个目录下中有两个子目录:/rootfs 只读根文件系统层(来自联合挂载(union mounted)的镜像),/rw提供一个单独的读写层,供容器来创建临时文件。
最后,使用我们新创建的容器数据填充一个模板,用以生成LXC 配置文件。更多关于LXC的信息,请参见下一节。
运行容器
我们的容器终于创建出来了!但它还没有运行,现在让我们启动它。