Skip to content

Commit

Permalink
cgroups: refactor v2 kill path to use cgroups.kill interface file
Browse files Browse the repository at this point in the history
This PR refactors the cgroups v2 group kill code path to use the
cgroups.kill interface file for destroying the cgroup. Previously
we copied the freeze + sigkill + unfreeze pattern from the v1 code,
but v2 provides a more efficient and more race-free way to handle
this.

Closes #14371
  • Loading branch information
shoenig committed Aug 30, 2022
1 parent 040a2ff commit a9447e2
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 44 deletions.
3 changes: 3 additions & 0 deletions .changelog/14371.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
cgroups: use cgroup.kill interface file when using cgroups v2
```
27 changes: 27 additions & 0 deletions client/lib/cgutil/editor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//go:build linux

package cgutil

import (
"os"
"path/filepath"
"strings"
)

// editor provides a simple mechanism for reading and writing cgroup files.
type editor struct {
fromRoot string
}

func (e *editor) path(file string) string {
return filepath.Join(CgroupRoot, e.fromRoot, file)
}

func (e *editor) write(file, content string) error {
return os.WriteFile(e.path(file), []byte(content), 0o644)
}

func (e *editor) read(file string) (string, error) {
b, err := os.ReadFile(e.path(file))
return strings.TrimSpace(string(b)), err
}
39 changes: 39 additions & 0 deletions client/lib/cgutil/editor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//go:build linux

package cgutil

import (
"os"
"path/filepath"
"testing"

"github.com/hashicorp/nomad/client/testutil"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/shoenig/test/must"
)

func createCG(t *testing.T) (string, func()) {
name := uuid.Short() + ".scope"
path := filepath.Join(CgroupRoot, name)
err := os.Mkdir(path, 0o755)
must.NoError(t, err)

return name, func() {
_ = os.Remove(path)
}
}

func TestCG_editor(t *testing.T) {
testutil.CgroupsCompatibleV2(t)

cg, rm := createCG(t)
t.Cleanup(rm)

edits := &editor{cg}
writeErr := edits.write("cpu.weight.nice", "13")
must.NoError(t, writeErr)

b, readErr := edits.read("cpu.weight.nice")
must.NoError(t, readErr)
must.Eq(t, "13", b)
}
55 changes: 11 additions & 44 deletions client/lib/cgutil/group_killer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"time"

"github.com/hashicorp/go-hclog"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fs"
"github.com/opencontainers/runc/libcontainer/cgroups/fs2"
"github.com/opencontainers/runc/libcontainer/configs"
)

Expand Down Expand Up @@ -96,56 +95,24 @@ func (d *killer) v1(cgroup *configs.Cgroup) error {
}

func (d *killer) v2(cgroup *configs.Cgroup) error {
if cgroup == nil {
if cgroup == nil || cgroup.Path == "" {
return errors.New("missing cgroup")
}

path := filepath.Join(CgroupRoot, cgroup.Path)

existingPIDs, err := cgroups.GetPids(path)
if err != nil {
return fmt.Errorf("failed to determine pids in cgroup: %w", err)
}

d.logger.Trace("killing processes", "cgroup_path", path, "cgroup_version", "v2", "executor_pid", d.pid, "existing_pids", existingPIDs)

mgr, err := fs2.NewManager(cgroup, "", rootless)
if err != nil {
return fmt.Errorf("failed to create v2 cgroup manager: %w", err)
}

// move executor PID into the root init.scope so we can kill the task pids
// without killing the executor (which is the process running this code, doing
// the killing)
init, err := fs2.NewManager(nil, filepath.Join(CgroupRoot, "init.scope"), rootless)
if err != nil {
return fmt.Errorf("failed to create v2 init cgroup manager: %w", err)
}
if err = init.Apply(d.pid); err != nil {
return fmt.Errorf("failed to move executor pid into init.scope cgroup: %w", err)
}

d.logger.Trace("move of executor pid into init.scope complete", "pid", d.pid)

// ability to freeze the cgroup
freeze := func() {
_ = mgr.Freeze(configs.Frozen)
}

// ability to thaw the cgroup
thaw := func() {
_ = mgr.Freeze(configs.Thawed)
// move executor (d.PID) into init.scope
editSelf := &editor{"init.scope"}
if err := editSelf.write("cgroup.procs", strconv.Itoa(d.pid)); err != nil {
return err
}

// do the common kill logic

if err = d.kill(path, freeze, thaw); err != nil {
// write "1" to cgroup.kill
editTask := &editor{cgroup.Path}
if err := editTask.write("cgroup.kill", "1"); err != nil {
return err
}

// note: do NOT remove the cgroup from disk; leave that to the alloc-level
// cpuset mananager.

// note: do NOT remove the cgroup from disk; leave that to the Client, at
// least until #14375 is implemented.
return nil
}

Expand Down

0 comments on commit a9447e2

Please sign in to comment.