盐开水 發表於 2018-6-7 07:59:00

从 docker 到 runC

<p><span style="font-family: Microsoft YaHei; font-size: 15px">笔者在前文《RunC 简介》和《Containerd 简介》中分别介绍了 runC 和 containerd。本文我们将结合 docker 中的其它组件探索 docker 是如何把这些组件组织起来协调工作的。</span></p>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">Docker 的主要组件</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">安装 docker ,其实是安装了 docker 客户端、dockerd 等一系列的组件,其中比较重要的有下面几个。</span></p>
<p><strong><span style="font-family: Microsoft YaHei; font-size: 15px">Docker CLI(docker)</span></strong><br><span style="font-family: Microsoft YaHei; font-size: 15px">docker 程序是一个客户端工具,用来把用户的请求发送给 docker daemon(dockerd)。该程序的安装路径为:</span></p>
<div class="cnblogs_code">
<pre>/usr/bin/<span style="color: rgba(255, 0, 0, 1)">docker</span></pre>
</div>
<p><strong><span style="font-family: Microsoft YaHei; font-size: 15px">Dockerd</span></strong><br><span style="font-family: Microsoft YaHei; font-size: 15px">docker daemon(dockerd),一般也会被称为 docker engine。该程序的安装路径为:</span></p>
<div class="cnblogs_code">
<pre>/usr/bin/<span style="color: rgba(255, 0, 0, 1)">dockerd</span></pre>
</div>
<p><strong><span style="font-family: Microsoft YaHei; font-size: 15px">Containerd</span></strong><br><span style="font-family: Microsoft YaHei; font-size: 15px">详情请参考《Containerd 简介》。该程序的安装路径为:</span></p>
<div class="cnblogs_code">
<pre>/usr/bin/<span style="color: rgba(255, 0, 0, 1)">docker-containerd</span></pre>
</div>
<p><strong><span style="font-family: Microsoft YaHei; font-size: 15px">Containerd-shim</span></strong><br><span style="font-family: Microsoft YaHei; font-size: 15px">它是 containerd 的组件,是容器的运行时载体,我们在 docker 宿主机上看到的 shim 也正是代表着一个个通过调用 containerd 启动的 docker 容器。该程序的安装路径为:</span></p>
<div class="cnblogs_code">
<pre>/usr/bin/<span style="color: rgba(255, 0, 0, 1)">docker-containerd-shim</span></pre>
</div>
<p><strong><span style="font-family: Microsoft YaHei; font-size: 15px">RunC</span></strong><br><span style="font-family: Microsoft YaHei; font-size: 15px">详情请参考《RunC 简介》。该程序的安装路径为:</span></p>
<div class="cnblogs_code">
<pre>/usr/bin/<span style="color: rgba(255, 0, 0, 1)">docker-runc</span></pre>
</div>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">从 hello world 开始</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">Docker 很贴心的为我们提供了 hello-world 镜像来验证安装是否成功,但是透过这个镜像我们还能看到更多的信息:</span></p>
<div class="cnblogs_code">
<pre>$ docker run hello-world</pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px"><img src="https://images2018.cnblogs.com/blog/952033/201806/952033-20180603152346309-1942589255.png"></span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">上面的输出信息指出,hello-world 容器的运行经历了如下四步:</span></p>
<ol>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">Docker 客户端向 docker daemon 发送请求</span></li>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">Docker daemon 从 Docker Hub 上拉取镜像</span></li>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">Docker daemon 使用镜像运行了一个容器并产生了输出</span></li>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">Docker daemon 把输出的内容发送给了 docker 客户端</span></li>
</ol>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">这是一个很抽象也很容器理解的过程,但是我们还想知道更多:docker daemon 是如何创建并运行容器的?</span><br><span style="font-family: Microsoft YaHei; font-size: 15px">其实容器部分的操作和管理都被 dockerd 外包给 containerd 了,下图描述了运行一个容器时各个组件之间的关系:</span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px"><img src="https://images2018.cnblogs.com/blog/952033/201806/952033-20180603152512054-430725545.png"></span></p>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">Docker Engine API</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">从本质上说,docker 是一个客户端/服务器架构的应用。Dockerd 以 Engine API (REST)的方式对外提供服务,Engine API 里描述了 dockerd 支持的所有请求。Docker 客户端与 dockerd 之间就是通过 REST 的方式通信的。在 ubuntu 16.04 中,dockerd 默认是不监听 tcp 端口的,为了方便演示,我们让 dockerd 监听 tcp 端口。这样就可以使用 curl 代替 docker 客户端向 dockerd 发送请求了。具体的操作为,先</span><span style="font-family: Microsoft YaHei; font-size: 15px">修改 /lib/systemd/system/docker.service 文件,注释掉默认的 ExecStart 并添加新的 ExecStart 配置:</span></p>
<div class="cnblogs_code">
<pre># <span style="color: rgba(0, 0, 0, 1)">ExecStart=/usr/bin/dockerd -H fd://
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock</span></pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">然后重启 docker.service:</span></p>
<div class="cnblogs_code">
<pre>$ <span style="color: rgba(0, 0, 255, 1)">sudo</span> systemctl daemon-<span style="color: rgba(0, 0, 0, 1)">reload
$ </span><span style="color: rgba(0, 0, 255, 1)">sudo</span> systemctl restart docker.service</pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">这样 dockerd 就开始监听 tcp 端口 2375 了:</span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px"><img src="https://images2018.cnblogs.com/blog/952033/201806/952033-20180603152626162-740443473.png"></span></p>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">Docker 与 Dockerd 的交互</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">Docker 客户端与 dockerd 之间就是通过 REST 的方式通信的。前面我们已经让 dockerd 监听 tcp 端口了,所以我们可以使用 curl 来代替 docker 客户端。这里我们简单的演示如何请求 dockerd 从 docker hub 上下载 hello-world 镜像:</span></p>
<div class="cnblogs_code">
<pre>$ curl <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">127.0.0.1:2375/v1.37/images/create?fromImage=hello-world&amp;tag=latest</span><span style="color: rgba(128, 0, 0, 1)">'</span> -X POST</pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px"><img src="https://images2018.cnblogs.com/blog/952033/201806/952033-20180603152711372-1605324742.png"></span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">如果去看看 Engine API,你会发现其它的请求也都是用类似方式发送的,是不是很简单啊!</span></p>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">创建容器</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">容器镜像的下载是由 dockerd 完成的,但容器的创建和运行就需要 <span style="color: rgba(255, 0, 0, 1)">containerd(docker-containerd)</span> 来完成了。Dockerd 与 docker-containerd 之间是通过 grpc 协议通信的。当 docker-containerd 收到 dockerd 启动容器的请求之后,会做一些初始化工作,然后启动 <span style="color: rgba(255, 0, 0, 1)">docker-containerd-shim</span> 进程,并将相关配置作为参数传给它。docker-containerd 负责管理所有本机正在运行的容器,而一个 docker-containerd-shim 进程只负责管理一个运行的容器,它相当于 <span style="color: rgba(255, 0, 0, 1)">docker-runc</span> 的一个封装,充当 docker-containerd 和 docker-runc 之间的桥梁,docker-runc 能干的就交给 docker-runc 来做,docker-runc 做不了的就放到这里来做。下面我们用 ubuntu 镜像运行一个容器:</span></p>
<div class="cnblogs_code">
<pre>$ docker run -<span style="color: rgba(0, 0, 255, 1)">id</span> ubuntu bash</pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px"><img src="https://images2018.cnblogs.com/blog/952033/201806/952033-20180603152811209-792603130.png"></span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">上图中黄线框起来的是几个主要的进程,它们之间是有父子关系的(systemd 没有出现在上图):</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><strong><span style="color: rgba(255, 0, 0, 1)">systemd</span>---<span style="color: rgba(255, 0, 0, 1)">dockerd</span>---<span style="color: rgba(255, 0, 0, 1)">docker-containerd</span>---<span style="color: rgba(255, 0, 0, 1)">docker-containerd-shim</span>---<span style="color: rgba(255, 0, 0, 1)">bash</span></strong></span></pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">细心的朋友一定发现了,上图中没有出现 docker-runc 进程,这是为什么呢?</span><br><span style="font-family: Microsoft YaHei; font-size: 15px">实际上,在容器启动的过程中,docker-runc 进程是作为 docker-containerd-shim 的子进程存在的。docker-runc 进程根据配置找到容器的 rootfs 并创建子进程 bash 作为容器中的第一个进程。当这一切都完成后 docker-runc 进程退出,然后容器进程 bash 由 docker-runc 的父进程 docker-containerd-shim 接管。</span></p>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">为什么需要 docker-containerd-shim?</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">也许大家会问,为什么在容器的启动或运行过程中需要一个 docker-containerd-shim 进程呢?把它移除掉整个架构会更简洁也更优美一些!事实上 docker-containerd-shim 的存在是非常有必要的,其目的有如下几点:</span></p>
<ul>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">它允许容器运行时(即 runC)在启动容器之后退出,简单说就是不必为每个容器一直运行一个容器运行时(runC)</span></li>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">即使在 containerd 和 dockerd 都挂掉的情况下,容器的标准 IO 和其它的文件描述符也都是可用的</span></li>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">向 containerd 报告容器的退出状态</span></li>







</ul>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">前两点尤其重要,有了它们就可以在不中断容器运行的情况下升级或重启 dockerd(这对于生产环境来说意义重大)。 从这里可以看到对 containerd-shim 的一些解释。</span></p>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">总结</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">笔者先在前文《RunC 简介》和《Containerd 简介》中分别介绍了 runC 和 containerd 等 docker 的核心组件。本文则通过 demo 演示了在创建、运行容器的过程中这些组件如何配合 docker engine 完成相关的任务,以及相关进程之间的关系和作用。希望本文可以帮助大家理解 docker 的整体架构及其组件间的协作方式。</span></p>
<p><strong><span style="font-family: Microsoft YaHei; font-size: 15px">参考:</span></strong><br><span style="font-family: Microsoft YaHei; font-size: 15px">How the docker container creation process works</span><br><span style="font-family: Microsoft YaHei; font-size: 15px">走进docker:hello-world的背后发生了什么?</span></p>

</div>
<div id="MySignature" role="contentinfo">
    <div>作者:sparkdev</div>
<div>出处:http://www.cnblogs.com/sparkdev/</div>
<div>本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。</div><br><br>
来源:https://www.cnblogs.com/sparkdev/p/9129334.html
頁: [1]
查看完整版本: 从 docker 到 runC