mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
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 <david.calavera@gmail.com>
This commit is contained in:
parent
bb1996ab8f
commit
0e50d946a2
18 changed files with 124 additions and 32 deletions
|
@ -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
|
||||
|
|
|
@ -1149,6 +1149,7 @@ _docker_run() {
|
|||
--publish -p
|
||||
--restart
|
||||
--security-opt
|
||||
--stop-signal
|
||||
--ulimit
|
||||
--user -u
|
||||
--uts
|
||||
|
|
|
@ -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)'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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*.
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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*.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{
|
||||
|
|
Loading…
Add table
Reference in a new issue