Skip to content

Commit 59e6781

Browse files
Add runsc update command.
This change enables updating resource constraints of running containers. It is the runsc equivalent of runc update. The cgroupV1 implementation is still a stub. More changes to come.
1 parent 149350e commit 59e6781

File tree

6 files changed

+287
-23
lines changed

6 files changed

+287
-23
lines changed

runsc/cgroup/cgroup.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ type Cgroup interface {
319319
Install(res *specs.LinuxResources) error
320320
Uninstall() error
321321
Join() (func(), error)
322+
Set(res *specs.LinuxResources) error
322323
CPUQuota() (float64, error)
323324
CPUUsage() (uint64, error)
324325
NumCPU() (int, error)
@@ -626,6 +627,10 @@ func (c *cgroupV1) Join() (func(), error) {
626627
return cu.Release(), nil
627628
}
628629

630+
func (c *cgroupV1) Set(res *specs.LinuxResources) error {
631+
return errors.New("not implemented")
632+
}
633+
629634
// CPUQuota returns the CFS CPU quota.
630635
func (c *cgroupV1) CPUQuota() (float64, error) {
631636
path := c.MakePath("cpu")

runsc/cgroup/cgroup_v2.go

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

@@ -233,6 +212,35 @@ func (c *cgroupV2) Join() (func(), error) {
233212
return cu.Release(), nil
234213
}
235214

215+
// Set sets the cgroup resources.
216+
func (c *cgroupV2) Set(res *specs.LinuxResources) error {
217+
for controllerName, ctrlr := range controllers2 {
218+
// First check if our controller is found in the system.
219+
found := false
220+
for _, knownController := range c.Controllers {
221+
if controllerName == knownController {
222+
found = true
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+
236244
func getCPUQuota(path string) (float64, error) {
237245
cpuMax, err := getValue(path, cpuLimitCgroup)
238246
if err != nil {

runsc/cli/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ func forEachCmd(cb func(cmd subcommands.Command, group string)) {
261261
cb(new(cmd.Spec), "")
262262
cb(new(cmd.Start), "")
263263
cb(new(cmd.State), "")
264+
cb(new(cmd.Update), "")
264265
cb(new(cmd.Wait), "")
265266

266267
// 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: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// Copyright 2018 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 accepted format is as follow (unchanged values can be omitted):
82+
83+
{
84+
"memory": {
85+
"limit": 0,
86+
"reservation": 0,
87+
"swap": 0,
88+
"checkBeforeUpdate": true
89+
},
90+
"cpu": {
91+
"shares": 0,
92+
"quota": 0,
93+
"burst": 0,
94+
"period": 0,
95+
"realtimeRuntime": 0,
96+
"realtimePeriod": 0,
97+
"cpus": "",
98+
"mems": "",
99+
"idle": 0
100+
},
101+
"blockIO": {
102+
"weight": 0
103+
}
104+
}
105+
106+
Note: if data is to be read from a file or the standard input, all
107+
other options are ignored.
108+
`)
109+
110+
f.Uint64Var(&u.cpuPeriod, "cpu-period", 0, "CPU CFS period to be used for hardcapping (in usecs). 0 to use system default")
111+
f.Int64Var(&u.cpuQuota, "cpu-quota", 0, "CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period")
112+
f.Uint64Var(&u.cpuBurst, "cpu-burst", 0, "CPU CFS hardcap burst limit (in usecs). Allowed accumulated cpu time additionally for burst a given period")
113+
f.Uint64Var(&u.cpuShares, "cpu-share", 0, "CPU shares (relative weight vs. other containers)")
114+
f.Uint64Var(&u.cpuRtPeriod, "cpu-rt-period", 0, "CPU realtime period to be used for hardcapping (in usecs). 0 to use system default")
115+
f.Int64Var(&u.cpuRtRuntime, "cpu-rt-runtime", 0, "CPU realtime hardcap limit (in usecs). Allowed cpu time in a given period")
116+
f.StringVar(&u.cpusetCpus, "cpuset-cpus", "", "CPU(s) to use")
117+
f.StringVar(&u.cpusetMems, "cpuset-mems", "", "Memory node(s) to use")
118+
f.Int64Var(&u.cpuIdle, "cpu-idle", 0, "set cgroup SCHED_IDLE or not, 0: default behavior, 1: SCHED_IDLE")
119+
120+
f.Int64Var(&u.memory, "memory", 0, "Memory limit (in bytes)")
121+
f.Int64Var(&u.memoryReservation, "memory-reservation", 0, "Memory reservation or soft_limit (in bytes)")
122+
f.Int64Var(&u.memorySwap, "memory-swap", 0, "Total memory usage (memory + swap); set '-1' to enable unlimited swap")
123+
124+
f.IntVar(&u.blkioWeight, "blkio-weight", 0, "Specifies per cgroup weight, range is from 10 to 1000")
125+
126+
f.Int64Var(&u.pidsLimit, "pids-limit", 0, "Maximum number of pids allowed in the container")
127+
128+
f.StringVar(&u.l3CacheSchema, "l3-cache-schema", "", "The string of Intel RDT/CAT L3 cache schema")
129+
f.StringVar(&u.memBwSchema, "mem-bw-schema", "", "The string of Intel RDT/MBA memory bandwidth schema")
130+
}
131+
132+
// Execute implements subcommands.Command.Execute.
133+
func (u *Update) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
134+
if f.NArg() != 1 {
135+
f.Usage()
136+
return subcommands.ExitUsageError
137+
}
138+
139+
id := f.Arg(0)
140+
conf := args[0].(*config.Config)
141+
142+
c, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{})
143+
if err != nil {
144+
util.Fatalf("loading container %v", err)
145+
}
146+
147+
r := specs.LinuxResources{
148+
Memory: &specs.LinuxMemory{
149+
Limit: i64Ptr(0),
150+
Reservation: i64Ptr(0),
151+
Swap: i64Ptr(0),
152+
CheckBeforeUpdate: boolPtr(false),
153+
},
154+
CPU: &specs.LinuxCPU{
155+
Shares: u64Ptr(0),
156+
Quota: i64Ptr(0),
157+
Burst: u64Ptr(0),
158+
Period: u64Ptr(0),
159+
RealtimeRuntime: i64Ptr(0),
160+
RealtimePeriod: u64Ptr(0),
161+
Cpus: "",
162+
Mems: "",
163+
},
164+
BlockIO: &specs.LinuxBlockIO{
165+
Weight: u16Ptr(0),
166+
},
167+
Pids: &specs.LinuxPids{
168+
Limit: 0,
169+
},
170+
}
171+
172+
if in := u.resources; in != "" {
173+
var (
174+
f *os.File
175+
err error
176+
)
177+
switch in {
178+
case "-":
179+
f = os.Stdin
180+
default:
181+
f, err = os.Open(in)
182+
if err != nil {
183+
return util.Errorf("opening %q: %v", in, err)
184+
}
185+
defer f.Close()
186+
}
187+
err = json.NewDecoder(f).Decode(&r)
188+
if err != nil {
189+
return util.Errorf("decoding %q: %v", in, err)
190+
}
191+
} else {
192+
r.Memory.Limit = i64Ptr(u.memory)
193+
r.Memory.Reservation = i64Ptr(u.memoryReservation)
194+
r.Memory.Swap = i64Ptr(u.memorySwap)
195+
196+
r.CPU.Shares = u64Ptr(u.cpuShares)
197+
r.CPU.Quota = i64Ptr(u.cpuQuota)
198+
r.CPU.Burst = u64Ptr(u.cpuBurst)
199+
r.CPU.Period = u64Ptr(u.cpuPeriod)
200+
r.CPU.RealtimeRuntime = i64Ptr(u.cpuRtRuntime)
201+
r.CPU.RealtimePeriod = u64Ptr(u.cpuRtPeriod)
202+
r.CPU.Cpus = u.cpusetCpus
203+
r.CPU.Mems = u.cpusetMems
204+
205+
r.BlockIO.Weight = u16Ptr(uint16(u.blkioWeight))
206+
207+
r.Pids.Limit = u.pidsLimit
208+
}
209+
210+
if err = c.Set(&r); err != nil {
211+
return util.Errorf("setting resources: %v", err)
212+
}
213+
214+
return subcommands.ExitSuccess
215+
}

runsc/container/container.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,40 @@ func Run(conf *config.Config, args Args) (unix.WaitStatus, error) {
586586
return 0, nil
587587
}
588588

589+
// Set sets the resources of a running container as configured.
590+
func (c *Container) Set(res *specs.LinuxResources) error {
591+
log.Debugf("Set resources for container, cid: %s", c.ID)
592+
if err := c.requireStatus("set resources for", Created, Running); err != nil {
593+
return err
594+
}
595+
596+
if c.Sandbox == nil {
597+
return fmt.Errorf("sandbox is not set")
598+
}
599+
600+
var cg cgroup.Cgroup
601+
if c.Sandbox.IsRootContainer(c.ID) {
602+
cg = c.Sandbox.CgroupJSON.Cgroup
603+
} else {
604+
cg = c.CompatCgroup.Cgroup
605+
}
606+
607+
if err := cg.Set(res); err != nil {
608+
// set back to original
609+
if err2 := cg.Set(c.Spec.Linux.Resources); err2 != nil {
610+
return fmt.Errorf("setting back cgroup configs failed due to error: %v, your state file and actual configs might be inconsistent.", err2)
611+
}
612+
return err
613+
}
614+
615+
c.Spec.Linux.Resources = res
616+
c.CompatCgroup = cgroup.CgroupJSON{Cgroup: cg}
617+
618+
c.Saver.lock(BlockAcquire)
619+
defer c.Saver.unlock()
620+
return c.saveLocked()
621+
}
622+
589623
// Execute runs the specified command in the container. It returns the PID of
590624
// the newly created process.
591625
func (c *Container) Execute(conf *config.Config, args *control.ExecArgs) (int32, error) {

0 commit comments

Comments
 (0)