CFS

CFS(Completely Fair Scheduler)调度器追求的是对所有进程的全面公平,实际上它的做法就是在一个特定的调度周期内,保证所有待调度的进程都能被执行一遍

公式1:
进程运行时间 = 调度周期 * 进程权重 / 所有进程权重之和

比如只有两个进程A, B,权重分别为1和2,调度周期设为30ms,那么分配给A的CPU时间为:30ms (1/(1+2)) = 10ms;而B的CPU时间为:30ms (2/(1+2)) = 20ms。那么在这30ms中A将运行10ms,B将运行20ms

公式2:
vruntime = 进程运行时间 * NICE_0_LOAD / 进程权重 

NICE_0_LOAD表示nice为0的进程的权重,也就是说,所有进程都以nice为0的进程的权重1024作为基准,以上面AB两个进程为例,B的权重是A的2倍,那么B的vruntime增加速度只有A的一半

把公式2中的实际运行时间用公式1来替换,最终得到

vruntime = (调度周期 * 进程权重 / 所有进程总权重) * NICE_0_LOAD / 进程权重

vruntime = 调度周期 * NICE_0_LOAD / 所有进程总权重 

vruntime小就说明之前对于cpu占用时间短,所以相应的下一个选择这个进程运行的概率就高,而权重越大的vruntime增加的越慢,可以获得更多的cpu执行时间,这样做到“完全公平”。

流程简述
image.png

  • 每个sched_latency周期内,根据各个任务的权重值,可以计算出运行时间runtime;
  • 运行时间runtime可以转换成虚拟运行时间vruntime;
  • 根据虚拟运行时间的大小,插入到CFS红黑树中,虚拟运行时间少的调度实体放置到左边;
  • 在下一次任务调度的时候,选择虚拟运行时间少的调度实体来运行;

cgroup cpu参数

CPU cgroup 配置说明

  • cpu.cfs_period_us:表示一个cpu带宽,单位为微秒。多核场景下,如配置cpu.cfs_period_us=10000,而cfs_quota_us=20000,表示该cgroup可以完全使用2个cpu
    系统总CPU带宽: cpu核心数 * cfs_period_us
  • cpu.cfs_quota_us:表示Cgroup可以使用的cpu的带宽,单位为微秒。

    cfs_quota_us为-1,表示使用的CPU不受cgroup限制。
    
    cfs_quota_us的最小值为1ms(1000),最大值为1s。
    
    cpu.cfs_quota_us和cpu.cfs_period_us以绝对比例限制cgroup的cpu
  • cpu.shares: 以相对比例限制cgroup的cpu

    例如: 两个任务再cgroup中share设置都为1,他们有相同的cpu时间; 如果一个share值设置为2,那么可以使用cpu的时间就是cpu.shares设置为1的2倍。

cpu.stat

包含了下面三项统计结果

  • nr_periods: 表示过去了多少个cpu.cfs_period_us里面配置的时间周期
  • nr_throttled: 在上面的这些周期中,有多少次是受到了限制(即cgroup中的进程在指定的时间周期中用光了它的配额)
  • throttled_time: cgroup中的进程被限制使用CPU持续了多长时间(纳秒

docker资源限制

Docker启用容器的时候也能设置资源限制——输入docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash可以设置好容器的cpu资源限制

kubernetes基于CFS进行CPU管理

假设pod的资源定义为

    resources:
      limits:
        cpu: 500m
        memory: 512Mi
      requests:
        cpu: 200m
        memory: 100Mi

查看pod对应的cgroup信息

  • 通过docker确定对应的id

    85cb54d5b4-9c6kv 为pod中的关键字段
    docker ps |grep  85cb54d5b4-9c6kv
    
     {
            "Id": "38abd5a4414774a87e6c509913b03694411ad921f5b6ee8901fd7dfcf0afebd7",
            "Created": "2021-11-17T07:22:44.873699016Z",
            xxxxx
           "CgroupParent": "/kubepods/burstable/pod3b5332a9-b208-4236-b07b-da447f9fff8c",
    }

    其中Id和CgroupParent是查找cgroup的关键信息
    上面得知CgroupParent=/kubepods/burstable/pod3b5332a9-b208-4236-b07b-da447f9fff8c。那么在宿主机上的cgroup目录为/sys/fs/cgroup/cpu/kubepods/${CgroupParent}

  • 查看宿主机目录

    ls /sys/fs/cgroup/cpu/kubepods/burstable/pod3b5332a9-b208-4236-b07b-da447f9fff8c
    38abd5a4414774a87e6c509913b03694411ad921f5b6ee8901fd7dfcf0afebd7  cgroup.procs   cpuacct.usage_all         cpuacct.usage_percpu_user  cpu.cfs_period_us  cpu.rt_runtime_us  notify_on_release
    a2a521da5284c7ad9a0aca61bf470454f4d71b0d1ce18ea64dbab8ac107c9f06  cpuacct.stat   cpuacct.usage_percpu      cpuacct.usage_sys          cpu.cfs_quota_us   cpu.shares         tasks
    cgroup.clone_children                                             cpuacct.usage  cpuacct.usage_percpu_sys  cpuacct.usage_user         cpu.rt_period_us   cpu.stat

    上面为pod级别的cgroup文件,但一个pod是至少有两个个容器一个应用容器和pause容器,所以里面有两个文件夹,文件夹名称为docker inspect xxxx里看到的第一行id字段,就是上面的38abd5a4414774a87e6c509913b03694411ad921f5b6ee8901fd7dfcf0afebd7

  • 查看cgroup信息

    cat cpu.cfs_period_us
    100000
    cat cpu.shares
    204
    cat cpu.cfs_quota_us
    50000
    
    1. limits主要用以声明使用的最大的CPU核数。通过设置cfs_quota_us和cfs_period_us。比如limits.cpu=500m,则cfs_quota_us=50000(cfs_period_us值一般都使用默认的100000),cfs_quota_us/cpu.cfs_period_us = 0.5
    2. request则主要用以声明最小的CPU核数。一方面则体现在设置cpushare上。比如request.cpu=200m,则cpushare=1024*0.2=204.8

调度说明

有如下系统配置情况:

  • CFS调度周期为10ms,正常负载情况下,进程ready队列里面的进程在每10ms的间隔内都会保证被执行一次
  • CFS重分配周期为100ms,用于保证一个进程的limits设置会被反映在每100ms的重分配周期内可以占用的CPU时间数,在多核系统中,limit最大值可以是 CFS重分配周期*CPU核数
  • 该执行进程队列只有进程A和进程B两个进程
  • 进程A和B定义的CPU share占用都一样,所以在系统资源紧张的时候可以保证A和B进程都可以占用可用CPU资源的一半
  • 定义的CFS重分配周期都是100ms
  • 进程A在100ms内最多占用50ms,进程B在100ms内最多占用20ms

image.png

说明

  • 在前面的4个CFS调度周期内,进程A和B由于share值是一样的,所以每个CFS调度内(10ms),进程A和B都会占用5ms
  • 在第4个CFS调度周期结束的时候,在本CFS重分配周期内,进程B已经占用了20ms,在剩下的8个CFS调度周期即80ms内,进程B都会被限流,一直到下一个CFS重分配周期内,进程B才可以继续占用CPU
  • 在第5-7这3个CFS调度周期内,由于进程B被限流,所以进程A可以完全拥有这3个CFS调度的CPU资源,占用30ms的执行时间,这样在本CFS重分配周期内,进程A已经占用了50ms的CPU时间,在后面剩下的3个CFS调度周期即后面的30ms内,进程A也会被限流,一直到下一个CFS重分配周期内,进程A才可以继续占用CPU

CPU限流问题

告警主题: CPUThrottlingHigh
告警级别: warning
告警类型: CPUThrottlingHigh
故障实例: 
告警详情: 27% throttling of CPU in namespace kube-system for container kube-proxy in pod kube-proxy-9pj9j.
触发时间: 2020-05-08 17:34:17

推测原因: 某些特定的CFS重分配周期内,kube-proxy的CPU占用率超过了给它分配的limits
比如:
假设pod的资源定义为

    resources:
      requests:
        memory: "512Mi"
        cpu: "200m"
      limits:
        memory: "512Mi"
        cpu: "200m"

limits配置只有200m,这就意味着在默认的100ms的CFS重调度周期内,它只能占用20ms,所以在特定繁忙场景会有问题;

假设一个任务需要30ms,在不设置limit的情况下,在一个调度周期内30ms就可以完成
image.png

如果设置了limit.cpu:200m, 就需要两个调度周期,110ms才能完成,相当于延长了任务处理时间
image.png

社区中也出现了对应的问题
image.png

https://github.com/kubernetes...
https://github.com/kubernetes...

解决方案

  • 对于cpu敏感的任务可以提高limit限制或者不设置limit

    request = limit的情况下(guaranteed), 提升limit就必须提升request,从而提升cpu是使用的下限,同一个节点上可以调度的任务就会变少,cpu利用率就会贬低
    
    request < limit的情况下, 存在cpu使用过量的风险
  • 放大quota和period
    继续使用上面的资源例子,quota是20ms, period是100ms放大为quota是40ms,period是200ms;同样可以一个周期30ms完成
    image.png
    需要注意的是,如果任务超过的时间超过了quota将面临的更长的等待时间
  • cpu burst

    https://www.infoq.cn/article/...

吕晨曦
4 声望0 粉丝