github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/container_update.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 main 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "runtime" 24 25 "github.com/containerd/containerd" 26 "github.com/containerd/containerd/containers" 27 "github.com/containerd/containerd/pkg/cri/util" 28 "github.com/containerd/errdefs" 29 "github.com/containerd/log" 30 "github.com/containerd/nerdctl/pkg/api/types" 31 "github.com/containerd/nerdctl/pkg/clientutil" 32 nerdctlContainer "github.com/containerd/nerdctl/pkg/cmd/container" 33 "github.com/containerd/nerdctl/pkg/formatter" 34 "github.com/containerd/nerdctl/pkg/idutil/containerwalker" 35 "github.com/containerd/nerdctl/pkg/infoutil" 36 "github.com/containerd/typeurl/v2" 37 "github.com/docker/go-units" 38 runtimespec "github.com/opencontainers/runtime-spec/specs-go" 39 "github.com/spf13/cobra" 40 ) 41 42 type updateResourceOptions struct { 43 CPUPeriod uint64 44 CPUQuota int64 45 CPUShares uint64 46 MemoryLimitInBytes int64 47 MemoryReservation int64 48 MemorySwapInBytes int64 49 CpusetCpus string 50 CpusetMems string 51 PidsLimit int64 52 BlkioWeight uint16 53 } 54 55 func newUpdateCommand() *cobra.Command { 56 var updateCommand = &cobra.Command{ 57 Use: "update [flags] CONTAINER [CONTAINER, ...]", 58 Args: cobra.MinimumNArgs(1), 59 Short: "Update one or more running containers", 60 RunE: updateAction, 61 ValidArgsFunction: updateShellComplete, 62 SilenceUsage: true, 63 SilenceErrors: true, 64 } 65 updateCommand.Flags().SetInterspersed(false) 66 setUpdateFlags(updateCommand) 67 return updateCommand 68 } 69 70 func setUpdateFlags(cmd *cobra.Command) { 71 cmd.Flags().Float64("cpus", 0.0, "Number of CPUs") 72 cmd.Flags().Uint64("cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period") 73 cmd.Flags().Int64("cpu-quota", -1, "Limit CPU CFS (Completely Fair Scheduler) quota") 74 cmd.Flags().Uint64("cpu-shares", 0, "CPU shares (relative weight)") 75 cmd.Flags().StringP("memory", "m", "", "Memory limit") 76 cmd.Flags().String("memory-reservation", "", "Memory soft limit") 77 cmd.Flags().String("memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") 78 cmd.Flags().String("kernel-memory", "", "Kernel memory limit (deprecated)") 79 cmd.Flags().String("cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") 80 cmd.Flags().String("cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)") 81 cmd.Flags().Int64("pids-limit", -1, "Tune container pids limit (set -1 for unlimited)") 82 cmd.Flags().Uint16("blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)") 83 cmd.Flags().String("restart", "no", `Restart policy to apply when a container exits (implemented values: "no"|"always|on-failure:n|unless-stopped")`) 84 cmd.RegisterFlagCompletionFunc("restart", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 85 return []string{"no", "always", "on-failure", "unless-stopped"}, cobra.ShellCompDirectiveNoFileComp 86 }) 87 } 88 89 func updateAction(cmd *cobra.Command, args []string) error { 90 globalOptions, err := processRootCmdFlags(cmd) 91 if err != nil { 92 return err 93 } 94 client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address) 95 if err != nil { 96 return err 97 } 98 defer cancel() 99 options, err := getUpdateOption(cmd, globalOptions) 100 if err != nil { 101 return err 102 } 103 walker := &containerwalker.ContainerWalker{ 104 Client: client, 105 OnFound: func(ctx context.Context, found containerwalker.Found) error { 106 if found.MatchCount > 1 { 107 return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req) 108 } 109 err = updateContainer(ctx, client, found.Container.ID(), options, cmd) 110 return err 111 }, 112 } 113 114 return walker.WalkAll(ctx, args, true) 115 } 116 117 func getUpdateOption(cmd *cobra.Command, globalOptions types.GlobalCommandOptions) (updateResourceOptions, error) { 118 var options updateResourceOptions 119 cpus, err := cmd.Flags().GetFloat64("cpus") 120 if err != nil { 121 return options, err 122 } 123 cpuPeriod, err := cmd.Flags().GetUint64("cpu-period") 124 if err != nil { 125 return options, err 126 } 127 cpuQuota, err := cmd.Flags().GetInt64("cpu-quota") 128 if err != nil { 129 return options, err 130 } 131 if cpuQuota != -1 || cpuPeriod != 0 { 132 if cpus > 0.0 { 133 return options, errors.New("cpus and quota/period should be used separately") 134 } 135 } 136 if cpus > 0.0 { 137 cpuPeriod = uint64(100000) 138 cpuQuota = int64(cpus * 100000.0) 139 } 140 shares, err := cmd.Flags().GetUint64("cpu-shares") 141 if err != nil { 142 return options, err 143 } 144 memStr, err := cmd.Flags().GetString("memory") 145 if err != nil { 146 return options, err 147 } 148 memSwap, err := cmd.Flags().GetString("memory-swap") 149 if err != nil { 150 return options, err 151 } 152 var mem64 int64 153 if memStr != "" { 154 mem64, err = units.RAMInBytes(memStr) 155 if err != nil { 156 return options, fmt.Errorf("failed to parse memory bytes %q: %w", memStr, err) 157 } 158 } 159 var memSwap64 int64 160 if memSwap != "" { 161 if memSwap == "-1" { 162 memSwap64 = -1 163 } else { 164 memSwap64, err = units.RAMInBytes(memSwap) 165 if err != nil { 166 return options, fmt.Errorf("failed to parse memory-swap bytes %q: %w", memSwap, err) 167 } 168 if mem64 > 0 && memSwap64 > 0 && memSwap64 < mem64 { 169 return options, fmt.Errorf("minimum memoryswap limit should be larger than memory limit, see usage") 170 } 171 } 172 } else { 173 memSwap64 = mem64 * 2 174 } 175 if memSwap64 == 0 { 176 memSwap64 = mem64 * 2 177 } 178 memReserve, err := cmd.Flags().GetString("memory-reservation") 179 if err != nil { 180 return options, err 181 } 182 var memReserve64 int64 183 if memReserve != "" { 184 memReserve64, err = units.RAMInBytes(memReserve) 185 if err != nil { 186 return options, fmt.Errorf("failed to parse memory bytes %q: %w", memReserve, err) 187 } 188 } 189 if mem64 > 0 && memReserve64 > 0 && mem64 < memReserve64 { 190 return options, fmt.Errorf("minimum memory limit can not be less than memory reservation limit, see usage") 191 } 192 193 kernelMemStr, err := cmd.Flags().GetString("kernel-memory") 194 if err != nil { 195 return options, err 196 } 197 if kernelMemStr != "" && cmd.Flag("kernel-memory").Changed { 198 log.L.Warnf("The --kernel-memory flag is no longer supported. This flag is a noop.") 199 } 200 cpuset, err := cmd.Flags().GetString("cpuset-cpus") 201 if err != nil { 202 return options, err 203 } 204 cpusetMems, err := cmd.Flags().GetString("cpuset-mems") 205 if err != nil { 206 return options, err 207 } 208 pidsLimit, err := cmd.Flags().GetInt64("pids-limit") 209 if err != nil { 210 return options, err 211 } 212 blkioWeight, err := cmd.Flags().GetUint16("blkio-weight") 213 if err != nil { 214 return options, err 215 } 216 if blkioWeight != 0 && !infoutil.BlockIOWeight(globalOptions.CgroupManager) { 217 return options, fmt.Errorf("kernel support for cgroup blkio weight missing, weight discarded") 218 } 219 if blkioWeight > 0 && blkioWeight < 10 || blkioWeight > 1000 { 220 return options, errors.New("range of blkio weight is from 10 to 1000") 221 } 222 223 if runtime.GOOS == "linux" { 224 options = updateResourceOptions{ 225 CPUPeriod: cpuPeriod, 226 CPUQuota: cpuQuota, 227 CPUShares: shares, 228 CpusetCpus: cpuset, 229 CpusetMems: cpusetMems, 230 MemoryLimitInBytes: mem64, 231 MemoryReservation: memReserve64, 232 MemorySwapInBytes: memSwap64, 233 PidsLimit: pidsLimit, 234 BlkioWeight: blkioWeight, 235 } 236 } 237 return options, nil 238 } 239 240 func updateContainer(ctx context.Context, client *containerd.Client, id string, opts updateResourceOptions, cmd *cobra.Command) error { 241 container, err := client.LoadContainer(ctx, id) 242 if err != nil { 243 return err 244 } 245 cStatus := formatter.ContainerStatus(ctx, container) 246 if cStatus == "pausing" { 247 return fmt.Errorf("container %q is in pausing state", id) 248 } 249 spec, err := container.Spec(ctx) 250 if err != nil { 251 return err 252 } 253 254 oldSpec, err := copySpec(spec) 255 if err != nil { 256 return err 257 } 258 if runtime.GOOS == "linux" { 259 if spec.Linux == nil { 260 spec.Linux = &runtimespec.Linux{} 261 } 262 if spec.Linux.Resources == nil { 263 spec.Linux.Resources = &runtimespec.LinuxResources{} 264 } 265 if spec.Linux.Resources.BlockIO == nil { 266 spec.Linux.Resources.BlockIO = &runtimespec.LinuxBlockIO{} 267 } 268 if cmd.Flags().Changed("blkio-weight") { 269 if spec.Linux.Resources.BlockIO.Weight != &opts.BlkioWeight { 270 spec.Linux.Resources.BlockIO.Weight = &opts.BlkioWeight 271 } 272 } 273 if spec.Linux.Resources.CPU == nil { 274 spec.Linux.Resources.CPU = &runtimespec.LinuxCPU{} 275 } 276 if cmd.Flags().Changed("cpu-shares") { 277 if spec.Linux.Resources.CPU.Shares != &opts.CPUShares { 278 spec.Linux.Resources.CPU.Shares = &opts.CPUShares 279 } 280 } 281 if cmd.Flags().Changed("cpu-quota") { 282 if spec.Linux.Resources.CPU.Quota != &opts.CPUQuota { 283 spec.Linux.Resources.CPU.Quota = &opts.CPUQuota 284 } 285 } 286 if cmd.Flags().Changed("cpu-period") { 287 if spec.Linux.Resources.CPU.Period != &opts.CPUPeriod { 288 spec.Linux.Resources.CPU.Period = &opts.CPUPeriod 289 } 290 } 291 if cmd.Flags().Changed("cpus") { 292 if spec.Linux.Resources.CPU.Cpus != opts.CpusetCpus { 293 spec.Linux.Resources.CPU.Cpus = opts.CpusetCpus 294 } 295 } 296 if cmd.Flags().Changed("cpuset-mems") { 297 if spec.Linux.Resources.CPU.Mems != opts.CpusetMems { 298 spec.Linux.Resources.CPU.Mems = opts.CpusetMems 299 } 300 } 301 302 if cmd.Flags().Changed("cpuset-cpus") { 303 if spec.Linux.Resources.CPU.Cpus != opts.CpusetCpus { 304 spec.Linux.Resources.CPU.Cpus = opts.CpusetCpus 305 } 306 } 307 if spec.Linux.Resources.Memory == nil { 308 spec.Linux.Resources.Memory = &runtimespec.LinuxMemory{} 309 } 310 if cmd.Flags().Changed("memory") { 311 if spec.Linux.Resources.Memory.Limit != &opts.MemoryLimitInBytes { 312 spec.Linux.Resources.Memory.Limit = &opts.MemoryLimitInBytes 313 } 314 if spec.Linux.Resources.Memory.Swap != &opts.MemorySwapInBytes { 315 spec.Linux.Resources.Memory.Swap = &opts.MemorySwapInBytes 316 } 317 } 318 if cmd.Flags().Changed("memory-reservation") { 319 if spec.Linux.Resources.Memory.Reservation != &opts.MemoryReservation { 320 spec.Linux.Resources.Memory.Reservation = &opts.MemoryReservation 321 } 322 } 323 if spec.Linux.Resources.Pids == nil { 324 spec.Linux.Resources.Pids = &runtimespec.LinuxPids{} 325 } 326 if cmd.Flags().Changed("pids-limit") { 327 if spec.Linux.Resources.Pids.Limit != opts.PidsLimit { 328 spec.Linux.Resources.Pids.Limit = opts.PidsLimit 329 } 330 } 331 } 332 333 if err := updateContainerSpec(ctx, container, spec); err != nil { 334 log.G(ctx).WithError(err).Errorf("Failed to update spec %+v for container %q", spec, id) 335 // reset spec on error. 336 if err := updateContainerSpec(ctx, container, oldSpec); err != nil { 337 log.G(ctx).WithError(err).Errorf("Failed to update spec %+v for container %q", oldSpec, id) 338 } 339 } 340 341 restart, err := cmd.Flags().GetString("restart") 342 if err != nil { 343 return err 344 } 345 if cmd.Flags().Changed("restart") && restart != "" { 346 if err := nerdctlContainer.UpdateContainerRestartPolicyLabel(ctx, client, container, restart); err != nil { 347 return err 348 } 349 } 350 351 // If container is not running, only update spec is enough, new resource 352 // limit will be applied when container start. 353 if cStatus != "Up" { 354 return nil 355 } 356 task, err := container.Task(ctx, nil) 357 if err != nil { 358 if errdefs.IsNotFound(err) { 359 // Task exited already. 360 return nil 361 } 362 return fmt.Errorf("failed to get task:%w", err) 363 } 364 return task.Update(ctx, containerd.WithResources(spec.Linux.Resources)) 365 } 366 367 func updateContainerSpec(ctx context.Context, container containerd.Container, spec *runtimespec.Spec) error { 368 if err := container.Update(ctx, func(ctx context.Context, client *containerd.Client, c *containers.Container) error { 369 a, err := typeurl.MarshalAny(spec) 370 if err != nil { 371 return fmt.Errorf("failed to marshal spec %+v:%w", spec, err) 372 } 373 c.Spec = a 374 return nil 375 }); err != nil { 376 return fmt.Errorf("failed to update container spec:%w", err) 377 } 378 return nil 379 } 380 381 func copySpec(spec *runtimespec.Spec) (*runtimespec.Spec, error) { 382 var copySpec runtimespec.Spec 383 if err := util.DeepCopy(©Spec, spec); err != nil { 384 return nil, fmt.Errorf("failed to deep copy:%w", err) 385 } 386 return ©Spec, nil 387 } 388 389 func updateShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 390 return shellCompleteContainerNames(cmd, nil) 391 }