runc create container 流程分析

一、// runc/create.golinux

Action: func(context *cli.Context) errorjson

首先調用spec, err := setupSpec(context)加載配置文件config.json的內容。以後調用status, err := startcontainer(context, spec, true)進行容器的建立工做,其中最後一個布爾型的參數爲true,表示進行容器的建立。bootstrap

 

二、// runc/utils_linux.goapp

func startContainer(context *cli.Context, spec *specs.Spec, create bool) (int ,error)ide

首先調用container, err := createContainer(context, id, spec)建立容器, 以後填充runner結構r,以下所示:函數

r := &runner{oop

  enableSubreaper:     !context.Bool("no-subreaper"),ui

  shouldDestroy:       true,this

  container:        container,spa

  listenFDs:        listenFDs,    // if os.Getenv("LISTEN_FDS") != "" { listenFDs = activation.Files(false) }

  console:         context.String("console") 

  detach:         detach,

  pidFile:         context.String("pid-file")

  create:         create,

}

最後調用r.run(&spec.Process)。

--------------------------------------------------------------------開始建立容器對象---------------------------------------------------------------------

 

三、// runc/utils_linux.go

func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error)

首先調用config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{...})將配置轉換爲符合libcontainer要求的配置格式。以後調用factory, err := loadFactory(context)加載factory對象。最後,調用factory.Create(id, config)建立一個libcontainer的container對象。

 

四、// runc/utils_linux.go

// loadFactory returns the configured factory instance for execing containers

func loadFactory(context *cli.Context) (libcontainer.Factory, error)

首先獲取root dir的絕對路徑abs(默認爲/run/runc,用於存儲容器的狀態)以及cgroupManager,最後調用libcontainer.New(abs, cgroupManager, libcontainer.CriuPath(context.GlobalString("criu")))來生產一個Factory實例。

 

五、// runc/libcontainer/factory_linux.go

// 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)

首先填充一個LinuxFactory對象,以下所示:

l := &LinuxFactory{

  Root:    root,

  InitArgs:   []string{"/proc/self/exec", "init"},

  Validator:    validate.New(),

  CriuPath:    "criu",

}

接着調用Cgroupfs(l)將l的NewCgroupsManager設置爲利用本地的cgroups filesystem implementation來建立和管理cgroups(相對應的,也能夠利用systemd來)。再遍歷options,來對l進行選擇性配置。從這個函數的調用處可知,主要配置了cgroups和criupath。最後,返回l。

 

六、// runc/libcontainer/factory_linux.go

func (l *LinuxFactory) Create(id string, config *configs.Config) (Container)

該函數首先對id和config進行驗證,接着獲取uid, gid, containerRoot而且建立containerRoot(默認爲/run/runc/container-id)。以後再建立一個FIFO文件,填充一個linuxContainer對象,以下所示:

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

--------------------------------------------------------------------建立容器對象結束---------------------------------------------------------------------

 

七、// runc/utils_linux.go

func (r *runner) run(config *specs.Process) (int ,error)

(1)、調用process, err := newProcess(*config),根據config配置一個libcontainer.Process對象。若是r.listenFDs的數目不爲0的話,擴展process.Env和process.ExtraFiles,而後獲取rootuid和rootgid。

(2)、接着調用tty, err := setupIO(process, rootuid, rootgid, r.console, config.Terminal, r.detach || r.create)設置和process的IO。

(3)、調用handler := newSignalHandler(tty, r.enableSubreaper)以及以後的status, err := handler.forward(process)對來自process的SIGNAL進行處理(若是沒有detach而且不是create容器的話)。

(4)、調用startFn(process),若是是create容器的話,那麼startFn := r.container.Start,不然startFn := r.container.Run。由於咱們是對create 容器進行分析 ,所以startFn爲r.container.Start。

(5)、調用tty.ClosePostStart()關閉已經傳遞給容器的fds。

 

八、// runc/libcontainer/container_linux.go

func (c *linuxContainer) Start(process *Process)

這個函數很簡單,先調用status, err := c.currentStatus()獲取容器的當前狀態,最後調用c.start(process, status == Stopped),在create時,此時容器的狀態確實爲Stopped

 

九、// runc/libcontainer/container_linux.go

func (c *linuxContainer) start(process *Process, isInit bool) error

首先調用parent, err := c.newParentProcess(process, isInit)獲取一個parentProcess的接口類型。而後再調用parent.start()啓動進程。最後,若是isInit爲true,即create容器時,c.state = &createdState{c: c}, 假如c.config.Hooks != nil的話,執行一些c.config.Hooks.Poststart。(在基本的runc spec生成的config.json中hooks爲空)

 

十、// runc/libcontainer/container_linux.go

func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error)

調用parentPipe, childPipe, err := newPipe()生成一個管道,接着打開rootDir, err  = os.Open(c.root),即打開/run/runc/container-id。而後調用cmd, err := c.commandTemplate(p, childPipe, rootDir)配置啓動命令。commandTemplate中最重要的內容爲

(1)、添加了兩個ExtraFiles:cmd.ExtraFiles = append(p.ExtraFiles, childPipe, rootDir)

(2)、cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-2),

      fmt.Sprintf("_LIBCONTAINER_STATEDIR=%d", stdioFdCount+len(cmd.ExtraFiles)-1))

最後,若爲create container,則調用c.newInitProcess(p, cmd, parentPipe, childPipe, rootDir),不然調用c.newSetnsProcess(p, cmd, parentPipe, childPipe, rootDir)

 

十一、// runc/libcontainer/container_linux.go

func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe, rootDir *os.File) (*initProcess, error)

(1)、首先擴展cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard)) (initStandard是一個值爲"standard"的string)。

(2)、接着獲取config.Namespace的nsMaps,類型爲map[configs.NamespaceType]string。再調用_, sharePidns := nsMaps[configs.NEWPID]

(3)、調用data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps, "")

(4)、最後返回: return &initProcess {
  cmd:    cmd,

  childPipe:  childPipe,

  parentPipe:  parentPipe,

  manager:   c.cgroupManager,

  config:     c.newInitConfig(p),    // 根據c和p填充結構initConfig

  container:   c,

  process:      p,

  bootstrapData: data,

  sharePidns:   sharePidns,

  rootDir:     rootDir,

}

 

十二、// runc/libcontainer/container_linux.go

// bootstrapData encodes the necessary data in netlink binary format as a io.Reader

// Consumer can write the data to a bootstrap program such as one that uses nsenter package to bootsrap the container's init process correctly

// with correct namespaces, uid/gid, mapping etc

func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string, consolePath) (io.Reader, error)

先建立一個利用r := nl.NewlinkRequest(int(InitMsg), 0)建立一個netlink message。以後,將cloneFlags,console path, namespace path等信息寫入。最後,return bytes.NewReader(r.Serialize())

 

1三、// runc/libcontainer/process_linux.go

(1)、調用p.cmd.Start啓動runc init

(2)、io.Copy(p.parentPipe, p.bootstrapData) // 將bootstrapData的數據傳入runc init

(3)、p.execSetns()   // 獲取容器init進程的pid,process = os.FindProcess(pid.Pid), p.cmd.Process = process, p.process.ops = p

(4)、fds, err := getPipeFds(p.pid()) // save the standard descriptor names before the container can potentially move them
(5)、p.setExternalDescriptors(fds)

(6)、p.manager.Apply(p.pid()) // do this before syncing with child so that no children can escape the cgroup

(7)、p.createNetworkInterfaces() // creating network interfaces

(8)、p.sendConfig() // sending config to init process, utils.WriteJSON(p.parentPipe, p.config)

(9)、進入loop,從p.parenPipe中獲取同步狀態procSync

loop :

  for {

    dec.Decode(&procSync)

    switch procSync.Type {

    case procReady:

      ... // set oom_score_adj, set rlimits, call prestart hooks

      utils.WriteJSON(p.parentPipe, syncT{procRun}) // reading syncT run type

      sentRun = true

    case procHooks:

      ...

    case procError:

      ...
    }

  }

  ....

  syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR)

 

-------------------------------------------------------------------------runc init 執行流程----------------------------------------------------------

一、// main_unix.go

Action: func(context *cli.Context) error

  factory, _ := libcontainer.New("")

  factory.StartInitialization()

 

二、// runc/libcontainer/factory_linux.go

// StartInitialization loads a container by opening fd from the parent to read the configuration and state

func (l *LinuxFactory) StartInitialization() (err error)

這個函數很簡單,從"_LIBCONTAINER_INITPIPE"等環境變量中獲取pipefd, rootfd(/run/runc/container-id), it(初始化類型,create中爲standard),接着調用i, err = newContainerInit(it, pipe, rootfd),最後調用i.Init()

 

三、// runc/libcontainer/init_linux.go

func newContainerInit(t initType, pipe *os.File, stateDirFD int) (inter, error)

該函數很簡單,先調用json.NewDecoder(pipe).Decode(&config)中從管道中獲取配置信息,再調用populateProcessEnvironment(config.Env)將config中的環境變量加載到當前進程中。最後,根據initType的不一樣,當t爲initSetns時,返回&linuxSetnsInit{config: config}。在create container時,則返回&linuxStandardInit{pipe: pipe, parentPid: syscall.Getppid(), config: config, stateDirFD: stateDirFD}

 

四、// runc/libcontainer/standard_init_linux.go

func (l *linuxStandardInit) Init() error

(1)、該函數先處理l.config.Config.NoNewKeyring,l.config.Console, setupNetwork, setupRoute, label.Init()

(2)、if l.config.Config.Namespaces.Contains(configs.NEWNS) -> setupRootfs(l.config.Config, console, l.pipe)

(3)、設置hostname, apparmor.ApplyProfile(...), label.SetProcessLabel(...),l.config.Config.Sysctl

(4)、調用remountReadonly(path)從新掛載ReadonlyPaths,在配置文件中爲/proc/asound,/proc/bus, /proc/fs等等

(5)、調用maskPath(path)設置maskedPaths,pdeath := system.GetParentDeathSignal(), 處理l.config.NoNewPrivileges

(6)、調用syncParentReady(l.pipe) // 告訴父進程容器能夠執行Execv了, 從父進程來看,create已經完成了

(7)、處理l.config.Config.Seccomp 和 l.config.NoNewPrivileges, finalizeNamespace(l.config),pdeath.Restore(), 判斷syscall.Getppid()和l.parentPid是否相等,找到name, err := exec.Lookpath(l.config.Args[0]),最後l.pipe.Close(),init完成。此時create 在子進程中也完成了。

(8)、fd, err := syscall.Openat(l.stateDirFD, execFifoFilename, os.O_WRONLY|syscall.O_CLOEXEC, 0) ---> wait for the fifo to be opened on the other side before exec'ing the user process,其實此處就是在等待start命令。以後,再往fd中寫一個字節,用於同步:syscall.Write(fd, []byte("0"))

(9)、調用syscall.Exec(name, l.config.Args[0:], os.Environ())執行容器命令

相關文章
相關標籤/搜索