kubernetes调度实践及原理分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/redenval/article/details/81912161

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 affinityinter-pod affinity/anti-affinity,下文会进行详细说明。

节点亲和性(Node affinity)

Node affinity在Kubernetes 1.2做为alpha引入,其涵盖了nodeSelector功能,主要分为requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution两种类型。前者可认为一种强制限制,如果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阶段。

调度过程

具体的调度过程,一般如下:

  1. 首先,客户端通过API Server的REST API/kubectl/helm创建pod/service/deployment/job等,支持类型主要为JSON/YAML/helm tgz。
  2. 接下来,API Server收到用户请求,存储到相关数据到etcd。
  3. 调度器通过API Server查看未调度(bind)的Pod列表,循环遍历地为每个Pod分配节点,尝试为Pod分配节点。调度过程分为2个阶段:

    第一阶段:预选过程,过滤节点,调度器用一组规则过滤掉不符合要求的主机。比如Pod指定了所需要的资源量,那么可用资源比Pod需要的资源量少的主机会被过滤掉。

    第二阶段:优选过程,节点优先级打分,对第一步筛选出的符合要求的主机进行打分,在主机打分阶段,调度器会考虑一些整体优化策略,比如把容一个Replication Controller的副本分布到不同的主机上,使用最低负载的主机等。

  4. 选择主机:选择打分最高的节点,进行binding操作,结果存储到etcd中。
  5. 所选节点对于的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函数步骤如下:

  1. 在plugin/pkg/scheduler/algorithm/predicates/predicates.go文件中编写对象实现上面接口。
  2. 编写完过滤函数之后进行注册,让 kube-scheduler 启动的时候知道它的存在,注册部分可以在 plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go 完成,可以参考其他过滤函数(例如PodFitsHostPorts)的注册代码:kubernetes/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go factory.RegisterFitPredicate(“PodFitsPorts”, predicates.PodFitsHostPorts)。
  3. 在 –policy-config-file把自定义过滤函数写进去,kube-scheduler运行时可以执行自定义调度逻辑了。
  4. 自定义优先级函数,实现过程和过滤函数类似。

方式三:编写自己的调度器

除了上面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优先级和抢占等新特性。

文章参考如下:1, 2

猜你喜欢

转载自blog.csdn.net/redenval/article/details/81912161
今日推荐