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 }