云原生时代,容器有多重要不言而喻。而runc是一个 CLI 工具,用于根据 OCI 规范在 Linux 上生成和运行容器,是容器的底层技术之一。本系列从源代码层面深入剖析runc项目,带你解开容器神秘面纱的背后世界。
在剖析runc之前,我们需要重点了解下oci标准。
Open Container Initiative (OCI) 是Linux基金会旗下负责 操作系统层虚拟化(容器) 开放标准制定的项目。当前主要开发两项标准:
runtime标准概括了如何运行一个从磁盘解包的 文件系统包 (filesystem bundle) (类似macOS上分发的软件bundle) 。从上层的OCI实现将下载一个OCI镜像,然后将镜像解包成为一个OCI运行时的文件系统包(OCI Runtime filesystem bundler)。此时OCI运行时文件系统包将被一个OCI runtime运行起来。
OCI标准定义了容器运行时和镜像规范,使得不同容器实现(LXC, Docker, Kata, rkt) 以相同的标准运行,这样开发人员构建、打包和部署容器,可以运行在不同厂商的解决方案上。
由此可见,oci标准是整个容器技术乃至由此构建的云原生系统的灵魂所在。这个标准一定要引起我们足够的重视。
基础环境
golang + vscode
环境搭建不做赘述
runc 调试
vscode调试 launch.json:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "runc",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"args": [
"run",
"--bundle",
"/root/code/go/kubernetes/runc/bundle",
"testcontainer",
]
}
]
}
注意,针对不同的命令需要不同的args,比较麻烦,所以我直接采用命令 go run + log
的方式来阅读和调试。
go run:go run your_runc_path/runc --debug create ...
bundle是什么?为什么我们需要bundle?请参考文档:bundle
rootfs准备
RunC 是运行容器的运行时,它负责利用符合标准的文件等资源运行容器,但是它不包含 docker 那样的镜像管理功能。所以要用 runC 运行容器,我们先得准备好容器的文件系统和容器的配置文件config.json(由oci标准定义)。
$ cd runc
$ docker pull busybox
$ docker export $(docker create busybox) > bundle/busybox.tar
$ tar -C bundle/rootfs -xf bundle/busybox.tar
上面的命令会制作一个rootfs,关于rootfs,可以参考 什么是根文件系统 文章中的英文部分的解释。
config.json 准备
$ cd runc
$ go run . --debug spec --bundle bundle # 生成config.json,--rootless会生成一个普通用户模式的容器配置
config.json是容器的配置文件,是容器两个最重要的基础文件之一,另一个是state.json,系列后面的文章会详细介绍这个state.json文件。下面介绍下 config.json:
{
"ociVersion": "1.0.2-dev", // oci标准版本
"process": { // 容器进程定义,具体细节请参考官方 runtime-spec 项目中的文档
"terminal": true, // 调试的时候改成false,这个参数很重要,系列文章会做详细的解释
"user": { // 容器用户
"uid": 0,
"gid": 0
},
"args": [ // 为调试方便,这里 sh 参数更改成 "sleep","30"
"sh"
],
"env": [ // 容器环境变量
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "/", // 容器初始化目录
"capabilities": {
"bounding": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"effective": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"inheritable": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"permitted": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"ambient": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
]
},
"rlimits": [
{
"type": "RLIMIT_NOFILE",
"hard": 1024,
"soft": 1024
}
],
"noNewPrivileges": true
},
"root": {
"path": "rootfs",
"readonly": true // 为了调试方便,这里可以改成false
},
"hostname": "runc",
"mounts": [
{
"destination": "/proc",
"type": "proc",
"source": "proc"
},
{
"destination": "/dev",
"type": "tmpfs",
"source": "tmpfs",
"options": [
"nosuid",
"strictatime",
"mode=755",
"size=65536k"
]
},
{
"destination": "/dev/pts",
"type": "devpts",
"source": "devpts",
"options": [
"nosuid",
"noexec",
"newinstance",
"ptmxmode=0666",
"mode=0620",
"gid=5"
]
},
{
"destination": "/dev/shm",
"type": "tmpfs",
"source": "shm",
"options": [
"nosuid",
"noexec",
"nodev",
"mode=1777",
"size=65536k"
]
},
{
"destination": "/dev/mqueue",
"type": "mqueue",
"source": "mqueue",
"options": [
"nosuid",
"noexec",
"nodev"
]
},
{
"destination": "/sys",
"type": "sysfs",
"source": "sysfs",
"options": [
"nosuid",
"noexec",
"nodev",
"ro"
]
},
{
"destination": "/sys/fs/cgroup",
"type": "cgroup",
"source": "cgroup",
"options": [
"nosuid",
"noexec",
"nodev",
"relatime",
"ro"
]
}
],
"linux": {
"resources": { // 生成config.json命令无--rootless参数
"devices": [
{
"allow": false,
"access": "rwm"
}
]
},
"uidMappings": [ // rootless容器参数
{
"containerID": 0,
"hostID": 0,
"size": 1
}
],
"gidMappings": [ // rootless容器参数
{
"containerID": 0,
"hostID": 0,
"size": 1
}
],
"namespaces": [
{
"type": "pid"
},
{
"type": "network"
},
{
"type": "ipc"
},
{
"type": "uts"
},
{
"type": "mount"
}
],
"maskedPaths": [
"/proc/acpi",
"/proc/asound",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/sys/firmware",
"/proc/scsi"
],
"readonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
}
}
现在我们得到了rootfs和config.json组成的bundle,这个bundle将会成为后面我们剖析源代码的关键所在。接下来我们就可以开始剖析runc源代码啦。、