From 0e50d946a25beb134bce2aaf4a209b5cfcbacf8f Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 4 Aug 2015 13:51:48 -0700 Subject: [PATCH 1/2] Signal to stop a container. Allow to set the signal to stop a container in `docker run`: - Use `--stop-signal` with docker-run to set the default signal the container will use to exit. Signed-off-by: David Calavera --- api/server/container.go | 25 +++++------------- contrib/completion/bash/docker | 1 + contrib/completion/fish/docker.fish | 1 + contrib/completion/zsh/_docker | 1 + daemon/container.go | 23 +++++++++++++---- daemon/container_unit_test.go | 28 +++++++++++++++++++++ daemon/daemon.go | 5 ++++ docs/reference/api/docker_remote_api.md | 1 + integration-cli/docker_cli_run_unix_test.go | 16 ++++++++++++ man/docker-create.1.md | 4 +++ man/docker-inspect.1.md | 3 ++- man/docker-run.1.md | 6 ++++- man/docker-stop.1.md | 2 +- pkg/signal/signal.go | 21 ++++++++++++++++ pkg/signal/signal_unix.go | 13 ++++++---- pkg/signal/signal_windows.go | 2 ++ runconfig/config.go | 1 + runconfig/parse.go | 3 +++ 18 files changed, 124 insertions(+), 32 deletions(-) diff --git a/api/server/container.go b/api/server/container.go index 3af7972674..87b1998541 100644 --- a/api/server/container.go +++ b/api/server/container.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" "strings" + "syscall" "time" "golang.org/x/net/websocket" @@ -220,32 +221,18 @@ func (s *Server) postContainersKill(ctx context.Context, w http.ResponseWriter, return err } - var sig uint64 + var sig syscall.Signal name := vars["name"] // If we have a signal, look at it. Otherwise, do nothing if sigStr := r.Form.Get("signal"); sigStr != "" { - // Check if we passed the signal as a number: - // The largest legal signal is 31, so let's parse on 5 bits - sigN, err := strconv.ParseUint(sigStr, 10, 5) - if err != nil { - // The signal is not a number, treat it as a string (either like - // "KILL" or like "SIGKILL") - syscallSig, ok := signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")] - if !ok { - return fmt.Errorf("Invalid signal: %s", sigStr) - } - sig = uint64(syscallSig) - } else { - sig = sigN - } - - if sig == 0 { - return fmt.Errorf("Invalid signal: %s", sigStr) + var err error + if sig, err = signal.ParseSignal(sigStr); err != nil { + return err } } - if err := s.daemon.ContainerKill(name, sig); err != nil { + if err := s.daemon.ContainerKill(name, uint64(sig)); err != nil { _, isStopped := err.(daemon.ErrContainerNotRunning) // Return error that's not caused because the container is stopped. // Return error if the container is not running and the api is >= 1.20 diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 4b8d9bd209..9d63416c4b 100644 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -1149,6 +1149,7 @@ _docker_run() { --publish -p --restart --security-opt + --stop-signal --ulimit --user -u --uts diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index 9a32f8c64e..7dcc554139 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -335,6 +335,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l restart -d 'Res complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l rm -d 'Automatically remove the container when it exits (incompatible with -d)' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l security-opt -d 'Security Options' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l sig-proxy -d 'Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.' +complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l stop-signal 'Signal to kill a container' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-TTY' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s v -l volume -d 'Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)' diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index fc894c84b5..448dac9d59 100644 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -502,6 +502,7 @@ __docker_subcommand() { "($help -d --detach)"{-d,--detach}"[Detached mode: leave the container running in the background]" \ "($help)--rm[Remove intermediate containers when it exits]" \ "($help)--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]" \ + "($help)--stop-signal[Signal to kill a container]" \ "($help -): :__docker_images" \ "($help -):command: _command_names -e" \ "($help -)*::arguments: _normal" && ret=0 diff --git a/daemon/container.go b/daemon/container.go index bd622b8780..52465be590 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -27,6 +27,7 @@ import ( "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/nat" "github.com/docker/docker/pkg/promise" + "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/runconfig" "github.com/docker/docker/volume" @@ -495,10 +496,10 @@ func (container *Container) Kill() error { return nil } -// Stop halts a container by sending SIGTERM, waiting for the given +// Stop halts a container by sending a stop signal, waiting for the given // duration in seconds, and then calling SIGKILL and waiting for the // process to exit. If a negative duration is given, Stop will wait -// for SIGTERM forever. If the container is not running Stop returns +// for the initial signal forever. If the container is not running Stop returns // immediately. func (container *Container) Stop(seconds int) error { if !container.IsRunning() { @@ -506,9 +507,9 @@ func (container *Container) Stop(seconds int) error { } // 1. Send a SIGTERM - if err := container.killPossiblyDeadProcess(int(syscall.SIGTERM)); err != nil { + if err := container.killPossiblyDeadProcess(container.stopSignal()); err != nil { logrus.Infof("Failed to send SIGTERM to the process, force killing") - if err := container.killPossiblyDeadProcess(int(syscall.SIGKILL)); err != nil { + if err := container.killPossiblyDeadProcess(9); err != nil { return err } } @@ -523,7 +524,7 @@ func (container *Container) Stop(seconds int) error { } } - container.logEvent("stop") + container.LogEvent("stop") return nil } @@ -1140,3 +1141,15 @@ func (container *Container) copyImagePathContent(v volume.Volume, destination st return v.Unmount() } + +func (container *Container) stopSignal() int { + var stopSignal syscall.Signal + if container.Config.StopSignal != "" { + stopSignal, _ = signal.ParseSignal(container.Config.StopSignal) + } + + if int(stopSignal) == 0 { + stopSignal, _ = signal.ParseSignal(signal.DefaultStopSignal) + } + return int(stopSignal) +} diff --git a/daemon/container_unit_test.go b/daemon/container_unit_test.go index ab30a8e373..ff45f71276 100644 --- a/daemon/container_unit_test.go +++ b/daemon/container_unit_test.go @@ -31,3 +31,31 @@ func TestValidContainerNames(t *testing.T) { } } } + +func TestContainerStopSignal(t *testing.T) { + c := &Container{ + CommonContainer: CommonContainer{ + Config: &runconfig.Config{}, + }, + } + + def, err := signal.ParseSignal(signal.DefaultStopSignal) + if err != nil { + t.Fatal(err) + } + + s := c.stopSignal() + if s != int(def) { + t.Fatalf("Expected %v, got %v", def, s) + } + + c = &Container{ + CommonContainer: CommonContainer{ + Config: &runconfig.Config{StopSignal: "SIGKILL"}, + }, + } + s = c.stopSignal() + if s != 9 { + t.Fatalf("Expected 9, got %v", s) + } +} diff --git a/daemon/daemon.go b/daemon/daemon.go index f5a0c035f3..bf4727cba1 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1095,6 +1095,11 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig, } } + _, err := signal.ParseSignal(config.StopSignal) + if err != nil { + return nil, err + } + // Now do platform-specific verification return verifyPlatformContainerSettings(daemon, hostConfig, config) } diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index 1e1ff2d5c8..0d09f5c4ef 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -82,6 +82,7 @@ This section lists each version from latest to oldest. Each listing includes a * `DELETE /volumes/(name)`remove a volume with the specified name. * `VolumeDriver` has been moved from config to hostConfig to make the configuration portable. * `GET /images/(name)/json` now returns information about tags of the image. +* The `config` option now accepts the field `StopSignal`, which specifies the signal to use to kill a container. ### v1.20 API changes diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 9ca05145e8..d3ddace8db 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -315,3 +315,19 @@ func (s *DockerSuite) TestRunWithSwappinessInvalid(c *check.C) { c.Fatalf("failed. test was able to set invalid value, output: %q", out) } } + +func (s *DockerSuite) TestStopContainerSignal(c *check.C) { + out, _ := dockerCmd(c, "run", "--stop-signal", "SIGUSR1", "-d", "busybox", "/bin/sh", "-c", `trap 'echo "exit trapped"; exit 0' USR1; while true; do sleep 1; done`) + containerID := strings.TrimSpace(out) + + if err := waitRun(containerID); err != nil { + c.Fatal(err) + } + + dockerCmd(c, "stop", containerID) + out, _ = dockerCmd(c, "logs", containerID) + + if !strings.Contains(out, "exit trapped") { + c.Fatalf("Expected `exit trapped` in the log, got %v", out) + } +} diff --git a/man/docker-create.1.md b/man/docker-create.1.md index 9385c882be..d040e62b7b 100644 --- a/man/docker-create.1.md +++ b/man/docker-create.1.md @@ -51,6 +51,7 @@ docker-create - Create a new container [**--read-only**[=*false*]] [**--restart**[=*RESTART*]] [**--security-opt**[=*[]*]] +[**--stop-signal**[=*SIGNAL*]] [**-t**|**--tty**[=*false*]] [**-u**|**--user**[=*USER*]] [**--ulimit**[=*[]*]] @@ -239,6 +240,9 @@ This value should always larger than **-m**, so you should always use this with **--security-opt**=[] Security Options +**--stop-signal**=SIGTERM + Signal to stop a container. Default is SIGTERM. + **-t**, **--tty**=*true*|*false* Allocate a pseudo-TTY. The default is *false*. diff --git a/man/docker-inspect.1.md b/man/docker-inspect.1.md index a1bbb317a9..e289899bbb 100644 --- a/man/docker-inspect.1.md +++ b/man/docker-inspect.1.md @@ -180,7 +180,8 @@ To get information on a container use its ID or instance name: "Memory": 0, "MemorySwap": 0, "CpuShares": 0, - "Cpuset": "" + "Cpuset": "", + "StopSignal": 15, } } ] diff --git a/man/docker-run.1.md b/man/docker-run.1.md index 0bb339d34e..b37d07b0e9 100644 --- a/man/docker-run.1.md +++ b/man/docker-run.1.md @@ -53,6 +53,7 @@ docker-run - Run a command in a new container [**--restart**[=*RESTART*]] [**--rm**[=*false*]] [**--security-opt**[=*[]*]] +[**--stop-signal**[=*SIGNAL*]] [**--sig-proxy**[=*true*]] [**-t**|**--tty**[=*false*]] [**-u**|**--user**[=*USER*]] @@ -371,7 +372,7 @@ its root filesystem mounted as read only prohibiting any writes. **--restart**="no" Restart policy to apply when a container exits (no, on-failure[:max-retry], always, unless-stopped). - + **--rm**=*true*|*false* Automatically remove the container when it exits (incompatible with -d). The default is *false*. @@ -384,6 +385,9 @@ its root filesystem mounted as read only prohibiting any writes. "label:level:LEVEL" : Set the label level for the container "label:disable" : Turn off label confinement for the container +**--stop-signal**=SIGTERM + Signal to stop a container. Default is SIGTERM. + **--sig-proxy**=*true*|*false* Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is *true*. diff --git a/man/docker-stop.1.md b/man/docker-stop.1.md index 9b882db49d..4939070d97 100644 --- a/man/docker-stop.1.md +++ b/man/docker-stop.1.md @@ -19,7 +19,7 @@ Stop a running container (Send SIGTERM, and then SIGKILL after Print usage statement **-t**, **--time**=10 - Number of seconds to wait for the container to stop before killing it. Default is 10 seconds. + Number of seconds to wait for the container to stop before killing it. Default is 10 seconds. #See also **docker-start(1)** to restart a stopped container. diff --git a/pkg/signal/signal.go b/pkg/signal/signal.go index db60bf2e59..106fe20d74 100644 --- a/pkg/signal/signal.go +++ b/pkg/signal/signal.go @@ -3,8 +3,12 @@ package signal import ( + "fmt" "os" "os/signal" + "strconv" + "strings" + "syscall" ) // CatchAll catches all signals and relays them to the specified channel. @@ -21,3 +25,20 @@ func StopCatch(sigc chan os.Signal) { signal.Stop(sigc) close(sigc) } + +// ParseSignal translates a string to a valid syscall signal. +// It returns an error if the signal map doesn't include the given signal. +func ParseSignal(rawSignal string) (syscall.Signal, error) { + s, err := strconv.Atoi(rawSignal) + if err == nil { + if s == 0 { + return -1, fmt.Errorf("Invalid signal: %s", rawSignal) + } + return syscall.Signal(s), nil + } + signal, ok := SignalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] + if !ok { + return -1, fmt.Errorf("Invalid signal: %s", rawSignal) + } + return signal, nil +} diff --git a/pkg/signal/signal_unix.go b/pkg/signal/signal_unix.go index 5c1ad5f722..d4fea931d1 100644 --- a/pkg/signal/signal_unix.go +++ b/pkg/signal/signal_unix.go @@ -9,8 +9,11 @@ import ( // Signals used in api/client (no windows equivalent, use // invalid signals so they don't get handled) -// SIGCHLD is a signal sent to a process when a child process terminates, is interrupted, or resumes after being interrupted. -const SIGCHLD = syscall.SIGCHLD - -// SIGWINCH is a signal sent to a process when its controlling terminal changes its size -const SIGWINCH = syscall.SIGWINCH +const ( + // SIGCHLD is a signal sent to a process when a child process terminates, is interrupted, or resumes after being interrupted. + SIGCHLD = syscall.SIGCHLD + // SIGWINCH is a signal sent to a process when its controlling terminal changes its size + SIGWINCH = syscall.SIGWINCH + // DefaultStopSignal is the syscall signal used to stop a container in unix systems. + DefaultStopSignal = "SIGTERM" +) diff --git a/pkg/signal/signal_windows.go b/pkg/signal/signal_windows.go index 1f1a6edbfc..b0585b0ed9 100644 --- a/pkg/signal/signal_windows.go +++ b/pkg/signal/signal_windows.go @@ -11,4 +11,6 @@ import ( const ( SIGCHLD = syscall.Signal(0xff) SIGWINCH = syscall.Signal(0xff) + // DefaultStopSignal is the syscall signal used to stop a container in windows systems. + DefaultStopSignal = "15" ) diff --git a/runconfig/config.go b/runconfig/config.go index 04010954be..16a2f95b26 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -34,6 +34,7 @@ type Config struct { MacAddress string // Mac Address of the container OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile Labels map[string]string // List of labels set to this container + StopSignal string // Signal to stop a container } // DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper diff --git a/runconfig/parse.go b/runconfig/parse.go index 8850b9f46c..079dff728e 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -9,6 +9,7 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/nat" "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/pkg/units" ) @@ -93,6 +94,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flLoggingDriver = cmd.String([]string{"-log-driver"}, "", "Logging driver for container") flCgroupParent = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container") flVolumeDriver = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container") + flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal)) ) cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR") @@ -322,6 +324,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe Entrypoint: entrypoint, WorkingDir: *flWorkingDir, Labels: convertKVStringsToMap(labels), + StopSignal: *flStopSignal, } hostConfig := &HostConfig{ From 3781cde61ff10b1d9114ae5b4c5c1d1b2c20a1ee Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 18 Aug 2015 10:30:44 -0700 Subject: [PATCH 2/2] Add `STOPSIGNAL` instruction to dockerfiles. This way, images creators can set the exit signal their programs use. Signed-off-by: David Calavera --- builder/command/command.go | 2 ++ builder/dispatchers.go | 19 +++++++++++++++++++ builder/evaluator.go | 18 ++++++++++-------- builder/parser/parser.go | 1 + contrib/syntax/kate/Dockerfile.xml | 1 + .../Syntaxes/Dockerfile.tmLanguage | 2 +- contrib/syntax/vim/syntax/dockerfile.vim | 2 +- daemon/container.go | 2 +- daemon/container_unit_test.go | 7 ++++++- daemon/daemon.go | 12 +++++++----- docs/reference/api/docker_remote_api_v1.21.md | 5 ++++- docs/reference/builder.md | 9 +++++++++ docs/reference/commandline/create.md | 1 + docs/reference/commandline/run.md | 7 +++++++ integration-cli/docker_cli_build_test.go | 15 +++++++++++++++ integration-cli/docker_cli_create_test.go | 12 ++++++++++++ man/docker-inspect.1.md | 2 +- 17 files changed, 98 insertions(+), 19 deletions(-) diff --git a/builder/command/command.go b/builder/command/command.go index 93b1c8bd81..968dba1f8d 100644 --- a/builder/command/command.go +++ b/builder/command/command.go @@ -17,6 +17,7 @@ const ( Expose = "expose" Volume = "volume" User = "user" + StopSignal = "stopsignal" ) // Commands is list of all Dockerfile commands @@ -35,4 +36,5 @@ var Commands = map[string]struct{}{ Expose: {}, Volume: {}, User: {}, + StopSignal: {}, } diff --git a/builder/dispatchers.go b/builder/dispatchers.go index 78e3f87229..b00001260f 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -20,6 +20,7 @@ import ( "github.com/Sirupsen/logrus" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/nat" + "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/pkg/system" "github.com/docker/docker/runconfig" @@ -534,3 +535,21 @@ func volume(b *builder, args []string, attributes map[string]bool, original stri } return nil } + +// STOPSIGNAL signal +// +// Set the signal that will be used to kill the container. +func stopSignal(b *builder, args []string, attributes map[string]bool, original string) error { + if len(args) != 1 { + return fmt.Errorf("STOPSIGNAL requires exactly one argument") + } + + sig := args[0] + _, err := signal.ParseSignal(sig) + if err != nil { + return err + } + + b.Config.StopSignal = sig + return b.commit("", b.Config.Cmd, fmt.Sprintf("STOPSIGNAL %v", args)) +} diff --git a/builder/evaluator.go b/builder/evaluator.go index 2b0c038a01..18d2c36a63 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -45,14 +45,15 @@ import ( // Environment variable interpolation will happen on these statements only. var replaceEnvAllowed = map[string]struct{}{ - command.Env: {}, - command.Label: {}, - command.Add: {}, - command.Copy: {}, - command.Workdir: {}, - command.Expose: {}, - command.Volume: {}, - command.User: {}, + command.Env: {}, + command.Label: {}, + command.Add: {}, + command.Copy: {}, + command.Workdir: {}, + command.Expose: {}, + command.Volume: {}, + command.User: {}, + command.StopSignal: {}, } var evaluateTable map[string]func(*builder, []string, map[string]bool, string) error @@ -73,6 +74,7 @@ func init() { command.Expose: expose, command.Volume: volume, command.User: user, + command.StopSignal: stopSignal, } } diff --git a/builder/parser/parser.go b/builder/parser/parser.go index c72d9df4d8..37548401d3 100644 --- a/builder/parser/parser.go +++ b/builder/parser/parser.go @@ -61,6 +61,7 @@ func init() { command.Entrypoint: parseMaybeJSON, command.Expose: parseStringsWhitespaceDelimited, command.Volume: parseMaybeJSONToList, + command.StopSignal: parseString, } } diff --git a/contrib/syntax/kate/Dockerfile.xml b/contrib/syntax/kate/Dockerfile.xml index 4fdef2393b..05692504e7 100644 --- a/contrib/syntax/kate/Dockerfile.xml +++ b/contrib/syntax/kate/Dockerfile.xml @@ -23,6 +23,7 @@ WORKDIR USER LABEL + STOPSIGNAL diff --git a/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage b/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage index 61e45ccbf6..0ca231c319 100644 --- a/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage +++ b/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage @@ -25,7 +25,7 @@ match - ^\s*(?:(ONBUILD)\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY|LABEL)\s + ^\s*(?:(ONBUILD)\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY|LABEL|STOPSIGNAL)\s captures diff --git a/contrib/syntax/vim/syntax/dockerfile.vim b/contrib/syntax/vim/syntax/dockerfile.vim index 220a4db3a2..3cb1ecfddf 100644 --- a/contrib/syntax/vim/syntax/dockerfile.vim +++ b/contrib/syntax/vim/syntax/dockerfile.vim @@ -11,7 +11,7 @@ let b:current_syntax = "dockerfile" syntax case ignore -syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|LABEL|VOLUME|WORKDIR|COPY)\s/ +syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|LABEL|VOLUME|WORKDIR|COPY|STOPSIGNAL)\s/ highlight link dockerfileKeyword Keyword syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/ diff --git a/daemon/container.go b/daemon/container.go index 52465be590..c73e7aadb7 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -524,7 +524,7 @@ func (container *Container) Stop(seconds int) error { } } - container.LogEvent("stop") + container.logEvent("stop") return nil } diff --git a/daemon/container_unit_test.go b/daemon/container_unit_test.go index ff45f71276..71d37cf436 100644 --- a/daemon/container_unit_test.go +++ b/daemon/container_unit_test.go @@ -1,6 +1,11 @@ package daemon -import "testing" +import ( + "testing" + + "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/runconfig" +) func TestGetFullName(t *testing.T) { name, err := GetFullContainerName("testing") diff --git a/daemon/daemon.go b/daemon/daemon.go index bf4727cba1..3dcf93b629 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1076,6 +1076,13 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig, return nil, fmt.Errorf("The working directory '%s' is invalid. It needs to be an absolute path.", config.WorkingDir) } } + + if len(config.StopSignal) > 0 { + _, err := signal.ParseSignal(config.StopSignal) + if err != nil { + return nil, err + } + } } if hostConfig == nil { @@ -1095,11 +1102,6 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig, } } - _, err := signal.ParseSignal(config.StopSignal) - if err != nil { - return nil, err - } - // Now do platform-specific verification return verifyPlatformContainerSettings(daemon, hostConfig, config) } diff --git a/docs/reference/api/docker_remote_api_v1.21.md b/docs/reference/api/docker_remote_api_v1.21.md index 6a9e37faf1..4fdb330010 100644 --- a/docs/reference/api/docker_remote_api_v1.21.md +++ b/docs/reference/api/docker_remote_api_v1.21.md @@ -166,6 +166,7 @@ Create a container "ExposedPorts": { "22/tcp": {} }, + "StopSignal": "SIGTERM", "HostConfig": { "Binds": ["/tmp:/tmp"], "Links": ["redis3:redis"], @@ -250,6 +251,7 @@ Json Parameters: container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` +- **StopSignal** - Signal to stop a container as a string or unsigned integer. `SIGTERM` by default. - **HostConfig** - **Binds** – A list of volume bindings for this container. Each volume binding is a string in one of these forms: + `container_path` to create a new volume for the container @@ -367,7 +369,8 @@ Return low-level information on the container `id` "Tty": false, "User": "", "Volumes": null, - "WorkingDir": "" + "WorkingDir": "", + "StopSignal": "SIGTERM" }, "Created": "2015-01-06T15:47:31.485331387Z", "Driver": "devicemapper", diff --git a/docs/reference/builder.md b/docs/reference/builder.md index 3a73b87d29..195139758f 100644 --- a/docs/reference/builder.md +++ b/docs/reference/builder.md @@ -158,6 +158,7 @@ the `Dockerfile`: * `USER` * `WORKDIR` * `VOLUME` +* `STOPSIGNAL` as well as: @@ -1012,6 +1013,14 @@ For example you might add something like this: > **Warning**: The `ONBUILD` instruction may not trigger `FROM` or `MAINTAINER` instructions. +## STOPSIGNAL + + STOPSIGNAL signal + +The `STOPSIGNAL` instruction sets the system call signal that will be sent to the container to exit. +This signal can be a valid unsigned number that matches a position in the kernel's syscall table, for instance 9, +or a signal name in the format SIGNAME, for instance SIGKILL. + ## Dockerfile examples # Nginx diff --git a/docs/reference/commandline/create.md b/docs/reference/commandline/create.md index 74d5185a34..7c64d7297d 100644 --- a/docs/reference/commandline/create.md +++ b/docs/reference/commandline/create.md @@ -61,6 +61,7 @@ Creates a new container. --read-only=false Mount the container's root filesystem as read only --restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped) --security-opt=[] Security options + --stop-signal="SIGTERM" Signal to stop a container -t, --tty=false Allocate a pseudo-TTY --disable-content-trust=true Skip image verification -u, --user="" Username or UID diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md index 6ee97906d9..c7726497cc 100644 --- a/docs/reference/commandline/run.md +++ b/docs/reference/commandline/run.md @@ -62,6 +62,7 @@ weight=1 --restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped) --rm=false Automatically remove the container when it exits --security-opt=[] Security Options + --stop-signal="SIGTERM" Signal to stop a container --sig-proxy=true Proxy received signals to the process -t, --tty=false Allocate a pseudo-TTY -u, --user="" Username or UID (format: [:]) @@ -531,3 +532,9 @@ containers with `daemon` user: The 4th container fails and reports "[8] System error: resource temporarily unavailable" error. This fails because the caller set `nproc=3` resulting in the first three containers using up the three processes quota set for the `daemon` user. + +### Stopping a container with a specific signal + +The `--stop-signal` flag sets the system call signal that will be sent to the container to exit. +This signal can be a valid unsigned number that matches a position in the kernel's syscall table, for instance 9, +or a signal name in the format SIGNAME, for instance SIGKILL. diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 1e5ef40148..308e9407a7 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5660,3 +5660,18 @@ func (s *DockerSuite) TestBuildNullStringInAddCopyVolume(c *check.C) { _, err = buildImageFromContext(name, ctx, true) c.Assert(err, check.IsNil) } + +func (s *DockerSuite) TestBuildStopSignal(c *check.C) { + name := "test_build_stop_signal" + _, err := buildImage(name, + `FROM busybox + STOPSIGNAL SIGKILL`, + true) + c.Assert(err, check.IsNil) + res, err := inspectFieldJSON(name, "Config.StopSignal") + c.Assert(err, check.IsNil) + + if res != `"SIGKILL"` { + c.Fatalf("Signal %s, expected SIGKILL", res) + } +} diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index f854d0d6fe..ac36320848 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -458,3 +458,15 @@ func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) { c.Fatalf("Missing expected output on trusted push:\n%s", out) } } + +func (s *DockerSuite) TestCreateStopSignal(c *check.C) { + name := "test_create_stop_signal" + dockerCmd(c, "create", "--name", name, "--stop-signal", "9", "busybox") + + res, err := inspectFieldJSON(name, "Config.StopSignal") + c.Assert(err, check.IsNil) + + if res != `"9"` { + c.Fatalf("Expected 9, got %s", res) + } +} diff --git a/man/docker-inspect.1.md b/man/docker-inspect.1.md index e289899bbb..f7faf4a7b2 100644 --- a/man/docker-inspect.1.md +++ b/man/docker-inspect.1.md @@ -181,7 +181,7 @@ To get information on a container use its ID or instance name: "MemorySwap": 0, "CpuShares": 0, "Cpuset": "", - "StopSignal": 15, + "StopSignal": "SIGTERM" } } ]