Docker之Linux Cgroups

Linux Cgroups介绍

上面是构建Linux容器的namespace技术,它帮进程隔离出自己单独的空间,但Docker又是怎么限制每个空间的大小,保证他们不会互相争抢呢?那么就要用到Linux的Cgroups技术。

概念

Linux Cgroups(Control Groups) 提供了对一组进程及将来的子进程的资源的限制,控制和统计的能力,这些资源包括CPU,内存,存储,网络等。通过Cgroups,可以方便的限制某个进程的资源占用,并且可以实时的监控进程的监控和统计信息。

Cgroups中的三个组件:

  • cgroup
    cgroup 是对进程分组管理的一种机制,一个cgroup包含一组进程,并可以在这个cgroup上增加Linux subsystem的各种参数的配置,将一组进程和一组subsystem的系统参数关联起来。
  • subsystem
    subsystem 是一组资源控制的模块,一般包含有:
    • blkio 设置对块设备(比如硬盘)的输入输出的访问控制
    • cpu 设置cgroup中的进程的CPU被调度的策略
    • cpuacct 可以统计cgroup中的进程的CPU占用
    • cpuset 在多核机器上设置cgroup中的进程可以使用的CPU和内存(此处内存仅使用于NUMA架构)
    • devices 控制cgroup中进程对设备的访问
    • freezer 用于挂起(suspends)和恢复(resumes) cgroup中的进程
    • memory 用于控制cgroup中进程的内存占用
    • net_cls 用于将cgroup中进程产生的网络包分类(classify),以便Linux的tc(traffic controller) 可以根据分类(classid)区分出来自某个cgroup的包并做限流或监控。
    • net_prio 设置cgroup中进程产生的网络流量的优先级
    • ns 这个subsystem比较特殊,它的作用是cgroup中进程在新的namespace fork新进程(NEWNS)时,创建出一个新的cgroup,这个cgroup包含新的namespace中进程。

    每个subsystem会关联到定义了相应限制的cgroup上,并对这个cgroup中的进程做相应的限制和控制,这些subsystem是逐步合并到内核中的,如何看到当前的内核支持哪些subsystem呢?可以安装cgroup的命令行工具(apt-get install cgroup-bin),然后通过lssubsys看到kernel支持的subsystem。

    # / lssubsys -a
    cpuset
    cpu,cpuacct
    blkio
    memory
    devices
    freezer
    net_cls,net_prio
    perf_event
    hugetlb
    pids
  • hierarchy
    hierarchy 的功能是把一组cgroup串成一个树状的结构,一个这样的树便是一个hierarchy,通过这种树状的结构,Cgroups可以做到继承。比如我的系统对一组定时的任务进程通过cgroup1限制了CPU的使用率,然后其中有一个定时dump日志的进程还需要限制磁盘IO,为了避免限制了影响到其他进程,就可以创建cgroup2继承于cgroup1并限制磁盘的IO,这样cgroup2便继承了cgroup1中的CPU的限制,并且又增加了磁盘IO的限制而不影响到cgroup1中的其他进程。

三个组件相互的关系:

通过上面的组件的描述我们就不难看出,Cgroups的是靠这三个组件的相互协作实现的,那么这三个组件是什么关系呢?

  • 系统在创建新的hierarchy之后,系统中所有的进程都会加入到这个hierarchy的根cgroup节点中,这个cgroup根节点是hierarchy默认创建,后面在这个hierarchy中创建cgroup都是这个根cgroup节点的子节点。
  • 一个subsystem只能附加到一个hierarchy上面
  • 一个hierarchy可以附加多个subsystem
  • 一个进程可以作为多个cgroup的成员,但是这些cgroup必须是在不同的hierarchy中
  • 一个进程fork出子进程的时候,子进程是和父进程在同一个cgroup中的,也可以根据需要将其移动到其他的cgroup中。

这几句话现在不理解暂时没关系,后面我们实际使用过程中会逐渐的了解到他们之间的联系的。

kernel接口:

上面介绍了那么多的Cgroups的结构,那到底要怎么调用kernel才能配置Cgroups呢?上面了解到Cgroups中的hierarchy是一种树状的组织结构,Kernel为了让对Cgroups的配置更直观,Cgroups通过一个虚拟的树状文件系统去做配置的,通过层级的目录虚拟出cgroup树,下面我们就以一个配置的例子来了解下如何操作Cgroups。

  • 首先,我们要创建并挂载一个hierarchy(cgroup树):

     ~ mkdir cgroup-test # 创建一个hierarchy挂载点
     ~ sudo mount -t cgroup -o none,name=cgroup-test cgroup-test ./cgroup-test # 挂载一个hierarchy
     ~ ls ./cgroup-test # 挂载后我们就可以看到系统在这个目录下生成了一些默认文件
    cgroup.clone_children  cgroup.procs  cgroup.sane_behavior  notify_on_release  release_agent  tasks

    这些文件就是这个hierarchy中根节点cgroup配置项了,上面这些文件分别的意思是:

    • cgroup.clone_children cpuset的subsystem会读取这个配置文件,如果这个的值是1(默认是0),子cgroup才会继承父cgroup的cpuset的配置。
    • cgroup.procs是树中当前节点的cgroup中的进程组ID,现在我们在根节点,这个文件中是会有现在系统中所有进程组ID。
    • notify_on_releaserelease_agent会一起使用,notify_on_release表示当这个cgroup最后一个进程退出的时候是否执行release_agentrelease_agent则是一个路径,通常用作进程退出之后自动清理掉不再使用的cgroup。
    • tasks也是表示该cgroup下面的进程ID,如果把一个进程ID写到tasks文件中,便会将这个进程加入到这个cgroup中。
  • 然后,我们创建在刚才创建的hierarchy的根cgroup中扩展出两个子cgroup:
     cgroup-test sudo mkdir cgroup-1 # 创建子cgroup "cgroup-1"
     cgroup-test sudo mkdir cgroup-2 # 创建子cgroup "cgroup-1"
     cgroup-test tree
    .
    |-- cgroup-1
    |   |-- cgroup.clone_children
    |   |-- cgroup.procs
    |   |-- notify_on_release
    |   `-- tasks
    |-- cgroup-2
    |   |-- cgroup.clone_children
    |   |-- cgroup.procs
    |   |-- notify_on_release
    |   `-- tasks
    |-- cgroup.clone_children
    |-- cgroup.procs
    |-- cgroup.sane_behavior
    |-- notify_on_release
    |-- release_agent
    `-- tasks

    可以看到在一个cgroup的目录下创建文件夹,kernel就会把文件夹标记会这个cgroup的子cgroup,他们会继承父cgroup的属性。

  • 在cgroup中添加和移动进程:
    一个进程在一个Cgroups的hierarchy中只能存在在一个cgroup节点上,系统的所有进程默认都会在根节点,可以将进程在cgroup节点间移动,只需要将进程ID写到移动到的cgroup节点的tasks文件中。
     cgroup-1 echo $$
    7475
     cgroup-1 sudo sh -c "echo $$ >> tasks" # 将我所在的终端的进程移动到cgroup-1中
     cgroup-1 cat /proc/7475/cgroup
    13:name=cgroup-test:/cgroup-1
    11:perf_event:/
    10:cpu,cpuacct:/user.slice
    9:freezer:/
    8:blkio:/user.slice
    7:devices:/user.slice
    6:cpuset:/
    5:hugetlb:/
    4:pids:/user.slice/user-1000.slice
    3:memory:/user.slice
    2:net_cls,net_prio:/
    1:name=systemd:/user.slice/user-1000.slice/session-19.scope

    可以看到我们当前的7475进程已经被加到了cgroup-test:/cgroup-1中。

  • 通过subsystem限制cgroup中进程的资源
    上面我们创建hierarchy的时候,但这个hierarchy并没有关联到任何subsystem,所以没办法通过那个hierarchy中的cgroup限制进程的资源占用,其实系统默认就已经把每个subsystem创建了一个默认的hierarchy,比如memory的hierarchy:
      ~ mount | grep memory
    cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory,nsroot=/)

    可以看到,在/sys/fs/cgroup/memory目录便是挂在了memory subsystem的hierarchy。下面我们就通过在这个hierarchy中创建cgroup,限制下占用的进程占用的内存:

     memory stress --vm-bytes 200m --vm-keep -m 1 # 首先,我们不做限制启动一个占用内存的stress进程
     memory sudo mkdir test-limit-memory && cd test-limit-memory # 创建一个cgroup
     test-limit-memory sudo sh -c "echo "100m" > memory.limit_in_bytes" sudo sh -c "echo "100m" > memory.limit_in_bytes" # 设置最大cgroup最大内存占用为100m
     test-limit-memory sudo sh -c "echo $$ > tasks" # 将当前进程移动到这个cgroup中
     test-limit-memory stress --vm-bytes 200m --vm-keep -m 1 # 再次运行占用内存200m的的stress进程

    运行结果如下(通过top监控):

    PID  PPID     TIME+ %CPU %MEM  PR  NI S    VIRT    RES   UID COMMAND
    8336  8335   0:08.23 99.0 10.0  20   0 R  212284 205060  1000 stress
    8335  7475   0:00.00  0.0  0.0  20   0 S    7480    876  1000 stress
    
    PID  PPID     TIME+ %CPU %MEM  PR  NI S    VIRT    RES   UID COMMAND
    8310  8309   0:01.17  7.6  5.0  20   0 R  212284 102056  1000 stress
    8309  7475   0:00.00  0.0  0.0  20   0 S    7480    796  1000 stress

    可以看到通过cgroup,我们成功的将stress进程的最大内存占用限制到了100m。

    Docker是如何使用Cgroups的:

    我们知道Docker是通过Cgroups去做的容器的资源限制和监控,我们下面就以一个实际的容器实例来看下Docker是如何配置Cgroups的:

     ~ # docker run -m 设置内存限制
     ~ sudo docker run -itd -m  128m ubuntu
    957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11
     ~ # docker会为每个容器在系统的hierarchy中创建cgroup
     ~ cd /sys/fs/cgroup/memory/docker/957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11
     957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 # 查看cgroup的内存限制
     957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 cat memory.limit_in_bytes
    134217728
     957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 # 查看cgroup中进程所使用的内存大小
     957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 cat memory.usage_in_bytes
    430080

    可以看到Docker通过为每个容器创建Cgroup并通过Cgroup去配置的资源限制和资源监控。

用go语言实现通过cgroup限制容器的资源

下面我们在上一节的容器的基础上加上cgroup的限制,下面这个demo实现了限制容器的内存的功能:

package main

import (
    "os/exec"
    "path"
    "os"
    "fmt"
    "io/ioutil"
    "syscall"
    "strconv"
)

const cgroupMemoryHierarchyMount = "/sys/fs/cgroup/memory"

func main() {
    if os.Args[0] == "/proc/self/exe" {
        //容器进程
        fmt.Printf("current pid %d", syscall.Getpid())
        fmt.Println()
        cmd := exec.Command("sh", "-c", `stress --vm-bytes 200m --vm-keep -m 1`)
        cmd.SysProcAttr = &syscall.SysProcAttr{
        }
        cmd.Stdin = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr

        if err := cmd.Run(); err != nil {
            fmt.Println(err)
            os.Exit(1)
        }
    }

    cmd := exec.Command("/proc/self/exe")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Start(); err != nil {
        fmt.Println("ERROR", err)
        os.Exit(1)
    } else {
        //得到fork出来进程映射在外部命名空间的pid
        fmt.Printf("%v", cmd.Process.Pid)

        // 在系统默认创建挂载了memory subsystem的Hierarchy上创建cgroup
        os.Mkdir(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit"), 0755)
        // 将容器进程加入到这个cgroup中
        ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "tasks") , []byte(strconv.Itoa(cmd.Process.Pid)), 0644)
        // 限制cgroup进程使用
        ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "memory.limit_in_bytes") , []byte("100m"), 0644)
    }
    cmd.Process.Wait()
}

通过对Cgroups虚拟文件系统的配置,我们让容器中的把stress进程的内存占用限制到了100m

 PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
10861 root      20   0  212284 102464    212 R  6.2  5.0   0:01.13 stress
时间: 2024-10-24 04:51:24

Docker之Linux Cgroups的相关文章

理解Docker(4):Docker 容器使用 cgroups 限制资源使用

上一篇文章将到 Docker 容器使用 linux namespace 来隔离其运行环境,使得容器中的进程看起来就像爱一个独立环境中运行一样.但是,光有运行环境隔离还不够,因为这些进程还是可以不受限制地使用系统资源,比如网络.磁盘.CPU以及内存 等.为了让容器中的进程更加可控,Docker 使用 Linux cgroups 来限制容器中的进程允许使用的系统资源. 1. 基础知识:Linux control groups 1.1 概念 Linux Cgroup 可???让???您???为???系

Docker基础 Linux内核之Cgroups(2)

作为开源Container技术代表的Docker,它跟Linux内核的Namespace和Cgroup两大特性密不可分.物有本末,事有终始.知所先后,则近道矣.理解Linux的这两大特性将有助于我们更深入的理解Docker. 在本文中我们将会简要介绍一下如何在CentOS上利用Cgroups限制CPU的使用率. Cgroups的历史 Cgroups是Control Groups的缩写, 它是Linux 内核的一个特征,在2.6.24被引入. Cgroups用于提供对Linux的进程组进行资源上的

docker在Linux(有网、无网)下面的安装教程

关于docker的概念,我在这里就不作说明了,直接切入正题吧! 有些客户对保密性要求比较高,因此去现场部署项目的时候,是没有网络的,这样的话,就比较难受了,网上许多无网的安装教程中需要yum指令,前提得有网络才能使用yum呀.下面将会分别介绍下有网和无网情况下docker的安装. 有网 docker ce支持64位版本 Centos 7,并且要求内核版本不低于3.10(可通过uname -r 指令查看内核信息),如果版本低于这个,不用慌,yum update 即可. 1.安装依赖包:  yum

从Docker在Linux和Windows下的区别简单理解Docker的层次结构

上篇文章我们成功在Windows下安装了Docker,输出了一个简单的Hello World程序.本文中我们将利用Docker已有的云端镜像training/webapp来发布一个简单Python的Web程序,在浏览器中输出hello world. 本文内容的测试环境是Windows7下的Docker,用例基于官方文档用例.   一:从运行一个简单的Python Web程序说起 启动Docker客户端并登陆.在客户端中输入以下内容: $ sudo docker run -d -P trainin

Docker在Linux上运行NetCore系列(一)配置运行DotNetCore控制台

转发请注明此文章作者与路径,请尊重原著,违者必究. 本篇文章操作系统信息 Linux:ubuntu 16.04.3 amd64 查看NetCore支持的Linux系统 NetCore不是支持Linux的所有系统,只支持部分,所以在Linux上安装NetCore之前要查看系统与版本是否支持NetCore. 查看NetCore2.0以上各个版本在Linux支持的系统及版本: https://github.com/dotnet/core/blob/master/release-notes/2.0/2.

docker for linux(3)

Docker 使用步骤 1. 安装docker准备 * 安装虚拟机,配置centos7系统:uname -r 查看Linux内核信息.Docker依赖64位,内核是3.10及以上 * 虚拟机安装安装软件工具: yum(Yellow dog Updater Modified) ->基于rpm的软件包管理工具,可以自动解决软件包直接的依赖关系 yum install 软件包 #安装 yum remove 软件包 #卸载 yum install vim -y #安装增强版编辑工具 2.安装docker

Docker在Linux/Windows上运行NetCore文章系列

原文:Docker在Linux/Windows上运行NetCore文章系列 Windows系列 因为Window很简单,VS提供界面化配置,所以只写了一篇文章 Docker在Windows上运行NetCore系列(一)使用命令控制台运行.NetCore控制台应用 Linux(ubuntu 16.04) Docker在Linux上运行NetCore系列(一)配置运行DotNetCore控制台 Docker在Linux上运行NetCore系列(二)把本地编译好的镜像发布到线上阿里云仓库 Docker

Docker在Linux上运行NetCore系列(五)更新应用程序

原文:Docker在Linux上运行NetCore系列(五)更新应用程序 转发请注明此文章作者与路径,请尊重原著,违者必究. 本篇文章与其它系列文章不同,为了方便测试,新建了一个ASP.Net Core视图应用. 备注:下面说的应用,只是在容器中运行的应用程序. 查看现在运行的应用 容器中已经运行了一个应用testaspnetcoredockerlinuxname,版本是1.0.我们下面查看一下已经在运行中的应用. 输入命令[sudo docker ps]可以看到运行中的容器. 红色线的就是我们

使用 Docker 在 Linux 上托管 ASP.NET Core 应用程序

说在前面 在阅读本文之前,您必须对 Docker 的中涉及的基本概念以及常见命令有一定了解,本文侧重实战,不会对相关概念详述. 同时请确保您本地开发机器已完成如下安装: Docker 18.06 或更高版本的 Docker 客户端 .NET Core SDK 2.2 或更高版本 Visual Studio Code 代码编辑器,以及 C# 语法插件 1.17.1 或更高版本 注:本文实验环境是 Ubuntu 18.04 LTS.如果您的机器是 Window,也可以把 Docker 装在虚拟机或服