当前位置: 首页 > 工具软件 > runC > 使用案例 >

runc源码解析--1.源代码调试环境搭建

东门秦迟
2023-12-01

runc源码解析–1.源代码调试环境搭建

  1. runc源码解析–1.源代码调试环境搭建
  2. 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

一、基础环境搭建

  1. golang + vscode
    环境搭建不做赘述

  2. 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

  1. 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,可以参考 什么是根文件系统 文章中的英文部分的解释。

  2. 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源代码啦。、


总结

  1. 参考文章
    RunC 是什么?
    容器安全拾遗 - Rootless Container初探
    什么是根文件系统
    浅谈linux中的根文件系统(rootfs的原理和介绍)
  2. oci标准的定义(runtime-spec和image-spec)非常重要,是整个容器的灵魂所在。所以一定要熟读 runtime-spec 项目的文档
 类似资料: