Kubernetes CPU管理及调度
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kubernetes CPU管理及调度相关的知识,希望对你有一定的参考价值。
参考技术A 在 内存管理篇 ,我们研究了Kubernetes(K8s)requests和limits的资源含义,以及Docker容器运行时的内存含义。我们在这里将介绍K8中的CPU的含义,以及CPU的requests和limits,并研究如何为Pod设置合理的cpu资源。那么,CPU在kubernetes中的确切含义是什么?一个CPU等价于node操作系统提供的一个CPU核心。
与内存相反,CPU在kubernetes中是可压缩的,这意味着它可以被throttled(限制)。当您指定容器的CPU limits时,实际上是在nodes上限制该容器的CPU时间,而不是设置容器对特定的一个CPU或一组CPU的亲和性。这意味着,即使您指定的limits少于节点上的CPU总数,您的容器仍会看到(并使用)该节点的所有CPU,只是你的容器可以使用的cpu的时间是有限的。
例如,在具有8个CPU核心的node上指定某个容器的CPU limits为4,这也就意味着该容器只能使用相当于4个CPU核心的全部时间,但是可以分布在所有CPU核心上。对于在专用node上运行的单个容器,这种情况下,该node所有CPU核心的最大允许使用率将为50%。
那么,kuberne上的这种限制转换成Docker中的控制会是怎样呢? K8s通过传递cpu-period和cpu-quota选项来实现kubernetes中的CPU limits 。 cpu-period始终设置为100000μs(100ms),表示跟踪容器CPU利用率的时间段。 cpu-quota是容器在每个cpu周期内可以使用的CPU时间总量。这两个设置都控制着内核的完全公平调度程序(CFS)。这是不同的K8s CPU限制转换为Docker配置的方式:
limits 1: cpu-quota=100000
limits 4: cpu-quota=400000
limits 0.5: cpu-quota=50000
limits 1 意味着每100ms,允许使用总CPU时间相当于一个CPU核心的100%CPU时间, limits 4 意味着每100ms允许使用总CPU时间相当于一个CPU核心400%的CPU时间 (或者,相当于4个CPU核心100%的CPU时间) 。不要忘记,这些CPU时间都是分布在所有的CPU核心上的。 由于CFS配额的工作方式, 在给定时间段内超出其配额的任何容器在该时间段内将不允许再次运行,直到下一个时间段 -这意味着您的程序会感觉到莫名其妙的暂停,特别是如果您的程序设置了CPU limits,并且对延迟敏感的情况下这种感觉更加明显。
您可以通过指定Pod的request来指定Pod所需的CPU(或CPU的一部分),在指定request的时候,kubernetes不允许node的request的值超过节点的CPU核数(属于某个node上的所有pod的cpu的request的总和不能超过该node上显示的CPU数量)。您可以将request的总数设置为node的CPU的核数,但是当node的工作负载过高时,例如当CPU的使用率达到甚至超过100%时会怎样?这时, 容器的CPU调度优先级由Pod的``requests''的值来决定,该requests的值乘以1024后的乘积作为cpu-shares选项传递给Docker 。cpu-shares实际上就是一个权重;如果节点上运行的所有容器具有相同的权重,则在负载过大的情况下(没有空闲的CPU周期时),所有容器将具有相同的CPU调度优先级。如果一个容器的权重比其他容器大,则在负载过大的情况下,它将具有更高的CPU调度优先级,并能够比其他容器获得更多的CPU时间。
在 内存篇 , burstable的QoS配置意味着在其他容器不使用CPU的情况下,您的容器使用这些空闲的CPU时间,从而可能使用到比requests更多的CPU时间。这样可以潜在地更有效地利用基础资源,但具有更大的不可预测性,例如,对CPU比较依赖的应用程序的响应延迟可能会受到在同一nodes上的其他容器的暂时影响。在最坏的情况下,一个node上的burstable容器若过多,将会引起非常高的CPU负载,从而相互对彼此产生负面。
如果您是K8新手,那么最好将requests和limits的值设置为相同,从而确保你的pod是Guaranteed类型的容器。当您对CPU的资源利用特性了解的更深入时,并可能发现您在CPU方面的配置已过剩时,您可以考虑引入Burstable容器,以期获得更大的收益,甚至可以提高CPU的整体使用率。
您应该为容器分配多少个CPU?根本没有标准答案,这取决于你应用的组特性,可接受的性能,pod的调度策略,主机类型,成本等。
但是-如果您对你的应用了如指掌,则可以在性能测试环境中甚至在生产环境中尝试不同的配置。最终你将会找到性能和成本的平衡点,但是container-throttling是Linux上运行的CPU密集型应用的重要影响因素。通常可以使用Prometheus来查看CPU的相关信息,也可以直接进入容器并查看cgroup CPU统计信息:
cat /sys/fs/cgroup/cpu,cpuacct/cpu.stat
该文件中包含了总共执行了多少次CPU调度,容器被限制的总次数以及累积限制的时间(以纳秒为单位)。
参考: https://medium.com/expedia-group-tech/kubernetes-container-resource-requirements-part-2-cpu-83ca227a18b1
Kubernetes-- 资源管理
在 Kubernetes 里,Pod 是最小的原子调度单位。这也就意味着,所有跟调度和资源管理相关的属性都应该是属于 Pod 对象的字段。而这其中最重要的部分,就是 Pod 的 CPU 和内存配置,如下所示:
apiVersion: v1
kind: Pod
metadata:
name: frontend
spec:
containers:
- name: db
image: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "password"
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
- name: wp
image: wordpress
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
在 Kubernetes 中,像 CPU 这样的资源被称作“可压缩资源”(compressible resources)。它的典型特点是,当可压缩资源不足时,Pod 只会“饥饿”,但不会退出。
而像内存这样的资源,则被称作“不可压缩资源(incompressible resources)。当不可压缩资源不足时,Pod 就会因为 OOM(Out-Of-Memory)被内核杀掉。
其中,Kubernetes 里为 CPU 设置的单位是“CPU 的个数”。比如,cpu=1 指的就是,这个 Pod 的 CPU 限额是 1 个 CPU。当然,具体“1 个 CPU”在宿主机上如何解释,是 1 个 CPU 核心,还是 1 个 vCPU,还是 1 个 CPU 的超线程(Hyperthread),完全取决于宿主机的 CPU 实现方式。Kubernetes 只负责保证 Pod 能够使用到“1 个 CPU”的计算能力。
Kubernetes 里 Pod 的 CPU 和内存资源,实际上还要分为 limits 和 requests 两种情况,如下所示:
spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory
在调度的时候,kube-scheduler 只会按照 requests 的值进行计算。而在真正设置 Cgroups 限制的时候,kubelet 则会按照 limits 的值来进行设置。
更确切地说,当你指定了 requests.cpu=250m 之后,相当于将 Cgroups 的 cpu.shares 的值设置为 (250/1000)*1024。而当你没有设置 requests.cpu 的时候,cpu.shares 默认则是 1024。这样,Kubernetes 就通过 cpu.shares 完成了对 CPU 时间的按比例分配。
Kubernetes 这种对 CPU 和内存资源限额的设计,实际上参考了 Borg 论文中对“动态资源边界”的定义,既:容器化作业在提交时所设置的资源边界,并不一定是调度系统所必须严格遵守的,这是因为在实际场景中,大多数作业使用到的资源其实远小于它所请求的资源限额。
基于这种假设,Borg 在作业被提交后,会主动减小它的资源限额配置,以便容纳更多的作业、提升资源利用率。而当作业资源使用量增加到一定阈值时,Borg 会通过“快速恢复”过程,还原作业原始的资源限额,防止出现异常情况。
而 Kubernetes 的 requests+limits 的做法,其实就是上述思路的一个简化版:用户在提交 Pod 时,可以声明一个相对较小的 requests 值供调度器使用,而 Kubernetes 真正设置给容器 Cgroups 的,则是相对较大的 limits 值。不难看到,这跟 Borg 的思路相通的。
在 Kubernetes 中,不同的 requests 和 limits 的设置方式,其实会将这个 Pod 划分到不同的 QoS 级别当中。
当 Pod 里的每一个 Container 都同时设置了 requests 和 limits,并且 requests 和 limits 值相等的时候,这个 Pod 就属于 Guaranteed 类别,如下所示:
apiVersion: v1
kind: Pod
metadata:
name: qos-demo
namespace: qos-example
spec:
containers:
- name: qos-demo-ctr
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "700m"
requests:
memory: "200Mi"
cpu: "700m"
当这个 Pod 创建之后,它的 qosClass 字段就会被 Kubernetes 自动设置为 Guaranteed。需要注意的是,当 Pod 仅设置了 limits 没有设置 requests 的时候,Kubernetes 会自动为它设置与 limits 相同的 requests 值,所以,这也属于 Guaranteed 情况。
而当 Pod 不满足 Guaranteed 的条件,但至少有一个 Container 设置了 requests。那么这个 Pod 就会被划分到 Burstable 类别。比如下面这个例子:
apiVersion: v1
kind: Pod
metadata:
name: qos-demo-2
namespace: qos-example
spec:
containers:
- name: qos-demo-2-ctr
image: nginx
resources:
limits
memory: "200Mi"
requests:
memory: "100Mi"
而如果一个 Pod 既没有设置 requests,也没有设置 limits,那么它的 QoS 类别就是 BestEffort。比如下面这个例子:
apiVersion: v1
kind: Pod
metadata:
name: qos-demo-3
namespace: qos-example
spec:
containers:
- name: qos-demo-3-ctr
image: nginx
那么,Kubernetes 为 Pod 设置这样三种 QoS 类别,具体有什么作用呢?
实际上,QoS 划分的主要应用场景,是当宿主机资源紧张的时候,kubelet 对 Pod 进行 Eviction(即资源回收)时需要用到的。
而当 Eviction 发生的时候,kubelet 具体会挑选哪些 Pod 进行删除操作,就需要参考这些 Pod 的 QoS 类别了。
- 首当其冲的,自然是 BestEffort 类别的 Pod。( Pod 既没有设置 requests,也没有设置 limits)
- 其次,是属于 Burstable 类别、并且发生“饥饿”的资源使用量已经超出了 requests 的 Pod。( Pod 不满足 Guaranteed 的条件,但至少有一个 Container 设置了 requests。那么这个 Pod 就会被划分到 Burstable 类别)
- 最后,才是 Guaranteed 类别。并且,Kubernetes 会保证只有当 Guaranteed 类别的 Pod 的资源使用量超过了其 limits 的限制,或者宿主机本身正处于 Memory Pressure 状态时,Guaranteed 的 Pod 才可能被选中进行 Eviction 操作。
以上是关于Kubernetes CPU管理及调度的主要内容,如果未能解决你的问题,请参考以下文章
kubernates dashboard管理界面安装及管理员用户配置
kubernates dashboard管理界面安装及管理员用户配置