真正运行容器的工具:深入了解 runc 和 OCI 规范

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 真正运行容器的工具:深入了解 runc 和 OCI 规范

原始容器运行时


如果试图将链从最终用户绘制到实际的容器进程,它可能如下所示:640.png

640.png

runc 是一个命令行客户端,用于运行根据 Open Container Initiative (OCI) 格式打包的应用程序,并且是 Open Container Initiative 规范的兼容实现。


有一个关于如何运行容器和管理容器映像的开放容器计划(OCI) 和规范。runc 符合此规范,但还有其他符合 OCI 的运行时。甚至可以运行符合 OCI 标准的虚拟机,Kata Containers 与gVisor就是符合符合 OCI 标准的虚拟机。gVisor 为代表的用户态 Kernel 方案是安全容器的未来,只是现在还不够完善。


runc 希望提供一个“ OCI 包”,它只是一个根文件系统和一个config.json 文件。而不是Podman 或 Docker 那样有“镜像”概念,所以不能只执行runc run nginx:latest这样来启动一个容器。


Runc 符合 OCI 规范(具体来说,是runtime-spec),这意味着它可以使用 OCI 包并从中运行一个容器。值得重申的是,这些bundle并不是“容器镜像”,它们要简单得多。层、标签、容器注册表和存储库等功能 - 所有这些都不是 OCI 包甚至运行时规范的一部分。有一个单独的 OCI-spec (image-spec )定义镜像。


文件系统包是你下载容器镜像并解压后得到的。所以它是这样的:


OCI Image -> OCI Runtime Bundle -> OCI Runtime


在我们的例子中,这意味着:


Container image -> Root filesystem and config.json -> runc


让我们构建一个应用程序包。我们可以从 config.json 文件开始,因为这部分非常简单:


mkdir my-bundle
cd my-bundle
runc spec


runc spec生成一个虚拟的 config.json。它已经有一个“进程”部分,用于指定在容器内运行哪个进程 - 即使有几个环境变量。


{
        "ociVersion": "1.0.1-dev",
        "process": {
             "terminal": true,
                "user": {
                        "uid": 0,
                        "gid": 0
                },
                "args": [
                        "sh"
                ],
                "env": [
                        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                        "TERM=xterm"
                ],
...


它还定义了在哪里查找根文件系统...


...
        "root": {
                "path": "rootfs",
                "readonly": true
        },
...


...以及其他许多内容,包括容器内的默认挂载、功能、主机名等。如果检查此文件,会注意到,许多部分与平台无关,并且特定于具体操作系统的部分嵌套在适当的内部部分。例如,会注意到有一个带有 Linux 特定选项的“linux”部分。


如果我们尝试运行这个包,我们会得到一个错误:


# runc run test
rootfs (/root/my-bundle/rootfs) does not exist


如果我们简单地创建文件夹,我们会得到另一个错误:


# mkdir rootfs
# runc run test
container_linux.go:345: starting container process caused "exec: \"sh\": executable file not found in $PATH"


这完全有道理 - 空文件夹并不是真正有用的根文件系统,我们的容器没有机会做任何有用的事情。我们需要创建一个真正的 Linux 根文件系统。这里可以使用如下命令解压rootfs:


$ docker export $(docker create busybox) | tar -C /mycontainer/rootfs -xvf -


这里我们使用skopeo 和 umoci 获取 OCI 应用程序包。


如何使用 skopeo 和 umoci 获取 OCI 应用程序包


从头开始创建 rootfilesystem 是一种相当麻烦的事情,因此让我们使用现有的最小映像之一  busybox。


要拉取镜像,我们首先需要安装skopeo。我们也可以使用 Buildah,但它的功能太多,无法满足我们的需求。Buildah 专注于构建镜像,甚至具有运行容器的基本功能。由于我们今天尽可能地低级别,我们将使用 skopeo:


  • skopeo 是一个命令行程序,可对容器镜像和镜像存储库执行各种操作。
  • skopeo 可以在不同来源和目的地之间复制镜像、检查镜像甚至删除它们。
  • skopeo 无法构建映像,它不知道如何处理 Containerfile。它非常适合自动化容器镜像升级的 CI/CD 管道。


yum install skopeo -y


然后复制busybox镜像:


skopeo copy docker://busybox:latest oci:busybox:latest


没有“拉取”——我们需要告诉 skopeo 镜像的来源和目的地。skopeo 支持几乎十几种不同类型的来源和目的地。请注意,此命令将创建一个新busybox文件夹,将在其中找到所有 OCI 镜像文件,具有不同的镜像层、清单等。


不要混淆 Image manifest 和 Application runtime bundle manifest,它们是不一样的。


我们复制的是一个 OCI Image,但是我们已经知道,runc 需要 OCI Runtime Bundle。我们需要一个将镜像转换为解压包的工具。这个工具将是umoci - 一个 openSUSE 实用程序,其唯一目的是操作 OCI 镜像。要安装它,请从 Github Releases获取最新版本的PATH。在撰写本文时,最新版本是0.4.5. umoci unpack获取 OCI 镜像并从中制作一个包:


umoci unpack --image busybox:latest bundle


让我们看看bundle文件夹里面有什么:


# ls bundle
config.json
rootfs
sha256_73c6c5e21d7d3467437633012becf19e632b2589234d7c6d0560083e1c70cd23.mtree
umoci.json


让我们将rootfs目录复制到之前创建的my-bundle目录。如果你好奇,这是rootfs的内容,如下:


bin  dev  etc  home  root  tmp  usr  var


如果它看起来像一个基本的 Linux 根文件系统,那么就是对的。


根据 OCI Runtime 规范,Linux ABI 下的应用程序会期望 Linux 环境提供以下特殊的文件系统:


  • /proc 文件夹,挂载 proc 文件系统。
  • /sys 文件夹,挂载 sysfs 文件系统。
  • /dev/pts 文件夹,挂载 devpts 文件系统。
  • /dev/shm 文件夹,挂载 tmpfs 文件系统。


这几个文件夹的作用这里略去,有兴趣的读者可以自行查阅 man7.org。runc 文档中还额外要求提供:


  • n
  • /dev/mqueue 文件夹,挂载 mqueue 文件系统。


runc 是 OCI Runtime 规范的参考实现,规范为容器的创建提供了整洁的接口,只需要为 runc 提供一份 config.json [1]。


使用 runc 运行 OCI 应用程序包


我们准备好将我们的应用程序包作为名为 的容器运行test:


runc run test


接下来发生的事情是我们最终进入了一个新创建的容器内的 shell!


# runc run test
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var


我们以默认foreground模式运行前一个容器。在这种模式下,每个容器进程都成为一个长时间运行的runc进程的子进程:


6801   997  \_ sshd: root [priv]
6805  6801      \_ sshd: root@pts/1
6806  6805          \_ -bash
6825  6806              \_ zsh
7342  6825                  \_ runc run test
7360  7342                  |   \_ runc run test


如果我终止与该服务器的 ssh 会话,runc 进程也会终止,最终杀死容器进程。让我们通过sleep infinite在 config.json 中替换 command并将终端选项设置为“false”来更仔细地检查这个容器。


runc不提供大量的命令行参数。它有类似start,stop和 run的命令来做容器的生命周期管理,但是容器的配置总是来自文件,而不是来自命令行:


{
        "ociVersion": "1.0.1-dev",
        "process": {
                "terminal": false,
                "user": {
                        "uid": 0,
                        "gid": 0
                },
                "args": [
                        "sleep",
                        "infinite"
                ]
...


这次让我们以分离模式运行容器:


runc run test --detach


我们可以看到正在运行的容器runc list:


ID          PID         STATUS      BUNDLE            CREATED                          OWNER
test        4258        running     /root/my-bundle   2020-04-23T20:29:39.371137097Z   root


在 Docker 的情况下,有一个Docker Daemon守护进程知道关于容器的一切。runc 如何找到我们的容器?事实证明,它只是在文件系统上保持状态,默认情况下在里面/run/runc/CONTAINER_NAME/state.json:


# cat /run/runc/test/state.json
{"id":"test","init_process_pid":4258,"init_process_start":9561183,"created":"2020-04-23T20:29:39.371137097Z","config":{"no_pivot_root":false,"parent_death_signal":0,"rootfs":"/root/my-bundle/rootfs","readonlyfs":true,"rootPropagation":0,"mounts"....


当我们在分离模式下运行时,原始runc run命令(不再有这样的进程)和这个容器

间没有关系。如果我们查看进程表,我们会看到容器的父进程是PID 1:


# ps axfo pid,ppid,command
4258     1 sleep infinite


Docker、containerd、CRI-O 等使用分离模式。它的目的是简化 runc 和全功能容器管理工具之间的集成。值得一提的是 runc 本身并不是某种类型的库——它是一个 CLI。当其他工具使用 runc 时,它们会调用我们刚刚在操作中看到的相同 runc 命令。


在runc 文档中阅读有关前台模式和分离模式之间差异的更多信息。虽然容器进程的PID是4258,但在容器内部PID显示为1:


# runc exec test ps                     
PID   USER     TIME  COMMAND
    1 root      0:00 sleep infinite
   13 root      0:00 ps


这要归功于Linux 命名空间,它是真正的容器背后的基本技术之一。我们可以通过lsns在主机系统上执行来列出所有当前的命名空间 :


# lsns
NS TYPE   NPROCS   PID USER COMMAND
4026532219 mnt         1  4258 root sleep infinite
4026532220 uts         1  4258 root sleep infinite
4026532221 ipc         1  4258 root sleep infinite
4026532222 pid         1  4258 root sleep infinite
4026532224 net         1  4258 root sleep infinite


runc 负责我们容器进程的进程、网络、挂载和其他命名空间。


容器世界的影子统治者


Podman、Docker 和所有其他工具,包括在那里运行的大多数 Kubernetes 集群,都归结为runc启动容器进程的二进制文件。


在实际工作中,几乎永远不会做我刚刚给你展示的事情 - 除非正在开发或者调试自己的或现有的容器工具。不能从容器映像中组装应用程序包,并且使用 Podman 而不是直接使用 runc 会更好。


runc就是Low-Level实现的实现,我们了解幕后发生的事情以及运行容器真正涉及的内容是非常有帮助的。最终用户和最终容器过程之间仍然有很多层,但是如果了解最后一层,那么容器将不再是神奇的东西,有时也很奇怪。最后你会发现容器它只是 runc 在命名空间中生成一个进程。当然最后一层是Linux内核,相比宇宙中有无数层。


runc 最重要的部分是它跟踪 OCI运行时规范。尽管几乎每一个容器,这些天与runc催生,它不具有与runc催生。可以将其与遵循运行时规范的任何其他容器运行时交换,并且容器引擎(如 CRI-O)应该以相同的方式工作。


High-Level容器运行时可以不依赖于 runc 本身。它们依赖于一些遵循 OCI 规范的容器运行时。这是当今容器世界真正美丽的部分。

相关实践学习
通过ACR快速部署网站应用
本次实验任务是在云上基于ECS部署Docker环境,制作网站镜像并上传至ACR镜像仓库,通过容器镜像运行网站应用,网站运行在Docker容器中、网站业务数据存储在Mariadb数据库中、网站文件数据存储在服务器ECS云盘中,通过公网地址进行访问。
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。     相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
3月前
|
运维 监控 数据可视化
小白也能部署应用,3个免费的容器化部署工具测评
本文对比了三款容器化部署工具:Docker Compose、Portainer 和 Websoft9。Docker Compose 适合开发者编排多容器应用,Portainer 提供图形化管理界面,而 Websoft9 则面向中小企业和非技术人员,提供一键部署与全流程运维支持,真正实现“开箱即用”。三款工具各有定位,Websoft9 更贴近大众用户需求。
小白也能部署应用,3个免费的容器化部署工具测评
|
3月前
|
运维 数据可视化 C++
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
2025年热门Web化容器部署工具对比:Portainer与Websoft9。Portainer以轻量可视化管理见长,适合技术团队运维;Websoft9则提供一站式应用部署与容器管理,内置丰富开源模板,降低中小企业部署门槛。两者各有优势,助力企业提升容器化效率。
344 1
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
|
数据可视化 开发工具 git
GitOps 驱动的 Docker Compose 可视工具化来了,图形化编辑器上玩转容器编排
Docker Compose 简化了多容器应用的部署,但随着应用复杂度上升,文本配置方式逐渐暴露出维护难、协作效率低等问题。基于 GitOps 的可视化 Docker Compose 工具应运而生,通过图形界面降低使用门槛,提升配置准确性和团队协作效率。结合 GitOps,实现配置变更的版本追踪、自动化部署与环境一致性,为多容器应用管理提供高效、安全的解决方案。
|
4月前
|
NoSQL Redis Docker
使用Docker Compose工具进行容器编排的教程
以上就是使用Docker Compose进行容器编排的基础操作。这能帮你更有效地在本地或者在服务器上部署和管理多容器应用。
450 11
|
3月前
|
Ubuntu PHP Docker
一个可以运行的Dockerfile_php ,用来创建php容器镜像
该简介描述了一个基于 Dragonwell 8 Ubuntu 的 Docker 镜像,用于构建包含 PHP 7.4 及常用扩展的运行环境。通过更换为阿里云源提升安装速度,配置了 PHP-FPM 并暴露 9000 端口,使用自定义 Dockerfile 构建镜像并成功运行容器。
|
10月前
|
存储 监控 对象存储
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
针对本地存储和 PVC 这两种容器存储使用方式,我们对 ACK 的容器存储监控功能进行了全新升级。此次更新完善了对集群中不同存储类型的监控能力,不仅对之前已有的监控大盘进行了优化,还针对不同的云存储类型,上线了全新的监控大盘,确保用户能够更好地理解和管理容器业务应用的存储资源。
630 274
|
8月前
|
存储 开发工具 开发者
揭秘 Microsoft.Docker.SDK:让容器开发更轻松的强大工具揭秘
随着云计算和容器技术的快速发展,`Docker` 已经成为容器化技术的事实标准。`Microsoft` 作为 `Docker` 的主要支持者和参与者,推出了 `Microsoft.Docker.SDK`,旨在帮助开发者更轻松地进行容器开发。本文将深入揭秘 Microsoft.Docker.SDK 的功能、使用方法以及它在容器开发中的应用。
234 13
|
8月前
|
开发工具 虚拟化 git
自学软硬件第755 docker容器虚拟化技术youtube视频下载工具
docker容器虚拟化技术有什么用?怎么使用?TubeTube 项目使用youtube视频下载工具
|
9月前
|
人工智能 Prometheus 监控
容器化AI模型的监控与治理:确保模型持续稳定运行
在前几篇文章中,我们探讨了AI模型的容器化部署及构建容器化机器学习流水线。然而,将模型部署到生产环境只是第一步,更重要的是确保其持续稳定运行并保持性能。为此,必须关注容器化AI模型的监控与治理。 监控和治理至关重要,因为AI模型在生产环境中面临数据漂移、概念漂移、模型退化和安全风险等挑战。全面的监控涵盖模型性能、数据质量、解释性、安全性和版本管理等方面。使用Prometheus和Grafana可有效监控性能指标,而遵循模型治理最佳实践(如建立治理框架、定期评估、持续改进和加强安全)则能进一步提升模型的可信度和可靠性。总之,容器化AI模型的监控与治理是确保其长期稳定运行的关键。
|
8月前
|
存储 监控 对象存储
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
251 0
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明