容器运行时runc内部实现深入解读

容器运行时runc内部实现深入解读runc 内部实现深入解读最近把 runc 的实现代码仔细的看了一遍 有点复杂 特别是 nsenter 那块反反复复看了不下三遍才搞懂是个什么机制 不过确实写得不错 防止日后遗忘 另外也给其他朋友一点参考

大家好,欢迎来到IT知识分享网。

runc 内部实现深入解读

最近把runc的实现代码仔细的看了一遍,有点复杂,特别是nsenter那块反反复复看了不下三遍才搞懂是个什么机制,不过确实写得不错,防止日后遗忘,另外也给其他朋友一点参考(网上有一些关于runc的介绍文章,但是没有nsenter这块的介绍)

概要设计

runc的实现分为两个大阶段:

  • 第一个阶段叫bootstrap, 设置一些环境以及配置信息, 创建匿名管道, 把容器的配置信息通过管道发送给第二阶段。
  • 第二个阶段代码里没有明确的名称,就把它叫做worker阶段吧, 主要就是创建子进程并根据管道传过来的配置信息,设置进程的namespace信息。

bootstrap和worker主要通过socketpair这种全双工管道交互,这么实现的主要考虑是worker的大部分时间处于自己的namespace中,所以有些事情自己做不了,必须让bootstrap协助完成。

另外bootstrap用GO语言实现,worker的开始的最重要的部分用C语言实现,后面的部分也是以GO语言实现,中间的切换非常美妙。

另外值得说一下的是runc支持linux,windows,solaris等多种操作系统,所以代码的一个通用规则是对于某个功能是定一个一个接口,然后针对各种平台的实现定义一个独立的文件,比如

container.go container_linux.go container_widnows.go container_solaris.go 

这样可以方便理解实现,也不要被代码量吓倒。

bootstrap代码分析

下面结合runc create <nn>来一起看一下runc具体执行流程:
create.go:

spec, err := setupSpec(context) 

进入到中执行,先解析config.json文件,下载bundle,然后

status, err := startContainer(context, spec, CT_ACT_CREATE, nil) 

开始了utils_linux.go,

func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) { container, err := createContainer(context, id, spec) ..... r := &runner{ enableSubreaper: !context.Bool("no-subreaper"), shouldDestroy: true, container: container, listenFDs: listenFDs, notifySocket: notifySocket, consoleSocket: context.String("console-socket"), detach: context.Bool("detach"), pidFile: context.String("pid-file"), preserveFDs: context.Int("preserve-fds"), action: action, criuOpts: criuOpts, } return r.run(spec.Process) } 

看一下createContainer的实现

func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) { .... factory, err := loadFactory(context) return factory.Create(id, config) } 

loadFactory最重会走到factory_linux.go

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, InitArgs: []string{"/proc/self/exe", "init"}, Validator: validate.New(), CriuPath: "criu", } Cgroupfs(l) for _, opt := range options { if err := opt(l); err != nil { return nil, err } } return l, nil } 

到目前为止都很简单,两个值得注意下的
InitArgs: []string{“/proc/self/exe”, “init”}/proc/self/exe是指向自身,也就是runc,这个很重要,后面会涉及到
options里面定义了具体采用哪种方式的cgroups,给予文件的还是systemd的方式

好了,有了factory,那就用这个factory创建container结构体,

func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, error) { containerRoot := filepath.Join(l.Root, id) if err := os.MkdirAll(containerRoot, 0711); err != nil { return nil, newGenericError(err, SystemError) } if err := os.Chown(containerRoot, unix.Geteuid(), unix.Getegid()); err != nil { return nil, newGenericError(err, SystemError) } if config.Rootless { RootlessCgroups(l) } c := &linuxContainer{ id: id, root: containerRoot, config: config, initArgs: l.InitArgs, criuPath: l.CriuPath, cgroupManager: l.NewCgroupsManager(config.Cgroups, nil), } c.state = &stoppedState{c: c} return c, nil } 

看到创建了一个linuxContainer结构体,并设置状态为Stopped, 最后开始run这个container,注意这个run不适真正的run,只是初始化container的许多信息而已。

func (r *runner) run(config *specs.Process) (int, error) { process, err := newProcess(*config) if len(r.listenFDs) > 0 { process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(r.listenFDs)), "LISTEN_PID=1") process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...) } baseFd := 3 + len(process.ExtraFiles) for i := baseFd; i < baseFd+r.preserveFDs; i++ { process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), "PreserveFD:"+strconv.Itoa(i))) } ... switch r.action { case CT_ACT_CREATE: err = r.container.Start(process) case CT_ACT_RESTORE: err = r.container.Restore(process, r.criuOpts) case CT_ACT_RUN: err = r.container.Run(process) default: panic("Unknown action") } 

最重进入container_linux.go : linuxContainer.Start

func (c *linuxContainer) start(process *Process, isInit bool) error { parent, err := c.newParentProcess(process, isInit) if err != nil { return newSystemErrorWithCause(err, "creating new parent process") } if err := parent.start(); err != nil { // terminate the process to ensure that it properly is reaped. if err := parent.terminate(); err != nil { logrus.Warn(err) } return newSystemErrorWithCause(err, "starting container process") } ... 
func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) { parentPipe, childPipe, err := utils.NewSockPair("init") if err != nil { return nil, newSystemErrorWithCause(err, "creating new init pipe") } cmd, err := c.commandTemplate(p, childPipe) if err != nil { return nil, newSystemErrorWithCause(err, "creating new command template") } if !doInit { return c.newSetnsProcess(p, cmd, parentPipe, childPipe) } // We only set up fifoFd if we're not doing a `runc exec`. The historic // reason for this is that previously we would pass a dirfd that allowed // for container rootfs escape (and not doing it in `runc exec` avoided // that problem), but we no longer do that. However, there's no need to do // this for `runc exec` so we just keep it this way to be safe. if err := c.includeExecFifo(cmd); err != nil { return nil, newSystemErrorWithCause(err, "including execfifo in cmd.Exec setup") } return c.newInitProcess(p, cmd, parentPipe, childPipe) } 

主要做了三件事:

  • 创建SockPair管道
  • 创建cmd, 并把管道的一端给cmd
  • 根据状态创建initProcess 或者 setnsProcess

侧重看一下cmd:

func (c *linuxContainer) commandTemplate(p *Process, chil

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/147796.html

(0)
上一篇 2025-04-04 20:15
下一篇 2025-04-04 20:20

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信