runc源码解析–1.源代码调试环境搭建
- runc源码解析–1.源代码调试环境搭建
- runc源码解析–2.create container详解
前言
云原生时代,容器有多重要不言而喻。而runc是一个 CLI 工具,用于根据 OCI 规范在 Linux 上生成和运行容器,是容器的底层技术之一。本系列从源代码层面深入剖析runc项目,带你解开容器神秘面纱的背后世界。
在剖析runc之前,我们需要重点了解下oci标准。
Open Container Initiative (OCI) 是Linux基金会旗下负责 操作系统层虚拟化(容器) 开放标准制定的项目。当前主要开发两项标准:
- 运行时标准 (Runtime Specification, runtime-spec )
- 镜像标准 (Image Specification, image-spec )
runtime标准概括了如何运行一个从磁盘解包的 文件系统包 (filesystem bundle) (类似macOS上分发的软件bundle) 。从上层的OCI实现将下载一个OCI镜像,然后将镜像解包成为一个OCI运行时的文件系统包(OCI Runtime filesystem bundler)。此时OCI运行时文件系统包将被一个OCI runtime运行起来。
OCI标准定义了容器运行时和镜像规范,使得不同容器实现(LXC, Docker, Kata, rkt) 以相同的标准运行,这样开发人员构建、打包和部署容器,可以运行在不同厂商的解决方案上。
由此可见,oci标准是整个容器技术乃至由此构建的云原生系统的灵魂所在。这个标准一定要引起我们足够的重视。
基础环境
- runc:release-1.1
- golang:v.1.18
- os:ubuntu20.04
- ide:vscode + golang插件
- image:busybox
一、基础环境搭建
-
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?请参考文档: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源代码啦。、
总结
- 参考文章
RunC 是什么?
容器安全拾遗 - Rootless Container初探
什么是根文件系统
浅谈linux中的根文件系统(rootfs的原理和介绍) - oci标准的定义(runtime-spec和image-spec)非常重要,是整个容器的灵魂所在。所以一定要熟读 runtime-spec 项目的文档