Docker、Containerd、RunC分别是什么

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: Docker、Containerd、RunC分别是什么

什么是RunC


上一遍文章《真正运行容器的工具:深入了解 runc 和 OCI 规范》已经讲清楚了Runc与OCI。这里再讲解一下概念。


Docker、Google、CoreOS 和其他供应商创建了开放容器计划 (OCI),目前主要有两个标准文档:容器运行时标准 (runtime spec)和 容器镜像标准(image spec)。


640.png


OCI 对容器 runtime 的标准主要是指定容器的运行状态,和 runtime 需要提供的命令。下图可以是容器状态转换图:


640.png


  • init 状态:这个是我自己添加的状态,并不在标准中,表示没有容器存在的初始状态
  • creating:使用 create 命令创建容器,这个过程称为创建中
  • created:容器创建出来,但是还没有运行,表示镜像和配置没有错误,容器能够运行在当前平台
  • running:容器的运行状态,里面的进程处于 up 状态,正在执行用户设定的任务
  • stopped:容器运行完成,或者运行出错,或者 stop 命令之后,容器处于暂停状态。这个状态,容器还有很多信息保存在平台中,并没有完全被删除


Runc的来历


RunC 是从 Docker 的 libcontainer 中迁移而来的,实现了容器启停、资源隔离等功能。Docker将RunC捐赠给 OCI 作为OCI 容器运行时标准的参考实现。Docker 默认提供了 docker-runc 实现。事实上,通过 containerd 的封装,可以在 Docker Daemon 启动的时候指定 RunC的实现。最初,人们对 Docker 对 OCI 的贡献感到困惑。他们贡献的是一种“运行”容器的标准方式,仅此而已。它们不包括镜像格式或注册表推/拉格式。当你运行一个 Docker 容器时,这些是 Docker 实际经历的步骤:


  • 下载镜像
  • 将镜像文件解开为bundle文件,将一个文件系统拆分成多层
  • 从bundle文件运行容器


Docker标准化的仅仅是第三步。在此之前,每个人都认为容器运行时支持Docker支持的所有功能。最终,Docker方面澄清:原始OCI规范指出,只有“运行容器”的部分组成了runtime。这种“概念失联”一直持续到今天,并使“容器运行时”成为一个令人困惑的话题。希望我能证明双方都不是完全错误的,并且在本文中将广泛使用该术语。RunC 就可以按照这个 OCI 文档来创建一个符合规范的容器,既然是标准肯定就有其他 OCI 实现,比如 Kata、gVisor 这些容器运行时都是符合 OCI 标准的。


怎么使用 runc


create the bundle
$ mkdir -p /mycontainer/rootfs
# [ab]use Docker to copy a root fs into the bundle
$ docker export $(docker create busybox) | tar -C /mycontainer/rootfs -xvf -
# create the specification, by default sh will be the entrypoint of the container
$ cd /mycontainer
$ runc spec
# launch the container
$ sudo -i
$ cd /mycontainer
$ runc run mycontainerid
# list containers
$ runc list
# stop the container
$ runc kill mycontainerid
# cleanup
$ runc delete mycontainerid


在命令行中使用 runc,我们可以根据需要启动任意数量的容器。但是,如果我们想自动化这个过程,我们需要一个容器管理器。为什么这样?想象一下,我们需要启动数十个容器来跟踪它们的状态。其中一些需要在失败时重新启动,需要在终止时释放资源,必须从注册表中提取图像,需要配置容器间网络等等。就需要有Low-Level和High-Level容器运行时,runc就是Low-Level实现的实现。


Low-Level和High-Level容器运行时


当人们想到容器运行时,可能会想到一系列示例;runc、lxc、lmctfy、Docker(容器)、rkt、cri-o。这些中的每一个都是为不同的情况而构建的,并实现了不同的功能。有些,如 containerd 和 cri-o,实际上使用 runc 来运行容器,在High-Level实现镜像管理和 API。与 runc 的Low-Level实现相比,可以将这些功能(包括镜像传输、镜像管理、镜像解包和 API)视为High-Level功能。考虑到这一点,您可以看到容器运行时空间相当复杂。每个运行时都涵盖了这个Low-Level到High-Level频谱的不同部分。这是一个非常主观的图表:


640.png


因此,从实际出发,通常只专注于正在运行的容器的runtime通常称为“Low-Level容器运行时”,支持更多高级功能(如镜像管理和gRPC / Web API)的运行时通常称为“High-Level容器运行时”,“High-Level容器运行时”或通常仅称为“容器运行时”,我将它们称为“High-Level容器运行时”。值得注意的是,Low-Level容器运行时和High-Level容器运行时是解决不同问题的、从根本上不同的事物。


  • Low-Level容器运行时:容器是通过Linux nanespace和Cgroups实现的,Namespace能让你为每个容器提供虚拟化系统资源,像是文件系统和网络,Cgroups提供了限制每个容器所能使用的资源的如内存和CPU使用量的方法。在最低级别的运行时中,容器运行时负责为容器建立namespaces和cgroups,然后在其中运行命令,Low-Level容器运行时支持在容器中使用这些操作系统特性。目前来看低级容器运行时有:runc :我们最熟悉也是被广泛使用的容器运行时,代表实现Docker。runv:runV 是一个基于虚拟机管理程序(OCI)的运行时。它通过虚拟化 guest kernel,将容器和主机隔离开来,使得其边界更加清晰,这种方式很容易就能帮助加强主机和容器的安全性。代表实现是kata和Firecracker。runsc:runsc = runc + safety ,典型实现就是谷歌的gvisor,通过拦截应用程序的所有系统调用,提供安全隔离的轻量级容器运行时沙箱。截止目前,貌似并不没有生产环境使用案例。wasm : Wasm的沙箱机制带来的隔离性和安全性,都比Docker做的更好。但是wasm 容器处于草案阶段,距离生产环境尚有很长的一段路。


  • High-Level容器运行时:通常情况下,开发人员想要运行一个容器不仅仅需要Low-Level容器运行时提供的这些特性,同时也需要与镜像格式、镜像管理和共享镜像相关的API接口和特性,而这些特性一般由High-Level容器运行时提供。就日常使用来说,Low-Level容器运行时提供的这些特性可能满足不了日常所需,因为这个缘故,唯一会使用Low-Level容器运行时的人是那些实现High-Level容器运行时以及容器工具的开发人员。那些实现Low-Level容器运行时的开发者会说High-Level容器运行时比如containerd和cri-o不像真正的容器运行时,因为从他们的角度来看,他们将容器运行的实现外包给了runc。但是从用户的角度来看,它们只是提供容器功能的单个组件,可以被另一个的实现替换,因此从这个角度将其称为runtime仍然是有意义的。即使containerd和cri-o都使用runc,但是它们是截然不同的项目,支持的特性也是非常不同的。dockershim, containerd 和cri-o都是遵循CRI的容器运行时,我们称他们为高层级运行时(High-level Runtime)。


Kubernetes 只需支持 containerd 等high-level container runtime即可。由containerd 按照OCI 规范去对接不同的low-level container runtime,比如通用的runc,安全增强的gvisor,隔离性更好的runv。


containerd


与RunC_一样_,我们又可以在这里看到一个docker公司的开源产品containerd曾经是开源docker项目的一部分。尽管_containerd_是另一个自给自足的软件。


  • 一方面,它称自己为容器运行时,但是与运行时__RunC_不同_。不仅_containerd_和_runc_的职责不同,组织形式也不同。显然_runc_是只是一个命令行工具,_containerd_是一个长期居住守护进程。_runc_的实例不能超过底层容器进程。通常它在create调用时开始它的生命,然后只是在容器的 rootfs 中的指定文件去运行。
  • 另一方面,_containerd _可以管理超过数千个_runc_容器。它更像是一个服务器,它侦听传入请求以启动、停止或报告容器的状态。在引擎盖下_containerd_使用RunC。然而,_containerd_不仅仅是一个容器生命周期管理器。它还负责镜像管理(从注册中心拉取和推送镜像,在本地存储镜像等)、跨容器网络管理和其他一些功能。


640.png


containerd 是一个工业级标准的容器运行时,它强调简单性健壮性可移植性,containerd 可以负责干下面这些事情:


  • 管理容器的生命周期(从创建容器到销毁容器)
  • 拉取/推送容器镜像
  • 存储管理(管理镜像及容器数据的存储)
  • 调用 runc 运行容器(与 runc 等容器运行时交互)
  • 管理容器网络接口及网络


640.png


上图是 Containerd 整体的架构。由下往上,Containerd支持的操作系统和架构有 Linux、Windows 以及像 ARM 的一些平台。在这些底层的操作系统之上运行的就是底层容器运行时,其中有上文提到的runc、gVisor 等。在底层容器运行时之上的是Containerd 相关的组件,比如 Containerd 的 runtime、core、API、backend、store 还有metadata 等等。构筑在 Containerd 组件之上以及跟这些组件做交互的都是 Containerd 的 client,Kubernetes 跟 Containerd 通过 CRI 做交互时,本身也作为 Containerd 的一个 client。Containerd 本身有提供了一个 CRI,叫 ctr,不过这个命令行工具并不是很好用。


在这些组件之上就是真正的平台,Google Cloud、Docker、IBM、阿里云、微软云还有RANCHER等等都是,这些平台目前都已经支持 containerd, 并且有些已经作为自己的默认容器运行时了。


从 k8s 的角度看,选择 containerd作为运行时的组件,它调用链更短,组件更少,更稳定,占用节点资源更少。


640.png


Docker


Docker 于 2013 年发布,解决了开发人员在端到端运行容器时遇到的许多问题。这里是他包含的所有东西:


  • 容器镜像格式
  • 一种构建容器镜像的方法(Dockerfile/docker build);
  • 一种管理容器镜像(docker image、docker rm等);
  • 一种管理容器实例的方法(docker ps, docker rm 等);
  • 一种共享容器镜像的方法(docker push/pull);
  • 一种运行容器的方式(docker run);


当时,Docker 是一个单体系统。但是,这些功能中没有一个是真正相互依赖的。这些中的每一个都可以在可以一起使用的更小、更集中的工具中实现。每个工具都可以通过使用一种通用格式、一种容器标准来协同工作。从 Docker 1.11 之后,Docker Daemon 被分成了多个模块以适应 OCI 标准。拆分之后,结构分成了以下几个部分。


640.png


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

 

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


现在创建一个docker容器的时候,Docker Daemon 并不能直接帮我们创建了,而是请求 containerd 来创建一个容器。当containerd 收到请求后,也不会直接去操作容器,而是创建一个叫做 containerd-shim 的进程。让这个进程去操作容器,我们指定容器进程是需要一个父进程来做状态收集、维持 stdin 等 fd 打开等工作的,假如这个父进程就是 containerd,那如果 containerd 挂掉的话,整个宿主机上所有的容器都得退出了,而引入 containerd-shim 这个垫片就可以来规避这个问题了,就是提供的live-restore的功能。这里需要注意systemd的 MountFlags=slave。


然后创建容器需要做一些 namespaces 和 cgroups 的配置,以及挂载 root 文件系统等操作。runc 就可以按照这个 OCI 文档来创建一个符合规范的容器。


真正启动容器是通过 containerd-shim 去调用 runc 来启动容器的,runc 启动完容器后本身会直接退出,containerd-shim 则会成为容器进程的父进程, 负责收集容器进程的状态, 上报给 containerd, 并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理, 确保不会出现僵尸进程。containerd,containerd-shim和容器进程(即容器主进程)三个进程,是有依赖关系的。可以参考《containerd,containerd-shim和runc的依存关系》[1],查看怎么保证live-restore的功能的。

相关实践学习
通过容器镜像仓库与容器服务快速部署spring-hello应用
本教程主要讲述如何将本地Java代码程序上传并在云端以容器化的构建、传输和运行。
Kubernetes极速入门
Kubernetes(K8S)是Google在2014年发布的一个开源项目,用于自动化容器化应用程序的部署、扩展和管理。Kubernetes通常结合docker容器工作,并且整合多个运行着docker容器的主机集群。 本课程从Kubernetes的简介、功能、架构,集群的概念、工具及部署等各个方面进行了详细的讲解及展示,通过对本课程的学习,可以对Kubernetes有一个较为全面的认识,并初步掌握Kubernetes相关的安装部署及使用技巧。本课程由黑马程序员提供。   相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
6月前
|
Kubernetes 搜索推荐 Docker
K8S容器运行时弃用Docker转型Containerd
K8S容器运行时弃用Docker转型Containerd
1075 0
|
1月前
|
搜索推荐 应用服务中间件 nginx
docker与containerd镜像获取及导出导入的区别与注意事项(报错信息:ctr: content digest sha256........ac47: not found)
docker与containerd镜像获取及导出导入的区别与注意事项(报错信息:ctr: content digest sha256........ac47: not found)
|
3月前
|
Kubernetes Unix Linux
k8s将节点容器运行时从Docker迁移到Containerd
k8s将节点容器运行时从Docker迁移到Containerd
|
存储 Kubernetes Cloud Native
【云原生】k8s新版本与Docker和Containerd的关联关系
【云原生】k8s新版本与Docker和Containerd的关联关系
1322 0
|
Kubernetes 开发工具 Docker
kubernetes环境从docker迁移到containerd
kubernetes环境从docker迁移到containerd
|
Kubernetes Linux 调度
将Kubernetes集群的CRI实现从cri-docker更改为containerd
本文记录了将Kubernetes集群的CRI实现从cri-docker更改为containerd的过程,包括cri-docker相关的卸载和containerd的安装配置。
882 0
|
存储 Kubernetes Shell
使用 shell 脚本二进制部署 k8s 环境 [支持 docker 和 containerd]
使用 shell 脚本二进制部署 k8s 环境 [支持 docker 和 containerd]
404 1
|
存储 Kubernetes 搜索推荐
containerd vs docker
containerd vs docker
containerd vs docker
|
Kubernetes 安全 Docker
runC爆严重漏洞影响Kubernetes、Docker,阿里云修复runC漏洞的公告
runC爆严重漏洞影响Kubernetes、Docker等的消息,阿里云修复runC漏洞的公告和解决办法。
7932 0