什么是RunCDocker,Google、CoreOS等厂商创建了OpenContainerInitiative(OCI),目前主要有两个标准文档:容器运行时标准(运行时规范)和容器图像标准(图像规范)。OCI对容器运行时的标准主要规定了容器的运行状态和运行时需要提供的命令。下图可以是一个容器状态转换图:initstate:这是我自己添加的状态,标准中没有,表示没有容器存在的初始状态creating:使用create命令创建一个容器,这个过程叫做creatingcreated:containercreation出来了,但是还没有运行,说明镜像和配置都正确,容器可以运行在当前平台上,或者停止命令后,容器处于暂停状态。在这种状态下,容器在平台上还保存着大量的信息,并没有被彻底删除。Runc的由来RunC是从Docker的libcontainer迁移过来的,实现了容器启停、资源隔离等功能。Docker将RunC捐赠给了OCI,作为OCI容器运行时标准的参考实现。Docker默认提供了docker-runc实现。其实通过containerd的封装,可以在DockerDaemon启动的时候指定RunC的实现。最初,人们对Docker对OCI的贡献感到困惑。他们贡献的是一种“运行”容器的标准方式,仅此而已。它们不包括镜像格式或注册表推/拉格式。当你运行一个Docker容器时,这些是Docker实际经历的步骤:下载图像将图像解包到一个bundle文件中,将一个文件系统拆分成多个层从一个bundle文件运行一个容器只是Docker标准化的第三步。在此之前,每个人都认为容器运行时支持Docker所支持的一切。最后,Docker澄清了:原来的OCI规范中说只有“运行容器”部分构成了运行时。这种“概念上的脱节”一直持续到今天,并使“容器运行时”成为一个令人困惑的话题。希望我能证明任何一方都没有完全错误,并且该术语将在本文中广泛使用。RunC可以按照这个OCI文档来创建一个符合规范的容器。既然是标准,那肯定还有其他的OCI实现,比如Kata、gVisor,这些容器运行时都是符合OCI标准的。如何使用runccreatethebundle$mkdir-p/mycontainer/rootfs#[ab]useDocker将rootfs复制到bundle$dockerexport$(dockercreatebusybox)|tar-C/mycontainer/rootfs-xvf-#createthespecification,默认情况下sh将是容器的入口点$cd/mycontainer$sucspec#$launchdo-containercd/mycontainer$runcrunmycontainerid#listcontainers$runclist#stopthecontainer$runckillmycontainerid#cleanup$runcdeletemycontainerid在命令行上使用runc,我们可以启动任意数量的容器。然而,如果我们想要自动化这个过程,我们需要一个容器管理器。为什么会这样?想象一下,我们需要启动几十个容器来跟踪它们的状态。其中一些需要在失败时重新启动,需要在终止时释放资源,必须从注册表中提取图像,需要配置容器间网络等等。当需要Low-Level和High-Level容器时,runc是Low-Level实现的执行。低级和高级容器运行时当人们想到容器运行时时,可能会想到一系列示例;runc、lxc、lmctfy、Docker(容器)、rkt、cri-o。其中每一个都是为不同的情况而构建的,并实现不同的功能。有些,如containerd和cri-o,实际上使用runc来运行容器,在高层实现图像管理和API。与runc的Low-Level实现相比,这些功能(包括图片传输、图片管理、图片解包、API)可以看作是High-Level功能。考虑到这一点,您可以看到容器运行时空间非常复杂。每个运行时涵盖此低级到高级频谱的不同部分。这是一个非常主观的图表:作为一个实际问题,因此,仅关注运行容器的运行时通常被称为“低级容器运行时”,支持更高级的功能,如图像管理和gRPC/WebAPI)运行时是通常被称为“高级容器运行时”、“高级容器运行时”或一般简称为“容器运行时”,我将它们称为“高级容器运行时”。值得注意的是,Low-Level容器运行时和High-Level容器运行时是解决不同问题的根本不同的东西。Low-LevelContainerRuntime:容器通过Linuxnanespace和Cgroups实现。命名空间允许您为每个容器提供虚拟化的系统资源,例如文件系统和网络。Cgroups提供限制每个容器的资源。内存和CPU使用率等方法。在最低级别的运行时,容器运行时负责为容器建立命名空间和cgroup,然后在其中运行命令。低级容器运行时支持在容器中使用这些操作系统功能。目前容器运行时的底层有:runc:我们最熟悉、使用最广泛的容器运行时,代表了Docker的实现。runv:runV是基于管理程序(OCI)的运行时。它通过虚拟化guest内核将容器与宿主机隔离开来,使其边界更加清晰,可以轻松帮助增强宿主机和容器的安全性。代表性的实现是kata和Firecracker。runsc:runsc=runc+safety,典型的实现是谷歌的gvisor,它通过拦截应用程序的所有系统调用,提供了一个轻量级的容器运行时沙箱,用于安全隔离。到目前为止,似乎还没有任何生产用例。wasm:Wasm的沙箱机制带来的隔离性和安全性优于Docker。但wasm容器处于草案阶段,距离生产环境还有很长的路要走。High-Levelcontainerruntime:通常,开发者想要运行一个容器,不仅需要Low-Levelcontainerruntime提供的这些特性,还需要与镜像格式、镜像管理、共享镜像相关的API接口和特性,而这些功能通常由高级容器运行时提供。在日常使用方面,Low-Levelcontainerruntime提供的功能可能无法满足日常需求。出于这个原因,唯一会使用Low-Level容器运行时的人是那些实现High-Level容器运行时和容器工具的人。开发商。实现底层容器运行时的开发者会说,containerd、cri-o等高层容器运行时不像真正的容器运行时,因为在他们看来,他们将容器运行时的实现外包给了runc。但从用户的角度来看,它们只是一个提供容器功能的单一组件,可以被另一个实现所取代,所以从这个角度来看,将其称为运行时还是有意义的。尽管containerd和cri-o都使用runc,但它们是非常不同的项目并且支持非常不同的功能。dockershim、containerd和cri-o都是CRI兼容的容器运行时,我们称它们为高级运行时(High-levelRuntime)。Kubernetes只需要支持高级容器运行时,例如containerd。Containerd根据OCI规范连接不同的底层容器运行时,比如普通的runc,安全增强的gvisor,以及更隔离的runv。containerd和RunC_一样,这里又可以看到containerd是docker公司的开源产品,曾经是docker开源项目的一部分。尽管_containerd_是另一个自给自足的软件。一方面,它称自己为容器运行时,但它与运行时__RunC_不同。_containerd_和_runc_不仅职责不同,组织形式也不同。显然_runc_只是一个命令行工具,而_containerd_是一个长期存在的守护进程。_runc_实例的寿命不能超过底层容器进程。通常它从创建调用开始它的生命,然后只在容器的rootfs中的指定文件上运行。另一方面,_containerd_可以管理超过数千个_runc_容器。它更像是一个服务器,监听传入的请求以启动、停止或报告容器的状态。在引擎盖下_containerd_使用RunC。然而,_containerd_不仅仅是一个容器生命周期管理器。它还负责图像管理(从注册表中拉取和推送图像、在本地存储图像等)、跨容器网络管理和一些其他功能。image.pngcontainerd是一个行业标准的容器运行时,它强调简单性、健壮性和可移植性。containerd可以负责以下事情:管理容器的生命周期(从创建容器到销毁容器)Pull/push容器镜像存储管理(管理镜像和容器数据存储)调用runc运行容器(与runc等交互)containerruntimes)Managecontainernetworkinterfaceandnetwork上图是Containerd的整体架构。从下到上,Containerd支持的操作系统和架构包括Linux、Windows以及ARM等一些平台。在这些底层操作系统上运行的是底层容器运行时,包括上面提到的runc、gVisor等。在底层容器运行时之上是与Containerd相关的组件,例如Containerd的运行时、核心、API、后端、存储和元数据。Containerd的客户端建立在Containerd的组件之上,并与这些组件进行交互。当Kubernetes通过CRI与Containerd交互时,它也充当了Containerd的客户端。Containerd本身提供了一个叫ctr的CRI,但是这个命令行工具用处不大。在这些组件之上是真正的平台,例如谷歌云、Docker、IBM、阿里云、微软云和RANCHER。这些平台目前都支持containerd,其中一些已经被用作它们的默认容器运行时。从k8s的角度来看,选择containerd作为运行时组件,调用链更短,组件更少,更稳定,占用节点资源更少。Docker于2013年发布,Docker解决了开发人员在端到端运行容器时遇到的许多问题。这是它包含的所有内容:容器镜像格式一种构建容器镜像的方法(Dockerfile/dockerbuild);一种管理容器镜像(dockerimage、dockerrm等)的方法;一种管理容器实例的方法(dockerps、dockerrm等);一种共享容器镜像的方式(dockerpush/pull);一种运行容器的方法(dockerrun);当时,Docker是一个单体系统。然而,这些功能都不是真正相互依赖的。这些中的每一个都可以在更小、更集中的工具中实现,这些工具可以一起使用。每个工具都可以通过使用一种通用格式、一种容器标准来协同工作。从Docker1.11开始,DockerDaemon被划分为多个模块以满足OCI标准。拆分后,结构分为以下几个部分。其中,containerd独立负责容器运行时和生命周期(如创建、启动、停止、中止、信号处理、删除等),其他如镜像构建、卷管理、日志记录等由DockerDaemon的其他模块。Docker的模块化块拥抱开放标准,希望通过OCI的标准化,让容器技术快速发展。现在创建docker容器时,DockerDaemon不能直接帮我们创建,而是请求containerd创建容器。containerd收到请求后,并不直接操作容器,而是创建一个名为containerd-shim的进程。让这个进程操作容器。我们指定容器进程需要一个父进程来做状态收集,维护stdin等fd打开等,如果父进程是containerd,那么如果containerd挂了,整个宿主机上的所有容器都要退出,而引入containerd-shimshim可以避免这个问题,也就是提供的live-restore功能。这里注意MountFlags=systemd的slave。然后创建容器需要一些命名空间和cgroups的配置,以及挂载根文件系统等操作。runc可以按照这个OCI文档来创建一个符合规范的容器。容器实际上是通过containerd-shim调用runc来启动的。runc启动容器后,会直接退出,containerd-shim会成为容器进程的父进程,负责收集容器进程的状态,上报给containerd,以及容器中pid为1的进程后退出后,它会接管容器中的子进程进行清理,从而保证不会出现僵尸进程。containerd、containerd-shim和容器进程(即容器主进程)这三个进程之间存在依赖关系。如何保证live-restore功能可以参考《containerd,containerd-shim和runc的依存关系》[1]。
