ソースを参照

API: add "signal" parameter to container stop and restart endpoints

Containers can have a default stop-signal (`--stop-signal` / `STOPSIGNAL`) and
timeout (`--stop-timeout`). It is currently not possible to update either of
these after the container is created (`docker update` does not allow updating
them), and while either of these can be overridden through some commands, we
currently do not have a command that can override *both*:

command         | stop-signal | stop-timeout | notes
----------------|-------------|--------------|----------------------------
docker kill     | yes         | DNA          | only sends a single signal
docker restart  | no          | yes          |
docker stop     | no          | yes          |

As a result, if a user wants to stop a container with a custom signal and
timeout, the only option is to do this manually:

    docker kill -s <custom signal> mycontainer
    # wait <desired timeout>
    # press ^C to cancel the graceful stop
    # forcibly kill the container
    docker kill mycontainer

This patch adds a new `signal` query parameter to the container "stop" and
"restart" endpoints. This parameter can be added as a new flag on the CLI,
which would allow stopping and restarting with a custom timeout and signal,
for example:

    docker stop --signal=SIGWINCH --time=120 mycontainer

    docker restart --signal=SIGWINCH --time=120 mycontainer

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Sebastiaan van Stijn 3 年 前
コミット
83a185897d

+ 25 - 4
api/server/router/container/container_routes.go

@@ -221,7 +221,18 @@ func (s *containerRouter) postContainersStop(ctx context.Context, w http.Respons
 		return err
 		return err
 	}
 	}
 
 
-	var options container.StopOptions
+	var (
+		options container.StopOptions
+		version = httputils.VersionFromContext(ctx)
+	)
+	if versions.GreaterThanOrEqualTo(version, "1.42") {
+		if sig := r.Form.Get("signal"); sig != "" {
+			if _, err := signal.ParseSignal(sig); err != nil {
+				return errdefs.InvalidParameter(err)
+			}
+			options.Signal = sig
+		}
+	}
 	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
 	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
 		valSeconds, err := strconv.Atoi(tmpSeconds)
 		valSeconds, err := strconv.Atoi(tmpSeconds)
 		if err != nil {
 		if err != nil {
@@ -233,8 +244,8 @@ func (s *containerRouter) postContainersStop(ctx context.Context, w http.Respons
 	if err := s.backend.ContainerStop(ctx, vars["name"], options); err != nil {
 	if err := s.backend.ContainerStop(ctx, vars["name"], options); err != nil {
 		return err
 		return err
 	}
 	}
-	w.WriteHeader(http.StatusNoContent)
 
 
+	w.WriteHeader(http.StatusNoContent)
 	return nil
 	return nil
 }
 }
 
 
@@ -278,7 +289,18 @@ func (s *containerRouter) postContainersRestart(ctx context.Context, w http.Resp
 		return err
 		return err
 	}
 	}
 
 
-	var options container.StopOptions
+	var (
+		options container.StopOptions
+		version = httputils.VersionFromContext(ctx)
+	)
+	if versions.GreaterThanOrEqualTo(version, "1.42") {
+		if sig := r.Form.Get("signal"); sig != "" {
+			if _, err := signal.ParseSignal(sig); err != nil {
+				return errdefs.InvalidParameter(err)
+			}
+			options.Signal = sig
+		}
+	}
 	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
 	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
 		valSeconds, err := strconv.Atoi(tmpSeconds)
 		valSeconds, err := strconv.Atoi(tmpSeconds)
 		if err != nil {
 		if err != nil {
@@ -292,7 +314,6 @@ func (s *containerRouter) postContainersRestart(ctx context.Context, w http.Resp
 	}
 	}
 
 
 	w.WriteHeader(http.StatusNoContent)
 	w.WriteHeader(http.StatusNoContent)
-
 	return nil
 	return nil
 }
 }
 
 

+ 12 - 1
api/swagger.yaml

@@ -6862,6 +6862,11 @@ paths:
           required: true
           required: true
           description: "ID or name of the container"
           description: "ID or name of the container"
           type: "string"
           type: "string"
+        - name: "signal"
+          in: "query"
+          description: |
+            Signal to send to the container as an integer or string (e.g. `SIGINT`).
+          type: "string"
         - name: "t"
         - name: "t"
           in: "query"
           in: "query"
           description: "Number of seconds to wait before killing the container"
           description: "Number of seconds to wait before killing the container"
@@ -6891,6 +6896,11 @@ paths:
           required: true
           required: true
           description: "ID or name of the container"
           description: "ID or name of the container"
           type: "string"
           type: "string"
+        - name: "signal"
+          in: "query"
+          description: |
+            Signal to send to the container as an integer or string (e.g. `SIGINT`).
+          type: "string"
         - name: "t"
         - name: "t"
           in: "query"
           in: "query"
           description: "Number of seconds to wait before killing the container"
           description: "Number of seconds to wait before killing the container"
@@ -6932,7 +6942,8 @@ paths:
           type: "string"
           type: "string"
         - name: "signal"
         - name: "signal"
           in: "query"
           in: "query"
-          description: "Signal to send to the container as an integer or string (e.g. `SIGINT`)"
+          description: |
+            Signal to send to the container as an integer or string (e.g. `SIGINT`).
           type: "string"
           type: "string"
           default: "SIGKILL"
           default: "SIGKILL"
       tags: ["Container"]
       tags: ["Container"]

+ 5 - 0
api/types/container/config.go

@@ -15,6 +15,11 @@ const MinimumDuration = 1 * time.Millisecond
 
 
 // StopOptions holds the options to stop or restart a container.
 // StopOptions holds the options to stop or restart a container.
 type StopOptions struct {
 type StopOptions struct {
+	// Signal (optional) is the signal to send to the container to (gracefully)
+	// stop it before forcibly terminating the container with SIGKILL after the
+	// timeout expires. If not value is set, the default (SIGTERM) is used.
+	Signal string `json:",omitempty"`
+
 	// Timeout (optional) is the timeout (in seconds) to wait for the container
 	// Timeout (optional) is the timeout (in seconds) to wait for the container
 	// to stop gracefully before forcibly terminating it with SIGKILL.
 	// to stop gracefully before forcibly terminating it with SIGKILL.
 	//
 	//

+ 8 - 0
daemon/stop.go

@@ -7,6 +7,7 @@ import (
 	containertypes "github.com/docker/docker/api/types/container"
 	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
+	"github.com/moby/sys/signal"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
@@ -44,6 +45,13 @@ func (daemon *Daemon) containerStop(ctx context.Context, ctr *container.Containe
 		stopSignal  = ctr.StopSignal()
 		stopSignal  = ctr.StopSignal()
 		stopTimeout = ctr.StopTimeout()
 		stopTimeout = ctr.StopTimeout()
 	)
 	)
+	if options.Signal != "" {
+		sig, err := signal.ParseSignal(options.Signal)
+		if err != nil {
+			return errdefs.InvalidParameter(err)
+		}
+		stopSignal = int(sig)
+	}
 	if options.Timeout != nil {
 	if options.Timeout != nil {
 		stopTimeout = *options.Timeout
 		stopTimeout = *options.Timeout
 	}
 	}

+ 3 - 0
docs/api/version-history.md

@@ -21,6 +21,9 @@ keywords: "API, Docker, rcli, REST, documentation"
   was introduced in API 1.31 as part of an experimental feature, and no longer
   was introduced in API 1.31 as part of an experimental feature, and no longer
   used since API 1.40.
   used since API 1.40.
   Use field `BuildCache` instead to track storage used by the builder component.
   Use field `BuildCache` instead to track storage used by the builder component.
+* `POST /containers/{id}/stop` and `POST /containers/{id}/restart` now accept a
+  `signal` query parameter, which allows overriding the container's default stop-
+  signal.
 * `GET /images/json` now accepts query parameter `shared-size`. When set `true`,
 * `GET /images/json` now accepts query parameter `shared-size`. When set `true`,
   images returned will include `SharedSize`, which provides the size on disk shared
   images returned will include `SharedSize`, which provides the size on disk shared
   with other images present on the system.
   with other images present on the system.