containerd的create container的API如下所示:
type CreateContainerRequest struct { Id string BundlePath string Checkpoint string Stdin string Stdout string Stderr string Labels []string NoPivotRoot bool Runtime string RuntimeArgs []string CheckpointDir string }
StartTask結構如下所示:
type StartTask struct { baseTask ID string BundlePath string Stdout string Stderr string Stdin string StartResponse chan StartResponse Labels []string NoPivotRoot bool Checkpoint *runtime.Checkpoint CheckpointDir string Runtime string RuntimeArgs []string }
1、containerd/api/grpc/server/server.go
func (s *apiServer) CreateContainer(ctx context.Context, c *types.CreateContainerRequest) (*types.CreateContainerResponse, error)
(1)、根據c填充獲得e := &supervisor.StartTask{},並調用s.sv.SendTask(e)
(2)、調用 r := <-e.StartResponse,再調用apiC ,err := createAPIContainer(r.Container, false)獲取創建的容器實例
(3)、return &types.CreateContainerResponse{Container: apiC,}
2、containerd/supervisor/create.go
func (s *Supervisor) start(t *StartTask) error
(1)、根據t的內容創建容器,runtime.New主要根據ContainerOpts填充結構container獲得容器實例,再將創建state並寫入狀態文件中。
container, err := runtime.New(runtime.ContainerOpts{ Root: s.stateDir, ID: t.ID, Bundle: t.BundlePath, Runtime: rt, RuntimeArgs: rtArgs, Shim: s.shim, Labels: t.Labels, NoPivotRoot: t.NoPivotRoot, Timeout: s.timeout })
(2)、注冊新增加的容器,調用s.containers[t.ID] = &containerInfo{container: container,}
(3)、根據新獲得的container實例和t的內容,填充獲得startTask,再調用s.startTask <- task 交由worker處理
task := &startTask{ Err: t.ErrorCh(), Container: container, StartResponse: t.StartResponse, Stdin: t.Stdin, Stdout: t.Stdout, Stderr: t.Stderr }
3、containerd/supervisor/worker.go
// Start runs a loop in charge of starting new containers
func (w *Worker) Start()
containerd在剛創建時,就啟動了10個goroutine,用於處理startTasks,這里的Start函數就是從startTasks這個channel中獲取任務,並處理
(1)、調用process, err := t.Container.Start(t.checkpointPath, runtime.NewStdio(t.Stdin, t.Stdout, t.Stderr))啟動容器,NewStdio()僅僅只是將參數封裝到一個統一的Stdio結構中
(2)、分別調用w.s.monitor.MonitorOOM(t.Container),w.s.monitorProcess(process)對容器進行監控,調用t.StartResponse <- StartResponse{Container: t.Container}返回創建成功的容器實例
4、containerd/runtime/container.go
func (c *container) Start(checkpointPath string, s Stdio) (Process, error)
(1)、創建processRoot := filepath.Join(c.root, c.id, InitProcessID)目錄
(2)、容器創建命令 cmd := exec.Command(c.shim, c.id, c.bundle, c.runtime),配置cmd目錄為processRoot,spec, err := c.readSpec()
(3)、生成config如下:
config := &processConfig { checkpoint: checkpointPath, root: processRoot, id: InitProcessID, c: c, stdio: s, spec: spec, processSpec: specs.ProcessSpec(spec.Process), }
(4)、根據config,調用p, err := newProcess(config),生成process實例
(5)、最后調用c.createCmd(InitProcessID, cmd, p),並返回 return p, nil
5、containerd/runtime/process.go
func newProcess(config *processConfig) (*process, error)
(1)、根據config生成process實例,p := &process{root: config.root, id: config.id, container: config.c, ....., cmdDoneCh: make(chan struct{}), state: Running,}
(2)、創建狀態文件os.Create(filepath.Join(config.root, "process.config")),生成ps := ProcessState{...},並將ps的內容寫入狀態文件中
(3)、調用exit, err := getExitPipe(filepath.Join(config.root, ExitFile))和control ,err := getControlPipe(filepath.Join(config.root, ControlFile))生成兩個pipe文件,並分別將exit和control賦值給p.exitPipe和p.controlPipe,最后 return p
6、containerd/runtime/process.go
getExitPipe和getControlPipe生成兩個FIFO文件,其中Exit函數中的FIFO是只讀的,而Control函數中的FIFO是讀寫的
7、containerd/runtime/container.go
func (c *container) createCmd(pid string, cmd *exec.Cmd, p *process)
(1)、p.cmd = cmd, 再調用 cmd.Start()
(2)、構建defer函數,其中生成一個goroutine,其中調用p.cmd.Wait(),再調用same, err := p.isSameProcess(),如果same為true並且p.pid > 0則再進行一些處理
(3)、調用c.waitForCreate(p, cmd),c.processes[pid] = p
8、containerd/runtime/container.go
func (c *container) waitForCreate(p *process, cmd *exec.Cmd) error
(1)、該函數先啟動一個goroutine用於從pidfile中讀取pid
(2)、select,從(1)中的goroutine接收結果,或者超時,當超時時,調用cmd.Process.Kill()和cmd.Wait()