runc 详解

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" }
    ]
  }
}
  1. ociVersion
    指定遵循的 OCI 规范版本(如 "1.0.0"),确保容器运行时(如 runc)与配置文件兼容。

  2. process 部分
    定义容器内启动的进程信息,是核心中的核心:

    • args:容器启动时执行的命令(如 ["/bin/bash"] 表示启动交互终端)。
    • env:环境变量(如 PATHTERM 等,影响容器内程序的运行环境)。
    • user:进程运行的 UID/GID(这里是 0:0,即 root 用户)。
    • capabilities:容器进程拥有的 Linux capabilities(权限控制,如 CAP_NET_BIND_SERVICE 允许绑定特权端口)。
  3. root 部分

    • path:指定根文件系统的路径(这里是 rootfs,即当前目录下的 rootfs 文件夹),这是容器的“根目录”,所有文件操作都基于此。
  4. mounts 部分
    定义容器需要挂载的文件系统(如 /proc/sys/dev 等),是容器与宿主机隔离的关键:

    • 例如 /proc 挂载让容器能看到自己的进程信息,而非宿主机的。
    • 挂载选项(如 ro 表示只读)增强了安全性。
  5. linux 部分
    包含 Linux 特有的配置,与容器隔离性密切相关:

    • namespaces:定义容器使用的命名空间(如 pid 隔离进程、network 隔离网络、uts 隔离主机名等),是容器“隔离”特性的核心实现。
    • resources:限制容器的资源(如设备访问权限)。
    • maskedPaths/readonlyPaths:通过隐藏或只读保护宿主机敏感路径(如 /proc/kcore),防止容器越权访问。
  6. hostname
    容器的主机名(如 umoci-default),由 UTS 命名空间隔离,不影响宿主机。

  7. 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

操作讲解

  1. recvtty tty.sock:启动一个终端接收器,用于后续与容器内的终端交互(因为容器默认需要一个交互终端)。
  2. sudo runc create ...:基于 bundle 目录(含 rootfsconfig.json)创建容器,但仅初始化容器环境,不执行用户指定的命令(如 /bin/bash)。
  3. runc list:查看容器状态,显示为 created(已创建但未运行)。
  4. runc ps container:查看容器内的进程,发现只有 runc init 进程在运行。

为什么容器状态是 created 而不是 running
runc 的容器生命周期分为多个阶段,create 命令的作用是:

  • 完成容器的初始化工作(如创建命名空间、配置控制组、挂载 rootfs、设置网络等);
  • 不会执行用户指定的进程(如 config.json 中定义的 /bin/bash),而是停留在“就绪”状态。

这类似于“系统开机后停留在登录界面,但未输入密码登录”——环境已准备好,但核心程序(用户命令)未启动。

容器内为什么只有 runc init 进程在运行?
runc initrunc 自动创建的初始化进程(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 规则,容器可能被允许执行危险的系统调用(如 mountptrace),导致宿主机被攻击;
  • 若未限制容器的 CAP_SYS_ADMIN 等特权 capability,容器内进程可能突破隔离篡改宿主机资源;
  • 高级运行时(如 containerd、Docker)会默认启用安全基线配置(如禁用不必要的系统调用、限制特权 capability),而 runc 需完全手动配置,对用户的安全知识要求极高。

二、缺乏上层功能,需手动处理复杂依赖

runc 仅负责“从 bundle 启动隔离进程”,不包含任何上层管理能力,实际使用中需手动解决一系列问题:

  1. 网络配置runc 不提供网络功能,容器默认没有网络(无法访问互联网或其他容器)。若要配置网络,需手动:

    • 创建虚拟网卡(如 veth 设备);
    • 配置网桥(如 docker0);
    • 通过 OCI Hook 在容器启动前挂载网络命名空间;
      这些操作涉及 Linux 网络底层知识,且极易出错(如 IP 冲突、路由配置错误)。
  2. 镜像与存储管理runc 不认识“镜像”,需手动用 skopeo 拉取镜像、umoci 解压为 bundle,且无法管理镜像版本、清理冗余 layer,长期使用会导致磁盘空间浪费。

  3. 生命周期与监控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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值