Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: cli support add kill command #2924

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions apis/server/container_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"strconv"
"strings"
"syscall"
"time"

"github.com/alibaba/pouch/apis/metrics"
Expand Down Expand Up @@ -186,6 +187,24 @@ func (s *Server) getContainers(ctx context.Context, rw http.ResponseWriter, req
return EncodeResponse(rw, http.StatusOK, containerList)
}

func (s *Server) killContainer(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
var sig syscall.Signal
name := mux.Vars(req)["name"]

// If we have a signal, look at it. Otherwise, do nothing
if sigStr := req.FormValue("signal"); sigStr != "" {
var err error
if sig, err = utils.ParseSignal(sigStr); err != nil {
return err
}
}

if err := s.ContainerMgr.Kill(ctx, name, uint64(sig)); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please just return s.ContainerMgr.Kill(ctx, name, uint64(sig)) to simplify code.

return err
}
return nil
}

func (s *Server) startContainer(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
label := util_metrics.ActionStartLabel
defer func(start time.Time) {
Expand Down
1 change: 1 addition & 0 deletions apis/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func initRoute(s *Server) *mux.Router {
{Method: http.MethodGet, Path: "/containers/{name:.*}/checkpoints", HandlerFunc: withCancelHandler(s.listContainerCheckpoint)},
{Method: http.MethodDelete, Path: "/containers/{name}/checkpoints/{id}", HandlerFunc: withCancelHandler(s.deleteContainerCheckpoint)},
{Method: http.MethodPost, Path: "/containers/create", HandlerFunc: s.createContainer},
{Method: http.MethodPost, Path: "/containers/{name:.*}/kill", HandlerFunc: s.killContainer},
{Method: http.MethodPost, Path: "/containers/{name:.*}/start", HandlerFunc: s.startContainer},
{Method: http.MethodPost, Path: "/containers/{name:.*}/stop", HandlerFunc: s.stopContainer},
{Method: http.MethodPost, Path: "/containers/{name:.*}/attach", HandlerFunc: s.attachContainer},
Expand Down
79 changes: 79 additions & 0 deletions cli/kill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package main

import (
"context"
"errors"
"fmt"
"strings"

"github.com/spf13/cobra"
)

// killDescription is used to describe kill command in detail and auto generate command doc.
var killDescription = "Kill one or more running container objects in Pouchd. " +
"You can kill a container using the container’s ID, ID-prefix, or name. " +
"This is useful when you wish to kill a container which is running."

// KillCommand use to implement 'kill' command, it kill one or more containers.
type KillCommand struct {
baseCommand
signal string
}

// Init initialize kill command.
func (s *KillCommand) Init(c *Cli) {
s.cli = c
s.cmd = &cobra.Command{
Use: "kill [OPTIONS] CONTAINER [CONTAINER...]",
Short: "kill one or more running containers",
Long: killDescription,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return s.runKill(args)
},
Example: killExample(),
}
s.addFlags()
}

// addFlags adds flags for specific command.
func (s *KillCommand) addFlags() {
flagSet := s.cmd.Flags()
flagSet.StringVarP(&s.signal, "signal", "s", "KILL", "Signal to send to the container")

}

// runKill is the entry of kill command.
func (s *KillCommand) runKill(args []string) error {
ctx := context.Background()
apiClient := s.cli.Client()

var errs []string
for _, name := range args {
if err := apiClient.ContainerKill(ctx, name, s.signal); err != nil {
errs = append(errs, err.Error())
continue
}
fmt.Printf("%s\n", name)
}

if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
}

return nil
}

// killExample shows examples in kill command, and is used in auto-generated cli docs.
func killExample() string {
return `$ pouch ps -a
Name ID Status Created Image Runtime
foo2 5a0ede Up 2 seconds 3 second ago registry.hub.docker.com/library/busybox:latest runc
foo1 e05637 Up 6 seconds 7 seconds ago registry.hub.docker.com/library/busybox:latest runc
$ pouch kill foo1
foo1
$ pouch ps
Name ID Status Created Image Runtime
foo2 5a0ede Up 11 seconds 12 seconds ago registry.hub.docker.com/library/busybox:latest runc
`
}
1 change: 1 addition & 0 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func main() {
cli.AddCommand(base, &PullCommand{})
cli.AddCommand(base, &PushCommand{})
cli.AddCommand(base, &CreateCommand{})
cli.AddCommand(base, &KillCommand{})
cli.AddCommand(base, &StartCommand{})
cli.AddCommand(base, &StopCommand{})
cli.AddCommand(base, &PsCommand{})
Expand Down
17 changes: 17 additions & 0 deletions client/container_kill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package client

import (
"context"
"net/url"
)

// ContainerKill kills a container.
func (client *APIClient) ContainerKill(ctx context.Context, name, signal string) error {
q := url.Values{}
q.Add("signal", signal)

resp, err := client.post(ctx, "/containers/"+name+"/kill", q, nil, nil)
ensureCloseReader(resp)

return err
}
1 change: 1 addition & 0 deletions client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type CommonAPIClient interface {
type ContainerAPIClient interface {
ContainerCreate(ctx context.Context, config types.ContainerConfig, hostConfig *types.HostConfig, networkConfig *types.NetworkingConfig, containerName string) (*types.ContainerCreateResp, error)
ContainerStart(ctx context.Context, name string, options types.ContainerStartOptions) error
ContainerKill(ctx context.Context, name, signal string) error
ContainerStop(ctx context.Context, name, timeout string) error
ContainerRemove(ctx context.Context, name string, options *types.ContainerRemoveOptions) error
ContainerList(ctx context.Context, option types.ContainerListOptions) ([]*types.Container, error)
Expand Down
40 changes: 40 additions & 0 deletions ctrd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,46 @@ func (c *Client) recoverContainer(ctx context.Context, id string, io *containeri
return nil
}

// KillContainer will kill a container by signal
func (c *Client) KillContainer(ctx context.Context, containerID string, signal int) error {
err := c.killContainer(ctx, containerID, signal)
if err != nil {
return convertCtrdErr(err)
}
return nil
}

// killContainer is the real process of killing a container
func (c *Client) killContainer(ctx context.Context, containerID string, signal int) error {
wrapperCli, err := c.Get(ctx)
if err != nil {
return fmt.Errorf("failed to get a containerd grpc client: %v", err)
}

ctx = leases.WithLease(ctx, wrapperCli.lease.ID)

if !c.lock.TrylockWithRetry(ctx, containerID) {
return errtypes.ErrLockfailed
}
defer c.lock.Unlock(containerID)

pack, err := c.watch.get(containerID)
if err != nil {
return err
}
// the caller need to execute the all hooks.
pack.l.Lock()
pack.skipStopHooks = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I don't know if it is necessary to add these things. But if you want to kill a container, you should skip the stop hooks.

pack.l.Unlock()
defer func() {
pack.l.Lock()
pack.skipStopHooks = false
pack.l.Unlock()
}()

return pack.task.Kill(ctx, syscall.Signal(signal), containerd.WithKillAll)
}

// DestroyContainer kill container and delete it.
func (c *Client) DestroyContainer(ctx context.Context, id string, timeout int64) (*Message, error) {
msg, err := c.destroyContainer(ctx, id, timeout)
Expand Down
2 changes: 2 additions & 0 deletions ctrd/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type APIClient interface {

// ContainerAPIClient provides access to containerd container features.
type ContainerAPIClient interface {
// KillContainer kill container by signal
KillContainer(ctx context.Context, containerID string, signal int) error
// CreateContainer creates a containerd container and start process.
CreateContainer(ctx context.Context, container *Container, checkpointDir string) error
// DestroyContainer kill container and delete it.
Expand Down
3 changes: 3 additions & 0 deletions daemon/mgr/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ type ContainerMgr interface {
// List returns the list of containers.
List(ctx context.Context, option *ContainerListOption) ([]*Container, error)

// Kill a running container
Kill(ctx context.Context, name string, signal uint64) (err error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the desc here I think is kill one running container, wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


// Start a container.
Start(ctx context.Context, id string, options *types.ContainerStartOptions) error

Expand Down
94 changes: 94 additions & 0 deletions daemon/mgr/container_kill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package mgr

import (
"context"
"fmt"
"strings"
"syscall"

"github.com/pkg/errors"

"github.com/alibaba/pouch/pkg/errtypes"
"github.com/sirupsen/logrus"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please restruct the package import sequence.

)

// Kill kills a container.
func (mgr *ContainerManager) Kill(ctx context.Context, name string, signal uint64) (err error) {
c, err := mgr.container(name)
if err != nil {
return err
}

if syscall.Signal(signal) == syscall.SIGKILL {
return mgr.kill(ctx, c)
}
return mgr.killWithSignal(ctx, c, int(signal))
}

func (mgr *ContainerManager) kill(ctx context.Context, c *Container) error {
if !c.IsRunning() {
return fmt.Errorf("Container %s is not running", c.ID)
}

if err := mgr.killDeadProcess(ctx, c, int(syscall.SIGKILL)); err != nil {
if errtypes.IsNoSuchProcess(err) {
return nil
}

if c.IsRunning() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why killDeadProcess don't lock the container here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function killDeadProcess will call the function killWithSignal. The latter will lock the container.

return err
}
}

if pid := c.State.Pid; pid != 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if err := syscall.Kill(int(pid), 9); err != nil {
if err != syscall.ESRCH {
return err
}

e := errors.Wrapf(errtypes.ErrNoSuchProcess, "Cannot kill process (pid=%d) with signal %d", c.State.Pid, 9)
logrus.Debug(e)
return nil
}
}
return nil
}

func (mgr *ContainerManager) killDeadProcess(ctx context.Context, c *Container, signal int) error {
err := mgr.killWithSignal(ctx, c, signal)
if err == syscall.ESRCH {
e := errors.Wrapf(errtypes.ErrNoSuchProcess, "Cannot kill process (pid=%d) with signal %d", c.State.Pid, signal)
logrus.Debug(e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add more msg to this err

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

return e
}
return err
}

func (mgr *ContainerManager) killWithSignal(ctx context.Context, c *Container, signal int) error {
logrus.Debugf("Sending %d to %s", signal, c.ID)
c.Lock()
defer c.Unlock()

if c.State.Paused {
return fmt.Errorf("Container %s is paused. Unpause the container before stopping", c.ID)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before killing?

}

if !c.State.Running {
return fmt.Errorf("Container %s is not running", c.ID)
}

if err := mgr.Client.KillContainer(ctx, c.ID, signal); err != nil {
// if container or process not exists, ignore the error
if strings.Contains(err.Error(), "container not found") ||
strings.Contains(err.Error(), "no such process") {
logrus.Warnf("container kill failed because of 'container not found' or 'no such process': %s", err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return err?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, return fmt.Errorf("Cannot kill container %s: %s", c.ID, err).

} else {
return fmt.Errorf("Cannot kill container %s: %s", c.ID, err)
}
}
attributes := map[string]string{
"signal": fmt.Sprintf("%d", signal),
}
mgr.LogContainerEventWithAttributes(ctx, c, "kill", attributes)
return nil
}
49 changes: 49 additions & 0 deletions docs/commandline/pouch_kill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## pouch kill
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this file. This file is auto-generated. https://github.com/alibaba/pouch/tree/master/docs#commandline

What's more, all commandline docs are auto generated via source code


Kill one or more running containers

### Synopsis

Kill one or more running containers in Pouchd. You can kill a container using the container’s ID, ID-prefix, or name. This is useful when you wish to kill a container which is running.

```
pouch kill [OPTIONS] CONTAINER [CONTAINER...]
```

### Examples

```
$ pouch ps -a
Name ID Status Created Image Runtime
foo2 5a0ede Up 2 seconds 3 second ago registry.hub.docker.com/library/busybox:latest runc
foo1 e05637 Up 6 seconds 7 seconds ago registry.hub.docker.com/library/busybox:latest runc
$ pouch kill foo1
foo1
$ pouch ps
Name ID Status Created Image Runtime
foo2 5a0ede Up 11 seconds 12 seconds ago registry.hub.docker.com/library/busybox:latest runc
```

### Options

```
-h, --help help for kill
-s, --signal string Signal to send to the container (default "KILL")
```

### Options inherited from parent commands

```
-D, --debug Switch client log level to DEBUG mode
-H, --host string Specify connecting address of Pouch CLI (default "unix:///var/run/pouchd.sock")
--tlscacert string Specify CA file of TLS
--tlscert string Specify cert file of TLS
--tlskey string Specify key file of TLS
--tlsverify Use TLS and verify remote
```

### SEE ALSO

* [pouch](pouch.md) - An efficient container engine


9 changes: 9 additions & 0 deletions pkg/errtypes/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ var (

// ErrPreCheckFailed represents that failed to pre check.
ErrPreCheckFailed = errorType{codePreCheckFailed, "pre check failed"}

// ErrNoSuchProcess represents there is no such process
ErrNoSuchProcess = errorType{codeNoSuchProcess, "no such process"}
)

const (
Expand All @@ -51,6 +54,7 @@ const (
codeInUse
codeNotModified
codePreCheckFailed
codeNoSuchProcess

// volume error code
codeVolumeExisted
Expand Down Expand Up @@ -97,6 +101,11 @@ func IsNotModified(err error) bool {
return checkError(err, codeNotModified)
}

// IsNoSuchProcess checks the error is no such process error or not.
func IsNoSuchProcess(err error) bool {
return checkError(err, codeNoSuchProcess)
}

// IsPreCheckFailed checks the error is failed to pre check or not.
func IsPreCheckFailed(err error) bool {
return checkError(err, codePreCheckFailed)
Expand Down
Loading