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
相关推荐
zru_960233 分钟前
在Windows和Linux系统上的Docker环境中使用的镜像是否相同
linux·运维·docker
运维实习生~1 小时前
k8s调度的过程,各组件之间的配合解析
云原生·容器·kubernetes
inquisiter2 小时前
SEV内存加密位linux内核设置过程
linux·运维·支持向量机
xhaoDream2 小时前
NFS客户端与服务端用户不一致问题
linux·服务器·经验分享
航月3 小时前
linux中VI命令的详细解释
linux·运维·服务器
浩浩测试一下3 小时前
内网渗透(CS&&MSF) 构建内网代理的全面指南:Cobalt Strike 与 Metasploit Framework 深度解析
linux·服务器·网络
小刘爱喇石( ˝ᗢ̈˝ )4 小时前
k8s主要控制器简述(二)DaemonSet|Job|CronJob
云原生·容器·kubernetes
Gold Steps.4 小时前
Docker与K8S是什么&该怎么选?
docker·云原生·容器·kubernetes
showker5 小时前
mac环境下chatwoot客服聊天docker本地部署+对接通义千问Qwen2.5
macos·docker·容器
慕城南风5 小时前
怎么查看linux是Ubuntu还是centos
linux·ubuntu·centos