// Update the container's Status.Health struct based on the latest probe's result. func handleProbeResult(d *Daemon, c *container.Container, result *types.HealthcheckResult) { c.Lock() defer c.Unlock() retries := c.Config.Healthcheck.Retries if retries <= 0 { retries = defaultProbeRetries } h := c.State.Health oldStatus := h.Status if len(h.Log) >= maxLogEntries { h.Log = append(h.Log[len(h.Log)+1-maxLogEntries:], result) } else { h.Log = append(h.Log, result) } if result.ExitCode == exitStatusHealthy { h.FailingStreak = 0 h.Status = types.Healthy } else { // Failure (including invalid exit code) h.FailingStreak++ if h.FailingStreak >= retries { h.Status = types.Unhealthy } // Else we're starting or healthy. Stay in that state. } if oldStatus != h.Status { d.LogContainerEvent(c, "health_status: "+h.Status) } }
func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig *runconfig.HostConfig) error { container.Lock() if err := parseSecurityOpt(container, hostConfig); err != nil { container.Unlock() return err } container.Unlock() // Do not lock while creating volumes since this could be calling out to external plugins // Don't want to block other actions, like `docker ps` because we're waiting on an external plugin if err := daemon.registerMountPoints(container, hostConfig); err != nil { return err } container.Lock() defer container.Unlock() // Register any links from the host config before starting the container if err := daemon.registerLinks(container, hostConfig); err != nil { return err } container.HostConfig = hostConfig container.ToDisk() return nil }
// containerPause pauses the container execution without stopping the process. // The execution can be resumed by calling containerUnpause. func (daemon *Daemon) containerPause(container *container.Container) error { container.Lock() defer container.Unlock() // We cannot Pause the container which is not running if !container.Running { return derr.ErrorCodeNotRunning.WithArgs(container.ID) } // We cannot Pause the container which is already paused if container.Paused { return derr.ErrorCodeAlreadyPaused.WithArgs(container.ID) } // We cannot Pause the container which is restarting if container.Restarting { return derr.ErrorCodeContainerRestarting.WithArgs(container.ID) } if err := daemon.execDriver.Pause(container.Command); err != nil { return derr.ErrorCodeCantPause.WithArgs(container.ID, err) } container.Paused = true daemon.LogContainerEvent(container, "pause") return nil }
// killWithSignal sends the container the given signal. This wrapper for the // host specific kill command prepares the container before attempting // to send the signal. An error is returned if the container is paused // or not running, or if there is a problem returned from the // underlying kill command. func (daemon *Daemon) killWithSignal(container *container.Container, sig int) error { logrus.Debugf("Sending %d to %s", sig, container.ID) container.Lock() defer container.Unlock() // We could unpause the container for them rather than returning this error if container.Paused { return derr.ErrorCodeUnpauseContainer.WithArgs(container.ID) } if !container.Running { return derr.ErrorCodeNotRunning.WithArgs(container.ID) } container.ExitOnNext() // if the container is currently restarting we do not need to send the signal // to the process. Telling the monitor that it should exit on it's next event // loop is enough if container.Restarting { return nil } if err := daemon.kill(container, sig); err != nil { return err } attributes := map[string]string{ "signal": fmt.Sprintf("%d", sig), } daemon.LogContainerEventWithAttributes(container, "kill", attributes) return nil }
// containerPause pauses the container execution without stopping the process. // The execution can be resumed by calling containerUnpause. func (daemon *Daemon) containerPause(container *container.Container) error { container.Lock() defer container.Unlock() // We cannot Pause the container which is not running if !container.Running { return errNotRunning{container.ID} } // We cannot Pause the container which is already paused if container.Paused { return fmt.Errorf("Container %s is already paused", container.ID) } // We cannot Pause the container which is restarting if container.Restarting { return errContainerIsRestarting(container.ID) } if err := daemon.containerd.Pause(container.ID); err != nil { return fmt.Errorf("Cannot pause container %s: %s", container.ID, err) } return nil }
// killWithSignal sends the container the given signal. This wrapper for the // host specific kill command prepares the container before attempting // to send the signal. An error is returned if the container is paused // or not running, or if there is a problem returned from the // underlying kill command. func (daemon *Daemon) killWithSignal(container *container.Container, sig int) error { logrus.Debugf("Sending kill signal %d to container %s", sig, container.ID) container.Lock() defer container.Unlock() // We could unpause the container for them rather than returning this error if container.Paused { return fmt.Errorf("Container %s is paused. Unpause the container before stopping", container.ID) } if !container.Running { return errNotRunning{container.ID} } if container.Config.StopSignal != "" { containerStopSignal, err := signal.ParseSignal(container.Config.StopSignal) if err != nil { return err } if containerStopSignal == syscall.Signal(sig) { container.ExitOnNext() } } else { container.ExitOnNext() } if !daemon.IsShuttingDown() { container.HasBeenManuallyStopped = true } // if the container is currently restarting we do not need to send the signal // to the process. Telling the monitor that it should exit on its next event // loop is enough if container.Restarting { return nil } if err := daemon.kill(container, sig); err != nil { err = fmt.Errorf("Cannot kill container %s: %s", container.ID, err) // if container or process not exists, ignore the error if strings.Contains(err.Error(), "container not found") || strings.Contains(err.Error(), "no such process") { logrus.Warnf("container kill failed because of 'container not found' or 'no such process': %s", err.Error()) } else { return err } } attributes := map[string]string{ "signal": fmt.Sprintf("%d", sig), } daemon.LogContainerEventWithAttributes(container, "kill", attributes) return nil }
// Update the container's Status.Health struct based on the latest probe's result. func handleProbeResult(d *Daemon, c *container.Container, result *types.HealthcheckResult, done chan struct{}) { c.Lock() defer c.Unlock() // probe may have been cancelled while waiting on lock. Ignore result then select { case <-done: return default: } retries := c.Config.Healthcheck.Retries if retries <= 0 { retries = defaultProbeRetries } h := c.State.Health oldStatus := h.Status if len(h.Log) >= maxLogEntries { h.Log = append(h.Log[len(h.Log)+1-maxLogEntries:], result) } else { h.Log = append(h.Log, result) } if result.ExitCode == exitStatusHealthy { h.FailingStreak = 0 h.Status = types.Healthy } else { // Failure (including invalid exit code) h.FailingStreak++ if h.FailingStreak >= retries { h.Status = types.Unhealthy } // Else we're starting or healthy. Stay in that state. } if oldStatus != h.Status { d.LogContainerEvent(c, "health_status: "+h.Status) } }
// containerUnpause resumes the container execution after the container is paused. func (daemon *Daemon) containerUnpause(container *container.Container) error { container.Lock() defer container.Unlock() // We cannot unpause the container which is not paused if !container.Paused { return fmt.Errorf("Container %s is not paused", container.ID) } if err := daemon.containerd.Resume(container.ID); err != nil { return fmt.Errorf("Cannot unpause container %s: %s", container.ID, err) } return nil }
// reducePsContainer is the basic representation for a container as expected by the ps command. func (daemon *Daemon) reducePsContainer(container *container.Container, ctx *listContext, reducer containerReducer) (*types.Container, error) { container.Lock() defer container.Unlock() // filter containers to return action := includeContainerInList(container, ctx) switch action { case excludeContainer: return nil, nil case stopIteration: return nil, errStopIteration } // transform internal container struct into api structs return reducer(container, ctx) }
// containerUnpause resumes the container execution after the container is paused. func (daemon *Daemon) containerUnpause(container *container.Container) error { container.Lock() defer container.Unlock() // We cannot unpause the container which is not running if !container.Running { return derr.ErrorCodeNotRunning.WithArgs(container.ID) } // We cannot unpause the container which is not paused if !container.Paused { return derr.ErrorCodeNotPaused.WithArgs(container.ID) } if err := daemon.execDriver.Unpause(container.Command); err != nil { return err } container.Paused = false daemon.LogContainerEvent(container, "unpause") return nil }
// containerUnpause resumes the container execution after the container is paused. func (daemon *Daemon) containerUnpause(container *container.Container) error { container.Lock() defer container.Unlock() // We cannot unpause the container which is not running if !container.Running { return errNotRunning{container.ID} } // We cannot unpause the container which is not paused if !container.Paused { return fmt.Errorf("Container %s is not paused", container.ID) } if err := daemon.execDriver.Unpause(container.Command); err != nil { return fmt.Errorf("Cannot unpause container %s: %s", container.ID, err) } container.Paused = false daemon.LogContainerEvent(container, "unpause") return nil }
func (d *Daemon) containerExec(container *container.Container, ec *exec.Config) error { container.Lock() defer container.Unlock() callback := func(processConfig *execdriver.ProcessConfig, pid int, chOOM <-chan struct{}) error { if processConfig.Tty { // The callback is called after the process Start() // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave // which we close here. if c, ok := processConfig.Stdout.(io.Closer); ok { c.Close() } } ec.Close() return nil } // We use a callback here instead of a goroutine and an chan for // synchronization purposes cErr := promise.Go(func() error { return d.monitorExec(container, ec, callback) }) return ec.Wait(cErr) }
// containerStatPath stats the filesystem resource at the specified path in this // container. Returns stat info about the resource. func (daemon *Daemon) containerStatPath(container *container.Container, path string) (stat *types.ContainerPathStat, err error) { container.Lock() defer container.Unlock() if err = daemon.Mount(container); err != nil { return nil, err } defer daemon.Unmount(container) err = daemon.mountVolumes(container) defer container.UnmountVolumes(true, daemon.LogVolumeEvent) if err != nil { return nil, err } resolvedPath, absPath, err := container.ResolvePath(path) if err != nil { return nil, err } return container.StatPath(resolvedPath, absPath) }
// killWithSignal sends the container the given signal. This wrapper for the // host specific kill command prepares the container before attempting // to send the signal. An error is returned if the container is paused // or not running, or if there is a problem returned from the // underlying kill command. func (daemon *Daemon) killWithSignal(container *container.Container, sig int) error { logrus.Debugf("Sending %d to %s", sig, container.ID) container.Lock() defer container.Unlock() // We could unpause the container for them rather than returning this error if container.Paused { return fmt.Errorf("Container %s is paused. Unpause the container before stopping", container.ID) } if !container.Running { return errNotRunning{container.ID} } container.ExitOnNext() if !daemon.IsShuttingDown() { container.HasBeenManuallyStopped = true } // if the container is currently restarting we do not need to send the signal // to the process. Telling the monitor that it should exit on it's next event // loop is enough if container.Restarting { return nil } if err := daemon.kill(container, sig); err != nil { return fmt.Errorf("Cannot kill container %s: %s", container.ID, err) } attributes := map[string]string{ "signal": fmt.Sprintf("%d", sig), } daemon.LogContainerEventWithAttributes(container, "kill", attributes) return nil }
func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig *containertypes.HostConfig) error { // Do not lock while creating volumes since this could be calling out to external plugins // Don't want to block other actions, like `docker ps` because we're waiting on an external plugin if err := daemon.registerMountPoints(container, hostConfig); err != nil { return err } container.Lock() defer container.Unlock() // Register any links from the host config before starting the container if err := daemon.registerLinks(container, hostConfig); err != nil { return err } // make sure links is not nil // this ensures that on the next daemon restart we don't try to migrate from legacy sqlite links if hostConfig.Links == nil { hostConfig.Links = []string{} } container.HostConfig = hostConfig return container.ToDisk() }
// 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) (err error) { container.Lock() defer container.Unlock() if container.Running { return nil } if container.RemovalInProgress || container.Dead { return derr.ErrorCodeContainerBeingRemoved } // 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.ExitCode = 128 } container.ToDisk() daemon.Cleanup(container) daemon.LogContainerEvent(container, "die") } }() if err := daemon.conditionalMountOnStart(container); err != nil { return err } // Make sure NetworkMode has an acceptable value. We do this to ensure // backwards API compatibility. container.HostConfig = runconfig.SetDefaultNetModeIfBlank(container.HostConfig) if err := daemon.initializeNetworking(container); err != nil { return err } linkedEnv, err := daemon.setupLinkedContainers(container) if err != nil { return err } if err := container.SetupWorkingDirectory(); err != nil { return err } env := container.CreateDaemonEnvironment(linkedEnv) if err := daemon.populateCommand(container, env); err != nil { return err } if !container.HostConfig.IpcMode.IsContainer() && !container.HostConfig.IpcMode.IsHost() { if err := daemon.setupIpcDirs(container); err != nil { return err } } mounts, err := daemon.setupMounts(container) if err != nil { return err } mounts = append(mounts, container.IpcMounts()...) mounts = append(mounts, container.TmpfsMounts()...) container.Command.Mounts = mounts if err := daemon.waitForStart(container); err != nil { return err } container.HasBeenStartedBefore = true return nil }
// registerMountPoints initializes the container mount points with the configured volumes and bind mounts. // It follows the next sequence to decide what to mount in each final destination: // // 1. Select the previously configured mount points for the containers, if any. // 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination. // 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations. // 4. Cleanup old volumes that are about to be reassigned. func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) { binds := map[string]bool{} mountPoints := map[string]*volume.MountPoint{} defer func() { // clean up the container mountpoints once return with error if retErr != nil { for _, m := range mountPoints { if m.Volume == nil { continue } daemon.volumes.Dereference(m.Volume, container.ID) } } }() // 1. Read already configured mount points. for name, point := range container.MountPoints { mountPoints[name] = point } // 2. Read volumes from other containers. for _, v := range hostConfig.VolumesFrom { containerID, mode, err := volume.ParseVolumesFrom(v) if err != nil { return err } c, err := daemon.GetContainer(containerID) if err != nil { return err } for _, m := range c.MountPoints { cp := &volume.MountPoint{ Name: m.Name, Source: m.Source, RW: m.RW && volume.ReadWrite(mode), Driver: m.Driver, Destination: m.Destination, Propagation: m.Propagation, Named: m.Named, } if len(cp.Source) == 0 { v, err := daemon.volumes.GetWithRef(cp.Name, cp.Driver, container.ID) if err != nil { return err } cp.Volume = v } mountPoints[cp.Destination] = cp } } // 3. Read bind mounts for _, b := range hostConfig.Binds { // #10618 bind, err := volume.ParseMountSpec(b, hostConfig.VolumeDriver) if err != nil { return err } _, tmpfsExists := hostConfig.Tmpfs[bind.Destination] if binds[bind.Destination] || tmpfsExists { return fmt.Errorf("Duplicate mount point '%s'", bind.Destination) } if len(bind.Name) > 0 { // create the volume v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil, nil) if err != nil { return err } bind.Volume = v bind.Source = v.Path() // bind.Name is an already existing volume, we need to use that here bind.Driver = v.DriverName() bind.Named = true if bind.Driver == "local" { bind = setBindModeIfNull(bind) } } binds[bind.Destination] = true mountPoints[bind.Destination] = bind } container.Lock() // 4. Cleanup old volumes that are about to be reassigned. for _, m := range mountPoints { if m.BackwardsCompatible() { if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil { daemon.volumes.Dereference(mp.Volume, container.ID) } } } container.MountPoints = mountPoints container.Unlock() return nil }
// 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, resetRestartManager bool) (err error) { 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 fmt.Errorf("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) } container.ToDisk() 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 } // Make sure NetworkMode has an acceptable value. We do this to ensure // backwards API compatibility. container.HostConfig = runconfig.SetDefaultNetModeIfBlank(container.HostConfig) if err := daemon.initializeNetworking(container); err != nil { return err } spec, err := daemon.createSpec(container) if err != nil { return err } createOptions, err := daemon.getLibcontainerdCreateOptions(container) if err != nil { return err } if resetRestartManager { container.ResetRestartManager(true) } if err := daemon.containerd.Create(container.ID, checkpoint, container.CheckpointDir(), *spec, container.InitializeStdio, createOptions...); err != nil { errDesc := grpc.ErrorDesc(err) logrus.Errorf("Create container failed with error: %s", errDesc) // if we receive an internal error from the initial start of a container then lets // return it instead of entering the restart loop // set to 127 for container cmd not found/does not exist) if strings.Contains(errDesc, container.Path) && (strings.Contains(errDesc, "executable file not found") || strings.Contains(errDesc, "no such file or directory") || strings.Contains(errDesc, "system cannot find the file specified")) { container.SetExitCode(127) } // set to 126 for container cmd can't be invoked errors if strings.Contains(errDesc, syscall.EACCES.Error()) { container.SetExitCode(126) } // attempted to mount a file onto a directory, or a directory onto a file, maybe from user specified bind mounts if strings.Contains(errDesc, syscall.ENOTDIR.Error()) { errDesc += ": Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type" container.SetExitCode(127) } container.Reset(false) return fmt.Errorf("%s", errDesc) } return nil }
// registerMountPoints initializes the container mount points with the configured volumes and bind mounts. // It follows the next sequence to decide what to mount in each final destination: // // 1. Select the previously configured mount points for the containers, if any. // 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination. // 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations. // 4. Cleanup old volumes that are about to be reassigned. func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) { binds := map[string]bool{} mountPoints := map[string]*volume.MountPoint{} defer func() { // clean up the container mountpoints once return with error if retErr != nil { for _, m := range mountPoints { if m.Volume == nil { continue } daemon.volumes.Dereference(m.Volume, container.ID) } } }() // 1. Read already configured mount points. for destination, point := range container.MountPoints { mountPoints[destination] = point } // 2. Read volumes from other containers. for _, v := range hostConfig.VolumesFrom { containerID, mode, err := volume.ParseVolumesFrom(v) if err != nil { return err } c, err := daemon.GetContainer(containerID) if err != nil { return err } for _, m := range c.MountPoints { cp := &volume.MountPoint{ Name: m.Name, Source: m.Source, RW: m.RW && volume.ReadWrite(mode), Driver: m.Driver, Destination: m.Destination, Propagation: m.Propagation, Spec: m.Spec, CopyData: false, } if len(cp.Source) == 0 { v, err := daemon.volumes.GetWithRef(cp.Name, cp.Driver, container.ID) if err != nil { return err } cp.Volume = v } mountPoints[cp.Destination] = cp } } // 3. Read bind mounts for _, b := range hostConfig.Binds { bind, err := volume.ParseMountRaw(b, hostConfig.VolumeDriver) if err != nil { return err } // #10618 _, tmpfsExists := hostConfig.Tmpfs[bind.Destination] if binds[bind.Destination] || tmpfsExists { return fmt.Errorf("Duplicate mount point '%s'", bind.Destination) } if bind.Type == mounttypes.TypeVolume { // create the volume v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil, nil) if err != nil { return err } bind.Volume = v bind.Source = v.Path() // bind.Name is an already existing volume, we need to use that here bind.Driver = v.DriverName() if bind.Driver == volume.DefaultDriverName { setBindModeIfNull(bind) } } binds[bind.Destination] = true mountPoints[bind.Destination] = bind } for _, cfg := range hostConfig.Mounts { mp, err := volume.ParseMountSpec(cfg) if err != nil { return dockererrors.NewBadRequestError(err) } if binds[mp.Destination] { return fmt.Errorf("Duplicate mount point '%s'", cfg.Target) } if mp.Type == mounttypes.TypeVolume { var v volume.Volume if cfg.VolumeOptions != nil { var driverOpts map[string]string if cfg.VolumeOptions.DriverConfig != nil { driverOpts = cfg.VolumeOptions.DriverConfig.Options } v, err = daemon.volumes.CreateWithRef(mp.Name, mp.Driver, container.ID, driverOpts, cfg.VolumeOptions.Labels) } else { v, err = daemon.volumes.CreateWithRef(mp.Name, mp.Driver, container.ID, nil, nil) } if err != nil { return err } if err := label.Relabel(mp.Source, container.MountLabel, false); err != nil { return err } mp.Volume = v mp.Name = v.Name() mp.Driver = v.DriverName() // only use the cached path here since getting the path is not necessary right now and calling `Path()` may be slow if cv, ok := v.(interface { CachedPath() string }); ok { mp.Source = cv.CachedPath() } } binds[mp.Destination] = true mountPoints[mp.Destination] = mp } container.Lock() // 4. Cleanup old volumes that are about to be reassigned. for _, m := range mountPoints { if m.BackwardsCompatible() { if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil { daemon.volumes.Dereference(mp.Volume, container.ID) } } } container.MountPoints = mountPoints container.Unlock() return nil }
func (daemon *Daemon) setSecurityOptions(container *container.Container, hostConfig *containertypes.HostConfig) error { container.Lock() defer container.Unlock() return parseSecurityOpt(container, hostConfig) }
// registerMountPoints initializes the container mount points with the configured volumes and bind mounts. // It follows the next sequence to decide what to mount in each final destination: // // 1. Select the previously configured mount points for the containers, if any. // 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination. // 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations. // 4. Cleanup old volumes that are about to be reassigned. func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) error { binds := map[string]bool{} mountPoints := map[string]*volume.MountPoint{} // 1. Read already configured mount points. for name, point := range container.MountPoints { mountPoints[name] = point } // 2. Read volumes from other containers. for _, v := range hostConfig.VolumesFrom { containerID, mode, err := volume.ParseVolumesFrom(v) if err != nil { return err } c, err := daemon.GetContainer(containerID) if err != nil { return err } for _, m := range c.MountPoints { cp := &volume.MountPoint{ Name: m.Name, Source: m.Source, RW: m.RW && volume.ReadWrite(mode), Driver: m.Driver, Destination: m.Destination, Propagation: m.Propagation, } if len(cp.Source) == 0 { v, err := daemon.createVolume(cp.Name, cp.Driver, nil) if err != nil { return err } cp.Volume = v } mountPoints[cp.Destination] = cp } } // 3. Read bind mounts for _, b := range hostConfig.Binds { // #10618 bind, err := volume.ParseMountSpec(b, hostConfig.VolumeDriver) if err != nil { return err } if binds[bind.Destination] { return derr.ErrorCodeMountDup.WithArgs(bind.Destination) } if len(bind.Name) > 0 && len(bind.Driver) > 0 { // create the volume v, err := daemon.createVolume(bind.Name, bind.Driver, nil) if err != nil { return err } bind.Volume = v bind.Source = v.Path() // bind.Name is an already existing volume, we need to use that here bind.Driver = v.DriverName() bind = setBindModeIfNull(bind) } if label.RelabelNeeded(bind.Mode) { if err := label.Relabel(bind.Source, container.MountLabel, label.IsShared(bind.Mode)); err != nil { return err } } binds[bind.Destination] = true mountPoints[bind.Destination] = bind } container.Lock() // 4. Cleanup old volumes that are about to be reassigned. for _, m := range mountPoints { if m.BackwardsCompatible() { if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil { daemon.volumes.Decrement(mp.Volume) } } } container.MountPoints = mountPoints container.Unlock() return nil }
// 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) (err error) { container.Lock() defer container.Unlock() if container.Running { return nil } if container.RemovalInProgress || container.Dead { return fmt.Errorf("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.ExitCode = 128 } container.ToDisk() daemon.Cleanup(container) } }() if err := daemon.conditionalMountOnStart(container); err != nil { return err } // Make sure NetworkMode has an acceptable value. We do this to ensure // backwards API compatibility. container.HostConfig = runconfig.SetDefaultNetModeIfBlank(container.HostConfig) if err := daemon.initializeNetworking(container); err != nil { return err } spec, err := daemon.createSpec(container) if err != nil { return err } if err := daemon.containerd.Create(container.ID, *spec, libcontainerd.WithRestartManager(container.RestartManager(true))); err != nil { errDesc := grpc.ErrorDesc(err) logrus.Errorf("Create container failed with error: %s", errDesc) // if we receive an internal error from the initial start of a container then lets // return it instead of entering the restart loop // set to 127 for container cmd not found/does not exist) if strings.Contains(errDesc, "executable file not found") || strings.Contains(errDesc, "no such file or directory") || strings.Contains(errDesc, "system cannot find the file specified") { container.ExitCode = 127 } // set to 126 for container cmd can't be invoked errors if strings.Contains(errDesc, syscall.EACCES.Error()) { container.ExitCode = 126 } container.Reset(false) return fmt.Errorf("%s", errDesc) } return nil }
// containerArchivePath creates an archive of the filesystem resource at the specified // path in this container. Returns a tar archive of the resource and stat info // about the resource. func (daemon *Daemon) containerArchivePath(container *container.Container, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) { container.Lock() defer func() { if err != nil { // Wait to unlock the container until the archive is fully read // (see the ReadCloseWrapper func below) or if there is an error // before that occurs. container.Unlock() } }() if err = daemon.Mount(container); err != nil { return nil, nil, err } defer func() { if err != nil { // unmount any volumes container.UnmountVolumes(true, daemon.LogVolumeEvent) // unmount the container's rootfs daemon.Unmount(container) } }() if err = daemon.mountVolumes(container); err != nil { return nil, nil, err } resolvedPath, absPath, err := container.ResolvePath(path) if err != nil { return nil, nil, err } stat, err = container.StatPath(resolvedPath, absPath) if err != nil { return nil, nil, err } // We need to rebase the archive entries if the last element of the // resolved path was a symlink that was evaluated and is now different // than the requested path. For example, if the given path was "/foo/bar/", // but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want // to ensure that the archive entries start with "bar" and not "baz". This // also catches the case when the root directory of the container is // requested: we want the archive entries to start with "/" and not the // container ID. data, err := archive.TarResourceRebase(resolvedPath, filepath.Base(absPath)) if err != nil { return nil, nil, err } content = ioutils.NewReadCloserWrapper(data, func() error { err := data.Close() container.UnmountVolumes(true, daemon.LogVolumeEvent) daemon.Unmount(container) container.Unlock() return err }) daemon.LogContainerEvent(container, "archive-path") return content, stat, nil }
// containerExtractToDir extracts the given tar archive to the specified location in the // filesystem of this container. The given path must be of a directory in the // container. If it is not, the error will be ErrExtractPointNotDirectory. If // noOverwriteDirNonDir is true then it will be an error if unpacking the // given content would cause an existing directory to be replaced with a non- // directory and vice versa. func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, noOverwriteDirNonDir bool, content io.Reader) (err error) { container.Lock() defer container.Unlock() if err = daemon.Mount(container); err != nil { return err } defer daemon.Unmount(container) err = daemon.mountVolumes(container) defer container.UnmountVolumes(true, daemon.LogVolumeEvent) if err != nil { return err } // The destination path needs to be resolved to a host path, with all // symbolic links followed in the scope of the container's rootfs. Note // that we do not use `container.ResolvePath(path)` here because we need // to also evaluate the last path element if it is a symlink. This is so // that you can extract an archive to a symlink that points to a directory. // Consider the given path as an absolute path in the container. absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path) // This will evaluate the last path element if it is a symlink. resolvedPath, err := container.GetResourcePath(absPath) if err != nil { return err } stat, err := os.Lstat(resolvedPath) if err != nil { return err } if !stat.IsDir() { return ErrExtractPointNotDirectory } // Need to check if the path is in a volume. If it is, it cannot be in a // read-only volume. If it is not in a volume, the container cannot be // configured with a read-only rootfs. // Use the resolved path relative to the container rootfs as the new // absPath. This way we fully follow any symlinks in a volume that may // lead back outside the volume. // // The Windows implementation of filepath.Rel in golang 1.4 does not // support volume style file path semantics. On Windows when using the // filter driver, we are guaranteed that the path will always be // a volume file path. var baseRel string if strings.HasPrefix(resolvedPath, `\\?\Volume{`) { if strings.HasPrefix(resolvedPath, container.BaseFS) { baseRel = resolvedPath[len(container.BaseFS):] if baseRel[:1] == `\` { baseRel = baseRel[1:] } } } else { baseRel, err = filepath.Rel(container.BaseFS, resolvedPath) } if err != nil { return err } // Make it an absolute path. absPath = filepath.Join(string(filepath.Separator), baseRel) toVolume, err := checkIfPathIsInAVolume(container, absPath) if err != nil { return err } if !toVolume && container.HostConfig.ReadonlyRootfs { return ErrRootFSReadOnly } options := &archive.TarOptions{ ChownOpts: &archive.TarChownOptions{ UID: 0, GID: 0, // TODO: use config.User? Remap to userns root? }, NoOverwriteDirNonDir: noOverwriteDirNonDir, } if err := chrootarchive.Untar(content, resolvedPath, options); err != nil { return err } daemon.LogContainerEvent(container, "extract-to-dir") return nil }
// 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) (err error) { //这里的锁是干什么的? container.Lock() defer container.Unlock() if container.Running { return nil } if container.RemovalInProgress || container.Dead { return fmt.Errorf("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.ExitCode = 128 } container.ToDisk() daemon.Cleanup(container) attributes := map[string]string{ "exitCode": fmt.Sprintf("%d", container.ExitCode), } daemon.LogContainerEventWithAttributes(container, "die", attributes) } }() //挂载容器的文件系统。会调用daemon/daemon.go中的Mount()方法。 //不过那个方法比较奇怪,感觉正常情况下并不会做什么事情。 if err := daemon.conditionalMountOnStart(container); err != nil { return err } // Make sure NetworkMode has an acceptable value. We do this to ensure // backwards API compatibility. //设置默认的网络模式。 container.HostConfig = runconfig.SetDefaultNetModeIfBlank(container.HostConfig) /* initializeNetworking() 对网络进行初始化,docker网络模式有三种,分别是 bridge模式 (每个容器用户单独的网络栈),host模式(与宿主机共用一个网络栈),contaier模式 (与其他容器共用一个网络栈,猜测kubernate中的pod所用的模式); 根据config和hostConfig中的参数来确定容器的网络模式,然后调动libnetwork包来建立网络 */ if err := daemon.initializeNetworking(container); err != nil { return err } //创建容器关于namespace和cgroup等的运行环境。在daemon/oci_linux.go中。 //Spec中包括了容器的最基本的信息。 spec, err := daemon.createSpec(container) if err != nil { return err } //运行容器,详细请见libcontainerd/client_linux.go中Create()方法。 if err := daemon.containerd.Create(container.ID, *spec, libcontainerd.WithRestartManager(container.RestartManager(true))); 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 // set to 127 for container cmd not found/does not exist) if strings.Contains(err.Error(), "executable file not found") || strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "system cannot find the file specified") { container.ExitCode = 127 err = fmt.Errorf("Container command '%s' not found or does not exist.", container.Path) } // set to 126 for container cmd can't be invoked errors if strings.Contains(err.Error(), syscall.EACCES.Error()) { container.ExitCode = 126 err = fmt.Errorf("Container command '%s' could not be invoked.", container.Path) } container.Reset(false) // start event is logged even on error daemon.LogContainerEvent(container, "start") return err } return nil }
func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) { container.Lock() defer func() { if err != nil { // Wait to unlock the container until the archive is fully read // (see the ReadCloseWrapper func below) or if there is an error // before that occurs. container.Unlock() } }() if err := daemon.Mount(container); err != nil { return nil, err } defer func() { if err != nil { // unmount any volumes container.UnmountVolumes(true, daemon.LogVolumeEvent) // unmount the container's rootfs daemon.Unmount(container) } }() if err := daemon.mountVolumes(container); err != nil { return nil, err } basePath, err := container.GetResourcePath(resource) if err != nil { return nil, err } stat, err := os.Stat(basePath) if err != nil { return nil, err } var filter []string if !stat.IsDir() { d, f := filepath.Split(basePath) basePath = d filter = []string{f} } else { filter = []string{filepath.Base(basePath)} basePath = filepath.Dir(basePath) } archive, err := archive.TarWithOptions(basePath, &archive.TarOptions{ Compression: archive.Uncompressed, IncludeFiles: filter, }) if err != nil { return nil, err } reader := ioutils.NewReadCloserWrapper(archive, func() error { err := archive.Close() container.UnmountVolumes(true, daemon.LogVolumeEvent) daemon.Unmount(container) container.Unlock() return err }) daemon.LogContainerEvent(container, "copy") return reader, nil }