【docker 17 源码分析】 docker run container 源码分析二 docker start

   问题:Docker run 来执行一个容器,那么执行 Docker run 之后到底都做了什么工作呢?

     首先,用户通过Docker client输入docker run来创建一个容器。Docker client 主要的工作是通过解析用户所提供的一系列参数后,分别发送了这样两条请求:

    docker create 和 docker start,这篇文章分析 docker start 命令

二. docker start container 源码分析


1. 客户端 ContainerStart

    函数位置 client/container_start.go,post 请求到 docker daemon 进程,路由为 /containers/${container-id}/start

func (cli *Client) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error {
       query := url.Values{}
       if len(options.CheckpointID) != 0 {
              query.Set("checkpoint", options.CheckpointID)
       }
       if len(options.CheckpointDir) != 0 {
              query.Set("checkpoint-dir", options.CheckpointDir)
       }

       resp, err := cli.post(ctx, "/containers/"+containerID+"/start", query, nil, nil)
       ensureReaderClosed(resp)
       return err
}

2. daemon 端 ContainerStart

    函数位置 daemon/start.go,2.1 讲解 GetContainer 函数,2.2 重点讲解 daemon.containerStart 函数

// ContainerStart starts a container.
func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.HostConfig, 
       checkpoint string, checkpointDir string) error {
       container, err := daemon.GetContainer(name)

       // check if hostConfig is in line with the current system settings.
       // It may happen cgroups are umounted or the like.
       if _, err = daemon.verifyContainerSettings(container.HostConfig, nil, false); err != nil {

       return daemon.containerStart(container, checkpoint, checkpointDir, true)
}

    2.1 GetContainer,可以根据全容器 ID,容器名,容器 ID 前缀

func (daemon *Daemon) GetContainer(prefixOrName string) (*container.Container, error) {
       if containerByID := daemon.containers.Get(prefixOrName); containerByID != nil {
              // prefix is an exact match to a full container ID
              return containerByID, nil
       }

       // GetByName will match only an exact name provided; we ignore errors
       if containerByName, _ := daemon.GetByName(prefixOrName); containerByName != nil {
              // prefix is an exact match to a full container Name
              return containerByName, nil
       }

       containerID, indexError := daemon.idIndex.Get(prefixOrName)
      
       return daemon.containers.Get(containerID), nil
}

    2.2 containerStart 函数,2.2.1 讲解 conditionalMountOnStart,2.2.2 讲解 initalizeNetworking, 2.2.3 讲解 Create

func (daemon *Daemon) containerStart(container *container.Container, checkpoint string,
       checkpointDir string, resetRestartManager bool) (err error) {
       if err := daemon.conditionalMountOnStart(container); err != nil {

       if err := daemon.initializeNetworking(container); err != nil {
 
       spec, err := daemon.createSpec(container)

       createOptions, err := daemon.getLibcontainerdCreateOptions(container)

       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 nil
}

    2.2.1 conditionalMountOnStart 在创建的时候调用了一次,这次 start 又 mount 一次?有点蒙等了。

func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
       return daemon.Mount(container)
}

    2.2.2 initalizeNetworking 对网络进行初始化,docker 网络有三种,bridge模式(每个容器用户单独的网络栈),host模式(与宿主机共用一个网络栈),contaier 模式(与其他容器共用一个网络栈,猜测kubernate中的pod所用的模式);根据 config 和 hostConfig 中的参数来确定容器的网络模式,然后调 libnetwork 包来建立网络,默认使用的是 bridge 模式,allocateNetwork 在 2.2.2.1 中进行讲解

func (daemon *Daemon) initializeNetworking(container *container.Container) error {
       if container.HostConfig.NetworkMode.IsContainer() {
              // we need to get the hosts files from the container to join
              nc, err := daemon.getNetworkedContainer(container.ID, container.HostConfig.NetworkMode.ConnectedContainer())

              err = daemon.initializeNetworkingPaths(container, nc)
            
              container.Config.Hostname = nc.Config.Hostname
              container.Config.Domainname = nc.Config.Domainname
              return nil
       }

       if container.HostConfig.NetworkMode.IsHost() {
              if container.Config.Hostname == "" {
                     container.Config.Hostname, err = os.Hostname()
              }
       }

       if err := daemon.allocateNetwork(container); err != nil {

       return container.BuildHostnameFile()
}

    2.2.2.1 allocateNetwork 函数,主要函数为 connectToNetwork

func (daemon *Daemon) allocateNetwork(container *container.Container) error {
       defaultNetName := runconfig.DefaultDaemonNetworkMode().NetworkName()
       if nConf, ok := container.NetworkSettings.Networks[defaultNetName]; ok {
              cleanOperationalData(nConf)
              if err := daemon.connectToNetwork(container, defaultNetName, nConf.EndpointSettings, updateSettings); err != nil {
       }

       // the intermediate map is necessary because "connectToNetwork" modifies "container.NetworkSettings.Networks"
       networks := make(map[string]*network.EndpointSettings)
       for n, epConf := range container.NetworkSettings.Networks {
              if n == defaultNetName {
                     continue
              }

              networks[n] = epConf
       }

       for netName, epConf := range networks {
              cleanOperationalData(epConf)
              if err := daemon.connectToNetwork(container, netName, epConf.EndpointSettings, updateSettings); err != nil {
       }

       if _, err := container.WriteHostConfig(); err != nil {
 
       return nil
}

    2.2.3 Create 函数位于文件 libcontainerd/client_unix.go 中,创建目录,以及文件 config.json,调用 start 函数在 2.2.3.1 中讲解

func (clnt *client) Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) (err error) {
       container := clnt.newContainer(filepath.Join(dir, containerID), options...)
       if err := container.clean(); err != nil {

       if err := idtools.MkdirAllAndChown(container.dir, 0700, idtools.IDPair{uid, gid}); err != nil && !os.IsExist(err) {

       f, err := os.Create(filepath.Join(container.dir, configFilename))

       if err := json.NewEncoder(f).Encode(spec); err != nil {
    
       return container.start(&spec, checkpoint, checkpointDir, attachStdio)
}

    2.2.3.1 start 函数位于 libcontainerd/container_unix.go 中,主要是发送 CreateContainerRequest RPC 请求给 docker-containerd 进程

func (ctr *container) start(spec *specs.Spec, checkpoint, checkpointDir string, attachStdio StdioCallback) (err error) {
       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
       })

       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 {

       resp, err := ctr.client.remote.apiClient.CreateContainer(context.Background(), r)
  
       ctr.systemPid = systemPid(resp.Container)
       close(ready)

       return ctr.client.backend.StateChanged(ctr.containerID, StateInfo{
              CommonStateInfo: CommonStateInfo{
                     State: StateStart,
                     Pid:   ctr.systemPid,
              }})

}


总结:

  1.       GetContainer 获得容器信息
  2.       initializeNetworking 初始化网络
  3.       getLibcontainerdCreateOptions 得到创建容器的选项
  4.       发送 CreateContainerRequest RPC 给 docker-containerd 进程


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值