Post

runc基本实验

runc基本实验

手动使用runc来运行一个容器。

✅ 1. 准备基本环境

确保已经有:

  • 已安装的runc(从GitHub编译或通过包管理器)
  • 一个root权限的shell(比如使用 sudo

✅ 2. 准备一个rootfs

容器启动需要一个根文件系统(rootfs),可以用busybox来构造一个轻量的rootfs

使用BusyBox构造最小rootfs

1
2
3
4
5
6
7
8
$ mkdir ~/mycontainer
$ cd ~/mycontainer

# 下载并解压busybox静态构建版本
$ wget https://busybox.net/downloads/binaries/1.36.0-i686-uclibc/busybox
$ chmod +x busybox
$ mkdir -p rootfs/bin
$ cp busybox rootfs/bin/

然后创建必要的目录:

1
2
$ cd rootfs
$ mkdir -p etc proc sys dev tmp

busybox添加一个简单的shell环境:

1
2
3
$ cd bin
$ ln -s busybox sh
$ cd ../..

✅ 3. 生成默认的config.json

runc提供了一个命令来自动生成配置文件:

1
$ runc spec

这会在当前目录生成一个config.json文件。

✅ 4. 修改config.json

主要改动两个地方:

① 设置root文件系统路径

1
2
3
4
"root": {
  "path": "rootfs",
  "readonly": false
}

确保路径和你准备的rootfs一致。

② 修改默认命令(process.args

默认是 ["sh"],你可以保留它表示用busybox里的 sh

1
2
3
4
5
"process": {
  ...
  "args": ["sh"],
  ...
}

✅ 5. 运行容器

使用runc启动容器(容器名任意):

1
$ sudo runc run mycontainer

你会进入一个busybox shell

1
$ / #

说明容器已经启动成功了!

✅ 示例目录结构回顾:

1
2
3
4
5
6
7
8
9
10
11
mycontainer/
├── config.json
└── rootfs/
    ├── bin/
    │   ├── busybox
    │   └── sh -> busybox
    ├── etc/
    ├── proc/
    ├── sys/
    ├── dev/
    └── tmp/

为什么 ./sh 不完全等同于 ./busybox

实际上,./sh./busybox 是同一个二进制文件,但是:

  • BusyBox 根据它被执行时的 程序名(argv[0]) 来判断运行哪个子命令(applet)。
  • ./busybox 这个名字会让 BusyBox 进入默认模式,必须带参数指定具体功能,比如 ./busybox ls
  • ./sh 这个名字告诉 BusyBox:“启动 sh(shell)这个 applet”,即直接进入交互式 shell。

详细解释

  1. 符号链接(symlink)只是给文件起了个别名,文件本身内容没变。
  2. 运行程序时,系统会把调用的程序名(argv[0])传给程序。
  3. BusyBox 程序会检查 argv[0]
    • 如果是 busybox,则需要后面带命令参数,比如 lscat
    • 如果是 sh,它知道用户想直接启动 shell。

举例对比

1
2
3
4
5
6
7
bash


复制编辑
./busybox ls     # argv[0]="busybox",argv[1]="ls" → 运行 ls 命令
./sh             # argv[0]="sh" → 运行 shell 命令
./sh ls          # argv[0]="sh",argv[1]="ls" → shell 解释执行“ls”脚本或文件,shell不会自动调用 busybox 的 ls

所以:

  • ./sh 会进入 shell 交互环境
  • ./sh ls 是告诉 shell 去执行名为 ls 的脚本文件(不是调用 BusyBox 的 ls applet)

总结

命令作用
./busybox需要跟命令参数指定 applet
./busybox ls调用 BusyBox 中的 ls 命令
./sh进入 BusyBox shell
./sh lsshell 执行 ls 文件(会报错)

如果你想运行 ls,可以:

1
2
3
4
5
bash


复制编辑
./busybox ls

或者先进入 shell:

1
2
3
4
5
6
7
bash


复制编辑
./sh
# 进入 shell 后
ls

报错:

1
2
3
4
5
bash


复制编辑
exec /bin/busybox: no such file or directory

说明容器内找不到 /bin/busybox 这个文件,虽然你本地 rootfs/bin/busybox 存在,但可能是:


1. 路径不匹配

确认 config.json"root": {"path": "rootfs"},这意味着容器运行时会把 rootfs 目录当作容器的根目录 /,因此:

  • 你容器里的路径 /bin/busybox 实际对应宿主机的 mycontainer/rootfs/bin/busybox

2. 运行环境缺少动态库导致找不到文件

即使文件存在,如果 BusyBox 是动态链接的,容器里找不到它依赖的动态库,也会报类似“no such file or directory”的错误。


解决方案:检查 BusyBox 是否静态编译

用宿主机运行:

1
2
3
4
5
bash


复制编辑
file rootfs/bin/busybox

如果输出包含:

  • dynamically linked — 容器里可能找不到依赖库,启动失败。
  • statically linked — 不依赖外部库,容器里应该能直接跑。

如何确认或制作静态 BusyBox

如果是动态链接版本,请重新用静态版本替换:

1
2
3
4
5
6
bash


复制编辑
wget https://busybox.net/downloads/binaries/1.36.0-x86_64-uclibc/busybox -O rootfs/bin/busybox
chmod +x rootfs/bin/busybox

3. 权限问题

确认权限:

1
2
3
4
5
bash


复制编辑
ls -l rootfs/bin/busybox

权限至少要有执行权限 -rwxr-xr-x


4. 再次确认 config.json 启动命令

建议改成:

1
2
3
4
5
6
7
8
9
10
11
12
13
json


复制编辑
"process": {
  "terminal": true,
  "args": ["/bin/sh"],
  "env": [
    "PATH=/bin:/usr/bin:/sbin:/usr/sbin",
    "TERM=xterm"
  ],
  ...
}

因为 /bin/sh 是链接指向 busybox。


5. 确认 rootfs 结构

1
2
3
4
5
6
7
8
9
10
arduino


复制编辑
mycontainer/
├── config.json
├── rootfs/
│   └── bin/
│       ├── busybox
│       └── sh -> busybox

总结

  • 极大概率是 busybox 不是静态编译的,容器里缺少依赖库,导致加载失败。
  • file 命令确认后,下载官方静态版本替换即可。

如果你需要,我可以帮你找一个静态编译好的 BusyBox 版本,并写一份完整的步骤给你。你看怎么样?

您说:

➜ mycontainer file rootfs/bin/busybox rootfs/bin/busybox: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=defe73a9c3b5d1d19a710577df03bdf3ee77283b, for GNU/Linux 3.2.0, stripped

ChatGPT 说:

你的 BusyBox 是 动态链接的,依赖于动态链接器 /lib64/ld-linux-x86-64.so.2 和其他动态库。

但是你的 rootfs 里没有这些依赖文件(比如 /lib64/ld-linux-x86-64.so.2 和相关 .so 库),导致容器启动时找不到运行时环境,从而报错:

1
2
3
4
5
bash


复制编辑
exec /bin/busybox: no such file or directory

解决方案

1. 用静态编译的 BusyBox 替换动态版本

官方静态版本示例:

1
2
3
4
5
6
bash


复制编辑
wget https://busybox.net/downloads/binaries/1.36.0-x86_64-uclibc/busybox -O rootfs/bin/busybox
chmod +x rootfs/bin/busybox

静态版本体积更小,不依赖任何动态库,直接运行。


2. 或者你需要在 rootfs 里补齐所有动态库依赖

这比较复杂,需要从宿主机复制:

  • /lib64/ld-linux-x86-64.so.2
  • 所有 BusyBox 依赖的 .so

可以用命令查依赖:

1
2
3
4
5
bash


复制编辑
ldd rootfs/bin/busybox

然后把这些依赖拷贝到 rootfs/lib64rootfs/lib 对应目录里,保证容器中路径一致。

要用源码编译出一个静态链接的 BusyBox 可执行文件,可以按以下步骤操作。


编译 BusyBox 静态二进制的步骤

1. 获取源码

1
2
3
4
5
6
bash


复制编辑
git clone https://github.com/mirror/busybox.git
cd busybox

或者下载官方压缩包解压。


2. 配置编译选项(静态编译)

先载入默认配置:

1
2
3
4
5
bash


复制编辑
make defconfig

然后进入配置界面:

1
2
3
4
5
bash


复制编辑
make menuconfig

3. 在配置界面开启静态编译

  • 进入菜单: BusyBox SettingsBuild Options
  • 找到并勾选: Build BusyBox as a static binary (no shared libs)
  • 保存退出

4. 编译

1
2
3
4
5
bash


复制编辑
make -j$(nproc)

5. 编译完成后查看文件类型

1
2
3
4
5
bash


复制编辑
file busybox

你应该看到类似:

1
2
3
4
5
arduino


复制编辑
busybox: ELF 64-bit LSB executable, x86-64, statically linked, ...

6. 测试执行

1
2
3
4
5
6
bash


复制编辑
./busybox ls
./busybox sh

额外说明

  • 如果没有 make menuconfig,请先安装 libncurseslibncurses-dev,Debian/Ubuntu 上:
1
2
3
4
5
bash


复制编辑
sudo apt-get install libncurses-dev
  • 如果不想进入图形配置,也可以用命令直接开启静态编译:
1
2
3
4
5
6
7
bash


复制编辑
make defconfig
scripts/config --enable STATIC
make -j$(nproc)

这样编译出的 BusyBox 就是纯静态版本,不依赖系统的任何动态库,可以直接放到你的容器 rootfs 里跑,解决 no such file or directory 的动态库依赖问题。

This post is licensed under CC BY 4.0 by the author.