笔者在前文《RunC 简介》和《Containerd 简介》中分别介绍了 runC 和 containerd。本文我们将结合 docker 中的其它组件探索 docker 是如何把这些组件组织起来协调工作的。

Docker 的主要组件

安装 docker ,其实是安装了 docker 客户端、dockerd 等一系列的组件,其中比较重要的有下面几个。

Docker CLI(docker)
docker 程序是一个客户端工具,用来把用户的请求发送给 docker daemon(dockerd)。该程序的安装路径为:

/usr/bin/docker

Dockerd
docker daemon(dockerd),一般也会被称为 docker engine。该程序的安装路径为:

/usr/bin/dockerd

Containerd
详情请参考《Containerd 简介》。该程序的安装路径为:

/usr/bin/docker-containerd

Containerd-shim
它是 containerd 的组件,是容器的运行时载体,我们在 docker 宿主机上看到的 shim 也正是代表着一个个通过调用 containerd 启动的 docker 容器。该程序的安装路径为:

/usr/bin/docker-containerd-shim

RunC
详情请参考《RunC 简介》。该程序的安装路径为:

/usr/bin/docker-runc

从 hello world 开始

Docker 很贴心的为我们提供了 hello-world 镜像来验证安装是否成功,但是透过这个镜像我们还能看到更多的信息:

$ docker run hello-world

上面的输出信息指出,hello-world 容器的运行经历了如下四步:

  1. Docker 客户端向 docker daemon 发送请求
  2. Docker daemon 从 Docker Hub 上拉取镜像
  3. Docker daemon 使用镜像运行了一个容器并产生了输出
  4. Docker daemon 把输出的内容发送给了 docker 客户端

这是一个很抽象也很容器理解的过程,但是我们还想知道更多:docker daemon 是如何创建并运行容器的?
其实容器部分的操作和管理都被 dockerd 外包给 containerd 了,下图描述了运行一个容器时各个组件之间的关系:

Docker Engine API

从本质上说,docker 是一个客户端/服务器架构的应用。Dockerd 以 Engine API (REST)的方式对外提供服务,Engine API 里描述了 dockerd 支持的所有请求。Docker 客户端与 dockerd 之间就是通过 REST 的方式通信的。在 ubuntu 16.04 中,dockerd 默认是不监听 tcp 端口的,为了方便演示,我们让 dockerd 监听 tcp 端口。这样就可以使用 curl 代替 docker 客户端向 dockerd 发送请求了。具体的操作为,先修改 /lib/systemd/system/docker.service 文件,注释掉默认的 ExecStart 并添加新的 ExecStart 配置:

# ExecStart=/usr/bin/dockerd -H fd://
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock

然后重启 docker.service:

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker.service

这样 dockerd 就开始监听 tcp 端口 2375 了:

Docker 与 Dockerd 的交互

Docker 客户端与 dockerd 之间就是通过 REST 的方式通信的。前面我们已经让 dockerd 监听 tcp 端口了,所以我们可以使用 curl 来代替 docker 客户端。这里我们简单的演示如何请求 dockerd 从 docker hub 上下载 hello-world 镜像:

$ curl '127.0.0.1:2375/v1.37/images/create?fromImage=hello-world&tag=latest' -X POST

如果去看看 Engine API,你会发现其它的请求也都是用类似方式发送的,是不是很简单啊!

创建容器

容器镜像的下载是由 dockerd 完成的,但容器的创建和运行就需要 containerd(docker-containerd) 来完成了。Dockerd 与 docker-containerd 之间是通过 grpc 协议通信的。当 docker-containerd 收到 dockerd 启动容器的请求之后,会做一些初始化工作,然后启动 docker-containerd-shim 进程,并将相关配置作为参数传给它。docker-containerd 负责管理所有本机正在运行的容器,而一个 docker-containerd-shim 进程只负责管理一个运行的容器,它相当于 docker-runc 的一个封装,充当 docker-containerd 和 docker-runc 之间的桥梁,docker-runc 能干的就交给 docker-runc 来做,docker-runc 做不了的就放到这里来做。下面我们用 ubuntu 镜像运行一个容器:

$ docker run -id ubuntu bash

上图中黄线框起来的是几个主要的进程,它们之间是有父子关系的(systemd 没有出现在上图):

systemd---dockerd---docker-containerd---docker-containerd-shim---bash

细心的朋友一定发现了,上图中没有出现 docker-runc 进程,这是为什么呢?
实际上,在容器启动的过程中,docker-runc 进程是作为 docker-containerd-shim 的子进程存在的。docker-runc 进程根据配置找到容器的 rootfs 并创建子进程 bash 作为容器中的第一个进程。当这一切都完成后 docker-runc 进程退出,然后容器进程 bash 由 docker-runc 的父进程 docker-containerd-shim 接管。

为什么需要 docker-containerd-shim?

也许大家会问,为什么在容器的启动或运行过程中需要一个 docker-containerd-shim 进程呢?把它移除掉整个架构会更简洁也更优美一些!事实上 docker-containerd-shim 的存在是非常有必要的,其目的有如下几点:

  • 它允许容器运行时(即 runC)在启动容器之后退出,简单说就是不必为每个容器一直运行一个容器运行时(runC)
  • 即使在 containerd 和 dockerd 都挂掉的情况下,容器的标准 IO 和其它的文件描述符也都是可用的
  • 向 containerd 报告容器的退出状态

前两点尤其重要,有了它们就可以在不中断容器运行的情况下升级或重启 dockerd(这对于生产环境来说意义重大)。 从这里可以看到对 containerd-shim 的一些解释。

总结

笔者先在前文《RunC 简介》和《Containerd 简介》中分别介绍了 runC 和 containerd 等 docker 的核心组件。本文则通过 demo 演示了在创建、运行容器的过程中这些组件如何配合 docker engine 完成相关的任务,以及相关进程之间的关系和作用。希望本文可以帮助大家理解 docker 的整体架构及其组件间的协作方式。

参考:
How the docker container creation process works
走进docker:hello-world的背后发生了什么?

从 docker 到 runC的更多相关文章

  1. docker 内部组件结构 -- docker daemon, container,runC

    Docker, Containerd, RunC : 从 Docker 1.11 开始, docker 容器运行已经不是简单地通过 Docker Daemon 来启动, 而是集成了Container, ...

  2. Docker 生态概览

    Docker 和容器技术的发展可谓是日新月异,本文试图以全局的视角来梳理一下 docker 目前的生态圈.既然是概览,所以不会涉及具体的技术细节. Docker 自从发布以来发生了很多的变化,并且有些 ...

  3. Docker系列01—容器的发展历程---Docker的生态圈

    本文收录在容器技术学习系列文章总目录 Docker 和容器技术的发展可谓是日新月异,本文试图以全局的视角来梳理一下 docker 目前的生态圈.既然是概览,所以不会涉及具体的技术细节. Docker ...

  4. 在 Docker 里跑 Java,你必须知道的那些事儿!(转)

    原文 https://www.jianshu.com/p/0897d0581872 背景:众所周知,当我们执行没有任何调优参数(如“java-jar mypplication-fat.jar”)的 J ...

  5. Docker的基本操作与示例

    一.RunC RunC是一个由OCI(Open Container Initiative)制定的标准化轻量容器运行工具.OCI是专门致力于制定容器格式和运行时开放的工业化标准的组织.那容器标准化后Do ...

  6. Docker 生态

    Docker 和容器技术的发展可谓是日新月异,本文试图以全局的视角来梳理一下 docker 目前的生态圈.既然是概览,所以不会涉及具体的技术细节. Docker 自从发布以来发生了很多的变化,并且有些 ...

  7. Docker生态概览

    百花齐放的容器技术 虽然 docker 把容器技术推向了巅峰,但容器技术却不是从 docker 诞生的.实际上,容器技术连新技术都算不上,因为它的诞生和使用确实有些年头了.下面的一串名称肯能有的你都没 ...

  8. Docker installation in sles SP2

    Please refer to official site for installation  details :  https://docs.docker.com/install/linux/doc ...

  9. Docker从入门到实践(1)

    一.Docker简介 1.1.什么是 Docker Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目,它是基于 dotCloud 公司多 ...

随机推荐

  1. 共享Session

    概述 现在的大型网站中,会面临实现多台服务器中的session数据共享问题.当使用多台服务器架设成集群之后,我们通过负载均衡的方式,同一个用户(或者ip)访问时被分配到不同的服务器上,假设在A服务器登 ...

  2. 一个2013届毕业生(踏上IT行业)的迷茫(4)

    等了大概三个月,终于到9月份了,以前没有出过远门,这次要去西安上学,一个人父母还是不放心,带了几件衣服就和父亲匆匆去坐火车,这一路有多少个第一次啊,第一次和父亲一块坐车.第一次坐火车.第一次出县城.第 ...

  3. ADO.NET- 中批量添加数据的几种实现方法比较

    在.Net中经常会遇到批量添加数据,如将Excel中的数据导入数据库,直接在DataGridView控件中添加数据再保存到数据库等等. 方法一:一条一条循环添加 通常我们的第一反应是采用for或for ...

  4. Delphi 的内存操作函数(1): 给字符指针分配内存( 给字符指针(PChar、PWideChar、PAnsiChar)分配内存最佳的选择是StrAlloc。分配内存的时候会对字符串进行初始化)

    马上能想到的函数有: GetMem AllocMem ReallocMem FreeMem GetMemory ReallocMemory FreeMemory New Dispose NewStr ...

  5. HSQL一个简短的引论

    前言     在对dao层写測试类的时候,我们须要一个測试数据库,一般我们会是专门建立一个真实的測试数据库,可是有了HSQLDB事情就变得简单了起来. 正题 一.简单介绍: hsql数据库是一款纯Ja ...

  6. Android GPS获取当前位置信息

    package com.example.gpstest; import org.apache.http.util.LangUtils; import android.content.Context; ...

  7. Android无线调试——抛开USB数据线

    开发Android的朋友都知道,真机调试需要把手机与PC相连,然后把应用部署到真机上进行安装和调试.长长的USB线显得很麻烦,而且如果需要USB接口与其他设备连接的话显得很不方便.今天介绍一种不通过U ...

  8. Qt常用函数 记录(update erase repaint 的区别)

    一界面重载函数使用方法:1在头文件里定义函数protected: void paintEvent(QPaintEvent *event); 2 在CPP内直接重载void ----------::pa ...

  9. Linux学习(1)vi编辑器的常用命令

    今天对Linux中的vi编辑器进行了学习,对其中的常用命令进行总结: 数字 0 或^:光标移到行首 $              :光标移到行尾 H             :光标移到屏幕的首行 L ...

  10. 在vs2017 版本15.7.6中不支持2.1.0以上版本的net core sdk

    问题起因 :使用.net core 2.1sdk 电脑直接安装的2.1.1 没有安装2.1.0的sdk 在项目开发过程中报错:当前 . NET SDK 不支持将 .NET Core 2.1.1 设置为 ...