Cgroup与Namespace学习归纳理解

笔者作为一个初学者,在学习了docker的原理和概念后,对cgroup和namespace产生了兴趣,于是开始学习它们。学习理解并做出了归纳。

什么是cgroup

cgroups,其名称源自控制组群(control groups)的简写,是Linux内核的一个功能,用来限制、控制与分离一个进程组的资源(如CPU、内存、磁盘输入输出等)

cgroups的一个设计目标是为不同的应用情况提供统一的接口,从控制单一进程到操作系统层虚拟化(像OpenVZ,Linux-VServer,LXC)。cgroups提供:
资源限制:组可以被设置不超过设定的内存限制;这也包括虚拟内存。
优先级:一些组可能会得到大量的CPU或磁盘IO吞吐量。
结算:用来衡量系统确实把多少资源用到适合的目的上。
控制:冻结组或检查点和重启动。

Google的工程师(主要是Paul Menage和Rohit Seth)于2006年以“过程容器”的名义开始了这项功能的研究。在2007年末,为了避免在Linux内核上下文中由术语“ 容器 ” 的多种含义引起的混淆,术语改为“控制组” ,并且控制组功能在内核2.6版中合并到Linux内核主线中.24,于2008年1月发布。此后,开发人员添加了许多新功能和控制器,例如2014年对kernfs的支持,防火墙,和统一的层次结构。

为什么需要cgroup

在 Linux 里,一直以来就有对进程进行分组的概念和需求,比如 session group, progress group 等,后来随着人们对这方面的需求越来越多,比如需要追踪一组进程的内存和 IO 使用情况等,于是出现了 cgroup,用来统一将进程进行分组,并在分组的基础上对进程进行监控和资源控制管理等。

Cgroups最初的目标是为资源管理提供的一个统一的框架,既整合现有的cpuset等子系统,也为未来开发新的子系统提供接口。现在的cgroups适用于多种应用场景,从单个进程的资源控制,到实现操作系统层次的虚拟化(OS Level Virtualization)。Cgroups提供了以下功能:
1.限制进程组可以使用的资源数量(Resource limiting )。比如:memory子系统可以为进程组设定一个memory使用上限,一旦进程组使用的内存达到限额再申请内存,就会触发OOM(out of memory)。
2.进程组的优先级控制(Prioritization )。比如:可以使用cpu子系统为某个进程组分配特定cpu share。
3.记录进程组使用的资源数量(Accounting )。比如:可以使用cpuacct子系统记录某个进程组使用的cpu时间
4.进程组隔离(Isolation)。比如:使用ns子系统可以使不同的进程组使用不同的namespace,以达到隔离的目的,不同的进程组有各自的进程、网络、文件系统挂载空间。
5.进程组控制(Control)。比如:使用freezer子系统可以将进程组挂起和恢复。

Cgroup 是透过阶层式的方式来管理的,和程序、子群组相同,都会由它们的 parent 继承部份属性。然而,这两个模型之间有所不同。

Cgroup 是将任意进程进行分组化管理的 Linux 内核功能。Cgroup 本身是提供将进程进行分组化管理的功能和接口的基础结构,I/O 或内存的分配控制等具体的资源管理功能是通过这个功能来实现的。这些具体的资源管理功能称为 Cgroup 子系统或控制器。Cgroup 子系统有控制内存的 Memory 控制器、控制进程调度的 CPU 控制器等。运行中的内核可以使用的 Cgroup 子系统由/proc/cgroup 来确认。

Cgroup 提供了一个 CGroup 虚拟文件系统,作为进行分组管理和各子系统设置的用户接口。要使用 Cgroup,必须挂载 CGroup 文件系统。这时通过挂载选项指定使用哪个子系统。

Cgroup的子系统

cgroup子系统目前有下列几种:

  1. devices 进程范围设备权限
  2. cpuset 分配进程可使用的 CPU数和内存节点
  3. cpu 控制CPU占有率
  4. cpuacct 统计CPU使用情况,例如运行时间,throttled时间
  5. memory 限制内存的使用上限
  6. freezer 暂停 Cgroup 中的进程
  7. net_cls 配合 tc(traffic controller)限制网络带宽
  8. net_prio 设置进程的网络流量优先级
  9. huge_tlb 限制 HugeTLB 的使用
  10. perf_event 允许 Perf 工具基于 Cgroup 分组做性能检测

Cgroup支持的文件种类

Release_agent 删除分组时执行的命令,这个文件只存在于根分组

Notify_on_release 设置是否执行 release_agent。为 1 时执行

Tasks 属于分组的线程 TID 列表

Cgroup.procs 属于分组的进程 PID 列表。仅包括多线程进程的线程 leader 的 TID,这点与 tasks 不同

Cgroup.event_control 监视状态变化和分组删除事件的配置文件

相互关系

相互关系

每次在系统中创建新层级时,该系统中的所有任务都是那个层级的默认 cgroup(我们称之为 root cgroup,此 cgroup 在创建层级时自动创建,后面在该层级中创建的 cgroup 都是此 cgroup 的后代)的初始成员;

一个子系统最多只能附加到一个层级;

一个层级可以附加多个子系统;

一个任务可以是多个 cgroup 的成员,但是这些 cgroup 必须在不同的层级;

系统中的进程(任务)创建子进程(任务)时,该子任务自动成为其父进程所在 cgroup 的成员。然后可根据需要将该子任务移动到不同的 cgroup 中,但开始时它总是继承其父任务的 cgroup。

在这里插入图片描述

Cgroup 特点

在 cgroups 中,任务就是系统的一个进程。

控制族群(control group)。控制族群就是一组按照某种标准划分的进程。Cgroups 中的资源控制都是以控制族群为单位实现。一个进程可以加入到某个控制族群,也从一个进程组迁移到另一个控制族群。一个进程组的进程可以使用 cgroups 以控制族群为单位分配的资源,同时受到 cgroups 以控制族群为单位设定的限制。

层级(hierarchy)。控制族群可以组织成 hierarchical 的形式,既一颗控制族群树。控制族群树上的子节点控制族群是父节点控制族群的孩子,继承父控制族群的特定的属性。

子系统(subsytem)。一个子系统就是一个资源控制器,比如 cpu 子系统就是控制 cpu 时间分配的一个控制器。子系统必须附加(attach)到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。

Cgroup 设计原理分析

CGroups 的源代码较为清晰,我们可以从进程的角度出发来剖析 cgroups 相关数据结构之间的关系。在 Linux 中,管理进程的数据结构是 task_struct,其中与 cgroups 有关的代码如下所示:

#ifdef CONFIG_CGROUPS 
/* Control Group info protected by css_set_lock */ 
struct css_set *cgroups; 
/* cg_list protected by css_set_lock and tsk->alloc_lock */ 
struct list_head cg_list; 
#endif

其中 cgroups 指针指向了一个 css_set 结构,而 css_set 存储了与进程有关的 cgroups 信息。cg_list 是一个嵌入的 list_head 结构,用于将连到同一个 css_set 的进程组织成一个链表。下面我们来看 css_set 的结构,代码如以下所示:

struct css_set { 
atomic_t refcount;
struct hlist_node hlist; 
struct list_head tasks; 
struct list_head cg_links; 
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; 
struct rcu_head rcu_head; 
};

其中 refcount 是该 css_set 的引用数,因为一个 css_set 可以被多个进程公用,只要这些进程的 cgroups 信息相同,比如:在所有已创建的层级里面都在同一个 cgroup 里的进程。hlist 是嵌入的 hlist_node,用于把所有 css_set 组织成一个 hash 表,这样内核可以快速查找特定的 css_set。tasks 指向所有连到此 css_set 的进程连成的链表。cg_links 指向一个由 struct_cg_cgroup_link 连成的链表。

Subsys 是一个指针数组,存储一组指向 cgroup_subsys_state 的指针。一个 cgroup_subsys_state 就是进程与一个特定子系统相关的信息。通过这个指针数组,进程就可以获得相应的 cgroups 控制信息了。cgroup_subsys_state 结构如下所示:

struct cgroup_subsys_state { 
struct cgroup *cgroup; 
atomic_t refcnt; 
unsigned long flags; 
struct css_id *id; 
};

cgroup 指针指向了一个 cgroup 结构,也就是进程属于的 cgroup。进程受到子系统的控制,实际上是通过加入到特定的 cgroup 实现的,因为 cgroup 在特定的层级上,而子系统又是附和到上面的。通过以上三个结构,进程就可以和 cgroup 连接起来了:task_struct->css_set->cgroup_subsys_state->cgroup。cgroup 结构如下所示:

struct cgroup { 
unsigned long flags; 
atomic_t count; 
struct list_head sibling; 
struct list_head children; 
struct cgroup *parent; 
struct dentry *dentry; 
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; 
struct cgroupfs_root *root;
struct cgroup *top_cgroup; 
struct list_head css_sets; 
struct list_head release_list; 
struct list_head pidlists;
struct mutex pidlist_mutex; 
struct rcu_head rcu_head; 
struct list_head event_list; 
spinlock_t event_list_lock; 
};

sibling,children 和 parent 三个嵌入的 list_head 负责将统一层级的 cgroup 连接成一棵 cgroup 树。

subsys 是一个指针数组,存储一组指向 cgroup_subsys_state 的指针。这组指针指向了此 cgroup 跟各个子系统相关的信息,这个跟 css_set 中的道理是一样的。

root 指向了一个 cgroupfs_root 的结构,就是 cgroup 所在的层级对应的结构体。这样一来,之前谈到的几个 cgroups 概念就全部联系起来了。

top_cgroup 指向了所在层级的根 cgroup,也就是创建层级时自动创建的那个 cgroup。

css_set 指向一个由 struct_cg_cgroup_link 连成的链表,跟 css_set 中 cg_links 一样。

下面分析一个 css_set 和 cgroup 之间的关系,cg_cgroup_link 的结构如下所示:

struct cg_cgroup_link { 
struct list_head cgrp_link_list; 
struct cgroup *cgrp; 
struct list_head cg_link_list; 
struct css_set *cg; };

cgrp_link_list 连入到 cgrouo->css_set 指向的链表,cgrp 则指向此 cg_cgroup_link 相关的 cgroup。

cg_link_list 则连入到 css_set->cg_lonks 指向的链表,cg 则指向此 cg_cgroup_link 相关的 css_set。

cgroup 和 css_set 是一个多对多的关系,必须添加一个中间结构来将两者联系起来,这就是 cg_cgroup_link 的作用。cg_cgroup_link 中的 cgrp 和 cg 就是此结构提的联合主键,而 cgrp_link_list 和 cg_link_list 分别连入到 cgroup 和 css_set 相应的链表,使得能从 cgroup 或 css_set 都可以进行遍历查询。

Cgroup版本

Cgroups最初由Paul Menage和Rohit Seth编写,并于2007年进入Linux内核主线。此后称为cgroups版本1。

然后由Tejun Heo接管了cgroup的开发和维护。Tejun Heo重新设计并重写了cgroup。现在,此重写称为版本2,cgroups-v2的文档首次出现在2016年3月14日发布的Linux内核4.5中。

与v1不同,cgroup v2仅具有单个进程层次结构,并且在进程之间进行区分,而不对线程进行区分。

在cgroup v2中,所有已安装的控制器都位于一个统一的层次结构中。尽管(不同的)控制器可以同时安装在v1和v2层次结构下,但是不可能同时在v1和v2层次结构下同时安装相同的控制器。

  1. Cgroups v2提供了安装所有控制器所依据的统一层次结构。
  2. 不允许“内部”过程。除根cgroup以外,进程只能驻留在叶节点(本身不包含子cgroup的cgroup)中。
  3. 必须通过文件cgroup.controllers 和cgroup.subtree_control指定活动的cgroup 。
  4. 该任务的文件已被删除。此外,cpuset 控制器使用的 cgroup.clone_children文件已被删除。
  5. cgroup.events文件提供了一种用于通知空cgroup的改进机制 。

在cgroups v1中,能够针对不同的层次结构安装不同的控制器的功能旨在为应用程序设计提供极大的灵活性。但是,实际上,灵活性的用途比预期的要少,并且在许多情况下增加了复杂性。因此,在cgroups v2中,所有可用的控制器都是按单个层次结构安装的。可用的控制器会自动挂载,这意味着使用以下命令挂载cgroup v2文件系统时不必(或不可能)指定控制器:

mount -t cgroup2 none /mnt/cgroup2

仅当当前未通过针对cgroup v1层次结构的安装使用cgroup v2控制器时,该控制器才可用。或者,换句话说,不可能针对v1层次结构和统一的v2层次结构使用同一控制器。

什么是Namespace

namespace即“命名空间”,也称“名称空间” 。VS.NET中的各种语言使用的一种代码组织的形式 通过名称空间来分类,区别不同的代码功能 同时也是VS.NET中所有类的完全名称的一部分。

命名空间是用来组织和重用代码的。如同名字一样的意思,NameSpace(名字空间),之所以出来这样一个东西,是因为人类可用的单词数太少,并且不同的人写的程序不可能所有的变量都没有重名现象,对于库来说,这个问题尤其严重,如果两个人写的库文件中出现同名的变量或函数(不可避免),使用起来就有问题了。为了解决这个问题,引入了名字空间这个概念,通过使用 namespace xxx;你所使用的库函数或变量就是在该名字空间中定义的,这样一来就不会引起不必要的冲突了。
通常来说,命名空间是唯一识别的一套名字,这样当对象来自不同的地方但是名字相同的时候就不会含糊不清了。使用扩展标记语言的时候,XML的命名空间是所有元素类别和属性的集合。元素类别和属性的名字是可以通过唯一XML命名空间来唯一。

在XML里,任何元素类别或者属性因此分为两部分名字,一个是命名空间里的名字另一个是它的本地名。在XML里,命名空间通常是一个统一资源识别符(URI)的名字。而URI只当名字用。主要目的是为了避免名字的冲突。

为什么要使用Namespace

例如,在同一个班中有两个相同名字的人,他们都叫Tony,那么他们一定也有其他的一些信息方便人们去分辨它们。Namespace能实现轻量级虚拟化(容器)服务。在同一个 namespace 下的进程可以感知彼此的变化,而对外界的进程一无所知。这样就可以让容器中的进程产生错觉,认为自己置身于一个独立的系统中,从而达到隔离的目的。也就是说 linux 内核提供的 namespace 技术为 docker 等容器技术的出现和发展提供了基础条件。

Namespace的定义

namespace 是 Linux 内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。

Linux namespaces 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程,对其他 namespace 中的进程没有影响。

6种在Linux 内核中实现的Namespace

IPC 隔离 System V IPC 和 POSIX 消息队列
Network 隔离网络资源
Mount 隔离文件系统挂载点
PID 隔离进程ID
UTS 隔离主机名和域名
User 隔离用户和用户组

与命名空间相关的三个系统调用:
clone创建全新的Namespace,由clone创建的新进程就位于这个新的namespace里。创建时传入 flags参数,可选值有 CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWUTS, CLONE_NEWUSER, 分别对应上面六种namespace。
unshare为已有进程创建新的namespace。
setns把某个进程放在已有的某个namespace里。

6种命名空间

UTS namespace
UTS namespace 对主机名和域名进行隔离。为什么要隔离主机名?因为主机名可以代替IP来访问。如果不隔离,同名访问会出冲突。

IPC namespace
Linux 提供很多种进程通信机制,IPC namespace 针对 System V 和 POSIX 消息队列,这些 IPC 机制会使用标识符来区别不同的消息队列,然后两个进程通过标识符找到对应的消息队列。
IPC namespace 使得 相同的标识符在两个 namespace 代表不同的消息队列,因此两个namespace 中的进程不能通过 IPC 来通信。

PID namespace
隔离进程号,不同namespace 的进程可以使用相同的进程号。
当创建一个 PID namespace 时,第一个进程的PID 是1,即 init 进程。它负责回收所有孤儿进程的资源,所有发给 init 进程的信号都会被屏蔽。

Mount namespace
隔离文件挂载点,每个进程能看到的文件系统都记录在/proc/$$/mounts里。在一个 namespace 里挂载、卸载的动作不会影响到其他 namespace。

Network namespace
隔离网络资源。每个 namespace 都有自己的网络设备、IP、路由表、/proc/net 目录、端口号等。网络隔离可以保证独立使用网络资源,比如开发两个web 应用可以使用80端口。
新创建的 Network namespace 只有 loopback 一个网络设备,需要手动添加网络设备。

User namespace
隔离用户和用户组。它的厉害之处在于,可以让宿主机上的一个普通用户在 namespace 里成为 0 号用户,也就是 root 用户。这样普通用户可以在容器内“随心所欲”,但是影响也仅限在容器内。

Namespace发展历史

Linux 在很早的版本中就实现了部分的 namespace,比如内核 2.4 就实现了 mount namespace。大多数的 namespace 支持是在内核 2.6 中完成的,比如 IPC、Network、PID、和 UTS。还有个别的 namespace 比较特殊,比如 User,从内核 2.6 就开始实现了,但在内核 3.8 中才宣布完成。同时,随着 Linux 自身的发展以及容器技术持续发展带来的需求,也会有新的 namespace 被支持,比如在内核 4.6 中就添加了 Cgroup namespace。

参考:
https://www.cnblogs.com/sparkdev/p/9365405.html
https://www.jianshu.com/p/ab423c3db59d
https://baike.baidu.com/item/namespace/1700121?fr=aladdin
https://blog.csdn.net/qq_40416052/article/details/82528676
https://www.cnblogs.com/ryanyangcs/p/11198140.html
https://baike.baidu.com/item/Cgroup/4988200?fr=aladdin
https://blog.csdn.net/huang987246510/article/details/80765628
https://zhuanlan.zhihu.com/p/61155161
https://www.cnblogs.com/menkeyi/p/10941843.html
https://www.jianshu.com/p/aa3012aaa8ba
https://my.oschina.net/u/1262062/blog/2051159

  • 10
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值