github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/update.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"strconv"
     9  
    10  	"github.com/opencontainers/runc/libcontainer/cgroups"
    11  	"github.com/sirupsen/logrus"
    12  
    13  	"github.com/docker/go-units"
    14  	"github.com/opencontainers/runc/libcontainer/configs"
    15  	"github.com/opencontainers/runc/libcontainer/intelrdt"
    16  	"github.com/opencontainers/runtime-spec/specs-go"
    17  	"github.com/urfave/cli"
    18  )
    19  
    20  func i64Ptr(i int64) *int64   { return &i }
    21  func u64Ptr(i uint64) *uint64 { return &i }
    22  func u16Ptr(i uint16) *uint16 { return &i }
    23  func boolPtr(b bool) *bool    { return &b }
    24  
    25  var updateCommand = cli.Command{
    26  	Name:      "update",
    27  	Usage:     "update container resource constraints",
    28  	ArgsUsage: `<container-id>`,
    29  	Flags: []cli.Flag{
    30  		cli.StringFlag{
    31  			Name:  "resources, r",
    32  			Value: "",
    33  			Usage: `path to the file containing the resources to update or '-' to read from the standard input
    34  
    35  The accepted format is as follow (unchanged values can be omitted):
    36  
    37  {
    38    "memory": {
    39      "limit": 0,
    40      "reservation": 0,
    41      "swap": 0,
    42      "checkBeforeUpdate": true
    43    },
    44    "cpu": {
    45      "shares": 0,
    46      "quota": 0,
    47      "burst": 0,
    48      "period": 0,
    49      "realtimeRuntime": 0,
    50      "realtimePeriod": 0,
    51      "cpus": "",
    52      "mems": "",
    53      "idle": 0
    54    },
    55    "blockIO": {
    56      "weight": 0
    57    }
    58  }
    59  
    60  Note: if data is to be read from a file or the standard input, all
    61  other options are ignored.
    62  `,
    63  		},
    64  
    65  		cli.IntFlag{
    66  			Name:  "blkio-weight",
    67  			Usage: "Specifies per cgroup weight, range is from 10 to 1000",
    68  		},
    69  		cli.StringFlag{
    70  			Name:  "cpu-period",
    71  			Usage: "CPU CFS period to be used for hardcapping (in usecs). 0 to use system default",
    72  		},
    73  		cli.StringFlag{
    74  			Name:  "cpu-quota",
    75  			Usage: "CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period",
    76  		},
    77  		cli.StringFlag{
    78  			Name:  "cpu-burst",
    79  			Usage: "CPU CFS hardcap burst limit (in usecs). Allowed accumulated cpu time additionally for burst a given period",
    80  		},
    81  		cli.StringFlag{
    82  			Name:  "cpu-share",
    83  			Usage: "CPU shares (relative weight vs. other containers)",
    84  		},
    85  		cli.StringFlag{
    86  			Name:  "cpu-rt-period",
    87  			Usage: "CPU realtime period to be used for hardcapping (in usecs). 0 to use system default",
    88  		},
    89  		cli.StringFlag{
    90  			Name:  "cpu-rt-runtime",
    91  			Usage: "CPU realtime hardcap limit (in usecs). Allowed cpu time in a given period",
    92  		},
    93  		cli.StringFlag{
    94  			Name:  "cpuset-cpus",
    95  			Usage: "CPU(s) to use",
    96  		},
    97  		cli.StringFlag{
    98  			Name:  "cpuset-mems",
    99  			Usage: "Memory node(s) to use",
   100  		},
   101  		cli.StringFlag{
   102  			Name:   "kernel-memory",
   103  			Usage:  "(obsoleted; do not use)",
   104  			Hidden: true,
   105  		},
   106  		cli.StringFlag{
   107  			Name:   "kernel-memory-tcp",
   108  			Usage:  "(obsoleted; do not use)",
   109  			Hidden: true,
   110  		},
   111  		cli.StringFlag{
   112  			Name:  "memory",
   113  			Usage: "Memory limit (in bytes)",
   114  		},
   115  		cli.StringFlag{
   116  			Name:  "cpu-idle",
   117  			Usage: "set cgroup SCHED_IDLE or not, 0: default behavior, 1: SCHED_IDLE",
   118  		},
   119  		cli.StringFlag{
   120  			Name:  "memory-reservation",
   121  			Usage: "Memory reservation or soft_limit (in bytes)",
   122  		},
   123  		cli.StringFlag{
   124  			Name:  "memory-swap",
   125  			Usage: "Total memory usage (memory + swap); set '-1' to enable unlimited swap",
   126  		},
   127  		cli.IntFlag{
   128  			Name:  "pids-limit",
   129  			Usage: "Maximum number of pids allowed in the container",
   130  		},
   131  		cli.StringFlag{
   132  			Name:  "l3-cache-schema",
   133  			Usage: "The string of Intel RDT/CAT L3 cache schema",
   134  		},
   135  		cli.StringFlag{
   136  			Name:  "mem-bw-schema",
   137  			Usage: "The string of Intel RDT/MBA memory bandwidth schema",
   138  		},
   139  	},
   140  	Action: func(context *cli.Context) error {
   141  		if err := checkArgs(context, 1, exactArgs); err != nil {
   142  			return err
   143  		}
   144  		container, err := getContainer(context)
   145  		if err != nil {
   146  			return err
   147  		}
   148  
   149  		r := specs.LinuxResources{
   150  			// nil and u64Ptr(0) are not interchangeable
   151  			Memory: &specs.LinuxMemory{
   152  				CheckBeforeUpdate: boolPtr(false), // constant
   153  			},
   154  			CPU:     &specs.LinuxCPU{},
   155  			BlockIO: &specs.LinuxBlockIO{},
   156  			Pids:    &specs.LinuxPids{},
   157  		}
   158  
   159  		config := container.Config()
   160  
   161  		if in := context.String("resources"); in != "" {
   162  			var (
   163  				f   *os.File
   164  				err error
   165  			)
   166  			switch in {
   167  			case "-":
   168  				f = os.Stdin
   169  			default:
   170  				f, err = os.Open(in)
   171  				if err != nil {
   172  					return err
   173  				}
   174  				defer f.Close()
   175  			}
   176  			err = json.NewDecoder(f).Decode(&r)
   177  			if err != nil {
   178  				return err
   179  			}
   180  		} else {
   181  			if val := context.Int("blkio-weight"); val != 0 {
   182  				r.BlockIO.Weight = u16Ptr(uint16(val))
   183  			}
   184  			if val := context.String("cpuset-cpus"); val != "" {
   185  				r.CPU.Cpus = val
   186  			}
   187  			if val := context.String("cpuset-mems"); val != "" {
   188  				r.CPU.Mems = val
   189  			}
   190  			if val := context.String("cpu-idle"); val != "" {
   191  				idle, err := strconv.ParseInt(val, 10, 64)
   192  				if err != nil {
   193  					return fmt.Errorf("invalid value for cpu-idle: %w", err)
   194  				}
   195  				r.CPU.Idle = i64Ptr(idle)
   196  			}
   197  
   198  			for _, pair := range []struct {
   199  				opt  string
   200  				dest **uint64
   201  			}{
   202  				{"cpu-burst", &r.CPU.Burst},
   203  				{"cpu-period", &r.CPU.Period},
   204  				{"cpu-rt-period", &r.CPU.RealtimePeriod},
   205  				{"cpu-share", &r.CPU.Shares},
   206  			} {
   207  				if val := context.String(pair.opt); val != "" {
   208  					v, err := strconv.ParseUint(val, 10, 64)
   209  					if err != nil {
   210  						return fmt.Errorf("invalid value for %s: %w", pair.opt, err)
   211  					}
   212  					*pair.dest = &v
   213  				}
   214  			}
   215  			for _, pair := range []struct {
   216  				opt  string
   217  				dest **int64
   218  			}{
   219  				{"cpu-quota", &r.CPU.Quota},
   220  				{"cpu-rt-runtime", &r.CPU.RealtimeRuntime},
   221  			} {
   222  				if val := context.String(pair.opt); val != "" {
   223  					v, err := strconv.ParseInt(val, 10, 64)
   224  					if err != nil {
   225  						return fmt.Errorf("invalid value for %s: %w", pair.opt, err)
   226  					}
   227  					*pair.dest = &v
   228  				}
   229  			}
   230  			for _, pair := range []struct {
   231  				opt  string
   232  				dest **int64
   233  			}{
   234  				{"memory", &r.Memory.Limit},
   235  				{"memory-swap", &r.Memory.Swap},
   236  				{"kernel-memory", &r.Memory.Kernel}, //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
   237  				{"kernel-memory-tcp", &r.Memory.KernelTCP},
   238  				{"memory-reservation", &r.Memory.Reservation},
   239  			} {
   240  				if val := context.String(pair.opt); val != "" {
   241  					var v int64
   242  
   243  					if val != "-1" {
   244  						v, err = units.RAMInBytes(val)
   245  						if err != nil {
   246  							return fmt.Errorf("invalid value for %s: %w", pair.opt, err)
   247  						}
   248  					} else {
   249  						v = -1
   250  					}
   251  					*pair.dest = &v
   252  				}
   253  			}
   254  
   255  			r.Pids.Limit = int64(context.Int("pids-limit"))
   256  		}
   257  
   258  		// Fix up values
   259  		if r.Memory.Limit != nil && *r.Memory.Limit == -1 && r.Memory.Swap == nil {
   260  			// To avoid error "unable to set swap limit without memory limit"
   261  			r.Memory.Swap = i64Ptr(0)
   262  		}
   263  		if r.CPU.Idle != nil && r.CPU.Shares == nil {
   264  			// To avoid error "failed to write \"4\": write /sys/fs/cgroup/runc-cgroups-integration-test/test-cgroup-7341/cpu.weight: invalid argument"
   265  			r.CPU.Shares = u64Ptr(0)
   266  		}
   267  
   268  		if (r.Memory.Kernel != nil) || (r.Memory.KernelTCP != nil) { //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
   269  			logrus.Warn("Kernel memory settings are ignored and will be removed")
   270  		}
   271  
   272  		// Update the values
   273  		if r.BlockIO.Weight != nil {
   274  			config.Cgroups.Resources.BlkioWeight = *r.BlockIO.Weight
   275  		}
   276  
   277  		// Setting CPU quota and period independently does not make much sense,
   278  		// but historically runc allowed it and this needs to be supported
   279  		// to not break compatibility.
   280  		//
   281  		// For systemd cgroup drivers to set CPU quota/period correctly,
   282  		// it needs to know both values. For fs2 cgroup driver to be compatible
   283  		// with the fs driver, it also needs to know both values.
   284  		//
   285  		// Here in update, previously set values are available from config.
   286  		// If only one of {quota,period} is set and the other is not, leave
   287  		// the unset parameter at the old value (don't overwrite config).
   288  		var (
   289  			p uint64
   290  			q int64
   291  		)
   292  		if r.CPU.Period != nil {
   293  			p = *r.CPU.Period
   294  		}
   295  		if r.CPU.Quota != nil {
   296  			q = *r.CPU.Quota
   297  		}
   298  		if (p == 0 && q == 0) || (p != 0 && q != 0) {
   299  			// both values are either set or unset (0)
   300  			config.Cgroups.Resources.CpuPeriod = p
   301  			config.Cgroups.Resources.CpuQuota = q
   302  		} else {
   303  			// one is set and the other is not
   304  			if p != 0 {
   305  				// set new period, leave quota at old value
   306  				config.Cgroups.Resources.CpuPeriod = p
   307  			} else if q != 0 {
   308  				// set new quota, leave period at old value
   309  				config.Cgroups.Resources.CpuQuota = q
   310  			}
   311  		}
   312  
   313  		config.Cgroups.Resources.CpuBurst = r.CPU.Burst // can be nil
   314  		if r.CPU.Shares != nil {
   315  			config.Cgroups.Resources.CpuShares = *r.CPU.Shares
   316  			// CpuWeight is used for cgroupv2 and should be converted
   317  			config.Cgroups.Resources.CpuWeight = cgroups.ConvertCPUSharesToCgroupV2Value(*r.CPU.Shares)
   318  		}
   319  		if r.CPU.RealtimePeriod != nil {
   320  			config.Cgroups.Resources.CpuRtPeriod = *r.CPU.RealtimePeriod
   321  		}
   322  		if r.CPU.RealtimeRuntime != nil {
   323  			config.Cgroups.Resources.CpuRtRuntime = *r.CPU.RealtimeRuntime
   324  		}
   325  		config.Cgroups.Resources.CpusetCpus = r.CPU.Cpus
   326  		config.Cgroups.Resources.CpusetMems = r.CPU.Mems
   327  		if r.Memory.Limit != nil {
   328  			config.Cgroups.Resources.Memory = *r.Memory.Limit
   329  		}
   330  		config.Cgroups.Resources.CPUIdle = r.CPU.Idle
   331  		if r.Memory.Reservation != nil {
   332  			config.Cgroups.Resources.MemoryReservation = *r.Memory.Reservation
   333  		}
   334  		if r.Memory.Swap != nil {
   335  			config.Cgroups.Resources.MemorySwap = *r.Memory.Swap
   336  		}
   337  		if r.Memory.CheckBeforeUpdate != nil {
   338  			config.Cgroups.Resources.MemoryCheckBeforeUpdate = *r.Memory.CheckBeforeUpdate
   339  		}
   340  		config.Cgroups.Resources.PidsLimit = r.Pids.Limit
   341  		config.Cgroups.Resources.Unified = r.Unified
   342  
   343  		// Update Intel RDT
   344  		l3CacheSchema := context.String("l3-cache-schema")
   345  		memBwSchema := context.String("mem-bw-schema")
   346  		if l3CacheSchema != "" && !intelrdt.IsCATEnabled() {
   347  			return errors.New("Intel RDT/CAT: l3 cache schema is not enabled")
   348  		}
   349  
   350  		if memBwSchema != "" && !intelrdt.IsMBAEnabled() {
   351  			return errors.New("Intel RDT/MBA: memory bandwidth schema is not enabled")
   352  		}
   353  
   354  		if l3CacheSchema != "" || memBwSchema != "" {
   355  			// If intelRdt is not specified in original configuration, we just don't
   356  			// Apply() to create intelRdt group or attach tasks for this container.
   357  			// In update command, we could re-enable through IntelRdtManager.Apply()
   358  			// and then update intelrdt constraint.
   359  			if config.IntelRdt == nil {
   360  				state, err := container.State()
   361  				if err != nil {
   362  					return err
   363  				}
   364  				config.IntelRdt = &configs.IntelRdt{}
   365  				intelRdtManager := intelrdt.NewManager(&config, container.ID(), state.IntelRdtPath)
   366  				if err := intelRdtManager.Apply(state.InitProcessPid); err != nil {
   367  					return err
   368  				}
   369  			}
   370  			config.IntelRdt.L3CacheSchema = l3CacheSchema
   371  			config.IntelRdt.MemBwSchema = memBwSchema
   372  		}
   373  
   374  		// XXX(kolyshkin@): currently "runc update" is unable to change
   375  		// device configuration, so add this to skip device update.
   376  		// This helps in case an extra plugin (nvidia GPU) applies some
   377  		// configuration on top of what runc does.
   378  		// Note this field is not saved into container's state.json.
   379  		config.Cgroups.SkipDevices = true
   380  
   381  		return container.Set(config)
   382  	},
   383  }