Runc 深度解析:从原理到实操

Runc 深度解析:从原理到实操


一、Runc 是什么?

1.1 定义

Runc 是 OCI(Open Container Initiative)运行时规范的参考实现 ,是容器技术栈中最底层的组件,负责真正创建和运行容器进程

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    容器技术栈                                │
│                                                             │
│  用户命令:docker run / ctr run / kubectl run               │
│                      ↓                                      │
│  高级运行时:Docker Daemon / Containerd                      │
│                      ↓                                      │
│  底层运行时:Runc  ← 你在这里                                │
│                      ↓                                      │
│  Linux 内核:namespace + cgroup + 文件系统                   │
│                      ↓                                      │
│  容器进程                                                    │
└─────────────────────────────────────────────────────────────┘

1.2 一句话理解

Runc 是"容器的创建者",它读取配置文件,调用 Linux 系统调用,创建出一个隔离的进程,这就是容器。

1.3 历史演变

复制代码
┌─────────────────────────────────────────────────────────────┐
│  Runc 的诞生                                                 │
│                                                             │
│  2013年                                                      │
│  Docker 发布,使用 LXC 作为运行时                             │
│  问题:LXC 不是 Docker 自己的,依赖外部项目                    │
│                                                             │
│  2014年                                                      │
│  Docker 开发 libcontainer,自己的运行时库                     │
│  libcontainer 直接调用 Linux 系统调用                         │
│                                                             │
│  2015年                                                      │
│  OCI(Open Container Initiative)成立                        │
│  目标:制定容器格式和运行时的行业标准                          │
│                                                             │
│  Docker 将 libcontainer 重构为 runc                          │
│  runc 成为 OCI 运行时规范的第一个参考实现                      │
│                                                             │
│  现在                                                        │
│  runc 是行业标准,所有主流容器运行时都基于 runc 或兼容其接口    │
└─────────────────────────────────────────────────────────────┘

二、OCI 运行时规范

2.1 什么是 OCI?

OCI(Open Container Initiative) 是 Linux 基金会下的项目,制定容器行业的两大标准:

复制代码
┌─────────────────────────────────────────────────────────────┐
│  OCI 两大规范                                                │
│                                                             │
│  1. 镜像规范(Image Spec)                                   │
│     定义容器镜像的格式                                        │
│     ├── 镜像清单(manifest)                                 │
│     ├── 镜像层(layers)                                     │
│     └── 配置(config)                                       │
│                                                             │
│  2. 运行时规范(Runtime Spec)                               │
│     定义如何运行容器                                          │
│     ├── 容器配置格式(config.json)                          │
│     ├── 运行时接口(create、start、delete 等)                │
│     └── 容器生命周期                                         │
└─────────────────────────────────────────────────────────────┘

2.2 运行时规范核心内容

OCI 运行时规范定义了:

规范内容 说明
config.json 容器配置文件格式
命令接口 create、start、state、kill、delete 等
容器状态 created、running、paused、stopped
钩子机制 prestart、poststart、poststop
命名空间 使用哪些 Linux namespace
资源限制 cgroup 配置

2.3 Runc 与 OCI 的关系

复制代码
┌─────────────────────────────────────────────────────────────┐
│  OCI 运行时规范                                               │
│  (一份文档,定义接口和格式)                                  │
│                                                             │
│  实现者:                                                    │
│  ├── runc     ← Docker 的实现,最主流                        │
│  ├── crun     ← C 语言实现,更快更小                          │
│  ├── kata     ← 基于虚拟机的实现                              │
│  └── gvisor   ← Google 的沙箱实现                             │
│                                                             │
│  所有实现都遵循相同的接口,可以互相替换                        │
└─────────────────────────────────────────────────────────────┘

三、Runc 的工作原理

3.1 核心流程

复制代码
┌─────────────────────────────────────────────────────────────┐
│  runc create 容器流程                                         │
│                                                             │
│  1. 读取 config.json                                        │
│     ↓                                                       │
│  2. 创建容器目录(bundle)                                   │
│     ↓                                                       │
│  3. clone() 系统调用,创建新进程                              │
│     ├── 设置 namespace(隔离)                               │
│     ├── 设置 cgroup(资源限制)                              │
│     ├── 设置 rootfs(文件系统)                              │
│     └── 设置 capabilities(权限)                           │
│     ↓                                                       │
│  4. 写入容器状态到 state.json                                │
│     ↓                                                       │
│  5. 容器进入 "created" 状态                                  │
│                                                             │
│  runc start 容器流程                                         │
│     ↓                                                       │
│  6. 执行用户命令                                             │
│     ↓                                                       │
│  7. 容器进入 "running" 状态                                  │
└─────────────────────────────────────────────────────────────┘

3.2 涉及的 Linux 技术

技术 作用 Runc 中的使用
namespace 资源隔离 隔离 PID、网络、挂载点等
cgroup 资源限制 限制 CPU、内存、IO 等
pivot_root 切换根文件系统 切换到容器的 rootfs
clone() 创建新进程 创建容器进程
execve() 执行程序 执行容器内的命令
capabilities 权限控制 限制容器权限
seccomp 系统调用过滤 限制容器可用的系统调用
AppArmor/SELinux 强制访问控制 增强安全性

3.3 Namespace 隔离详解

bash 复制代码
# Runc 创建容器时设置的 namespace

# PID namespace:容器有自己的进程树
# 容器内 PID 1 的进程,在宿主机可能是 PID 12345

# Network namespace:容器有自己的网络栈
# 容器有自己的网卡、路由表、iptables

# Mount namespace:容器有自己的文件系统视图
# 容器内的挂载不影响宿主机

# UTS namespace:容器有自己的主机名
# 容器可以设置自己的 hostname

# IPC namespace:容器有自己的 IPC 资源
# 容器内的信号量、消息队列隔离

# User namespace:容器有自己的用户映射
# 容器内 root 可以映射到宿主机普通用户

四、Runc 安装与验证

4.1 安装方式

方式一:包管理器安装

bash 复制代码
# Ubuntu/Debian
apt-get update
apt-get install runc

# CentOS/RHEL
yum install runc

# 验证安装
runc --version

方式二:二进制安装

bash 复制代码
# 下载最新版本
wget https://github.com/opencontainers/runc/releases/download/v1.1.10/runc.amd64

# 安装
install -m 755 runc.amd64 /usr/local/sbin/runc

# 验证
runc --version

# 预期输出:
# runc version 1.1.10
# spec: 1.0.2-dev
# go: go1.20.10

4.2 验证安装

bash 复制代码
# 查看版本
runc --version

# 查看帮助
runc --help

# 查看支持的命令
runc help

五、实操:手动创建容器

5.1 准备工作:创建 Bundle

Bundle 是 OCI 运行时规范定义的容器包,包含:

复制代码
┌─────────────────────────────────────────────────────────────┐
│  Bundle 结构                                                 │
│                                                             │
│  mycontainer/                                               │
│  ├── config.json     ← 容器配置文件(必须)                   │
│  └── rootfs/         ← 容器根文件系统(必须)                 │
│      ├── bin/                                               │
│      ├── dev/                                               │
│      ├── etc/                                               │
│      └── ...                                                │
└─────────────────────────────────────────────────────────────┘

5.2 步骤一:创建目录结构

bash 复制代码
# 创建 bundle 目录
mkdir -p mycontainer/rootfs

# 进入目录
cd mycontainer

5.3 步骤二:准备 rootfs

方法一:使用 Docker 导出

bash 复制代码
# 拉取最小镜像
docker pull alpine:latest

# 导出 rootfs
docker export $(docker create alpine) | tar -xf - -C rootfs/

# 查看 rootfs 内容
ls rootfs/

# 输出示例:
# bin  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

方法二:使用 Containerd 导出

bash 复制代码
# 拉取镜像
ctr images pull docker.io/library/alpine:latest

# 导出镜像
ctr images export alpine.tar docker.io/library/alpine:latest

# 解压镜像(需要解压两层)
mkdir -p temp
tar -xf alpine.tar -C temp

# 找到 layer 并解压到 rootfs
# (具体步骤取决于镜像格式)

5.4 步骤三:生成配置文件

bash 复制代码
# 使用 runc 生成默认配置
runc spec

# 查看生成的 config.json
cat config.json

5.5 步骤四:理解 config.json

config.json 是容器的"说明书",告诉 runc 如何创建容器:

json 复制代码
{
  "ociVersion": "1.0.2",
  "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"
    ],
    "cwd": "/",
    "capabilities": {
      "bounding": ["CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE"],
      "effective": ["CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE"],
      "inheritable": ["CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE"],
      "permitted": ["CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE"]
    }
  },
  "root": {
    "path": "rootfs",
    "readonly": false
  },
  "hostname": "runc",
  "mounts": [
    {
      "destination": "/proc",
      "type": "proc",
      "source": "proc"
    }
  ],
  "linux": {
    "namespaces": [
      {
        "type": "pid"
      },
      {
        "type": "network"
      },
      {
        "type": "ipc"
      },
      {
        "type": "uts"
      },
      {
        "type": "mount"
      }
    ]
  }
}

关键字段详解:

复制代码
┌─────────────────────────────────────────────────────────────┐
│  config.json 关键字段                                        │
│                                                             │
│  ociVersion      OCI 规范版本                                │
│                                                             │
│  process         容器进程配置                                │
│  ├── terminal    是否分配终端                                │
│  ├── user        运行用户(uid/gid)                         │
│  ├── args        执行命令(["sh"] 表示运行 shell)            │
│  ├── env         环境变量                                    │
│  ├── cwd         工作目录                                    │
│  └── capabilities Linux 权限能力                             │
│                                                             │
│  root            根文件系统配置                              │
│  ├── path        rootfs 路径                                │
│  └── readonly    是否只读                                    │
│                                                             │
│  hostname        容器主机名                                  │
│                                                             │
│  mounts          挂载点列表                                  │
│  ├── destination 容器内挂载路径                              │
│  ├── type        文件系统类型                                │
│  └── source      宿主机源路径                                │
│                                                             │
│  linux           Linux 特定配置                              │
│  ├── namespaces  命名空间配置                                │
│  ├── resources   cgroup 资源限制                             │
│  └── seccomp     系统调用过滤                                │
└─────────────────────────────────────────────────────────────┘

5.6 步骤五:创建并启动容器

bash 复制代码
# 确保在 bundle 目录下
cd mycontainer

# 创建容器(进入 created 状态)
runc create mycontainer

# 查看容器状态
runc state mycontainer

# 输出示例:
# {
#   "ociVersion": "1.0.2",
#   "id": "mycontainer",
#   "status": "created",
#   "bundle": "/root/mycontainer",
#   "created": "2024-01-01T10:00:00.000000000Z"
# }

# 启动容器(进入 running 状态)
runc start mycontainer

# 此时容器运行 sh,你可以交互
# 输入命令测试:
# ls /
# hostname
# ps aux

# 退出容器
# exit

5.7 容器生命周期管理

bash 复制代码
# ─────────────────────────────────────────
# 查看容器状态
# ─────────────────────────────────────────
runc state mycontainer

# ─────────────────────────────────────────
# 列出所有容器
# ─────────────────────────────────────────
runc list

# 输出示例:
# ID            PID     STATUS    BUNDLE                CREATED
# mycontainer   12345   running   /root/mycontainer     2024-01-01T10:00:00Z

# ─────────────────────────────────────────
# 暂停容器
# ─────────────────────────────────────────
runc pause mycontainer

# 查看状态(变为 paused)
runc state mycontainer
# "status": "paused"

# 恢复容器
runc resume mycontainer

# ─────────────────────────────────────────
# 发送信号到容器
# ─────────────────────────────────────────
# 发送 SIGTERM
runc kill mycontainer

# 发送 SIGKILL(强制终止)
runc kill mycontainer SIGKILL

# ─────────────────────────────────────────
# 删除容器
# ─────────────────────────────────────────
# 容器停止后才能删除
runc delete mycontainer

# 确认删除
runc list
# (无输出)

5.8 完整实操流程图

复制代码
┌─────────────────────────────────────────────────────────────┐
│  Runc 手动创建容器完整流程                                    │
│                                                             │
│  1. 创建 bundle 目录                                         │
│     mkdir -p mycontainer/rootfs                             │
│     cd mycontainer                                          │
│                                                             │
│  2. 准备 rootfs                                             │
│     docker export $(docker create alpine) | tar -xf - -C rootfs/│
│                                                             │
│  3. 生成配置文件                                             │
│     runc spec                                               │
│                                                             │
│  4. 创建容器                                                 │
│     runc create mycontainer                                 │
│     状态:created                                           │
│                                                             │
│  5. 启动容器                                                 │
│     runc start mycontainer                                  │
│     状态:running                                           │
│                                                             │
│  6. 与容器交互                                               │
│     (容器内运行 sh,可输入命令)                              │
│                                                             │
│  7. 停止容器                                                 │
│     runc kill mycontainer                                   │
│     状态:stopped                                           │
│                                                             │
│  8. 删除容器                                                 │
│     runc delete mycontainer                                 │
└─────────────────────────────────────────────────────────────┘

六、Runc 命令详解

6.1 命令概览

bash 复制代码
runc --help

# 可用命令:
# create      创建容器
# start       启动容器
# run         创建并启动容器(一步完成)
# delete      删除容器
# state       查看容器状态
# list        列出所有容器
# pause       暂停容器
# resume      恢复容器
# kill        发送信号到容器
# exec        在运行中的容器内执行命令
# spec        生成默认配置文件
# events      显示容器事件
# update      更新容器资源限制
# ps          显示容器内进程

6.2 create vs run

bash 复制代码
# 方式一:分步执行(create + start)
runc create mycontainer
runc start mycontainer

# 方式二:一步完成(run)
runc run mycontainer

# 区别:
# create + start:可以在 created 状态做些操作(如设置网络)
# run:一步到位,适合简单场景

6.3 exec - 在容器内执行命令

bash 复制代码
# 先启动一个后台容器
# 修改 config.json,设置 process.terminal = false
# process.args = ["sleep", "3600"]

runc run -d mycontainer

# 在容器内执行命令
runc exec mycontainer ls /

# 在容器内执行交互式命令
runc exec -t mycontainer sh

# 查看 exec 命令帮助
runc exec --help

6.4 update - 动态更新资源限制

bash 复制代码
# 更新 CPU 限制
runc update --cpu-quota 50000 mycontainer

# 更新内存限制
runc update --memory 536870912 mycontainer  # 512MB

# 通过 JSON 文件更新
cat > resources.json << EOF
{
  "cpu": {
    "quota": 50000,
    "period": 100000
  },
  "memory": {
    "limit": 536870912
  }
}
EOF

runc update -r resources.json mycontainer

6.5 events - 监控容器事件

bash 复制代码
# 实时监控容器事件
runc events mycontainer

# 输出示例:
# {"type":"stats","id":"mycontainer","data":{"cpu":{"usage":{"total":12345678}}}}

七、深入 config.json 配置

7.1 完整配置示例

json 复制代码
{
  "ociVersion": "1.0.2",
  "process": {
    "terminal": false,
    "user": {
      "uid": 0,
      "gid": 0,
      "additionalGids": [10, 100]
    },
    "args": [
      "/bin/sh",
      "-c",
      "echo 'Hello from container' && sleep 3600"
    ],
    "env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "TERM=xterm",
      "HOSTNAME=runc-container",
      "MY_VAR=custom_value"
    ],
    "cwd": "/root",
    "capabilities": {
      "bounding": [
        "CAP_AUDIT_WRITE",
        "CAP_CHOWN",
        "CAP_DAC_OVERRIDE",
        "CAP_FOWNER",
        "CAP_FSETID",
        "CAP_KILL",
        "CAP_MKNOD",
        "CAP_NET_BIND_SERVICE",
        "CAP_NET_RAW",
        "CAP_SETFCAP",
        "CAP_SETGID",
        "CAP_SETPCAP",
        "CAP_SETUID",
        "CAP_SYS_CHROOT"
      ],
      "effective": [
        "CAP_AUDIT_WRITE",
        "CAP_CHOWN",
        "CAP_DAC_OVERRIDE"
      ],
      "inheritable": [],
      "permitted": [
        "CAP_AUDIT_WRITE",
        "CAP_CHOWN"
      ]
    },
    "rlimits": [
      {
        "type": "RLIMIT_NOFILE",
        "hard": 1024,
        "soft": 1024
      }
    ],
    "noNewPrivileges": true
  },
  "root": {
    "path": "rootfs",
    "readonly": false
  },
  "hostname": "runc-container",
  "mounts": [
    {
      "destination": "/proc",
      "type": "proc",
      "source": "proc",
      "options": ["nosuid", "noexec", "nodev"]
    },
    {
      "destination": "/dev",
      "type": "tmpfs",
      "source": "tmpfs",
      "options": ["nosuid", "strictatime", "mode=755", "size=65536k"]
    },
    {
      "destination": "/dev/pts",
      "type": "devpts",
      "source": "devpts",
      "options": ["nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"]
    },
    {
      "destination": "/dev/shm",
      "type": "tmpfs",
      "source": "shm",
      "options": ["nosuid", "noexec", "nodev", "mode=1777", "size=65536k"]
    },
    {
      "destination": "/dev/mqueue",
      "type": "mqueue",
      "source": "mqueue",
      "options": ["nosuid", "noexec", "nodev"]
    },
    {
      "destination": "/sys",
      "type": "sysfs",
      "source": "sysfs",
      "options": ["nosuid", "noexec", "nodev", "ro"]
    },
    {
      "destination": "/etc/resolv.conf",
      "type": "bind",
      "source": "/etc/resolv.conf",
      "options": ["ro", "bind"]
    },
    {
      "destination": "/etc/hosts",
      "type": "bind",
      "source": "/etc/hosts",
      "options": ["ro", "bind"]
    }
  ],
  "hooks": {
    "prestart": [
      {
        "path": "/usr/bin/setup-network",
        "args": ["setup-network", "mycontainer"],
        "env": ["LOG_LEVEL=debug"]
      }
    ],
    "poststart": [
      {
        "path": "/usr/bin/log-start",
        "args": ["log-start", "mycontainer"]
      }
    ],
    "poststop": [
      {
        "path": "/usr/bin/cleanup",
        "args": ["cleanup", "mycontainer"]
      }
    ]
  },
  "linux": {
    "uidMappings": [
      {
        "containerID": 0,
        "hostID": 1000,
        "size": 1
      }
    ],
    "gidMappings": [
      {
        "containerID": 0,
        "hostID": 1000,
        "size": 1
      }
    ],
    "resources": {
      "cpu": {
        "shares": 1024,
        "quota": 100000,
        "period": 100000,
        "cpus": "0-1"
      },
      "memory": {
        "limit": 536870912,
        "reservation": 268435456,
        "swap": 1073741824
      },
      "blockIO": {
        "weight": 500
      },
      "pids": {
        "limit": 1024
      }
    },
    "namespaces": [
      {
        "type": "pid"
      },
      {
        "type": "network"
      },
      {
        "type": "ipc"
      },
      {
        "type": "uts"
      },
      {
        "type": "mount"
      },
      {
        "type": "cgroup"
      },
      {
        "type": "user"
      }
    ],
    "seccomp": {
      "defaultAction": "SCMP_ACT_ERRNO",
      "architectures": ["SCMP_ARCH_X86_64"],
      "syscalls": [
        {
          "names": ["read", "write", "exit"],
          "action": "SCMP_ACT_ALLOW"
        }
      ]
    },
    "rootfsPropagation": "private"
  },
  "annotations": {
    "org.example.key": "value"
  }
}

7.2 关键配置详解

7.2.1 Namespace 配置
json 复制代码
"namespaces": [
  {"type": "pid"},      // 进程隔离
  {"type": "network"},  // 网络隔离
  {"type": "ipc"},      // IPC 隔离
  {"type": "uts"},      // 主机名隔离
  {"type": "mount"},    // 挂载点隔离
  {"type": "cgroup"},   // Cgroup 隔离
  {"type": "user"}      // 用户隔离
]

共享 namespace(加入已有 namespace):

json 复制代码
{
  "type": "network",
  "path": "/proc/12345/ns/net"  // 加入 PID 12345 的网络 namespace
}
7.2.2 资源限制配置
json 复制代码
"resources": {
  "cpu": {
    "shares": 1024,      // CPU 权重(相对值)
    "quota": 100000,     // CPU 配额(微秒)
    "period": 100000,    // CPU 周期(微秒)
    "cpus": "0-1"        // 绑定的 CPU 核心
  },
  "memory": {
    "limit": 536870912,       // 内存硬限制(512MB)
    "reservation": 268435456, // 内存软限制(256MB)
    "swap": 1073741824        // 内存+Swap 限制(1GB)
  },
  "blockIO": {
    "weight": 500  // IO 权重(100-1000)
  },
  "pids": {
    "limit": 1024  // 最大进程数
  }
}
7.2.3 User Namespace 映射
json 复制代码
"uidMappings": [
  {
    "containerID": 0,   // 容器内 UID 0
    "hostID": 1000,     // 映射到宿主机 UID 1000
    "size": 1           // 映射范围大小
  }
],
"gidMappings": [
  {
    "containerID": 0,   // 容器内 GID 0
    "hostID": 1000,     // 映射到宿主机 GID 1000
    "size": 1
  }
]

效果:容器内的 root(UID 0)实际上是宿主机的普通用户(UID 1000)

7.2.4 Hooks(钩子)
json 复制代码
"hooks": {
  "prestart": [   // 容器创建前执行
    {
      "path": "/usr/bin/setup-network",
      "args": ["setup-network", "mycontainer"]
    }
  ],
  "poststart": [  // 容器启动后执行
    {
      "path": "/usr/bin/log-start",
      "args": ["log-start", "mycontainer"]
    }
  ],
  "poststop": [   // 容器停止后执行
    {
      "path": "/usr/bin/cleanup",
      "args": ["cleanup", "mycontainer"]
    }
  ]
}
7.2.5 Seccomp(系统调用过滤)
json 复制代码
"seccomp": {
  "defaultAction": "SCMP_ACT_ERRNO",  // 默认拒绝
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": ["read", "write", "open", "close", "exit"],
      "action": "SCMP_ACT_ALLOW"  // 允许这些系统调用
    },
    {
      "names": ["reboot", "kexec_load"],
      "action": "SCMP_ACT_KILL"  // 禁止并杀死进程
    }
  ]
}

八、Runc 与 Containerd 的关系

8.1 调用关系

复制代码
┌─────────────────────────────────────────────────────────────┐
│  Containerd 调用 Runc 的流程                                  │
│                                                             │
│  1. Containerd 收到创建容器请求                               │
│     ↓                                                       │
│  2. Containerd 准备 bundle                                   │
│     ├── 解压镜像到 rootfs                                    │
│     └── 生成 config.json                                    │
│     ↓                                                       │
│  3. Containerd 调用 runc create                              │
│     runc create --bundle /var/run/containerd/xxx container-id│
│     ↓                                                       │
│  4. Runc 创建容器进程                                         │
│     ↓                                                       │
│  5. Containerd 调用 runc start                               │
│     runc start container-id                                 │
│     ↓                                                       │
│  6. 容器运行                                                 │
│     ↓                                                       │
│  7. Containerd 通过 shim 进程监控容器                         │
└─────────────────────────────────────────────────────────────┘

8.2 Shim 进程的作用

复制代码
┌─────────────────────────────────────────────────────────────┐
│  Shim 进程                                                   │
│                                                             │
│  问题:                                                      │
│  如果 containerd 重启,如何保持容器运行?                      │
│                                                             │
│  解决:Shim 进程                                             │
│  ├── 每个容器一个 shim 进程                                   │
│  ├── Shim 是 containerd 和 runc 的中间层                     │
│  ├── Shim 持有容器的 stdin/stdout/stderr                     │
│  └── Containerd 重启后,通过 shim 重新连接容器                 │
│                                                             │
│  架构:                                                      │
│  containerd → shim → runc → 容器进程                         │
└─────────────────────────────────────────────────────────────┘

8.3 查看 Containerd 调用 Runc

bash 复制代码
# 查看 containerd 创建的容器
ctr containers list

# 查看容器目录
ls /run/containerd/io.containerd.runtime.v2.task/k8s.io/<container-id>/

# 文件包括:
# config.json   - runc 配置文件
# log.json      - 容器日志
# init.pid      - 容器进程 PID

# 可以直接用 runc 管理这个容器
cd /run/containerd/io.containerd.runtime.v2.task/k8s.io/<container-id>/
runc state <container-id>

九、高级实操

9.1 创建一个有网络能力的容器

bash 复制代码
# 1. 创建 bundle
mkdir -p netcontainer/rootfs
cd netcontainer
docker export $(docker create alpine) | tar -xf - -C rootfs/

# 2. 生成配置
runc spec

# 3. 创建网络 namespace
ip netns add mynetns

# 4. 配置网络(创建 veth pair)
ip link add veth0 type veth peer name veth1
ip link set veth1 netns mynetns
ip netns exec mynetns ip link set veth1 up
ip netns exec mynetns ip addr add 10.0.0.2/24 dev veth1

# 5. 修改 config.json,加入网络 namespace
# 找到 namespaces 部分,修改 network 类型:
# {
#   "type": "network",
#   "path": "/var/run/netns/mynetns"
# }

# 6. 创建并启动容器
runc run mynetcontainer

# 7. 在容器内测试网络
# ip addr
# ping 10.0.0.1

9.2 创建一个资源受限的容器

bash 复制代码
# 1. 创建 bundle
mkdir -p limitedcontainer/rootfs
cd limitedcontainer
docker export $(docker create alpine) | tar -xf - -C rootfs/

# 2. 生成配置
runc spec

# 3. 修改 config.json,添加资源限制
# 在 linux.resources 部分添加:

cat > resources_patch.json << 'EOF'
{
  "linux": {
    "resources": {
      "cpu": {
        "shares": 512,
        "quota": 50000,
        "period": 100000
      },
      "memory": {
        "limit": 134217728
      },
      "pids": {
        "limit": 100
      }
    }
  }
}
EOF

# 手动合并到 config.json(或使用 jq)
# 限制说明:
# - CPU:最多使用 0.5 核
# - 内存:最多 128MB
# - 进程数:最多 100 个

# 4. 运行容器
runc run limitedcontainer

9.3 使用 User Namespace(安全容器)

bash 复制代码
# 1. 创建 bundle
mkdir -p usercontainer/rootfs
cd usercontainer
docker export $(docker create alpine) | tar -xf - -C rootfs/

# 2. 生成配置
runc spec

# 3. 修改 config.json,添加 user namespace 映射
# 在 linux 部分添加:

# "uidMappings": [
#   {"containerID": 0, "hostID": 1000, "size": 1}
# ],
# "gidMappings": [
#   {"containerID": 0, "hostID": 1000, "size": 1}
# ],
# "namespaces": [
#   {"type": "pid"},
#   {"type": "network"},
#   {"type": "ipc"},
#   {"type": "uts"},
#   {"type": "mount"},
#   {"type": "cgroup"},
#   {"type": "user"}  // 添加 user namespace
# ]

# 4. 运行容器
runc run usercontainer

# 5. 在容器内验证
# id
# uid=0(root) gid=0(root)

# 在宿主机查看进程的真实 UID
ps aux | grep <容器进程PID>
# 会看到进程的 UID 是 1000,不是 0

9.4 调试 Runc

bash 复制代码
# 启用调试日志
runc --debug run mycontainer

# 查看容器详细信息
runc state mycontainer

# 查看容器内所有进程
runc ps mycontainer

# 监控容器资源使用
runc events mycontainer

# 使用 strace 跟踪 runc 系统调用
strace -f -o runc.trace runc create mycontainer

十、常见问题排查

10.1 权限问题

bash 复制代码
# 错误:permission denied
# 原因:需要 root 权限

# 解决
sudo runc run mycontainer

10.2 rootfs 不存在

bash 复制代码
# 错误:rootfs does not exist
# 原因:config.json 中 root.path 指向的目录不存在

# 检查配置
cat config.json | grep -A 2 '"root"'

# 确保 rootfs 目录存在
ls rootfs/

10.3 容器立即退出

bash 复制代码
# 错误:容器启动后立即退出
# 原因:process.args 中的命令执行完毕后退出了

# 解决:使用持久运行的命令
# 修改 config.json:
# "args": ["sleep", "3600"]
# 或
# "args": ["sh"]  # 交互式

10.4 网络不通

bash 复制代码
# 错误:容器内无法访问网络
# 原因:网络 namespace 配置不正确

# 检查 namespace 配置
cat config.json | grep -A 10 '"namespaces"'

# 确保有 network namespace
# 如果要共享宿主机网络,可以移除 network namespace

10.5 cgroup 挂载问题

bash 复制代码
# 错误:cgroup filesystem not mounted
# 原因:系统 cgroup 未正确挂载

# 检查 cgroup 挂载
mount | grep cgroup

# 手动挂载(如果需要)
mount -t cgroup2 none /sys/fs/cgroup

十一、Runc 与其他运行时对比

11.1 对比表

运行时 语言 特点 适用场景
runc Go OCI 标准实现,最成熟 通用场景
crun C 更快、更小、内存占用低 性能敏感场景
kata Go 基于虚拟机,强隔离 安全敏感场景
gvisor C++ 用户态内核,沙箱隔离 安全敏感场景
youki Rust 性能好,内存安全 新兴选择

11.2 性能对比

bash 复制代码
# 创建 1000 个容器的时间对比(参考值)

# runc (Go)
# ~30 秒

# crun (C)
# ~10 秒

# kata (VM)
# ~300 秒(需要启动虚拟机)

十二、总结

12.1 核心要点

复制代码
┌─────────────────────────────────────────────────────────────┐
│  Runc 核心知识点                                              │
│                                                             │
│  1. 定位                                                    │
│     Runc 是 OCI 运行时规范的参考实现                          │
│     是容器技术栈的最底层                                      │
│                                                             │
│  2. 职责                                                    │
│     读取 config.json,调用 Linux 系统调用,创建容器进程        │
│                                                             │
│  3. 核心技术                                                 │
│     namespace(隔离)+ cgroup(限制)+ rootfs(文件系统)      │
│                                                             │
│  4. 使用方式                                                │
│     直接使用:runc create/start/run/delete                   │
│     间接使用:通过 containerd/docker 调用                     │
│                                                             │
│  5. 配置文件                                                │
│     config.json 定义容器的所有配置                            │
│     包括 namespace、cgroup、mounts、process 等                │
└─────────────────────────────────────────────────────────────┘

12.2 学习路径

复制代码
┌─────────────────────────────────────────────────────────────┐
│  Runc 学习路径                                               │
│                                                             │
│  入门                                                       │
│  ├── 理解 OCI 规范                                          │
│  ├── 掌握 runc 基本命令                                     │
│  └── 手动创建一个简单容器                                    │
│                                                             │
│  进阶                                                       │
│  ├── 深入理解 config.json                                   │
│  ├── 配置 namespace 和 cgroup                               │
│  └── 使用 hooks 和 seccomp                                  │
│                                                             │
│  高级                                                       │
│  ├── 理解 runc 与 containerd 的关系                          │
│  ├── 调试和排查问题                                         │
│  └── 对比其他运行时(crun、kata)                             │
└─────────────────────────────────────────────────────────────┘

12.3 一句话总结

Runc 是容器的"创建者",它把 config.json 变成一个真实的隔离进程。


十三、参考资源

相关推荐
STDD1 小时前
ntfy 自托管推送通知服务搭建:一条 curl 命令向手机发送通知
java·开发语言·智能手机
小林ixn2 小时前
从拼多多手机号验证到模板引擎:深入正则表达式与 JS 字符串处理
开发语言·javascript·正则表达式
手可摘星辰的少年2 小时前
Linux字符设备驱动的实现与QEMU验证
linux
手可摘星辰的少年2 小时前
使用额外ext4磁盘镜像在QEMU中传递与加载内核模块
linux
周末也要写八哥2 小时前
线程的生命周期之线程睡眠
java·开发语言·jvm
炸薯条!2 小时前
二叉树的链式表示(2)
java·数据结构·算法
右耳朵猫AI2 小时前
Python周刊2026W22 | Django 6.1 Alpha 1发布、Nuitka 4.1发布、PEP 831终稿、PEP 808已接受
开发语言·python·django
半个烧饼不加肉2 小时前
JS 底层探究-- 普通函数和构造函数
开发语言·javascript·原型模式
hai3152475432 小时前
libcore_final.c —— 九章数流矩阵系统
linux·运维·网络