Linux:用 runc 构建 ARM 平台容器

文章目录

  • [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 源码使用了 goC 语言,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 日由 DockerCoreOS 和容器行业的其他领导者推出。

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.tarOCI 镜像就制作完成了。

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
相关推荐
AlfredZhao17 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩2 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
古城小栈2 天前
Unix 与 Linux 异同小叙
linux·服务器·unix
凡人叶枫2 天前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++