Переглянути джерело

Update `docker stop` and `docker restart` to allow not specifying timeout and use the one specified at container creation time.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
Yong Tang 9 роки тому
батько
коміт
cc703784f3

+ 2 - 2
api/server/router/container/backend.go

@@ -37,10 +37,10 @@ type stateBackend interface {
 	ContainerPause(name string) error
 	ContainerPause(name string) error
 	ContainerRename(oldName, newName string) error
 	ContainerRename(oldName, newName string) error
 	ContainerResize(name string, height, width int) error
 	ContainerResize(name string, height, width int) error
-	ContainerRestart(name string, seconds int) error
+	ContainerRestart(name string, seconds *int) error
 	ContainerRm(name string, config *types.ContainerRmConfig) error
 	ContainerRm(name string, config *types.ContainerRmConfig) error
 	ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool, checkpoint string) error
 	ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool, checkpoint string) error
-	ContainerStop(name string, seconds int) error
+	ContainerStop(name string, seconds *int) error
 	ContainerUnpause(name string) error
 	ContainerUnpause(name string) error
 	ContainerUpdate(name string, hostConfig *container.HostConfig, validateHostname bool) (types.ContainerUpdateResponse, error)
 	ContainerUpdate(name string, hostConfig *container.HostConfig, validateHostname bool) (types.ContainerUpdateResponse, error)
 	ContainerWait(name string, timeout time.Duration) (int, error)
 	ContainerWait(name string, timeout time.Duration) (int, error)

+ 17 - 3
api/server/router/container/container_routes.go

@@ -169,7 +169,14 @@ func (s *containerRouter) postContainersStop(ctx context.Context, w http.Respons
 		return err
 		return err
 	}
 	}
 
 
-	seconds, _ := strconv.Atoi(r.Form.Get("t"))
+	var seconds *int
+	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
+		valSeconds, err := strconv.Atoi(tmpSeconds)
+		if err != nil {
+			return err
+		}
+		seconds = &valSeconds
+	}
 
 
 	if err := s.backend.ContainerStop(vars["name"], seconds); err != nil {
 	if err := s.backend.ContainerStop(vars["name"], seconds); err != nil {
 		return err
 		return err
@@ -223,9 +230,16 @@ func (s *containerRouter) postContainersRestart(ctx context.Context, w http.Resp
 		return err
 		return err
 	}
 	}
 
 
-	timeout, _ := strconv.Atoi(r.Form.Get("t"))
+	var seconds *int
+	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
+		valSeconds, err := strconv.Atoi(tmpSeconds)
+		if err != nil {
+			return err
+		}
+		seconds = &valSeconds
+	}
 
 
-	if err := s.backend.ContainerRestart(vars["name"], timeout); err != nil {
+	if err := s.backend.ContainerRestart(vars["name"], seconds); err != nil {
 		return err
 		return err
 	}
 	}
 
 

+ 10 - 3
cli/command/container/restart.go

@@ -13,7 +13,8 @@ import (
 )
 )
 
 
 type restartOptions struct {
 type restartOptions struct {
-	nSeconds int
+	nSeconds        int
+	nSecondsChanged bool
 
 
 	containers []string
 	containers []string
 }
 }
@@ -28,6 +29,7 @@ func NewRestartCommand(dockerCli *command.DockerCli) *cobra.Command {
 		Args:  cli.RequiresMinArgs(1),
 		Args:  cli.RequiresMinArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			opts.containers = args
 			opts.containers = args
+			opts.nSecondsChanged = cmd.Flags().Changed("time")
 			return runRestart(dockerCli, &opts)
 			return runRestart(dockerCli, &opts)
 		},
 		},
 	}
 	}
@@ -40,9 +42,14 @@ func NewRestartCommand(dockerCli *command.DockerCli) *cobra.Command {
 func runRestart(dockerCli *command.DockerCli, opts *restartOptions) error {
 func runRestart(dockerCli *command.DockerCli, opts *restartOptions) error {
 	ctx := context.Background()
 	ctx := context.Background()
 	var errs []string
 	var errs []string
+	var timeout *time.Duration
+	if opts.nSecondsChanged {
+		timeoutValue := time.Duration(opts.nSeconds) * time.Second
+		timeout = &timeoutValue
+	}
+
 	for _, name := range opts.containers {
 	for _, name := range opts.containers {
-		timeout := time.Duration(opts.nSeconds) * time.Second
-		if err := dockerCli.Client().ContainerRestart(ctx, name, &timeout); err != nil {
+		if err := dockerCli.Client().ContainerRestart(ctx, name, timeout); err != nil {
 			errs = append(errs, err.Error())
 			errs = append(errs, err.Error())
 		} else {
 		} else {
 			fmt.Fprintf(dockerCli.Out(), "%s\n", name)
 			fmt.Fprintf(dockerCli.Out(), "%s\n", name)

+ 10 - 3
cli/command/container/stop.go

@@ -13,7 +13,8 @@ import (
 )
 )
 
 
 type stopOptions struct {
 type stopOptions struct {
-	time int
+	time        int
+	timeChanged bool
 
 
 	containers []string
 	containers []string
 }
 }
@@ -28,6 +29,7 @@ func NewStopCommand(dockerCli *command.DockerCli) *cobra.Command {
 		Args:  cli.RequiresMinArgs(1),
 		Args:  cli.RequiresMinArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			opts.containers = args
 			opts.containers = args
+			opts.timeChanged = cmd.Flags().Changed("time")
 			return runStop(dockerCli, &opts)
 			return runStop(dockerCli, &opts)
 		},
 		},
 	}
 	}
@@ -39,12 +41,17 @@ func NewStopCommand(dockerCli *command.DockerCli) *cobra.Command {
 
 
 func runStop(dockerCli *command.DockerCli, opts *stopOptions) error {
 func runStop(dockerCli *command.DockerCli, opts *stopOptions) error {
 	ctx := context.Background()
 	ctx := context.Background()
-	timeout := time.Duration(opts.time) * time.Second
+
+	var timeout *time.Duration
+	if opts.timeChanged {
+		timeoutValue := time.Duration(opts.time) * time.Second
+		timeout = &timeoutValue
+	}
 
 
 	var errs []string
 	var errs []string
 
 
 	errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, id string) error {
 	errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, id string) error {
-		return dockerCli.Client().ContainerStop(ctx, id, &timeout)
+		return dockerCli.Client().ContainerStop(ctx, id, timeout)
 	})
 	})
 	for _, container := range opts.containers {
 	for _, container := range opts.containers {
 		if err := <-errChan; err != nil {
 		if err := <-errChan; err != nil {

+ 9 - 3
cmd/dockerd/daemon.go

@@ -296,7 +296,7 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
 	// Wait for serve API to complete
 	// Wait for serve API to complete
 	errAPI := <-serveAPIWait
 	errAPI := <-serveAPIWait
 	c.Cleanup()
 	c.Cleanup()
-	shutdownDaemon(d, 15)
+	shutdownDaemon(d)
 	containerdRemote.Cleanup()
 	containerdRemote.Cleanup()
 	if errAPI != nil {
 	if errAPI != nil {
 		return fmt.Errorf("Shutting down due to ServeAPI error: %v", errAPI)
 		return fmt.Errorf("Shutting down due to ServeAPI error: %v", errAPI)
@@ -342,16 +342,22 @@ func (cli *DaemonCli) stop() {
 // shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case
 // shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case
 // d.Shutdown() is waiting too long to kill container or worst it's
 // d.Shutdown() is waiting too long to kill container or worst it's
 // blocked there
 // blocked there
-func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) {
+func shutdownDaemon(d *daemon.Daemon) {
+	shutdownTimeout := d.ShutdownTimeout()
 	ch := make(chan struct{})
 	ch := make(chan struct{})
 	go func() {
 	go func() {
 		d.Shutdown()
 		d.Shutdown()
 		close(ch)
 		close(ch)
 	}()
 	}()
+	if shutdownTimeout < 0 {
+		<-ch
+		logrus.Debug("Clean shutdown succeeded")
+		return
+	}
 	select {
 	select {
 	case <-ch:
 	case <-ch:
 		logrus.Debug("Clean shutdown succeeded")
 		logrus.Debug("Clean shutdown succeeded")
-	case <-time.After(timeout * time.Second):
+	case <-time.After(time.Duration(shutdownTimeout) * time.Second):
 		logrus.Error("Force shutdown daemon")
 		logrus.Error("Force shutdown daemon")
 	}
 	}
 }
 }

+ 3 - 3
container/container.go

@@ -45,8 +45,8 @@ import (
 const configFileName = "config.v2.json"
 const configFileName = "config.v2.json"
 
 
 const (
 const (
-	// defaultStopTimeout is the timeout (in seconds) for the syscall signal used to stop a container.
-	defaultStopTimeout = 10
+	// DefaultStopTimeout is the timeout (in seconds) for the syscall signal used to stop a container.
+	DefaultStopTimeout = 10
 )
 )
 
 
 var (
 var (
@@ -588,7 +588,7 @@ func (container *Container) StopTimeout() int {
 	if container.Config.StopTimeout != nil {
 	if container.Config.StopTimeout != nil {
 		return *container.Config.StopTimeout
 		return *container.Config.StopTimeout
 	}
 	}
-	return defaultStopTimeout
+	return DefaultStopTimeout
 }
 }
 
 
 // InitDNSHostConfig ensures that the dns fields are never nil.
 // InitDNSHostConfig ensures that the dns fields are never nil.

+ 2 - 2
container/container_unit_test.go

@@ -43,8 +43,8 @@ func TestContainerStopTimeout(t *testing.T) {
 	}
 	}
 
 
 	s := c.StopTimeout()
 	s := c.StopTimeout()
-	if s != defaultStopTimeout {
-		t.Fatalf("Expected %v, got %v", defaultStopTimeout, s)
+	if s != DefaultStopTimeout {
+		t.Fatalf("Expected %v, got %v", DefaultStopTimeout, s)
 	}
 	}
 
 
 	stopTimeout := 15
 	stopTimeout := 15

+ 1 - 1
daemon/cluster/executor/backend.go

@@ -25,7 +25,7 @@ type Backend interface {
 	PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
 	PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
 	CreateManagedContainer(config types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error)
 	CreateManagedContainer(config types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error)
 	ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool, checkpoint string) error
 	ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool, checkpoint string) error
-	ContainerStop(name string, seconds int) error
+	ContainerStop(name string, seconds *int) error
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
 	UpdateContainerServiceConfig(containerName string, serviceConfig *clustertypes.ServiceConfig) error
 	UpdateContainerServiceConfig(containerName string, serviceConfig *clustertypes.ServiceConfig) error
 	ContainerInspectCurrent(name string, size bool) (*types.ContainerJSON, error)
 	ContainerInspectCurrent(name string, size bool) (*types.ContainerJSON, error)

+ 4 - 3
daemon/cluster/executor/container/adapter.go

@@ -279,11 +279,12 @@ func (c *containerAdapter) wait(ctx context.Context) error {
 }
 }
 
 
 func (c *containerAdapter) shutdown(ctx context.Context) error {
 func (c *containerAdapter) shutdown(ctx context.Context) error {
-	// Default stop grace period to 10s.
-	stopgrace := 10
+	// Default stop grace period to nil (daemon will use the stopTimeout of the container)
+	var stopgrace *int
 	spec := c.container.spec()
 	spec := c.container.spec()
 	if spec.StopGracePeriod != nil {
 	if spec.StopGracePeriod != nil {
-		stopgrace = int(spec.StopGracePeriod.Seconds)
+		stopgraceValue := int(spec.StopGracePeriod.Seconds)
+		stopgrace = &stopgraceValue
 	}
 	}
 	return c.backend.ContainerStop(c.container.name(), stopgrace)
 	return c.backend.ContainerStop(c.container.name(), stopgrace)
 }
 }

+ 29 - 4
daemon/daemon.go

@@ -692,6 +692,8 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
 }
 }
 
 
 func (daemon *Daemon) shutdownContainer(c *container.Container) error {
 func (daemon *Daemon) shutdownContainer(c *container.Container) error {
+	stopTimeout := c.StopTimeout()
+
 	// TODO(windows): Handle docker restart with paused containers
 	// TODO(windows): Handle docker restart with paused containers
 	if c.IsPaused() {
 	if c.IsPaused() {
 		// To terminate a process in freezer cgroup, we should send
 		// To terminate a process in freezer cgroup, we should send
@@ -708,8 +710,8 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
 		if err := daemon.containerUnpause(c); err != nil {
 		if err := daemon.containerUnpause(c); err != nil {
 			return fmt.Errorf("Failed to unpause container %s with error: %v", c.ID, err)
 			return fmt.Errorf("Failed to unpause container %s with error: %v", c.ID, err)
 		}
 		}
-		if _, err := c.WaitStop(time.Duration(c.StopTimeout()) * time.Second); err != nil {
-			logrus.Debugf("container %s failed to exit in %d second of SIGTERM, sending SIGKILL to force", c.ID, c.StopTimeout())
+		if _, err := c.WaitStop(time.Duration(stopTimeout) * time.Second); err != nil {
+			logrus.Debugf("container %s failed to exit in %d second of SIGTERM, sending SIGKILL to force", c.ID, stopTimeout)
 			sig, ok := signal.SignalMap["KILL"]
 			sig, ok := signal.SignalMap["KILL"]
 			if !ok {
 			if !ok {
 				return fmt.Errorf("System does not support SIGKILL")
 				return fmt.Errorf("System does not support SIGKILL")
@@ -721,8 +723,8 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
 			return err
 			return err
 		}
 		}
 	}
 	}
-	// If container failed to exit in c.StopTimeout() seconds of SIGTERM, then using the force
-	if err := daemon.containerStop(c, c.StopTimeout()); err != nil {
+	// If container failed to exit in stopTimeout seconds of SIGTERM, then using the force
+	if err := daemon.containerStop(c, stopTimeout); err != nil {
 		return fmt.Errorf("Failed to stop container %s with error: %v", c.ID, err)
 		return fmt.Errorf("Failed to stop container %s with error: %v", c.ID, err)
 	}
 	}
 
 
@@ -730,6 +732,29 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
 	return nil
 	return nil
 }
 }
 
 
+// ShutdownTimeout returns the shutdown timeout based on the max stopTimeout of the containers
+func (daemon *Daemon) ShutdownTimeout() int {
+	// By default we use container.DefaultStopTimeout + 5s, which is 15s.
+	// TODO (yongtang): Will need to allow shutdown-timeout once #23036 is in place.
+	graceTimeout := 5
+	shutdownTimeout := container.DefaultStopTimeout + graceTimeout
+	if daemon.containers != nil {
+		for _, c := range daemon.containers.List() {
+			if shutdownTimeout >= 0 {
+				stopTimeout := c.StopTimeout()
+				if stopTimeout < 0 {
+					shutdownTimeout = -1
+				} else {
+					if stopTimeout+graceTimeout > shutdownTimeout {
+						shutdownTimeout = stopTimeout + graceTimeout
+					}
+				}
+			}
+		}
+	}
+	return shutdownTimeout
+}
+
 // Shutdown stops the daemon.
 // Shutdown stops the daemon.
 func (daemon *Daemon) Shutdown() error {
 func (daemon *Daemon) Shutdown() error {
 	daemon.shutdown = true
 	daemon.shutdown = true

+ 7 - 2
daemon/restart.go

@@ -13,15 +13,20 @@ import (
 // timeout, ContainerRestart will wait forever until a graceful
 // timeout, ContainerRestart will wait forever until a graceful
 // stop. Returns an error if the container cannot be found, or if
 // stop. Returns an error if the container cannot be found, or if
 // there is an underlying error at any stage of the restart.
 // there is an underlying error at any stage of the restart.
-func (daemon *Daemon) ContainerRestart(name string, seconds int) error {
+func (daemon *Daemon) ContainerRestart(name string, seconds *int) error {
 	container, err := daemon.GetContainer(name)
 	container, err := daemon.GetContainer(name)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	if err := daemon.containerRestart(container, seconds); err != nil {
+	if seconds == nil {
+		stopTimeout := container.StopTimeout()
+		seconds = &stopTimeout
+	}
+	if err := daemon.containerRestart(container, *seconds); err != nil {
 		return fmt.Errorf("Cannot restart container %s: %v", name, err)
 		return fmt.Errorf("Cannot restart container %s: %v", name, err)
 	}
 	}
 	return nil
 	return nil
+
 }
 }
 
 
 // containerRestart attempts to gracefully stop and then start the
 // containerRestart attempts to gracefully stop and then start the

+ 6 - 2
daemon/stop.go

@@ -16,7 +16,7 @@ import (
 // will wait for a graceful termination. An error is returned if the
 // will wait for a graceful termination. An error is returned if the
 // container is not found, is already stopped, or if there is a
 // container is not found, is already stopped, or if there is a
 // problem stopping the container.
 // problem stopping the container.
-func (daemon *Daemon) ContainerStop(name string, seconds int) error {
+func (daemon *Daemon) ContainerStop(name string, seconds *int) error {
 	container, err := daemon.GetContainer(name)
 	container, err := daemon.GetContainer(name)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -25,7 +25,11 @@ func (daemon *Daemon) ContainerStop(name string, seconds int) error {
 		err := fmt.Errorf("Container %s is already stopped", name)
 		err := fmt.Errorf("Container %s is already stopped", name)
 		return errors.NewErrorWithStatusCode(err, http.StatusNotModified)
 		return errors.NewErrorWithStatusCode(err, http.StatusNotModified)
 	}
 	}
-	if err := daemon.containerStop(container, seconds); err != nil {
+	if seconds == nil {
+		stopTimeout := container.StopTimeout()
+		seconds = &stopTimeout
+	}
+	if err := daemon.containerStop(container, *seconds); err != nil {
 		return fmt.Errorf("Cannot stop container %s: %v", name, err)
 		return fmt.Errorf("Cannot stop container %s: %v", name, err)
 	}
 	}
 	return nil
 	return nil

+ 3 - 3
runconfig/opts/parse.go

@@ -94,6 +94,7 @@ type ContainerOptions struct {
 	cgroupParent      string
 	cgroupParent      string
 	volumeDriver      string
 	volumeDriver      string
 	stopSignal        string
 	stopSignal        string
+	stopTimeout       int
 	isolation         string
 	isolation         string
 	shmSize           string
 	shmSize           string
 	noHealthcheck     bool
 	noHealthcheck     bool
@@ -106,7 +107,6 @@ type ContainerOptions struct {
 	init              bool
 	init              bool
 	initPath          string
 	initPath          string
 	credentialSpec    string
 	credentialSpec    string
-	stopTimeout       int
 
 
 	Image string
 	Image string
 	Args  []string
 	Args  []string
@@ -146,7 +146,6 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
 		ulimits:           NewUlimitOpt(nil),
 		ulimits:           NewUlimitOpt(nil),
 		volumes:           opts.NewListOpts(nil),
 		volumes:           opts.NewListOpts(nil),
 		volumesFrom:       opts.NewListOpts(nil),
 		volumesFrom:       opts.NewListOpts(nil),
-		stopSignal:        flags.String("stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal)),
 	}
 	}
 
 
 	// General purpose flags
 	// General purpose flags
@@ -163,6 +162,7 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
 	flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only")
 	flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only")
 	flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits")
 	flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits")
 	flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
 	flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
+	flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container")
 	flags.Var(copts.sysctls, "sysctl", "Sysctl options")
 	flags.Var(copts.sysctls, "sysctl", "Sysctl options")
 	flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
 	flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
 	flags.Var(copts.ulimits, "ulimit", "Ulimit options")
 	flags.Var(copts.ulimits, "ulimit", "Ulimit options")
@@ -561,7 +561,7 @@ func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *c
 		config.StopSignal = copts.stopSignal
 		config.StopSignal = copts.stopSignal
 	}
 	}
 	if flags.Changed("stop-timeout") {
 	if flags.Changed("stop-timeout") {
-		config.StopTimeout = copts.flStopTimeout
+		config.StopTimeout = &copts.stopTimeout
 	}
 	}
 
 
 	hostConfig := &container.HostConfig{
 	hostConfig := &container.HostConfig{