k8s-cgroup
问题:
- k8s中cpu的request和limit中设置”0.5”和”100m”表示什么意思?
- 一个4核8线程的cpu, 设置cpu为”1”的limit实际的限制多少?
- cpu中request和limit使用上有什么区别?
- cgroup中是如何限制的?
Linux Cgroup
众所周知, kubernetes和docker中对cpu, memory进行了使用限制, 用于内存和cpu的资源使用隔离. 而底层使用的是linux cgroup技术. 内存比较简单, 本文主要讲cpu的cgroup.
在cgroup里面,跟CPU相关的子系统有cpusets、cpuacct和cpu。
其中cpuset主要用于设置CPU的亲和性,可以限制cgroup中的进程只能在指定的CPU上运行,或者不能在指定的CPU上运行,同时cpuset还能设置内存的亲和性。设置亲和性一般只在比较特殊的情况才用得着,所以这里不做介绍。
cpuacct包含当前cgroup所使用的CPU的统计信息,信息量较少,有兴趣可以去看看它的文档,这里不做介绍。
本篇只介绍cpu子系统,包括怎么限制cgroup的CPU使用上限及相对于其它cgroup的相对值。
创建子cgroup
在ubuntu下,systemd已经帮我们mount好了cpu子系统,我们只需要在相应的目录下创建子目录就可以了
1 | #从这里的输出可以看到,cpuset被挂载在了/sys/fs/cgroup/cpuset, |
我们只需要关注cpu.开头的文件
cpu subsystem
cpu子系统调度cpu到cgroups中, 目前有两种调度策略:
- Completely Fair Scheduler (CFS) —-将cpu时间划分成合适的份额, 按比例和权重分配给cgroup.cfs可以设置相对权重和绝对权重, 目前k8s用的是这个调度策略.
- Real-Time scheduler (RT) —RT调度器与CFS中的绝对权重控制相似, 不过仅用于实时任务. 可以在运行时进行实时调整参数.
- Completely Fair Scheduler (CFS):
强制绝对控制参数:
cpu.cfs_period_us & cpu.cfs_quota_us
cfs_period_us用来配置时间周期长度,cfs_quota_us用来配置当前cgroup在设置的周期长度内所能使用的CPU时间数,两个文件配合起来设置CPU的使用上限。两个文件的单位都是微秒(us),cfs_period_us的取值范围为1毫秒(ms)到1秒(s),cfs_quota_us的取值大于1ms即可,如果cfs_quota_us的值为-1(默认值),表示不受cpu时间的限制。下面是几个例子:
1 | 1.限制只能使用1个CPU(每250ms能使用250ms的CPU时间) |
cpu.stat:
包含了下面三项统计结果
- nr_periods: 表示过去了多少个cpu.cfs_period_us里面配置的时间周期
- nr_throttled: 在上面的这些周期中,有多少次是受到了限制(即cgroup中的进程在指定的时间周期中用光了它的配额)
- throttled_time: cgroup中的进程被限制使用CPU持续了多长时间(纳秒)
- 相对控制参数: cpu.shares
shares用来设置CPU的相对值,并且是针对所有的CPU(内核),默认值是1024,假如系统中有两个cgroup,分别是A和B,A的shares值是1024,B的shares值是512,那么A将获得1024/(1204+512)=66%的CPU资源,而B将获得33%的CPU资源。shares有两个特点:
- 如果A不忙,没有使用到66%的CPU时间,那么剩余的CPU时间将会被系统分配给B,即B的CPU使用率可以超过33%
- 如果添加了一个新的cgroup C,且它的shares值是1024,那么A的限额变成了1024/(1204+512+1024)=40%,B的变成了20%
从上面两个特点可以看出:
- 在闲的时候,shares基本上不起作用,只有在CPU忙的时候起作用,这是一个优点。
- 由于shares是一个绝对值,需要和其它cgroup的值进行比较才能得到自己的相对限额,而在一个部署很多容器的机器上,cgroup的数量是变化的,所以这个限额也是变化的,自己设置了一个高的值,但别人可能设置了一个更高的值,所以这个功能没法精确的控制CPU使用率。
- Real-Time scheduler (RT):
cpu.rt_period_us: 周期时间, us, 同cfs
cpu.rt_runtime_us: 最长持续周期, us. 例如设置rt_runtime_us = 200000, rt_period_us = 1000000, 这就是说如果node有2cpu, 那么每秒钟占用时间就是2*0.2 = 0.4s. 这个也是绝对时间.
运行事例:
以cfs为例.
1 | #继续使用上面创建的子cgroup: test |
kubernetes 资源控制机制
kubernetes使用runc作为runtime, runc中通过cpuGroup对cpu子系统的的调度进行设置. 其中Set()用于初始设置, Apply()方法用于动态更改设置, . 原理就是往上述的指定文件写入相应条目和数字. 没有什么可说的.
内存的Set():
1 | func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error { |
内存Apply():
1 | func (s *MemoryGroup) Apply(d *cgroupData) (err error) { |
cpu的Set():
1 | func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error { |
cpu的Apply() 只能设置RT:
1 | func (s *CpuGroup) Apply(d *cgroupData) error { |
K8s Limits & Request 代码分析
k8s中管理cgroup的结构体在k8s.io/kubernetes/pkg/kubelet/cm/cgroup_manager_linux.go中:
1 | // cgroupManagerImpl implements the CgroupManager interface. |
来看下Update方法:
1 | // Update updates the cgroup with the specified Cgroup Configuration |
先看下参数是如何提取的:
1 | func (m *cgroupManagerImpl) toResources(resourceConfig *ResourceConfig) *libcontainerconfigs.Resources { |
在来看看CpuShare, CpuQuota, 和CpuPeriod都来字哪里:
1 | // ResourceConfigForPod takes the input pod and outputs the cgroup resource config. |
设置的代码:
1 | func setSupportedSubsystems(cgroupConfig *libcontainerconfigs.Cgroup) error { |
代码很明显了:
Request -> cpu.shares
Limits -> cpu.quota(CFS或者RT)
QuotaPeriod 为100ms
所以当Request设置0.5(0.5 1024=512), 等价于设置500m(500 1024/1000=512), 也就是512.
1 | [root@tdc-tester04 ~]# cat /sys/fs/cgroup/cpu/kubepods/burstable/pod4de91174-9002-11e8-a663-ac1f6b83dd66/1cbc69fd7756efdcba38d11a57acab5cdbe0b9b2e5eef2a9e86e3c6c8850b1a1/cpu.shares |
当Limit设置1(1 1000 100000/1000=100000), 等价于1000m(1000 * 100000 / 1000 = 100000 ), 也就是quota=100000, period=100000.
1 | [root@tdc-tester04 ~]# cat /sys/fs/cgroup/cpu/kubepods/burstable/pod4de91174-9002-11e8-a663-ac1f6b83dd66/1cbc69fd7756efdcba38d11a57acab5cdbe0b9b2e5eef2a9e86e3c6c8850b1a1/cpu.cfs_quota_us |
至此所有路走通.
值得注意的点
1 | The CPU resource is measured in cpu units. One cpu, in Kubernetes, is equivalent to: |
从文档来看所以4核8线程对k8s来讲就是8核.
如果只设置了request没有设置limit, 意味着一个pod可以任意使用node资源, 如果没有其他pod创建. 如果集群管理员设置了LimitRange, 那么当pod没有设置limit的时候就会使用LimitRange里设置的值作为默认.
参考文献
- https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/#what-if-you-specify-a-container-s-request-but-not-its-limit
- https://segmentfault.com/a/1190000008323952
- https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/sec-cpu


