docker中如何理解cgroups

发布时间:2021-10-19 18:32:23 作者:柒染
来源:亿速云 阅读:138

这篇文章将为大家详细讲解有关docker中如何理解cgroups,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

理解docker,主要从namesapce,cgroups,联合文件,运行时(runC),网络几个方面。接下来我们会花一些时间,分别介绍。

namesapce主要是隔离作用,cgroups主要是资源限制,联合文件主要用于镜像分层存储和管理,runC是运行时,遵循了oci接口,一般来说基于libcontainer。网络主要是docker单机网络和多主机通信模式。

cgroups简介

cgroups是什么?

Cgroup是control group的简写,属于Linux内核提供的一个特性,用于限制和隔离一组进程对系统资源的使用,也就是做资源QoS,这些资源主要包括CPU、内存、block I/O和网络带宽。Cgroup从2.6.24开始进入内核主线,目前各大发行版都默认打开了Cgroup特性。
Cgroups提供了以下四大功能:

Cgroups中的三个组件

cgroups子系统

docker中如何理解cgroups
cgroup中实现的子系统及其作用如下:

每个子系统的目录下有更详细的设置项,例如:
cpu
docker中如何理解cgroups
除了限制 CPU 的使用量,cgroup 还能把任务绑定到特定的 CPU,让它们只运行在这些 CPU 上,这就是 cpuset 子资源的功能。除了 CPU 之外,还能绑定内存节点(memory node)。
在把任务加入到 cpuset 的 task 文件之前,用户必须设置 cpuset.cpus 和 cpuset.mems 参数。

memory
图片描述

这里专门讲一下监控和统计相关的参数,比如cadvisor采集的那些参数。

docker如何使用cgroup

# Run a container that will spawn 300 processes.
docker run cirocosta/stress pid  -n 300
Starting to spawn 300 blocking children
[1] Waiting for SIGINT

# Open another window and see that we have 300
# PIDS
docker stats
CONTAINER      …   MEM USAGE / LIMIT          PIDS
a730051832     …   21.02MiB / 1.951GiB     300
# let's get the ID of the container. Docker uses that ID
# to name things in the host to we can probably use it to
# find the cgroup created for the container
# under the parent docker cgroup
docker ps
CONTAINER ID        IMAGE               COMMAND       
a730051832e7        cirocosta/stress    "pid -n 300"  

 # Having the prefix in hands, let's search for it under the
 # mountpoint for cgroups in our system
 find  /sys/fs/cgroup/ -name "a730051832e7*"
 
/sys/fs/cgroup/cpu,cpuacct/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959
/sys/fs/cgroup/cpuset/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959
/sys/fs/cgroup/devices/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959
/sys/fs/cgroup/pids/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959
/sys/fs/cgroup/freezer/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959
/sys/fs/cgroup/perf_event/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959
/sys/fs/cgroup/blkio/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959
/sys/fs/cgroup/memory/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959
/sys/fs/cgroup/net_cls,net_prio/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959
/sys/fs/cgroup/hugetlb/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959
/sys/fs/cgroup/systemd/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959

# There they are! Docker creates a control group with the name
# being the exact ID of the container under all the subsystems.

# What can we discover from this inspection? We can look at the
# subsystem that we want to place contrainst on (PIDs), for instance:

 tree /sys/fs/cgroup/pids/docker/a7300518327d...
/sys/fs/cgroup/pids/docker/a73005183...
├── cgroup.clone_children
├── cgroup.procs
├── notify_on_release
├── pids.current
├── pids.events
├── pids.max
└── tasks

# Which means that, if we want to know how many PIDs are in use right
# now we can look at 'pids.current', to know the limits, 'pids.max' and
# to know which processes have been assigned to this control group,
# look at tasks. Lets do it:
cat /sys/fs/cgroup/pids/docker/a730...c660a75a959/tasks 
5329
5371
5372
5373
5374
5375
5376
5377
(...)
# continues until the 300th entry - as we have 300 processes in this container

# 300 pids
cat /sys/fs/cgroup/pids/docker/a730051832e7d7764...9/pids.current
300

# no max set
cat /sys/fs/cgroup/pids/docker/a730051832e7d77.../pids.max 
max
PS
  1. 一般在安装k8s的过程中经常会遇到如下错误:

    create kubelet: misconfiguration: kubelet cgroup driver: "cgroupfs"
    is different from docker cgroup driver: "systemd"


    其实此处错误信息已经很明白了,就是docker 和kubelet指定的cgroup driver不一样。 docker
    支持systemd和cgroupfs两种驱动方式。通过runc代码可以更加直观了解。
    docker中如何理解cgroups

  2. cgroup 只能限制 CPU 的使用,而不能保证CPU的使用。也就是说, 使用
    cpuset-cpus,可以让容器在指定的CPU或者核上运行,但是不能确保它独占这些CPU;cpu-shares
    是个相对值,只有在CPU不够用的时候才其作用。也就是说,当CPU够用的时候,每个容器会分到足够的CPU;不够用的时候,会按照指定的比重在多个容器之间分配CPU。

  3. 对内存来说,cgroups 可以限制容器最多使用的内存。使用 -m 参数可以设置最多可以使用的内存。

代码解读

关于cgroups在runc的代码部分,大家可以点击进去详细阅读。这边我们只讲一个大概。
首先container的创建是由factory调用create方法实现的,而cgroup相关,factory实现了根据配置文件cgroup drive驱动的配置项,新建CgroupsManager的方法,systemd和cgroupfs两种实现方式:

// SystemdCgroups is an options func to configure a LinuxFactory to return
// containers that use systemd to create and manage cgroups.
func SystemdCgroups(l *LinuxFactory) error {
    l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
        return &systemd.Manager{
            Cgroups: config,
            Paths:   paths,
        }
    }
    return nil
}

// 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
}

抽象cgroup manager接口。接口如下:

type Manager interface {
    // Applies cgroup configuration to the process with the specified pid
    Apply(pid int) error

    // Returns the PIDs inside the cgroup set
    GetPids() ([]int, error)

    // Returns the PIDs inside the cgroup set & all sub-cgroups
    GetAllPids() ([]int, error)

    // Returns statistics for the cgroup set
    GetStats() (*Stats, error)

    // Toggles the freezer cgroup according with specified state
    Freeze(state configs.FreezerState) error

    // Destroys the cgroup set
    Destroy() error

    // The option func SystemdCgroups() and Cgroupfs() require following attributes:
    //     Paths   map[string]string
    //     Cgroups *configs.Cgroup
    // Paths maps cgroup subsystem to path at which it is mounted.
    // Cgroups specifies specific cgroup settings for the various subsystems

    // Returns cgroup paths to save in a state file and to be able to
    // restore the object later.
    GetPaths() map[string]string

    // Sets the cgroup as configured.
    Set(container *configs.Config) error
}

在创建container的过程中,会调用上面接口的方法。例如:
在container_linux.go中,

func (c *linuxContainer) Set(config configs.Config) error {
    c.m.Lock()
    defer c.m.Unlock()
    status, err := c.currentStatus()
    if err != nil {
        return err
    }
    ...
    if err := c.cgroupManager.Set(&config); err != nil {
        // Set configs back
        if err2 := c.cgroupManager.Set(c.config); err2 != nil {
            logrus.Warnf("Setting back cgroup configs failed due to error: %v, your state.json and actual configs might be inconsistent.", err2)
        }
        return err
    }
...
}

接下来我们重点讲一下fs的实现。

图片描述
在fs中,基本上每个子系统都是一个文件,如上图。

重点说一下memory.go,即memory子系统,其他子系统与此类似。
关键方法:

func (s *MemoryGroup) Apply(d *cgroupData) (err error) {
    path, err := d.path("memory")
    if err != nil && !cgroups.IsNotFound(err) {
        return err
    } else if path == "" {
        return nil
    }
    if memoryAssigned(d.config) {
        if _, err := os.Stat(path); os.IsNotExist(err) {
            if err := os.MkdirAll(path, 0755); err != nil {
                return err
            }
            // Only enable kernel memory accouting when this cgroup
            // is created by libcontainer, otherwise we might get
            // error when people use `cgroupsPath` to join an existed
            // cgroup whose kernel memory is not initialized.
            if err := EnableKernelMemoryAccounting(path); err != nil {
                return err
            }
        }
    }
    defer func() {
        if err != nil {
            os.RemoveAll(path)
        }
    }()

    // We need to join memory cgroup after set memory limits, because
    // kmem.limit_in_bytes can only be set when the cgroup is empty.
    _, err = d.join("memory")
    if err != nil && !cgroups.IsNotFound(err) {
        return err
    }
    return nil
}
func (raw *cgroupData) path(subsystem string) (string, error) {
    mnt, err := cgroups.FindCgroupMountpoint(subsystem)
    // If we didn't mount the subsystem, there is no point we make the path.
    if err != nil {
        return "", err
    }

    // If the cgroup name/path is absolute do not look relative to the cgroup of the init process.
    if filepath.IsAbs(raw.innerPath) {
        // Sometimes subsystems can be mounted together as 'cpu,cpuacct'.
        return filepath.Join(raw.root, filepath.Base(mnt), raw.innerPath), nil
    }

    // Use GetOwnCgroupPath instead of GetInitCgroupPath, because the creating
    // process could in container and shared pid namespace with host, and
    // /proc/1/cgroup could point to whole other world of cgroups.
    parentPath, err := cgroups.GetOwnCgroupPath(subsystem)
    if err != nil {
        return "", err
    }

    return filepath.Join(parentPath, raw.innerPath), nil
}
func (raw *cgroupData) join(subsystem string) (string, error) {
    path, err := raw.path(subsystem)
    if err != nil {
        return "", err
    }
    if err := os.MkdirAll(path, 0755); err != nil {
        return "", err
    }
    if err := cgroups.WriteCgroupProc(path, raw.pid); err != nil {
        return "", err
    }
    return path, nil
}

关于docker中如何理解cgroups就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

推荐阅读:
  1. Libvirt、Docker的基础技术-CGroups介绍
  2. linux cgroups详细介绍

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

cgroups docker

上一篇:k8s cpu limit node节点异常 怎么办

下一篇:如何理解Linux CPU负载和CPU使用率

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》