Runc 作为 OCI 运行时标准的实现版本工具,其继承早期版本 Docker 的核心库 libcontainer 来 实现的 linux 系统层的资源隔离 、限制及控制等功能。Docker 容器技术通过利用 linux 的内核特性功能 cgroup 来限制与控制 container 的资源使用。本文将通过对 Runc 的 Cgroup 相关源码解析揭开如何对 cgroup 利用与实现对容器资源管控的面纱。linux 系统层的 cgroup 的基础知识本文将不做过多的介绍,请参考。
Runc 支持两种 cgroup driver: 一种是 cgroupfs ,一种是 systemd,在 runc 源码目录上也可以看到相应的两个目录 fs 和 systemd。而 kubelet 指定的 cgroup driver 为 cgroupfs,我们本文也仅关注于 cgroupfs 的分析,代码文件libcontainer/cgroups/fs/apply_raw.go 为实现 cgroupfs Manager管理操作。更多关于 cgroup 文件、目录详细说明可参考本文后的附录一。
linux 系统默认情况下将 mount cgroupfs 目录" /sys/fs/cgroup/ "和" /proc/$pid/cgroup "两个目录下操作实现对 进程的资源限制。对于 cgroupfs 文件系统操作方式也同样是 runC 实现的 cgroup 操作的关键接口所在,这也是runC对Cgroup操作根本原理所在。
本文先从 runc 执行过程中涉及 cgroup 的初始化、配置、应用的整个相关的过程分析,如要了解完整的 container run 的所有详细执行过程可参考本套 RunC 系列文档《 RunC 源码通读指南之 Run 》,本文仅指出执行流程中与 cgroup 相关初始化及应用的过程。其后分析了cgroup manager 和 subsystem 的实现详细分析,其实就是对 cgroup 的 CRUD 操作实现细节。最后在附录内附有 cgroup包的文件说明、公共 utils 方法功能解析说明以及各 subsystem 限制资源配置项的用途说明。
Container 的创建过程由 factory 调用 create 方法实现,在创建 factory 对象时指定了NewCgroupsManage func,在 factory 创建 container 时调用 func 为容器配置了fs.Manager对象。调用过程 runc create 命令创建容器开始: startContainer() => createContainer() => loadFactory() => libcontainer.New()
!FILENAME libcontainer/factory_linux.go:131
func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
//...
l := &LinuxFactory{
Root: root,
InitPath: "/proc/self/exe",
InitArgs: []string{os.Args[0], "init"},
Validator: validate.New(),
CriuPath: "criu",
}
Cgroupfs(l) //为LinuxFactory配置NewCgroupsManage实现func
//...
return l, nil
}
初始化配置LinuxFactory对象的NewCgroupsManage的func赋值,func将根据参数配置返回一个fs.Manager对象
!FILENAME libcontainer/factory_linux.go:65
// Cgroupfs is an options func to configure a LinuxFactory to return containers
// that use the native cgroups filesystem implementation to create and manage
// cgroups.
func Cgroupfs(l *LinuxFactory) error {
l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
return &fs.Manager{
Cgroups: config,
Paths: paths,
}
}
return nil
}
创建 Container 容器对象,返回 linuxContainer 结构。LinuxFactory.NewCgroupsManager() 调用根据全局 config 赋值并返回 Cgroup Manager 对象 fs.Manger
!FILENAME libcontainer/factory_linux.go:188
func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, error) {
//...
c := &linuxContainer{
id: id,
root: containerRoot,
config: config,
initPath: l.InitPath,
initArgs: l.InitArgs,
criuPath: l.CriuPath,
newuidmapPath: l.NewuidmapPath,
newgidmapPath: l.NewgidmapPath,
cgroupManager: l.NewCgroupsManager(config.Cgroups, nil), //为容器指定fs.Manager
}
//...
return c, nil
}
从容器的执行流程来看,此时已完成 container 对象的创建,接下来startContainer()中已创建的 runner 对象 run() 方法执行,容器进入运行阶段。执行流程runc run命令:runner.run() => newProcess() => runner.container.Run(process) => linuxContainer.strat() => linuxContainer.newParentProcess(process) => =>linuxContainer.commandTemplate() => linuxContaine.newInitProcess() =>parent.start() => initProcess.start() 。
Parent.start() 执行其实则是 runc init 命令的执行,其基本的执行流程(详细请参考《 RunC 源码通读指南之 Run 》):
- parentproces 创建runc init子进程,中间会被 /runc/libcontainer/nsenter 劫持(c代码部分preamble),使 runc init 子进程位于容器配置指定的各个 namespace 内
- parentProcess 用init管道将容器配置信息传输给runc init进程,runc init再据此进行容器的初始化操作。初始化完成之后,再向另一个管道exec.fifo进行写操作,进入阻塞状态
InitProcess.start()执行过程中对cgroup 资源组的配置与应用工作
!FILENAME libcontainer/process_linux.go:282
func (p *initProcess) start() error {
defer p.messageSockPair.parent.Close()
// 当前执行空间进程称为bootstrap进程
// 启动了 cmd,即启动了 runc init 命令,创建 runc init 子进程
// 同时也激活了C代码nsenter模块的执行(为了 namespace 的设置 clone 了三个进程parent、child、init)
// C 代码执行后返回 go 代码部分,最后的 init 子进程为了好区分此处命名为" nsInit "(即配置了Namespace的init)
// runc init go代码为容器初始化其它部分(网络、rootfs、路由、主机名、console、安全等)
err := p.cmd.Start() // runc init
//...
// 为进程 runc init 应用 Cgroup (p.cmd.Process.Pid())
if err := p.manager.Apply(p.pid()); err != nil {
return newSystemErrorWithCause(err, "applying cgroup configuration for process")
}
//...
// messageSockPair 管道写入 bootstrapData
if _, err := io.Copy(p.messageSockPair.parent, p.bootstrapData); err != nil {
return newSystemErrorWithCause(err, "copying bootstrap data to pipe")
}
// 获取 nsInit pid
childPid, err := p.getChildPid()
if err != nil {
return newSystemErrorWithCause(err, "getting the final child's pid from pipe")
}
//...
// 为 nsInit 进程应用 Cgroup
if err := p.manager.Apply(childPid); err != nil {
return newSystemErrorWithCause(err, "applying cgroup configuration for process")
}
// 为 child 进程应用 intel RDT
if p.intelRdtManager != nil {
if err := p.intelRdtManager.Apply(childPid); err != nil {
return newSystemErrorWithCause(err, "applying Intel RDT configuration for process")
}
}
//...
// 解析runc init子进程的所有同步消息,当io.EOF返回
ierr := parseSync(p.messageSockPair.parent, func(sync *syncT) error {
switch sync.Type {
case procReady:
// prestart hooks 启动前执行勾子
if !p.config.Config.Namespaces.Contains(configs.NEWNS) {
// 根据全局配置设置Cgroup
if err := p.manager.Set(p.config.Config); err != nil {
return newSystemErrorWithCause(err, "setting cgroup config for ready process")
}
//...
// 运行执行前勾子
for i, hook := range p.config.Config.Hooks.Prestart {
if err := hook.Run(s); err != nil {
return newSystemErrorWithCausef(err, "running prestart hook %d", i)
}
}
}
}
// 与子进程 runC init 同步
if err := writeSync(p.messageSockPair.parent, procRun); err != nil {
return newSystemErrorWithCause(err, "writing syncT 'run'")
}
sentRun = true
case procHooks:
// 配置 cgroup
if err := p.manager.Set(p.config.Config); err != nil {
return newSystemErrorWithCause(err, "setting cgroup config for procHooks process")
}
//...
if p.config.Config.Hooks != nil {
//...
// 执行勾子定义任务
// 与子进程 runc-init 同步
}
sentResume = true
default:
return newSystemError(fmt.Errorf("invalid JSON payload from child"))
}
return nil
})
//...
return nil
}
从整个执行过程来看,容器 init go 代码运行初始化配置后向exec.fifo管道写数据,阻塞,直到用户调用runc start
,读取管道中的数据将最后执行用户定义的entrypoint程序。
上面已为Cgroup在容器创建过程中的配置与应用的管理过程,而接下来我们将看看底层是如何实现cgroup的。
Cgroup manger 为 Runc 实现对系统的 cgroup 操作的管理器抽象。manger对象实现对 cgroup 的配置项值设置、pid应用、销毁 、暂停/恢复、获取配置等操作。这里Apply() 和 Set() 注意一下两者的差别,一个是设置子系统的相关资源约束项的值,一个是将进程pid操作应用至相关的cgroup子系统。
我们先来查看几个关键接口、结构体定义:
- cgroup manager接口定义
!FILENAME libcontainer/cgroups/cgroups.go:11
type Manager interface {
// 为指定的 pid 应用 cgroup 配置
Apply(pid int) error
// 返回 cgroup 集内所有 pid
GetPids() ([]int, error)
// 返回 cgroup 集和 subcgroups 的所有 pid
GetAllPids() ([]int, error)
// 返回 cgroup 集统计信息
GetStats() (*Stats, error)
// 任务暂停与恢复操作
Freeze(state configs.FreezerState) error
// 销毁 cgroup 集
Destroy() error
// 获取保存 cgroup 状态文件路径
GetPaths() map[string]string
// 设置 Cgroup 配置值
Set(container *configs.Config) error // +configs.Config 容器进程配置结构
}
- Configs.Config 容器进程配置的结构体内 cgroups 相关定义
!FILENAME libcontainer/configs/config.go:81
// 定义容器内执行进程的配置项,此处仅关注 Cgroup 相关
type Config struct {
//...
// 容器的 Cgroups 资源限制配置
Cgroups *Cgroup `json:"cgroups"` //+Cgroup 结构
// ....
// 当 RootlessCgroups 设置为 true,cgroups 错误将被忽略
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
}
- Configs.cgroups 的结构定义
!FILENAME libcontainer/configs/cgroup_linux.go:11
type Cgroup struct {
// Deprecated, use Path instead
Name string `json:"name,omitempty"`
// name of parent of cgroup or slice
// Deprecated, use Path instead
Parent string `json:"parent,omitempty"`
// Path指定由容器创建(和/或)连接的cgroup的路径。假定路径相对于主机系统cgroup挂载点。
Path string `json:"path"`
// ScopePrefix describes prefix for the scope name
ScopePrefix string `json:"scope_prefix"`
// Paths represent the absolute cgroups paths to join.
// This takes precedence over Path.
Paths map[string]string
// 资源包含各种Cgroup的应用设置
*Resources // +参考下面定义
}
// 每项详细说明参考本文附录一
type Resources struct {
AllowAllDevices *bool `json:"allow_all_devices,omitempty"`
AllowedDevices []*Device `json:"allowed_devices,omitempty"`
DeniedDevices []*Device `json:"denied_devices,omitempty"`
Devices []*Device `json:"devices"`
Memory int64 `json:"memory"`
MemoryReservation int64 `json:"memory_reservation"`
MemorySwap int64 `json:"memory_swap"`
KernelMemory int64 `json:"kernel_memory"`
KernelMemoryTCP int64 `json:"kernel_memory_tcp"`
CpuShares uint64 `json:"cpu_shares"`
CpuQuota int64 `json:"cpu_quota"`
CpuPeriod uint64 `json:"cpu_period"`
CpuRtRuntime int64 `json:"cpu_rt_quota"`
CpuRtPeriod uint64 `json:"cpu_rt_period"`
CpusetCpus string `json:"cpuset_cpus"`
CpusetMems string `json:"cpuset_mems"`
PidsLimit int64 `json:"pids_limit"`
BlkioWeight uint16 `json:"blkio_weight"`
BlkioLeafWeight uint16 `json:"blkio_leaf_weight"`
BlkioWeightDevice []*WeightDevice `json:"blkio_weight_device"`
BlkioThrottleReadBpsDevice []*ThrottleDevice `json:"blkio_throttle_read_bps_device"`
BlkioThrottleWriteBpsDevice []*ThrottleDevice `json:"blkio_throttle_write_bps_device"`
BlkioThrottleReadIOPSDevice []*ThrottleDevice `json:"blkio_throttle_read_iops_device"`
BlkioThrottleWriteIOPSDevice []*ThrottleDevice `json:"blkio_throttle_write_iops_device"`
Freezer FreezerState `json:"freezer"`
HugetlbLimit []*HugepageLimit `json:"hugetlb_limit"`
OomKillDisable bool `json:"oom_kill_disable"`
MemorySwappiness *uint64 `json:"memory_swappiness"`
NetPrioIfpriomap []*IfPrioMap `json:"net_prio_ifpriomap"`
NetClsClassid uint32 `json:"net_cls_classid_u"`
}
Runc 代码在 Manager 接口的实现类有两个版本driver,一个实现类 fs.Manager ,一个是 systemd.Manager ,本文主要分析 cgroupfs 驱动即 fs.Manager 的实现代码。
我们先看一下 fs.Manager 的定义
!FILENAME libcontainer/cgroups/fs/apply_raw.go:65
type Manager struct {
mu sync.Mutex
Cgroups *configs.Cgroup // 全局配置的 cgroup 项定义(configs.Cgroup前面有结构体说明)
Rootless bool
Paths map[string]string // 存放子系统名与路径
}
cgroupData cgroup 配置数据定义
!FILENAME libcontainer/cgroups/fs/apply_raw.go:98
type cgroupData struct {
root string // cgroup 根路径
innerPath string // 指定由容器创建(和/或)连接的cgroup的路径
config *configs.Cgroup // Cgroup 全局配置项
pid int // 进程id
}
manager.Apply() 将指定的 pid 应用资源的限制
!FILENAME libcontainer/cgroups/fs/apply_raw.go:132
func (m *Manager) Apply(pid int) (err error) {
if m.Cgroups == nil { // 全局 cgroup 配置是否存在检测
return nil
}
//...
var c = m.Cgroups
d, err := getCgroupData(m.Cgroups, pid) // +获取与构建 cgroupData 对象
//...
m.Paths = make(map[string]string)
// 如果全局配置存在 cgroup paths 配置,
if c.Paths != nil {
for name, path := range c.Paths {
_, err := d.path(name) // 查找子系统的 cgroup path 是否存在
if err != nil {
if cgroups.IsNotFound(err) {
continue
}
return err
}
m.Paths[name] = path
}
return cgroups.EnterPid(m.Paths, pid) // 将 pid 写入子系统的 cgroup.procs 文件
}
// 遍历所有 cgroup 子系统,将配置应用 cgroup 资源限制
for _, sys := range subsystems {
p, err := d.path(sys.Name()) // 查找子系统的 cgroup path
if err != nil {
//...
return err
}
m.Paths[sys.Name()] = p
if err := sys.Apply(d); err != nil { // 各子系统 apply() 方法调用
//...
}
return nil
}
获取与构建 cgroupData 对象
!FILENAME libcontainer/cgroups/fs/apply_raw.go:291
func getCgroupData(c *configs.Cgroup, pid int) (*cgroupData, error) {
root, err := getCgroupRoot() // +获取cgroup root根目录
if err != nil {
return nil, err
}
//...
return &cgroupData{
root: root,
innerPath: innerPath,
config: c,
pid: pid,
}, nil
}
cgroupRoot 全局变量为空则通过查找" /proc/self/mountinfo "满足条件为" filesystem 列为 cgroup "的挂载点目录,则为 cgroup 的根目录
!FILENAME libcontainer/cgroups/fs/apply_raw.go:77
func getCgroupRoot() (string, error) {
cgroupRootLock.Lock()
defer cgroupRootLock.Unlock()
if cgroupRoot != "" {
return cgroupRoot, nil
}
root, err := cgroups.FindCgroupMountpointDir() // 查找"/proc/self/mountinfo"挂载点目录
if err != nil {
return "", err
}
if _, err := os.Stat(root); err != nil { //判断是否存在
return "", err
}
cgroupRoot = root
return cgroupRoot, nil
}
manager.Set() 根据容器的全局配置 Config 的 Cgroups 资源限制项,将 configs 写入至 cgroup 子系统文件
!FILENAME libcontainer/cgroups/fs/apply_raw.go:282
func (m *Manager) Set(container *configs.Config) error {
//...
paths := m.GetPaths()
// 遍历所有子系统,设置容器的全局配置 Config 的 Cgroups 资源限制项
for _, sys := range subsystems {
path := paths[sys.Name()]
if err := sys.Set(path, container.Cgroups); err != nil {
//...
}
return nil
}
manager.Freeze() 根据容器的全局 configs 配置应用 cgroup 暂停与恢复操作状态值
!FILENAME libcontainer/cgroups/fs/apply_raw.go:264
func (m *Manager) Freeze(state configs.FreezerState) error {
paths := m.GetPaths()
dir := paths["freezer"] // 获取子系统的 path
prevState := m.Cgroups.Resources.Freezer
m.Cgroups.Resources.Freezer = state
freezer, err := subsystems.Get("freezer")
if err != nil {
return err
}
err = freezer.Set(dir, m.Cgroups) // 设置 state 状态值
//...
}
其它 manager 方法:manager.GetPids() /manager.GetAllPids() / manager.GetPaths() / manager.Destroy() / manager.GetStats() 略
Cgroupfs 子系统接口、关键类型、关键全局变量定义
!FILENAME libcontainer/cgroups/fs/apply_raw.go
// 子系统接口定义
type subsystem interface {
// 返回子系统的名称
Name() string
// 返回cgroup stats状态
GetStats(path string, stats *cgroups.Stats) error
// 移除cgroup
Remove(*cgroupData) error
// 创建和加入cgroup
Apply(*cgroupData) error
// 设置cgroup配置项值
Set(path string, cgroup *configs.Cgroup) error
}
// 子系统集类型定义
type subsystemSet []subsystem
// subsystems全局变量定义了支持的子系统列表;
// "&CpusetGroup{}..."都为subsystem接口的具体实现
var (
subsystems = subsystemSet{
&CpusetGroup{},
&DevicesGroup{},
&MemoryGroup{},
&CpuGroup{},
&CpuacctGroup{},
&PidsGroup{},
&BlkioGroup{},
&HugetlbGroup{},
&NetClsGroup{},
&NetPrioGroup{},
&PerfEventGroup{},
&FreezerGroup{},
&NameGroup{GroupName: "name=systemd", Join: true},
}
"cgroups/fs/" 目录下包含了各种支持的子系统实现代码,下面我们用 cpu subsystem 的实现代码详细分析作为代表,其它子系统的实现逻辑类似,本文将不作详细的一一分析。
"cgroups/fs/cpu.go" 文件内包含了 cpu subsystem 的实现代码:
CpuGroup.Name() 获取 cpu 子系统名称
!FILENAME libcontainer/cgroups/fs/cpu.go:18
func (s *CpuGroup) Name() string {
return "cpu"
}
CpuGroup.Apply() 基于 Cgroup configs 设置项,应用 CPU 资源限制
!FILENAME libcontainer/cgroups/fs/cpu.go:22
func (s *CpuGroup) Apply(d *cgroupData) error {
path, err := d.path("cpu")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return s.ApplyDir(path, d.config, d.pid) // +创建目录和pid写cgroup.procs文件应用cpu限制
}
创建目录和 pid 写入 cgroup.procs 文件应用 cpu 限制
!FILENAME libcontainer/cgroups/fs/cpu.go:32
func (s *CpuGroup) ApplyDir(path string, cgroup *configs.Cgroup, pid int) error {
if path == "" {
return nil
}
// 创建目录
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
// 设置 RT(realtime)调度值: cpu.rt_period_us ,cpu.rt_runtime_us
if err := s.SetRtSched(path, cgroup); err != nil {
return err
}
// pid加入cgroup procs文件应用cgroup组限制
return cgroups.WriteCgroupProc(path, pid)
}
CpuGroup.Set() 基于 Cgroup configs 设置项,写入配置值至相应的子系统控制资源文件,实现 CPU 的限制调节
!FILENAME libcontainer/cgroups/fs/cpu.go:66
func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.CpuShares != 0 {
// 写入文件值,控制cgroup组之间的配额占比
if err := writeFile(path, "cpu.shares", strconv.FormatUint(cgroup.Resources.CpuShares, 10)); err != nil {
return err
}
}
if cgroup.Resources.CpuPeriod != 0 {
// 写入文件值,CFS调度 CPU 时间的周期
if err := writeFile(path, "cpu.cfs_period_us", strconv.FormatUint(cgroup.Resources.CpuPeriod, 10)); err != nil {
return err
}
}
if cgroup.Resources.CpuQuota != 0 {
// 写入文件值,CFS调度 期间内可使用的 cpu 时间
if err := writeFile(path, "cpu.cfs_quota_us", strconv.FormatInt(cgroup.Resources.CpuQuota, 10)); err != nil {
return err
}
}
// 设置 RT(realtime)调度值: cpu.rt_period_us ,cpu.rt_runtime_us
return s.SetRtSched(path, cgroup)
}
CpuGroup.GetStats() 获取子系统的 cpu.stat 状态文件信息
!FILENAME libcontainer/cgroups/fs/cpu.go:89
func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
f, err := os.Open(filepath.Join(path, "cpu.stat")) // cpu.stat文件
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
t, v, err := getCgroupParamKeyValue(sc.Text()) // K/V解析
if err != nil {
return err
}
switch t {
case "nr_periods": //"nr_periods" 进入周期的次数
stats.CpuStats.ThrottlingData.Periods = v
case "nr_throttled": //"nr_throttled" 运行时间被调整的次数
stats.CpuStats.ThrottlingData.ThrottledPeriods = v
case "throttled_time": //"throttled_time" 用于调整的时间
stats.CpuStats.ThrottlingData.ThrottledTime = v
}
}
return nil
}
CpuGroup.remove() 删除 cpu 子系统
!FILENAME libcontainer/cgroups/fs/cpu.go:85
func (s *CpuGroup) Remove(d *cgroupData) error {
return removePath(d.path("cpu")) //删除path
}
其它 cgroup 的子系统资源设置项说明可参考后面的附录。
├── cgroups.go ---------------------------->| 定义cgroup Manager接口操作
├── cgroups_test.go
├── cgroups_unsupported.go
├── fs
│ ├── apply_raw.go ---------------------------->| cgroupfs driver Manager实现
│ ├── apply_raw_test.go
│ ├── blkio.go -------[blkio子系统]--------->|
│ ├── blkio_test.go |
│ ├── cpu.go -------[cpu子系统]----------->|
│ ├── cpu_test.go |
│ ├── cpuacct.go -------[cpuacct子系统]------->|
│ ├── cpuset.go -------[cpuset子系统]-------->|
│ ├── cpuset_test.go |
│ ├── devices.go -------[devices子系统]------->|\
│ ├── devices_test.go | |\ 各子系统实现,Cgroupfs目录文件
│ ├── freezer.go -------[freezer子系统]------->| |/ 的CRUD方法
│ ├── freezer_test.go |/
│ ├── fs_unsupported.go |
│ ├── hugetlb.go -------[hugetlb子系统]------->|
│ ├── hugetlb_test.go |
│ ├── kmem.go |
│ ├── kmem_disabled.go |
│ ├── memory.go -------[memory子系统]-------->|
│ ├── memory_test.go |
│ ├── name.go -------[systemd子系统]------->|
│ ├── net_cls.go -------[netcls子系统]-------->|
│ ├── net_cls_test.go |
│ ├── net_prio.go -------[netprio子系统]------->|
│ ├── net_prio_test.go |
│ ├── perf_event.go -------[perf event子系统]---->|
│ ├── pids.go -------[pid子系统]----------->|
│ ├── pids_test.go
│ ├── stats_util_test.go
│ ├── util_test.go
│ ├── utils.go ---------------------------->| fs包内共享的工具方法
│ └── utils_test.go
├── stats.go ---------------------------->| cgroup stats 定义与对象构建
├── systemd
│ ├── apply_nosystemd.go
│ └── apply_systemd.go ---------------------------->| cgroup systemd driver manager 实现
├── utils.go ---------------------------->| 包内共享的工具方法
└── utils_test.go
1. [pids] --- 限制cgroup及其所有子孙cgroup里面能创建的总的task数量
pids.max 所允许创建的总的最大进程数量
pids.current 现有的总的进程数量
2. [memory] --- 限制内存子系统限制内存使用量
memory.memsw.limit_in_bytes 内存+swap空间使用的总量限制
memory.limit_in_bytes 内存使用量限制
memory.kmem.limit_in_bytes 限制内核使用的内存大小
memory.soft_limit_in_bytes 内存软限制
memory.kmem.tcp.limit_in_bytes 设置tcp 缓存内存的hard limit
memory.oom_control 设置/读取内存超限控制信息
memory.swappiness 用来调整cgroup使用swap的状态,表示不使用交换分区
3. [cpu] --- 限制进程的cpu总体占用率
cpu.rt_period_us 实时任务统计CPU使用时间的周期
cpu.rt_runtime_us 实时任务周期内允许任务使用单个CPU核的时间,如果系统中有多个核,则可以使用核倍数的时间
cpu.shares 控制各个cgroup组之间的配额占比
//下面两个组合使用,限制该组中的所有进程在单位时间里可以使用的 cpu 时间
//如:将 cpu.cfs_quota_us 设为 50000,相对于 cpu.cfs_period_us 的 100000 即 50%
//cfs_quota_us 也是可以大于 cfs_period_us 的,这主要是对于多核情况
cpu.cfs_period_us 时间周期,默认为 100000,即百毫秒
cpu.cfs_quota_us 期间内可使用的 cpu 时间,默认 -1,即无限制
4. [cpuset] --- 多核心的cpu环境,为cgroup任务分配独立的内存节点和CPU节点
cpuset.cpus 限制只能使用特定CPU节点
cpuset.mems 限制只能使用特定内存节点
5. [devices] --- 以控制进程能够访问某些设备
// echo "a 1:5 r" > devices.deny
// a|b|C all/block/character
// r|w|m read/write/create
devices.deny 拒绝
devices.allow 允许
6. [blkio] --- 设置限制每个块设备的输入输出控制。例如:磁盘,光盘以及usb等等
// CFQ Completely Fair Queuing 完全公平队列
blkio.weight 权重值(范围100-1000)
blkio.leaf_weight
blkio.weight_device 块设备级的值 (major:minor weight) (优先级高于blkio.weight)
blkio.leaf_weight_device
// 限制IOPS使用上限
blkio.throttle.read_bps_device 读设备 bytes/s
blkio.throttle.write_bps_device 写设备 bytes/s
blkio.throttle.read_iops_device 读设备 io/s
blkio.throttle.write_iops_device 写设备 io/s
7. [net_prio] --- 配置每个网络接口的流量优先级
net_prio.ifpriomap 优先级图谱
8. [net_cls] --- 标记每个网络包,可供QOS/netfilter使用
net_cls.classid 标签id
9. [freezer] --- 暂停和恢复cgroup任务
freezer.state 当前的状态,两个状态是写有效(Frozen已冻结/Thawed解冻状态)
10.[hugetlb] --- 对于HugeTLB系统进行限制,这是一个大页文件系统
hugetlb.XX.limit_in_bytes 限制大页字节数
cgroups 包下 utils 定义的方法用途简析
!FILENAME libcontainer/cgroups/utils.go
// 查找/proc/self/mountinfo下满足条件“cgroupPath,subsystem”的项,返回"Cgroup根目录与挂载点"
func FindCgroupMountpointAndRoot(cgroupPath, subsystem string) (string, string, error) {...}
func findCgroupMountpointAndRootFromReader(reader io.Reader, cgroupPath, subsystem string) (string, string, error) {...}
// 获取"/proc/self/cgroup"进行匹配是否存在subsystem
func isSubsystemAvailable(subsystem string) bool{...}
// 查"/proc/self/mountinfo"满足条件"filesystem列为cgroup"的挂载点目录;
// 一般为"/sys/fs/cgroup/"
func FindCgroupMountpointDir() (string, error) {...}
// 获取所有Cgroup信息结构化Mount slice返回;[]Mount{Mountpoint/Root/Subsystems[]}
func GetCgroupMounts(all bool) ([]Mount, error) {...}
func getCgroupMountsHelper(ss map[string]bool,mi io.Reader, all bool) ([]Mount, error) {...}
// 打开/proc/<pid>/cgroup文件调用parseCgroupFromReader()
func ParseCgroupFile(path string) (map[string]string, error) {...}
// 解析"/proc/[pid]/cgroup"输出map[subsystem]cgroup-path
func parseCgroupFromReader(r io.Reader) (map[string]string, error) {...}
// 返回subsystem的cgroup-path
func getControllerPath(subsystem string, cgroups map[string]string) (string, error) {...}
// 获取当前进程的(root)Cgroup-path,通过解析"/proc/self/cgroup"文件
func GetOwnCgroup(subsystem string) (string, error) {...}
// 获取当前进程的(mnt)Cgroup-path
func GetOwnCgroupPath(subsystem string) (string, error) {...}
// 获取init进程的(root)Cgroup-path,通过解析"/proc/1/cgroup"文件
func GetInitCgroup(subsystem string) (string, error) {...}
// 获取init进程的(mnt)Cgroup-path
func GetInitCgroupPath(subsystem string) (string, error) {...}
// 获取Cgroup mnt path
func getCgroupPathHelper(subsystem, cgroup string) (string, error) {...}
// 获取指定被加入cgroup path的所有pid
func GetPids(path string) ([]int, error){...}
// 获取指定被加入cgroup path和subcgroups的所有pid
func GetAllPids(path string) ([]int, error) {...}
// 打开指定dir的cgroup subsystem的"cgroup.procs" 读取pids
func readProcsFile(dir string) ([]int, error) {...}
// 写入指定的 pid 至 Cgroup subsystem "cgroup.procs"文件
func EnterPid(cgroupPaths map[string]string, pid int) error
// 打开指定dir的cgroup subsystem的"cgroup.procs"写入指定的pid
func WriteCgroupProc(dir string, pid int) error {...}
// 获取大页大小列表
func GetHugePageSize() ([]string, error) {...}
// 读"/sys/kernel/mm/hugepages"目录下文件,解析文件名获取hg大小
func getHugePageSizeFromFilenames(fileNames []string) ([]string, error) {...}
相关文档: // TODO 补充链接
- 《RunC 源码通读指南之 Run》
- 《RunC 源码通读指南之 Create & Start》
- 《RunC 源码通读指南之 Namespace》
- 《RunC 源码通读指南之 Networks》
本文 END