Skip to content

Commit cba2ea7

Browse files
committed
Ensure cgroup v2 cpu controller is enabled
1 parent 8ec9e80 commit cba2ea7

File tree

3 files changed

+204
-30
lines changed

3 files changed

+204
-30
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package cgroups
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
)
8+
9+
const DefaultCGroupMount = "/sys/fs/cgroup"
10+
11+
type CgroupSetup int
12+
13+
const (
14+
Unknown CgroupSetup = iota
15+
Legacy
16+
Unified
17+
)
18+
19+
func (s CgroupSetup) String() string {
20+
return [...]string{"Legacy", "Unified"}[s]
21+
}
22+
23+
func GetCgroupSetup() (CgroupSetup, error) {
24+
controllers := filepath.Join(DefaultCGroupMount, "cgroup.controllers")
25+
_, err := os.Stat(controllers)
26+
27+
if os.IsNotExist(err) {
28+
return Legacy, nil
29+
}
30+
31+
if err == nil {
32+
return Unified, nil
33+
}
34+
35+
return Unknown, err
36+
}
37+
38+
func IsUnifiedCgroupSetup() (bool, error) {
39+
setup, err := GetCgroupSetup()
40+
if err != nil {
41+
return false, err
42+
}
43+
44+
return setup == Unified, nil
45+
}
46+
47+
func IsLegacyCgroupSetup() (bool, error) {
48+
setup, err := GetCgroupSetup()
49+
if err != nil {
50+
return false, err
51+
}
52+
53+
return setup == Legacy, nil
54+
}
55+
56+
func EnsureCpuControllerEnabled(basePath, cgroupPath string) error {
57+
targetPath := filepath.Join(basePath, cgroupPath)
58+
if enabled, err := isCpuControllerEnabled(targetPath); err != nil || enabled {
59+
return err
60+
}
61+
62+
err := writeCpuController(basePath)
63+
if err != nil {
64+
return err
65+
}
66+
67+
levelPath := basePath
68+
cgroupPath = strings.TrimPrefix(cgroupPath, "/")
69+
levels := strings.Split(cgroupPath, string(os.PathSeparator))
70+
for _, l := range levels[:len(levels)-1] {
71+
levelPath = filepath.Join(levelPath, l)
72+
err = writeCpuController(levelPath)
73+
if err != nil {
74+
return err
75+
}
76+
}
77+
78+
return nil
79+
}
80+
81+
func isCpuControllerEnabled(path string) (bool, error) {
82+
controllerFile := filepath.Join(path, "cgroup.controllers")
83+
controllers, err := os.ReadFile(controllerFile)
84+
if err != nil {
85+
return false, err
86+
}
87+
88+
for _, ctrl := range strings.Fields(string(controllers)) {
89+
if ctrl == "cpu" {
90+
// controller is already activated
91+
return true, nil
92+
}
93+
}
94+
95+
return false, nil
96+
}
97+
98+
func writeCpuController(path string) error {
99+
f, err := os.OpenFile(filepath.Join(path, "cgroup.subtree_control"), os.O_WRONLY, 0)
100+
if err != nil {
101+
return err
102+
}
103+
defer f.Close()
104+
105+
_, err = f.Write([]byte("+cpu"))
106+
if err != nil {
107+
return err
108+
}
109+
110+
return nil
111+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package cgroups
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
)
8+
9+
var cgroupPath = []string{"kubepods", "burstable", "pods234sdf", "234as8df34"}
10+
11+
func createHierarchy(t *testing.T, cpuEnabled bool) (string, string) {
12+
testRoot := t.TempDir()
13+
if err := os.WriteFile(filepath.Join(testRoot, "cgroup.controllers"), []byte(""), 0755); err != nil {
14+
t.Fatal(err)
15+
}
16+
17+
if err := os.WriteFile(filepath.Join(testRoot, "cgroup.subtree_control"), []byte(""), 0755); err != nil {
18+
t.Fatal(err)
19+
}
20+
21+
testCgroup := ""
22+
for i, level := range cgroupPath {
23+
testCgroup = filepath.Join(testCgroup, level)
24+
fullPath := filepath.Join(testRoot, testCgroup)
25+
if err := os.Mkdir(fullPath, 0o755); err != nil {
26+
t.Fatal(err)
27+
}
28+
29+
ctrlFile, err := os.Create(filepath.Join(fullPath, "cgroup.controllers"))
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
defer ctrlFile.Close()
34+
35+
if cpuEnabled {
36+
if _, err := ctrlFile.WriteString("cpu"); err != nil {
37+
t.Fatal(err)
38+
}
39+
}
40+
41+
subTreeFile, err := os.Create(filepath.Join(fullPath, "cgroup.subtree_control"))
42+
if err != nil {
43+
t.Fatal(err)
44+
}
45+
defer subTreeFile.Close()
46+
47+
if cpuEnabled && i < len(cgroupPath)-1 {
48+
if _, err := subTreeFile.WriteString("cpu"); err != nil {
49+
t.Fatal(err)
50+
}
51+
}
52+
}
53+
54+
return testRoot, testCgroup
55+
}
56+
57+
func TestEnableController(t *testing.T) {
58+
root, cgroup := createHierarchy(t, false)
59+
if err := EnsureCpuControllerEnabled(root, cgroup); err != nil {
60+
t.Fatal(err)
61+
}
62+
63+
levelPath := root
64+
for _, level := range cgroupPath {
65+
verifyCpuControllerToggled(t, levelPath, true)
66+
levelPath = filepath.Join(levelPath, level)
67+
}
68+
69+
verifyCpuControllerToggled(t, levelPath, false)
70+
}
71+
72+
func verifyCpuControllerToggled(t *testing.T, path string, enabled bool) {
73+
t.Helper()
74+
75+
content, err := os.ReadFile(filepath.Join(path, "cgroup.subtree_control"))
76+
if err != nil {
77+
t.Fatal(err)
78+
}
79+
80+
if enabled && string(content) != "+cpu" {
81+
t.Fatalf("%s should have enabled cpu controller", path)
82+
} else if !enabled && string(content) == "+cpu" {
83+
t.Fatalf("%s should not have enabled cpu controller", path)
84+
}
85+
}

components/ws-daemon/pkg/cpulimit/dispatch.go

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,16 @@ package cpulimit
66

77
import (
88
"context"
9-
"os"
109
"path/filepath"
11-
"strings"
1210
"sync"
1311
"time"
1412

15-
"github.com/opencontainers/runc/libcontainer/cgroups/fs2"
16-
"github.com/opencontainers/runc/libcontainer/configs"
1713
"github.com/prometheus/client_golang/prometheus"
1814
"github.com/sirupsen/logrus"
1915
"golang.org/x/xerrors"
2016
"k8s.io/apimachinery/pkg/api/resource"
2117

18+
"github.com/gitpod-io/gitpod/common-go/cgroups"
2219
wsk8s "github.com/gitpod-io/gitpod/common-go/kubernetes"
2320
"github.com/gitpod-io/gitpod/common-go/log"
2421
"github.com/gitpod-io/gitpod/common-go/util"
@@ -225,38 +222,19 @@ func (d *DispatchListener) WorkspaceUpdated(ctx context.Context, ws *dispatch.Wo
225222
}
226223

227224
func newCFSController(basePath, cgroupPath string) (CFSController, error) {
228-
controllers := filepath.Join(basePath, "cgroup.controllers")
229-
_, err := os.Stat(controllers)
230-
231-
if os.IsNotExist(err) {
232-
return CgroupV1CFSController(filepath.Join(basePath, "cpu", cgroupPath)), nil
225+
unified, err := cgroups.IsUnifiedCgroupSetup()
226+
if err != nil {
227+
return nil, xerrors.Errorf("could not determine cgroup setup: %w", err)
233228
}
234229

235-
if err == nil {
230+
if unified {
236231
fullPath := filepath.Join(basePath, cgroupPath)
237-
if err := ensureControllerEnabled(fullPath, "cpu"); err != nil {
232+
if err := cgroups.EnsureCpuControllerEnabled(basePath, cgroupPath); err != nil {
238233
return nil, err
239234
}
240235

241236
return CgroupV2CFSController(fullPath), nil
237+
} else {
238+
return CgroupV1CFSController(filepath.Join(basePath, "cpu", cgroupPath)), nil
242239
}
243-
244-
return nil, err
245-
}
246-
247-
func ensureControllerEnabled(targetPath, controller string) error {
248-
controllerFile := filepath.Join(targetPath, "cgroup.controllers")
249-
controllers, err := os.ReadFile(controllerFile)
250-
if err != nil {
251-
return err
252-
}
253-
254-
for _, ctrl := range strings.Fields(string(controllers)) {
255-
if ctrl == controller {
256-
// controller is already activated
257-
return nil
258-
}
259-
}
260-
261-
return fs2.CreateCgroupPath(targetPath, &configs.Cgroup{})
262240
}

0 commit comments

Comments
 (0)