mesos学习之cgroup接口封装

初次接触linux container,细读了mesos的linux container功能封装的代码。写得啰嗦了一点。

1 Cgroup介绍和规则

控制群组Cgroup允许你在用户自定义的任务(进程)群组中分配资源,如cpu时间、系统内存、网络带宽,或者这些资源的组合。你可以监控你配置的控制群组,拒绝控制群组访问某些特定资源,甚至动态重新配置正运行在系统之上的控制群组。控制群组配置服务(cgconfig)可以配置成在系统启动时运行;或者在你重新定义cgroups时重新建立;也可以对它们进行持久化,在系统重启后依旧不变。

Cgroup模型与linux进程模型类似

1、 都是分层的。

2、 子cgroup会从父cgroup继承特定的属性。

Cgroup基本概念

Hierarchy:层级,一个层级包括一组在树形目录上排列的控制群组集合,每一个在系统中运行的任务都属于层级的控组群组中的其中一个。

Subsystem:子系统,表示内核中的一个子系统(如cpu、memory、cpuset等),每一个子系统都可以关联到一个层级中。可以使用挂载命令mount -t cgroup -o subsystems name hierarchy 来实现将子系统关联到层级中。

Cgroup:控制群组,一个控制群组是一个任务集合。

Control:控制群组中的一个control file。

Cgroups的四个规则

1、 单个层级可以挂载多个cgroup子目录。

2、 单个cgroup子系统不能挂载在多个层级上。

3、 同一个任务可以是不同层级的cgroup的成员,但不能是同一层级的不同cgroup中的成员。

4、父进程创建的子进程会继承父进程的cgroup membership,但父子进程是完全独立,如果需要子进程可以移动到别的cgroup中。

Cgroup部分功能的实现方式:

mesos学习之cgroup接口封装_第1张图片

2  cgroup的封装

bool enabled();

用途:查看当前机器的cgroups模块是否可用

实现:查看/proc/cgroups目录是否存在,如果存在cgroups模块是可用的,否则不可用。可以使用llinux的stat系统调用来查看是否存在该目录

Try > hierarchies();

用途:获取当前已挂载的层级

实现:调用setmntent/getmntent函数读取/proc/mounts的类型为cgroup的cgroup虚拟文件系统已经挂载的条目信息(对于如何解析/proc/mounts文件,见上一篇文章),每一个条目都是一个活跃的层级,每个条目都包含如下信息:文件系统name,挂载点绝对路径,文件系统类型,选项,dump频率,挂载的子系统和fsck的检查次数。然后返回所有已挂载的层级的绝对路径。

Try > subsystems();

用途:获取当前所有可用的cgroup子系统

实现:通过解析/proc/cgroups文件来实现,/proc/cgroups中的每个条目都包含如下信息:子系统name,子系统是否关联已挂载的层级,子系统的cgroup控制组数目,子系统是否可用(1可用,0不可用)。将所有可用的子系统作为结果返回。/proc/cgroups的文件内容见下图。

mesos学习之cgroup接口封装_第2张图片

Try > subsystems(const std::string& hierarchy);

用途:获取关联到某一层级上的所有子系统

参数:hierarchy为传入的参数为层级的相对路径

实现:

| 首先获取到层级的绝对路径,调用c函数realpath来得到。

| 通过解析/proc/mounts来得到所有已挂载层级及其相关信息的集合

    | 遍历层级的集合,通过绝对路径的比较来查找指定路径是否存在已挂载的层级

| 如果没有找到,则返回失败,找到,则继续。

| 通过调用上述的subsystems()函数,得到所有可用的cgroup子系统的集合

| 通过解析层级的条目信息来得到所有关联到层级上的子系统(在前一篇文章中有代码示例),如果子系统是可用的,即包含在上一步骤中得到的可用子系统集合中,那么就作为结果返回。

Try enabled(const std::string& subsystems);

用途:检查指定的cgroup子系统是否全部可用

参数:以”cpu, memory, xxx”字符串格式传入的子系统集合

实现:同subsystems(),通过解析/proc/groups得到所有可用的cgroup子系统集合,查看传入的子系统是否全部都包含在可用子系统集合中。

Try busy(const std::string& subsystems);

用途:查看指定的子系统是否已经关联到某一个已经挂载的层级上。

参数:以”cpu, memory, xxx”字符串格式传入的子系统集合

实现:同subsystems(),获取所有可用的子系统集合,查看传入的子系统是否包含在可用子系统集合中,并通过查看子系统的hierarchy值(/proc/cgroups文件条目的第二个参数就是hierarchy),这个值为1则表示已经关联到某一层级上。

Try mounted(const string& hierarchy, const string& subsystems)

用途:查看给定层级是否已经挂载cgroups虚拟文件系统,并关联了指定的子系统。

参数:hierarchy,层级相对路径;subsystems,”cpu, memory,xxx”格式的子系统字符串;

实现:

| 确认指定的层级相对目录存在,且获取指定层级的绝对路径

| 调用hierarchies()来获取所有已挂载的层级绝对路径集合,如果指定的层级目录包含在集合中,则继续,否则返回失败。

| 调用subsystems(const std::string& hierarchy),根据指定层级绝对路径,来得到层级的所有子系统集合。

| 查看指定的子系统是否包含在层级的子系统中,如果没有返回失败。

Try mount(const string& hierarchy, const string& subsystems, int retry)

用途:挂载一个cgroup虚拟文件系统到指定路径,并关联给定的子系统。

参数:hierarchy,指定挂载路径;subsystems,”cpu, memory, xxx”格式的子系统;retry,在mount失败时重新尝试的次数。

实现:mount使用迭代的方式处理在mount失败时尝试重新进行mount

| 检查指定路径是否存在,如果已经存在,则返回失败。

| 遍历传入的子系统

    | 调用enabled(const std::string& subsystems)和busy(const string& subsystems),确保子系统是否可用且没有被其他cgroup层级关联。如果不可用或者被其他层级使用,则返回失败。

    | 创建挂载目录

    | 通过调用mount系统调用,传入路径、cgroup文件系统类型和子系统信息,挂载cgroup虚拟文件系统。

Try unmount(const std::string& hierarchy)

用途:卸载已经挂载的cgroup层级,并删除层级目录

参数:hierarchy,层级目录

实现:调用unmount系统调用,来卸载层级,并调用rmdir删除层级目录

Try create(const std::string& hierarchy, const std::string& cgroup);

用途:在一个已挂载的层级中创建一个控制群组

参数:hierarchy,给定的层级路径;cgroup,控制群组名称。

实现:

| 先调用mounted(const string& hierarchy, const string& subsystems),查看给定的层级路径是否已经挂载cgoup虚拟文件系统。

| 生产控制群组的路径,hierarchy/cgroup,然后调用mkdir创建控制群组。

| 一般情况下控制群组就创建完了,但是当层级的关联子系统中包含cpuset,那么需要将控制群组的父目录中的cpuset.cpus和cpuset.mems文件中的内容拷贝到控制群组目录的cpuset.cpus和cpuset.mems中。

说明:Cpuset子系统用于分配独立的cpus和内存节点到控制群组。Cpuset有强制参数cpuset.cpus和cpuset.mems,在将任务进程移动到控制群组之前,必须将这些强制参数写入到控制群组目录下的对应伪文件中。

Try > get(

    const std::string& hierarchy,

const std::string& cgroup);

用途:返回某一层级目录下的控制群组的子群组,因为cgroup模型是树形结构,允许在控制群组中创建子群组。

实现:

| 首先确定给定的层级路径已经挂载了cgroup虚拟文件系统。

| 然后使用fts_xxx的接口来获取所有子群组目录名称,并作为结果返回。

Try remove(const std::string& hierarchy, const std::string& cgroup);

用途:从给定的层级中删除一个控制群组。这个函数不会迭代地删除,如果有子群组则返回失败。

实现:

| 确认给定的层级路径已挂载cgroup控制群组,层级目录下存在名为cgroup的控制群组。

| 调用get(const std::string& hierarchy,const std::string& cgroup)来查看是否由子群组,如果没有,则调用rmdir来删除该控制群组。

获取和分配控制群组任务

获取和分配控制群组任务是通过控制群组目录下的tasks文件来进行的。tasks接口完成获取控制群组任务功能,通过读取和解析tasks文件来得到任务进程的PID集合。

assign接口完成向控制群组分配任务进程的任务,通过向tasks文件写入任务进程的pid来实现。

Try > tasks(

    const std::string& hierarchy,

const std::string& cgroup);

Try assign(

    const std::string& hierarchy,

    const std::string& cgroup,

    pid_t pid);

Try kill(

    const std::string& hierarchy,

    const std::string& cgroup,

int signal);

用途:向已挂载层级的控制群组的所有任务进程发送信号,通过tasks函数获取所有任务的进程ID,然后遍历这些进程ID,调用kill系统调用,向这些进程发送信号。

读写控组群组的文件信息

读写层级的控组群组下的控制文件,如下两个函数,没什么好说的,就是调用系统的open、read和write实现字符串的读取和写入

Try read(

    const std::string& hierarchy,

    const std::string& cgroup,

const std::string& control);

Try write(

    const std::string& hierarchy,

    const std::string& cgroup,

    const std::string& control,

const std::string& value);

3 OOM事件监听

1、mesos采用的第三方库Libprocess实现了一组类似java并行编程中的future和spawn接口。使用者也可以在future中设置回调函数进行异步地调用。Libprocess使用了std::tr1和一个线程池来实现异步回调函数设置。另外libprocess实现了actor模型,在线程池之上实现了一种轻量级的对象process,作为并行编程的基本单位,可以使用process来异步地执行任务。Libprocess相关内容可能会在后续的文章中进行介绍。

2、OOM事件监听的实现:

OOM事件是指Out of memory,当控制群组的任务进程消耗的内存超过了允许的内存范围,就会发送OOM事件。Memory子系统使用oom_control参数来指定发送OOM事件后的处理方式。当oom_control的值为1时,memory子系统会使用Out of Memory killer立即杀掉任务进程;当oom_control的值为0时,可以允许用户空间的应用程序监听OOM事件。Memory子系统oom_control参数的值可以在控制群组的memory.oom_control文件中进行读取和写入。

Memory子系统提供了一套机制支持监听OOM事件,这套机制是通过eventfd来实现的。先创建一个eventfd,然后将控制群组的cgroup.oom_control文件以只读权限打开,然后将eventfd和oom_control文件的fd以”%d %d”格式写入到控制组的cgroup.event_control文件中。然后就可以通过读取eventfd来监听OOM事件。那么取消对OOM事件的监听,只需要关闭eventfd。官方给出的代码如下:

mesos学习之cgroup接口封装_第3张图片

process::Future listen(

    const std::string& hierarchy,

    const std::string& cgroup,

    const std::string& control,

const Option& args = Option::none());

用途:目前只支持监听cgroup的oom事件,并返回一个Future用于设置OOM事件处理的异步回调函数。

参数:control,值为memory.oom_control的字符串。

实现:

| 调用spawn启动类型为EventListener的process,这个process将完成OOM的事件监听。

| EventListener会将eventfd和cgroup.oom_control文件的fd写入到cgroup.event_control中,然后阻塞读eventfd的io事件;在读到eventfd的IO事件后,会触发在Future中设置OOM事件处理的异步回调,libprocess的线程池会异步执行这个异步回调函数。

| 在异步回调执行完后,关闭eventfd,最后结束自己。

4 控制群组的任务挂起和重启装

Cgroup控制群组可以支持对任务进程的挂起和重启,使用cgroup的freezer子系统来实现,若想要支持这个功能,必须在挂载层级时关联Freezer子系统。如果层级关联了Freezer子系统,控制群组目录下有一个freezer.state文件,可以通过读取和写入freezer.state来实现任务进程的执行状态查询和修改。

freezer.state只有在非root的cgroup目录下可用,有三种可能的状态值:

1、FROZEN:表示cgroup中的任务已经被挂起。

2、FREEZING:表示cgroup中任务对应的进程正在被挂起

3、THAWED:表示cgroup中的任务已经被重启。

Mesos封装了任务挂起和重启的两个接口如下:

Future freeze(

    const string& hierarchy,

    const string& cgroup,

    const Duration& interval,

    unsigned int retries);

Future thaw(

    const string& hierarchy,

    const string& cgroup,

const Duration& interval);

这两个函数都返回一个Future,可以设置异步回调,在任务挂起或重启后再执行具体动作。具体任务挂起和重启的功能是由类型为Freezer的process来实现的。

挂起功能实现如下:

| 查询freezer.state文件,如果值为FROZEN,则触发异步回调

| 如果不为FROZEN,则往freezer.state文件中写入值为”FROZEN”的字符串

| 然后监控freezer.state的状态

    | 如果状态已经为FROZEN,则触发future设置的异步回调,然后结束自己

    | 如果状态为FREEZING,说明控制器的某些进程处于stopped/traced state ('T' state shown in ps command),则需要获取控制器的所有进程ID,遍历所有进程,如果进程的状态为stopped/traced state,则向进程发送SIGCONT,杀死进程。

    | 然后再次将FROZEN写入到freezer.state文件中

    | 定时地重复监控freezer.state状态地动作,支持超过一定次数或者任务已经挂起

重启功能实现如下:

| 查询freezer.state文件,如果值为THAWED,则触发异步回调

| 如果不为THAWED,则往freezer.state文件中写入值为”THAWED”的字符串

| 监控freezer.state的状态

    | 如果值为THAWED,则触发future设置的异步回调,然后结束自己

    | 定时地重复监控freezer.state状态地动作,支持超过一定次数或者任务已经挂起

5 销毁控制组和清理控制组

process::Future destroy(

    const std::string& hierarchy,

    const std::string& cgroup = "/",

const Duration& interval = Milliseconds(100));

process::Future cleanup(const std::string& hierarchy);

Destroy实现kill控制群组中所有正在运行的任务进程。Cleanup则在内部调用Destroy之后,再删除控制群组的目录。

Kill控制群组的功能是由多个process组合完成的,EmptyWatcher、TasksKiller和Destroyer。EmptyWatcher等待控制群组的任务数为空,TaskerKiller完成kill控制群组的任务进程的功能,Destroyer则集成EmptyWatcher和TaskerKiller,完成整体功能。

功能的主体流程是一个调用链,如下所示:

| 先向控制群组所有进程发送 SIGSTOP信号,停止进程

| 然后向控制组所有进程都发送SIGKILL信号,杀死进程

| 然后等待控制组的任务为空

| 然后挂起(freeze)控制组的所有任务

| 然后再向控制组的没有退出的进程发送SIGKILL信号

| 然后重启(thaw)控制组的所有任务

| 等待控制组的任务为空

| 设置调用链结束后,触发future设置的异步回调

 

作者zy,QQ105789990

你可能感兴趣的:(linux,container,mesos)