From 7b062cf4c1ac9ca46090e3cf77c203f20adcaec4 Mon Sep 17 00:00:00 2001 From: baude Date: Wed, 13 Sep 2017 15:56:27 -0500 Subject: [PATCH] Add support for kpod kill Kill one or more containers using a user-provided signal. If not signal is provided, KILL is used. Signed-off-by: baude --- README.md | 1 + cmd/kpod/kill.go | 71 +++++++++++++++++++ cmd/kpod/main.go | 1 + completions/bash/kpod | 10 +++ docs/kpod-kill.1.md | 33 +++++++++ docs/kpod.1.md | 3 + libkpod/kill.go | 45 ++++++++++++ test/kpod_kill.bats | 86 +++++++++++++++++++++++ test/testdata/container_config_sleep.json | 71 +++++++++++++++++++ transfer.md | 1 + 10 files changed, 322 insertions(+) create mode 100644 cmd/kpod/kill.go create mode 100644 docs/kpod-kill.1.md create mode 100644 libkpod/kill.go create mode 100644 test/kpod_kill.bats create mode 100644 test/testdata/container_config_sleep.json diff --git a/README.md b/README.md index 322c51d4..8e6c3d41 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ It is currently in active development in the Kubernetes community through the [d | [kpod-images(1)](/docs/kpod-images.1.md) | List images in local storage |[![...](/docs/play.png)](https://asciinema.org/a/133649)| | [kpod-info(1)](/docs/kpod-info.1.md) | Display system information || | [kpod-inspect(1)](/docs/kpod-inspect.1.md) | Display the configuration of a container or image |[![...](/docs/play.png)](https://asciinema.org/a/133418)| +| [kpod-kill(1)](/docs/kpod-kill.1.md) | Kill the main process in one or more running containers | [kpod-load(1)](/docs/kpod-load.1.md) | Load an image from docker archive or oci |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)| | [kpod-logs(1)](/docs/kpod-logs.1.md) | Display the logs of a container || | [kpod-mount(1)](/docs/kpod-mount.1.md) | Mount a working container's root filesystem || diff --git a/cmd/kpod/kill.go b/cmd/kpod/kill.go new file mode 100644 index 00000000..bdce1875 --- /dev/null +++ b/cmd/kpod/kill.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "os" + + "github.com/docker/docker/pkg/signal" + "github.com/kubernetes-incubator/cri-o/libkpod" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +var ( + killFlags = []cli.Flag{ + cli.StringFlag{ + Name: "signal, s", + Usage: "Signal to send to the container", + Value: "KILL", + }, + } + killDescription = "The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal." + killCommand = cli.Command{ + Name: "kill", + Usage: "Kill one or more running containers with a specific signal", + Description: killDescription, + Flags: killFlags, + Action: killCmd, + ArgsUsage: "[CONTAINER_NAME_OR_ID]", + } +) + +// killCmd kills one or more containers with a signal +func killCmd(c *cli.Context) error { + args := c.Args() + if len(args) == 0 { + return errors.Errorf("specify one or more containers to kill") + } + config, err := getConfig(c) + if err != nil { + return errors.Wrapf(err, "could not get config") + } + server, err := libkpod.New(config) + if err != nil { + return errors.Wrapf(err, "could not get container server") + } + killSignal := c.String("signal") + // Check if the signalString provided by the user is valid + // Invalid signals will return err + sysSignal, err := signal.ParseSignal(killSignal) + if err != nil { + return err + } + defer server.Shutdown() + err = server.Update() + if err != nil { + return errors.Wrapf(err, "could not update list of containers") + } + var lastError error + for _, container := range c.Args() { + id, err := server.ContainerKill(container, sysSignal) + if err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "unable to kill %v", container) + } else { + fmt.Println(id) + } + } + return lastError +} diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go index c00de39b..2936e49f 100644 --- a/cmd/kpod/main.go +++ b/cmd/kpod/main.go @@ -37,6 +37,7 @@ func main() { imagesCommand, infoCommand, inspectCommand, + killCommand, loadCommand, logsCommand, mountCommand, diff --git a/completions/bash/kpod b/completions/bash/kpod index 4803864d..a18c4f8d 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -137,6 +137,15 @@ _kpod_inspect() { ;; esac } +_kpod_kill() { + local options_with_args=" + --signal -s + " + local boolean_options=" + --help + -h" + _complete_ "$options_with_args" "$boolean_options" +} _kpod_logs() { local options_with_args=" @@ -442,6 +451,7 @@ _kpod_kpod() { images info inspect + kill load logs mount diff --git a/docs/kpod-kill.1.md b/docs/kpod-kill.1.md new file mode 100644 index 00000000..91247d28 --- /dev/null +++ b/docs/kpod-kill.1.md @@ -0,0 +1,33 @@ +% kpod(1) kpod-kill- Kill one or more containers with a signal +% Brent Baude +# kpod-kill"1" "September 2017" "kpod" + +## NAME +kpod kill - Kills one or more containers with a signal + +## SYNOPSIS +**kpod kill [OPTIONS] CONTAINER [...]** + +## DESCRIPTION +The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal. + +## OPTIONS + +**--signal, s** + +Signal to send to the container. For more information on Linux signals, refer to *man signal(7)*. + + +## EXAMPLE + +kpod kill mywebserver + +kpod kill 860a4b23 + +kpod kill --signal TERM 860a4b23 + +## SEE ALSO +kpod(1), kpod-stop(1) + +## HISTORY +September 2017, Originally compiled by Brent Baude diff --git a/docs/kpod.1.md b/docs/kpod.1.md index 2db4e29c..63a4e06b 100644 --- a/docs/kpod.1.md +++ b/docs/kpod.1.md @@ -67,6 +67,9 @@ Displays system information ### inspect Display a container or image's configuration +### kill +Kill the main process in one or more containers + ### load Load an image from docker archive diff --git a/libkpod/kill.go b/libkpod/kill.go new file mode 100644 index 00000000..b2c3219a --- /dev/null +++ b/libkpod/kill.go @@ -0,0 +1,45 @@ +package libkpod + +import ( + "github.com/docker/docker/pkg/signal" + "github.com/kubernetes-incubator/cri-o/oci" + "github.com/kubernetes-incubator/cri-o/utils" + "github.com/pkg/errors" + "os" + "syscall" +) + +// Reverse lookup signal string from its map +func findStringInSignalMap(killSignal syscall.Signal) (string, error) { + for k, v := range signal.SignalMap { + if v == killSignal { + return k, nil + } + } + return "", errors.Errorf("unable to convert signal to string") + +} + +// ContainerKill sends the user provided signal to the containers primary process. +func (c *ContainerServer) ContainerKill(container string, killSignal syscall.Signal) (string, error) { // nolint + ctr, err := c.LookupContainer(container) + if err != nil { + return "", errors.Wrapf(err, "failed to find container %s", container) + } + c.runtime.UpdateStatus(ctr) + cStatus := c.runtime.ContainerStatus(ctr) + + // If the container is not running, error and move on. + if cStatus.Status != oci.ContainerStateRunning { + return "", errors.Errorf("cannot kill container %s: it is not running", container) + } + signalString, err := findStringInSignalMap(killSignal) + if err != nil { + return "", err + } + if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, c.runtime.Path(ctr), "kill", ctr.ID(), signalString); err != nil { + return "", err + } + c.ContainerStateToDisk(ctr) + return ctr.ID(), nil +} diff --git a/test/kpod_kill.bats b/test/kpod_kill.bats new file mode 100644 index 00000000..cf3e7859 --- /dev/null +++ b/test/kpod_kill.bats @@ -0,0 +1,86 @@ +#!/usr/bin/env bats + +load helpers + +ROOT="$TESTDIR/crio" +RUNROOT="$TESTDIR/crio-run" +KPOD_OPTIONS="--root $ROOT --runroot $RUNROOT ${STORAGE_OPTS} --runtime $RUNTIME_BINARY" +function teardown() { + cleanup_test +} + +function start_sleep_container () { + pod_id=$(crioctl pod run --config "$TESTDATA"/sandbox_config.json) + ctr_id=$(crioctl ctr create --config "$TESTDATA"/container_config_sleep.json --pod "$pod_id") + crioctl ctr start --id "$ctr_id" +} + +@test "kill a bogus container" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} kill foobar + echo "$output" + [ "$status" -ne 0 ] +} + +@test "kill a running container by id" { + start_crio + ${KPOD_BINARY} ${KPOD_OPTIONS} pull docker.io/library/busybox:latest + ctr_id=$( start_sleep_container ) + crioctl ctr status --id "$ctr_id" + ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a + ${KPOD_BINARY} ${KPOD_OPTIONS} logs "$ctr_id" + crioctl ctr status --id "$ctr_id" + run ${KPOD_BINARY} ${KPOD_OPTIONS} kill "$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + cleanup_ctrs + cleanup_pods + stop_crio +} + +@test "kill a running container by id with TERM" { + start_crio + ${KPOD_BINARY} ${KPOD_OPTIONS} pull docker.io/library/busybox:latest + ctr_id=$( start_sleep_container ) + crioctl ctr status --id "$ctr_id" + ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a + ${KPOD_BINARY} ${KPOD_OPTIONS} logs "$ctr_id" + crioctl ctr status --id "$ctr_id" + run ${KPOD_BINARY} ${KPOD_OPTIONS} kill -s TERM "$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + cleanup_ctrs + cleanup_pods + stop_crio +} + +@test "kill a running container by name" { + start_crio + ${KPOD_BINARY} ${KPOD_OPTIONS} pull docker.io/library/busybox:latest + ctr_id=$( start_sleep_container ) + crioctl ctr status --id "$ctr_id" + ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a + ${KPOD_BINARY} ${KPOD_OPTIONS} logs "$ctr_id" + crioctl ctr status --id "$ctr_id" + ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a + run ${KPOD_BINARY} ${KPOD_OPTIONS} kill "k8s_container999_podsandbox1_redhat.test.crio_redhat-test-crio_1" + echo "$output" + [ "$status" -eq 0 ] + cleanup_ctrs + cleanup_pods + stop_crio +} + +@test "kill a running container by id with a bogus signal" { + start_crio + ${KPOD_BINARY} ${KPOD_OPTIONS} pull docker.io/library/busybox:latest + ctr_id=$( start_sleep_container ) + crioctl ctr status --id "$ctr_id" + ${KPOD_BINARY} ${KPOD_OPTIONS} logs "$ctr_id" + crioctl ctr status --id "$ctr_id" + run ${KPOD_BINARY} ${KPOD_OPTIONS} kill -s foobar "$ctr_id" + echo "$output" + [ "$status" -ne 0 ] + cleanup_ctrs + cleanup_pods + stop_crio +} diff --git a/test/testdata/container_config_sleep.json b/test/testdata/container_config_sleep.json new file mode 100644 index 00000000..c86ff701 --- /dev/null +++ b/test/testdata/container_config_sleep.json @@ -0,0 +1,71 @@ +{ + "metadata": { + "name": "container999", + "attempt": 1 + }, + "image": { + "image": "docker.io/library/busybox:latest" + }, + "command": [ + "sleep", + "9999" + ], + "args": [], + "working_dir": "/", + "envs": [ + { + "key": "PATH", + "value": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + }, + { + "key": "TERM", + "value": "xterm" + }, + { + "key": "TESTDIR", + "value": "test/dir1" + }, + { + "key": "TESTFILE", + "value": "test/file1" + } + ], + "labels": { + "type": "small", + "batch": "no" + }, + "annotations": { + "owner": "dragon", + "daemon": "crio" + }, + "privileged": true, + "log_path": "", + "stdin": false, + "stdin_once": false, + "tty": false, + "linux": { + "resources": { + "cpu_period": 10000, + "cpu_quota": 20000, + "cpu_shares": 512, + "oom_score_adj": 30 + }, + "security_context": { + "readonly_rootfs": false, + "selinux_options": { + "user": "system_u", + "role": "system_r", + "type": "svirt_lxc_net_t", + "level": "s0:c4,c5" + }, + "capabilities": { + "add_capabilities": [ + "setuid", + "setgid" + ], + "drop_capabilities": [ + ] + } + } + } +} diff --git a/transfer.md b/transfer.md index 077ecef4..752e8263 100644 --- a/transfer.md +++ b/transfer.md @@ -45,6 +45,7 @@ There are other equivalents for these tools | `docker export` | [`kpod export`](./docs/kpod-export.1.md) | | `docker history`| [`kpod history`](./docs/kpod-history.1.md)| | `docker images` | [`kpod images`](./docs/kpod-images.1.md) | +| `docker kill` | [`kpod kill`](./docs/kpod-kill.1.md) | | `docker load` | [`kpod load`](./docs/kpod-load.1.md) | | `docker pause` | [`kpod pause`](./docs/kpod-pause.1.md) | | `docker ps` | [`kpod ps`](./docs/kpod-ps.1.md) |