Run简介

2020-09-08 21:25:39

我们在生产中使用runc已经有一段时间了,虽然一开始看起来有些晦涩难懂,但我们已经习惯了它,以至于它看起来比docker更直观。我想把这篇博文作为对runc的温和介绍,以供那些想要尝试它的人们参考。这篇博文将试图作为快速入门的runc入门的入门读物,即使用runc管理容器生命周期。

Runc是一个轻量级的便携容器运行时,它的一部分由Docker内部使用,并打包成一个单一的二进制文件,作为开放容器倡议(OCI)下的一个开源项目发布,作为回馈社区的一种方式[1]。简单地说,runc是一个用Go编写的轻量级工具,它帮助管理容器的生命周期,即创建、运行、终止和删除容器。在本文中,您将逐一了解这些步骤,并了解在运行容器时runc与Docker有何不同。

出于本文的目的,您将使用官方的golang docker图像作为运行示例。您可以在Docker Hub上找到有关此图像的更多详细信息。

与docker不同,runc不会在幕后抽象单调乏味的任务,而是希望您安排它工作所需的任何内容。让我们浏览一下需求:

Runc是一款可移植的软件,对于GNU/Linux用户来说,只需获取二进制文件并授予其执行权限即可使用runc:

您可以前往项目的发布页面下载其他版本。同样值得注意的是,runc不能在MacOS上运行,所以如果您是在Mac上,最好的办法是在docker容器中运行runc。

在使用docker运行容器时指定配置选项(如卷装载、内存限制或uid:GID映射)与为docker命令指定命令行选项一样简单。使用runc时,这些配置将作为文件传递给runc。这个配置文件,即运行时规范,是由Open Container Initiative(OCI)制定的配置标准,用于指定容器的选项,并由runc[2]使用。简而言之,运行时规范是一个名为config.json的JSON文件,它包含与特定容器相关的配置。

在这篇文章中,您将了解一些运行时规范中的属性,以获得足够的理解,以便能够运行容器。后面的部分将重点介绍需要进一步解释的属性。请考虑以下示例运行时规范代码片段:

{";ociVersion";:";1.0.1-dev";,";进程";:{";Terminal";:true,";user";:{";uid";:0,";gid";:0},";args";:[";Sh";],";env";:[";path=/usr/local/bin/";term=xterm";],";cwd";:";/";,";Capability";:{...},";r限制";:[{...}],";noNewPrivileges";:true},...}。

Runc要求配置文件采用完全相同的格式,其中包含某些必填字段和一些其他可选字段。您可能已经从上面的代码片段中找到了一些选项,例如,环境变量、当前工作目录和uid:GID映射。还有其他配置选项可以让您更好地控制容器应该如何执行,您将在后面的小节中进行介绍。

这就是它听起来的样子,容器的根文件系统。您可以通过多种方式从Docker映像获取根文件系统,但最简单也是OCI建议的方法是使用Docker导出:

这将为我们的golang docker映像生成未存档的根文件系统,现在您将使用runc创建容器。

现在您已经收集了所需的先决条件,现在可以继续使用runc运行容器了。

在运行容器之前,您必须确保以所需的方式满足所有前提条件。默认情况下,runc二进制文件在称为“包”的目录中查找根文件系统和运行时规范。捆绑包目录必须具有运行时规范(config.json)和根文件系统(Rootfs)才能运行runc。

~/golang.├──config.json└──rootfs/├──HOME/├──tmp/├──..。└──..。

如上所述,有了这样的目录,您将能够运行~/golang目录中的runc命令,使其正常工作。

$runc create golang#list活动容器$runc listID PID状态包CREATED OWNERgolang 39 CREATED~/golang 2020-04-12T14:23:46.7358607Z root#开始在容器$runc start golang$runc listID PID状态包CREATED OWNERgolang 39运行~/golang 2020-04-12T14:23:46.7358607Z root#创建并启动容器$runc run-d golo。

与本节的标题相反,有多个命令可以使用runc“旋转”容器。在上面的代码片段中,我们使用了以下命令:

创建:从捆绑包创建容器的实例,但它不会运行在运行时规范(config.json)中指定为入口点的命令。

Start:为创建的容器运行init命令(在运行时规范中指定)。这实质上意味着您的容器现在正在“运行”。您的应用程序或您在容器中运行的任何命令现在都可以响应请求或执行您希望它完成的工作。

运行:它创建并启动容器。传递a-d将在分离模式下运行容器。与单独创建然后启动容器相比,您将更频繁地使用此命令。

列表:列出您创建的容器,它们可能正在运行、暂停、停止或处于已创建状态。(不是州的详尽列表)。这对于查询使用runc启动或停止的容器的当前状态最有用。

现在可以使用runc“旋转”容器,使用runc终止或删除容器也相当简单。

启动容器并使用它之后,需要以这样一种方式丢弃它:它不会在后台运行,不会无缘无故地持有资源或填充PID表。

尽管runc没有提供停止命令,但是runc kill命令在没有任何选项的情况下运行时,会向容器的init进程发送SIGTERM信号,从而停止容器。这会将容器移动到停止状态。不过,您的容器可能仍然持有容器包含的进程使用的某些资源。另一方面,当您谈到杀死容器内的进程时,有必要讨论一下与在docker中处理僵尸进程相关的问题。

Runc delete命令释放容器占用的所有资源,从而完全删除容器。您将无法以任何方式检查此容器(检查文件系统或其他方式),并且在执行runc list时不会显示该容器。

#创建并启动容器$runc run-d golang$runc listID PID状态包创建OWNERgolang 39运行~/golang 2020-04-12T14:26:46.7358607Z root$runc kill golang$runc listID PID状态包创建OWNERgolang 39 STOPPED~/golang 2020-04-12T14:26:46.7358607Z root$runc delete golang$runc list。

您已经向正在运行的容器发送了一个信号,然后释放处于停止状态的该容器持有的所有资源,以完全“停止它”。通过运行runc<;命令>;--help,了解有关可与这些命令一起使用的其他选项的更多信息。

现在,您可以使用runc管理容器,您将更进一步,看看如何使用runc对容器进行检查点/恢复。

与docker类似,runc提供了一个方便的功能来检查点/恢复正在运行的容器。您可以将容器检查点设置为序列化正在运行的进程的状态,然后将内容存储在磁盘上。这个序列化的BLOB包含将进程恢复到稍后检查点时的同一时间点所需的所有元数据。这是对CRIU工作原理的一个极其简单的解释。它比听起来复杂得多,但是对于本文的目的来说已经足够了。

为了实现这一点,runc使用了CRIU,这是一个开放源码软件,用于执行实际的检查点和恢复。为了使用诸如CHECKPOINT和RESTORE之类的runc命令,您应该在主机系统上安装CRIU二进制文件。您可以在这里找到安装CRIU的说明。

#在分离模式下运行容器$runc run-d golang$runc listID PID状态包创建的OWNERgolang 39运行~/golang 2020-04-12T14:26:46.7358607Z root$runc checkpoint golang$runc restore golang$runc listID PID状态包创建的OWNERgolang 39运行~/golang 2020-04-12T14:26:46.7358607Z root

在上面的代码片段中,您在Golang容器运行时设置了检查点。当您为容器设置检查点时,CRIU将检查点元数据存储在当前目录中。您可以在以后的任何时间点将Golang容器恢复到检查点时的相同状态。您必须确保CRIU创建的文件没有以任何方式修改。

这可能不是很清楚为什么或如何有用,但在某些情况下,这样的功能可以帮助您改进工作流程,我能想到的一些用例是:

如果容器启动时间是阻碍您的因素,例如,如果您有一个容器,在该容器中初始化进程需要时间才能启动。您可以启动容器,让服务器启动并运行,为容器设置检查点,然后继续恢复容器。您实际上放弃了等待初始化进程准备就绪的初始开销。一个常见的候选方案可能是您的数据库服务器。

如果您在CPU/内存使用率是关键指标的环境中运行应用程序,则将检查点容器保留在文件系统上并在服务请求之前立即恢复它会有所帮助,这对于需要定期运行同时维护内存和磁盘内部状态的作业可能很有用。

可能还有其他更复杂的用例,但我认为检查点/恢复是根据需求节省时间和资源的好方法。

除了管理容器生命周期之外,您还可以执行检查点/恢复等操作。现在必须关注运行时规范,因为它控制容器如何运行、它获得什么资源以及其他配置选项。

如上所述,运行时规范是存储在runc读取的文件中的一组配置选项,用于在运行容器之前应用配置。在docker中,您可以显式设置您可以在运行时规范中提供的一些选项。了解最常见的配置选项后,您可以:

限制可以分配给容器内新cgroup下运行的进程的内存量。例如,下面的代码片断将指示runc将容器可用的内存量限制在2068MB。值-1表示无限制内存。

在每个进程的基础上应用限制,例如,您可以限制进程可以打开的打开文件描述符的数量。您可以在此指定所有限制,有关详细列表,请参阅官方手册页。

控制可以在容器内运行的进程数。如果应用以下内容,则将进程数限制为仅1024个。如果您运行的代码来自不受信任的供应商,这可能是至关重要的,因为他们很可能会变成一个叉形炸弹,最终您可能会丢失您的容器。另一方面,不要过度使用这个限制,几乎所有的程序都使用fork/exec启动子进程,限制这些可能最终会阻碍容器的功能。

是否允许rootfs为只读。使用以下配置,容器的rootfs将为只读,即不允许写入:

以不同的user:group身份在容器中运行进程。以下代码片段以用户101和组201的身份运行容器。如果您正在执行此操作,还要确保检查是否需要为容器提供setuid和setgid功能。

您可以像Docker一样将卷从主机装载到容器。以下命令将/home/document/image从主机挂载到具有读写权限的/mnt/image处的容器:

";装载";:[{";目标";:";/mnt/image";,";type";:";bind";,";source";::";/home/document/image";,";选项";:[";rbind";,";rw";]}]。

在运行时规范中还有许多其他配置选项,您可以使用这些选项来根据您的需求配置您的容器。为了简明扼要和保持这篇文章的相关性,在这里全部讨论它们是不明智的。您可以前往Runtime-spec的Github repo获取所有可用的选项。

您了解了如何使用runc管理容器的生命周期,runc是一种轻量级、可移植的容器运行时。您不太可能每天使用runc来管理容器,docker对此更加用户友好和方便,特别是当您考虑到额外的时间和精力来收集runc完成其工作的前提条件时。另一方面,runc可以证明填补了docker无法填补的空白,这些差距可以从性能到缺少附加的、通常是不必要的内置到docker中的功能(runc没有捆绑在一起)不等。但这些都是你要做的决定。不管怎么说,仅仅为了好玩而学习新东西总是一个好主意。

如果您在这篇文章中发现了错误陈述或可以改进的地方,请随时提出公关或与我联系。