文/ 天云软件 云平台开发工程师 张伟
张伟现主要负责天云软件SkyForm云平台设计及开发工作。熟悉各种开源IaaS平台,如CloudStack,OpenStack;熟悉各种资源管理及调度框架,如Kubernetes,Mesos,YARN,Borg等。
经过六个月的持续优化,kubernetes宣布1.2版本已经可以支持1000+节点的群集,并且有相当出色的响应能力,这对kubernetes来说是一个重大的改进。随着kubernetes集群规模的扩大,kubernetes调度器作为集群的大脑,在如何提高集群的资源利用率、保证集群中服务的稳定运行中也会变得越来越重要。本文将从调度流程、调度算法、资源限制几个方面详细介绍kubernetes调度器。
kubernets调度器
kubernets scheduler主要负责的工作是:接受API Server创建的新Pod,并为其安排一个主机,将信息写入etcd中。当然在这个过程中要处理的事情远远没有这么简单,需要综合考虑多种决策因素,比如把同一个replication controller的Pod分配到不同的主机上,防止因主机节点宕机对业务造成较大冲击;以及如何考虑资源均衡,从而提升整个集群的资源使用率等。
调度流程
kubernetes调度器通过API Server查找还未分配主机的Pod,并尝试为这些Pod分配主机,这个过程如下图所示:
![kubernetes调度详解_第1张图片](http://img.e-com-net.com/image/info8/62622f2bc3d04190ae1f74642b270e08.jpg)
- 客户端提交创建请求,可以通过API Server的Restful API,也可以使用kubectl命令行工具。支持的数据类型包括JSON和YAML。
- API Server处理用户请求,存储Pod数据到etcd。
- 调度器通过API Server查看未绑定的Pod。尝试为Pod分配主机。
- 过滤主机:调度器用一组规则过滤掉不符合要求的主机。比如Pod指定了所需要的资源量,那么可用资源比Pod需要的资源量少的主机会被过滤掉。
- 主机打分:对第一步筛选出的符合要求的主机进行打分,在主机打分阶段,调度器会考虑一些整体优化策略,比如把容一个Replication Controller的副本分布到不同的主机上,使用最低负载的主机等。
- 选择主机:选择打分最高的主机,进行binding操作,结果存储到etcd中。
- 所选主机对于的kubelet根据调度结果执行Pod创建操作。
调度算法
kubernetes通过一组规则,为每一个未调度的Pod选择一个主机,如调度流程中介绍,kubernetes的调度算法主要包括两个方面,过滤主机和主机打分。
kubernetes调度器的源码位于kubernetes/plugin/中,大体的代码目录结构如下所示:
—-kubernetes
——–plugin
————cmd //kub-scheduler启动函数在cmd包中
————pkg //调度相关的具体实现
—————-scheduler
——————–algorithm
————————predicates //主机筛选策略
————————priorities //主机打分策略
——————–algorithmprovider
————————defaults //定义默认的调度器
过滤主机的目的是过滤掉不符合Pod要求的主机,现在kubernetes中实现的过滤规则主要包括以下几种(在kubernetes/plugin/pkg/scheduler/algorithm/predicates中实现):
- NoDiskConflict:检查在此主机上是否存在卷冲突。如果这个主机已经挂载了卷,其它同样使用这个卷的Pod不能调度到这个主机上。GCE, Amazon EBS, and Ceph RBD使用的规则如下:
- GCE允许同时挂载多个卷,只要这些卷都是只读的。
- Amazon EBS不允许不同的Pod挂载同一个卷。
- Ceph RBD不允许任何两个pods分享相同的monitor,match pool和 image。
- NoVolumeZoneConflict:检查给定的zone限制前提下,检查如果在此主机上部署Pod是否存在卷冲突。假定一些volumes可能有zone调度约束, VolumeZonePredicate根据volumes自身需求来评估pod是否满足条件。必要条件就是任何volumes的zone-labels必须与节点上的zone-labels完全匹配。节点上可以有多个zone-labels的约束(比如一个假设的复制卷可能会允许进行区域范围内的访问)。目前,这个只对PersistentVolumeClaims支持,而且只在PersistentVolume的范围内查找标签。处理在Pod的属性中定义的volumes(即不使用PersistentVolume)有可能会变得更加困难,因为要在调度的过程中确定volume的zone,这很有可能会需要调用云提供商。
- PodFitsResources:检查主机的资源是否满足Pod的需求。根据实际已经分配的资源量做调度,而不是使用已实际使用的资源量做调度。
- PodFitsHostPorts:检查Pod内每一个容器所需的HostPort是否已被其它容器占用。如果有所需的HostPort不满足需求,那么Pod不能调度到这个主机上。
- HostName:检查主机名称是不是Pod指定的HostName。
- MatchNodeSelector:检查主机的标签是否满足Pod的*nodeSelector*属性需求。
- MaxEBSVolumeCount:确保已挂载的EBS存储卷不超过设置的最大值。默认值是39。它会检查直接使用的存储卷,和间接使用这种类型存储的PVC。计算不同卷的总目,如果新的Pod部署上去后卷的数目会超过设置的最大值,那么Pod不能调度到这个主机上。
- MaxGCEPDVolumeCount:确保已挂载的GCE存储卷不超过设置的最大值。默认值是16。规则同上。
可以通过配置修改kubernetes默认支持的过滤规则。
经过过滤后,再对符合需求的主机列表进行打分,最终选择一个分值最高的主机部署Pod。kubernetes用一组优先级函数处理每一个待选的主机(在kubernetes/plugin/pkg/scheduler/algorithm/priorities中实现)。每一个优先级函数会返回一个0-10的分数,分数越高表示主机越“好”, 同时每一个函数也会对应一个表示权重的值。最终主机的得分用以下公式计算得出:
finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + … + (weightn * priorityFuncn)
现在支持的优先级函数包括以下几种:
- LeastRequestedPriority:如果新的pod要分配给一个节点,这个节点的优先级就由节点空闲的那部分与总容量的比值(即(总容量-节点上pod的容量总和-新pod的容量)/总容量)来决定。CPU和memory权重相当,比值最大的节点的得分最高。需要注意的是,这个优先级函数起到了按照资源消耗来跨节点分配pods的作用。计算公式如下:
cpu((capacity – sum(requested)) * 10 / capacity) + memory((capacity – sum(requested)) * 10 / capacity) / 2
- BalancedResourceAllocation:尽量选择在部署Pod后各项资源更均衡的机器。*BalancedResourceAllocation*不能单独使用,而且必须和*LeastRequestedPriority*同时使用,它分别计算主机上的cpu和memory的比重,主机的分值由cpu比重和memory比重的“距离”决定。计算公式如下:
score = 10 – abs(cpuFraction-memoryFraction)*10
- SelectorSpreadPriority:对于属于同一个service、replication controller的Pod,尽量分散在不同的主机上。如果指定了区域,则会尽量把Pod分散在不同区域的不同主机上。调度一个Pod的时候,先查找Pod对于的service或者replication controller,然后查找service或replication controller中已存在的Pod,主机上运行的已存在的Pod越少,主机的打分越高。
- CalculateAntiAffinityPriority:对于属于同一个service的Pod,尽量分散在不同的具有指定标签的主机上。
- ImageLocalityPriority:根据主机上是否已具备Pod运行的环境来打分。ImageLocalityPriority会判断主机上是否已存在Pod运行所需的镜像,根据已有镜像的大小返回一个0-10的打分。如果主机上不存在Pod所需的镜像,返回0;如果主机上存在部分所需镜像,则根据这些镜像的大小来决定分值,镜像越大,打分就越高。
- NodeAffinityPriority(Kubernetes1.2实验中的新特性):Kubernetes调度中的亲和性机制。Node Selectors(调度时将pod限定在指定节点上),支持多种操作符(In, NotIn, Exists, DoesNotExist, Gt, Lt),而不限于对节点labels的精确匹配。另外,Kubernetes支持两种类型的选择器,一种是“hard(requiredDuringSchedulingIgnoredDuringExecution)”选择器,它保证所选的主机必须满足所有Pod对主机的规则要求。这种选择器更像是之前的nodeselector,在nodeselector的基础上增加了更合适的表现语法。另一种是“soft(preferresDuringSchedulingIgnoredDuringExecution)”选择器,它作为对调度器的提示,调度器会尽量但不保证满足NodeSelector的所有要求。
多层次资源限制
kubernetes包含多种资源限制,用来控制Container、Pod和多租户级别的资源共享。
![kubernetes调度详解_第2张图片](http://img.e-com-net.com/image/info8/809eb57023224c5bb7dfcea07acb15af.jpg)
每一个容器都可以指定*spec.container[].resources.limits.cpu*,*spec.container[].resources.limits.memory*,*spec.container[].resources.requests.cpu*和*spec.container[].resources.requests.memory*。对容器的资源限制是可选的,可以通过修改集群配置设置默认的资源限制属性。
LimitRange是在namespace级别设置的pod容量限制。所有在对应的命名空间中的Pod,都会受到LimitRange的资源限制。
Pod的每一类资源限制是Pod中对应的资源类型限制的总和,在调度的过程中调度器使用这个总和值和主机上可用容量做比较,决定主机是否满足资源需求。这种调度属于静态的资源调度,即使主机上的真实负载很低,只要容量限制不满足需求,也不能在主机上部署Pod。
Resource Quota是在命名空间级别设置的资源限制,主要是解决多租户共享资源的问题。Resource Quota中的cpu指对应的命名空间中所有Pod可以使用的cpu的总和,memory指对应的命名空间中所有Pod可以使用的memory的总和。
结语
kubernetes的调度器是完全插件化的实现,可以很方便的集成其它的调度器进行扩展。目前的调度器包含多个级别的资源限制,但是调度相关的实现相对简单,使用一些静态的规则去做调度,对于提升集群资源使用率、保障业务的QoS效果不是很明显。
kubernetes调度器以后可能支持使用不同的调度器去调度不同的任务,这个实验中的新特性对于有多种类型业务混合部署在kubernetes集群中的用户会有很大帮助。
kubernetes正在开发中的QoS借鉴Borg的成熟经验,重点关注提高集群资源利用率的问题,这个特性非常值得期待。