runc 详解
OCI 运行时规范(OCI Runtime Specification)
准备工作:
# 更新软件包索引
sudo apt update
# 安装 runc
sudo apt install runc
# 安装 jq
sudo apt install jq
# 安装 skopeo(Ubuntu 20.04+ 官方仓库提供)
sudo apt install -y skopeo
# 安装 umoci
wget https://github.com/opencontainers/umoci/releases/download/v0.4.7/umoci.amd64 -O umoci
chmod +x umoci
sudo mv umoci /usr/local/bin/
生成示例规范
> runc spec # 会生成 config.json 文件
> cat config.json | jq
config.json 文件主要包含 runc 开始运行容器所需的所有信息,比如:
- 正在运行的进程的属性
- 定义的环境变量
- 用户和组 ID
- 所需的挂载点
- 要设置的 Linux 命名空间
从镜像获取根文件系统
skopeo copy docker://opensuse/tumbleweed:latest oci:tumbleweed:latest
sudo umoci unpack --image tumbleweed:latest bundle
这两个命令用于处理容器镜像,涉及镜像格式转换和镜像解压操作,常用于容器镜像的管理和分析。
第一个命令:
- 使用
skopeo工具将 Docker 格式的镜像转换为 OCI(Open Container Initiative)标准格式的镜像。会从 Docker Hub 拉取opensuse/tumbleweed:latest镜像,并在当前目录生成符合 OCI 标准的镜像文件,存储在与镜像名相关的目录结构中。 skopeo:是一个用于检查、复制和管理容器镜像的工具,支持多种镜像格式和存储位置之间的转换。- 各部分含义:
docker://opensuse/tumbleweed:latest:源镜像地址docker://:表示从 Docker 镜像仓库(如 Docker Hub)获取镜像opensuse/tumbleweed:latest:具体镜像名称和标签,这里是 openSUSE 的滚动更新版本
oci:tumbleweed:latest:目标镜像地址oci::表示要转换为 OCI 标准格式的镜像tumbleweed:latest:转换后的镜像名称和标签,会在当前目录创建对应的 OCI 镜像存储结构
第二个命令:
- 使用
umoci工具将 OCI 格式的镜像解压到指定目录(称为 bundle),生成容器运行所需的文件系统和配置。解压后的 bundle 可以用于直接运行容器(如通过runc等工具),也可以用于检查镜像内容、修改文件系统后重新打包镜像等场景。 umoci:是一个专门用于操作 OCI 镜像的工具,支持镜像的解压、修改、重新打包等操作。- 各部分含义:
unpack:表示执行解压操作--image tumbleweed:latest:指定要解压的 OCI 镜像bundle:指定解压后文件的存放目录(会自动创建)
- 会在当前目录创建
bundle文件夹,其中包含:rootfs目录:镜像的根文件系统,包含了操作系统的所有文件和目录config.json文件:容器的配置信息,包括进程、环境变量、权限等设置
总结来说,这两个命令组合起来实现了 “Docker 镜像 → OCI 镜像 → 可运行的文件系统” 的转换过程,常用于容器底层技术的研究、镜像定制或跨平台迁移等场景。
解压的容器映像已经包含我们运行该包所需的运行时规范:
# 将 bundle 目录及其内容的所有者改为当前用户
sudo chown -R $(id -u) bundle
cat bundle/config.json
{
"ociVersion": "1.0.0",
"process": {
"terminal": true,
"user": { "uid": 0, "gid": 0 },
"args": ["/bin/bash"],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm",
"HOME=/root"
],
"cwd": "/",
"capabilities": { [...] },
"rlimits": [...]
},
"root": { "path": "rootfs" },
"hostname": "mrsdalloway",
"mounts": [...],
"annotations": {
"org.opencontainers.image.title": "openSUSE Tumbleweed Base Container",
"org.opencontainers.image.url": "https://www.opensuse.org/",
"org.opencontainers.image.vendor": "openSUSE Project",
"org.opencontainers.image.version": "20250816.34.185",
[...]
},
"linux": {
"resources": { "devices": [ { "allow": false, "access": "rwm" } ] },
"namespaces": [
{ "type": "pid" },
{ "type": "network" },
{ "type": "ipc" },
{ "type": "uts" },
{ "type": "mount" }
]
}
}
-
ociVersion
指定遵循的 OCI 规范版本(如"1.0.0"),确保容器运行时(如 runc)与配置文件兼容。 -
process部分
定义容器内启动的进程信息,是核心中的核心:args:容器启动时执行的命令(如["/bin/bash"]表示启动交互终端)。env:环境变量(如PATH、TERM等,影响容器内程序的运行环境)。user:进程运行的 UID/GID(这里是0:0,即 root 用户)。capabilities:容器进程拥有的 Linux capabilities(权限控制,如CAP_NET_BIND_SERVICE允许绑定特权端口)。
-
root部分path:指定根文件系统的路径(这里是rootfs,即当前目录下的rootfs文件夹),这是容器的“根目录”,所有文件操作都基于此。
-
mounts部分
定义容器需要挂载的文件系统(如/proc、/sys、/dev等),是容器与宿主机隔离的关键:- 例如
/proc挂载让容器能看到自己的进程信息,而非宿主机的。 - 挂载选项(如
ro表示只读)增强了安全性。
- 例如
-
linux部分
包含 Linux 特有的配置,与容器隔离性密切相关:namespaces:定义容器使用的命名空间(如pid隔离进程、network隔离网络、uts隔离主机名等),是容器“隔离”特性的核心实现。resources:限制容器的资源(如设备访问权限)。maskedPaths/readonlyPaths:通过隐藏或只读保护宿主机敏感路径(如/proc/kcore),防止容器越权访问。
-
hostname
容器的主机名(如umoci-default),由 UTS 命名空间隔离,不影响宿主机。 -
annotations
通过annotations可以向容器添加任意元数据,更高级别的运行时可以利用这些元数据,向规范添加附加信息。
runc 操控容器
准备
git clone --branch v1.1.12 https://github.com/opencontainers/runc.git
cd runc
go build -o recvtty ./contrib/cmd/recvtty
sudo mv recvtty /usr/local/bin/
recvtty --help
cd ../
recvtty tty.sock # 执行后会 “阻塞等待”,表示套接字已成功创建,等待容器连接。
创建容器
在另一个终端:
cd /home/kevin/project/runtime_test
sudo runc create -b bundle --console-socket $(pwd)/tty.sock container
会显示:
kevin@ZB-PF4WQHKS:~/project/runtime_test$ sudo runc list
ID PID STATUS BUNDLE CREATED OWNER
container 3647133 created /home/kevin/project/runtime_test/bundle 2025-08-19T03:24:12.485927902Z root
操作讲解
recvtty tty.sock:启动一个终端接收器,用于后续与容器内的终端交互(因为容器默认需要一个交互终端)。sudo runc create ...:基于bundle目录(含rootfs和config.json)创建容器,但仅初始化容器环境,不执行用户指定的命令(如/bin/bash)。runc list:查看容器状态,显示为created(已创建但未运行)。runc ps container:查看容器内的进程,发现只有runc init进程在运行。
为什么容器状态是 created 而不是 running?
runc 的容器生命周期分为多个阶段,create 命令的作用是:
- 完成容器的初始化工作(如创建命名空间、配置控制组、挂载
rootfs、设置网络等); - 但不会执行用户指定的进程(如
config.json中定义的/bin/bash),而是停留在“就绪”状态。
这类似于“系统开机后停留在登录界面,但未输入密码登录”——环境已准备好,但核心程序(用户命令)未启动。
容器内为什么只有 runc init 进程在运行?
runc init 是 runc 自动创建的初始化进程(PID 通常为容器内的 1 号进程),它的作用是:
- 作为容器内的“第一个进程”,负责后续启动用户指定的命令(如
/bin/bash); - 管理容器内的信号转发(如宿主机发送
kill信号时,由init传递给用户进程); - 监控用户进程的状态,确保容器生命周期与用户进程一致(用户进程退出,容器也会退出)。
此时 runc init 处于“等待状态”,等待你执行 runc start 命令来启动用户进程(/bin/bash)。
启动容器
该runc init命令设置一个包含所有必要命名空间的全新环境,并启动一个新的初始进程。主进程/bin/bash尚未在容器内运行,这可以通过以下方式完成runc start:
sudo runc start container
在进程运行的终端中recvtty,现在应该弹出一个新的 bash shell 会话:
kevin@ZB-PF4WQHKS:~/project/runtime_test$ recvtty tty.sock
bash: /root/.bashrc: Permission denied
umoci-default:/ # ls
ls
.profile boot etc lib mnt proc run srv tmp var
bin dev home lib64 opt root sbin sys usr
umoci-default:/ #
暂停、恢复、停止、删除、查看事件
使用pause可以暂停容器,此时 tty 是没有响应的;再调用resume可以恢复容器,之前尝试在 tty 输入的所有内容现在都会在恢复的容器终端中弹出:
sudo runc pause container # recvtty 会话会阻塞住
sudo runc resume container # recvtty 会话会恢复
要停止容器,我们只需退出recvtty会话即可。处于该状态的容器stopped无法再次运行,因此必须从新状态重新创建它们。然后,可以使用runc delete命令移除容器
sudo runc delete container
可以查看容器的事件
sudo runc events container | jq
事件不在此处列举。
不要直接使用 runc
一、安全配置门槛极高,易因疏漏导致风险
runc 虽然支持 seccomp(系统调用过滤)、AppArmor/SELinux(访问控制)、 capabilities(权限精细化控制)等安全特性,但这些功能需要手动配置且参数复杂,稍有疏忽就会留下安全隐患:
- 例如,若未正确配置 seccomp 规则,容器可能被允许执行危险的系统调用(如
mount、ptrace),导致宿主机被攻击; - 若未限制容器的
CAP_SYS_ADMIN等特权 capability,容器内进程可能突破隔离篡改宿主机资源; - 高级运行时(如 containerd、Docker)会默认启用安全基线配置(如禁用不必要的系统调用、限制特权 capability),而
runc需完全手动配置,对用户的安全知识要求极高。
二、缺乏上层功能,需手动处理复杂依赖
runc 仅负责“从 bundle 启动隔离进程”,不包含任何上层管理能力,实际使用中需手动解决一系列问题:
-
网络配置:
runc不提供网络功能,容器默认没有网络(无法访问互联网或其他容器)。若要配置网络,需手动:- 创建虚拟网卡(如
veth设备); - 配置网桥(如
docker0); - 通过 OCI Hook 在容器启动前挂载网络命名空间;
这些操作涉及 Linux 网络底层知识,且极易出错(如 IP 冲突、路由配置错误)。
- 创建虚拟网卡(如
-
镜像与存储管理:
runc不认识“镜像”,需手动用skopeo拉取镜像、umoci解压为 bundle,且无法管理镜像版本、清理冗余 layer,长期使用会导致磁盘空间浪费。 -
生命周期与监控:
runc没有后台守护进程,无法自动重启崩溃的容器,也不提供日志收集、健康检查等功能,需手动编写脚本或集成第三方工具(如 systemd、prometheus),运维成本极高。
三、生产环境需要标准化与可扩展性
生产环境中,容器通常需要与编排系统(如 Kubernetes)、监控系统、CI/CD 流程集成,而 runc 缺乏标准化接口:
- 高级运行时(如 containerd)通过 CRI(容器运行时接口)与 Kubernetes 对接,支持动态资源调整、滚动更新等编排能力;
runc无此类接口,需手动编写适配代码,难以融入现代容器生态;- 高级运行时支持多运行时切换(如从 runc 切换到安全隔离更强的 Kata Containers),而
runc本身无法提供这种灵活性。
四、“无根模式”也无法解决所有问题
虽然 runc 支持“无根模式”(非 root 用户运行容器,减少特权风险),但这仅能缓解部分安全问题,仍无法解决:
- 网络、存储等手动配置的复杂性;
- 缺乏上层管理工具导致的运维负担;
- 与现有容器生态(如 Kubernetes)的兼容性问题。
总结:runc 的定位是“底层执行引擎”,而非“生产工具”
runc 的设计目标是作为高级运行时的“执行组件”(如 containerd 调用 runc 启动容器),而非直接面向用户的工具。它的价值在于提供标准化的底层隔离能力,而将复杂的上层管理、安全配置、生态集成交给更高级的运行时(如 containerd、CRI-O)处理。
因此,除非是为了学习容器底层原理,或有特殊的定制化需求(如构建自己的容器引擎),否则生产环境中应优先使用高级运行时,而非直接使用 runc。
2224

被折叠的 条评论
为什么被折叠?



