Explorar el Código

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 hace 3 años
padre
commit
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
 	}
 
-	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 != "" {
 		valSeconds, err := strconv.Atoi(tmpSeconds)
 		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 {
 		return err
 	}
-	w.WriteHeader(http.StatusNoContent)
 
+	w.WriteHeader(http.StatusNoContent)
 	return nil
 }
 
@@ -278,7 +289,18 @@ func (s *containerRouter) postContainersRestart(ctx context.Context, w http.Resp
 		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 != "" {
 		valSeconds, err := strconv.Atoi(tmpSeconds)
 		if err != nil {
@@ -292,7 +314,6 @@ func (s *containerRouter) postContainersRestart(ctx context.Context, w http.Resp
 	}
 
 	w.WriteHeader(http.StatusNoContent)
-
 	return nil
 }
 

+ 12 - 1
api/swagger.yaml

@@ -6862,6 +6862,11 @@ paths:
           required: true
           description: "ID or name of the container"
           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"
           in: "query"
           description: "Number of seconds to wait before killing the container"
@@ -6891,6 +6896,11 @@ paths:
           required: true
           description: "ID or name of the container"
           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"
           in: "query"
           description: "Number of seconds to wait before killing the container"
@@ -6932,7 +6942,8 @@ paths:
           type: "string"
         - name: "signal"
           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"
           default: "SIGKILL"
       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.
 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
 	// 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"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/errdefs"
+	"github.com/moby/sys/signal"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
@@ -44,6 +45,13 @@ func (daemon *Daemon) containerStop(ctx context.Context, ctr *container.Containe
 		stopSignal  = ctr.StopSignal()
 		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 {
 		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
   used since API 1.40.
   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`,
   images returned will include `SharedSize`, which provides the size on disk shared
   with other images present on the system.