kubernetes调度策略分析
Kubernetes调度器根据特定的算法与策略将pod调度到工作节点上。在默认情况下,Kubernetes调度器可以满足绝大多数需求,例如调度pod到资源充足的节点上运行,或调度pod分散到不同节点使集群节点资源均衡等。但一些特殊的场景,默认调度算法策略并不能满足实际需求,例如使用者期望按需将某些pod调度到特定硬件节点(数据库服务部署到SSD硬盘机器、CPU/内存密集型服务部署到高配CPU/内存服务器),或就近部署交互频繁的pod(例如同一机器、同一机房、或同一网段等)。
Kubernetes中的调度策略主要分为全局调度与运行时调度2种。其中全局调度策略在调度器启动时配置,而运行时调度策略主要包括选择节点(nodeSelector),节点亲和性(nodeAffinity),pod亲和与反亲和性(podAffinity与podAntiAffinity)。Node Affinity、podAffinity/AntiAffinity以及后文即将介绍的污点(Taints)与容忍(tolerations)等特性。
本文着重介绍运行时调度策略。
设置节点label
Label是Kubernetes核心概念之一,其以key/value的形式附加到各种对象上,如Pod、Service、Deployment、Node等,达到识别这些对象,管理关联关系等目的,如Node和Pod的关联。
获取当前集群中的全部节点:
kubectl get nodes
为指定节点设置label:
kubectl label nodes <node-name> <label-key>=<label-value>
确认节点label是否设置成功:
kubectl get nodes -l ‘label_key=label_value’
选择节点(nodeSelector)
nodeSelector是目前最为简单的一种pod运行时调度限制,目前在Kubernetes1.7.x及以下版本可用。Pod.spec.nodeSelector通过kubernetes的label-selector机制选择节点,由调度器调度策略匹配label,而后调度pod到目标节点,该匹配规则属于强制约束。后文要讲的nodeAffinity具备nodeSelector的全部功能,所以未来Kubernetes会将nodeSelector废除。
nodeSelector举例:
设置label
$ kubectl label nodes bjo-rpt-re-002.dev.fwmrm.net disktype=ssd node "bjo-rpt-re-002.dev.fwmrm.net" labeled
查看满足非master节点且disktype类型为ssd的节点:
kubectl get nodes -l 'role!=master, disktype=ssd'
NAME STATUS AGE VERSION
bjo-rpt-re-002.dev.fwmrm.net Ready 39d v1.7.1
pod.yaml文件内容:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssd
创建pod:
kubectl create -f pod.yaml
查看pod nginx被调度到预期节点运行:
$ kubectl get po nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx 1/1 Running 0 10s 10.244.3.13 bjo-rpt-re-002.dev.fwmrm.net
内置节点label
Kubernetes自v1.4开始,节点有一些内置label,罗列如下:
- kubernetes.io/hostname
- failure-domain.beta.kubernetes.io/zone
- failure-domain.beta.kubernetes.io/region
- beta.kubernetes.io/instance-type
- beta.kubernetes.io/os
- beta.kubernetes.io/arch
内置label举例
yaml文件内容:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
kubernetes.io/hostname: bjo-ep-svc-017.dev.fwmrm.net
创建pod,并检查结果符合预期,pod被调度在预先设置的节点 bjo-ep-svc-017.dev.fwmrm.net:
$ kubectl get po nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx 1/1 Running 0 3m 10.244.1.58 bjo-ep-svc-017.dev.fwmrm.net
亲和性(Affinity)与非亲和性(anti-affinity)
前面提及的nodeSelector,其仅以一种非常简单的方式、即label强制限制pod调度到指定节点。而亲和性(Affinity)与非亲和性(anti-affinity)则更加灵活的指定pod调度到预期节点上,相比nodeSelector,Affinity与anti-affinity优势体现在:
- 表述语法更加多样化,不再仅受限于强制约束与匹配。
- 调度规则不再是强制约束(hard),取而代之的是软限(soft)或偏好(preference)。
- 指定pod可以和哪些pod部署在同一个/不同拓扑结构下。
亲和性主要分为3种类型:node affinity与inter-pod affinity/anti-affinity,下文会进行详细说明。
节点亲和性(Node affinity)
Node affinity在Kubernetes 1.2做为alpha引入,其涵盖了nodeSelector功能,主要分为requiredDuringSchedulingIgnoredDuringExecution与preferredDuringSchedulingIgnoredDuringExecution两种类型。前者可认为一种强制限制,如果Node的标签发生了变化导致其没有符合Pod的调度要求节点,那么pod调度就会失败。而后者可认为理解为软限或偏好,同样如果 Node 的标签发生了变化导致其不再符合pod的调度要求,pod 依然会调度运行。
Node affinity举例
设置节点label:
$ kubectl label nodes bjo-ep-dep-040.dev.fwmrm.net cpu=high
node "bjo-ep-dep-040.dev.fwmrm.net" labeled
$ kubectl label nodes bjo-ep-svc-017.dev.fwmrm.net cpu=mid
node "bjo-ep-svc-017.dev.fwmrm.net" labeled
$ kubectl label nodes bjo-rpt-re-002.dev.fwmrm.net cpu=low
node "bjo-rpt-re-002.dev.fwmrm.net" labeled
部署pod的预期是到非master节点(role!=master)、且CPU高配的机器上(cpu=high)。
查看满足条件节点:
$ kubectl get nodes -l 'cpu=high, role!=master'
NAME STATUS AGE VERSION
bjo-ep-dep-040.dev.fwmrm.net Ready 41d v1.7.1
pod.yaml文件内容如下:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: role
operator: NotIn
values:
- master
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: cpu
operator: In
values:
- high
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
检查结果符合预期,pod nginx成功部署到非master节点且CPU高配的机器上。
$ kubectl get po nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx 1/1 Running 0 32s 10.244.2.185 bjo-ep-dep-040.dev.fwmrm.net
pod亲和性(Inter-pod affinity)与反亲和性(anti-affinity)
inter-pod affinity与anti-affinity由Kubernetes 1.4引入,当前处于beta阶段,其中podAffinity用于调度pod可以和哪些pod部署在同一拓扑结构之下。而podAntiAffinity相反,其用于规定pod不可以和哪些pod部署在同一拓扑结构下。通过pod affinity与anti-affinity来解决pod和pod之间的关系。
与Node affinity类似,pod affinity与anti-affinity同样分为requiredDuringSchedulingIgnoredDuringExecution 和preferredDuringSchedulingIgnoredDuringExecution两种类型,前者被认为是强制约束,而后者后者可认为理解软限(soft)或偏好(preference)。
pod affinity与anti-affinity举例
本示例中假设部署场景为:期望is服务与oltp服务就近部署,而不希望与solr服务部署同一拓扑结构上。
yaml文件部分内容:
spec:
replicas: 1
template:
metadata:
labels:
app: is
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: NotIn
values:
- solr
topologyKey: kubernetes.io/hostname
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- oltp
topologyKey: beta.kubernetes.io/os
查看部署结果,is服务与oltp部署到了同一台机器,而solr被部署在其他机器上。
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
is-3059482752-5s14t 0/1 Running 1 1m 10.244.1.60 bjo-ep-svc-017.dev.fwmrm.net
oltp-282283610-kdvnp 1/1 Running 0 1m 10.244.1.53 bjo-ep-svc-017.dev.fwmrm.net
solr-908150957-rswlm 1/1 Running 0 1m 10.244.3.5 bjo-rpt-re-002.dev.fwmrm.net
亲和性/反亲和性调度策略比较
调度策略 匹配标签 操作符 拓扑域支持 调度目标
nodeAffinity 主机 In, NotIn, Exists, 否 pod到指定主机
DoesNotExist, Gt, Lt
podAffinity Pod In, NotIn, Exists, 是 pod与指定pod同一拓扑域
DoesNotExist
PodAntiAffinity Pod In, NotIn, Exists, 是 pod与指定pod非同一拓扑域
DoesNotExist
污点(Taints)与容忍(tolerations)
对于Node affinity,无论是强制约束(hard)或偏好(preference)方式,都是调度pod到预期节点上,而Taints恰好与之相反,如果一个节点标记为Taints,除非 Pod也被标识为可以耐受污点节点,否则该Taints节点不会被调度pod。Taints与tolerations当前处于beta阶段,
Taints节点应用场景比如用户希望把Kubernetes Master节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊资源预留给某些pod。pod不会再被调度到taint标记过的节点。
taint标记节点举例如下:
$ kubectl taint nodes bjo-ep-dep-039.dev.fwmrm.net key=value:NoSchedule
node "bjo-ep-dep-039.dev.fwmrm.net" tainted
如果仍然希望某个pod调度到taint节点上,则必须在 Spec 中做出Toleration 定义,才能调度到该节点,举例如下:
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
effect 共有三个可选项,可按实际需求进行设置:
- NoSchedule:pod不会被调度到标记为taints节点。
- PreferNoSchedule:NoSchedule的“preference”或“soft”版本。
- NoExecute:该选项意味着一旦Taint 生效,如该节点内正在运行的 Pod 没有对应 Tolerate 设置,会直接被逐出。
实现原理分析
Kubernetes基本架构如下:
其中, Controller Manager主要用于管理计算节点(Node Controller)以及Pod副本(Replication Controller)等,Scheduler根据特定的算法和策略调度Pod到具体的计算节点,Kubelet通过apiserver或者监控本地的配置文件(eg. Kubeadm创建的Kuernetes集群),通过docker daemon创建Pod的container。
Scheduler是Kubernetes的调度器,其作用是根据特定的调度算法和策略将Pod调度到指定的计算节点(Node)上,其做为单独的程序运行,启动之后会一直监听API Server,获取PodSpec.NodeName为空的Pod,对每个Pod都会创建一个绑定(binding)。
普通用户可以把Scheduler可理解为一个黑盒,黑盒的输入为待调度的Pod和全部计算节点的信息,经过黑盒内部的调度算法和策略处理,输出为最优的节点,而后将Pod调度该节点上 。
调度策略
Kubernetes的调度器以插件化形式实现的,方便用户定制和二次开发。用户可以自定义调度器并以插件形式与Kubernetes集成,或集成其他调度器,便于调度不同类型的任务。
Kubernetes调度器的源码位于kubernetes/plugin/中,大体的代码目录结构如下所示:
kubernetes/plugin/pkg/
`-- scheduler //调度相关的具体实现
|-- algorithm
| |-- predicates //节点筛选策略
| `-- priorities //节点打分策略
| `-- util
|-- algorithmprovider
| `-- defaults //定义默认的调度器
Scheduler创建和运行的过程, 对应的代码在plugin/pkg/scheduler/scheduler.go。
上面初步介绍了Kubernetes调度器,在新增Pod的过程中,调度器的调度策略被分成两个阶段:Predicates阶段和Priorities阶段。
调度过程
具体的调度过程,一般如下:
- 首先,客户端通过API Server的REST API/kubectl/helm创建pod/service/deployment/job等,支持类型主要为JSON/YAML/helm tgz。
- 接下来,API Server收到用户请求,存储到相关数据到etcd。
调度器通过API Server查看未调度(bind)的Pod列表,循环遍历地为每个Pod分配节点,尝试为Pod分配节点。调度过程分为2个阶段:
第一阶段:预选过程,过滤节点,调度器用一组规则过滤掉不符合要求的主机。比如Pod指定了所需要的资源量,那么可用资源比Pod需要的资源量少的主机会被过滤掉。
第二阶段:优选过程,节点优先级打分,对第一步筛选出的符合要求的主机进行打分,在主机打分阶段,调度器会考虑一些整体优化策略,比如把容一个Replication Controller的副本分布到不同的主机上,使用最低负载的主机等。
- 选择主机:选择打分最高的节点,进行binding操作,结果存储到etcd中。
- 所选节点对于的kubelet根据调度结果执行Pod创建操作。
预选(Predicates)与优选(Priorites)的策略罗列如下:
接下来,分别介绍调度器Scheduler的预选与优选两个阶段:
预选(Predicates)
下面分别对Predicates的策略进行介绍:
- NoDiskConflict:pod所需的卷是否和节点已存在的卷冲突。如果节点已经挂载了某个卷,其它同样使用这个卷的pod不能再调度到这个主机上.
- NoVolumeZoneConflict:检查给定的zone限制前提下,检查如果在此主机上部署Pod是否存在卷冲突。假定一些volumes可能有zone调度约束, VolumeZonePredicate根据volumes自身需求来评估pod是否满足条件。必要条件就是任何volumes的zone-labels必须与节点上的zone-labels完全匹配。节点上可以有多个zone-labels的约束(比如一个假设的复制卷可能会允许进行区域范围内的访问)。目前,这个只对PersistentVolumeClaims支持,而且只在PersistentVolume的范围内查找标签。处理在Pod的属性中定义的volumes(即不使用PersistentVolume)有可能会变得更加困难,因为要在调度的过程中确定volume的zone,这很有可能会需要调用云提供商。
- PodFitsResources:检查节点是否有足够资源(例如 CPU、内存与GPU等)满足一个Pod的运行需求。调度器首先会确认节点是否有足够的资源运行Pod,如果资源不能满足Pod需求,会返回失败原因(例如,CPU/内存 不足等)。这里需要注意的是:根据实际已经分配的资源量做调度,而不是使用已实际使用的资源量做调度。请参见我之前写的文章:《Kubernetes之服务质量保证(QoS)》。
- PodFitsHostPorts:检查Pod容器所需的HostPort是否已被节点上其它容器或服务占用。如果所需的HostPort不满足需求,那么Pod不能调度到这个主机上。 注:1.0版本被称之为PodFitsPorts,1.0之后版本变更为PodFitsHostPorts,为了向前兼容PodFitsPorts名称仍然保留。
- HostName:检查节点是否满足PodSpec的NodeName字段中指定节点主机名,不满足节点的全部会被过滤掉。
- MatchNodeSelector:检查节点标签(label)是否匹配Pod的nodeSelector属性要求。
- MaxEBSVolumeCount:确保已挂载的EBS存储卷不超过设置的最大值(默认值为39。Amazon推荐最大卷数量为40,其中一个卷为root卷,具体可以参考http://docs.aws.amazon.com/AWS … imits)。调度器会检查直接使用以及间接使用这种类型存储的PVC。计算不同卷的总和,如果卷数目会超过设置的最大值,那么新Pod不能调度到这个节点上。 最大卷的数量可通过环境变量KUBE_MAX_PD_VOLS设置。
- MaxGCEPDVolumeCount:确保已挂载的GCE存储卷不超过预设的最大值(GCE默认值最大存储卷值为16,具体可参见https://cloud.google.com/compu … types)。与MaxEBSVolumeCount类似,最大卷的数量同样可通过环境变量KUBE_MAX_PD_VOLS设置。
- MaxAzureDiskVolumeCount : 确保已挂载的Azure存储卷不超过设置的最大值。默认值是16。规则同MaxEBSVolumeCount。
- CheckNodeMemoryPressure : 判断节点是否已经进入到内存压力状态,如果是则只允许调度内存为0标记的Pod。检查Pod能否调度到内存有压力的节点上。如有节点存在内存压力, Guaranteed类型的Pod(例如,requests与limit均指定且值相等) 不能调度到节点上。QoS相关请参见我之前写的文章:《Kubernetes之服务质量保证(QoS)》。
- CheckNodeDiskPressure : 判断节点是否已经进入到磁盘压力状态,如果是,则不调度新的Pod。
- PodToleratesNodeTaints : 根据 taints 和 toleration 的关系判断Pod是否可以调度到节点上Pod是否满足节点容忍的一些条件。
- MatchInterPodAffinity : 节点亲和性筛选。
- GeneralPredicates:包含一些基本的筛选规则,主要考虑 Kubernetes 资源是否充足,比如 CPU 和 内存 是否足够,端口是否冲突、selector 是否匹配等:
- PodFitsResources:检查主机上的资源是否满足Pod的需求。资源的计算是根据主机上运行Pod请求的资源作为参考的,而不是以实际运行的资源数量
- PodFitsHost:如果Pod指定了spec.NodeName,看节点的名字是否何它匹配,只有匹配的节点才能运行Pod
- PodFitsHostPorts:检查Pod申请的主机端口是否已经被其他Pod占用,如果是,则不能调度
- PodSelectorMatches:检查主机的标签是否满足Pod的 selector。包括NodeAffinity和nodeSelector中定义的标签。
关于predicates更多详细的细节,请参考plugin/pkg/scheduler/algorithm/predicates/predicates.go
关于节点亲和性(Affinity)、Pod亲和性(Affinity)与反亲和性(anti-affinity)以及污点(Taints)与容忍(tolerations)等调度的使用,请参见本文上半部分。
优选(Priorities)
经过预选策略(Predicates)对节点过滤后,获取节点列表,再对符合需求的节点列表进行打分,最终选择Pod调度到一个分值最高的节点。Kubernetes用一组优先级函数处理每一个通过预选的节点(kubernetes/plugin/pkg/scheduler/algorithm/priorities中实现)。每一个优先级函数会返回一个0-10的分数,分数越高表示节点越优, 同时每一个函数也会对应一个表示权重的值。最终主机的得分用以下公式计算得出:
finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + … + (weightn * priorityFuncn)
目前支持优选的优先级函数包括以下几种:
LeastRequestedPriority:节点的优先级就由节点空闲资源与节点总容量的比值,即由(总容量-节点上Pod的容量总和-新Pod的容量)/总容量)来决定。CPU和内存具有相同权重,资源空闲比越高的节点得分越高。需要注意的是,这个优先级函数起到了按照资源消耗来跨节点分配Pod的作用。详细的计算规则如下:
cpu((capacity – sum(requested)) * 10 / capacity) + memory((capacity – sum(requested)) * 10 / capacity) / 2
LeastRequestedPriority举例说明:例如CPU的可用资源为100,运行容器申请的资源为15,则cpu分值为8.5分,内存可用资源为100,运行容器申请资源为20,则内存分支为8分。则此评价规则在此节点的分数为(8.5 +8) / 2 = 8.25分。
BalancedResourceAllocation:CPU和内存使用率越接近的节点权重越高,该策略不能单独使用,必须和LeastRequestedPriority组合使用,尽量选择在部署Pod后各项资源更均衡的机器。如果请求的资源(CPU或者内存)大于节点的capacity,那么该节点永远不会被调度到。
BalancedResourceAllocation举例说明:该调度策略是出于平衡度的考虑,避免出现CPU,内存消耗不均匀的事情。例如某节点的CPU剩余资源还比较充裕,假如为100,申请10,则cpuFraction为0.1,而内存剩余资源不多,假如为20,申请10,则memoryFraction为0.5,这样由于CPU和内存使用不均衡,此节点的得分为10-abs ( 0.1 - 0.5 ) * 10 = 6 分。假如CPU和内存资源比较均衡,例如两者都为0.5,那么代入公式,则得分为10分。
- InterPodAffinityPriority:通过迭代 weightedPodAffinityTerm 的元素计算和,并且如果对该节点满足相应的PodAffinityTerm,则将 “weight” 加到和中,具有最高和的节点是最优选的。
SelectorSpreadPriority:为了更好的容灾,对同属于一个service、replication controller或者replica的多个Pod副本,尽量调度到多个不同的节点上。如果指定了区域,调度器则会尽量把Pod分散在不同区域的不同节点上。当一个Pod的被调度时,会先查找Pod对于的service或者replication controller,然后查找service或replication controller中已存在的Pod,运行Pod越少的节点的得分越高。
SelectorSpreadPriority举例说明:这里主要针对多实例的情况下使用。例如,某一个服务,可能存在5个实例,例如当前节点已经分配了2个实例了,则本节点的得分为10*((5-2)/ 5)=6分,而没有分配实例的节点,则得分为10 * ((5-0) / 5)=10分。没有分配实例的节点得分越高。
注:1.0版本被称之为ServiceSpreadingPriority,1.0之后版本变更为SelectorSpreadPriority,为了向前兼容ServiceSpreadingPriority名称仍然保留。
- NodeAffinityPriority:Kubernetes调度中的亲和性机制。Node Selectors(调度时将pod限定在指定节点上),支持多种操作符(In, NotIn, Exists, DoesNotExist, Gt, Lt),而不限于对节点labels的精确匹配。
- NodePreferAvoidPodsPriority(权重1W):如果节点的 Anotation 没有设置 key-value:scheduler. alpha.kubernetes.io/ preferAvoidPods = “…”,则节点对该 policy 的得分就是10分,加上权重10000,那么该node对该policy的得分至少10W分。如果Node的Anotation设置了,scheduler.alpha.kubernetes.io/preferAvoidPods = “…” ,如果该pod对应的Controller是 ReplicationController或ReplicaSet,则该node对该policy的得分就是0分。
- TaintTolerationPriority : 使用Pod中tolerationList与节点Taint 进行匹配,配对成功的项越多,则得分越低。
另外在优选的调度规则中,有几个未被默认使用的规则:
- ImageLocalityPriority:根据Node上是否存在一个pod的容器运行所需镜像大小对优先级打分,分值为0-10。遍历全部Node,如果某个Node上pod容器所需的镜像一个都不存在,分值为0;如果Node上存在Pod容器部分所需镜像,则根据这些镜像的大小来决定分值,镜像越大,分值就越高;如果Node上存在pod所需全部镜像,分值为10。
- EqualPriority : EqualPriority 是一个优先级函数,它给予所有节点相等权重。
- MostRequestedPriority : 在 ClusterAutoscalerProvider 中,替换LeastRequestedPriority,给使用多资源的节点,更高的优先级。计算公式为:(cpu(10 sum(requested) / capacity) + memory(10 sum(requested) / capacity)) / 2
要想获得所有节点最终的权重分值,就要先计算每个优先级函数对应该节点的分值,然后计算总和。因此不管过程如何,如果有 N 个节点,M 个优先级函数,一定会计算 M*N 个中间值,构成一个二维表格:
最后,会把表格中按照节点把优先级函数的权重列表相加,得到最终节点的分值。上面代码就是这个过程,中间过程可以并发计算(下文图中的workQueue),以加快速度。
自定义调度
使用kube-schduler的默认调度就能满足大部分需求。在默认情况下,Kubernetes调度器可以满足绝大多数需求,例如调度Pod到资源充足的节点上运行,或调度Pod分散到不同节点使集群节点资源均衡等。前面已经提到,kubernetes的调度器以插件化的形式实现的, 方便用户对调度的定制与二次开发。下面介绍几种方式:
方式一:定制预选(Predicates) 和优选(Priority)策略
kube-scheduler在启动的时候可以通过 –policy-config-file参数可以指定调度策略文件,用户可以根据需要组装Predicates和Priority函数。选择不同的过滤函数和优先级函数、控制优先级函数的权重、调整过滤函数的顺序都会影响调度过程。
考官方给出的Policy文件实例:
"kind" : "Policy",
"apiVersion" : "v1",
"predicates" : [
{"name" : "PodFitsHostPorts"},
{"name" : "PodFitsResources"},
{"name" : "NoDiskConflict"},
{"name" : "NoVolumeZoneConflict"},
{"name" : "MatchNodeSelector"},
{"name" : "HostName"}
],
"priorities" : [
{"name" : "LeastRequestedPriority", "weight" : 1},
{"name" : "BalancedResourceAllocation", "weight" : 1},
{"name" : "ServiceSpreadingPriority", "weight" : 1},
{"name" : "EqualPriority", "weight" : 1}
],
"hardPodAffinitySymmetricWeight" : 10
方式二:自定义Priority和Predicate
上面的方式一是对已有的调度模块进行组合,Kubernetes还允许用户编写自己的Priority 和 Predicate函数。
过滤函数的接口:
// FitPredicate is a function that indicates if a pod fits into an existing node.
// The failure information is given by the error.
type FitPredicate func(pod *v1.Pod, meta PredicateMetadata, nodeInfo *schedulerca
自定义Predicates函数步骤如下:
- 在plugin/pkg/scheduler/algorithm/predicates/predicates.go文件中编写对象实现上面接口。
- 编写完过滤函数之后进行注册,让 kube-scheduler 启动的时候知道它的存在,注册部分可以在 plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go 完成,可以参考其他过滤函数(例如PodFitsHostPorts)的注册代码:kubernetes/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go factory.RegisterFitPredicate(“PodFitsPorts”, predicates.PodFitsHostPorts)。
- 在 –policy-config-file把自定义过滤函数写进去,kube-scheduler运行时可以执行自定义调度逻辑了。
- 自定义优先级函数,实现过程和过滤函数类似。
方式三:编写自己的调度器
除了上面2种方式外,Kubernetes也允许用户编写自己的调度器组件,并在创建资源的时候引用它。多个调度器可以同时运行和工作,只要名字不冲突。
使用某个调度器就是在Pod的spec.schedulername字段中填写上调度器的名字。Kubernetes提供的调度器名字是default,如果自定义的调度器名字是my-scheduler,那么只有当spec.schedulername字段是my-scheduler才会被调度。
调度器最核心的逻辑并不复杂。Scheduler首先监听apiserver ,获取没有被调度的Pod和全部节点列表,而后根据一定的算法和策略从节点中选择一个作为调度结果,最后向apiserver中写入binding 。比如下面就是用bash编写的简单调度器:
#!/bin/bash
SERVER='localhost:8001'
while true;
do
for PODNAME in $(kubectl --server $SERVER get pods -o json | jq '.items[] | select(.spec.schedulerName == "my-scheduler") | select(.spec.nodeName == null) | .metadata.name' | tr -d '"')
;
do
NODES=($(kubectl --server $SERVER get nodes -o json | jq '.items[].metadata.name' | tr -d '"'))
NUMNODES=${#NODES[@]}
CHOSEN=${NODES[$[ $RANDOM % $NUMNODES ]]}
curl --header "Content-Type:application/json" --request POST --data '{"apiVersion":"v1", "kind": "Binding", "metadata": {"name": "'$PODNAME'"}, "target": {"apiVersion": "v1", "kind"
: "Node", "name": "'$CHOSEN'"}}' http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/
echo "Assigned $PODNAME to $CHOSEN"
done
sleep 1
done
它通过kubectl命令从apiserver获取未调度的Pod(spec.schedulerName 是my-scheduler,并且spec.nodeName 为空),同样地,用kubectl从apiserver获取nodes的信息,然后随机选择一个node作为调度结果,并写入到apiserver中。
当然要想编写一个生产级别的调度器,要完善的东西还很多,比如:
- 调度过程中需要保证Pod是最新的,这个例子中每次调度 pod 的时候,它在 apiserver 中的内容可能已经发生了变化
- 调度过程需要考虑资源等因素(节点的资源利用率,存储和网络的信息等)
- 尽量提高调度的性能(使用并发来提高调度的性能)
可通过kubectl describe pod pod_name查看一个Pod采用的调度器,例如:
Pod优先级(Priority)和抢占(Preemption)
Pod优先级(Priority)
Pod优先级(Priority)和抢占(Preemption)是Kubernetes 1.8版本引入的功能,在1.8版本默认是禁用的,当前处于Alpha阶段,不建议在生产环境使用。
与前面所讲的调度优选策略中的优先级(Priorities)不同,前文所讲的优先级指的是节点优先级,而pod priority指的是Pod的优先级,高优先级的Pod会优先被调度,或者在资源不足低情况牺牲低优先级的Pod,以便于重要的Pod能够得到资源部署。
为了定义Pod优先级,需要先定义PriorityClass对象,该对象没有Namespace限制,官网示例:
apiVersion: scheduling.k8s.io/v1alpha1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
然后通过在Pod的spec. priorityClassName中指定已定义的PriorityClass名称即可:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
抢占(Preemption)
当节点没有足够的资源供调度器调度Pod、导致Pod处于pending时,抢占(preemption)逻辑会被触发。Preemption会尝试从一个节点删除低优先级的Pod,从而释放资源使高优先级的Pod得到节点资源进行部署。
回过头来,再重新看一下Pod的调度过程,也许会清晰很多。如下图示例:
注:使用 workQueue 来并行运行检查,并发数最大是 16。对应源码示例:orkqueue.Parallelize(16, len(nodes), checkNode)。
总结
本次分享主要介绍了Pod的预选与优选调度策略、Kubernetes调度的定制与开发,以及Kubernetes1.8的Pod优先级和抢占等新特性。