github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/container/kill.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package container 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "os" 24 "strings" 25 "syscall" 26 27 "github.com/containerd/containerd" 28 "github.com/containerd/containerd/cio" 29 "github.com/containerd/containerd/errdefs" 30 gocni "github.com/containerd/go-cni" 31 "github.com/containerd/log" 32 "github.com/containerd/nerdctl/v2/pkg/api/types" 33 "github.com/containerd/nerdctl/v2/pkg/containerutil" 34 "github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker" 35 "github.com/containerd/nerdctl/v2/pkg/labels" 36 "github.com/containerd/nerdctl/v2/pkg/netutil" 37 "github.com/containerd/nerdctl/v2/pkg/netutil/nettype" 38 "github.com/containerd/nerdctl/v2/pkg/portutil" 39 "github.com/containerd/nerdctl/v2/pkg/rootlessutil" 40 "github.com/moby/sys/signal" 41 ) 42 43 // Kill kills a list of containers 44 func Kill(ctx context.Context, client *containerd.Client, reqs []string, options types.ContainerKillOptions) error { 45 if !strings.HasPrefix(options.KillSignal, "SIG") { 46 options.KillSignal = "SIG" + options.KillSignal 47 } 48 49 parsedSignal, err := signal.ParseSignal(options.KillSignal) 50 if err != nil { 51 return err 52 } 53 54 walker := &containerwalker.ContainerWalker{ 55 Client: client, 56 OnFound: func(ctx context.Context, found containerwalker.Found) error { 57 if found.MatchCount > 1 { 58 return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req) 59 } 60 if err := cleanupNetwork(ctx, found.Container, options.GOptions); err != nil { 61 return fmt.Errorf("unable to cleanup network for container: %s, %q", found.Req, err) 62 } 63 if err := killContainer(ctx, found.Container, parsedSignal); err != nil { 64 if errdefs.IsNotFound(err) { 65 fmt.Fprintf(options.Stderr, "No such container: %s\n", found.Req) 66 os.Exit(1) 67 } 68 return err 69 } 70 _, err := fmt.Fprintln(options.Stdout, found.Container.ID()) 71 return err 72 }, 73 } 74 75 return walker.WalkAll(ctx, reqs, true) 76 } 77 78 func killContainer(ctx context.Context, container containerd.Container, signal syscall.Signal) (err error) { 79 defer func() { 80 if err != nil { 81 containerutil.UpdateErrorLabel(ctx, container, err) 82 } 83 }() 84 if err := containerutil.UpdateExplicitlyStoppedLabel(ctx, container, true); err != nil { 85 return err 86 } 87 task, err := container.Task(ctx, cio.Load) 88 if err != nil { 89 return err 90 } 91 92 status, err := task.Status(ctx) 93 if err != nil { 94 return err 95 } 96 97 paused := false 98 99 switch status.Status { 100 case containerd.Created, containerd.Stopped: 101 return fmt.Errorf("cannot kill container %s: container is not running", container.ID()) 102 case containerd.Paused, containerd.Pausing: 103 paused = true 104 default: 105 } 106 107 if err := task.Kill(ctx, signal); err != nil { 108 return err 109 } 110 111 // signal will be sent once resume is finished 112 if paused { 113 if err := task.Resume(ctx); err != nil { 114 log.G(ctx).Warnf("cannot unpause container %s: %s", container.ID(), err) 115 } 116 } 117 return nil 118 } 119 120 // cleanupNetwork removes cni network setup, specifically the forwards 121 func cleanupNetwork(ctx context.Context, container containerd.Container, globalOpts types.GlobalCommandOptions) error { 122 return rootlessutil.WithDetachedNetNSIfAny(func() error { 123 // retrieve info to get current active port mappings 124 info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata) 125 if err != nil { 126 return err 127 } 128 ports, portErr := portutil.ParsePortsLabel(info.Labels) 129 if portErr != nil { 130 return fmt.Errorf("no oci spec: %q", portErr) 131 } 132 portMappings := []gocni.NamespaceOpts{ 133 gocni.WithCapabilityPortMap(ports), 134 } 135 136 // retrieve info to get cni instance 137 spec, err := container.Spec(ctx) 138 if err != nil { 139 return err 140 } 141 networksJSON := spec.Annotations[labels.Networks] 142 var networks []string 143 if err := json.Unmarshal([]byte(networksJSON), &networks); err != nil { 144 return err 145 } 146 netType, err := nettype.Detect(networks) 147 if err != nil { 148 return err 149 } 150 151 switch netType { 152 case nettype.Host, nettype.None, nettype.Container: 153 // NOP 154 case nettype.CNI: 155 e, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithDefaultNetwork()) 156 if err != nil { 157 return err 158 } 159 cniOpts := []gocni.Opt{ 160 gocni.WithPluginDir([]string{globalOpts.CNIPath}), 161 } 162 netMap, err := e.NetworkMap() 163 if err != nil { 164 return err 165 } 166 for _, netstr := range networks { 167 net, ok := netMap[netstr] 168 if !ok { 169 return fmt.Errorf("no such network: %q", netstr) 170 } 171 cniOpts = append(cniOpts, gocni.WithConfListBytes(net.Bytes)) 172 } 173 cni, err := gocni.New(cniOpts...) 174 if err != nil { 175 return err 176 } 177 178 var namespaceOpts []gocni.NamespaceOpts 179 namespaceOpts = append(namespaceOpts, portMappings...) 180 namespace := spec.Annotations[labels.Namespace] 181 fullID := namespace + "-" + container.ID() 182 if err := cni.Remove(ctx, fullID, "", namespaceOpts...); err != nil { 183 log.L.WithError(err).Errorf("failed to call cni.Remove") 184 return err 185 } 186 return nil 187 default: 188 return fmt.Errorf("unexpected network type %v", netType) 189 } 190 return nil 191 }) 192 }