Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deps: switch to runc/libcontainer/cgroups cgroup manager, from sylabs 531 #41

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions LICENSE_DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ The dependencies and their licenses are as follows:

**License URL:** <https://github.com/Netflix/go-expect/blob/master/LICENSE>

## github.com/containerd/cgroups

**License:** Apache-2.0

**License URL:** <https://github.com/containerd/cgroups/blob/master/LICENSE>

## github.com/containerd/containerd

**License:** Apache-2.0
Expand Down Expand Up @@ -179,11 +173,11 @@ The dependencies and their licenses are as follows:

**License URL:** <https://github.com/opencontainers/image-spec/blob/master/specs-go/LICENSE>

## github.com/opencontainers/runc/libcontainer/user
## github.com/opencontainers/runc/libcontainer

**License:** Apache-2.0

**License URL:** <https://github.com/opencontainers/runc/blob/master/libcontainer/user/LICENSE>
**License URL:** <https://github.com/opencontainers/runc/blob/master/libcontainer/LICENSE>

## github.com/opencontainers/runtime-spec/specs-go

Expand Down Expand Up @@ -371,11 +365,11 @@ The dependencies and their licenses are as follows:

**License URL:** <https://github.com/cyphar/filepath-securejoin/blob/master/LICENSE>

## github.com/gogo/protobuf
## github.com/gogo/protobuf/proto

**License:** BSD-3-Clause

**License URL:** <https://github.com/gogo/protobuf/blob/master/LICENSE>
**License URL:** <https://github.com/gogo/protobuf/blob/master/proto/LICENSE>

## github.com/golang/protobuf

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/buger/jsonparser v1.1.1
github.com/cenkalti/backoff/v4 v4.1.2
github.com/containerd/cgroups v1.0.3
github.com/containerd/containerd v1.6.1
github.com/containernetworking/cni v1.0.1
github.com/containernetworking/plugins v1.1.0
Expand All @@ -27,6 +26,7 @@ require (
github.com/moby/sys/mount v0.3.0 // indirect
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.3-0.20211202193544-a5463b7f9c84
github.com/opencontainers/runc v1.1.0
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
github.com/opencontainers/runtime-tools v0.9.1-0.20210326182921-59cdde06764b
github.com/opencontainers/selinux v1.10.0
Expand Down
1 change: 0 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1204,7 +1204,6 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
Expand Down
2 changes: 0 additions & 2 deletions internal/pkg/cgroups/config_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ func Int64ptr(i int) *int64 {
return &t
}

var wildcard = Int64ptr(-1)

// LinuxHugepageLimit structure corresponds to limiting kernel hugepages
type LinuxHugepageLimit struct {
// Pagesize is the hugepage size
Expand Down
278 changes: 278 additions & 0 deletions internal/pkg/cgroups/manager_libcontainer_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
// Copyright (c) Contributors to the Apptainer project, established as
// Apptainer a Series of LF Projects LLC.
// For website terms of use, trademark policy, privacy policy and other
// project policies see https://lfprojects.org/policies
// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved.
DrDaveD marked this conversation as resolved.
Show resolved Hide resolved
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.

package cgroups

import (
"fmt"
"path/filepath"
"strings"

lccgroups "github.com/opencontainers/runc/libcontainer/cgroups"
lcmanager "github.com/opencontainers/runc/libcontainer/cgroups/manager"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/specconv"
specs "github.com/opencontainers/runtime-spec/specs-go"
)

const unifiedMountPoint = "/sys/fs/cgroup"

// ManagerLibcontainer manages a cgroup 'Group', using the runc/libcontainer packages
type ManagerLC struct {
group string
pid int
cgroup lccgroups.Manager
}

func (m *ManagerLC) load() (err error) {
if m.group != "" {
return m.loadFromPath()
}
return m.loadFromPid()
}

func (m *ManagerLC) loadFromPid() (err error) {
if m.pid == 0 {
return fmt.Errorf("cannot load from pid - no process ID specified")
}

pidCGFile := fmt.Sprintf("/proc/%d/cgroup", m.pid)
paths, err := lccgroups.ParseCgroupFile(pidCGFile)
if err != nil {
return fmt.Errorf("cannot read %s: %w", pidCGFile, err)
}

// cgroups v2 path is always given by the unified "" subsystem
ok := false
if lccgroups.IsCgroup2UnifiedMode() {
m.group, ok = paths[""]
if !ok {
return fmt.Errorf("could not find cgroups v2 unified path")
}
return m.loadFromPath()
}

// For cgroups v1 we are relying on fetching the 'devices' subsystem path.
// The devices subsystem is needed for our OCI engine and its presence is
// enforced in runc/libcontainer/cgroups/fs initialization without 'skipDevices'.
// This means we never explicitly put a container into a cgroup without a
// set 'devices' path.
m.group, ok = paths["devices"]
if !ok {
return fmt.Errorf("could not find cgroups v1 path (using devices subsystem)")
}
return m.loadFromPath()
}

func (m *ManagerLC) loadFromPath() (err error) {
if m.group == "" {
return fmt.Errorf("cannot load from path - no path specified")
}

lcConfig := &configs.Cgroup{
Path: m.group,
Resources: &configs.Resources{},
}

m.cgroup, err = lcmanager.New(lcConfig)
if err != nil {
return fmt.Errorf("while creating cgroup manager: %w", err)
}

return nil
}

// GetCgroupRootPath returns cgroup root path
// TODO - this returns "" on error which needs to be checked for
// carefully. Should return an actual error instead.
func (m *ManagerLC) GetCgroupRootPath() string {
if m.cgroup == nil {
return ""
}

// v2 - has a single fixed mountpoint for the root cgroup
if lccgroups.IsCgroup2UnifiedMode() {
return unifiedMountPoint
}

// v1 - Get absolute paths to cgroup by subsystem
subPaths := m.cgroup.GetPaths()

// For cgroups v1 we are relying on fetching the 'devices' subsystem path.
// The devices subsystem is needed for our OCI engine and its presence is
// enforced in runc/libcontainer/cgroups/fs initialization without 'skipDevices'.
// This means we never explicitly put a container into a cgroup without a
// set 'devices' path.
devicePath, ok := subPaths["devices"]
if !ok {
return ""
}

// Take the piece before the first occurrence of "devices" as the root.
// I.E. /sys/fs/cgroup/devices/singularity/196219 -> /sys/fs/cgroup
pathParts := strings.Split(devicePath, "devices")
if len(pathParts) != 2 {
return ""
}

return filepath.Clean(pathParts[0])
}

// ApplyFromSpec applies a cgroups configuration from an OCI LinuxResources spec
// struct, creating a new group if necessary, and places the process with
// Manager.Pid into the cgroup. The `Unified` key for native v2 cgroup
// specifications is not yet supported.
func (m *ManagerLC) ApplyFromSpec(resources *specs.LinuxResources) (err error) {
if m.group == "" {
return fmt.Errorf("path must be specified when creating a cgroup")
}
if m.pid == 0 {
return fmt.Errorf("pid must be specified when creating a cgroup")
}

spec := &specs.Spec{
Linux: &specs.Linux{
CgroupsPath: m.group,
Resources: resources,
},
}

opts := &specconv.CreateOpts{
CgroupName: m.group,
UseSystemdCgroup: false,
RootlessCgroups: false,
Spec: spec,
}

lcConfig, err := specconv.CreateCgroupConfig(opts, nil)
if err != nil {
return fmt.Errorf("could not create cgroup config: %w", err)
}

m.cgroup, err = lcmanager.New(lcConfig)
if err != nil {
return fmt.Errorf("while creating cgroup manager: %w", err)
}

err = m.cgroup.Apply(m.pid)
if err != nil {
return fmt.Errorf("while creating cgroup: %w", err)
}

err = m.cgroup.Set(lcConfig.Resources)
if err != nil {
return fmt.Errorf("while setting cgroup limits: %w", err)
}

return nil
}

// ApplyFromFile applies a cgroup configuration from a toml file, creating a new
// group if necessary, and places the process with Manager.Pid into the cgroup.
// The `Unified` key for native v2 cgroup specifications is not yet supported.
func (m *ManagerLC) ApplyFromFile(path string) error {
spec, err := readSpecFromFile(path)
if err != nil {
return err
}
return m.ApplyFromSpec(&spec)
}

// UpdateFromSpec updates the existing managed cgroup using configuration from
// an OCI LinuxResources spec struct. The `Unified` key for native v2 cgroup
// specifications is not yet supported.
func (m *ManagerLC) UpdateFromSpec(resources *specs.LinuxResources) (err error) {
if m.cgroup == nil {
err = m.load()
if err != nil {
return fmt.Errorf("while creating cgroup manager: %w", err)
}
}
if m.group == "" {
return fmt.Errorf("cgroup path not set on manager, cannot update")
}

spec := &specs.Spec{
Linux: &specs.Linux{
CgroupsPath: m.group,
Resources: resources,
},
}

opts := &specconv.CreateOpts{
CgroupName: m.group,
UseSystemdCgroup: false,
RootlessCgroups: false,
Spec: spec,
}

lcConfig, err := specconv.CreateCgroupConfig(opts, nil)
if err != nil {
return fmt.Errorf("could not create cgroup config: %w", err)
}

err = m.cgroup.Set(lcConfig.Resources)
if err != nil {
return fmt.Errorf("while setting cgroup limits: %w", err)
}

return nil
}

// UpdateFromFile updates the existing managed cgroup using configuration
// from a toml file.
func (m *ManagerLC) UpdateFromFile(path string) error {
spec, err := readSpecFromFile(path)
if err != nil {
return err
}
return m.UpdateFromSpec(&spec)
}

// Remove deletes the managed cgroup.
func (m *ManagerLC) Remove() (err error) {
if m.cgroup == nil {
if err := m.load(); err != nil {
return err
}
}
return m.cgroup.Destroy()
}

func (m *ManagerLC) AddProc(pid int) (err error) {
if pid == 0 {
return fmt.Errorf("cannot add a zero pid to cgroup")
}
if m.cgroup == nil {
if err := m.load(); err != nil {
return err
}
}
return m.cgroup.Apply(pid)
}

// Pause freezes processes in the managed cgroup.
func (m *ManagerLC) Pause() (err error) {
if m.cgroup == nil {
if err := m.load(); err != nil {
return err
}
}
return m.cgroup.Freeze(configs.Frozen)
}

// Resume unfreezes process in the managed cgroup.
func (m *ManagerLC) Resume() (err error) {
if m.cgroup == nil {
if err := m.load(); err != nil {
return err
}
}
return m.cgroup.Freeze(configs.Thawed)
}
Loading