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
pidshierarchy
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
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
Docker之Linux Cgroups的更多相关文章
- 理解Docker(4):Docker 容器使用 cgroups 限制资源使用
本系列文章将介绍Docker的有关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...
- Docker 基础技术之 Linux cgroups 详解
PS:欢迎大家关注我的公众号:aCloudDeveloper,专注技术分享,努力打造干货分享平台,二维码在文末可以扫,谢谢大家. 推荐大家到公众号阅读,那里阅读体验更好,也沉淀了很多篇干货. 前面两篇 ...
- Linux Cgroups
目录 Linux Cgroups Cgroups中的三个组件 三个组件的关系 Kernel接口 Docker是如何使用Cgroups的 Go语言实现Cgroups限制容器资源 Linux Cgroup ...
- Docker:Linux离线安装docker
docker离线下载路径 docker所有版本:https://download.docker.com/linux/static/stable/ 离线安装 1.解压 #解压tar包 tar -xvf ...
- Docker在Linux上运行NetCore系列(三)在Linux上使用Docker运行Asp.NetCore
转发请注明此文章作者与路径,请尊重原著,违者必究. 系列文章:https://www.cnblogs.com/alunchen/p/10121379.html 开始说明 上几篇文章都是通过Linux运 ...
- Docker在Linux/Windows上运行NetCore文章系列
Windows系列 因为Window很简单,VS提供界面化配置,所以只写了一篇文章 Docker在Windows上运行NetCore系列(一)使用命令控制台运行.NetCore控制台应用 Linux( ...
- Docker在Linux上运行NetCore系列(五)更新应用程序
转发请注明此文章作者与路径,请尊重原著,违者必究. 本篇文章与其它系列文章不同,为了方便测试,新建了一个ASP.Net Core视图应用. 备注:下面说的应用,只是在容器中运行的应用程序. 查看现在运 ...
- Docker在Linux上运行NetCore系列(四)使用私有Nuget与多个本地包引用运行ASPNetCore
转发请注明此文章作者与路径,请尊重原著,违者必究. 本篇文章演示了使用Dockerfile在Linux(ubuntu16.04)系统上构建ASPNetCore应用,并且在一个解决方案中存在多个项目之间 ...
- Docker在Linux上运行NetCore系列(二)把本地编译好的镜像发布到线上阿里云仓库
转发请注明此文章作者与路径,请尊重原著,违者必究. 系列文章:https://www.cnblogs.com/alunchen/p/10121379.html 开始 本篇文章结束在本地创建完成镜像后, ...
随机推荐
- java基础(环境设置,基础语法,函数数组)
框架图 环境搭建 课程中常见dos命令: dir : 列出当前目录下的文件以及文件夹 md : 创建目录 rd : 删除目录 cd : 进入指定目录 cd.. : 退回到上一级目录 cd/ : 退回到 ...
- linux Makefile编写的整理
最近将Makefile的编写进行了整理和提炼了一下,大致分为五个步骤: 编译总共为五个部分 1.设置编译环境 set compile environment 2.获取要编译的源文件,以及把源文件转换为 ...
- MicroPHP 2.2.0 发布
ver 2.2.0: 增加了: 1.$this->cache为最新的phpfastcache2.1,缓存功能更加强大,而且编写自己的缓存类非常容易. 2.自定义缓存类说明: ...
- Swift编程语言(中文版)官方手册翻译(进度8.8%)
翻译着玩,进度会比较慢. 等不及的可以看CocoaChina翻译小组,他们正在组织翻译,而且人手众多,相信会提前很多完成翻译. 原文可以在iTunes免费下载 目前进度 7 JUN 2014: 8.8 ...
- Microsoft 参考源代码系统更新,有惊喜哦。
在以前,MS的参考源代码在单步调试时时好用时不好用,最后我找到了原因,那就是如果想用MS的参考源代码进行单步调试,那么你就得想尽办法把系统上的.NET FX降级到RTM版本(卸载各种相关补丁),今天我 ...
- 开始VS 2012 中LightSwitch系列的第1部分:表中有什么?描述你的数据
[原文发表地址] Beginning LightSwitch in VS 2012 Part 1: What’s in a Table? Describing Your Data [原文发表时间] ...
- hadoop研究:mapreduce研究前的准备工作
继续研究hadoop,有童鞋问我,为啥不接着写hive的文章了,原因主要是时间不够,我对hive的研究基本结束,现在主要是hdfs和mapreduce,能写文章的时间也不多,只有周末才有时间写文章,所 ...
- 学习Scala01 环境安装
Scala是一门运行在jvm上的多范式语言,作为一个java程序员,使用Scala来写写程序,既不用担心会没有java强大的库支持,又能快速地写出简短强悍的代码,除此之外scala还为我们提供了强大的 ...
- node.js调试
用了几天node.js感觉很新奇,但是调试问题实在是愁煞人,开始的时候懒的学习调试方法,看看异常内容就可以了,但随着代码复杂程度的上升,并不是所有错误都是语法错误了,不调试搞不定了,只好搜搜资料,学习 ...
- Lucene系列-概述
为了生存,就得干一行爱一行.那就学习下lucene吧. 全文检索介绍 流程: 建索引 准备待搜索文档 文档分词:将文档分成一个个单独的单词,去除标点符号.停词(无意义的冠词介词等),得到token 语 ...