Skip to content

Overlay size option: --overlay2=all:memory,size=1g #11723

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions runsc/boot/gofer_conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,24 @@ func (l *GoferMountConfLowerType) Set(v string) error {

// GoferMountConf describes how a gofer mount is configured in the sandbox.
type GoferMountConf struct {
Upper GoferMountConfUpperType `json:"upper"`
Lower GoferMountConfLowerType `json:"lower"`
Upper GoferMountConfUpperType `json:"upper"`
Size string `json:"size,omitempty"`
}

// String returns a human-readable string representing the gofer mount config.
func (g GoferMountConf) String() string {
return fmt.Sprintf("%s:%s", g.Lower, g.Upper)
res := fmt.Sprintf("%s:%s", g.Lower, g.Upper)
if g.Size != "" {
res += ":size=" + g.Size
}
return res
}

// Set sets the value. Set(String()) should be idempotent.
func (g *GoferMountConf) Set(v string) error {
parts := strings.Split(v, ":")
if len(parts) != 2 {
if len(parts) < 2 || len(parts) > 3 {
return fmt.Errorf("invalid gofer mount config format: %q", v)
}
if err := g.Lower.Set(parts[0]); err != nil {
Expand All @@ -145,8 +150,17 @@ func (g *GoferMountConf) Set(v string) error {
if err := g.Upper.Set(parts[1]); err != nil {
return err
}
g.Size = ""
if len(parts) >= 3 {
sizeArg := parts[2]
size, cut := strings.CutPrefix(sizeArg, "size=")
if !cut {
return fmt.Errorf("invalid gofer mount config format: %q", v)
}
g.Size = size
}
if !g.valid() {
return fmt.Errorf("invalid gofer mount config: %+v", g)
return fmt.Errorf("invalid gofer mount config: %q", v)
}
return nil
}
Expand Down
25 changes: 25 additions & 0 deletions runsc/boot/gofer_conf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,28 @@ func TestGoferConfFlags(t *testing.T) {
}
}
}

func TestGoferMountConfSetGet(t *testing.T) {
t.Run("Without size", func(t *testing.T) {
conf := GoferMountConf{}
err := conf.Set("lisafs:anon")
if err != nil {
t.Fatalf("Expect success: %v", err)
}
s := conf.String()
if s != "lisafs:anon" {
t.Fatalf("Expected lisafs:anon, got %s", s)
}
})
t.Run("With size", func(t *testing.T) {
conf := GoferMountConf{}
err := conf.Set("lisafs:anon:size=1719")
if err != nil {
t.Fatalf("Expect success: %v", err)
}
s := conf.String()
if s != "lisafs:anon:size=1719" {
t.Fatalf("Expected lisafs:anon:size=1719, got %s", s)
}
})
}
14 changes: 14 additions & 0 deletions runsc/boot/mount_hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ func (p *PodMountHints) FindMount(mountSrc string) *MountHint {
type RootfsHint struct {
Mount specs.Mount
Overlay config.OverlayMedium
// Size of overlay tmpfs. Passed as `size={Size}` to tmpfs mount.
// Use default if unspecified.
Size string
}

func (r *RootfsHint) setSource(val string) error {
Expand All @@ -252,6 +255,15 @@ func (r *RootfsHint) setType(val string) error {
return nil
}

func (r *RootfsHint) setOptions(val string) error {
size, cut := strings.CutPrefix(val, "size=")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we first split val by , and then process the result? To make room for multiple options in the future. That would also be consistent with how --overlay2 flag is processed; its value is split by "," and we ensure that only 1 option is present, if any and that option should be size.

if !cut {
return fmt.Errorf("only size= option is supported: %q", val)
}
r.Size = size
return nil
}

func (r *RootfsHint) setField(key, val string) error {
switch key {
case "source":
Expand All @@ -260,6 +272,8 @@ func (r *RootfsHint) setField(key, val string) error {
return r.setType(val)
case "overlay":
return r.Overlay.Set(val)
case "options":
return r.setOptions(val)
default:
return fmt.Errorf("invalid rootfs annotation: %s=%s", key, val)
}
Expand Down
6 changes: 6 additions & 0 deletions runsc/boot/vfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,12 @@ func (c *containerMounter) configureOverlay(ctx context.Context, conf *config.Co
// filesystem specific options.
upperOpts := *lowerOpts
upperOpts.GetFilesystemOptions = vfs.GetFilesystemOptions{InternalMount: true}
if mountConf.Size != "" {
if upperOpts.GetFilesystemOptions.Data != "" {
upperOpts.GetFilesystemOptions.Data += ","
}
upperOpts.GetFilesystemOptions.Data += "size=" + mountConf.Size
}

overlayOpts := *lowerOpts
overlayOpts.GetFilesystemOptions = vfs.GetFilesystemOptions{InternalMount: true}
Expand Down
64 changes: 60 additions & 4 deletions runsc/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -906,24 +906,42 @@ type Overlay2 struct {
rootMount bool
subMounts bool
medium OverlayMedium
// Size of overlay upper layer.
// Passed as is to tmpfs mount, as `size={size}`.
// Empty means use default.
// Size is applied to each overlay independently and not shared by overlays.
size string
}

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

func setOverlay2Err(v string) error {
return fmt.Errorf("expected format is --overlay2={mount}:{medium}[,size={size}], got %q", v)
}

// `--overlay2=...` param `size=`.
const overlay2SizeEq = "size="

// Set implements flag.Value. Set(String()) should be idempotent.
func (o *Overlay2) Set(v string) error {
if v == "none" {
o.rootMount = false
o.subMounts = false
o.medium = NoOverlay
o.size = ""
return nil
}
vs := strings.Split(v, ":")
parts := strings.Split(v, ",")
if len(parts) < 1 {
return setOverlay2Err(v)
}

vs := strings.Split(parts[0], ":")
if len(vs) != 2 {
return fmt.Errorf("expected format is --overlay2={mount}:{medium}, got %q", v)
return setOverlay2Err(v)
}

switch mount := vs[0]; mount {
Expand All @@ -936,7 +954,25 @@ func (o *Overlay2) Set(v string) error {
return fmt.Errorf("unexpected mount specifier for --overlay2: %q", mount)
}

return o.medium.Set(vs[1])
err := o.medium.Set(vs[1])
if err != nil {
return err
}

if len(parts) == 1 {
o.size = ""
} else if len(parts) == 2 {
sizeArg := parts[1]
size, cut := strings.CutPrefix(sizeArg, overlay2SizeEq)
if !cut {
return setOverlay2Err(v)
}
o.size = size
} else {
return setOverlay2Err(v)
}

return nil
}

// Get implements flag.Value.
Expand All @@ -958,7 +994,13 @@ func (o Overlay2) String() string {
default:
panic("invalid state of subMounts = true and rootMount = false")
}
return res + ":" + o.medium.String()

var sizeSuffix string
if o.size != "" {
sizeSuffix = fmt.Sprintf(",%s%s", overlay2SizeEq, o.size)
}

return res + ":" + o.medium.String() + sizeSuffix
}

// Enabled returns true if the overlay option is enabled for any mounts.
Expand All @@ -974,6 +1016,13 @@ func (o *Overlay2) RootOverlayMedium() OverlayMedium {
return o.medium
}

func (o *Overlay2) RootOverlaySize() string {
if !o.rootMount {
return ""
}
return o.size
}

// SubMountOverlayMedium returns the overlay medium config of submounts.
func (o *Overlay2) SubMountOverlayMedium() OverlayMedium {
if !o.subMounts {
Expand All @@ -982,6 +1031,13 @@ func (o *Overlay2) SubMountOverlayMedium() OverlayMedium {
return o.medium
}

func (o *Overlay2) SubMountOverlaySize() string {
if !o.subMounts {
return ""
}
return o.size
}

// Medium returns the overlay medium config.
func (o Overlay2) Medium() OverlayMedium {
return o.medium
Expand Down
34 changes: 34 additions & 0 deletions runsc/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ func TestInvalidFlags(t *testing.T) {
value: "root:dir=tmp",
error: "overlay host file directory should be an absolute path, got \"tmp\"",
},
{
name: "overlay2",
value: "root:memory,sz=sdg",
error: "expected format is --overlay2",
},
} {
t.Run(tc.name, func(t *testing.T) {
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
Expand Down Expand Up @@ -781,3 +786,32 @@ root = "%s"
}

}

func TestParseSerializeOverlay2(t *testing.T) {
t.Run("Without size", func(t *testing.T) {
o := Overlay2{}
err := o.Set("all:memory")
if err != nil {
t.Fatalf("Set failed: %v", err)
}
if o.RootOverlaySize() != "" || o.RootOverlayMedium() != "" {
t.Fatalf("Size mismatch, expecting empty, got %q, %q", o.RootOverlaySize(), o.SubMountOverlaySize())
}
if o.String() != "all:memory" {
t.Fatalf("String mismatch, expecting all:memory, got %q", o.String())
}
})
t.Run("With size", func(t *testing.T) {
o := Overlay2{}
err := o.Set("root:memory,size=1g")
if err != nil {
t.Fatalf("Set failed: %v", err)
}
if o.RootOverlaySize() != "1g" || o.SubMountOverlaySize() != "" {
t.Fatalf("Size mismatch, expecting 1g, empty, got %q, %q", o.RootOverlaySize(), o.SubMountOverlaySize())
}
if o.String() != "all:memory,size=1g" {
t.Fatalf("String mismatch, expecting all:memory, got %q", o.String())
}
})
}
7 changes: 6 additions & 1 deletion runsc/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ func RegisterFlags(flagSet *flag.FlagSet) {
flagSet.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem validation to use for the root mount: exclusive (default), shared.")
flagSet.Var(fileAccessTypePtr(FileAccessShared), "file-access-mounts", "specifies which filesystem validation to use for volumes other than the root mount: shared (default), exclusive.")
flagSet.Bool("overlay", false, "DEPRECATED: use --overlay2=all:memory to achieve the same effect")
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.")
flagSet.Var(defaultOverlay2(), flagOverlay2, "wrap mounts with overlayfs. Format is\n"+
"* 'none' to turn overlay mode off\n"+
"* {mount}:{medium}[,size={size}], where\n"+
" 'mount' can be 'root' or 'all'\n"+
" 'medium' can be 'memory', 'self' or 'dir=/abs/dir/path' in which filestore will be created\n"+
" 'size' optional parameter overrides default overlay upper layer size\n")
flagSet.Bool("fsgofer-host-uds", false, "DEPRECATED: use host-uds=all")
flagSet.Var(hostUDSPtr(HostUDSNone), flagHostUDS, "controls permission to access host Unix-domain sockets. Values: none|open|create|all, default: none")
flagSet.Var(hostFifoPtr(HostFifoNone), "host-fifo", "controls permission to access host FIFOs (or named pipes). Values: none|open, default: none")
Expand Down
20 changes: 12 additions & 8 deletions runsc/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,7 @@ func (c *Container) forEachSelfMount(fn func(mountSrc string)) {
}
}

func createGoferConf(overlayMedium config.OverlayMedium, mountType string, mountSrc string) (boot.GoferMountConf, error) {
func createGoferConf(overlayMedium config.OverlayMedium, overlaySize string, mountType string, mountSrc string) (boot.GoferMountConf, error) {
var lower boot.GoferMountConfLowerType
switch mountType {
case boot.Bind:
Expand All @@ -915,20 +915,20 @@ func createGoferConf(overlayMedium config.OverlayMedium, mountType string, mount
case config.NoOverlay:
return boot.GoferMountConf{Lower: lower, Upper: boot.NoOverlay}, nil
case config.MemoryOverlay:
return boot.GoferMountConf{Lower: lower, Upper: boot.MemoryOverlay}, nil
return boot.GoferMountConf{Lower: lower, Upper: boot.MemoryOverlay, Size: overlaySize}, nil
case config.SelfOverlay:
mountSrcInfo, err := os.Stat(mountSrc)
if err != nil {
return boot.GoferMountConf{}, fmt.Errorf("failed to stat mount %q to see if it were a directory: %v", mountSrc, err)
}
if !mountSrcInfo.IsDir() {
log.Warningf("self filestore is only supported for directory mounts, but mount %q is not a directory, falling back to memory", mountSrc)
return boot.GoferMountConf{Lower: lower, Upper: boot.MemoryOverlay}, nil
return boot.GoferMountConf{Lower: lower, Upper: boot.MemoryOverlay, Size: overlaySize}, nil
}
return boot.GoferMountConf{Lower: lower, Upper: boot.SelfOverlay}, nil
return boot.GoferMountConf{Lower: lower, Upper: boot.SelfOverlay, Size: overlaySize}, nil
default:
if overlayMedium.IsBackedByAnon() {
return boot.GoferMountConf{Lower: lower, Upper: boot.AnonOverlay}, nil
return boot.GoferMountConf{Lower: lower, Upper: boot.AnonOverlay, Size: overlaySize}, nil
}
return boot.GoferMountConf{}, fmt.Errorf("unexpected overlay medium %q", overlayMedium)
}
Expand All @@ -939,17 +939,19 @@ func createGoferConf(overlayMedium config.OverlayMedium, mountType string, mount
func (c *Container) initGoferConfs(ovlConf config.Overlay2, mountHints *boot.PodMountHints, rootfsHint *boot.RootfsHint) error {
// Handle root mount first.
overlayMedium := ovlConf.RootOverlayMedium()
overlaySize := ovlConf.RootOverlaySize()
mountType := boot.Bind
if rootfsHint != nil {
overlayMedium = rootfsHint.Overlay
if !specutils.IsGoferMount(rootfsHint.Mount) {
mountType = rootfsHint.Mount.Type
}
overlaySize = rootfsHint.Size
}
if c.Spec.Root.Readonly {
overlayMedium = config.NoOverlay
}
goferConf, err := createGoferConf(overlayMedium, mountType, c.Spec.Root.Path)
goferConf, err := createGoferConf(overlayMedium, overlaySize, mountType, c.Spec.Root.Path)
if err != nil {
return err
}
Expand All @@ -960,7 +962,8 @@ func (c *Container) initGoferConfs(ovlConf config.Overlay2, mountHints *boot.Pod
if !specutils.IsGoferMount(c.Spec.Mounts[i]) {
continue
}
overlayMedium = ovlConf.SubMountOverlayMedium()
overlayMedium := ovlConf.SubMountOverlayMedium()
overlaySize := ovlConf.SubMountOverlaySize()
mountType = boot.Bind
if specutils.IsReadonlyMount(c.Spec.Mounts[i].Options) {
overlayMedium = config.NoOverlay
Expand All @@ -972,8 +975,9 @@ func (c *Container) initGoferConfs(ovlConf config.Overlay2, mountHints *boot.Pod
if !specutils.IsGoferMount(hint.Mount) {
mountType = hint.Mount.Type
}
overlaySize = ""
}
goferConf, err := createGoferConf(overlayMedium, mountType, c.Spec.Mounts[i].Source)
goferConf, err := createGoferConf(overlayMedium, overlaySize, mountType, c.Spec.Mounts[i].Source)
if err != nil {
return err
}
Expand Down