docker run命令解析(二)之Docker daemon--container start
阿新 • • 發佈:2018-12-20
上一篇咱們簡單的分析了docker run命令在Docker daemon中的create實現,接下來咱們開始start的分析。原始碼基於Docker-ce17.09
1.1、流程:
func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { version := httputils.VersionFromContext(ctx) //獲取hostConfig配置資訊 var hostConfig *container.HostConfig // A non-nil json object is at least 7 characters. //非nil 的json物件至少為7個字元。一系列檢查 if r.ContentLength > 7 || r.ContentLength == -1 { if versions.GreaterThanOrEqualTo(version, "1.24") { return bodyOnStartError{} } if err := httputils.CheckForJSON(r); err != nil { return err } c, err := s.decoder.DecodeHostConfig(r.Body) if err != nil { return err } hostConfig = c } //解析form表單 if err := httputils.ParseForm(r); err != nil { return err } //獲得與鍵關聯的值 checkpoint := r.Form.Get("checkpoint") checkpointDir := r.Form.Get("checkpoint-dir") //呼叫ContainerStart進一步啟動容器,下面詳細分析 if err := s.backend.ContainerStart(vars["name"], hostConfig, checkpoint, checkpointDir); err != nil { return err } w.WriteHeader(http.StatusNoContent) return nil }
// ContainerStart starts a container. func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.HostConfig, checkpoint string, checkpointDir string) error { if checkpoint != "" && !daemon.HasExperimental() { return validationError{errors.New("checkpoint is only supported in experimental mode")} } //可以根據全容器ID、容器名、容器ID字首獲取容器物件 container, err := daemon.GetContainer(name) if err != nil { return err } //呼叫containerStart進一步啟動容器,下面詳細分析 if err := daemon.containerStart(container, checkpoint, checkpointDir, true); err != nil { return err } return nil }
下面的containerStart() 沒看懂,再補充
// containerStart prepares the container to run by setting up everything the // container needs, such as storage and networking, as well as links // between containers. The container is left waiting for a signal to // begin running. func (daemon *Daemon) containerStart(container *container.Container, checkpoint string, checkpointDir string, resetRestartManager bool) (err error) { start := time.Now() container.Lock() defer container.Unlock() if resetRestartManager && container.Running { // skip this check if already in restarting step and resetRestartManager==false return nil } if container.RemovalInProgress || container.Dead { return stateConflictError{errors.New("container is marked for removal and cannot be started")} } // if we encounter an error during start we need to ensure that any other // setup has been cleaned up properly defer func() { if err != nil { container.SetError(err) // if no one else has set it, make sure we don't leave it at zero if container.ExitCode() == 0 { container.SetExitCode(128) } if err := container.CheckpointTo(daemon.containersReplica); err != nil { logrus.Errorf("%s: failed saving state on start failure: %v", container.ID, err) } container.Reset(false) daemon.Cleanup(container) // if containers AutoRemove flag is set, remove it after clean up if container.HostConfig.AutoRemove { container.Unlock() if err := daemon.ContainerRm(container.ID, &types.ContainerRmConfig{ForceRemove: true, RemoveVolume: true}); err != nil { logrus.Errorf("can't remove container %s: %v", container.ID, err) } container.Lock() } } }() if err := daemon.conditionalMountOnStart(container); err != nil { return err } if err := daemon.initializeNetworking(container); err != nil { return err } spec, err := daemon.createSpec(container) if err != nil { return systemError{err} } createOptions, err := daemon.getLibcontainerdCreateOptions(container) if err != nil { return err } if resetRestartManager { container.ResetRestartManager(true) } if checkpointDir == "" { checkpointDir = container.CheckpointDir() } if daemon.saveApparmorConfig(container); err != nil { return err } if err := daemon.containerd.Create(container.ID, checkpoint, checkpointDir, *spec, container.InitializeStdio, createOptions...); err != nil { return translateContainerdStartErr(container.Path, container.SetExitCode, err) } containerActions.WithValues("start").UpdateSince(start) return nil }
1.4、daemon.containerd.Create()中的Create是在client 接口裡面,client提供對容器功能的訪問。下面分析Create()函式 ,位置。
func (clnt *client) Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) (err error) {
clnt.lock(containerID)
defer clnt.unlock(containerID)
if _, err := clnt.getContainer(containerID); err == nil {
return fmt.Errorf("Container %s is already active", containerID)
}
uid, gid, err := getRootIDs(spec)
if err != nil {
return err
}
dir, err := clnt.prepareBundleDir(uid, gid)
if err != nil {
return err
}
//生成一個libcontainerd.container物件
container := clnt.newContainer(filepath.Join(dir, containerID), options...)
if err := container.clean(); err != nil {
return err
}
defer func() {
if err != nil {
container.clean()
clnt.deleteContainer(containerID)
}
}()
//建立目錄
if err := idtools.MkdirAllAndChown(container.dir, 0700, idtools.IDPair{uid, gid}); err != nil && !os.IsExist(err) {
return err
}
//生成檔案config.json
f, err := os.Create(filepath.Join(container.dir, configFilename))
if err != nil {
return err
}
defer f.Close()
if err := json.NewEncoder(f).Encode(spec); err != nil {
return err
}
//呼叫container.start()進一步啟動container
return container.start(&spec, checkpoint, checkpointDir, attachStdio)
}
該函式主要功能是傳送CreateContainerRequest RPC請求給 docker-containerd。(不明白)
func (ctr *container) start(spec *specs.Spec, checkpoint, checkpointDir string, attachStdio StdioCallback) (err error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ready := make(chan struct{})
fifoCtx, cancel := context.WithCancel(context.Background())
defer func() {
if err != nil {
cancel()
}
}()
iopipe, err := ctr.openFifos(fifoCtx, spec.Process.Terminal)
if err != nil {
return err
}
var stdinOnce sync.Once
// we need to delay stdin closure after container start or else "stdin close"
// event will be rejected by containerd.
// stdin closure happens in attachStdio
stdin := iopipe.Stdin
iopipe.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error {
var err error
stdinOnce.Do(func() { // on error from attach we don't know if stdin was already closed
err = stdin.Close()
go func() {
select {
case <-ready:
case <-ctx.Done():
}
select {
case <-ready:
if err := ctr.sendCloseStdin(); err != nil {
logrus.Warnf("failed to close stdin: %+v", err)
}
default:
}
}()
})
return err
})
//CreateContainerRequest物件,應該是儲存一些容器資訊給libcontainer start時使用
r := &containerd.CreateContainerRequest{
Id: ctr.containerID,
BundlePath: ctr.dir,
Stdin: ctr.fifo(unix.Stdin),
Stdout: ctr.fifo(unix.Stdout),
Stderr: ctr.fifo(unix.Stderr),
Checkpoint: checkpoint,
CheckpointDir: checkpointDir,
// check to see if we are running in ramdisk to disable pivot root
NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
Runtime: ctr.runtime,
RuntimeArgs: ctr.runtimeArgs,
}
ctr.client.appendContainer(ctr)
if err := attachStdio(*iopipe); err != nil {
ctr.closeFifos(iopipe)
return err
}
//傳送CreateContainerRequest RPC請求給docker containerd(不懂)
resp, err := ctr.client.remote.apiClient.CreateContainer(context.Background(), r)
if err != nil {
ctr.closeFifos(iopipe)
return err
}
ctr.systemPid = systemPid(resp.Container)
close(ready)
return ctr.client.backend.StateChanged(ctr.containerID, StateInfo{
CommonStateInfo: CommonStateInfo{
State: StateStart,
Pid: ctr.systemPid,
}})
}
參考:
《Docker原始碼分析》
百度網盤:連結: https://pan.baidu.com/s/1m5AqgfZKvPRu2BcmWz-WxA 提取碼: wjrj