[lxc-devel] [lxd/master] Basic infrastructure for cgroup abstraction

stgraber on Github lxc-bot at linuxcontainers.org
Mon Dec 9 20:16:13 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191209/499e6351/attachment.bin>
-------------- next part --------------
From f873bc3caf8fe7febf3dbfaea0e09dbab71772ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 9 Dec 2019 15:14:55 -0500
Subject: [PATCH 1/3] lxd/cgroup: Add basic cgroup abstraction
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/cgroup/abstraction.go | 31 ++++++++++++++++
 lxd/cgroup/errors.go      | 11 ++++++
 lxd/cgroup/init.go        | 74 +++++++++++++++++++++++++++++++++++++++
 lxd/cgroup/load.go        | 17 +++++++++
 lxd/cgroup/types.go       | 23 ++++++++++++
 5 files changed, 156 insertions(+)
 create mode 100644 lxd/cgroup/abstraction.go
 create mode 100644 lxd/cgroup/errors.go
 create mode 100644 lxd/cgroup/init.go
 create mode 100644 lxd/cgroup/load.go
 create mode 100644 lxd/cgroup/types.go

diff --git a/lxd/cgroup/abstraction.go b/lxd/cgroup/abstraction.go
new file mode 100644
index 0000000000..8144aab6a5
--- /dev/null
+++ b/lxd/cgroup/abstraction.go
@@ -0,0 +1,31 @@
+package cgroup
+
+import (
+	"fmt"
+)
+
+// CGroup represents the main cgroup abstraction.
+type CGroup struct {
+	rw ReadWriter
+}
+
+// SetMaxProcesses applies a limit to the number of processes
+func (cg *CGroup) SetMaxProcesses(max int64) error {
+	// Confirm we have the controller
+	version := cgControllers["pids"]
+	if version == Unavailable {
+		return ErrControllerMissing
+	}
+
+	// V1/V2 behavior
+	if version == V1 || version == V2 {
+		// Setting pids limits is conveniently identical on V1 and V2.
+		if max == -1 {
+			return cg.rw.Set(version, "pids", "pids.max", "max")
+		}
+
+		return cg.rw.Set(version, "pids", "pids.max", fmt.Sprintf("%d", max))
+	}
+
+	return ErrUnknownVersion
+}
diff --git a/lxd/cgroup/errors.go b/lxd/cgroup/errors.go
new file mode 100644
index 0000000000..7e86feeac3
--- /dev/null
+++ b/lxd/cgroup/errors.go
@@ -0,0 +1,11 @@
+package cgroup
+
+import (
+	"fmt"
+)
+
+// ErrControllerMissing indicates that the requested controller isn't setup on the system.
+var ErrControllerMissing = fmt.Errorf("Cgroup controller is missing")
+
+// ErrUnknownVersion indicates that a version other than those supported was detected during init.
+var ErrUnknownVersion = fmt.Errorf("Unknown cgroup version")
diff --git a/lxd/cgroup/init.go b/lxd/cgroup/init.go
new file mode 100644
index 0000000000..6cd78e24af
--- /dev/null
+++ b/lxd/cgroup/init.go
@@ -0,0 +1,74 @@
+package cgroup
+
+import (
+	"bufio"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/lxc/lxd/shared/logger"
+)
+
+var cgControllers = map[string]Backend{}
+
+func init() {
+	// Go through the list of resource controllers for LXD.
+	selfCg, err := os.Open("/proc/self/cgroup")
+	if err != nil {
+		if os.IsNotExist(err) {
+			logger.Warnf("System doesn't appear to support CGroups")
+		} else {
+			logger.Errorf("Unable to load list of cgroups: %v", err)
+		}
+
+		return
+	}
+	defer selfCg.Close()
+
+	// Go through the file line by line.
+	scanSelfCg := bufio.NewScanner(selfCg)
+	for scanSelfCg.Scan() {
+		line := strings.TrimSpace(scanSelfCg.Text())
+		fields := strings.SplitN(line, ":", 3)
+
+		// Deal with the V1 controllers.
+		if fields[1] != "" {
+			controllers := strings.Split(fields[1], ",")
+			for _, controller := range controllers {
+				cgControllers[controller] = V1
+			}
+
+			continue
+		}
+
+		// Parse V2 controllers.
+		path := fields[2]
+		hybridPath := filepath.Join(cgPath, "unified", path, "cgroup.controllers")
+		dedicatedPath := filepath.Join(cgPath, path, "cgroup.controllers")
+
+		controllers, err := os.Open(hybridPath)
+		if err != nil {
+			if !os.IsNotExist(err) {
+				logger.Errorf("Unable to load cgroup.controllers")
+				return
+			}
+
+			controllers, err = os.Open(dedicatedPath)
+			if !os.IsNotExist(err) {
+				logger.Errorf("Unable to load cgroup.controllers")
+				return
+			}
+		}
+
+		if err == nil {
+			// Record the fact that V2 is present at all.
+			cgControllers["unified"] = V2
+
+			scanControllers := bufio.NewScanner(controllers)
+			for scanControllers.Scan() {
+				line := strings.TrimSpace(scanSelfCg.Text())
+				cgControllers[line] = V2
+			}
+		}
+	}
+}
diff --git a/lxd/cgroup/load.go b/lxd/cgroup/load.go
new file mode 100644
index 0000000000..6543403218
--- /dev/null
+++ b/lxd/cgroup/load.go
@@ -0,0 +1,17 @@
+package cgroup
+
+import (
+	"fmt"
+)
+
+// New setups a new CGroup abstraction using the provided read/writer.
+func New(rw ReadWriter) (*CGroup, error) {
+	if rw == nil {
+		return nil, fmt.Errorf("A CGroup read/writer is required")
+	}
+
+	cg := CGroup{}
+	cg.rw = rw
+
+	return &cg, nil
+}
diff --git a/lxd/cgroup/types.go b/lxd/cgroup/types.go
new file mode 100644
index 0000000000..124d7e83b7
--- /dev/null
+++ b/lxd/cgroup/types.go
@@ -0,0 +1,23 @@
+package cgroup
+
+var cgPath = "/sys/fs/cgroup"
+
+// Backend indicates whether to use v1, v2 or unavailable.
+type Backend int
+
+const (
+	// Unavailable indicates the lack of controller.
+	Unavailable = Backend(0)
+
+	// V1 indicates the controller is backed by Cgroup V1.
+	V1 = Backend(1)
+
+	// V2 indicates the controller is backed by Cgroup V2.
+	V2 = Backend(2)
+)
+
+// The ReadWriter interface is used to read/write cgroup data.
+type ReadWriter interface {
+	Get(backend Backend, controller string, key string) (string, error)
+	Set(backend Backend, controller string, key string, value string) error
+}

From dc00626b9c537346a60e919730a47988b07d0cb9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 9 Dec 2019 15:15:06 -0500
Subject: [PATCH 2/3] lxd/container: Add wrapper for cgroup abstraction
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 45 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 33d1bb1cd5..b40881b88e 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -6926,3 +6926,48 @@ func (c *containerLXC) maasDelete() error {
 
 	return c.state.MAAS.DeleteContainer(project.Prefix(c.project, c.name))
 }
+
+func (c *containerLXC) cgroup(cc *lxc.Container) (*cgroup.CGroup, error) {
+	rw := lxcCgroupReadWriter{}
+	if cc != nil {
+		rw.cc = cc
+		rw.conf = true
+	} else {
+		rw.cc = c.c
+	}
+
+	return cgroup.New(&rw)
+}
+
+type lxcCgroupReadWriter struct {
+	cc   *lxc.Container
+	conf bool
+}
+
+func (rw *lxcCgroupReadWriter) Get(version cgroup.Backend, controller string, key string) (string, error) {
+	if rw.conf {
+		lxcKey := fmt.Sprintf("lxc.cgroup.%s", key)
+
+		if version == cgroup.V2 {
+			lxcKey = fmt.Sprintf("lxc.cgroup2.%s", key)
+		}
+
+		return strings.Join(rw.cc.ConfigItem(lxcKey), "\n"), nil
+
+		return "", fmt.Errorf("CGroup key %s isn't set", key)
+	}
+
+	return strings.Join(rw.cc.CgroupItem(key), "\n"), nil
+}
+
+func (rw *lxcCgroupReadWriter) Set(version cgroup.Backend, controller string, key string, value string) error {
+	if rw.conf {
+		if version == cgroup.V1 {
+			return lxcSetConfigItem(rw.cc, fmt.Sprintf("lxc.cgroup.%s", key), value)
+		}
+
+		return lxcSetConfigItem(rw.cc, fmt.Sprintf("lxc.cgroup2.%s", key), value)
+	}
+
+	return rw.cc.SetCgroupItem(key, value)
+}

From c8301ed674489e9b55848c2b93667f5f312edb54 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 9 Dec 2019 15:15:21 -0500
Subject: [PATCH 3/3] lxd/container: Port pids.max to cgroup abstraction
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 26 ++++++++++++++++++++++----
 1 file changed, 22 insertions(+), 4 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index b40881b88e..a1aeef3d7a 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -693,6 +693,12 @@ func (c *containerLXC) initLXC(config bool) error {
 		return err
 	}
 
+	// Load cgroup abstraction
+	cg, err := c.cgroup(cc)
+	if err != nil {
+		return err
+	}
+
 	freeContainer := true
 	defer func() {
 		if freeContainer {
@@ -1197,7 +1203,7 @@ func (c *containerLXC) initLXC(config bool) error {
 				return err
 			}
 
-			err = lxcSetConfigItem(cc, "lxc.cgroup.pids.max", fmt.Sprintf("%d", valueInt))
+			err = cg.SetMaxProcesses(valueInt)
 			if err != nil {
 				return err
 			}
@@ -2589,10 +2595,16 @@ func (c *containerLXC) Stop(stateful bool) error {
 		}
 	}
 
+	// Load cgroup abstraction
+	cg, err := c.cgroup(nil)
+	if err != nil {
+		return err
+	}
+
 	// Fork-bomb mitigation, prevent forking from this point on
 	if c.state.OS.CGroupPidsController {
 		// Attempt to disable forking new processes
-		c.CGroupSet("pids.max", "0")
+		cg.SetMaxProcesses(0)
 	} else if c.state.OS.CGroupFreezerController {
 		// Attempt to freeze the container
 		freezer := make(chan bool, 1)
@@ -4079,6 +4091,12 @@ func (c *containerLXC) Update(args db.InstanceArgs, userRequested bool) error {
 		return errors.Wrap(err, "Initialize LXC")
 	}
 
+	// Load cgroup abstraction
+	cg, err := c.cgroup(nil)
+	if err != nil {
+		return err
+	}
+
 	// If apparmor changed, re-validate the apparmor profile
 	if shared.StringInSlice("raw.apparmor", changedConfig) || shared.StringInSlice("security.nesting", changedConfig) {
 		err = apparmor.ParseProfile(c)
@@ -4401,7 +4419,7 @@ func (c *containerLXC) Update(args db.InstanceArgs, userRequested bool) error {
 				}
 
 				if value == "" {
-					err = c.CGroupSet("pids.max", "max")
+					err = cg.SetMaxProcesses(-1)
 					if err != nil {
 						return err
 					}
@@ -4411,7 +4429,7 @@ func (c *containerLXC) Update(args db.InstanceArgs, userRequested bool) error {
 						return err
 					}
 
-					err = c.CGroupSet("pids.max", fmt.Sprintf("%d", valueInt))
+					err = cg.SetMaxProcesses(valueInt)
 					if err != nil {
 						return err
 					}


More information about the lxc-devel mailing list