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