kubelet/cm: GetResourceStats -> MemoryUsage

Commit cc50aa9dfb introduced GetResourceStats, a method which collected
all the statistics from various cgroup controllers, only to discard all
of the info collected except a single value (memory usage).

While one may argue that this method can potentially be used from other
places, this did not happen since it was added 4+ years ago.

Let's streamline this code and only collect what we need, i.e. memory
usage. Rename the method accordingly.

While at it, fix pkg/kubelet/cm/cgroup_manager_unsupported.go to not
instantiate a new error every time a method is called.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit is contained in:
Kir Kolyshkin 2021-05-21 17:26:44 -07:00
parent c299b8fc9a
commit f1aee7e049
4 changed files with 26 additions and 88 deletions

View File

@ -17,6 +17,7 @@ limitations under the License.
package cm package cm
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -29,6 +30,7 @@ import (
libcontainercgroups "github.com/opencontainers/runc/libcontainer/cgroups" libcontainercgroups "github.com/opencontainers/runc/libcontainer/cgroups"
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs" cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
cgroupfs2 "github.com/opencontainers/runc/libcontainer/cgroups/fs2" cgroupfs2 "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
cgroupsystemd "github.com/opencontainers/runc/libcontainer/cgroups/systemd" cgroupsystemd "github.com/opencontainers/runc/libcontainer/cgroups/systemd"
libcontainerconfigs "github.com/opencontainers/runc/libcontainer/configs" libcontainerconfigs "github.com/opencontainers/runc/libcontainer/configs"
libcontainerdevices "github.com/opencontainers/runc/libcontainer/devices" libcontainerdevices "github.com/opencontainers/runc/libcontainer/devices"
@ -330,27 +332,6 @@ func (m *cgroupManagerImpl) Destroy(cgroupConfig *CgroupConfig) error {
return nil return nil
} }
type subsystem interface {
// Name returns the name of the subsystem.
Name() string
// Set the cgroup represented by cgroup.
Set(path string, cgroup *libcontainerconfigs.Resources) error
// GetStats returns the statistics associated with the cgroup
GetStats(path string, stats *libcontainercgroups.Stats) error
}
// getSupportedSubsystems returns a map of subsystem and if it must be mounted for the kubelet to function.
func getSupportedSubsystems() map[subsystem]bool {
supportedSubsystems := map[subsystem]bool{
&cgroupfs.MemoryGroup{}: true,
&cgroupfs.CpuGroup{}: true,
&cgroupfs.PidsGroup{}: true,
}
// not all hosts support hugetlb cgroup, and in the absent of hugetlb, we will fail silently by reporting no capacity.
supportedSubsystems[&cgroupfs.HugetlbGroup{}] = false
return supportedSubsystems
}
// getCpuWeight converts from the range [2, 262144] to [1, 10000] // getCpuWeight converts from the range [2, 262144] to [1, 10000]
func getCpuWeight(cpuShares *uint64) uint64 { func getCpuWeight(cpuShares *uint64) uint64 {
if cpuShares == nil { if cpuShares == nil {
@ -620,53 +601,21 @@ func (m *cgroupManagerImpl) ReduceCPULimits(cgroupName CgroupName) error {
return m.Update(containerConfig) return m.Update(containerConfig)
} }
func getStatsSupportedSubsystems(cgroupPaths map[string]string) (*libcontainercgroups.Stats, error) { // MemoryUsage returns the current memory usage of the specified cgroup,
stats := libcontainercgroups.NewStats() // as read from cgroupfs.
for sys, required := range getSupportedSubsystems() { func (m *cgroupManagerImpl) MemoryUsage(name CgroupName) (int64, error) {
if _, ok := cgroupPaths[sys.Name()]; !ok { var path, file string
if required {
return nil, fmt.Errorf("failed to find subsystem mount for required subsystem: %v", sys.Name())
}
// the cgroup is not mounted, but its not required so continue...
klog.V(6).InfoS("Unable to find subsystem mount for optional subsystem", "subsystemName", sys.Name())
continue
}
if err := sys.GetStats(cgroupPaths[sys.Name()], stats); err != nil {
return nil, fmt.Errorf("failed to get stats for supported subsystems : %v", err)
}
}
return stats, nil
}
func toResourceStats(stats *libcontainercgroups.Stats) *ResourceStats {
return &ResourceStats{
MemoryStats: &MemoryStats{
Usage: int64(stats.MemoryStats.Usage.Usage),
},
}
}
// Get sets the ResourceParameters of the specified cgroup as read from the cgroup fs
func (m *cgroupManagerImpl) GetResourceStats(name CgroupName) (*ResourceStats, error) {
var err error
var stats *libcontainercgroups.Stats
if libcontainercgroups.IsCgroup2UnifiedMode() { if libcontainercgroups.IsCgroup2UnifiedMode() {
cgroupPath := m.buildCgroupUnifiedPath(name) path = m.buildCgroupUnifiedPath(name)
manager, err := cgroupfs2.NewManager(nil, cgroupPath, false) file = "memory.current"
if err != nil {
return nil, fmt.Errorf("failed to create cgroup v2 manager: %v", err)
}
stats, err = manager.GetStats()
if err != nil {
return nil, fmt.Errorf("failed to get stats for cgroup %v: %v", name, err)
}
} else { } else {
cgroupPaths := m.buildCgroupPaths(name) mp, ok := m.subsystems.MountPoints["memory"]
stats, err = getStatsSupportedSubsystems(cgroupPaths) if !ok { // should not happen
if err != nil { return -1, errors.New("no cgroup v1 mountpoint for memory controller found")
return nil, fmt.Errorf("failed to get stats supported cgroup subsystems for cgroup %v: %v", name, err)
} }
path = mp + "/" + m.Name(name)
file = "memory.usage_in_bytes"
} }
return toResourceStats(stats), nil val, err := fscommon.GetCgroupParamUint(path, file)
return int64(val), err
} }

View File

@ -18,10 +18,12 @@ limitations under the License.
package cm package cm
import "fmt" import "errors"
type unsupportedCgroupManager struct{} type unsupportedCgroupManager struct{}
var errNotSupported = errors.New("Cgroup Manager is not supported in this build")
// Make sure that unsupportedCgroupManager implements the CgroupManager interface // Make sure that unsupportedCgroupManager implements the CgroupManager interface
var _ CgroupManager = &unsupportedCgroupManager{} var _ CgroupManager = &unsupportedCgroupManager{}
@ -51,11 +53,11 @@ func (m *unsupportedCgroupManager) Update(_ *CgroupConfig) error {
} }
func (m *unsupportedCgroupManager) Create(_ *CgroupConfig) error { func (m *unsupportedCgroupManager) Create(_ *CgroupConfig) error {
return fmt.Errorf("Cgroup Manager is not supported in this build") return errNotSupported
} }
func (m *unsupportedCgroupManager) GetResourceStats(name CgroupName) (*ResourceStats, error) { func (m *unsupportedCgroupManager) MemoryUsage(_ CgroupName) (int64, error) {
return nil, fmt.Errorf("Cgroup Manager is not supported in this build") return -1, errNotSupported
} }
func (m *unsupportedCgroupManager) Pids(_ CgroupName) []int { func (m *unsupportedCgroupManager) Pids(_ CgroupName) []int {

View File

@ -28,7 +28,7 @@ import (
units "github.com/docker/go-units" units "github.com/docker/go-units"
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs" cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api/v1/resource" "k8s.io/kubernetes/pkg/api/v1/resource"
v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos"
@ -247,12 +247,11 @@ func (m *qosContainerManagerImpl) retrySetMemoryReserve(configs map[v1.PodQOSCla
// Attempt to set the limit near the current usage to put pressure // Attempt to set the limit near the current usage to put pressure
// on the cgroup and prevent further growth. // on the cgroup and prevent further growth.
for qos, config := range configs { for qos, config := range configs {
stats, err := m.cgroupManager.GetResourceStats(config.Name) usage, err := m.cgroupManager.MemoryUsage(config.Name)
if err != nil { if err != nil {
klog.V(2).InfoS("Failed to get resource stats", "err", err) klog.V(2).InfoS("Failed to get resource stats", "err", err)
return return
} }
usage := stats.MemoryStats.Usage
// Because there is no good way to determine of the original Update() // Because there is no good way to determine of the original Update()
// on the memory resource was successful, we determine failure of the // on the memory resource was successful, we determine failure of the

View File

@ -17,7 +17,7 @@ limitations under the License.
package cm package cm
import ( import (
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
) )
@ -53,18 +53,6 @@ type CgroupConfig struct {
ResourceParameters *ResourceConfig ResourceParameters *ResourceConfig
} }
// MemoryStats holds the on-demand statistics from the memory cgroup
type MemoryStats struct {
// Memory usage (in bytes).
Usage int64
}
// ResourceStats holds on-demand statistics from various cgroup subsystems
type ResourceStats struct {
// Memory statistics.
MemoryStats *MemoryStats
}
// CgroupManager allows for cgroup management. // CgroupManager allows for cgroup management.
// Supports Cgroup Creation ,Deletion and Updates. // Supports Cgroup Creation ,Deletion and Updates.
type CgroupManager interface { type CgroupManager interface {
@ -90,8 +78,8 @@ type CgroupManager interface {
Pids(name CgroupName) []int Pids(name CgroupName) []int
// ReduceCPULimits reduces the CPU CFS values to the minimum amount of shares. // ReduceCPULimits reduces the CPU CFS values to the minimum amount of shares.
ReduceCPULimits(cgroupName CgroupName) error ReduceCPULimits(cgroupName CgroupName) error
// GetResourceStats returns statistics of the specified cgroup as read from the cgroup fs. // MemoryUsage returns current memory usage of the specified cgroup, as read from the cgroupfs.
GetResourceStats(name CgroupName) (*ResourceStats, error) MemoryUsage(name CgroupName) (int64, error)
} }
// QOSContainersInfo stores the names of containers per qos // QOSContainersInfo stores the names of containers per qos