docker启动Container进程之启动流程

本文深入剖析Docker启动Container的流程,从配置环境到启动进程,再到异常处理和资源回收。具体步骤包括构建DNS、挂载文件系统、初始化网络、验证资源设置、配置环境变量、创建工作路径、启动进程及监控、以及处理启动失败后的资源回收。重点讲解了如何为Container创建monitor以及启动和监控container进程的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

docker启动Container进程之启动流程


代码分析起点:daemon/container.go/Start
docker启动containter的起始点在daemon/container.go/Start方法。
这个方法做了三大方面的事情:

  • 配置启动container所需要的环境
  • 启动container进程
  • 如果container启动失败,做资源回收

1 配置启动container需要的环境

此部分为启动container进程做准备。包括一下几个方面:
1. 构建container的DNS
使用container的setupContainerDns方法构建container的DNS。 –dns
2. 挂在文件系统
使用Mount方法配置container的basefs路径。
3. 初始化网络
使用initialize方法配置container网络信息,详细分析见docker如果构建bridge网络 。–net
4. 更新parent hosts
使用container的updateParentHosts方法更新与当前container相关container的etc/hosts
5. 验证container资源设置
使用container的verifyDaemonSetting方法校验container的Me moryLimitSwapLimitIPv4ForwardingDisable设置。
6. 卷准备
使用container的prepareVolumes方法为container启动配置挂载数据卷。 –volume
7. 链接其他container
使用container的setupLinkedContainers方法配置container需要连接的其他container。 –link
8. 构建工作路径
使用container的setupWorkingDirectory方法配置container的工作路径。
9. 创建container进程环境
使用container的createDaemonEnvironment方法配置container内的环境变量。 –env
10. populateCommand
根据步骤9收集到的环境变量,使用populateCommand构建execdriver命令execdriver.Command
11. 构建mount点
使用container的setupMounts方法设置container内部的mount配置。

2 启动container进程

在准备好启动container进程的配置后,docker开始创建container进程,使用container的waitForStart方法完成此项任务。
通过为启动的container创建一个monitor来监控container进程的启动情况,这个地方使用了golang的一个特性:channel
如果container进程正常启动,那么会在container.monitor.startSigbal通道受到一个信号;如果在promise.Go(container.monitor.Start)通道收到错误消息,说明container启动失败并返回错误信息。
也就是说container monitor监控container主进程的执行情况。如果container被指定了一种重启策略,那么monitor保证进程按重启策略重启。当container被stop,monitor会reset并cleanup container占用的资源。

2.1 为container创建monitor

newContinerMonitor方法中根据为container内第一个进程配置的重启策略创建一个monitor对象。

func newContainerMonitor(container *Container, policy runconfig.RestartPolicy) *containerMonitor {
    return &containerMonitor{
        container:     container,
        restartPolicy: policy,
        timeIncrement: defaultTimeIncrement,
        stopChan:      make(chan struct{}),
        startSignal:   make(chan struct{}),
    }
}

containerMonitor结构字段

字段名含义
mux sync.Mutexmonitor锁
container *Container被监控的container
restartPolicy runconfig.RestartPolicymonitor使用的监控策略
failureCount intcontianer启动失败计算器
shouldStop bool告诉monitor,下次退出container的原因是由于docker或用户要求
startSignal chan struct{}通过此通道,container告诉docker启动完成,container最初的启动完成后被关闭
stopChan chan struct{}告诉monitor在下次重启之前有一个等待时间
timeIncrement int两次重启的时间间隔,单位:毫秒
lastStartTime time.Timemonitor最后执行container进程的时间

2.2 启动container进程

docker daemon启动一个goroutine执行container.monitor.Start,然后监听两个通道,如果捕获了startSignal,说明container进行正常启动;如果从promise.Go(container.monitor.Start)通道捕获到了err,说明container进程启动失败。其中contianer.monitor.Start为启动container进程的入口函数。

    // block until we either receive an error from the initial start of the container's
    // process or until the process is running in the container
    select {
    case <-container.monitor.startSignal:
    case err := <-promise.Go(container.monitor.Start):
        return err
    }

下面看看monitor.Start做了什么事情:

首先为container创建标准输入、输出和错误管道。

pipes := execdriver.NewPipes(m.container.stdin, m.container.stdout, m.container.stderr, m.container.Config.OpenStdin)

然后启动container进程

if exitStatus, err = m.container.daemon.Run(m.container, pipes, m.callback); err != nil {
            // if we receive an internal error from the initial start of a container then lets
            // return it instead of entering the restart loop
            if m.container.RestartCount == 0 {
                m.container.ExitCode = -1
                m.resetContainer(false)

                return err
            }

            log.Errorf("Error running container: %s", err)
        }

daemon/Run –> execdriver/Run –> native/driver.go/Run
在native/driver.go/Run中使用namespace包中的方法Exec创建容器进程。

go func() {
        exitCode, err := namespaces.Exec(container, c.ProcessConfig.Stdin, c.ProcessConfig.Stdout, c.ProcessConfig.Stderr, c.ProcessConfig.Console, dataPath, args, func(container *libcontainer.Config, console, dataPath, init string, child *os.File, args []string) *exec.Cmd {
            c.ProcessConfig.Path = d.initPath
            c.ProcessConfig.Args = append([]string{
                DriverName,
                "-console", console,
                "-pipe", "3",
                "-root", filepath.Join(d.root, c.ID),
                "--",
            }, args...)

            // set this to nil so that when we set the clone flags anything else is reset
            c.ProcessConfig.SysProcAttr = &syscall.SysProcAttr{
                Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)),
            }
            c.ProcessConfig.ExtraFiles = []*os.File{child}

            c.ProcessConfig.Env = container.Env
            c.ProcessConfig.Dir = container.RootFs

            return &c.ProcessConfig.Cmd
        }, func() {
            close(waitForStart)
            if startCallback != nil {
                c.ContainerPid = c.ProcessConfig.Process.Pid
                startCallback(&c.ProcessConfig, c.ContainerPid)
            }
        })
        execOutputChan <- execOutput{exitCode, err}
    }()

函数func(container *libcontainer.Config, console, dataPath, init string, child *os.File, args []string)构建了container进程的属性相关字段,例如 :dockerinit路径、进程参数、进程namespace flags、进程环境、进程根目录等信息。

3 启动失败后资源的回收

如果container进行启动失败,那么通过

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.ExitCode = 128
            }
            container.toDisk()
            container.cleanup()
        }
    }()

回收已经为container分配的资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值