Skip to content

Commit 024d96e

Browse files
stepancheggvisor-bot
authored andcommitted
Overlay size option: --overlay2=all:memory,size=1g
Implement #11504. Add optional `size=` parameter to `--overlay2` flag: when it is set, overlay upper layer is mounted with given size flag. Tested: ``` sudo bazel-bin/runsc/runsc_/runsc --overlay2=all:memory,size=1m run --bundle ~/bu cont-i root@:/# dd if=/dev/urandom of=/x bs=1k count=1000 1000+0 records in 1000+0 records out 1024000 bytes (1.0 MB, 1000 KiB) copied, 0.0109061 s, 93.9 MB/s root@:/# dd if=/dev/urandom of=/x bs=1k count=1100 dd: error writing '/x': No space left on device 1021+0 records in 1020+0 records out 1044480 bytes (1.0 MB, 1020 KiB) copied, 0.011396 s, 91.7 MB/s ``` ``` -overlay DEPRECATED: use --overlay2=all:memory to achieve the same effect -overlay2 value wrap mounts with overlayfs. Format is * 'none' to turn overlay mode off * {mount}:{medium}[size={size}], where 'mount' can be 'root' or 'all' 'medium' can be 'memory', 'self' or 'dir=/abs/dir/path' in which filestore will be created 'size' optional parameter overrides default overlay upper layer size (default root:self) -panic-log string file path where panic reports and other Go's runtime messages are written. ``` FUTURE_COPYBARA_INTEGRATE_REVIEW=#11723 from stepancheg:overlay-size 32c91c3 PiperOrigin-RevId: 764885043
1 parent e888aa7 commit 024d96e

File tree

10 files changed

+198
-18
lines changed

10 files changed

+198
-18
lines changed

g3doc/user_guide/filesystem.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ Self-backed rootfs overlay (`--overlay2=root:self`) is enabled by default in
5858
runsc for performance. If you need to propagate rootfs changes to the host
5959
filesystem, then disable it with `--overlay2=none`.
6060

61+
Overlay has `size=` option which is passed as `size=` tmpfs mount option. For
62+
example, `--overlay2=root:memory,size=2g`.
63+
6164
## Directfs
6265

6366
Directfs is a feature that allows the sandbox process to directly access the

runsc/boot/gofer_conf.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,19 +124,24 @@ func (l *GoferMountConfLowerType) Set(v string) error {
124124

125125
// GoferMountConf describes how a gofer mount is configured in the sandbox.
126126
type GoferMountConf struct {
127-
Upper GoferMountConfUpperType `json:"upper"`
128127
Lower GoferMountConfLowerType `json:"lower"`
128+
Upper GoferMountConfUpperType `json:"upper"`
129+
Size string `json:"size,omitempty"`
129130
}
130131

131132
// String returns a human-readable string representing the gofer mount config.
132133
func (g GoferMountConf) String() string {
133-
return fmt.Sprintf("%s:%s", g.Lower, g.Upper)
134+
res := fmt.Sprintf("%s:%s", g.Lower, g.Upper)
135+
if g.Size != "" {
136+
res += ":size=" + g.Size
137+
}
138+
return res
134139
}
135140

136141
// Set sets the value. Set(String()) should be idempotent.
137142
func (g *GoferMountConf) Set(v string) error {
138143
parts := strings.Split(v, ":")
139-
if len(parts) != 2 {
144+
if len(parts) < 2 || len(parts) > 3 {
140145
return fmt.Errorf("invalid gofer mount config format: %q", v)
141146
}
142147
if err := g.Lower.Set(parts[0]); err != nil {
@@ -145,8 +150,17 @@ func (g *GoferMountConf) Set(v string) error {
145150
if err := g.Upper.Set(parts[1]); err != nil {
146151
return err
147152
}
153+
g.Size = ""
154+
if len(parts) >= 3 {
155+
sizeArg := parts[2]
156+
size, cut := strings.CutPrefix(sizeArg, "size=")
157+
if !cut {
158+
return fmt.Errorf("invalid gofer mount config format: %q", v)
159+
}
160+
g.Size = size
161+
}
148162
if !g.valid() {
149-
return fmt.Errorf("invalid gofer mount config: %+v", g)
163+
return fmt.Errorf("invalid gofer mount config: %q", v)
150164
}
151165
return nil
152166
}

runsc/boot/gofer_conf_test.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,32 @@ func TestGoferConfFlags(t *testing.T) {
171171
}
172172
for i := range want {
173173
if want[i] != got[i] {
174-
t.Errorf("gofer conf is incorrect: want = %d, got = %d", want[i], got[i])
174+
t.Errorf("gofer conf is incorrect: want = %s, got = %s", want[i], got[i])
175175
}
176176
}
177177
}
178+
179+
func TestGoferMountConfSetGet(t *testing.T) {
180+
t.Run("Without size", func(t *testing.T) {
181+
conf := GoferMountConf{}
182+
err := conf.Set("lisafs:anon")
183+
if err != nil {
184+
t.Fatalf("Expect success: %v", err)
185+
}
186+
s := conf.String()
187+
if s != "lisafs:anon" {
188+
t.Fatalf("Expected lisafs:anon, got %s", s)
189+
}
190+
})
191+
t.Run("With size", func(t *testing.T) {
192+
conf := GoferMountConf{}
193+
err := conf.Set("lisafs:anon:size=1719")
194+
if err != nil {
195+
t.Fatalf("Expect success: %v", err)
196+
}
197+
s := conf.String()
198+
if s != "lisafs:anon:size=1719" {
199+
t.Fatalf("Expected lisafs:anon:size=1719, got %s", s)
200+
}
201+
})
202+
}

runsc/boot/mount_hints.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ func (p *PodMountHints) FindMount(mountSrc string) *MountHint {
238238
type RootfsHint struct {
239239
Mount specs.Mount
240240
Overlay config.OverlayMedium
241+
// Size of overlay tmpfs. Passed as `size={Size}` to tmpfs mount.
242+
// Use default if unspecified.
243+
Size string
241244
}
242245

243246
func (r *RootfsHint) setSource(val string) error {
@@ -258,6 +261,30 @@ func (r *RootfsHint) setType(val string) error {
258261
return nil
259262
}
260263

264+
func (r *RootfsHint) setOption(key, val string) error {
265+
switch key {
266+
case "size":
267+
r.Size = val
268+
default:
269+
return fmt.Errorf("invalid rootfs option: %s=%s", key, val)
270+
}
271+
return nil
272+
}
273+
274+
func (r *RootfsHint) setOptions(val string) error {
275+
for _, option := range strings.Split(val, ",") {
276+
parts := strings.SplitN(option, "=", 2)
277+
if len(parts) != 2 {
278+
return fmt.Errorf("rootfs options must be key=value: %s", option)
279+
}
280+
key, val := parts[0], parts[1]
281+
if err := r.setOption(key, val); err != nil {
282+
return err
283+
}
284+
}
285+
return nil
286+
}
287+
261288
func (r *RootfsHint) setField(key, val string) error {
262289
switch key {
263290
case "source":
@@ -266,6 +293,8 @@ func (r *RootfsHint) setField(key, val string) error {
266293
return r.setType(val)
267294
case "overlay":
268295
return r.Overlay.Set(val)
296+
case "options":
297+
return r.setOptions(val)
269298
default:
270299
return fmt.Errorf("invalid rootfs annotation: %s=%s", key, val)
271300
}

runsc/boot/mount_hints_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ func TestRootfsHintHappy(t *testing.T) {
258258
RootfsPrefix + "source": imagePath,
259259
RootfsPrefix + "type": erofs.Name,
260260
RootfsPrefix + "overlay": config.MemoryOverlay.String(),
261+
RootfsPrefix + "options": "size=100m",
261262
},
262263
}
263264
hint, err := NewRootfsHint(spec)
@@ -275,6 +276,9 @@ func TestRootfsHintHappy(t *testing.T) {
275276
if hint.Overlay != config.MemoryOverlay {
276277
t.Errorf("rootfs overlay, want: %q, got: %q", config.MemoryOverlay, hint.Overlay)
277278
}
279+
if hint.Size != "100m" {
280+
t.Errorf("rootfs size, want: 100m, got: %q", hint.Size)
281+
}
278282
}
279283

280284
// TestRootfsHintErrors tests that proper errors will be returned when parsing

runsc/boot/vfs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,12 @@ func (c *containerMounter) configureOverlay(ctx context.Context, conf *config.Co
586586
// filesystem specific options.
587587
upperOpts := *lowerOpts
588588
upperOpts.GetFilesystemOptions = vfs.GetFilesystemOptions{InternalMount: true}
589+
if mountConf.Size != "" {
590+
if upperOpts.GetFilesystemOptions.Data != "" {
591+
upperOpts.GetFilesystemOptions.Data += ","
592+
}
593+
upperOpts.GetFilesystemOptions.Data += "size=" + mountConf.Size
594+
}
589595

590596
overlayOpts := *lowerOpts
591597
overlayOpts.GetFilesystemOptions = vfs.GetFilesystemOptions{InternalMount: true}

runsc/config/config.go

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -909,24 +909,42 @@ type Overlay2 struct {
909909
rootMount bool
910910
subMounts bool
911911
medium OverlayMedium
912+
// Size of overlay upper layer.
913+
// Passed as is to tmpfs mount, as `size={size}`.
914+
// Empty means use default.
915+
// Size is applied to each overlay independently and not shared by overlays.
916+
size string
912917
}
913918

914919
func defaultOverlay2() *Overlay2 {
915920
// Rootfs overlay is enabled by default and backed by a file in rootfs itself.
916921
return &Overlay2{rootMount: true, subMounts: false, medium: SelfOverlay}
917922
}
918923

924+
func setOverlay2Err(v string) error {
925+
return fmt.Errorf("expected format is --overlay2={mount}:{medium}[,size={size}], got %q", v)
926+
}
927+
928+
// `--overlay2=...` param `size=`.
929+
const overlay2SizeEq = "size="
930+
919931
// Set implements flag.Value. Set(String()) should be idempotent.
920932
func (o *Overlay2) Set(v string) error {
921933
if v == "none" {
922934
o.rootMount = false
923935
o.subMounts = false
924936
o.medium = NoOverlay
937+
o.size = ""
925938
return nil
926939
}
927-
vs := strings.Split(v, ":")
940+
parts := strings.Split(v, ",")
941+
if len(parts) < 1 {
942+
return setOverlay2Err(v)
943+
}
944+
945+
vs := strings.Split(parts[0], ":")
928946
if len(vs) != 2 {
929-
return fmt.Errorf("expected format is --overlay2={mount}:{medium}, got %q", v)
947+
return setOverlay2Err(v)
930948
}
931949

932950
switch mount := vs[0]; mount {
@@ -939,7 +957,25 @@ func (o *Overlay2) Set(v string) error {
939957
return fmt.Errorf("unexpected mount specifier for --overlay2: %q", mount)
940958
}
941959

942-
return o.medium.Set(vs[1])
960+
err := o.medium.Set(vs[1])
961+
if err != nil {
962+
return err
963+
}
964+
965+
if len(parts) == 1 {
966+
o.size = ""
967+
} else if len(parts) == 2 {
968+
sizeArg := parts[1]
969+
size, cut := strings.CutPrefix(sizeArg, overlay2SizeEq)
970+
if !cut {
971+
return setOverlay2Err(v)
972+
}
973+
o.size = size
974+
} else {
975+
return setOverlay2Err(v)
976+
}
977+
978+
return nil
943979
}
944980

945981
// Get implements flag.Value.
@@ -961,7 +997,13 @@ func (o Overlay2) String() string {
961997
default:
962998
panic("invalid state of subMounts = true and rootMount = false")
963999
}
964-
return res + ":" + o.medium.String()
1000+
1001+
var sizeSuffix string
1002+
if o.size != "" {
1003+
sizeSuffix = fmt.Sprintf(",%s%s", overlay2SizeEq, o.size)
1004+
}
1005+
1006+
return res + ":" + o.medium.String() + sizeSuffix
9651007
}
9661008

9671009
// Enabled returns true if the overlay option is enabled for any mounts.
@@ -977,6 +1019,13 @@ func (o *Overlay2) RootOverlayMedium() OverlayMedium {
9771019
return o.medium
9781020
}
9791021

1022+
func (o *Overlay2) RootOverlaySize() string {
1023+
if !o.rootMount {
1024+
return ""
1025+
}
1026+
return o.size
1027+
}
1028+
9801029
// SubMountOverlayMedium returns the overlay medium config of submounts.
9811030
func (o *Overlay2) SubMountOverlayMedium() OverlayMedium {
9821031
if !o.subMounts {
@@ -985,6 +1034,13 @@ func (o *Overlay2) SubMountOverlayMedium() OverlayMedium {
9851034
return o.medium
9861035
}
9871036

1037+
func (o *Overlay2) SubMountOverlaySize() string {
1038+
if !o.subMounts {
1039+
return ""
1040+
}
1041+
return o.size
1042+
}
1043+
9881044
// Medium returns the overlay medium config.
9891045
func (o Overlay2) Medium() OverlayMedium {
9901046
return o.medium

runsc/config/config_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ func TestInvalidFlags(t *testing.T) {
231231
value: "root:dir=tmp",
232232
error: "overlay host file directory should be an absolute path, got \"tmp\"",
233233
},
234+
{
235+
name: "overlay2",
236+
value: "root:memory,sz=sdg",
237+
error: "expected format is --overlay2",
238+
},
234239
} {
235240
t.Run(tc.name, func(t *testing.T) {
236241
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
@@ -781,3 +786,32 @@ root = "%s"
781786
}
782787

783788
}
789+
790+
func TestParseSerializeOverlay2(t *testing.T) {
791+
t.Run("Without size", func(t *testing.T) {
792+
o := Overlay2{}
793+
err := o.Set("all:memory")
794+
if err != nil {
795+
t.Fatalf("Set failed: %v", err)
796+
}
797+
if o.RootOverlaySize() != "" || o.SubMountOverlaySize() != "" {
798+
t.Fatalf("Size mismatch, expecting empty, got %q, %q", o.RootOverlaySize(), o.SubMountOverlaySize())
799+
}
800+
if o.String() != "all:memory" {
801+
t.Fatalf("String mismatch, expecting all:memory, got %q", o.String())
802+
}
803+
})
804+
t.Run("With size", func(t *testing.T) {
805+
o := Overlay2{}
806+
err := o.Set("root:memory,size=1g")
807+
if err != nil {
808+
t.Fatalf("Set failed: %v", err)
809+
}
810+
if o.RootOverlaySize() != "1g" || o.SubMountOverlaySize() != "" {
811+
t.Fatalf("Size mismatch, expecting 1g, empty, got %q, %q", o.RootOverlaySize(), o.SubMountOverlaySize())
812+
}
813+
if o.String() != "root:memory,size=1g" {
814+
t.Fatalf("String mismatch, expecting ll:memory,size=1g, got %q", o.String())
815+
}
816+
})
817+
}

runsc/config/flags.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,12 @@ func RegisterFlags(flagSet *flag.FlagSet) {
119119
flagSet.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem validation to use for the root mount: exclusive (default), shared.")
120120
flagSet.Var(fileAccessTypePtr(FileAccessShared), "file-access-mounts", "specifies which filesystem validation to use for volumes other than the root mount: shared (default), exclusive.")
121121
flagSet.Bool("overlay", false, "DEPRECATED: use --overlay2=all:memory to achieve the same effect")
122-
flagSet.Var(defaultOverlay2(), flagOverlay2, "wrap mounts with overlayfs. Format is {mount}:{medium}, where 'mount' can be 'root' or 'all' and medium can be 'memory', 'self' or 'dir=/abs/dir/path' in which filestore will be created. 'none' will turn overlay mode off.")
122+
flagSet.Var(defaultOverlay2(), flagOverlay2, "wrap mounts with overlayfs. Format is\n"+
123+
"* 'none' to turn overlay mode off\n"+
124+
"* {mount}:{medium}[,size={size}], where\n"+
125+
" 'mount' can be 'root' or 'all'\n"+
126+
" 'medium' can be 'memory', 'self' or 'dir=/abs/dir/path' in which filestore will be created\n"+
127+
" 'size' optional parameter overrides default overlay upper layer size\n")
123128
flagSet.Bool("fsgofer-host-uds", false, "DEPRECATED: use host-uds=all")
124129
flagSet.Var(hostUDSPtr(HostUDSNone), flagHostUDS, "controls permission to access host Unix-domain sockets. Values: none|open|create|all, default: none")
125130
flagSet.Var(hostFifoPtr(HostFifoNone), "host-fifo", "controls permission to access host FIFOs (or named pipes). Values: none|open, default: none")

0 commit comments

Comments
 (0)