【runc 源码分析】runc init 流程分析

本文深入剖析了runc工具在容器初始化过程中的作用,详细解释了如何根据OCI标准创建并运行容器,包括初始化命令的执行流程,Factory接口的实现,以及容器内部网络配置和进程执行的细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    根据官方定义:runC是一个根据OCI(Open Container Initiative)标准创建并运行容器的CLI tool 

  • 容器的工业级标准化组织OCI(Open Container Initiative)出炉,这是业界大佬为避免容器生态和docker耦合过紧做的努力,也是docker做出的妥协
  • 随着docker等容器引擎自身功能越来越丰富,其逐渐呈现出组件化的趋势(将底层交给OCI,自己则专注网络,配置管理,集群,编排,安全等方面)
  • 内核中关于容器的开发也如火如荼,包括 capabilities, fs, net, uevent等和容器相关的子系统

 

NAME:
   docker-runc init - initialize the namespaces and launch the process (do not call it outside of runc)

USAGE:
   docker-runc init [arguments...]

 

Factory 接口

type Factory interface {
	// Creates a new container with the given id and starts the initial process inside it.
	Create(id string, config *configs.Config) (Container, error)

	// Load takes an ID for an existing container and returns the container information
	// from the state.  This presents a read only view of the container.
	Load(id string) (Container, error)

	// StartInitialization is an internal API to libcontainer used during the reexec of the
	// container.
	//
	// Errors:
	// Pipe connection error
	// System error
	StartInitialization() error

	// Type returns info string about factory type (e.g. lxc, libcontainer...)
	Type() string
}

 

namespaces定义

	NEWNET    NamespaceType = "NEWNET"
	NEWPID    NamespaceType = "NEWPID"
	NEWNS     NamespaceType = "NEWNS"
	NEWUTS    NamespaceType = "NEWUTS"
	NEWIPC    NamespaceType = "NEWIPC"
	NEWUSER   NamespaceType = "NEWUSER"
	NEWCGROUP NamespaceType = "NEWCGROUP"

 

     接着通过init管道将容器配置p.bootstrapData写入管道中。然后再调用parseSync()函数,通过init管道与容器初始化进程进行同步,待其初始化完成之后,执行PreStart Hook等回调。关闭init管道容器创建完成

 

一. runc init 分析

 

 

                                                                  网上的图片

 

  1.1 初始化iniit command

func init() {
	if len(os.Args) > 1 && os.Args[1] == "init" {
		runtime.GOMAXPROCS(1)
		runtime.LockOSThread()
	}
}

var initCommand = cli.Command{
	Name:  "init",
	Usage: `initialize the namespaces and launch the process (do not call it outside of runc)`,
	Action: func(context *cli.Context) error {
		factory, _ := libcontainer.New("")
		if err := factory.StartInitialization(); err != nil {
			// as the error is sent back to the parent there is no need to log
			// or write it to stderr because the parent process will handle this
			os.Exit(1)
		}
		panic("libcontainer: container init failed to exec")
	},
}

    1.1.1 New函数

      libcontainer.New函数初始化linuxFactory,实现了Factory接口

// New returns a linux based container factory based in the root directory and
// configures the factory with the provided option funcs.
func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
	if root != "" {
		if err := os.MkdirAll(root, 0700); err != nil {
			return nil, newGenericError(err, SystemError)
		}
	}
	l := &LinuxFactory{
		Root:      root,
		InitPath:  "/proc/self/exe",
		InitArgs:  []string{os.Args[0], "init"},
		Validator: validate.New(),
		CriuPath:  "criu",
	}
	Cgroupfs(l)
	for _, opt := range options {
		if opt == nil {
			continue
		}
		if err := opt(l); err != nil {
			return nil, err
		}
	}
	return l, nil
}

 

2. StartInitialization函数

     路径 libcontainer/factory_linux.go

// StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state
// This is a low level implementation detail of the reexec and should not be consumed externally
func (l *LinuxFactory) StartInitialization() (err error)

   2.1 获得pipe管道

	var (
		pipefd, fifofd int
		consoleSocket  *os.File
		envInitPipe    = os.Getenv("_LIBCONTAINER_INITPIPE")
		envFifoFd      = os.Getenv("_LIBCONTAINER_FIFOFD")
		envConsole     = os.Getenv("_LIBCONTAINER_CONSOLE")
	)

	// Get the INITPIPE.
	pipefd, err = strconv.Atoi(envInitPipe)
	if err != nil {
		return fmt.Errorf("unable to convert _LIBCONTAINER_INITPIPE=%s to int: %s", envInitPipe, err)
	}

	var (
		pipe = os.NewFile(uintptr(pipefd), "pipe")
		it   = initType(os.Getenv("_LIBCONTAINER_INITTYPE"))
	)
	defer pipe.Close()

   2.2 newContainerInit函数

      如果类型为 initStandard 则结构体 linuxStandardInit 实现了 Init 方法,容器内初始化做了一大堆事

func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd int) (initer, error) {
	var config *initConfig
	if err := json.NewDecoder(pipe).Decode(&config); err != nil {
		return nil, err
	}
	if err := populateProcessEnvironment(config.Env); err != nil {
		return nil, err
	}
	switch t {
	case initSetns:
		return &linuxSetnsInit{
			pipe:          pipe,
			consoleSocket: consoleSocket,
			config:        config,
		}, nil
	case initStandard:
		return &linuxStandardInit{
			pipe:          pipe,
			consoleSocket: consoleSocket,
			parentPid:     unix.Getppid(),
			config:        config,
			fifoFd:        fifoFd,
		}, nil
	}
	return nil, fmt.Errorf("unknown init type %q", t)
}

 

3. linuxStandardInit 

       路径 libcontainer/standard_init_linux.go

// linuxSetnsInit performs the container's initialization for running a new process
// inside an existing container.
type linuxSetnsInit struct {
	pipe          *os.File
	consoleSocket *os.File
	config        *initConfig
}

  3.1 Init函数

  • setupNetwork: 配置容器的网络,调用第三方 netlink.LinkSetup
  • setupRoute: 配置容器静态路由信息,调用第三方 netlink.RouteAdd
  • label.Init:   检查selinux是否被启动并将结果存入全局变量。
  • finalizeNamespace: 根据config配置将需要的特权capabilities加入白名单,设置user namespace,关闭不需要的文件描述符。
  • unix.Openat: 只写方式打开fifo管道并写入0,会一直保持阻塞,直到管道的另一端以读方式打开,并读取内容
  • syscall.Exec 系统调用来执行用户所指定的在容器中运行的程序

      配置 hostname、apparmor、processLabel、sysctl、readonlyPath、maskPath。create 虽然不会执行命令,但会检查命令路径,错误会在 create 期间返回

    3.1.1 setupNetWork函数

       配置容器的网络,调用第三方 netlink.LinkSetup,相当于命令ip link set $link up

       如果不指定任何网络,只有loopback

// setupNetwork sets up and initializes any network interface inside the container.
func setupNetwork(config *initConfig) error {
	for _, config := range config.Networks {
		strategy, err := getStrategy(config.Type)
		if err != nil {
			return err
		}
		if err := strategy.initialize(config); err != nil {
			return err
		}
	}
	return nil
}

    3.1.2 setupRoute

      配置容器静态路由信息,调用第三方 netlink.RouteAdd,相当于命令ip route add $route

func setupRoute(config *configs.Config) error {
	for _, config := range config.Routes {
		_, dst, err := net.ParseCIDR(config.Destination)
		if err != nil {
			return err
		}
		src := net.ParseIP(config.Source)
		if src == nil {
			return fmt.Errorf("Invalid source for route: %s", config.Source)
		}
		gw := net.ParseIP(config.Gateway)
		if gw == nil {
			return fmt.Errorf("Invalid gateway for route: %s", config.Gateway)
		}
		l, err := netlink.LinkByName(config.InterfaceName)
		if err != nil {
			return err
		}
		route := &netlink.Route{
			Scope:     netlink.SCOPE_UNIVERSE,
			Dst:       dst,
			Src:       src,
			Gw:        gw,
			LinkIndex: l.Attrs().Index,
		}
		if err := netlink.RouteAdd(route); err != nil {
			return err
		}
	}
	return nil
}

    3.1.3 syncParentReady函数

       syncParentReady函数发送ready到pipe,等待父进程下发exec命令

// syncParentReady sends to the given pipe a JSON payload which indicates that
// the init is ready to Exec the child process. It then waits for the parent to
// indicate that it is cleared to Exec.
func syncParentReady(pipe io.ReadWriter) error {
	// Tell parent.
	if err := writeSync(pipe, procReady); err != nil {
		return err
	}

	// Wait for parent to give the all-clear.
	return readSync(pipe, procRun)
}

     3.1.4 只写方式打开fifo管道并写入0,会一直保持阻塞,直到管道的另一端以读方式打开,并读取内容

	// Wait for the FIFO to be opened on the other side before exec-ing the
	// user process. We open it through /proc/self/fd/$fd, because the fd that
	// was given to us was an O_PATH fd to the fifo itself. Linux allows us to
	// re-open an O_PATH fd through /proc.
	fd, err := unix.Open(fmt.Sprintf("/proc/self/fd/%d", l.fifoFd), unix.O_WRONLY|unix.O_CLOEXEC, 0)
	if err != nil {
		return newSystemErrorWithCause(err, "open exec fifo")
	}
	if _, err := unix.Write(fd, []byte("0")); err != nil {
		return newSystemErrorWithCause(err, "write 0 exec fifo")
	}
	// Close the O_PATH fifofd fd before exec because the kernel resets
	// dumpable in the wrong order. This has been fixed in newer kernels, but
	// we keep this to ensure CVE-2016-9962 doesn't re-emerge on older kernels.
	// N.B. the core issue itself (passing dirfds to the host filesystem) has
	// since been resolved.
	// https://github.com/torvalds/linux/blob/v4.9/fs/exec.c#L1290-L1318
	unix.Close(l.fifoFd)

    3..1.5 系统调用来执行用户所指定的在容器中运行的程序

	if err := syscall.Exec(name, l.config.Args[0:], os.Environ()); err != nil {
		return newSystemErrorWithCause(err, "exec user process")
	}

 

<think>好的,我现在需要帮助用户分析Open vSwitch(OVS)的源码实现。用户已经提供了一些相关的引用资料,我需要结合这些资料来组织一个详细的回答。首先,我应该先理解用户的需求,他们可能是一个刚开始接触OVS源码的开发者或研究人员,想要了解源码的结构和关键模块。 根据引用资料,OVS的源码结构包括ovs-vswitchd守护进程、ovsdb-server数据库、内核模块等。用户提供的引用[1]提到了源码文件布局,我需要先介绍这一点,让用户有一个整体的认识。然后,根据引用[2]、[3]、[4],网桥(Bridge)的实现、vswitchd的功能以及主循环的结构都是关键部分。我需要将这些部分逐一展开,说明它们的功能和相互关系。 接下来,我需要确保回答符合用户给出的格式要求,比如使用中文,正确应用行内和独立公式的LaTeX格式(虽然目前可能用不上数学公式,但如果有需要的话要记得)。另外,用户要求生成相关问题,这些问题需要与源码分析相关,能够引导用户进一步探索。 在组织内容时,可能需要分模块介绍,比如源码结构、核心组件、数据路径处理、主循环机制等。每个部分需要结合引用中的具体信息,比如bridge_run()和bridge_wait()的作用,以及ovs-vswitchd的主循环结构。同时,要确保引用标识正确添加到对应的段落末尾,例如引用[^1]、[^2]等。 还要注意用户提到的“系统级指令”中的要求,比如行内数学表达式用$...$,独立公式用$$...$$,不过当前问题可能涉及较少的数学内容,但如果有涉及的地方需要正确应用。另外,回答需要结构清晰,分步骤说明,保证真实可靠,所以需要准确引用提供的资料,并正确标注来源。 最后,生成的相关问题应该针对OVS源码的深入分析,比如数据路径实现、与内核模块的交互、性能优化等,这样可以帮助用户进一步研究。需要确保问题具有针对性,能够引导用户探索源码的不同方面。 总结来说,回答的结构应该包括源码结构概述、核心模块分析(如网桥、vswitchd)、主循环机制、数据路径处理,以及源码分析方法,每个部分结合引用资料进行说明,并在最后生成相关问题。需要注意格式正确,引用标识正确放置,语言简洁明了,帮助用户逐步理解OVS的源码实现。</think>### Open vSwitch源码实现分析 #### 1. 源码结构概述 Open vSwitch(OVS)的源码组织主要分为以下模块: - **ovs-vswitchd**:用户空间守护进程,负责处理交换逻辑和流表管理[^4] - **ovsdb-server**:轻量级数据库,存储交换机配置和状态信息 - **内核模块**:实现快速数据路径(如`datapath/linux`目录) - **协议支持**:包括OpenFlow、NetFlow等协议实现 引用[1]指出,2.17.1版本源码目录结构清晰,核心模块集中在`lib`、`ofproto`、`vswitchd`等目录中[^1]。 #### 2. 核心组件分析 **(1) 网桥(Bridge)实现** - 通过`struct bridge`表示虚拟网桥,包含端口、流表、QoS策略等配置[^2] - 功能实现比Linux Bridge更强大,支持: $$ \text{OpenFlow协议} + \text{多级流表} + \text{SDN扩展} $$ - 关键数据结构关联: ```c struct bridge → struct ofproto → struct flow_table ``` **(2) vswitchd主循环** 主程序通过事件驱动模型运行: ```c while (!exiting) { bridge_run(); // 处理网桥状态更新 unixctl_server_run();// 执行控制命令 netdev_run(); // 处理网络设备事件 poll_block(); // 等待事件触发[^4] } ``` 其中`bridge_wait()`实现同步机制,协调OVSDB事务、接口通知等事件[^3]。 #### 3. 数据路径处理 - **快速路径**:内核模块处理常规数据包转发 - **慢速路径**:用户空间处理复杂匹配和OpenFlow规则 - 流量匹配公式: 设数据包特征为$P=\{src\_mac, dst\_mac, ...\}$,流表规则为$F=\{match\_fields, actions\}$,则: $$ \exists F_i \in FlowTable,\ s.t.\ P \subseteq match\_fields(F_i) $$ #### 4. 源码分析方法论 建议采用以下步骤: 1. 从`ovs-vswitchd/main.c`跟踪主循环 2. 分析`bridge_init()`初始化流程 3. 研究`ofproto`模块的OpenFlow实现 4. 对照OVSDB模式文件`vswitch.ovsschema` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值