123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- package daemon // import "github.com/docker/docker/daemon"
- import (
- "context"
- "strconv"
- "time"
- "github.com/containerd/log"
- "github.com/docker/docker/api/types/backend"
- "github.com/docker/docker/api/types/events"
- "github.com/docker/docker/container"
- "github.com/docker/docker/daemon/config"
- "github.com/docker/docker/errdefs"
- libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
- "github.com/docker/docker/restartmanager"
- "github.com/pkg/errors"
- )
- func (daemon *Daemon) setStateCounter(c *container.Container) {
- switch c.StateString() {
- case "paused":
- stateCtr.set(c.ID, "paused")
- case "running":
- stateCtr.set(c.ID, "running")
- default:
- stateCtr.set(c.ID, "stopped")
- }
- }
- func (daemon *Daemon) handleContainerExit(c *container.Container, e *libcontainerdtypes.EventInfo) error {
- var exitStatus container.ExitStatus
- c.Lock()
- cfg := daemon.config()
- // Health checks will be automatically restarted if/when the
- // container is started again.
- daemon.stopHealthchecks(c)
- tsk, ok := c.Task()
- if ok {
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- es, err := tsk.Delete(ctx)
- cancel()
- if err != nil {
- log.G(ctx).WithFields(log.Fields{
- "error": err,
- "container": c.ID,
- }).Warn("failed to delete container from containerd")
- } else {
- exitStatus = container.ExitStatus{
- ExitCode: int(es.ExitCode()),
- ExitedAt: es.ExitTime(),
- }
- }
- }
- ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
- c.StreamConfig.Wait(ctx)
- cancel()
- c.Reset(false)
- if e != nil {
- exitStatus.ExitCode = int(e.ExitCode)
- exitStatus.ExitedAt = e.ExitedAt
- if e.Error != nil {
- c.SetError(e.Error)
- }
- }
- daemonShutdown := daemon.IsShuttingDown()
- execDuration := time.Since(c.StartedAt)
- restart, wait, err := c.RestartManager().ShouldRestart(uint32(exitStatus.ExitCode), daemonShutdown || c.HasBeenManuallyStopped, execDuration)
- if err != nil {
- log.G(ctx).WithFields(log.Fields{
- "error": err,
- "container": c.ID,
- "restartCount": c.RestartCount,
- "exitStatus": exitStatus,
- "daemonShuttingDown": daemonShutdown,
- "hasBeenManuallyStopped": c.HasBeenManuallyStopped,
- "execDuration": execDuration,
- }).Warn("ShouldRestart failed, container will not be restarted")
- restart = false
- }
- attributes := map[string]string{
- "exitCode": strconv.Itoa(exitStatus.ExitCode),
- "execDuration": strconv.Itoa(int(execDuration.Seconds())),
- }
- daemon.Cleanup(c)
- if restart {
- c.RestartCount++
- log.G(ctx).WithFields(log.Fields{
- "container": c.ID,
- "restartCount": c.RestartCount,
- "exitStatus": exitStatus,
- "manualRestart": c.HasBeenManuallyRestarted,
- }).Debug("Restarting container")
- c.SetRestarting(&exitStatus)
- } else {
- c.SetStopped(&exitStatus)
- if !c.HasBeenManuallyRestarted {
- defer daemon.autoRemove(&cfg.Config, c)
- }
- }
- defer c.Unlock() // needs to be called before autoRemove
- daemon.setStateCounter(c)
- checkpointErr := c.CheckpointTo(daemon.containersReplica)
- daemon.LogContainerEventWithAttributes(c, events.ActionDie, attributes)
- if restart {
- go func() {
- err := <-wait
- if err == nil {
- // daemon.netController is initialized when daemon is restoring containers.
- // But containerStart will use daemon.netController segment.
- // So to avoid panic at startup process, here must wait util daemon restore done.
- daemon.waitForStartupDone()
- cfg := daemon.config() // Apply the most up-to-date daemon config to the restarted container.
- // update the error if we fail to start the container, so that the cleanup code
- // below can handle updating the container's status, and auto-remove (if set).
- err = daemon.containerStart(context.Background(), cfg, c, "", "", false)
- if err != nil {
- log.G(ctx).Debugf("failed to restart container: %+v", err)
- }
- }
- if err != nil {
- c.Lock()
- c.SetStopped(&exitStatus)
- daemon.setStateCounter(c)
- c.CheckpointTo(daemon.containersReplica)
- c.Unlock()
- defer daemon.autoRemove(&cfg.Config, c)
- if err != restartmanager.ErrRestartCanceled {
- log.G(ctx).Errorf("restartmanger wait error: %+v", err)
- }
- }
- }()
- }
- return checkpointErr
- }
- // ProcessEvent is called by libcontainerd whenever an event occurs
- func (daemon *Daemon) ProcessEvent(id string, e libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
- c, err := daemon.GetContainer(id)
- if err != nil {
- return errors.Wrapf(err, "could not find container %s", id)
- }
- switch e {
- case libcontainerdtypes.EventOOM:
- // StateOOM is Linux specific and should never be hit on Windows
- if isWindows {
- return errors.New("received StateOOM from libcontainerd on Windows. This should never happen")
- }
- c.Lock()
- defer c.Unlock()
- c.OOMKilled = true
- daemon.updateHealthMonitor(c)
- if err := c.CheckpointTo(daemon.containersReplica); err != nil {
- return err
- }
- daemon.LogContainerEvent(c, events.ActionOOM)
- case libcontainerdtypes.EventExit:
- if ei.ProcessID == ei.ContainerID {
- return daemon.handleContainerExit(c, &ei)
- }
- exitCode := 127
- if execConfig := c.ExecCommands.Get(ei.ProcessID); execConfig != nil {
- ec := int(ei.ExitCode)
- execConfig.Lock()
- defer execConfig.Unlock()
- // Remove the exec command from the container's store only and not the
- // daemon's store so that the exec command can be inspected. Remove it
- // before mutating execConfig to maintain the invariant that
- // c.ExecCommands only contains execs that have not exited.
- c.ExecCommands.Delete(execConfig.ID)
- execConfig.ExitCode = &ec
- execConfig.Running = false
- ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
- execConfig.StreamConfig.Wait(ctx)
- cancel()
- if err := execConfig.CloseStreams(); err != nil {
- log.G(ctx).Errorf("failed to cleanup exec %s streams: %s", c.ID, err)
- }
- exitCode = ec
- // If the exec failed at start in such a way that containerd
- // publishes an exit event for it, we will race processing the event
- // with daemon.ContainerExecStart() removing the exec from
- // c.ExecCommands. If we win the race, we will find that there is no
- // process to clean up. (And ContainerExecStart will clobber the
- // exit code we set.) Prevent a nil-dereferenc panic in that
- // situation to restore the status quo where this is merely a
- // logical race condition.
- if execConfig.Process != nil {
- go func() {
- if _, err := execConfig.Process.Delete(context.Background()); err != nil {
- log.G(ctx).WithFields(log.Fields{
- "error": err,
- "container": ei.ContainerID,
- "process": ei.ProcessID,
- }).Warn("failed to delete process")
- }
- }()
- }
- }
- daemon.LogContainerEventWithAttributes(c, events.ActionExecDie, map[string]string{
- "execID": ei.ProcessID,
- "exitCode": strconv.Itoa(exitCode),
- })
- case libcontainerdtypes.EventStart:
- c.Lock()
- defer c.Unlock()
- // This is here to handle start not generated by docker
- if !c.Running {
- ctr, err := daemon.containerd.LoadContainer(context.Background(), c.ID)
- if err != nil {
- if errdefs.IsNotFound(err) {
- // The container was started by not-docker and so could have been deleted by
- // not-docker before we got around to loading it from containerd.
- log.G(context.TODO()).WithFields(log.Fields{
- "error": err,
- "container": c.ID,
- }).Debug("could not load containerd container for start event")
- return nil
- }
- return err
- }
- tsk, err := ctr.Task(context.Background())
- if err != nil {
- if errdefs.IsNotFound(err) {
- log.G(context.TODO()).WithFields(log.Fields{
- "error": err,
- "container": c.ID,
- }).Debug("failed to load task for externally-started container")
- return nil
- }
- return err
- }
- c.SetRunning(ctr, tsk, false)
- c.HasBeenManuallyStopped = false
- c.HasBeenStartedBefore = true
- daemon.setStateCounter(c)
- daemon.initHealthMonitor(c)
- if err := c.CheckpointTo(daemon.containersReplica); err != nil {
- return err
- }
- daemon.LogContainerEvent(c, events.ActionStart)
- }
- case libcontainerdtypes.EventPaused:
- c.Lock()
- defer c.Unlock()
- if !c.Paused {
- c.Paused = true
- daemon.setStateCounter(c)
- daemon.updateHealthMonitor(c)
- if err := c.CheckpointTo(daemon.containersReplica); err != nil {
- return err
- }
- daemon.LogContainerEvent(c, events.ActionPause)
- }
- case libcontainerdtypes.EventResumed:
- c.Lock()
- defer c.Unlock()
- if c.Paused {
- c.Paused = false
- daemon.setStateCounter(c)
- daemon.updateHealthMonitor(c)
- if err := c.CheckpointTo(daemon.containersReplica); err != nil {
- return err
- }
- daemon.LogContainerEvent(c, events.ActionUnPause)
- }
- }
- return nil
- }
- func (daemon *Daemon) autoRemove(cfg *config.Config, c *container.Container) {
- c.Lock()
- ar := c.HostConfig.AutoRemove
- c.Unlock()
- if !ar {
- return
- }
- err := daemon.containerRm(cfg, c.ID, &backend.ContainerRmConfig{ForceRemove: true, RemoveVolume: true})
- if err == nil {
- return
- }
- if c := daemon.containers.Get(c.ID); c == nil {
- return
- }
- log.G(context.TODO()).WithFields(log.Fields{"error": err, "container": c.ID}).Error("error removing container")
- }
|