文章目录
- [1. 前言](#1. 前言)
- [2. 构建 runc](#2. 构建 runc)
-
- [2.1 准备 C 交叉编译器](#2.1 准备 C 交叉编译器)
- [2.2 编译 libseccomp 库](#2.2 编译 libseccomp 库)
- [2.3 编译 runc](#2.3 编译 runc)
-
- [2.3.1 安装 go 编译器](#2.3.1 安装 go 编译器)
- [2.3.2 编译 runc](#2.3.2 编译 runc)
- [3. 构建 runc 镜像包 + 测试运行](#3. 构建 runc 镜像包 + 测试运行)
-
- [3.1 OCI 规范](#3.1 OCI 规范)
- [3.2 手工构建 OCI 镜像](#3.2 手工构建 OCI 镜像)
- [3.3 运行](#3.3 运行)
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 构建 runc
runc
是容器的运行时容器运行时的基础组件,常见容器直接使用 Docker
构建,但在资源受限的一些嵌入式设备,Docker
整体就会显得庞大且冗余,这时候我们通常会使用 runc + OCI 镜像
的简单组合,来构建容器。
2.1 准备 C 交叉编译器
本文描述在 Ubuntu 22.04.5 LTS
桌面系统下,为 aarch64
平台构建容器的过程,不管是构建 runc
用到的 libseccomp
库,还是编译 runc
源代码中的 C
代码,都需要用到 aarch64
的交叉编译器,所以首先安装交叉编译器:
bash
$ sudo apt-get install gcc-aarch64-linux-gnu
当然,读者也可以使用已有的交叉编译器。
2.2 编译 libseccomp 库
runc
使用了 libseccomp
库,我们先构建 libseccomp
。下载 libseccomp
源码并解压,切换到 libseccomp
源码目录:
bash
$ sudo apt-get install autoconf libtool
$ ./autogen.sh
$ ./configure --host=aarch64-linux-gnu --prefix=`pwd`/_install
$ make && make install
成功编译完成后,在 libseccomp
源码目录下的 _install
目录下,生成了如下文件:
bash
$ cd _install
$ tree
.
├── bin
│ └── scmp_sys_resolver
├── include
│ ├── seccomp.h
│ └── seccomp-syscalls.h
├── lib
│ ├── libseccomp.a
│ ├── libseccomp.la
│ ├── libseccomp.so -> libseccomp.so.0.0.0
│ ├── libseccomp.so.0 -> libseccomp.so.0.0.0
│ ├── libseccomp.so.0.0.0
│ └── pkgconfig
│ └── libseccomp.pc
└── share
└── man
├── man1
│ └── scmp_sys_resolver.1
└── man3
......
8 directories, 44 files
然后将 seccomp.h,seccomp-syscalls.h,libseccomp.so.0.0.0
安装到交叉编译器 gcc-aarch64-linux-gnu
的对应目录:
bash
$ sudo cp include/*.h /usr/aarch64-linux-gnu/include
$ sudo cp lib/libseccomp.a /usr/aarch64-linux-gnu/lib
$ sudo cp lib/libseccomp.la /usr/aarch64-linux-gnu/lib
$ sudo cp lib/libseccomp.so.0.0.0 /usr/aarch64-linux-gnu/lib
$ sudo ln -s /usr/aarch64-linux-gnu/lib/lib/libseccomp.so.0.0.0 /usr/aarch64-linux-gnu/lib/lib/libseccomp.so.0
$ sudo ln -s /usr/aarch64-linux-gnu/lib/lib/libseccomp.so.0.0.0 /usr/aarch64-linux-gnu/lib/lib/libseccomp.so
2.3 编译 runc
2.3.1 安装 go 编译器
runc
源码使用了 go
和 C
语言,C
语言需要的交叉编译器前面已经准备好了,现在参考 Download and install 安装 go 编译器
:
bash
$ wget https://golang.google.cn/dl/go1.22.4.linux-amd64.tar.gz
$ rm -rf /usr/local/go && tar -C /usr/local -xzf go1.22.4.linux-amd64.tar.gz
$ export PATH=$PATH:/usr/local/go/bin
$ go version
go version go1.22.4 linux/amd64
2.3.2 编译 runc
下载 runc
源码解压到 runc 目录
,然后编译:
bash
$ cd runc
$ GOARCH=arm64 CGO_ENABLED=1 make
$ PREFIX=`pwd`/_install make install
$ file _install/sbin/runc
_install/sbin/runc: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=db10b08ff0a8c578e7c80a9009ca5234b258c9e2, for GNU/Linux 3.7.0, with debug_info, not stripped
将 libseccomp.so.0.0.0
拷贝到 aarch64 目标机器
的目录 /usr/lib
,并创建符号链接文件 libseccomp.so.0
:
bash
# ln -s /usr/lib/libseccomp.so.0.0.0 /usr/lib/libseccomp.so.0
拷贝 runc
文件到 aarch64 目标机器
的目录 /usr/sbin
,然后添加可执行权限:
bash
# chmod +x /usr/sbin/runc
测试运行:
bash
# ./runc --version
runc version 1.2.0-rc.2+dev
commit: v1.2.0-rc.2-29-gad5b481d
spec: 1.2.0
go: go1.22.4
3. 构建 runc 镜像包 + 测试运行
runc
将配置文件 config.json
运行于一个通过 Linux 命名空间
和 cgroup
隔离的独立环境。Linux 命名空间
用来控制可见的视野,cgroup
用于资源(CPU、内存、磁盘、网络等)的隔离。runc
运行需要一个镜像包
,这个镜像包
需要符合 (OCI: Open Container Initiative) 的相关规范。
3.1 OCI 规范
开放容器计划 (OCI: Open Container Initiative) 是一个轻量级的开放式治理结构(项目),在 Linux 基金会的支持下成立,其明确目的是围绕容器格式和运行时创建开放的行业标准。OCI
于 2015 年 6 月 22 日由 Docker
、CoreOS
和容器行业的其他领导者推出。
OCI
目前包含 3
个规范:运行时规范 (runtime-spec)
、镜像规范 (image-spec)
和分发规范 (distribution-spec)
。运行时规范
概述了如何运行在磁盘上解压的 OCI 镜像包
。
为了支持 OCI 镜像
格式包含足够的信息以在目标平台上启动应用程序(例如命令、参数、环境变量等)。此规范定义了如何创建 OCI 镜像
(通常由构建系统完成),并输出镜像清单、文件系统(层)序列化、镜像配置。概括地说,镜像清单
包含有关镜像内容和依赖关系的元数据,包括一个或多个文件系统序列化
存档的内容可寻址标识,这些存档将被解压缩以构成最终的可运行文件系统。镜像配置
包括应用程序参数、环境等信息。镜像清单
、镜像配置
和一个或多个文件系统序列化的组合称为 OCI 镜像
。
3.2 手工构建 OCI 镜像
托管在 Docker Hub
上的 Docker
官方镜像,是现成可用的 OCI 镜像
,但很可惜,国外的这些镜像无法正常访问,国内的一些镜像也不是长期可用。同时由于这些镜像对于嵌入式设备来说,往往显得庞大冗余,这时候我们就可以选择手工构建简易的、满足自身需求的、符合 OCI
相关规范的镜像文件。
构建一个运行于容器环境的测试程序 oci_test.c
:
c
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int count = 0;
while (1) {
printf("oci test: count = %d\n", ++count);
sleep(3);
}
return 0;
}
bash
$ aarch64-linux-gnu-gcc -o oci_test oci_test.c
$ mkdir -p mycontainer/rootfs
$ mkdir -p mycontainer/rootfs/{bin,dev,etc,lib,proc,sbin,sys,tmp,usr/bin,usr/lib,usr/sbin,var}
$ cp oci_test mycontainer/rootfs/usr/bin
$ cp /usr/aarch64-linux-gnu/lib/ld-linux-aarch64.so.1 mycontainer/rootfs/lib/
$ cp /usr/aarch64-linux-gnu/lib/libc.so.6 mycontainer/rootfs/lib/
$ cp /usr/aarch64-linux-gnu/lib/libm.so.6 mycontainer/rootfs/lib/
$ cp /usr/aarch64-linux-gnu/lib/libresolv.so.2 mycontainer/rootfs/lib/
$ tar -cf mycontainer.tar mycontainer
将镜像打包成 mycontainer.tar
,OCI 镜像
就制作完成了。
3.3 运行
如章节 2.3
中准备好 runc
,假定 runc
放置在 aarch64 目标机器
的 /test
目录下,将章节 3.2
中制作好的 mycontainer.tar
也拷贝到 /test
目录下:
bash
# cd /test
# ./runc spec # 生成配置文件 config.json
编辑 /test/config.json
,将配置项 terminal
修改为 false
,容器入口程序修改为 oci_test
:

接着创建并启动容器实例:
bash
# mv config.json ./mycontainer
# ./runc run -d -b ./mycontainer ocitest > ocitest.log 2>&1
# ./runc list
ID PID STATUS BUNDLE CREATED OWNER
ocitest 16427 running /test/mycontainer 1970-01-01T08:10:45.791058625Z root
# ./runc ps ocitest
# ps -ef | grep -v grep | grep "oci_test"
16427 root oci_test
# ls /sys/fs/cgroup/cpu/ocitest/ -l
total 0
-rw-r--r-- 1 root root 0 Jan 1 08:12 cgroup.clone_children
-rw-r--r-- 1 root root 0 Jan 1 08:10 cgroup.procs
-rw-r--r-- 1 root root 0 Jan 1 08:12 cpu.shares
-rw-r--r-- 1 root root 0 Jan 1 08:12 notify_on_release
-rw-r--r-- 1 root root 0 Jan 1 08:12 tasks
# cat ocitest.log
oci test: count = 1
oci test: count = 2
oci test: count = 3
oci test: count = 4
oci test: count = 5
oci test: count = 6
oci test: count = 7
oci test: count = 8
oci test: count = 9
oci test: count = 10
oci test: count = 11
oci test: count = 12
oci test: count = 13
oci test: count = 14
oci test: count = 15
oci test: count = 16
oci test: count = 17
oci test: count = 18
oci test: count = 19
oci test: count = 20
oci test: count = 21
oci test: count = 22
oci test: count = 23
oci test: count = 24
oci test: count = 25
oci test: count = 26
oci test: count = 27
oci test: count = 28
停止容器实例:
bash
# ./runc kill ocitest
# kill -9 16427
# ./runc list
ID PID STATUS BUNDLE CREATED OWNER
ocitest 0 stopped /test/mycontainer 1970-01-01T08:10:45.791058625Z root
在删除容器实例之前,其 cgroup
信息仍然存在:
bash
# ls /sys/fs/cgroup/cpu/ocitest/
cgroup.clone_children cpu.shares tasks
cgroup.procs notify_on_release
最后,删除容器实例:
bash
# ./runc delete ocitest
# ls /sys/fs/cgroup/cpu/ocitest/
ls: /sys/fs/cgroup/cpu/ocitest/: No such file or directory