diff --git a/cmd/ctr/commands/run/run_unix.go b/cmd/ctr/commands/run/run_unix.go index 5f553cd65..1291bfd20 100644 --- a/cmd/ctr/commands/run/run_unix.go +++ b/cmd/ctr/commands/run/run_unix.go @@ -27,7 +27,7 @@ import ( "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/contrib/nvidia" "github.com/containerd/containerd/oci" - specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -58,6 +58,7 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli spec containerd.NewContainerOpts ) + cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label")))) if config { opts = append(opts, oci.WithSpecFromFile(context.String("config"))) } else { @@ -98,7 +99,8 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli // Even when "readonly" is set, we don't use KindView snapshot here. (#1495) // We pass writable snapshot to the OCI runtime, and the runtime remounts it as read-only, // after creating some mount points on demand. - containerd.WithNewSnapshot(id, image)) + containerd.WithNewSnapshot(id, image), + containerd.WithImageStopSignal(image, "SIGTERM")) } if context.Bool("readonly") { opts = append(opts, oci.WithRootFSReadonly()) @@ -141,7 +143,6 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli } } - cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label")))) cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil)) var s specs.Spec diff --git a/cmd/ctr/commands/tasks/kill.go b/cmd/ctr/commands/tasks/kill.go index 24e93cafd..080ffa049 100644 --- a/cmd/ctr/commands/tasks/kill.go +++ b/cmd/ctr/commands/tasks/kill.go @@ -23,6 +23,8 @@ import ( "github.com/urfave/cli" ) +const defaultSignal = "SIGTERM" + var killCommand = cli.Command{ Name: "kill", Usage: "signal a container (default: SIGTERM)", @@ -30,7 +32,7 @@ var killCommand = cli.Command{ Flags: []cli.Flag{ cli.StringFlag{ Name: "signal, s", - Value: "SIGTERM", + Value: "", Usage: "signal to send to the container", }, cli.StringFlag{ @@ -47,7 +49,7 @@ var killCommand = cli.Command{ if id == "" { return errors.New("container id must be provided") } - signal, err := containerd.ParseSignal(context.String("signal")) + signal, err := containerd.ParseSignal(defaultSignal) if err != nil { return err } @@ -74,6 +76,17 @@ var killCommand = cli.Command{ if err != nil { return err } + if context.String("signal") != "" { + signal, err = containerd.ParseSignal(context.String("signal")) + if err != nil { + return err + } + } else { + signal, err = containerd.GetStopSignal(ctx, container, signal) + if err != nil { + return err + } + } task, err := container.Task(ctx, nil) if err != nil { return err diff --git a/container_opts.go b/container_opts.go index 580715fee..ca4bf6748 100644 --- a/container_opts.go +++ b/container_opts.go @@ -76,6 +76,23 @@ func WithContainerLabels(labels map[string]string) NewContainerOpts { } } +// WithImageStopSignal sets a well-known containerd label (StopSignalLabel) +// on the container for storing the stop signal specified in the OCI image +// config +func WithImageStopSignal(image Image, defaultSignal string) NewContainerOpts { + return func(ctx context.Context, _ *Client, c *containers.Container) error { + if c.Labels == nil { + c.Labels = make(map[string]string) + } + stopSignal, err := GetOCIStopSignal(ctx, image, defaultSignal) + if err != nil { + return err + } + c.Labels[StopSignalLabel] = stopSignal + return nil + } +} + // WithSnapshotter sets the provided snapshotter for use by the container // // This option must appear before other snapshotter options to have an effect. diff --git a/signals.go b/signals.go index cdcb5e72d..32c34309d 100644 --- a/signals.go +++ b/signals.go @@ -17,12 +17,73 @@ package containerd import ( + "context" + "encoding/json" "fmt" "strconv" "strings" "syscall" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/images" + "github.com/opencontainers/image-spec/specs-go/v1" ) +// StopSignalLabel is a well-known containerd label for storing the stop +// signal specified in the OCI image config +const StopSignalLabel = "io.containerd.image.config.stop-signal" + +// GetStopSignal retrieves the container stop signal, specified by the +// well-known containerd label (StopSignalLabel) +func GetStopSignal(ctx context.Context, container Container, defaultSignal syscall.Signal) (syscall.Signal, error) { + labels, err := container.Labels(ctx) + if err != nil { + return -1, err + } + + if stopSignal, ok := labels[StopSignalLabel]; ok { + return ParseSignal(stopSignal) + } + + return defaultSignal, nil +} + +// GetOCIStopSignal retrieves the stop signal specified in the OCI image config +func GetOCIStopSignal(ctx context.Context, image Image, defaultSignal string) (string, error) { + _, err := ParseSignal(defaultSignal) + if err != nil { + return "", err + } + ic, err := image.Config(ctx) + if err != nil { + return "", err + } + var ( + ociimage v1.Image + config v1.ImageConfig + ) + switch ic.MediaType { + case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: + p, err := content.ReadBlob(ctx, image.ContentStore(), ic) + if err != nil { + return "", err + } + + if err := json.Unmarshal(p, &ociimage); err != nil { + return "", err + } + config = ociimage.Config + default: + return "", fmt.Errorf("unknown image config media type %s", ic.MediaType) + } + + if config.StopSignal == "" { + return defaultSignal, nil + } + + return config.StopSignal, nil +} + // ParseSignal parses a given string into a syscall.Signal // it checks that the signal exists in the platform-appropriate signalMap func ParseSignal(rawSignal string) (syscall.Signal, error) {