
The rpc only reports one field, i.e. the cgroup driver, to kubelet. Containerd determines the effective cgroup driver by looking at all runtime handlers, starting from the default runtime handler (the rest in alphabetical order), and returning the cgroup driver setting of the first runtime handler that supports one. If no runtime handler supports cgroup driver (i.e. has a config option for it) containerd falls back to auto-detection, returning systemd if systemd is running and cgroupfs otherwise. This patch implements the CRI server side of Kubernetes KEP-4033: https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/4033-group-driver-detection-over-cri Signed-off-by: Markus Lehtonen <markus.lehtonen@intel.com>
153 lines
4.5 KiB
Go
153 lines
4.5 KiB
Go
package fs2
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
)
|
|
|
|
func supportedControllers() (string, error) {
|
|
return cgroups.ReadFile(UnifiedMountpoint, "/cgroup.controllers")
|
|
}
|
|
|
|
// needAnyControllers returns whether we enable some supported controllers or not,
|
|
// based on (1) controllers available and (2) resources that are being set.
|
|
// We don't check "pseudo" controllers such as
|
|
// "freezer" and "devices".
|
|
func needAnyControllers(r *configs.Resources) (bool, error) {
|
|
if r == nil {
|
|
return false, nil
|
|
}
|
|
|
|
// list of all available controllers
|
|
content, err := supportedControllers()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
avail := make(map[string]struct{})
|
|
for _, ctr := range strings.Fields(content) {
|
|
avail[ctr] = struct{}{}
|
|
}
|
|
|
|
// check whether the controller if available or not
|
|
have := func(controller string) bool {
|
|
_, ok := avail[controller]
|
|
return ok
|
|
}
|
|
|
|
if isPidsSet(r) && have("pids") {
|
|
return true, nil
|
|
}
|
|
if isMemorySet(r) && have("memory") {
|
|
return true, nil
|
|
}
|
|
if isIoSet(r) && have("io") {
|
|
return true, nil
|
|
}
|
|
if isCpuSet(r) && have("cpu") {
|
|
return true, nil
|
|
}
|
|
if isCpusetSet(r) && have("cpuset") {
|
|
return true, nil
|
|
}
|
|
if isHugeTlbSet(r) && have("hugetlb") {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// containsDomainController returns whether the current config contains domain controller or not.
|
|
// Refer to: http://man7.org/linux/man-pages/man7/cgroups.7.html
|
|
// As at Linux 4.19, the following controllers are threaded: cpu, perf_event, and pids.
|
|
func containsDomainController(r *configs.Resources) bool {
|
|
return isMemorySet(r) || isIoSet(r) || isCpuSet(r) || isHugeTlbSet(r)
|
|
}
|
|
|
|
// CreateCgroupPath creates cgroupv2 path, enabling all the supported controllers.
|
|
func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
|
|
if !strings.HasPrefix(path, UnifiedMountpoint) {
|
|
return fmt.Errorf("invalid cgroup path %s", path)
|
|
}
|
|
|
|
content, err := supportedControllers()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
const (
|
|
cgTypeFile = "cgroup.type"
|
|
cgStCtlFile = "cgroup.subtree_control"
|
|
)
|
|
ctrs := strings.Fields(content)
|
|
res := "+" + strings.Join(ctrs, " +")
|
|
|
|
elements := strings.Split(path, "/")
|
|
elements = elements[3:]
|
|
current := "/sys/fs"
|
|
for i, e := range elements {
|
|
current = filepath.Join(current, e)
|
|
if i > 0 {
|
|
if err := os.Mkdir(current, 0o755); err != nil {
|
|
if !os.IsExist(err) {
|
|
return err
|
|
}
|
|
} else {
|
|
// If the directory was created, be sure it is not left around on errors.
|
|
current := current
|
|
defer func() {
|
|
if Err != nil {
|
|
os.Remove(current)
|
|
}
|
|
}()
|
|
}
|
|
cgType, _ := cgroups.ReadFile(current, cgTypeFile)
|
|
cgType = strings.TrimSpace(cgType)
|
|
switch cgType {
|
|
// If the cgroup is in an invalid mode (usually this means there's an internal
|
|
// process in the cgroup tree, because we created a cgroup under an
|
|
// already-populated-by-other-processes cgroup), then we have to error out if
|
|
// the user requested controllers which are not thread-aware. However, if all
|
|
// the controllers requested are thread-aware we can simply put the cgroup into
|
|
// threaded mode.
|
|
case "domain invalid":
|
|
if containsDomainController(c.Resources) {
|
|
return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in an invalid state", current)
|
|
} else {
|
|
// Not entirely correct (in theory we'd always want to be a domain --
|
|
// since that means we're a properly delegated cgroup subtree) but in
|
|
// this case there's not much we can do and it's better than giving an
|
|
// error.
|
|
_ = cgroups.WriteFile(current, cgTypeFile, "threaded")
|
|
}
|
|
// If the cgroup is in (threaded) or (domain threaded) mode, we can only use thread-aware controllers
|
|
// (and you cannot usually take a cgroup out of threaded mode).
|
|
case "domain threaded":
|
|
fallthrough
|
|
case "threaded":
|
|
if containsDomainController(c.Resources) {
|
|
return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in %s mode", current, cgType)
|
|
}
|
|
}
|
|
}
|
|
// enable all supported controllers
|
|
if i < len(elements)-1 {
|
|
if err := cgroups.WriteFile(current, cgStCtlFile, res); err != nil {
|
|
// try write one by one
|
|
allCtrs := strings.Split(res, " ")
|
|
for _, ctr := range allCtrs {
|
|
_ = cgroups.WriteFile(current, cgStCtlFile, ctr)
|
|
}
|
|
}
|
|
// Some controllers might not be enabled when rootless or containerized,
|
|
// but we don't catch the error here. (Caught in setXXX() functions.)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|