一、// 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())執行容器命令