mirror of
https://github.com/kemko/nomad.git
synced 2026-01-05 09:55:44 +03:00
During Nomad client initialization with cgroups v2, we assert that the required cgroup controllers are available in the root `cgroup.subtree_control` file by idempotently writing to the file. But if Nomad is running with delegated cgroups, this will fail file permissions checks even if the subtree control file already has the controllers we need. Update the initialization to first check if the controllers are missing before attempting to write to them. This allows cgroup delegation so long as the cluster administrator has pre-created a Nomad owned cgroups tree and set the `Delegate` option in a systemd override. If not, initialization fails in the existing way. Although this is one small step along the way to supporting a rootless Nomad client, running Nomad as non-root is still unsupported. I've intentionally not documented setting up cgroup delegation in this PR, as this PR is insufficient by itself to have a secure and properly-working rootless Nomad client. Ref: https://github.com/hashicorp/nomad/issues/18211 Ref: https://github.com/hashicorp/nomad/issues/13669
88 lines
1.8 KiB
Go
88 lines
1.8 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
//go:build linux
|
|
|
|
package cgroupslib
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/hashicorp/go-set/v2"
|
|
)
|
|
|
|
// detect tries to detect which cgroups version we have by looking at the mount
|
|
// and whether Nomad owns the cgroup.
|
|
// - For cgroups v1 this requires root.
|
|
// - For cgroups v2 we look for root or whether we're the owner of the slice.
|
|
// - All other cases, including any file permission errors, return OFF.
|
|
func detect() Mode {
|
|
f, err := os.Open("/proc/self/mountinfo")
|
|
if err != nil {
|
|
return OFF
|
|
}
|
|
defer func() {
|
|
_ = f.Close()
|
|
}()
|
|
|
|
mode := scan(f)
|
|
|
|
if mode == CG1 && os.Geteuid() > 0 {
|
|
return OFF
|
|
}
|
|
|
|
if mode == CG2 {
|
|
if !functionalCgroups2("cgroup.controllers") {
|
|
return OFF
|
|
}
|
|
uid := os.Geteuid()
|
|
if uid > 0 {
|
|
// allow for cgroup delegation if we own the slice
|
|
cgPath := filepathCG("nomad.slice")
|
|
fi, err := os.Stat(cgPath)
|
|
if err != nil {
|
|
return OFF
|
|
}
|
|
if uid != int(fi.Sys().(*syscall.Stat_t).Uid) {
|
|
return OFF
|
|
}
|
|
}
|
|
}
|
|
|
|
return mode
|
|
}
|
|
|
|
func functionalCgroups2(controllersFile string) bool {
|
|
requiredCgroup2Controllers := []string{"cpuset", "cpu", "io", "memory", "pids"}
|
|
|
|
controllersRootPath := filepath.Join(root, controllersFile)
|
|
content, err := os.ReadFile(controllersRootPath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
rootSubtreeControllers := set.From[string](strings.Fields(string(content)))
|
|
return rootSubtreeControllers.ContainsSlice(requiredCgroup2Controllers)
|
|
}
|
|
|
|
func scan(in io.Reader) Mode {
|
|
scanner := bufio.NewScanner(in)
|
|
for scanner.Scan() {
|
|
tokens := set.From(strings.Fields(scanner.Text()))
|
|
if tokens.Contains("/sys/fs/cgroup") {
|
|
if tokens.Contains("tmpfs") {
|
|
return CG1
|
|
}
|
|
if tokens.Contains("cgroup2") {
|
|
return CG2
|
|
}
|
|
}
|
|
}
|
|
return OFF
|
|
}
|