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。
详细解释
- 符号链接(symlink)只是给文件起了个别名,文件本身内容没变。
- 运行程序时,系统会把调用的程序名(
argv[0])传给程序。 - BusyBox 程序会检查
argv[0]:- 如果是
busybox,则需要后面带命令参数,比如ls、cat。 - 如果是
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 ls | shell 执行 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/lib64 或 rootfs/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 Settings→Build 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,请先安装libncurses或libncurses-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 的动态库依赖问题。