Skip to content

Commit 1645ffb

Browse files
committed
feature: cli support add kill command
Signed-off-by: Lang Chi <[email protected]>
1 parent a43bd5f commit 1645ffb

File tree

13 files changed

+483
-0
lines changed

13 files changed

+483
-0
lines changed

apis/server/container_bridge.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"strconv"
1010
"strings"
11+
"syscall"
1112
"time"
1213

1314
"github.com/alibaba/pouch/apis/metrics"
@@ -186,6 +187,25 @@ func (s *Server) getContainers(ctx context.Context, rw http.ResponseWriter, req
186187
return EncodeResponse(rw, http.StatusOK, containerList)
187188
}
188189

190+
func (s *Server) killContainer(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
191+
var sig syscall.Signal
192+
name := mux.Vars(req)["name"]
193+
194+
// If we have a signal, look at it. Otherwise, do nothing
195+
if sigStr := req.FormValue("signal"); sigStr != "" {
196+
var err error
197+
if sig, err = utils.ParseSignal(sigStr); err != nil {
198+
return err
199+
}
200+
}
201+
202+
fmt.Printf("signal: %d\n", uint64(sig))
203+
if err := s.ContainerMgr.Kill(ctx, name, uint64(sig)); err != nil {
204+
return err
205+
}
206+
return nil
207+
}
208+
189209
func (s *Server) startContainer(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
190210
label := util_metrics.ActionStartLabel
191211
defer func(start time.Time) {

apis/server/router.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func initRoute(s *Server) *mux.Router {
4040
{Method: http.MethodGet, Path: "/containers/{name:.*}/checkpoints", HandlerFunc: withCancelHandler(s.listContainerCheckpoint)},
4141
{Method: http.MethodDelete, Path: "/containers/{name}/checkpoints/{id}", HandlerFunc: withCancelHandler(s.deleteContainerCheckpoint)},
4242
{Method: http.MethodPost, Path: "/containers/create", HandlerFunc: s.createContainer},
43+
{Method: http.MethodPost, Path: "/containers/{name:.*}/kill", HandlerFunc: s.killContainer},
4344
{Method: http.MethodPost, Path: "/containers/{name:.*}/start", HandlerFunc: s.startContainer},
4445
{Method: http.MethodPost, Path: "/containers/{name:.*}/stop", HandlerFunc: s.stopContainer},
4546
{Method: http.MethodPost, Path: "/containers/{name:.*}/attach", HandlerFunc: s.attachContainer},

cli/kill.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"github.com/spf13/cobra"
8+
"strings"
9+
)
10+
11+
// killDescription is used to describe kill command in detail and auto generate command doc.
12+
var killDescription = "Kill one or more running container objects in Pouchd. " +
13+
"You can kill a container using the container’s ID, ID-prefix, or name. " +
14+
"This is useful when you wish to kill a container which is running."
15+
16+
// KillCommand use to implement 'kill' command, it kill one or more containers.
17+
type KillCommand struct {
18+
baseCommand
19+
signal string
20+
}
21+
22+
// Init initialize kill command.
23+
func (s *KillCommand) Init(c *Cli) {
24+
s.cli = c
25+
s.cmd = &cobra.Command{
26+
Use: "kill [OPTIONS] CONTAINER [CONTAINER...]",
27+
Short: "kill one or more running containers",
28+
Long: killDescription,
29+
Args: cobra.MinimumNArgs(1),
30+
RunE: func(cmd *cobra.Command, args []string) error {
31+
return s.runKill(args)
32+
},
33+
Example: killExample(),
34+
}
35+
s.addFlags()
36+
}
37+
38+
// addFlags adds flags for specific command.
39+
func (s *KillCommand) addFlags() {
40+
flagSet := s.cmd.Flags()
41+
flagSet.StringVarP(&s.signal, "signal", "s", "KILL", "Signal to send to the container")
42+
43+
}
44+
45+
// runKill is the entry of kill command.
46+
func (s *KillCommand) runKill(args []string) error {
47+
ctx := context.Background()
48+
apiClient := s.cli.Client()
49+
50+
var errs []string
51+
for _, name := range args {
52+
if err := apiClient.ContainerKill(ctx, name, s.signal); err != nil {
53+
errs = append(errs, err.Error())
54+
continue
55+
}
56+
fmt.Printf("%s\n", name)
57+
}
58+
59+
if len(errs) > 0 {
60+
return errors.New(strings.Join(errs, "\n"))
61+
}
62+
63+
return nil
64+
}
65+
66+
// killExample shows examples in kill command, and is used in auto-generated cli docs.
67+
func killExample() string {
68+
return `$ pouch ps -a
69+
Name ID Status Created Image Runtime
70+
foo2 5a0ede Up 2 seconds 3 second ago registry.hub.docker.com/library/busybox:latest runc
71+
foo1 e05637 Up 6 seconds 7 seconds ago registry.hub.docker.com/library/busybox:latest runc
72+
$ pouch kill foo1
73+
foo1
74+
$ pouch ps
75+
Name ID Status Created Image Runtime
76+
foo2 5a0ede Up 11 seconds 12 seconds ago registry.hub.docker.com/library/busybox:latest runc
77+
`
78+
}

cli/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func main() {
1818
cli.AddCommand(base, &PullCommand{})
1919
cli.AddCommand(base, &PushCommand{})
2020
cli.AddCommand(base, &CreateCommand{})
21+
cli.AddCommand(base, &KillCommand{})
2122
cli.AddCommand(base, &StartCommand{})
2223
cli.AddCommand(base, &StopCommand{})
2324
cli.AddCommand(base, &PsCommand{})

client/container_kill.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"net/url"
6+
)
7+
8+
// ContainerKill kills a container.
9+
func (client *APIClient) ContainerKill(ctx context.Context, name, signal string) error {
10+
q := url.Values{}
11+
q.Add("signal", signal)
12+
13+
resp, err := client.post(ctx, "/containers/"+name+"/kill", q, nil, nil)
14+
ensureCloseReader(resp)
15+
16+
return err
17+
}

client/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type CommonAPIClient interface {
2323
type ContainerAPIClient interface {
2424
ContainerCreate(ctx context.Context, config types.ContainerConfig, hostConfig *types.HostConfig, networkConfig *types.NetworkingConfig, containerName string) (*types.ContainerCreateResp, error)
2525
ContainerStart(ctx context.Context, name string, options types.ContainerStartOptions) error
26+
ContainerKill(ctx context.Context, name, signal string) error
2627
ContainerStop(ctx context.Context, name, timeout string) error
2728
ContainerRemove(ctx context.Context, name string, options *types.ContainerRemoveOptions) error
2829
ContainerList(ctx context.Context, option types.ContainerListOptions) ([]*types.Container, error)

ctrd/container.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,47 @@ func (c *Client) recoverContainer(ctx context.Context, id string, io *containeri
352352
return nil
353353
}
354354

355+
// KillContainer will kill a container by signal
356+
func (c *Client) KillContainer(ctx context.Context, containerID string, signal int) error {
357+
err := c.killContainer(ctx, containerID, signal)
358+
if err != nil {
359+
return convertCtrdErr(err)
360+
}
361+
return nil
362+
}
363+
364+
// killContainer is the real process of killing a container
365+
func (c *Client) killContainer(ctx context.Context, containerID string, signal int) error {
366+
wrapperCli, err := c.Get(ctx)
367+
if err != nil {
368+
return fmt.Errorf("failed to get a containerd grpc client: %v", err)
369+
}
370+
371+
ctx = leases.WithLease(ctx, wrapperCli.lease.ID)
372+
373+
if !c.lock.TrylockWithRetry(ctx, containerID) {
374+
return errtypes.ErrLockfailed
375+
}
376+
defer c.lock.Unlock(containerID)
377+
378+
pack, err := c.watch.get(containerID)
379+
if err != nil {
380+
return err
381+
}
382+
// if you call DestroyContainer to stop a container, will skip the hooks.
383+
// the caller need to execute the all hooks.
384+
pack.l.Lock()
385+
pack.skipStopHooks = true
386+
pack.l.Unlock()
387+
defer func() {
388+
pack.l.Lock()
389+
pack.skipStopHooks = false
390+
pack.l.Unlock()
391+
}()
392+
393+
return pack.task.Kill(ctx, syscall.Signal(signal), containerd.WithKillAll)
394+
}
395+
355396
// DestroyContainer kill container and delete it.
356397
func (c *Client) DestroyContainer(ctx context.Context, id string, timeout int64) (*Message, error) {
357398
msg, err := c.destroyContainer(ctx, id, timeout)

ctrd/interface.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ type APIClient interface {
3131

3232
// ContainerAPIClient provides access to containerd container features.
3333
type ContainerAPIClient interface {
34+
// Signal kill container by signal
35+
KillContainer(ctx context.Context, containerID string, signal int) error
3436
// CreateContainer creates a containerd container and start process.
3537
CreateContainer(ctx context.Context, container *Container, checkpointDir string) error
3638
// DestroyContainer kill container and delete it.

daemon/mgr/container.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ type ContainerMgr interface {
6868
// List returns the list of containers.
6969
List(ctx context.Context, option *ContainerListOption) ([]*Container, error)
7070

71+
// Kill one or more running containers
72+
Kill(ctx context.Context, name string, signal uint64) (err error)
73+
7174
// Start a container.
7275
Start(ctx context.Context, id string, options *types.ContainerStartOptions) error
7376

daemon/mgr/container_kill.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package mgr
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/sirupsen/logrus"
7+
"strings"
8+
"syscall"
9+
)
10+
11+
type errNoSuchProcess struct {
12+
pid int64
13+
signal int
14+
}
15+
16+
func isErrNoSuchProcess(err error) bool {
17+
_, ok := err.(errNoSuchProcess)
18+
return ok
19+
}
20+
21+
func (e errNoSuchProcess) Error() string {
22+
return fmt.Sprintf("Cannot kill process (pid=%d) with signal %d: no such process.", e.pid, e.signal)
23+
}
24+
25+
// Kill one or more running containers
26+
func (mgr *ContainerManager) Kill(ctx context.Context, name string, signal uint64) (err error) {
27+
c, err := mgr.container(name)
28+
if err != nil {
29+
return err
30+
}
31+
32+
if signal == 0 || syscall.Signal(signal) == syscall.SIGKILL {
33+
return mgr.kill(ctx, c)
34+
}
35+
return mgr.killWithSignal(ctx, c, int(signal))
36+
}
37+
38+
func (mgr *ContainerManager) kill(ctx context.Context, c *Container) error {
39+
if !c.IsRunning() {
40+
return fmt.Errorf("Container %s is not running", c.ID)
41+
}
42+
43+
if err := mgr.killDeadProcess(ctx, c, int(syscall.SIGKILL)); err != nil {
44+
if isErrNoSuchProcess(err) {
45+
return nil
46+
}
47+
48+
if c.IsRunning() {
49+
return err
50+
}
51+
}
52+
53+
if pid := c.State.Pid; pid != 0 {
54+
if err := syscall.Kill(int(pid), 9); err != nil {
55+
if err != syscall.ESRCH {
56+
return err
57+
}
58+
e := errNoSuchProcess{pid, 9}
59+
logrus.Debug(e)
60+
return nil
61+
}
62+
}
63+
return nil
64+
}
65+
66+
func (mgr *ContainerManager) killDeadProcess(ctx context.Context, c *Container, signal int) error {
67+
err := mgr.killWithSignal(ctx, c, signal)
68+
if err == syscall.ESRCH {
69+
e := errNoSuchProcess{c.State.Pid, signal}
70+
logrus.Debug(e)
71+
return e
72+
}
73+
return err
74+
}
75+
76+
func (mgr *ContainerManager) killWithSignal(ctx context.Context, c *Container, signal int) error {
77+
logrus.Debugf("Sending %d to %s", signal, c.ID)
78+
c.Lock()
79+
defer c.Unlock()
80+
81+
if c.State.Paused {
82+
return fmt.Errorf("Container %s is paused. Unpause the container before stopping", c.ID)
83+
}
84+
85+
if !c.State.Running {
86+
return fmt.Errorf("Container %s is not running", c.ID)
87+
}
88+
89+
if err := mgr.Client.KillContainer(ctx, c.ID, signal); err != nil {
90+
err = fmt.Errorf("Cannot kill container %s: %s", c.ID, err)
91+
// if container or process not exists, ignore the error
92+
if strings.Contains(err.Error(), "container not found") ||
93+
strings.Contains(err.Error(), "no such process") {
94+
logrus.Warnf("container kill failed because of 'container not found' or 'no such process': %s", err.Error())
95+
} else {
96+
return err
97+
}
98+
}
99+
attributes := map[string]string{
100+
"signal": fmt.Sprintf("%d", signal),
101+
}
102+
mgr.LogContainerEventWithAttributes(ctx, c, "kill", attributes)
103+
return nil
104+
}

0 commit comments

Comments
 (0)