Skip to content

Commit 5e29a0b

Browse files
runsc: add update command
This change enables updating resource constraints of running containers. It is the runsc equivalent of runc update.
1 parent d06b27e commit 5e29a0b

File tree

6 files changed

+316
-23
lines changed

6 files changed

+316
-23
lines changed

runsc/cgroup/cgroup.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ type Cgroup interface {
318318
Install(res *specs.LinuxResources) error
319319
Uninstall() error
320320
Join() (func(), error)
321+
Set(res *specs.LinuxResources) error
321322
CPUQuota() (float64, error)
322323
CPUUsage() (uint64, error)
323324
NumCPU() (int, error)
@@ -643,6 +644,37 @@ func (c *cgroupV1) Join() (func(), error) {
643644
return cu.Release(), nil
644645
}
645646

647+
// Set configures cgroups according to 'res'. Unlike Install(),
648+
// we always update the cgroup resources, even if the cgroup
649+
// path already exists.
650+
func (c *cgroupV1) Set(res *specs.LinuxResources) error {
651+
log.Debugf("Setting cgroup resources for %q", c.Name)
652+
if res == nil {
653+
return nil
654+
}
655+
656+
for key, ctrlr := range controllers {
657+
path := c.MakePath(key)
658+
if _, err := os.Stat(path); err != nil {
659+
if os.IsNotExist(err) && ctrlr.optional() {
660+
if err := ctrlr.skip(res); err != nil {
661+
return err
662+
}
663+
log.Infof("Skipping cgroup %q, path doesn't exist", key)
664+
continue
665+
} else if err != nil {
666+
return fmt.Errorf("failed to stat cgroup %q: %w", key, err)
667+
}
668+
}
669+
670+
if err := ctrlr.set(res, path); err != nil {
671+
return fmt.Errorf("failed to set %q cgroup: %w", key, err)
672+
}
673+
}
674+
675+
return nil
676+
}
677+
646678
// CPUQuota returns the CFS CPU quota.
647679
func (c *cgroupV1) CPUQuota() (float64, error) {
648680
path := c.MakePath("cpu")

runsc/cgroup/cgroup_v2.go

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -140,29 +140,8 @@ func (c *cgroupV2) Install(res *specs.LinuxResources) error {
140140
}
141141
if created {
142142
// If we created our final cgroup path then we can set the resources.
143-
for controllerName, ctrlr := range controllers2 {
144-
// First check if our controller is found in the system.
145-
found := false
146-
for _, knownController := range c.Controllers {
147-
if controllerName == knownController {
148-
found = true
149-
}
150-
}
151-
152-
// In case we don't have the controller.
153-
if found {
154-
if err := ctrlr.set(res, c.MakePath("")); err != nil {
155-
return err
156-
}
157-
continue
158-
}
159-
if ctrlr.optional() {
160-
if err := ctrlr.skip(res); err != nil {
161-
return err
162-
}
163-
} else {
164-
return fmt.Errorf("mandatory cgroup controller %q is missing for %q", controllerName, c.MakePath(""))
165-
}
143+
if err := c.Set(res); err != nil {
144+
return err
166145
}
167146
}
168147

@@ -232,6 +211,36 @@ func (c *cgroupV2) Join() (func(), error) {
232211
return cu.Release(), nil
233212
}
234213

214+
// Set sets the cgroup resources.
215+
func (c *cgroupV2) Set(res *specs.LinuxResources) error {
216+
for controllerName, ctrlr := range controllers2 {
217+
// First check if our controller is found in the system.
218+
found := false
219+
for _, knownController := range c.Controllers {
220+
if controllerName == knownController {
221+
found = true
222+
break
223+
}
224+
}
225+
226+
// In case we don't have the controller.
227+
if found {
228+
if err := ctrlr.set(res, c.MakePath("")); err != nil {
229+
return err
230+
}
231+
continue
232+
}
233+
if ctrlr.optional() {
234+
if err := ctrlr.skip(res); err != nil {
235+
return err
236+
}
237+
} else {
238+
return fmt.Errorf("mandatory cgroup controller %q is missing for %q", controllerName, c.MakePath(""))
239+
}
240+
}
241+
return nil
242+
}
243+
235244
func getCPUQuota(path string) (float64, error) {
236245
cpuMax, err := getValue(path, cpuLimitCgroup)
237246
if err != nil {

runsc/cli/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ func forEachCmd(cb func(cmd subcommands.Command, group string)) {
258258
cb(new(cmd.Spec), "")
259259
cb(new(cmd.Start), "")
260260
cb(new(cmd.State), "")
261+
cb(new(cmd.Update), "")
261262
cb(new(cmd.Wait), "")
262263

263264
// Helpers.

runsc/cmd/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ go_library(
7171
"symbolize.go",
7272
"syscalls.go",
7373
"umount_unsafe.go",
74+
"update.go",
7475
"usage.go",
7576
"wait.go",
7677
"write_control.go",

runsc/cmd/update.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// Copyright 2022 The gVisor Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cmd
16+
17+
import (
18+
"context"
19+
"encoding/json"
20+
"os"
21+
22+
"github.com/google/subcommands"
23+
specs "github.com/opencontainers/runtime-spec/specs-go"
24+
"gvisor.dev/gvisor/runsc/cmd/util"
25+
"gvisor.dev/gvisor/runsc/config"
26+
"gvisor.dev/gvisor/runsc/container"
27+
"gvisor.dev/gvisor/runsc/flag"
28+
)
29+
30+
func i64Ptr(i int64) *int64 { return &i }
31+
func u64Ptr(i uint64) *uint64 { return &i }
32+
func u16Ptr(i uint16) *uint16 { return &i }
33+
func boolPtr(b bool) *bool { return &b }
34+
35+
// Update implements subcommands.Command for the "update" command.
36+
type Update struct {
37+
resources string
38+
39+
cpuPeriod uint64
40+
cpuQuota int64
41+
cpuBurst uint64
42+
cpuShares uint64
43+
cpuRtPeriod uint64
44+
cpuRtRuntime int64
45+
cpusetCpus string // can this be string or this has to be list of smth?
46+
cpusetMems string
47+
cpuIdle int64
48+
49+
memory int64
50+
memoryReservation int64
51+
memorySwap int64
52+
53+
blkioWeight int
54+
55+
pidsLimit int64
56+
57+
l3CacheSchema string
58+
memBwSchema string
59+
}
60+
61+
// Name implements subcommands.Command.Name.
62+
func (*Update) Name() string {
63+
return "update"
64+
}
65+
66+
// Synopsis implements subcommands.Command.Synopsis.
67+
func (*Update) Synopsis() string {
68+
return "update container resource constraints"
69+
}
70+
71+
// Usage implements subcommands.Command.Usage.
72+
func (*Update) Usage() string {
73+
return `update [flags] <container id> - update container resource constraints
74+
`
75+
}
76+
77+
// SetFlags implements subcommands.Command.SetFlags.
78+
func (u *Update) SetFlags(f *flag.FlagSet) {
79+
f.StringVar(&u.resources, "resources", "", `path to the file containing the resources to update or '-' to read from the standard input.
80+
81+
The default value means resources will be read from the remaining flags.
82+
83+
The accepted format is as follows (unchanged values can be omitted):
84+
85+
{
86+
"memory": {
87+
"limit": 0,
88+
"reservation": 0,
89+
"swap": 0,
90+
"checkBeforeUpdate": true
91+
},
92+
"cpu": {
93+
"shares": 0,
94+
"quota": 0,
95+
"burst": 0,
96+
"period": 0,
97+
"realtimeRuntime": 0,
98+
"realtimePeriod": 0,
99+
"cpus": "",
100+
"mems": "",
101+
"idle": 0
102+
},
103+
"blockIO": {
104+
"weight": 0
105+
}
106+
}
107+
108+
Note: if data is to be read from a file or the standard input, all
109+
other options are ignored.
110+
`)
111+
112+
f.Uint64Var(&u.cpuPeriod, "cpu-period", 0, "CPU CFS period to be used for hardcapping (in usecs). 0 to use system default")
113+
f.Int64Var(&u.cpuQuota, "cpu-quota", 0, "CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period")
114+
f.Uint64Var(&u.cpuBurst, "cpu-burst", 0, "CPU CFS hardcap burst limit (in usecs). Allowed accumulated cpu time additionally for burst a given period")
115+
f.Uint64Var(&u.cpuShares, "cpu-share", 0, "CPU shares (relative weight vs. other containers)")
116+
f.Uint64Var(&u.cpuRtPeriod, "cpu-rt-period", 0, "CPU realtime period to be used for hardcapping (in usecs). 0 to use system default")
117+
f.Int64Var(&u.cpuRtRuntime, "cpu-rt-runtime", 0, "CPU realtime hardcap limit (in usecs). Allowed cpu time in a given period")
118+
f.StringVar(&u.cpusetCpus, "cpuset-cpus", "", "CPU(s) to use")
119+
f.StringVar(&u.cpusetMems, "cpuset-mems", "", "Memory node(s) to use")
120+
f.Int64Var(&u.cpuIdle, "cpu-idle", 0, "set cgroup SCHED_IDLE or not, 0: default behavior, 1: SCHED_IDLE")
121+
122+
f.Int64Var(&u.memory, "memory", 0, "Memory limit (in bytes)")
123+
f.Int64Var(&u.memoryReservation, "memory-reservation", 0, "Memory reservation or soft_limit (in bytes)")
124+
f.Int64Var(&u.memorySwap, "memory-swap", 0, "Total memory usage (memory + swap); set '-1' to enable unlimited swap")
125+
126+
f.IntVar(&u.blkioWeight, "blkio-weight", 0, "Specifies per cgroup weight, range is from 10 to 1000")
127+
128+
f.Int64Var(&u.pidsLimit, "pids-limit", 0, "Maximum number of pids allowed in the container")
129+
130+
f.StringVar(&u.l3CacheSchema, "l3-cache-schema", "", "The string of Intel RDT/CAT L3 cache schema")
131+
f.StringVar(&u.memBwSchema, "mem-bw-schema", "", "The string of Intel RDT/MBA memory bandwidth schema")
132+
}
133+
134+
// Execute implements subcommands.Command.Execute.
135+
func (u *Update) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
136+
if f.NArg() != 1 {
137+
f.Usage()
138+
return subcommands.ExitUsageError
139+
}
140+
141+
id := f.Arg(0)
142+
conf := args[0].(*config.Config)
143+
144+
c, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{})
145+
if err != nil {
146+
util.Fatalf("loading container %v", err)
147+
}
148+
149+
r := specs.LinuxResources{
150+
Memory: &specs.LinuxMemory{
151+
Limit: i64Ptr(0),
152+
Reservation: i64Ptr(0),
153+
Swap: i64Ptr(0),
154+
CheckBeforeUpdate: boolPtr(false),
155+
},
156+
CPU: &specs.LinuxCPU{
157+
Shares: u64Ptr(0),
158+
Quota: i64Ptr(0),
159+
Burst: u64Ptr(0),
160+
Period: u64Ptr(0),
161+
RealtimeRuntime: i64Ptr(0),
162+
RealtimePeriod: u64Ptr(0),
163+
Cpus: "",
164+
Mems: "",
165+
},
166+
BlockIO: &specs.LinuxBlockIO{
167+
Weight: u16Ptr(0),
168+
},
169+
Pids: &specs.LinuxPids{
170+
Limit: 0,
171+
},
172+
}
173+
174+
if in := u.resources; in != "" {
175+
var (
176+
f *os.File
177+
err error
178+
)
179+
switch in {
180+
case "-":
181+
f = os.Stdin
182+
default:
183+
f, err = os.Open(in)
184+
if err != nil {
185+
return util.Errorf("opening %q: %v", in, err)
186+
}
187+
defer f.Close()
188+
}
189+
err = json.NewDecoder(f).Decode(&r)
190+
if err != nil {
191+
return util.Errorf("decoding %q: %v", in, err)
192+
}
193+
} else {
194+
r.Memory.Limit = i64Ptr(u.memory)
195+
r.Memory.Reservation = i64Ptr(u.memoryReservation)
196+
r.Memory.Swap = i64Ptr(u.memorySwap)
197+
198+
r.CPU.Shares = u64Ptr(u.cpuShares)
199+
r.CPU.Quota = i64Ptr(u.cpuQuota)
200+
r.CPU.Burst = u64Ptr(u.cpuBurst)
201+
r.CPU.Period = u64Ptr(u.cpuPeriod)
202+
r.CPU.RealtimeRuntime = i64Ptr(u.cpuRtRuntime)
203+
r.CPU.RealtimePeriod = u64Ptr(u.cpuRtPeriod)
204+
r.CPU.Cpus = u.cpusetCpus
205+
r.CPU.Mems = u.cpusetMems
206+
207+
r.BlockIO.Weight = u16Ptr(uint16(u.blkioWeight))
208+
209+
r.Pids.Limit = u.pidsLimit
210+
}
211+
212+
if err = c.Set(&r); err != nil {
213+
return util.Errorf("setting resources: %v", err)
214+
}
215+
216+
return subcommands.ExitSuccess
217+
}

runsc/container/container.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,39 @@ func Run(conf *config.Config, args Args) (unix.WaitStatus, error) {
552552
return 0, nil
553553
}
554554

555+
// Set sets the resources of a running container as configured.
556+
func (c *Container) Set(res *specs.LinuxResources) error {
557+
log.Debugf("Set resources for container, cid: %s", c.ID)
558+
if err := c.requireStatus("set resources for", Created, Running); err != nil {
559+
return err
560+
}
561+
562+
if c.Sandbox == nil {
563+
return fmt.Errorf("sandbox cannot be nil")
564+
}
565+
566+
if !c.Sandbox.IsRootContainer(c.ID) {
567+
return fmt.Errorf("Set can only be called on the root container")
568+
}
569+
570+
cg := c.Sandbox.CgroupJSON.Cgroup
571+
if cg == nil {
572+
return fmt.Errorf("cgroup cannot be nil")
573+
}
574+
if err := cg.Set(res); err != nil {
575+
// set back to original
576+
if err2 := cg.Set(c.Spec.Linux.Resources); err2 != nil {
577+
return fmt.Errorf("setting back cgroup configs failed due to error: %v, your state file and actual configs might be inconsistent.", err2)
578+
}
579+
return err
580+
}
581+
c.Spec.Linux.Resources = res
582+
583+
c.Saver.lock(BlockAcquire)
584+
defer c.Saver.unlock()
585+
return c.saveLocked()
586+
}
587+
555588
// Execute runs the specified command in the container. It returns the PID of
556589
// the newly created process.
557590
func (c *Container) Execute(conf *config.Config, args *control.ExecArgs) (int32, error) {

0 commit comments

Comments
 (0)