github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/daemon/start.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/containerd/log" 8 "github.com/docker/docker/api/types/backend" 9 "github.com/docker/docker/api/types/events" 10 "github.com/docker/docker/container" 11 "github.com/docker/docker/errdefs" 12 "github.com/docker/docker/internal/compatcontext" 13 "github.com/docker/docker/libcontainerd" 14 "github.com/pkg/errors" 15 ) 16 17 // validateState verifies if the container is in a non-conflicting state. 18 func validateState(ctr *container.Container) error { 19 ctr.Lock() 20 defer ctr.Unlock() 21 22 // Intentionally checking paused first, because a container can be 23 // BOTH running AND paused. To start a paused (but running) container, 24 // it must be thawed ("un-paused"). 25 if ctr.Paused { 26 return errdefs.Conflict(errors.New("cannot start a paused container, try unpause instead")) 27 } else if ctr.Running { 28 // This is not an actual error, but produces a 304 "not modified" 29 // when returned through the API to indicates the container is 30 // already in the desired state. It's implemented as an error 31 // to make the code calling this function terminate early (as 32 // no further processing is needed). 33 return errdefs.NotModified(errors.New("container is already running")) 34 } 35 if ctr.RemovalInProgress || ctr.Dead { 36 return errdefs.Conflict(errors.New("container is marked for removal and cannot be started")) 37 } 38 return nil 39 } 40 41 // ContainerStart starts a container. 42 func (daemon *Daemon) ContainerStart(ctx context.Context, name string, checkpoint string, checkpointDir string) error { 43 daemonCfg := daemon.config() 44 if checkpoint != "" && !daemonCfg.Experimental { 45 return errdefs.InvalidParameter(errors.New("checkpoint is only supported in experimental mode")) 46 } 47 48 ctr, err := daemon.GetContainer(name) 49 if err != nil { 50 return err 51 } 52 if err := validateState(ctr); err != nil { 53 return err 54 } 55 56 // check if hostConfig is in line with the current system settings. 57 // It may happen cgroups are unmounted or the like. 58 if _, err = daemon.verifyContainerSettings(daemonCfg, ctr.HostConfig, nil, false); err != nil { 59 return errdefs.InvalidParameter(err) 60 } 61 62 return daemon.containerStart(ctx, daemonCfg, ctr, checkpoint, checkpointDir, true) 63 } 64 65 // containerStart prepares the container to run by setting up everything the 66 // container needs, such as storage and networking, as well as links 67 // between containers. The container is left waiting for a signal to 68 // begin running. 69 func (daemon *Daemon) containerStart(ctx context.Context, daemonCfg *configStore, container *container.Container, checkpoint string, checkpointDir string, resetRestartManager bool) (retErr error) { 70 start := time.Now() 71 container.Lock() 72 defer container.Unlock() 73 74 if resetRestartManager && container.Running { // skip this check if already in restarting step and resetRestartManager==false 75 return nil 76 } 77 78 if container.RemovalInProgress || container.Dead { 79 return errdefs.Conflict(errors.New("container is marked for removal and cannot be started")) 80 } 81 82 if checkpointDir != "" { 83 // TODO(mlaventure): how would we support that? 84 return errdefs.Forbidden(errors.New("custom checkpointdir is not supported")) 85 } 86 87 // if we encounter an error during start we need to ensure that any other 88 // setup has been cleaned up properly 89 defer func() { 90 if retErr != nil { 91 container.SetError(retErr) 92 // if no one else has set it, make sure we don't leave it at zero 93 if container.ExitCode() == 0 { 94 container.SetExitCode(exitUnknown) 95 } 96 if err := container.CheckpointTo(daemon.containersReplica); err != nil { 97 log.G(ctx).Errorf("%s: failed saving state on start failure: %v", container.ID, err) 98 } 99 container.Reset(false) 100 101 daemon.Cleanup(compatcontext.WithoutCancel(ctx), container) 102 // if containers AutoRemove flag is set, remove it after clean up 103 if container.HostConfig.AutoRemove { 104 container.Unlock() 105 if err := daemon.containerRm(&daemonCfg.Config, container.ID, &backend.ContainerRmConfig{ForceRemove: true, RemoveVolume: true}); err != nil { 106 log.G(ctx).Errorf("can't remove container %s: %v", container.ID, err) 107 } 108 container.Lock() 109 } 110 } 111 }() 112 113 if err := daemon.conditionalMountOnStart(container); err != nil { 114 return err 115 } 116 117 if err := daemon.initializeNetworking(&daemonCfg.Config, container); err != nil { 118 return err 119 } 120 121 mnts, err := daemon.setupContainerDirs(container) 122 if err != nil { 123 return err 124 } 125 126 m, cleanup, err := daemon.setupMounts(ctx, container) 127 if err != nil { 128 return err 129 } 130 mnts = append(mnts, m...) 131 defer cleanup(compatcontext.WithoutCancel(ctx)) 132 133 spec, err := daemon.createSpec(ctx, daemonCfg, container, mnts) 134 if err != nil { 135 // Any error that occurs while creating the spec, even if it's the 136 // result of an invalid container config, must be considered a System 137 // error (internal server error), as it's not an error with the request 138 // to start the container. 139 // 140 // Invalid configuration in the config itself must be validated when 141 // creating the container (creating its config), but some errors are 142 // dependent on the current state, for example when starting a container 143 // that shares a namespace with another container, and that container 144 // is not running (or missing). 145 return errdefs.System(err) 146 } 147 148 if resetRestartManager { 149 container.ResetRestartManager(true) 150 container.HasBeenManuallyStopped = false 151 } 152 153 if err := daemon.saveAppArmorConfig(container); err != nil { 154 return err 155 } 156 157 if checkpoint != "" { 158 checkpointDir, err = getCheckpointDir(checkpointDir, checkpoint, container.Name, container.ID, container.CheckpointDir(), false) 159 if err != nil { 160 return err 161 } 162 } 163 164 shim, createOptions, err := daemon.getLibcontainerdCreateOptions(daemonCfg, container) 165 if err != nil { 166 return err 167 } 168 169 ctr, err := libcontainerd.ReplaceContainer(ctx, daemon.containerd, container.ID, spec, shim, createOptions) 170 if err != nil { 171 return setExitCodeFromError(container.SetExitCode, err) 172 } 173 defer func() { 174 if retErr != nil { 175 if err := ctr.Delete(compatcontext.WithoutCancel(ctx)); err != nil { 176 log.G(ctx).WithError(err).WithField("container", container.ID). 177 Error("failed to delete failed start container") 178 } 179 } 180 }() 181 182 // TODO(mlaventure): we need to specify checkpoint options here 183 tsk, err := ctr.NewTask(context.TODO(), // Passing ctx caused integration tests to be stuck in the cleanup phase 184 checkpointDir, container.StreamConfig.Stdin() != nil || container.Config.Tty, 185 container.InitializeStdio) 186 if err != nil { 187 return setExitCodeFromError(container.SetExitCode, err) 188 } 189 defer func() { 190 if retErr != nil { 191 if err := tsk.ForceDelete(compatcontext.WithoutCancel(ctx)); err != nil { 192 log.G(ctx).WithError(err).WithField("container", container.ID). 193 Error("failed to delete task after fail start") 194 } 195 } 196 }() 197 198 if err := daemon.initializeCreatedTask(ctx, tsk, container, spec); err != nil { 199 return err 200 } 201 202 if err := tsk.Start(context.TODO()); err != nil { // passing ctx caused integration tests to be stuck in the cleanup phase 203 return setExitCodeFromError(container.SetExitCode, err) 204 } 205 206 container.HasBeenManuallyRestarted = false 207 container.SetRunning(ctr, tsk, true) 208 container.HasBeenStartedBefore = true 209 daemon.setStateCounter(container) 210 211 daemon.initHealthMonitor(container) 212 213 if err := container.CheckpointTo(daemon.containersReplica); err != nil { 214 log.G(ctx).WithError(err).WithField("container", container.ID). 215 Errorf("failed to store container") 216 } 217 218 daemon.LogContainerEvent(container, events.ActionStart) 219 containerActions.WithValues("start").UpdateSince(start) 220 221 return nil 222 } 223 224 // Cleanup releases any network resources allocated to the container along with any rules 225 // around how containers are linked together. It also unmounts the container's root filesystem. 226 func (daemon *Daemon) Cleanup(ctx context.Context, container *container.Container) { 227 // Microsoft HCS containers get in a bad state if host resources are 228 // released while the container still exists. 229 if ctr, ok := container.C8dContainer(); ok { 230 if err := ctr.Delete(context.Background()); err != nil { 231 log.G(ctx).Errorf("%s cleanup: failed to delete container from containerd: %v", container.ID, err) 232 } 233 } 234 235 daemon.releaseNetwork(container) 236 237 if err := container.UnmountIpcMount(); err != nil { 238 log.G(ctx).Warnf("%s cleanup: failed to unmount IPC: %s", container.ID, err) 239 } 240 241 if err := daemon.conditionalUnmountOnCleanup(container); err != nil { 242 // FIXME: remove once reference counting for graphdrivers has been refactored 243 // Ensure that all the mounts are gone 244 if mountid, err := daemon.imageService.GetLayerMountID(container.ID); err == nil { 245 daemon.cleanupMountsByID(mountid) 246 } 247 } 248 249 if err := container.UnmountSecrets(); err != nil { 250 log.G(ctx).Warnf("%s cleanup: failed to unmount secrets: %s", container.ID, err) 251 } 252 253 if err := recursiveUnmount(container.Root); err != nil { 254 log.G(ctx).WithError(err).WithField("container", container.ID).Warn("Error while cleaning up container resource mounts.") 255 } 256 257 for _, eConfig := range container.ExecCommands.Commands() { 258 daemon.unregisterExecCommand(container, eConfig) 259 } 260 261 if container.BaseFS != "" { 262 if err := container.UnmountVolumes(ctx, daemon.LogVolumeEvent); err != nil { 263 log.G(ctx).Warnf("%s cleanup: Failed to umount volumes: %v", container.ID, err) 264 } 265 } 266 267 container.CancelAttachContext() 268 }