写点什么

Docker、Containerd、RunC...:你应该知道的所有

  • 2017-02-12
  • 本文字数:2870 字

    阅读完需:约 9 分钟

Docker 1.11 开始,Docker 容器运行已经不是简单的通过 Docker daemon 来启动,而是集成了 containerd、runC 等多个组件。Docker 服务启动之后,我们也可以看见系统上启动了 dockerd、docker-containerd 等进程,本文主要介绍新版 Docker(1.11 以后)每个部分的功能和作用。

Docker Daemon

作为 Docker 容器管理的守护进程,Docker Daemon 从最初集成在docker命令中(1.11 版本前),到后来的独立成单独二进制程序(1.11 版本开始),其功能正在逐渐拆分细化,被分配到各个单独的模块中去。从 Docker 服务的启动脚本,也能看见守护进程的逐渐剥离:

在 Docker 1.8 之前,Docker 守护进程启动的命令为:

docker -d这个阶段,守护进程看上去只是 Docker client 的一个选项。

Docker 1.8 开始,启动命令变成了:

docker daemon这个阶段,守护进程看上去是docker命令的一个模块。

Docker 1.11 开始,守护进程启动命令变成了:

dockerd此时已经和 Docker client 分离,独立成一个二进制程序了。

当然,守护进程模块不停的在重构,其基本功能和定位没有变化。和一般的 CS 架构系统一样,守护进程负责和 Docker client 交互,并管理 Docker 镜像、容器。

下面就来介绍下独立分拆出来的其他几个模块。

Containerd

containerd 是容器技术标准化之后的产物,为了能够兼容 OCI 标准,将容器运行时及其管理功能从 Docker Daemon 剥离。理论上,即使不运行 dockerd,也能够直接通过 containerd 来管理容器。(当然,containerd 本身也只是一个守护进程,容器的实际运行时由后面介绍的 runC 控制。)

最近,Docker 刚刚宣布开源containerd 。从其项目介绍页面可以看出,containerd 主要职责是镜像管理(镜像、元信息等)、容器执行(调用最终运行时组件执行)。

containerd 向上为 Docker Daemon 提供了 gRPC 接口,使得 Docker Daemon 屏蔽下面的结构变化,确保原有接口向下兼容。向下通过 containerd-shim 结合 runC,使得引擎可以独立升级,避免之前 Docker Daemon 升级会导致所有容器不可用的问题。

Docker、containerd 和 containerd-shim 之间的关系,可以通过启动一个 Docker 容器,观察进程之间的关联。首先启动一个容器,

docker run -d busybox sleep 1000然后通过pstree命令查看进程之间的父子关系(其中 20708 是dockerd的 PID):

pstree -l -a -A 20708输出结果如下:

复制代码
dockerd -H fd:// --storage-driver=overlay2
|-docker-containe -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc
| |-docker-containe b9a04a582b66206492d29444b5b7bc6ec9cf1eb83eff580fe43a039ad556e223 /var/run/docker/libcontainerd/b9a04a582b66206492d29444b5b7bc6ec9cf1eb83eff580fe43a039ad556e223 docker-runc
| | |-sleep 1000

虽然pstree命令截断了命令,但我们还是能够看出,当 Docker daemon 启动之后,dockerd 和 docker-containerd 进程一直存在。当启动容器之后,docker-containerd 进程(也是这里介绍的 containerd 组件)会创建 docker-containerd-shim 进程,其中的参数 b9a04a582b66206492d29444b5b7bc6ec9cf1eb83eff580fe43a039ad556e223 就是要启动容器的 id。最后 docker-containerd-shim 子进程,已经是实际在容器中运行的进程(既 sleep 1000)。

docker-containerd-shim 另一个参数,是一个和容器相关的目录 /var/run/docker/libcontainerd/b9a04a582b66206492d29444b5b7bc6ec9cf1eb83eff580fe43a039ad556e223,里面的内容有:

复制代码
.
├── config.json
├── init-stderr
├── init-stdin
└── init-stdout

其中包括了容器配置和标准输入、标准输出、标准错误三个管道文件。

RunC

OCI 定义了容器运行时标准,runC 是 Docker 按照开放容器格式标准(OCF, Open Container Format)制定的一种具体实现。

runC 是从 Docker 的 libcontainer 中迁移而来的,实现了容器启停、资源隔离等功能。Docker 默认提供了 docker-runc 实现,事实上,通过 containerd 的封装,可以在 Docker Daemon 启动的时候指定 runc 的实现。

我们可以通过启动 Docker Daemon 时增加--add-runtime参数来选择其他的 runC 现。例如:

docker daemon --add-runtime "custom=/usr/local/bin/my-runc-replacement"下面就让我们看下这几个模块如何工作。

举个例子

这里通过 Docker 一些命令,实现不使用 Docker Daemon 直接启动一个镜像,以便了解 Docker Daemon 每个模块的作用。

首先,需要创建容器标准包,这部分实际上由 containerd 的 bundle 模块实现,将 Docker 镜像转换成容器标准包。

复制代码
mkdir my_container
cd my_container
mkdir rootfs
docker export $(docker create busybox) | tar -C rootfs -xvf -

上述命令将 busybox 镜像解压缩到指定的 rootfs 目录中。如果本地不存在 busybox 镜像,containerd 还会通过 distribution 模块去远程仓库拉取。

现在整个 my_container 目录结构如下:

复制代码
$ tree -d my_container/
my_container/
└── rootfs
├── bin
├── dev
│ ├── pts
│ └── shm
├── etc
├── home
├── proc
├── root
├── sys
├── tmp
├── usr
│ └── sbin
└── var
├── spool
│ └── mail
└── www
17 directories

此时,标准包所需的容器数据已经准备完毕,接下来我们需要创建配置文件:

docker-runc spec此时会生成一个名为config.json的配置文件,该文件和 Docker 容器的配置文件类似,主要包含容器挂载信息、平台信息、进程信息等容器启动依赖的所有数据。

最后,可以通过runc命令来启动容器:

runc run busybox注意,runc 必须使用 root 权限启动。

执行之后,我们可以看见容器已经启动:

复制代码
localhost my_container # runc run busybox
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
9 root 0:00 ps aux

此时,事实上已经可以不依赖 Docker 本身,如果系统上安装了runc包,即可运行容器。对于 Gentoo 系统来说,安装app-emulation/runc包即可。

当然,也可以使用 docker-runc 命令来启动容器:

复制代码
localhost my_container # docker-runc run busybox
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
7 root 0:00 ps aux

从这里可以看到标准化的重要性。

总结

从 Docker 1.11 之后,Docker Daemon 被分成了多个模块以适应 OCI 标准。拆分之后,结构分成了以下几个部分。

其中,containerd 独立负责容器运行时和生命周期(如创建、启动、停止、中止、信号处理、删除等),其他一些如镜像构建、卷管理、日志等由 Docker Daemon 的其他模块处理。

Docker 的模块块拥抱了开放标准,希望通过 OCI 的标准化,容器技术能够有很快的发展。


感谢木环对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017-02-12 18:0039658

评论

发布
暂无评论
发现更多内容

训练营第二周总结

大脸猫

极客大学架构师训练营

2020中国系统架构师大会活动回顾:ZEGO实时音视频服务架构实践

ZEGO即构

架构设计 RTC

叼!阿里Mysql三位封神专家总结800页性能优化的千金良方

996小迁

Java MySQL 编程 架构 面试

接口工具使用对比(apipost、jmeter、postman、swagger等)

测试人生路

Jmeter Postman 接口测试

花费6个月的时间,五面阿里java岗,最后竟然差点倒在最后HR面?

Java架构追梦

Java 学习 编程 架构 面试

Java-技术专题-Stream流详解

洛神灬殇

K近邻算法:机器学习萌新必学算法

华为云开发者联盟

学习 算法

Java-技术专题-JDK8新特性之Stream流

洛神灬殇

“绝影”机器狗如何利用ModelArts强化学习算法更改导航轨迹

华为云开发者联盟

人工智能 机器狗 导航

技术实践丨PostgreSQL插件之pg_dirtyread "闪回查询"

华为云开发者联盟

数据库 后端

训练营第二周作业

大脸猫

极客大学架构师训练营

【Java面试官】我管你什么P7、P8?答不出这十道题你就是渣!

Java架构师迁哥

苏州高铁新城打造区块链发展聚集地

CECBC

区块链 智能合约 金融

十个写代码的小技巧,保你在面试过程中脱颖而出

Java架构师迁哥

首个通用安全技术行业标准发布适用于区块链技术架构 但监管仍待完善

CECBC

金融 安全技术

2020年了,终于有大佬把Java程序员必学知识点总结整理出来了!

Java架构之路

Java 程序员 架构 面试 编程语言

作为打工人,如何正确的选择自动化测试工具(完全指南)

软测小生

软件测试 测试 软件自动化测试 测试工具 测试开发

一周信创舆情观察(10.19~10.25)

统小信uos

区块链将与人工智能、物联网、云计算技术形成互补

CECBC

人工智能 物联网

人少钱少需求多的新项目该怎么带?看到这篇我心里有底了!

华为云开发者联盟

技术 运维 后端

Java-技术专题-Object克隆方法解析

洛神灬殇

数字货币交易所系统开发技术

薇電13242772558

区块链 数字货币

阿里技术四面+交叉面+HR面成功拿到offer,谁说双非本科进不了大厂?

Java架构之路

Java 程序员 架构 面试 编程语言

Appium常用操作之「元素定位、swipe 滑屏操作」

清菡软件测试

【得物技术】一文读懂Vue生命周期

得物技术

Vue 生命周期 得物技术部 得物 钩子函数

产品资讯 | mPaaS 适配 targetSdkVersion 29

蚂蚁集团移动开发平台 mPaaS

移动开发 mPaaS

【JSRC小课堂】Web安全专题(四)SRC漏洞挖掘技术之命令执行漏洞

京东科技开发者

WEB安全

阿里P8大牛2020年总结整理的心血之作:17W字操作系统、网络协议教程、面试真题(附导图+笔记)

Java架构之路

Java 程序员 架构 面试 编程语言

架构师训练营 -week06-总结

大刘

极客大学架构师训练营

年末成功上岸腾讯T3-2,全凭这份业内跪求的Java核心笔记

小Q

Java 学习 架构 面试 程序猿

Docker、Containerd、RunC...:你应该知道的所有_语言 & 开发_金灵杰_InfoQ精选文章