CPU Cgroup
Cgroups 是对指定进程做计算机资源限制的,CPU Cgroup 是 Cgroups 其中的一个 Cgroups 子系统,它是用来限制进程的 CPU 使用的。
对于进程的 CPU 使用,我们知道它只包含两部分:一个是用户态(top命令中的 us 和 ni);还有一部分是内核态(top 命令中的 sy)。
至于其他状态(top 命令中的 wa、hi、si),这些 I/O 或者中断相关的 CPU 使用,CPU Cgroup 不会去做限制。
每个 Cgroups 子系统都是通过一个虚拟文件系统挂载点的方式,挂到一个缺省的目录下,CPU Cgroup 一般在 Linux 发行版里会放在 /sys/fs/cgroup/cpu 这个目录下。
# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
每个 cgroup 子系统代表一种资源:
- system - systemd 维护的自己使用的非 subsystem 的 cgroups 层级结构;
- cpu - 使用调度程序对 cgroup 内的任务提供 CPU 资源的访问;
- cpuacct - 生成 cgroup 中所有任务的处理器使用情况报告;
- memory - 限制 memory 的利用率;
- freezer- 允许 cgroup 中的一组任务挂起 / 恢复;
- cpuset - 为 cgroup 内的任务分配独立的处理器和内存节点;
- net_cls - 允许对 cgroup 中的任务产生的网络数据包进行标记;
- net_prio - 针对 cgroup 中的每个网络接口提供一种动态修改网络流量优先级的方法;
- pid - 限制 cgroup 中的进程数量;
- rdma - 子系统 Remote Direct Memory Access Controller,限制进程对RDMA/IB资源的使用。这些进程分组使用RDMA控制器;
- hugetlb - 为 cgroup 开启对 大页内存的支持 ;
- devices - 限制 cgroup 中的一组任务访问设备;
- perf_event - 支持访问 cgroup 中的 性能事件;
- blkio - blkio子系统用于限制块设备I/O速率。;
比如说,我们在子系统的最顶层开始建立两个控制组(也就是建立两个目录)group1 和 group2
# cd /sys/fs/cgroup/cpu
# mkdir group1 group2
# cd group2/
# ls
cgroup.clone_children cpuacct.stat cpuacct.usage_all cpuacct.usage_percpu_sys cpuacct.usage_sys cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.procs cpuacct.usage cpuacct.usage_percpu cpuacct.usage_percpu_user cpuacct.usage_user cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
考虑到在云平台里呢,大部分程序都不是实时调度的进程,而是普通调度(SCHED_NORMAL)类型进程,那什么是普通调度类型呢?
因为普通调度的算法在 Linux 中目前是 CFS (Completely Fair Scheduler,即完全公平调度器)。为了方便理解,我们就直接来看 CPU Cgroup 和 CFS 相关的参数,一共有三个。
# cat /sys/fs/cgroup/cpu/group2/cpu.cfs_period_us
100000
# cat /sys/fs/cgroup/cpu/group2/cpu.cfs_quota_us
-1
# cat /sys/fs/cgroup/cpu/group2/cpu.shares
1024
- 第一个参数是 cpu.cfs_period_us,它是 CFS 算法的一个调度周期,一般它的值是 100000,以 microseconds 为单位,也就 100ms。
- 第二个参数是 cpu.cfs_quota_us,它表示 CFS 算法中,在一个调度周期里这个控制组被允许的运行时间,比如这个值为 50000 时,就是 50ms。如果用这个值去除以调度周期(也就是 cpu.cfs_period_us),50ms/100ms = 0.5,这样这个控制组被允许使用的 CPU 最大配额就是 0.5 个 CPU。从这里能够看出,cpu.cfs_quota_us 是一个绝对值。如果这个值是 200000,也就是 200ms,那么它除以 period,也就是 200ms/100ms=2。
- 第三个参数, cpu.shares。这个值是 CPU Cgroup 对于控制组之间的 CPU 分配比例,它的缺省值是 1024。
在Kubernetes中
Kubernetes 会为每个容器都在 CPU Cgroup 的子系统中建立一个控制组,然后把容器中进程写入到这个控制组里。
cpu.cfs_quota_us、cpu.cfs_period_us
Kubernetes 是通过 CPU cgroup 控制模块中的 cpu.cfs_period_us,cpu.cfs_quota_us 两个配置来实现的。kubernetes 会为这个 container cgroup 配置两条信息:
cpu.cfs_period_us = 100000 (i.e. 100ms)
cpu.cfs_quota_us = quota = (cpu in millicores * 100000) / 1000
容器 CPU 的上限由 cpu.cfs_quota_us 除以 cpu.cfs_period_us 得出的值来决定的。而且,在操作系统里,cpu.cfs_period_us 的值一般是个固定值。
在 cgroup 的 CPU 子系统中,可以通过这两个配置,严格控制这个 cgroup 中的进程对 CPU 的使用量,保证使用的 CPU 资源不会超过 cfs_quota_us/cfs_period_us,也正好就是申请的 limit 值。
对于 cpu 来说,如果没有指定 limit 的话,那么 cfs_quota_us 将会被设置为 -1,即没有限制。
cpu.shares
在 CPU Cgroup 中 cpu.shares == 1024 表示 1 个 CPU 的比例,那么 Request CPU 的值就是 n,给 cpu.shares 的赋值对应就是 n*1024。
CPU request 是通过 cgroup 中 CPU 子系统中的 cpu.shares 配置来实现的。当你指定了某个容器的 CPU request 值为 x millicores 时,kubernetes 会为这个 container 所在的 cgroup 的 cpu.shares 的值指定为 x * 1024 / 1000。即:
cpu.shares = (cpu in millicores * 1024) / 1000
举个例子,当你的 container 的 CPU request 的值为 1 时,它相当于 1000 millicores,所以此时这个 container 所在的 cgroup 组的 cpu.shares 的值为 1024。
这样做希望达到的终效果就是:即便在极端情况下,即所有在这个物理机上面的 pod 都是 CPU 繁忙型的作业的时候(分配多少 CPU 就会使用多少 CPU),仍旧能够保证这个 container 的能够被分配到 1 个核的 CPU 计算量。其实就是保证这个 container 的对 CPU 资源的低需求。即"Request CPU"就是无论其他容器申请多少 CPU 资源,即使运行时整个节点的 CPU 都被占满的情况下,我的这个容器还是可以保证获得需要的 CPU 数目。
在docker中
在docker中可以使用以下参数:
参数 | 作用 |
---|---|
--cpuset-cpus |
限制一个容器可以使用的特定CPU或内核。如果你有一个以上的CPU,一个以逗号分隔的列表或以连字符分隔的容器可以使用的CPU范围。第一个CPU的编号是0。一个有效的值可能是0-3(使用第一、第二、第三和第四个CPU)或1,3(使用第二和第四个CPU)。 |
--cpu-shares |
将这个标志设置为一个大于或小于默认值1024的值,以增加或减少容器的重量,并使它能够获得更大或更小比例的主机CPU周期。这只有在CPU周期受到限制时才会强制执行。当有大量的CPU周期可用时,所有的容器都会根据自己的需要使用尽可能多的CPU。这样一来,这是一个软限制。 |
--cpu-period |
指定CPU CFS调度器的周期,与--cpu-quota 一起使用。默认为100000微秒(100毫秒)。大多数用户不改变这个默认值。对于大多数使用情况,--cpu 是一个更方便的选择。 |
--cpu-quota |
对容器施加一个CPU CFS配额。容器在节流前每--cpu-period 被限制的微秒数。因此,作为有效的上限。对于大多数使用情况,--cpu 是一个更方便的选择。 |
--cpus |
指定一个容器可以使用多少可用的CPU资源。例如,如果主机有两个CPU,而你设置了–cpus=“1.5”,那么容器最多保证使用一个半的CPU。这相当于设置--cpu-period="100000" 和--cpu-quota="150000" 。docker 1.13支持支持,替换cpu-period和cpu-quota |
示例:
如果你有1个CPU,以下每个命令都能保证容器每秒最多使用50%的CPU。
docker run -it --cpus=".5" ubuntu /bin/bash
这相当于手动指定–cpu-period和–cpu-quota;
docker run -it --cpu-period=100000 --cpu-quota=50000 ubuntu /bin/bash
总结
- 每个进程的 CPU Usage 只包含用户态(us 或 ni)和内核态(sy)两部分,其他的系统 CPU 开销并不包含在进程的 CPU 使用中,而 CPU Cgroup 只是对进程的 CPU 使用做了限制。
- CPU Cgroup 中的主要参数,包括这三个:cpu.cfs_quota_us,cpu.cfs_period_us 还有 cpu.shares。
- cpu.cfs_quota_us 和 cpu.cfs_period_us 这两个值决定了每个控制组中所有进程的可使用 CPU 资源的最大值。cpu.cfs_quota_us(一个调度周期里这个控制组被允许的运行时间)除以 cpu.cfs_period_us(用于设置调度周期)得到的这个值决定了 CPU Cgroup 每个控制组中 CPU 使用的上限值。
- cpu.shares 参数,正是这个值决定了 CPU Cgroup 子系统下控制组可用 CPU 的相对比例,当系统上 CPU 完全被占满的时候,这个比例才会在各个控制组间起效。