searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

cgroup技术介绍

2023-05-30 05:50:33
491
0

背景介绍

YARN是一个资源管理、任务调度的框架,yarn中的任务也需要像k8s一样支持对任务的资源进行限制,所以需要对cgroup技术做了些调研。

Cgroup的简介

Cgroup 是一个 Linux 内核特性,合并到2008年发布的 2.6.24 内核版本中,对一组进程的资源使用(CPU、内存、磁盘 I/O 和网络等)进行限制、审计和隔离。它可以根据需求把一系列系统任务及其子任务整合 (或分隔) 到按资源划分到等级不同的组内,从而为系统资源管理提供一个统一的框架。简单说,cgroups 可以限制、记录任务组所使用的物理资源。本质上来说,cgroups 是内核附加在程序上的一系列钩子 (hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。cgroups 已经成为很多技术的基础,特别是资源限制,比如 LXC、Docker、systemd。

Cgroup的组成与功能

Cgroup作用

资源限制(Resource limiting): Cgroups可以对进程组使用的资源总额进行限制。如对特定的进程进行内存使用上限限制,当超出上限时,会触发OOM。
优先级分配(Prioritization): 通过分配的CPU时间片数量及硬盘IO带宽大小,实际上就相当于控制了进程运行的优先级,如:可以使用cpu子系统为某个进程组分配特定cpu share。
资源统计(Accounting): Cgroups可以统计系统的资源使用量,如CPU使用时长、内存用量等等,这个功能非常适用于计费。
进程控制(Control:Cgroups可以对进程组执行挂起、恢复等操作。

Cgroups 几个核心概念

task: 在Cgroups中,task就是系统的一个进程。
subsystem: 一个 subsystem 就是一个内核模块,他被关联到一颗 cgroup 树之后, 就会在树的每个节点(进程组)上做具体的操作。subsystem 经常被称作 resource controller,因为它主要被用来调度或者限制每个进程组的资源

cgroup:控制组表示对这些任务进行怎样的资源管理策略,包含一个或多个子系统,一个任务可以加入某个cgroup,也可以从某个cgroup迁移到另外一个cgroup。
hierarchy: 一个 hierarchy 可以理解为一棵 cgroup 树,树的每个节点就是一个进程 组,每棵树都会与零到多个 subsystem 关联。在一颗树里面,会包含 Linux 系统中的所有 进程,但每个进程只能属于一个节点(进程组)。系统中可以有很多颗 cgroup 树,每棵树 都和不同的 subsystem 关联,一个进程可以属于多颗树,即一个进程可以属于多个进程 组,只是这些进程组和不同的 subsystem 关联。

Cgroup子系统

blkio -- 为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等)。
cpu --  使用调度程序提供对 CPU 的 cgroup 任务访问控制。
cpuacct -- 自动生成 cgroup 中任务所使用的 CPU 报告。
cpuset --  为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
devices -- 可允许或者拒绝 cgroup 中的任务访问设备。
freezer --  挂起或者恢复 cgroup 中的任务。
memory -- 设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。
net_cls -- 使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。

子系统,层级,控制组 和 Tasks 的关系

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

如下图将cpu和memory subsystems(或者任意多个subsystems)附加到同一个hierarchy。

 

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

如下图cpu subsystem已经附加到了hierarchy A,并且memory subsystem已经附加到了hierarchy B。因此cpu subsystem不能在附加到hierarchy B。

系统每次新建一个hierarchy时,该系统上的所有task默认构成了这个新建的hierarchy的初始化cgroup

这个cgroup也称为root cgroup,对于你创建的每个hierarchy,task只能存在于其中一个cgroup中,即一个task不能存在于同一个hierarchy的不同cgroup中,但是一个task可以存在在不同hierarchy中的多个cgroup中。如果操作时把一个task添加到同一个hierarchy中的另一个cgroup中,则会从第一个cgroup中移除。
如下图,cpu和 memory subsystem 被附加到 cpu_mem_cg 的 hierarchy。而net_cls subsystem被附加到net_cls hierarchy。并且httpd进程被同时加到了cpu_mem_cg hierarchy的cg1 cgroup中和net hierarchy的cg3 cgroup中。并通过两个hierarchy的subsystem分别对httpd进程进行cpu,memory及网络带宽的限制。

 

系统中的任何一个task(Linux中的进程)fork自己创建一个子task(子进程)时,子task会自动的继承父task cgroup的关系。

在同一个cgroup中,子task可以根据需要移到其它不同的cgroup中,父子task之间是相互独立不依赖的。
如下图,httpd进程在cpu_and_mem hierarchy的/cg1 cgroup中并把PID 4537写到该cgroup的tasks中,之后httpd(PID=4537)进程fork一个子进程httpd(PID=4840)与其父进程在同一个hierarchy的统一个cgroup中,但是由于父task和子task之间的关系独立不依赖的,所以子task可以移到其它的cgroup中。

Cgroup,cgroupfs,systemd的关系

Cgroupfs 和 systemd 都是对 cgroup 的封装实现,给用户提供了使用的命令和接口。

什么是cgroupfs

Cgroup 提供了一个原生接口并通过 cgroupfs 提供,cgroupfs 就是 Cgroup 的一个接口的封装。类似于 procfs 和 sysfs,是一种虚拟文件系统。并且 cgroupfs 是可以挂载的,默认情况下挂载在 /sys/fs/cgroup 目录。
docker 默认的 Cgroup Driver 是 cgroupfs

$ docker info | grep cgroup
Cgroup Driver: cgroupfs

什么是systemd

systemd 也是对于 Cgroup 接口的一个封装,systemd 以 PID 1 的形式在系统启动的时候运行,并提供了一套系统管理守护程序、库和实用程序,用来管理 Linux 计算机操作系统资源。

ubuntu 系统,debian 系统,centos7 系统,都是使用 systemd 初始化系统的,通过将 cgroup 层级系统与 systemd 单位树捆绑,默认情况下 systemd 默认会将所有资源划分为 3 个子 cgroup:System, User 和 Machine,来为 cgroup 树提供统一结构,每一个 cgroup 都是一个 slice,每个 slice 都可以有自己的子 slice。

Cgroup子系统介绍

cpu子系统

完全公平调度(CFS调度策略)

    1. 按权重比例设定 CPU 的分配

      cpu.shares:设定一个整数(必须大于等于 2)表示相对权重,最后除以权重总和算出相对比例,按比例分配 CPU 时间。(如 cgroup A 设置 100,cgroup B 设置 300,那么 cgroup A 中的 task 运行 25% 的 CPU 时间。对于一个 4 核 CPU 的系统来说,cgroup A 中的 task 可以 100% 占有某一个 CPU,这个比例是相对整体的一个值。)

      ~]# echo 250 > /cgroup/cpu/test1/cpu.shares
      ~]# echo 750 > /cgroup/cpu/test2/cpu.shares

    2. 按cpu周期使用时间来绝对限制

      cpu.cfs_period_us:设定周期时间,必须与cfs_quota_us配合使用。
      cpu.cfs_quota_us :设定周期内最多可使用的时间。这里的配置指 task 对单个 cpu 的使用上限,若cfs_quota_us是cfs_period_us的两倍,就表示在两个核上完全使用。数值范围为 1000 - 1000,000(微秒)
      cpu.cfs_quota_us = -1 表明对cgroup中的任务不做限制。

      ~]# echo -1 > /cgroup/cpu/test/cpu.cfs_quota_us

      ~]# echo 10000 > /cgroup/cpu/test/cpu.cfs_quota_us
      ~]# echo 10000 > /cgroup/cpu/test/cpu.cfs_period_us

      ~]# echo 10000 > /cgroup/cpu/test/cpu.cfs_quota_us
      ~]# echo 100000 > /cgroup/cpu/test/cpu.cfs_period_us

      ~]# echo 200000 > /cgroup/cpu/test/cpu.cfs_quota_us
      ~]# echo 100000 > /cgroup/cpu/test/cpu.cfs_period_us

实时调度(Real-Time Scheduler)

RT调度策略下的配置实时调度策略与公平调度策略中的按周期分配时间的方法类似,也是在周期内分配一个固定的运行时间。

    1. cpu.rt_period_us :实时调度策略设定周期时间。

    2. cpu.rt_runtime_us:实时调度策略设定周期中的运行时间。

cpuacct 子系统

这个子系统的配置是cpu子系统的补充,提供对 CPU 资源用量的统计,时间单位都是纳秒。

    1. cpuacct.stat  统计 cgroup 中所有 task 的用户态和内核态分别使用 cpu 的时长长
      ~]# cat cpuacct.stat
      user 136959
      system 6948
    2. cpuacct.usage 统计 cgroup 中所有 task 的 cpu 使用时长

      ~]# cat cpuacct.usage
      1530639176290

    3. cpuacct.usage_percpu 统计 cgroup 中所有 task 使用每个 cpu 的时长 

      ~]# cat cpuacct.usage_percpu
      772766441190 760043948502

 

重置该group下的cpu统计:

~]# echo 0 > /cgroup/cpuacct/cpuacct.usage

cpuset 子系统

为 task 分配独立 CPU 资源的子系统,参数较多,这里只选讲两个必须配置的参数,同时 Docker 中目前也只用到这两个。
cpuset.cpus:在这个文件中填写 cgroup 可使用的 CPU 编号,如0-2,16代表 0、1、2 和 16 这 4 个 CPU。
cpuset.mems:与 CPU 类似,表示 cgroup 可使用的memory node,格式同上

memory 子系统

  1. 限额类
    memory.limit_bytes:强制限制最大内存使用量,单位有k、m、g三种,填-1则代表无限制。
    memory.soft_limit_bytes:软限制,只有比强制限制设置的值小时才有意义。填写格式同上。当整体内存紧张的情况下,task 获取的内存就被限制在软限制额度之内。
    memory.memsw.limit_bytes:设定最大内存与 swap 区内存之和的用量限制。填写格式同上。
  2. 报警与自动控制
    memory.oom_control:改参数填 0 或 1, 0表示开启,当 cgroup 中的进程使用资源超过界限时立即杀死进程,1表示不启用。

  3. 统计与监控类
    memory.usage_bytes:报告该 cgroup 中进程使用的当前总内存用量(以字节为单位)
    memory.max_usage_bytes:报告该 cgroup 中进程使用的最大内存用量
    memory.failcnt:报告内存达到在 memory.limit_in_bytes设定的限制值的次数
    memory.stat:包含大量的内存统计数据。
    cache:页缓存,包括 tmpfs(shmem),单位为字节。
    rss:匿名和 swap 缓存,不包括 tmpfs(shmem),单位为字节。
    mapped_file:memory-mapped 映射的文件大小,包括 tmpfs(shmem),单位为字节
    pgpgin:存入内存中的页数
    pgpgout:从内存中读出的页数
    swap:swap 用量,单位为字节
    active_anon:在活跃的最近最少使用(least-recently-used,LRU)列表中的匿名和 swap 缓存,包括 tmpfs(shmem),单位为字节
    inactive_anon:不活跃的 LRU 列表中的匿名和 swap 缓存,包括 tmpfs(shmem),单位为字节
    active_file:活跃 LRU 列表中的 file-backed 内存,以字节为单位
    inactive_file:不活跃 LRU 列表中的 file-backed 内存,以字节为单位
    unevictable:无法再生的内存,以字节为单位
    hierarchical_memory_limit:包含 memory cgroup 的层级的内存限制,单位为字节
    hierarchical_memsw_limit:包含 memory cgroup 的层级的内存加 swap 限制,单位为字节

blkio 子系统

  1. 按权重分配块设备 IO 资源

    blkio.weight:填写 100-1000 的一个整数值,作为相对权重比率,作为通用的设备分配比。
    blkio.weight_device: 针对特定设备的权重比,写入格式为device_types:node_numbers weight,空格前的参数段指定设备,weight参数与blkio.weight相同并覆盖原有的通用分配比。

    ~]# echo 500 > blkio.weight
    ~]# echo 8:0 500 > blkio.weight_device

  2. 设定绝对资源使用上限

    blkio.throttle.read_bps_device:按每秒读取块设备的数据量设定上限,格式 device_types:node_numbers bytes_per_second。
    blkio.throttle.write_bps_device:按每秒写入块设备的数据量设定上限,格式 device_types:node_numbers bytes_per_second。
    blkio.throttle.read_iops_device:按每秒读操作次数设定上限,格式 device_types:node_numbers operations_per_second。
    blkio.throttle.write_iops_device:按每秒写操作次数设定上限,格式 device_types:node_numbers operations_per_second。

    ~]# echo "8:0 10485760" > /cgroup/blkio/test/blkio.throttle.read_bps_device
    ~]# echo "8:0 10" > /cgroup/blkio/test/blkio.throttle.read_iops_device
    ~]# echo "8:0 10485760" > /cgroup/blkio/test/blkio.throttle.write_bps_device
    ~]# echo "8:0 10" > /cgroup/blkio/test/blkio.throttle.write_iops_device

     

  3. 针对特定操作 (read, write, sync, 或 async) 设定读写速度上限

    blkio.throttle.io_serviced:针对特定操作按每秒操作次数设定上限,格式 device_types:node_numbers operation operations_per_second
    blkio.throttle.io_service_bytes:针对特定操作按每秒数据量设定上限,格式 device_types:node_numbers operation bytes_per_second

  4. 统计与监控,统计、监控进程的 io 情况。

    blkio.reset_stats:重置统计信息,写入一个 int 值即可。
    blkio.time:统计 cgroup 对设备的访问时间,按格式device_types:node_numbers milliseconds读取信息即可,以下类似。
    blkio.io_serviced:统计 cgroup 对特定设备的 IO 操作(包括 read、write、sync 及 async)次数,格式device_types:node_numbers operation number
    blkio.sectors:统计 cgroup 对设备扇区访问次数,格式 device_types:node_numbers sector_count
    blkio.io_service_bytes:统计 cgroup 对特定设备 IO 操作(包括 read、write、sync 及 async)的数据量,格式device_types:node_numbers operation bytes
    blkio.io_queued:统计 cgroup 的队列中对 IO 操作(包括 read、write、sync 及 async)的请求次数,格式number operation
    blkio.io_service_time:统计 cgroup 对特定设备的 IO 操作(包括 read、write、sync 及 async)时间 (单位为 ns),格式device_types:node_numbers operation time
    blkio.io_merged:统计 cgroup 将 BIOS 请求合并到 IO 操作(包括 read、write、sync 及 async)请求的次数,格式number operation
    blkio.io_wait_time:统计 cgroup 在各设备中各类型IO 操作(包括 read、write、sync 及 async)在队列中的等待时间(单位 ns),格式device_types:node_numbers operation time

devices 子系统

device - 限制 task 对 device 的使用

    • devices.allow:白名单
      语法 type device_types:node_numbers access type
      type有三种类型:b(块设备)、c(字符设备)、a(全部设备)
      access也有三种方式:r(读)、w(写)、m(创建)。
    • devices.deny:禁止名单,语法格式同上。
    • devices.list:统计在该cgroup中任务设置了哪些访问控制的设备以及读写权限。

freezer 子系统

freezer 用来控制cgroup中tasks进程的暂停和恢复,只有一个属性,表示进程的状态,把 task 放到 freezer 所在的 cgroup,再把 state 改为 FROZEN,就可以暂停进程。

freezer. state 只能在非root cgroup中使用,有以下三种状态:

FROZEN — 表明挂起该进程组中的进程。
FREEZING — 表明正在停止该进程组中的进程,是一个中间状态,只读,不能写。
THAWED — 表明恢复该进程组中的进程状态。

net_cls 子系统

net_cls子系统是用来控制进程使用网络资源的,它并不直接控制网络读写,而是给网络包打上一个标记,具体的网络包控制由tc机制来处理。

tc (Traffic Controller)可以为不同的cgroup报文分配不同的优先级或者可以使用标记对网络包执行操作。

使用net_cls子系统控制网络资源的使用,主要有以下三步:

    1. 创建绑定net_cls的cgroup
    2. 设置该cgroup的classid
    3. 配置符合classid的tc策略配置

如:

mkdir /sys/fs/cgroup/net_cls
mount -t cgroup -onet_cls net_cls /sys/fs/cgroup/net_cls
mkdir /sys/fs/cgroup/net_cls/0
echo 0x100001 > /sys/fs/cgroup/net_cls/0/net_cls.classid

setting a 10:1 handle:
cat /sys/fs/cgroup/net_cls/0/net_cls.classid
1048577
setting a 10:1 handle:
cat /sys/fs/cgroup/net_cls/0/net_cls.classid
1048577

configuring tc:
tc qdisc add dev eth0 root handle 10: htb
tc class add dev eth0 parent 10: classid 10:1 htb rate 40mbit
creating traffic class 10:1:


tc filter add dev eth0 parent 10: protocol ip prio 10 handle 1: cgroup

configuring iptables, basic example:
iptables -A OUTPUT -m cgroup ! --cgroup 0x100001 -j DROP

Cgroup子系统的使用

系统systemd对cgroup的使用

Centos 7中,系统会自动将cgroup文件系统挂在到/sys/fs/cgroup目录下。

如果我们将系统的资源看成一块馅饼,那么所有资源默认会被划分为 3 个 cgroup:System, User 和 Machine。每一个 cgroup 都是一个 slice,每个 slice 都可以有自己的子 slice,如下图所示:

 

系统默认创建了 3 个顶级 slice(System, User 和 Machine),每个 slice 都会获得相同的 CPU 使用时间(仅在 CPU 繁忙时生效),如果 user.slice 想获得 100% 的 CPU 使用时间,而此时 CPU 比较空闲,那么 user.slice 就能够如愿以偿。这三种顶级 slice 的含义如下:

system.slice -- 所有系统 service 的默认位置。
user.slice -- 所有用户会话的默认位置。每个用户会话都会在该 slice 下面创建一个子 slice,如果同一个用户多次登录该系统,仍然会使用相同的子 slice。
machine.slice -- 所有虚拟机和 Linux 容器的默认位置。

控制 CPU 资源使用的其中一种方法是 shares。shares 用来设置 CPU 的相对值(你可以理解为权重),并且是针对所有的 CPU(内核),默认值是 1024。因此在上图中,httpd, sshd, crond 和 gdm 的 CPU shares 均为 1024,System, User 和 Machine 的 CPU shares 也是 1024。

假设该系统上运行了 4 个 service,登录了两个用户,还运行了一个虚拟机。同时假设每个进程都要求使用尽可能多的 CPU 资源。

system.slice 会获得 33.333% 的 CPU 使用时间,其中每个 service 都会从 system.slice 分配的资源中获得 1/4 的 CPU 使用时间,即 8.25% 的 CPU 使用时间。
user.slice 会获得 33.333% 的 CPU 使用时间,其中每个登录的用户都会获得 16.5% 的 CPU 使用时间。假设有两个用户:tom 和 jack,如果 tom 注销登录或者杀死该用户会话下的所有进程,jack 就能够使用 33.333% 的 CPU 使用时间。
machine.slice 会获得 33.333% 的 CPU 使用时间,如果虚拟机被关闭或处于 idle 状态,那么 system.slice 和 user.slice 就会从这 33.333% 的 CPU 资源里分别获得 50% 的 CPU 资源,然后均分给它们的子 slice。


如果想严格控制 CPU 资源,设置 CPU 资源的使用上限,可以通过以下两个参数来设置:
cpu.cfs_period_us = 统计CPU使用时间的周期,单位是微秒(us)
cpu.cfs_quota_us = 周期内允许占用的CPU时间(指单核的时间,多核则需要在设置时累加)

 

systemctl 可以通过 CPUQuota 参数来设置 CPU 资源的使用上限,使用 tab 补全还可以设置其他资源限制。

$ systemctl set-property user-1000.slice CPUQuota=20%

 

如果你想通过配置文件来设置 cgroup,service 可以直接在 /etc/systemd/system/xxx.service.d 目录下面创建相应的配置文件,slice 可以直接在 /run/systemd/system/xxx.slice.d 目录下面创建相应的配置文件。事实上通过 systemctl 命令行工具设置 cgroup 也会写到该目录下的配置文件中:

查看对应的 cgroup 参数,这表示用户 user-42 在一个使用周期内(100 毫秒)可以使用 20 毫秒的 CPU 时间。不管 CPU 是否空闲,该用户使用的 CPU 资源都不会超过这个限制。

CPUQuota 的值可以超过 100%,例如:如果系统的 CPU 是多核,且 CPUQuota 的值为 200%,那么该 slice 就能够使用 2 核的 CPU 时间。

使用systemd控制cpu cgroup实战

写一个消耗cpu的shell脚本

#!/bin/sh

#filename cpu_usage.sh

FILE_NAME=`basename $0`
cpunum=$2
pid_array=()

function usage()
{
        echo "Usage: $FILE_NAME consume cpu_number|release ------the value of cpu_number is an interger such as 1,2,3"
        echo "Example: $FILE_NAME consume 12"
        echo "         $FILE_NAME release"
}

function endless_loop()
{
        echo -ne "i=0;
        while true
        do
                i=i+100;
                i=100
        done" | /bin/bash &
}

function consume()
{
        for i in `seq $1`
        do
                endless_loop
                pid_array[$i]=$!
        done
        echo "consume cpu resources process ids are: ${pid_array[*]}"
        sleep 36000
}

function release()
{
        for pid in $(ps -ef |grep /bin/bash |grep -v grep | awk '{print $2}' | xargs)
                do
                        kill -9 $pid
                done
}

function main()
{
        case "$1" in
                consume) consume $cpunum;;
                release) release;;
                *) usage; exit 1;;
        esac
}

main $*

 

创建slice,service

编辑文件 /etc/systemd/system/cpu_usage.service

mv  cpu_usage.sh /usr/libexec/cpu_usage.sh && chmod +x /usr/libexec/cpu_usage.sh

[Unit]
Description=cpu_usage
ConditionFileIsExecutable=/usr/libexec/cpu_usage.sh
[Service]
Type=simple
#cat /proc/cpuinfo |grep processor |grep -v grep |wc -l
Environment="arg=consume 2"
ExecStart=/usr/libexec/cpu_usage.sh $arg
[Install]
WantedBy=multi-user.target

启动cpu_usage 服务

         

观察测试结果

  • 发现cpu_usage服务 进程3886 和 3888跑满了2个 cpu
  • cpu_usage.service是通过systemd启动的,所以,执行systemd-cgls,将会在system.slice下面。
  • 如果不通过systemd unit service而直接运行,那么执行systemd-cgls,这个进程将属于cgroup树的user.slice下。

使用systemd 对进程分组

  • 修改服务所属的slice


  • 此时查看cpu_usage所属的服务slice


  • 此时,将big_data.slice纳入了层级,但是big_data.slice下的服务并没有应用任何子系统。

    [Unit]
    Description=cpu_usage
    ConditionFileIsExecutable=/usr/libexec/cpu_usage.sh
    [Service]
    Type=simple
    Slice=big_data.slice
    #cat /proc/cpuinfo |grep processor |grep -v grep |wc -l
    Environment="arg=consume 2"
    ExecStart=/usr/libexec/cpu_usage.sh $arg
    [Install]
    WantedBy=multi-user.target



  • 在/etc/systemd/system/cpu_usage.service中添加CPUAccounting=yes
    意思是big_data.slice下的cpu_usage.service,都将开始使用cgroup的cpu,cpuacct这个资源管理。

    [Unit]
    Description=cpu_usage
    ConditionFileIsExecutable=/usr/libexec/cpu_usage.sh
    [Service]
    Type=simple
    Slice=big_data.slice
    CPUAccounting=yes
    #cat /proc/cpuinfo |grep processor |grep -v grep |wc -l
    Environment="arg=consume 2"
    ExecStart=/usr/libexec/cpu_usage.sh $arg
    [Install]
    WantedBy=multi-user.target



限制cpu_usage的cpu使用到50%

  • 目前cgroup 对 cpu没有限制
  • 现在将cpu使用率降到单核的 50%,由于cpu_uage有两个进程在消耗cpu,每个25%,那2个就是单核的50%

    [Unit]
    Description=cpu_usage
    ConditionFileIsExecutable=/usr/libexec/cpu_usage.sh
    [Service]
    Type=simple
    Slice=big_data.slice
    CPUAccounting=yes
    CPUQuota=50%
    #cat /proc/cpuinfo |grep processor |grep -v grep |wc -l
    Environment="arg=consume 2"
    ExecStart=/usr/libexec/cpu_usage.sh $arg
    [Install]
    WantedBy=multi-user.target




使用systemd控制mem cgroup实战

写一个消耗mem的脚本

#!/bin/bash

#file_name mem_usage.sh
function consume() 
{
        x="a"
        while [ true ]; do
                x=$x$x
        done;
}

function main()
{
        consume
}
main $*

创建slice,service

编辑文件 /etc/systemd/system/mem_usage.service

mv cpu_usage.sh /usr/libexec/mem_usage.sh && chmod +x /usr/libexec/mem_usage.sh

[Unit]
Description=mem_usage
ConditionFileIsExecutable=/usr/libexec/mem_usage.sh
[Service]
Type=simple
Slice=big_data.slice
Environment="arg=consume 500M"
ExecStart=/usr/libexec/mem_usage.sh $arg
MemoryLimit=200M
TasksAccounting=yes
BlockIOAccounting=yes
[Install]
WantedBy=multi-user.target

启动mem_usage服务

查看mem_usage服务的内存始终不会超过200M,经过一段时间后进程 6501 被kill掉,cgroup目录/sys/fs/cgroup/memory/big_data.slice/mem_usage.service 也不存在了。

cpuset cgroup独占核实战

指定某些进程独占使用这些核心,其他进程组不能使用,由于systemd不支持cpuset子系统,来通过用户进程方式运行shell脚本。

查看节点cpu资源情况

[root@master cgroup]# cat /proc/cpuinfo  |grep -e "physical id" -e  "core id" -e  "cpu cores"
physical id     : 0
core id         : 0
cpu cores       : 2
physical id     : 0
core id         : 1
cpu cores       : 2

可以看到目前节点上有一个物理核,该物理核开启超线程,模拟出两个逻辑核,我们让脚本进程跑在第一个核上。

在没有配置独占核之前,运行脚本:

chmod +x cpu_usage.sh

./cpu_useage.sh consume 2

可以看到脚本中的两个子进程将两个cpu都打满了。

限制脚本的两个cpu到逻辑核0上

mkdir -p /sys/fs/cgroup/cpuset/cpu_usage
echo "0" > /sys/fs/cgroup/cpuset/cpu_usage/cpuset.cpus
#此处为numa节点编号
echo "0" > /sys/fs/cgroup/cpuset/cpu_usage/cpuset.mems


echo "3553" > /sys/fs/cgroup/cpuset/cpu_usage/tasks
echo "3555" > /sys/fs/cgroup/cpuset/cpu_usage/tasks

可以看到 进程3553 和 3555 都跑在了逻辑核0上,并且cpu0使用率为100,cpu1上为0,从而证实进程组被限制在了逻辑核cpu0上。

参考资料

  1. https://www.kernel.org/doc/html/v5.3/admin-guide/cgroup-v1/net_cls.html
0条评论
0 / 1000
l****n
3文章数
0粉丝数
l****n
3 文章 | 0 粉丝
l****n
3文章数
0粉丝数
l****n
3 文章 | 0 粉丝
原创

cgroup技术介绍

2023-05-30 05:50:33
491
0

背景介绍

YARN是一个资源管理、任务调度的框架,yarn中的任务也需要像k8s一样支持对任务的资源进行限制,所以需要对cgroup技术做了些调研。

Cgroup的简介

Cgroup 是一个 Linux 内核特性,合并到2008年发布的 2.6.24 内核版本中,对一组进程的资源使用(CPU、内存、磁盘 I/O 和网络等)进行限制、审计和隔离。它可以根据需求把一系列系统任务及其子任务整合 (或分隔) 到按资源划分到等级不同的组内,从而为系统资源管理提供一个统一的框架。简单说,cgroups 可以限制、记录任务组所使用的物理资源。本质上来说,cgroups 是内核附加在程序上的一系列钩子 (hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。cgroups 已经成为很多技术的基础,特别是资源限制,比如 LXC、Docker、systemd。

Cgroup的组成与功能

Cgroup作用

资源限制(Resource limiting): Cgroups可以对进程组使用的资源总额进行限制。如对特定的进程进行内存使用上限限制,当超出上限时,会触发OOM。
优先级分配(Prioritization): 通过分配的CPU时间片数量及硬盘IO带宽大小,实际上就相当于控制了进程运行的优先级,如:可以使用cpu子系统为某个进程组分配特定cpu share。
资源统计(Accounting): Cgroups可以统计系统的资源使用量,如CPU使用时长、内存用量等等,这个功能非常适用于计费。
进程控制(Control:Cgroups可以对进程组执行挂起、恢复等操作。

Cgroups 几个核心概念

task: 在Cgroups中,task就是系统的一个进程。
subsystem: 一个 subsystem 就是一个内核模块,他被关联到一颗 cgroup 树之后, 就会在树的每个节点(进程组)上做具体的操作。subsystem 经常被称作 resource controller,因为它主要被用来调度或者限制每个进程组的资源

cgroup:控制组表示对这些任务进行怎样的资源管理策略,包含一个或多个子系统,一个任务可以加入某个cgroup,也可以从某个cgroup迁移到另外一个cgroup。
hierarchy: 一个 hierarchy 可以理解为一棵 cgroup 树,树的每个节点就是一个进程 组,每棵树都会与零到多个 subsystem 关联。在一颗树里面,会包含 Linux 系统中的所有 进程,但每个进程只能属于一个节点(进程组)。系统中可以有很多颗 cgroup 树,每棵树 都和不同的 subsystem 关联,一个进程可以属于多颗树,即一个进程可以属于多个进程 组,只是这些进程组和不同的 subsystem 关联。

Cgroup子系统

blkio -- 为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等)。
cpu --  使用调度程序提供对 CPU 的 cgroup 任务访问控制。
cpuacct -- 自动生成 cgroup 中任务所使用的 CPU 报告。
cpuset --  为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
devices -- 可允许或者拒绝 cgroup 中的任务访问设备。
freezer --  挂起或者恢复 cgroup 中的任务。
memory -- 设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。
net_cls -- 使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。

子系统,层级,控制组 和 Tasks 的关系

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

如下图将cpu和memory subsystems(或者任意多个subsystems)附加到同一个hierarchy。

 

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

如下图cpu subsystem已经附加到了hierarchy A,并且memory subsystem已经附加到了hierarchy B。因此cpu subsystem不能在附加到hierarchy B。

系统每次新建一个hierarchy时,该系统上的所有task默认构成了这个新建的hierarchy的初始化cgroup

这个cgroup也称为root cgroup,对于你创建的每个hierarchy,task只能存在于其中一个cgroup中,即一个task不能存在于同一个hierarchy的不同cgroup中,但是一个task可以存在在不同hierarchy中的多个cgroup中。如果操作时把一个task添加到同一个hierarchy中的另一个cgroup中,则会从第一个cgroup中移除。
如下图,cpu和 memory subsystem 被附加到 cpu_mem_cg 的 hierarchy。而net_cls subsystem被附加到net_cls hierarchy。并且httpd进程被同时加到了cpu_mem_cg hierarchy的cg1 cgroup中和net hierarchy的cg3 cgroup中。并通过两个hierarchy的subsystem分别对httpd进程进行cpu,memory及网络带宽的限制。

 

系统中的任何一个task(Linux中的进程)fork自己创建一个子task(子进程)时,子task会自动的继承父task cgroup的关系。

在同一个cgroup中,子task可以根据需要移到其它不同的cgroup中,父子task之间是相互独立不依赖的。
如下图,httpd进程在cpu_and_mem hierarchy的/cg1 cgroup中并把PID 4537写到该cgroup的tasks中,之后httpd(PID=4537)进程fork一个子进程httpd(PID=4840)与其父进程在同一个hierarchy的统一个cgroup中,但是由于父task和子task之间的关系独立不依赖的,所以子task可以移到其它的cgroup中。

Cgroup,cgroupfs,systemd的关系

Cgroupfs 和 systemd 都是对 cgroup 的封装实现,给用户提供了使用的命令和接口。

什么是cgroupfs

Cgroup 提供了一个原生接口并通过 cgroupfs 提供,cgroupfs 就是 Cgroup 的一个接口的封装。类似于 procfs 和 sysfs,是一种虚拟文件系统。并且 cgroupfs 是可以挂载的,默认情况下挂载在 /sys/fs/cgroup 目录。
docker 默认的 Cgroup Driver 是 cgroupfs

$ docker info | grep cgroup
Cgroup Driver: cgroupfs

什么是systemd

systemd 也是对于 Cgroup 接口的一个封装,systemd 以 PID 1 的形式在系统启动的时候运行,并提供了一套系统管理守护程序、库和实用程序,用来管理 Linux 计算机操作系统资源。

ubuntu 系统,debian 系统,centos7 系统,都是使用 systemd 初始化系统的,通过将 cgroup 层级系统与 systemd 单位树捆绑,默认情况下 systemd 默认会将所有资源划分为 3 个子 cgroup:System, User 和 Machine,来为 cgroup 树提供统一结构,每一个 cgroup 都是一个 slice,每个 slice 都可以有自己的子 slice。

Cgroup子系统介绍

cpu子系统

完全公平调度(CFS调度策略)

    1. 按权重比例设定 CPU 的分配

      cpu.shares:设定一个整数(必须大于等于 2)表示相对权重,最后除以权重总和算出相对比例,按比例分配 CPU 时间。(如 cgroup A 设置 100,cgroup B 设置 300,那么 cgroup A 中的 task 运行 25% 的 CPU 时间。对于一个 4 核 CPU 的系统来说,cgroup A 中的 task 可以 100% 占有某一个 CPU,这个比例是相对整体的一个值。)

      ~]# echo 250 > /cgroup/cpu/test1/cpu.shares
      ~]# echo 750 > /cgroup/cpu/test2/cpu.shares

    2. 按cpu周期使用时间来绝对限制

      cpu.cfs_period_us:设定周期时间,必须与cfs_quota_us配合使用。
      cpu.cfs_quota_us :设定周期内最多可使用的时间。这里的配置指 task 对单个 cpu 的使用上限,若cfs_quota_us是cfs_period_us的两倍,就表示在两个核上完全使用。数值范围为 1000 - 1000,000(微秒)
      cpu.cfs_quota_us = -1 表明对cgroup中的任务不做限制。

      ~]# echo -1 > /cgroup/cpu/test/cpu.cfs_quota_us

      ~]# echo 10000 > /cgroup/cpu/test/cpu.cfs_quota_us
      ~]# echo 10000 > /cgroup/cpu/test/cpu.cfs_period_us

      ~]# echo 10000 > /cgroup/cpu/test/cpu.cfs_quota_us
      ~]# echo 100000 > /cgroup/cpu/test/cpu.cfs_period_us

      ~]# echo 200000 > /cgroup/cpu/test/cpu.cfs_quota_us
      ~]# echo 100000 > /cgroup/cpu/test/cpu.cfs_period_us

实时调度(Real-Time Scheduler)

RT调度策略下的配置实时调度策略与公平调度策略中的按周期分配时间的方法类似,也是在周期内分配一个固定的运行时间。

    1. cpu.rt_period_us :实时调度策略设定周期时间。

    2. cpu.rt_runtime_us:实时调度策略设定周期中的运行时间。

cpuacct 子系统

这个子系统的配置是cpu子系统的补充,提供对 CPU 资源用量的统计,时间单位都是纳秒。

    1. cpuacct.stat  统计 cgroup 中所有 task 的用户态和内核态分别使用 cpu 的时长长
      ~]# cat cpuacct.stat
      user 136959
      system 6948
    2. cpuacct.usage 统计 cgroup 中所有 task 的 cpu 使用时长

      ~]# cat cpuacct.usage
      1530639176290

    3. cpuacct.usage_percpu 统计 cgroup 中所有 task 使用每个 cpu 的时长 

      ~]# cat cpuacct.usage_percpu
      772766441190 760043948502

 

重置该group下的cpu统计:

~]# echo 0 > /cgroup/cpuacct/cpuacct.usage

cpuset 子系统

为 task 分配独立 CPU 资源的子系统,参数较多,这里只选讲两个必须配置的参数,同时 Docker 中目前也只用到这两个。
cpuset.cpus:在这个文件中填写 cgroup 可使用的 CPU 编号,如0-2,16代表 0、1、2 和 16 这 4 个 CPU。
cpuset.mems:与 CPU 类似,表示 cgroup 可使用的memory node,格式同上

memory 子系统

  1. 限额类
    memory.limit_bytes:强制限制最大内存使用量,单位有k、m、g三种,填-1则代表无限制。
    memory.soft_limit_bytes:软限制,只有比强制限制设置的值小时才有意义。填写格式同上。当整体内存紧张的情况下,task 获取的内存就被限制在软限制额度之内。
    memory.memsw.limit_bytes:设定最大内存与 swap 区内存之和的用量限制。填写格式同上。
  2. 报警与自动控制
    memory.oom_control:改参数填 0 或 1, 0表示开启,当 cgroup 中的进程使用资源超过界限时立即杀死进程,1表示不启用。

  3. 统计与监控类
    memory.usage_bytes:报告该 cgroup 中进程使用的当前总内存用量(以字节为单位)
    memory.max_usage_bytes:报告该 cgroup 中进程使用的最大内存用量
    memory.failcnt:报告内存达到在 memory.limit_in_bytes设定的限制值的次数
    memory.stat:包含大量的内存统计数据。
    cache:页缓存,包括 tmpfs(shmem),单位为字节。
    rss:匿名和 swap 缓存,不包括 tmpfs(shmem),单位为字节。
    mapped_file:memory-mapped 映射的文件大小,包括 tmpfs(shmem),单位为字节
    pgpgin:存入内存中的页数
    pgpgout:从内存中读出的页数
    swap:swap 用量,单位为字节
    active_anon:在活跃的最近最少使用(least-recently-used,LRU)列表中的匿名和 swap 缓存,包括 tmpfs(shmem),单位为字节
    inactive_anon:不活跃的 LRU 列表中的匿名和 swap 缓存,包括 tmpfs(shmem),单位为字节
    active_file:活跃 LRU 列表中的 file-backed 内存,以字节为单位
    inactive_file:不活跃 LRU 列表中的 file-backed 内存,以字节为单位
    unevictable:无法再生的内存,以字节为单位
    hierarchical_memory_limit:包含 memory cgroup 的层级的内存限制,单位为字节
    hierarchical_memsw_limit:包含 memory cgroup 的层级的内存加 swap 限制,单位为字节

blkio 子系统

  1. 按权重分配块设备 IO 资源

    blkio.weight:填写 100-1000 的一个整数值,作为相对权重比率,作为通用的设备分配比。
    blkio.weight_device: 针对特定设备的权重比,写入格式为device_types:node_numbers weight,空格前的参数段指定设备,weight参数与blkio.weight相同并覆盖原有的通用分配比。

    ~]# echo 500 > blkio.weight
    ~]# echo 8:0 500 > blkio.weight_device

  2. 设定绝对资源使用上限

    blkio.throttle.read_bps_device:按每秒读取块设备的数据量设定上限,格式 device_types:node_numbers bytes_per_second。
    blkio.throttle.write_bps_device:按每秒写入块设备的数据量设定上限,格式 device_types:node_numbers bytes_per_second。
    blkio.throttle.read_iops_device:按每秒读操作次数设定上限,格式 device_types:node_numbers operations_per_second。
    blkio.throttle.write_iops_device:按每秒写操作次数设定上限,格式 device_types:node_numbers operations_per_second。

    ~]# echo "8:0 10485760" > /cgroup/blkio/test/blkio.throttle.read_bps_device
    ~]# echo "8:0 10" > /cgroup/blkio/test/blkio.throttle.read_iops_device
    ~]# echo "8:0 10485760" > /cgroup/blkio/test/blkio.throttle.write_bps_device
    ~]# echo "8:0 10" > /cgroup/blkio/test/blkio.throttle.write_iops_device

     

  3. 针对特定操作 (read, write, sync, 或 async) 设定读写速度上限

    blkio.throttle.io_serviced:针对特定操作按每秒操作次数设定上限,格式 device_types:node_numbers operation operations_per_second
    blkio.throttle.io_service_bytes:针对特定操作按每秒数据量设定上限,格式 device_types:node_numbers operation bytes_per_second

  4. 统计与监控,统计、监控进程的 io 情况。

    blkio.reset_stats:重置统计信息,写入一个 int 值即可。
    blkio.time:统计 cgroup 对设备的访问时间,按格式device_types:node_numbers milliseconds读取信息即可,以下类似。
    blkio.io_serviced:统计 cgroup 对特定设备的 IO 操作(包括 read、write、sync 及 async)次数,格式device_types:node_numbers operation number
    blkio.sectors:统计 cgroup 对设备扇区访问次数,格式 device_types:node_numbers sector_count
    blkio.io_service_bytes:统计 cgroup 对特定设备 IO 操作(包括 read、write、sync 及 async)的数据量,格式device_types:node_numbers operation bytes
    blkio.io_queued:统计 cgroup 的队列中对 IO 操作(包括 read、write、sync 及 async)的请求次数,格式number operation
    blkio.io_service_time:统计 cgroup 对特定设备的 IO 操作(包括 read、write、sync 及 async)时间 (单位为 ns),格式device_types:node_numbers operation time
    blkio.io_merged:统计 cgroup 将 BIOS 请求合并到 IO 操作(包括 read、write、sync 及 async)请求的次数,格式number operation
    blkio.io_wait_time:统计 cgroup 在各设备中各类型IO 操作(包括 read、write、sync 及 async)在队列中的等待时间(单位 ns),格式device_types:node_numbers operation time

devices 子系统

device - 限制 task 对 device 的使用

    • devices.allow:白名单
      语法 type device_types:node_numbers access type
      type有三种类型:b(块设备)、c(字符设备)、a(全部设备)
      access也有三种方式:r(读)、w(写)、m(创建)。
    • devices.deny:禁止名单,语法格式同上。
    • devices.list:统计在该cgroup中任务设置了哪些访问控制的设备以及读写权限。

freezer 子系统

freezer 用来控制cgroup中tasks进程的暂停和恢复,只有一个属性,表示进程的状态,把 task 放到 freezer 所在的 cgroup,再把 state 改为 FROZEN,就可以暂停进程。

freezer. state 只能在非root cgroup中使用,有以下三种状态:

FROZEN — 表明挂起该进程组中的进程。
FREEZING — 表明正在停止该进程组中的进程,是一个中间状态,只读,不能写。
THAWED — 表明恢复该进程组中的进程状态。

net_cls 子系统

net_cls子系统是用来控制进程使用网络资源的,它并不直接控制网络读写,而是给网络包打上一个标记,具体的网络包控制由tc机制来处理。

tc (Traffic Controller)可以为不同的cgroup报文分配不同的优先级或者可以使用标记对网络包执行操作。

使用net_cls子系统控制网络资源的使用,主要有以下三步:

    1. 创建绑定net_cls的cgroup
    2. 设置该cgroup的classid
    3. 配置符合classid的tc策略配置

如:

mkdir /sys/fs/cgroup/net_cls
mount -t cgroup -onet_cls net_cls /sys/fs/cgroup/net_cls
mkdir /sys/fs/cgroup/net_cls/0
echo 0x100001 > /sys/fs/cgroup/net_cls/0/net_cls.classid

setting a 10:1 handle:
cat /sys/fs/cgroup/net_cls/0/net_cls.classid
1048577
setting a 10:1 handle:
cat /sys/fs/cgroup/net_cls/0/net_cls.classid
1048577

configuring tc:
tc qdisc add dev eth0 root handle 10: htb
tc class add dev eth0 parent 10: classid 10:1 htb rate 40mbit
creating traffic class 10:1:


tc filter add dev eth0 parent 10: protocol ip prio 10 handle 1: cgroup

configuring iptables, basic example:
iptables -A OUTPUT -m cgroup ! --cgroup 0x100001 -j DROP

Cgroup子系统的使用

系统systemd对cgroup的使用

Centos 7中,系统会自动将cgroup文件系统挂在到/sys/fs/cgroup目录下。

如果我们将系统的资源看成一块馅饼,那么所有资源默认会被划分为 3 个 cgroup:System, User 和 Machine。每一个 cgroup 都是一个 slice,每个 slice 都可以有自己的子 slice,如下图所示:

 

系统默认创建了 3 个顶级 slice(System, User 和 Machine),每个 slice 都会获得相同的 CPU 使用时间(仅在 CPU 繁忙时生效),如果 user.slice 想获得 100% 的 CPU 使用时间,而此时 CPU 比较空闲,那么 user.slice 就能够如愿以偿。这三种顶级 slice 的含义如下:

system.slice -- 所有系统 service 的默认位置。
user.slice -- 所有用户会话的默认位置。每个用户会话都会在该 slice 下面创建一个子 slice,如果同一个用户多次登录该系统,仍然会使用相同的子 slice。
machine.slice -- 所有虚拟机和 Linux 容器的默认位置。

控制 CPU 资源使用的其中一种方法是 shares。shares 用来设置 CPU 的相对值(你可以理解为权重),并且是针对所有的 CPU(内核),默认值是 1024。因此在上图中,httpd, sshd, crond 和 gdm 的 CPU shares 均为 1024,System, User 和 Machine 的 CPU shares 也是 1024。

假设该系统上运行了 4 个 service,登录了两个用户,还运行了一个虚拟机。同时假设每个进程都要求使用尽可能多的 CPU 资源。

system.slice 会获得 33.333% 的 CPU 使用时间,其中每个 service 都会从 system.slice 分配的资源中获得 1/4 的 CPU 使用时间,即 8.25% 的 CPU 使用时间。
user.slice 会获得 33.333% 的 CPU 使用时间,其中每个登录的用户都会获得 16.5% 的 CPU 使用时间。假设有两个用户:tom 和 jack,如果 tom 注销登录或者杀死该用户会话下的所有进程,jack 就能够使用 33.333% 的 CPU 使用时间。
machine.slice 会获得 33.333% 的 CPU 使用时间,如果虚拟机被关闭或处于 idle 状态,那么 system.slice 和 user.slice 就会从这 33.333% 的 CPU 资源里分别获得 50% 的 CPU 资源,然后均分给它们的子 slice。


如果想严格控制 CPU 资源,设置 CPU 资源的使用上限,可以通过以下两个参数来设置:
cpu.cfs_period_us = 统计CPU使用时间的周期,单位是微秒(us)
cpu.cfs_quota_us = 周期内允许占用的CPU时间(指单核的时间,多核则需要在设置时累加)

 

systemctl 可以通过 CPUQuota 参数来设置 CPU 资源的使用上限,使用 tab 补全还可以设置其他资源限制。

$ systemctl set-property user-1000.slice CPUQuota=20%

 

如果你想通过配置文件来设置 cgroup,service 可以直接在 /etc/systemd/system/xxx.service.d 目录下面创建相应的配置文件,slice 可以直接在 /run/systemd/system/xxx.slice.d 目录下面创建相应的配置文件。事实上通过 systemctl 命令行工具设置 cgroup 也会写到该目录下的配置文件中:

查看对应的 cgroup 参数,这表示用户 user-42 在一个使用周期内(100 毫秒)可以使用 20 毫秒的 CPU 时间。不管 CPU 是否空闲,该用户使用的 CPU 资源都不会超过这个限制。

CPUQuota 的值可以超过 100%,例如:如果系统的 CPU 是多核,且 CPUQuota 的值为 200%,那么该 slice 就能够使用 2 核的 CPU 时间。

使用systemd控制cpu cgroup实战

写一个消耗cpu的shell脚本

#!/bin/sh

#filename cpu_usage.sh

FILE_NAME=`basename $0`
cpunum=$2
pid_array=()

function usage()
{
        echo "Usage: $FILE_NAME consume cpu_number|release ------the value of cpu_number is an interger such as 1,2,3"
        echo "Example: $FILE_NAME consume 12"
        echo "         $FILE_NAME release"
}

function endless_loop()
{
        echo -ne "i=0;
        while true
        do
                i=i+100;
                i=100
        done" | /bin/bash &
}

function consume()
{
        for i in `seq $1`
        do
                endless_loop
                pid_array[$i]=$!
        done
        echo "consume cpu resources process ids are: ${pid_array[*]}"
        sleep 36000
}

function release()
{
        for pid in $(ps -ef |grep /bin/bash |grep -v grep | awk '{print $2}' | xargs)
                do
                        kill -9 $pid
                done
}

function main()
{
        case "$1" in
                consume) consume $cpunum;;
                release) release;;
                *) usage; exit 1;;
        esac
}

main $*

 

创建slice,service

编辑文件 /etc/systemd/system/cpu_usage.service

mv  cpu_usage.sh /usr/libexec/cpu_usage.sh && chmod +x /usr/libexec/cpu_usage.sh

[Unit]
Description=cpu_usage
ConditionFileIsExecutable=/usr/libexec/cpu_usage.sh
[Service]
Type=simple
#cat /proc/cpuinfo |grep processor |grep -v grep |wc -l
Environment="arg=consume 2"
ExecStart=/usr/libexec/cpu_usage.sh $arg
[Install]
WantedBy=multi-user.target

启动cpu_usage 服务

         

观察测试结果

  • 发现cpu_usage服务 进程3886 和 3888跑满了2个 cpu
  • cpu_usage.service是通过systemd启动的,所以,执行systemd-cgls,将会在system.slice下面。
  • 如果不通过systemd unit service而直接运行,那么执行systemd-cgls,这个进程将属于cgroup树的user.slice下。

使用systemd 对进程分组

  • 修改服务所属的slice


  • 此时查看cpu_usage所属的服务slice


  • 此时,将big_data.slice纳入了层级,但是big_data.slice下的服务并没有应用任何子系统。

    [Unit]
    Description=cpu_usage
    ConditionFileIsExecutable=/usr/libexec/cpu_usage.sh
    [Service]
    Type=simple
    Slice=big_data.slice
    #cat /proc/cpuinfo |grep processor |grep -v grep |wc -l
    Environment="arg=consume 2"
    ExecStart=/usr/libexec/cpu_usage.sh $arg
    [Install]
    WantedBy=multi-user.target



  • 在/etc/systemd/system/cpu_usage.service中添加CPUAccounting=yes
    意思是big_data.slice下的cpu_usage.service,都将开始使用cgroup的cpu,cpuacct这个资源管理。

    [Unit]
    Description=cpu_usage
    ConditionFileIsExecutable=/usr/libexec/cpu_usage.sh
    [Service]
    Type=simple
    Slice=big_data.slice
    CPUAccounting=yes
    #cat /proc/cpuinfo |grep processor |grep -v grep |wc -l
    Environment="arg=consume 2"
    ExecStart=/usr/libexec/cpu_usage.sh $arg
    [Install]
    WantedBy=multi-user.target



限制cpu_usage的cpu使用到50%

  • 目前cgroup 对 cpu没有限制
  • 现在将cpu使用率降到单核的 50%,由于cpu_uage有两个进程在消耗cpu,每个25%,那2个就是单核的50%

    [Unit]
    Description=cpu_usage
    ConditionFileIsExecutable=/usr/libexec/cpu_usage.sh
    [Service]
    Type=simple
    Slice=big_data.slice
    CPUAccounting=yes
    CPUQuota=50%
    #cat /proc/cpuinfo |grep processor |grep -v grep |wc -l
    Environment="arg=consume 2"
    ExecStart=/usr/libexec/cpu_usage.sh $arg
    [Install]
    WantedBy=multi-user.target




使用systemd控制mem cgroup实战

写一个消耗mem的脚本

#!/bin/bash

#file_name mem_usage.sh
function consume() 
{
        x="a"
        while [ true ]; do
                x=$x$x
        done;
}

function main()
{
        consume
}
main $*

创建slice,service

编辑文件 /etc/systemd/system/mem_usage.service

mv cpu_usage.sh /usr/libexec/mem_usage.sh && chmod +x /usr/libexec/mem_usage.sh

[Unit]
Description=mem_usage
ConditionFileIsExecutable=/usr/libexec/mem_usage.sh
[Service]
Type=simple
Slice=big_data.slice
Environment="arg=consume 500M"
ExecStart=/usr/libexec/mem_usage.sh $arg
MemoryLimit=200M
TasksAccounting=yes
BlockIOAccounting=yes
[Install]
WantedBy=multi-user.target

启动mem_usage服务

查看mem_usage服务的内存始终不会超过200M,经过一段时间后进程 6501 被kill掉,cgroup目录/sys/fs/cgroup/memory/big_data.slice/mem_usage.service 也不存在了。

cpuset cgroup独占核实战

指定某些进程独占使用这些核心,其他进程组不能使用,由于systemd不支持cpuset子系统,来通过用户进程方式运行shell脚本。

查看节点cpu资源情况

[root@master cgroup]# cat /proc/cpuinfo  |grep -e "physical id" -e  "core id" -e  "cpu cores"
physical id     : 0
core id         : 0
cpu cores       : 2
physical id     : 0
core id         : 1
cpu cores       : 2

可以看到目前节点上有一个物理核,该物理核开启超线程,模拟出两个逻辑核,我们让脚本进程跑在第一个核上。

在没有配置独占核之前,运行脚本:

chmod +x cpu_usage.sh

./cpu_useage.sh consume 2

可以看到脚本中的两个子进程将两个cpu都打满了。

限制脚本的两个cpu到逻辑核0上

mkdir -p /sys/fs/cgroup/cpuset/cpu_usage
echo "0" > /sys/fs/cgroup/cpuset/cpu_usage/cpuset.cpus
#此处为numa节点编号
echo "0" > /sys/fs/cgroup/cpuset/cpu_usage/cpuset.mems


echo "3553" > /sys/fs/cgroup/cpuset/cpu_usage/tasks
echo "3555" > /sys/fs/cgroup/cpuset/cpu_usage/tasks

可以看到 进程3553 和 3555 都跑在了逻辑核0上,并且cpu0使用率为100,cpu1上为0,从而证实进程组被限制在了逻辑核cpu0上。

参考资料

  1. https://www.kernel.org/doc/html/v5.3/admin-guide/cgroup-v1/net_cls.html
文章来自个人专栏
云原生k8s
3 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0