《自己动手写Docker》书摘之二---Linux Cgroups介绍

2023-05-16

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。

    “`shell

/ 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_release`和`release_agent`会一起使用,`notify_on_release`表示当这个cgroup最后一个进程退出的时候是否执行`release_agent`,`release_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
134217728957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 # 查看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
“`

小结

本节我们主要介绍了Linux Cgroups,通过Linux Cgroups的三种结构,可以随意的定制对资源的限制和做资源的监控,最后用GO语言做了一个Cgroups的限制资源的demo,介绍了怎么样用GO语言去操控容器的Cgroups而限制容器的资源。

相关图书推荐<<自己动手写 docker>>

这里写图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《自己动手写Docker》书摘之二---Linux Cgroups介绍 的相关文章

  • Android下打印调试堆栈方法

    http blog csdn net freshui article details 9456889 打印堆栈是调试的常用方法 xff0c 一般出现异常时 xff0c 我们可以在跑出异常时也将堆栈情况打印出来 xff0c 这样十分方便错误查
  • android抓取各种log的方法

    1 logcat xff08 四类log buffer是main xff0c radio xff0c system xff0c events xff09 adb wait for device logcat adb logcat v tim
  • archlinux下安装搜狗输入法

    一 安装 fcitx sudo pacman S fcitx fcitx qt5 fcitx configtool 二 安装 qt4 xff08 可以不用安装 xff09 AUR qt4 提供了 qt4 安装脚本 mkdir p Softw
  • 解决Unable to add window -- token android.os.BinderProxy is not valid; is your activity running?

    运行项目有时候在dialog这里一直报错 xff0c 按照日志在网上找解决方案 很多都跑到了底层去解决这问题 然而我不懂底层 xff0c 没办法 继续找咯 苍天明鉴 xff01 找到问题了 原因一般是展示dialog的时候用的是异步 xff
  • centos系统中php Curl 无法访问https ,更换ssl_version NSS为openssl

    在centos 6上面 curl模块的ssl 支持默认为NSS xff0c 涉及到的程序里有https xff0c 是需要双向认证的 xff0c 这时使用NSS会报错 所以需要更换为openssl 一 查看系统自带的curl的版本 root
  • python-求三角形的面积

    计算三角形的面积 法一 xff1a 计算三角形的面积 a 61 float input 39 输入三角形第一边长 xff1a 39 b 61 float input 39 输入三角形第二边长 39 c 61 float input 39 输
  • 如何把达梦8数据库安装在root用户下管理

    达梦数据库一般安装在dmdba用户下 xff0c 官方文档也是这么推荐的 那么有没有办法不创建dmdba用户 xff0c 直接安装在root下呢 xff1f 希望我不要把大家带偏 xff0c 只是本着研究学习的心态 xff0c 做个实验 结
  • The word 'localhost' is not correctly spelled 这个问题怎么解决

    有时工程中有下划线并提示 The word is not correctly spelled 此问题是MyEclipse校验单词拼写造成 xff0c 如果出在配置文件中 xff0c 一般会影响到程序的正常执行 解决方法 xff1a 在MyE
  • Seaship数据集+yolov5

    在SeaShip数据集上验证YoloV5 操作系统 xff1a ArchLinux 需要代理 1 conda安装配置 已经完成的小伙伴可以直接跳过 安装Anaconda yay安装 yay S anaconda 激活Anaconda环境 s
  • 对抗样本生成方法学习总结

    title 对抗样本生成方法 date 2023 03 21 15 08 46 tags pytorch 对抗样本 cover https s2 loli net 2023 03 20 TkeiAjqp3Jdg9tI png categor
  • 划词翻译简单实现

    环境 xff1a archlinux xff0c 其余linux系统类似 安装依赖 span class token function sudo span pacman S xsel span class token function su
  • 平凡的人生or开挂的人生——对知乎相关问题的回答

    知乎原题 xff1a 为什么有些人的人生和开了挂一样 xff1f https www zhihu com question 37106162 楼上说的都特别好 xff0c 我很想补充一点 我先抛出一个背景概念 我认为开挂人生 xff0c 本
  • ARM64启动过程分析

    文章目录 arm64启动过程分析arm64启动过程分析 xff08 一 xff09 boot protocolarm64启动过程分析 xff08 二 xff09 内核启动第一步arm64启动过程分析 xff08 三 xff09 创建启动阶段
  • Android Studio: Kotlin使用DataBinding异常

    异常 Error 7 30 Unresolved reference databinding E test Android Jetpack app src main java com mazaiting jetpack architectu
  • H5C3__元素垂直居中的方法

    在 CSS 中对元素进行水平居中是非常简单的 xff1a 如果它是一个行内元素 xff0c 就对它的父元素应用 text align center 如果它是一个块级元素 xff0c 就对它自身应用 margin auto 还有没有其他的方法
  • 2013年期中总结 2013年9月10日20:17:49

    题目中的时间 xff0c 我改了几次 xff0c 一直在想这半年来的经历 xff0c 我想我是成长着的吧 xff01 继上次总结 2012年年度总结 2013年2月25日21 49 44 xff0c 眨眼过去了半年 xff0c 细细想来这半
  • VMware安装Ubuntu20.04.5常见问题及解决方案

    文章目录 使用Xftp连接ubuntu系统ubuntu上安装指定版本nodejsubuntu设置连网ubuntu安装Java8ubuntu安装 deb格式软件ubuntu卸载 deb格式软件ubuntu中electron框架安装的缓存在如下
  • 关于行业代码对应表的数据库设计及导入(4位行业代码)

    最近使用asp net 43 sqlserver 43 layui前端框架完成了一个小页面 xff0c 其中有一个功能是需要将行业代码表导入倒数据库 看到这个行业代码表 xff0c 刚开始还真是无从下手 xff0c 经过查询找到一个页面是关
  • 当你不想学习的时候,来看看大脑是怎么想的《摘抄与所想》

    尽管科学家一个接一个的科研成果让我们对记忆有了越来越多的了解 xff0c 但直到今天 xff0c 科学家所发现的所谓大脑的秘密也只是冰山一角 xff0c 在很大程度上 xff0c 大脑和记忆仍是神秘的 研究人员认为 xff0c 记忆是一个过
  • English learning method ---学英语重中之重打通“任督二脉”

    漫漫十年艰辛路 xff0c 英语学习之旅 曾经秉承 路漫漫其修远兮 xff0c 吾将上下而求索 的信念 xff0c 初一那年了解到原来 xff08 a b c d e f g xff09 不仅仅读作 xff08 啊 xff0c 波 xff0

随机推荐