• 我们知道,kubernetes把集群中所有的工作节点提供的计算资源和存储资源整合成一个大的资源池,统一承载Pod形式的各类工作负载,为用户提供了一个虚拟的逻辑视图。在底层物理视图上,这些工作负载终究还是运行在集群中中的某个特定节点上,而控制平面组件kuberneres Scheduler就是为工作负载挑选最佳运行节点的调度器。该组件的程序文件kube-scheduler实现了一款通用调度器,能较好的完成绝大多数的调度任务,但它也难以成为满足用户特定场景调度需求的最佳解决方案,于是kubernetes设计了易于增强和扩展的调度框架,以允许用户自行增强默认调度器的功能。

kubernetes调度器

  • 在kubernetes系统上,调度是指基于集群上当前各节点资源分配状态及约束条件为Pod选出一个最佳运行节点,并由对应节点上的kubelet创建并运行该Pod的过程。事实上,对于每个未绑定至任何工作节点的Pod对象,无论是新创建、被节点驱逐或节点故障灯,kubernetes scheduler都要使用调度算法从集群中挑选一个最佳目标节点来运行它,如下图
    资源调度 - 图1
  • kubernetes在v1.15版本之后引入的调度框架重构了此前使用的经典调度器架构,它以插件化的方式在多个扩展点实现了调度器的绝大多数功能,替代了经典调度器中以预选(predicate)函数和优选(priority)函数为核心的调度载体,并支持通过Scheduler Extender进行webhook式扩展的架构,为用户扩展使用自定义调度插件提供了更便捷的接口。

调度器基础

  • kubernetes内置了适合绝大多数场景中Pod资源调度需求的默认调度器,它支持同时使用算法基于原生及可定制的工具来选出集群中最适合运行当前Pod资源的一个节点,核心目标是基于资源可用性把Pod资源公平的分不到集群节点上。scheduler是API Server的客户端,它注册并监听所有Pod资源规范中的.spec.nodeName字段的状态变动信息,并对该值为”空”的每个Pod对象启动调度机制
  • 显然,Pod中的每个容器对资源的需求各不相同,不同Pod间也可能存在特定共存关系要求,甚至Node也会有特定的限制条件,从而调度器的调度决策会涉及单项或整体资源需求、硬件或软件甚至是策略的约束关系、亲和或反亲和性规范、数据的局部性以及工作负载间的干扰等方方面面。因此,调度器需要根据特定的调度要求对现有节点进行预选,以过滤掉那些无法满足Pod运行条件的节点,而后对满足过滤条件的各个节点进行打分,并按综合得分进行排序,最后从优先级排序结果中挑选得分最高节点作为适合目标Pod的最佳节点。如果中间任何一个步骤反悔了错误信息,调度器就会终止调度过程。调度流程的最后,调度程序在binding(绑定)的过程中将调度决策通知给API Server,如下图所示,而后由相应节点的代理程序kubelet启动Pod的创建和启动等过程
    资源调度 - 图2
    • 节点预选:基于一系列预选规则(例如NodeAffinity和VolumeBinding等)对每个节点进行检查,将那些不符合筛选条件的节点过滤掉;没有节点满足目标Pod的资源需求时,该Pod将被置于Pending状态,直到出现至少一个能够满足条件的节点为止
    • 节点优选:根据优先算法(例如ImageLocalicy和PodTopologySpread等)对预选出的节点打分,并根据最终得分进行优先级排序
    • 优先级排序结果中挑选出优先级最高的节点运行Pod对象,最高优先级节点数量多于一个时,则从中随机选择一个作为Pod可绑定的目标Node对象
  • 带有”通用”性质的调度器能在大多数Pod调度场景中工作的很好,但也必定存在无法满足的需求场景,例如根据GPU资源用量调度深度学习类的应用Pod的场景等,扩展新的调度方式成为这类场景中必然要解决的问题。kubernetes scheduler支持源代码二次开发、多调度器、scheduler extender和scheduler framework等几种扩展方式
  • 对kube-scheduler源代码进行二次开发(添加自定义的调度机制)的扩展方式,不仅对团队技术力量有着较高的要求,也必然会带来程序版本更新方面的难题。比较来说,同时提供多个调度器程序的扩展方式剥离了与原调度器程序的耦合关系,这种方式仅要求在那些需要使用自定义调度机制的Pod资源上通过.pod.spec.scheduler字段来指定使用的调度器名称即可,如下图所示。显然,多个独立的调度器彼此间在集群全局紧密的写作
    资源调度 - 图3
  • 另一种扩展方式是基于scheduler extender(webhook)在指定的扩展点对kube-scheduler进行功能扩展,如下图所示。但kube-scheduler这种扩展方式也存在不少的问题。例如它仅支持predicate、priority和bind这三个有限的扩展点,通过webhook进行扩展有一定程度上的性能开销,很难终止调度过程,也无法使用调度器默认的缓存功能等
    资源调度 - 图4
  • kubernetes自1.15版本引入调度框架(scheduling framework)为现有的调度程序添加了一组新的”插件”API,从而调度器支持以插件形式对kube-scheduler进行功能扩展,与scheduler Extender不同的是,调度框架支持多插件并存机制,这些插件根据其功能可以在调度的一个或多个扩展点对原有的调度器进行扩展。调度器插件可根据自身的功能注册到一个或多个扩展点并由调度器进行调用,它们或许能够影响调度决策,也可能仅提供有助于调度决策的信息
  • 调度框架这种插件式API不仅允许将调度器的大部分调度功能以插件方式实现,还能让调度”核心”保持简单且易于维护。因而,传统调度器中的节点预选、优选(打分)和绑定等相关的函数代码也能转而实现为新的调度框架下的插件
  • 调度框架将每次调度一个Pod的整个过程进一步细分为”调度周期”和”绑定周期”两个阶段,前者负责为Pod选择一个最佳调度节点,后者为完成Pod到节点的绑定执行必要的检测或初始化操作等,它们联合起来称为”调度上下文”,调度框架提供了多个扩展点,事实上,其中的filter相当于传统调度器上的predicate,score则是priority,bind则保持了原有的名称。下面是调度框架支持的各扩展点的简单功能描述
    • QueueSort: 注册到该扩展点的插件负责对调度队列中的Pod资源进行排序,但一次仅支持启用单个插件;Pod排序队列的存在使得优先级调度及优选级抢占称为可能
    • PreFilter: PreFilter类的插件用于预处理Pod相关信息,或者检查Pod和集群必须满足的条件,任何错误都将会导致调度过程终止而返回
    • Filter: 该类型的插件负责过滤无法满足Pod资源运行条件的节点,对于每一个节点,调度程序都会按顺序调用每个插件对其进行逐一评估,任何插件拒绝该节点都会直接导致该插件被排除,且不再由后续的插件进行检查。节点过滤能够以并行方式进行,并且在一个调度周期内可以多次调用该扩展点上的插件
    • PostFilter: 该类插件对成功通过过滤插件检查的节点执行过滤后创建,比较早期版本的调度框架不支持prescore,该扩展点后来被重命名为prescore,而kubernetes1.19版本又重新添加了该扩展点。
    • prescore:该类插件对成功通过过滤插件检查的节点进行评估,并生成可由score插件共享的状态结果,任何错误都将导致调度过程终止而返回
    • score和Normalizescore:score类插件负责对成功通过过滤的节点进行评分和排序,对于每个节点,调度程序会调用每个插件为其打分;Normalizescore扩展点中注册的插件可为score扩展点钟的同名插件提供节点得分修正逻辑,以使得其满足特定的规范,不提供Normalizescore插件的话,score插件自身必须确保得分满足该规范,否则调度周期将被终止
    • Reserve: 信息类扩展点,一般用于为给定的Pod保留目标节点上的特定资源时提供状态信息,以避免将Pod绑定到目标节点的过程中发生资源争用
    • Permit: 该类型插件用于准许(approve)、阻止(deny)或延迟(wait)Pod资源的绑定,所有插件都返回approve才意味着该Pod可进入绑定周期,任意一个插件返回deny都会导致Pod重新返回调度队列,并触发unreserve类型的插件,而返回wait则意味着Pod将保持在该阶段,直接批准而返回approve或超时而返回deny
    • PreBind: 负责执行绑定Pod之前所需要的所有任务,例如设置存储卷等;任何插件返回错误都会导致该Pod被重新打回调度队列
    • Bind: 所有的prebind类插件完成之后才能运行该类插件,以将Pod绑定至目标节点上,各类插件依照其配置的顺序进行调用,或者由某个特定的插件全权”处理”该Pod,从而跳过后续的其他插件
    • PostBind: 信息类扩展点,相关插件在Pod成功绑定之后被调用,通常用于设置清单关联的资源
    • Unreserve:信息类扩展点,对于reserve扩展点预留了资源的Pod对象,因被其他扩展点插件所拒绝时,可由该扩展点通知取消为其预留的资源;一般来说,注册到该扩展点的插件也必将注册到reserver扩展点之上
  • 调度器框架允许单个调度器实现多个扩展点,这意味着,我们可以按需在一个插件中只实现一个扩展点,也可以同时实现多个扩展点,例如,内置的插件InterPodAffinity同时实现了Prefiler、filter、prescore和score扩展点。不过,除非特别有必要,否则应该尽力避免将同一个功能需求在不同的插件中重复实现

经典调度策略

  • 如前所述,kubernetes通用调度程序提供的经典调度策略使用predicate和priority函数实现核心调度功能,并支持多调度器和extender的扩展方式。预选函数是节点过滤器,负责根据待调度Pod的计算资源和存储资源需求,以及节点亲和关系及反亲和关系规范等来过滤节点。优选函数是节点优先级排序工具,负责基于各节点上当前的资源水位、Pod与Node的亲和或反亲和关系、Pod之间的亲和或反亲和关系,以及尽可能将同一组Pod资源合理分散到不同节点上的方式对过滤后的节点进行优选级排序,最高优级的节点即为待调度Pod资源的最近运行节点

节点预选

  • 简单来说,预选策略就是节点过滤器,例如节点标签必须能够匹配到Pod资源的标签选择器(由MatchNodeSelector实现的规则),以及Pod容器的资源请求量不能大于节点上剩余的可分配资源(由PodFistResources实现的规则)等。执行预选操作时,调度器将对每个节点基于配置使用的预选函数策略以特定的次序逐一筛查,并根据一票否决制进行节点淘汰。若预选后不存在任何一个满足条件的节点,Pod就会被置于Pending状态,直到集群当中至少出现一个节点可用为止
  • 预选操作回针对所有或特定样本数量的节点进行,对于每一个节点,调度器将使用配置的预选函数以特定次序进行逐一筛查,其中任何一个预选函数的否决都将导致该节点被过滤掉。若不存在任何一个满足条件的节点,则Pod将被置于pending状态,直到至少有一个节点可用。kubernetes的每个版本支持的预选函数都可能会发生变动,下图给出了kubernetes 1.17支持的各项预选函数以及它们的应用次序,实现边框标识的为kube-scheduler程序默认启用的函数。
    资源调度 - 图5
  • 这些预选函数根据指定判定标准及各Node对象和当前Pod对象能否适配,按照事先的主要目标答题可以分为如下几类
    • 节点存储卷数量限制检测:MaxEBSVolumeCount、MaxGCEPDVolumeCount、MaxCXIVolumeCount、MaxAzureDiskVolumeCount和MaxCinderVolumeCount
    • 检测节点状态是否适合运行Pod: CheckNodeUnschedulable和CheckNodeLabel-Presence
    • Pod与节点的匹配度检测:Hostname、PodFitsHostPorts、MatchNodeSelector、NoDisk-Conflict、PodFitsResources、PodToleratesNodeTaints、PodToleratesNodeNoExecuteTains、CheckVolumeBinding和NoVolumeZoneConflict
    • Pod间的亲和关系判定:MatchInterPodAffinity
    • 将一组Pod打散至集群或特定的拓扑结构中:CheckServiceAffinity和EvenPods-SPread
  • 在kubernetes scheduler上启用相应的预选函数才能事先相关调度机制的节点过滤需求,下面这些是kubernetes中支持的各预选函数的简要功能,其中ServiceAffinity和CheckNodeLabelPresence支持自定义配置,余下的均为静态函数
    • CheckNodeUnschedulable: 检查节点是否被标识为Unschedulable。以及是否将Pod调度到该类节点至少。
    • HostName:若Pod资源通过spec.nodeName明确指定了要绑定的目标节点,则节点名称与该字段值相同的节点才会被保留。
    • PodFistHostPorts: 若Pod容器定义了ports.hostPort属性,该预选函数负责检查其值指定的端口是否已被节点上的其他容器或服务所占用,该端口已被占用的节点将被过滤
    • MatchNodeSelector:若Pod资源规范上定义了spec.nodeSelector字段,则仅拥有匹配该标签选择器的标签节点才会被保留
    • NoDiskConflict:检查Pod对象请求的存储卷在此节点是否可用,不存在冲突则通过检查
    • PodFistResources:检查节点是否有足够资源(例如CPU、内存和GPU等)满足Pod的运行需求。节点声明其资源可用容量,而Pod定义资源需求,于是调度器会判断节点是否有足够的可用资源运行Pod对象,无法满足则返回失败原因(例如,CPU或内存资源不足等)。调度器评判资源消耗的标准是节点已分配资源量(各容器的requests值之和),而非节点上各Pod已用资源量,但那些在注解中标记为关键性的Pod资源则不受该预选函数控制
    • PodToleratesNodeTaints: 检查Pod的容忍度(spec.tolerations字段)是否能够容忍节点上的污点,不过它仅关注带有NoSchedule和NoExecute两个效用标识的五点
    • CheckNodeLabelPresence:检查节点上某些标签的存在性,要检查的标签以及其可否存在于用户的定义。集群中的部署节点以regions/zones/racks类标签的拓扑方式编制,且基于该类标签对相应节点进行了位置标识时,预选函数可以根据位置标识将Pod调度至此类节点之上
    • CheckServiceAffinity:根据调度的目标Pod对象所属的service资源已关联的其他Pod对象的位置(所运行节点)来判断当前Pod可运行的目标节点,目的在于将同一service对象的Pod放置在同一拓扑内(如同一个rack或zone)的节点上以提高效率
    • MaxEBSVolumeCount:检查节点上已挂载的EBS存储卷数量是否超过了设置的最大值
    • MaxGCEPDVolumeCount:检查节点上已挂载的GCE PD存储卷数量是否超过了设置的最大值,默认值为16
    • MaxCSIVolumeCount:检查节点上已挂载的CSI存储卷数量是否超过了设置的最大值
    • MaxAzureDiskVolumeCount:检查节点上已挂载的AzureDisk存储卷数量是否超过了设置的最大值,默认值为16
    • MaxCinderVolumeCount:检查节点上已挂载的Cinder存储卷数量是否超过了设置的最大值
    • CheckVolumeBinding:检查节点上已绑定和未绑定的PVC是否能够满足Pod的存储卷需求,对于已绑定的PVC,此预选函数检查给定节点是否能兼容相应PV,而对于未绑定的PVC,预选函数搜索那些可满足PVC申请的可用PV,并确保它可与给定节点兼容
    • NoVolumeZoneConflict:在给定了存储故障域的前提下,检测节点上的存储卷是否可满足Pod定义的需求
    • EventPodsSpread:检查节点是否能满足Pod规范中topologySpreadConstraints字段定义的约束,以支持Pod的拓扑感知调度
    • MatchInterPodAffinity: 检查给定节点是否能满足Pod对象的亲和性或反亲和性条件,用于事先Pod亲和性调度或反亲和性调度

节点优选

  • 成功通过预选函数过滤的节点将生成一个列表,调度流程随后进入优先级排序阶段。各优选函数主要评定成功通过过滤检查的节点对运行该Pod资源的适配程度。对于每个节点,调度器会使用各个拥有权重值的优选函数分别为其打分(0-10之间的分数,其中0表示不适用,10表示最适合托管该Pod对象),优选函数得出的初始分值乘以权重为该函数最终得分,而各个优选函数的最终分值之和是该节点的最终得分
  • 预选策略筛选并生成一个节点列表后即进入第二段的过程。在这个过程中,调度器向每个通过预选的节点传递一系列的优选函数(如BalancedResourceAllocation和TainTolerationPriority等)来己算其优先级分值
  • 另外,调度器还支持为每个选择器指定一个简单的由正数值表示的权重,进行节点优先级分值的计算时,它首先将每个优选函数的计算得分乘以其权重(大多数优先级的默认权重为1),任何将所有优选函数的得分相加从而得出节点的最终优先级分值。权重属性赋予了管理员定义优选函数倾向性的能力
  • 下图是各优选函数及其功能,实现边框标识的为kube-scheduler程序默认启用的优选函数。这些优选函数依然可大体氛围节点资源对Pod的适配、节点对Pod的亲和性或反亲和性、Pod间的亲和性或反亲和性,以及Pod打散为几个评估目标
    资源调度 - 图6
  • 下面对这些经典优选函数进行介绍
    • LeastRequestedPriority: 优先将Pod打散至集群中的各节点之上,以让各节点有近似的计算资源消耗比例,适用于集群规模变动较少的场景;其分值由节点空闲资源与节点总容量的比值己算而来,即由CPU或内存资源的总容量减去节点上已有Pod对象需求的容量总和,再减去当前要创建的Pod对象的需求容量得到的结果除以总容量。CPU和内存具有相同权重,资源空闲比例越高的节点得分也就越高,其己算公式为: ( cpu (( capacity - sum(requested)) 10 / capacity) + memroy((capacity - sum(requested)) 10 / capacity)) / 2
    • MostRequestsedPriority: 与优选函数LeastRequestedPriority评估节点得分的方法相似,但二者不同的是,当前函数将给予己算资源占用比列更大的节点以更高的得分,己算公司为:(cpu ((sum (requested)) 10 / capacity) + memroy ((sum (requested)) 10 / capacity)) / 2。该函数的目标在于优先节点让节点以满载的方式承载Pod资源,从而能够使用更少的节点数,因而较适用于节点规模可弹性伸缩的集群中,以最大化的节约节点数量
    • BalancedResourceAllocation: 以CPU和内存资源占用率的相近程度作为评估标准,二者越接近的节点权重越高。该优选函数不能单独使用,它需要和Least-RequestedPriority组合使用来平衡优化节点资源的使用状态,选择在部署当前Pod资源后该系统资源更为均衡的节点
    • ResourceLimitsPrioriyty:以是否能够满足Pod资源限制为评估标准,能够满足Pod对CPU或内存资源限制的节点计1分,节点未声明可分配资源或Pod未定义资源限制时不影响节点计分。
    • requestedTocapacityRatio:该函数允许用户自定义节点各类资源(例如CPU和内存等)的权重,以便提高大型集群中稀缺资源的利用率;该函数的行为可以通过名为requestedToCapacityRatioArguments的配置选型进行控制,它由shape和resources两个参数组成
    • NodeAffinityPriority:节点亲和调度机制,它根据Pod资源规范中的spec.node-selector;对给点节点进行匹配度检查,成功匹配到的条目越多则节点得分越高。不过,其评估过程使用表示首选亲和的标签选择器PreferredDuringSchedulingIgnoredDuringExecution
    • ImageLocalityPriority: 镜像亲和调度机制,它根据给定节点上是否拥有运行当前Pod对象的容器所依赖的镜像文件来己算节点得分值,没有Pod对象所依赖的任何镜像文件的节点得分为0,而存在镜像文件的各节点中,被Pod依赖到的镜像文件的体积之和越大的节点得分越高
    • TaintTolerationPriority: 基于Pod资源对节点的污点容忍调度偏好进行优先级评估,它将Pod对象的tolerations列表与节点的污点进行匹配度检查,成功匹配的条目越多,则节点得分越低
    • SelectorSpreadPriority:尽可能分散Pod至不同节点上的调度机制,它首先查找标签选择器能匹配当前Pod标签的ReplicationController、Replicaset和statefulset等控制器对象,而后查找可由这类对象的标签选择器匹配的现存各Pod对象及其所在的节点,而运行此类Pod对象越少的节点得分越高。简单来说,如其名称所示,此优选函数尽量把同一标签选择器匹配到的Pod资源打散到不同的节点上运行
    • ServiceSpreadingPriority:类似于SelectorSpreadPriority,它首先查找标签选择器能匹配当前Pod标签的service对象,而后查找可由这类service对象的标签选择器匹配的现存各Pod对象及其所在的节点,而运行此类Pod对象越少的节点得分越高
    • EvenPodsSpreadPriority: 用于将这一组特定的Pod对象在指定的拓扑结构上进行均衡打散,打散条件定义在Pod对象的spec.topologySpreadConstraints字段上,它内嵌labelSelector字段指定标签选择器以匹配符合条件的Pod对象,使用topologykey字段指定目标拓扑结构,使用maxSkew描述最大允许的不均衡数量,而无法满足指定条件时的评估策略则由whenUnsatisfiable字段定义,它有两个可用取值,默认值DoNotSchedule表示不予调度,而scheduleAnyway则表示满足最小不均衡的标准进行调度
    • EqualPriority:设定所有节点具有相同的权重1。
    • InterPodAffinityPriority:遍历Pod对象的亲和性条目,并将那些能够匹配到给定节点的条目的权重相加,结果值越大的节点得分越高
    • NodePreferAvoidPodsPriority:此优选函数权限默认为10000,它根据节点是否设置了注解信息scheduler.alpha.kubernetes.io/preferAvoidPods来计算其优先级。计算方式是,给定的节点无此注解信息时,其得分为10乘以权重10000,存在此注解信息时,由ReplicationController或ReplicaSet控制器管控的Pod对象的得分为0,其他Pod对象会被忽略(得最高分)
  • 这些优选函数中,LeastRequestedPriority和BalancedResourceAllocationPriority的目标是根据节点的可分配资源状态优先打散Pod并均衡分配至集群节点,而MostRequestedPriority的目标则刚好相反,它是优先将Pod”堆满”一个节点后再启用下一个,因而他们彼此之间互斥
  • 另外,除了程序中的默认位置,kube-scheduler启用的预选函数和优选函数也能够通过称为调度策略的配置文件进行自定义。自定义的调度配置文件遵循json格式且必须命名为policy.cfg,启用后它将完全覆盖默认的调度策略,因此需要用到的任何预选函数或优选函数必须要在该文件中显式声明

调度器插件

  • 随着kubernetes版本的快速演进,内置的插件也可能会随之快速变动,目前的1,19版本中提供了20多个调度器插件,调度周期中与过滤和打分相关的插件同样大体用于检查节点与Pod的匹配度、节点自身的调度限制、Pod与节点的亲和性或反亲和性、Pod间的亲和性和反亲和性,以及将Pod打散到集群或指定拓扑结构中的节点上等不同的目标
    • Priority:用于为调度队列提供基于Pod优先级的排序方式,仅实现了QueueSort扩展点
    • DefaultPreemption: 用于为调度流程提供默认的抢占逻辑,仅实现了PostFilter扩展点
    • ImageLocality:功能类似于同名的优选函数,仅负责实现score扩展点
    • TaintToleration:用于实现基于Pod容忍度和Node污点的调度机制,它实现了filter、Prescore和score这三个扩展点
    • NodeName:纯过滤器,仅实现了filter扩展点,负责检查节点名称与Pod资源规范中的spec.nodeName值是否一致
    • NodePorts:检查Pod请求使用的节点端口在该节点上是否可用,实现了prefiler和filter扩展点
    • NodePreferAvoidPods:根据节点上的主节scheduler.alpha.kubernetes.io/preferAvoidPods对节点进行打分默认权重较高(10000),它仅实现了score扩展点
    • NodeAffinity:负责实现基于Pod规范的nodeSelector或节点亲和(nodeAffinity)的调度机制,它支持filter和score扩展点
    • NodeUnschedulable: 纯过滤器,仅实现了filter扩展点,负责将那些spec.unschedulable字段值为true的节点过滤掉
    • NodeLabel:根据节点上配置的标签进行节点过滤和打分,实现了filter和score两个扩展点
    • VolumeBinding: 检查Pod请求的存储卷在节点上是否可用,实现了filter、reserve、Unreserve和Prebinding扩展点
    • VolumeRestrictions:检查i而电商挂载的某种特定类型的存储卷是否能满足限制,仅实现了filter扩展点
    • VolumeZone: 检查节点上的存储卷是否能满足zone限制,仅实现了filter扩展点
    • InterPodAffinity:用于实现Pod间的亲和或反亲和调度,实现了prefilter、filter、prescore和score扩展点
    • DefaultTopologySpread:倾向于将service、replicasets或statefulsets的Pod对象分别打散部署到集群当中的不同节点之上;该插件实现的扩展点有presocre和score两个
    • PodTopologySpread:用于控制Pod在集群region/zone/rack/node故障域或者用户自定义的拓扑域中的分布,是支撑PodtopologySpreadConstraints特性的基础插件;该插件实现放是的扩展点有prefilter、filter、prescore和score
    • ServiceAffinity:同一service下的Pod对象的反亲和调度机制,倾向于将该Pod资源同其自身所属的service对象的其他Pod分散运行于不同的节点,实现的扩展点有prefilter、filter和score
  • 不过,经典调度器中的预选函数PodFistResources,以及优选函数LeastRequested、MostRequested、BalancedResourceAllocation和RequestedTocapacityRatio的功能被整合在名为noderesources的插件目录下,但它们仍以独立的插件名称工作,因而仍可被视作独立的调度器插件,下面是这几个插件的功能说明
    • NodeResourcesFit: 在功能上对应于预选函数PodFistResources,仅用于实现filter扩展点
    • NodeResourcesLeastAllocated:功能上对应于优选函数LeastRequestedPriority,仅用于实现score扩展点
    • NodeResourcesBalancedAllocation:功能上对应于优选函数BalancedResource-Allocation,仅用于实现score扩展点
    • NodeResourcesMostAllocated:功能上对应于优选函数MostReqeuestedPriority,且功能与NodeResourcesLeastAllocated互斥,二者通常不应该同时使用,仅用于实现score扩展点
    • RequstedToCapacityRatio: 功能上对应于优选函数RequestedToCapacityRatio,通常仅用于实现score扩展点
  • 另外,经典调度器中使用的检测节点上特定存储类型存储卷数量限制的预选函数也被整合进同一个目录(nodevolumelist)中,但他们各自仍旧作为独立的插件使用,且仅能用于实现filter扩展点
    • NodeVolumeLimits: 功能同预选函数MaxCSIVolumeCount,检测节点是否满足指定的CSI存储插件类型上的存储卷数量闲资
    • EBSLimits:功能同预选函数MaxEVSVolumeCount,检测节点是否满足EBS存储卷数量限制,默认为16个
    • AzureDiskLimits:功能同预选函数MaxAzureDiskVolumeLimit,检测节点是否满足AzureDisk存储卷数量限制
    • CinderLimits:功能同预选函数MaxCinderVolumeCount,检测节点是否可满足cinder存储卷数量限制
    • GCEPDLimits:功能同预选函数MaxGCEPDVolumeCount,检测节点是否满足GCE PD存储卷数量限制
  • 目前,可用于绑定周期的内置调度器插件的defaultBinder一个,它为调度流程提供默认的Bind机制,而且仅实现了Bind扩展点。
  • 与经典调度器使用调度策略进行配置有所不同的是,调度框架使用调度配置来为调度器提供自定义的配置信息。启用了调度框架的kube-scheduler会自动创建一个名为default-scheduler的profile文件,它默认启用除NodeResourcesMostAllocated、RequestedToCapacityRatio、CinderLimits、Nodelabel和ServiceAffinity之外的其他调度插件

配置调度器

  • kubernetes官网:https://kubernetes.io/zh/docs/reference/scheduling/config/
  • 调度器程序kube-scheduler使用KubeSchedulerConfiguration格式的配置文件,且支持通过--config选项加载用户自定义的遵循该格式的配置文件。该类配置文件遵循YAML或JSON数据规范,隶属于kubescheduler.config.k8s.io这一API群组,kubernetes 1.18版本之后的版本才能支持v1alpha2,且自kubernetes 1.20版本晋升为v1beta1
  1. apiVersion: kubescheduler.config.k8s.io/v1beta1
  2. kind: KubeSchedulerConfiguration
  3. AlgorithmSource: #指定调度算法配置源,从v1alpha2版本起该配置进入废弃阶段
  4. Policy: #基于调度策略的调度算法配置源
  5. File: #文件格式的调度策略
  6. Path: <string> #调度策略文件policy.cfg的位置
  7. ConfigMap: #ConfigMap格式的调度策略
  8. Namespace: <string> #调度策略ConfigMap所属的名称空间
  9. Name: <string> #ConfigMap资源的名称
  10. Provider: <string> #配置使用的调度算法的名称,例如DefaultProvider
  11. LeaderElection: {} #多kube-scheduler实例并存时使用的领导选举算法
  12. ClientConnection: {} #与API Server通信时提供给代理服务器的配置信息
  13. HealthBindAddress: <string> #响应健康状态监测的服务器监听的地址和端口
  14. MetricsBindAddress: <string> #响应指标抓取请求的服务器监听的地址和端口
  15. DisablePreemption: <bool> #是否禁用抢占模式,false表示不禁用
  16. PercentageOfNodesToScore: <int32> #需要过滤出的可用节点百分比
  17. BindTimeOutSeconds: <int64> #绑定操作的超时时长,必须使用非负数
  18. PodInitialBackoffSeconds: <int64> #不可调度Pod的初始补偿时长,默认值为1
  19. PodMaxBackoffSeconds: <int 64> #不可调度Pod的最大补偿时长,默认为10
  20. Profiles: <[]string> #加载的kubeschedulerprofile配置列表,v1alpha2支持加载多个配置列表
  21. Extenders: <[]Extender> #加载的Extender列表
  • 由上面的KubeSchedulerConfiguration配置格式可知,目前kubernetes scheduler支持调度策略和调度配置两种调度器配置机制,前者遵循传统调度器的预选、优选和选择等工作逻辑,而后者则仅能够由新的调度框架通过扩展点来支持
  • 小提示:kube-scheduler默认会生成kubeschedulerconfiguration格式的配置,我们可通过其命令选项--write-config-to将其输出到指定的文件中

调度策略

  • 调度策略:https://kubernetes.io/zh/docs/reference/scheduling/policies/
  • 调度策略通过指定预选策略和优选函数分别实现节点过滤与计分功能,相关的策略可保存在配置文件或ConfigMap资源中,而后再KubeSchedulerConfiguration配置中由AlgorithmSource字段引用,或者直接在kube-scheduler程序上使用选项进行指定。传统的调度策略简要配置格式如下所示
  1. apiVersion: kubescheduler.config.k8s.io/v1
  2. kind: Policy
  3. Predicates: <[]object> #Predicate对象列表
  4. - Name: <string> #Predicate名称
  5. Argument: <Object> #可选字段
  6. priorities: <[]Object> #Priority对象列表
  7. - Name: <string> #Priority名称
  8. Weight: <string> #权重
  9. Argument: <Object> #可选字段,仅允许自定义配置的Priority支持
  10. Extenders: <[]object> #加载的Extenders列表
  11. HardPodAffinitySymmetricWeight: <int> #Pod强制亲和调度关联的隐式首选亲和规则权重
  12. AlwaysCheckAllPredicates: <bool> #是否禁用Predicate进行节点过滤时的短路模式
  • 下面示例(policy.cfg)定义了一个不同于程序默认配置的调度策略,它启用了Even-PodsSpreadPriority策略支持的Pod规范有topologySpreadConstraints定义的约束规则
  1. apiVersion: kubescheduler.config.k8s.io/v1
  2. kind: Policy
  3. predicates:
  4. - name: GeneraIPredicates
  5. - name: MaxCSIVolumeCountPred
  6. - name: CheckVolumeBinding
  7. - name: EvenPodsSpread
  8. - name: MatchInterPodAffinity
  9. - name: CheckNodeUnschedulable
  10. - name: NoDiskConflict
  11. - name: NoVolumeZoneConflict
  12. - name: MatchNodeSelector
  13. - name: PodToleratesNodeTaints
  14. priorities:
  15. - {name: LeastRequestedPriority,weight: 1}
  16. - {name: BalancedResourceAllocation, weight: 1}
  17. - {name: ServiceSpreadingPriority,weight: 2}
  18. - {name: EvenPodsSpreadPriority,weight: 1}
  19. - {name: TaintTolerationPriority,weight: 1}
  20. - {name: ImageLocalityPriority,weight: 2}
  21. - {name: SelectorSpreadPriority,weight: 1}
  22. - {name: InterPodAffinityPriority,weight: 1}
  23. - {name: EqualPriority,weight: 1}
  • 我们随后为kube-scheduler提供一个自定义的KubeSchedulerConfiguration配置文件,让它通过文件路径来引用自定义的调度策略。下面的示例(kubeschedconf-v1alpha1-demo.yaml)指定了基于v1alpha1的API版本从指定的文件处加载调度策略配置文件policy.cfg
  1. apiVersion: kubescheduler.config.k8s.io/v1beta1
  2. kind: KubeSchedulerConfiguration
  3. bindTimeOutSeconds: 600
  4. algorithmSource:
  5. policy:
  6. file:
  7. path: /etc/kubernetes/scheduler/policy.cfg
  8. provider: DefaultProvider
  9. clientConnection:
  10. kubeconfig: "/etc/kubernetes/scheduler.conf"
  11. disablePreemption: false
  • 将这两个文件放在控制平面节点k8s-master-01主机上的某一个目录,我这里放在了/etc/kubernetes/scheduler/,然后编辑/etc/kubernetes/manifests/kube-scheduler.yaml文件,为kube-scheduler添加存储卷以及—config选项来引用它,其中关键的配置部分如下
  1. spec:
  2. containers:
  3. - command:
  4. - kube-scheduler
  5. ...
  6. - --config=/etc/kubernetes/scheduler/kubeschedconf-v1alpha1-demo.yaml
  7. ...
  8. volumeMounts:
  9. ...
  10. - mountPath: /etc/kubernetes/scheduler
  11. name: scheduler-config
  12. readOnly: true
  13. volumes:
  14. ...
  15. - hostPath:
  16. path: /etc/kubernetes/scheduler
  17. type: Directory
  18. name: scheduler-config
  • 待kubescheduler的Pod重新加载配置并启动完成后,我们可以在日志中看到加载指定Predicate和Priority函数的信息。接下来就可以通过创建Pod对象来测试自定义调度策略的生效效果。另外,通过KubeSchedulerConfiguration引用指定的Extender,我们还能够使用调度器的经典扩展方式来添加外挂扩展

调度器配置

  • 调度器配置:https://kubernetes.io/zh/docs/reference/scheduling/config/
  • 调度配置支持管理员为调度框架的各扩展点指定要调用的插件,相关的配置定义在KubeSchedulerConfiguration配置文件的profile字段中。自API群组kubescheduler.config.k8s.io的v1alpha2版本起开始,kube-scheduler支持同时使用多个profile,每个profile拥有唯一的名称标识,并可由Pod资源在spec.schedulerName显式调用。调度框架默认会创建一个名为default-scheduler的配置文件,它启用了大部分的调度插件,而且是Pod资源默认使用的调度器。Profile的配置格式如下所示,配置默认的调度器或添加新的调度器时,需要在程序默认启用的调度插件的基础上”启用”或”禁用”指定的插件
SchedulerName: <string> #当前Profile的名称
Plugins: <object> #插件配置对象
  <ExtendPoint>: <object> #配置指定的扩展点,每个扩展点按名称指定
  enabled: <[]Plugin> #启用的插件列表
  - name: <string> #启用的插件名称
    weight: <int32> #插件权重,仅Score扩展点支持
  disabled: <[]Plugin> #禁用的插件列表
  - name: <string> #需要禁用的插件名称
    Weight: <int32> #插件权重
PluginConfig: <[]Object> #插件特有的配置
- name: <string> #插件名称
Args: <object> #配置信息
  • 下面的KubeSchedulerConfig配置示例(kubescheduler-config-demo.yaml)中,在Profile字段中默认的defalt-scheduler之外添加了一个名为demo-scheduler的自定义调度器,它采用了优先在节点上堆叠Pod调度方式,适合集群可弹性伸缩的环境中
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: "/etc/kubernetes/scheduler.conf"
disablePreemption: false
profiles:
- schedulerName: default-scheduler
- schedulerName: demo-scheduler
- plugins:
    score:
      disabled:
      - name: NodeResourcesBalancedAllocation
        weight: 1
      - name: NodeResourcesLeastAllocated
        weight: 1
      enabled:
      - name: NodeResourcesMostAllocated
        weight: 5
  • 采用与之前类似的配置方式,让kube-scheduler重新加载自定义的kubescheduler-configuration配置文件后,即可借助deployment控制器创建多个Pod副本进行测试,唯一的特殊要求是要在Pod模板上使用spec.schedulerName指定调度器为demo-scheduler。按照定义,调度器将所有副本堆满一个节点后,才会启用另外一个节点。我们单独提供一个scheduler的配置清单,然后定义一个deployment对象。Pod模板定义了demo-scheduler调度器,然后进行一定的request,limits等资源配置,它会在第一个节点无法容纳某个Pod对象的CPU或内存资源需求时转而使用第二个节点

节点亲和度

  • 节点亲和度是调度程序用来确定Pod对象调度位置(哪个或哪类节点)的调度规则,这些规则基于节点上的自定义标签和Pod对象上指定的标签选择器进行进行调度,而支持这种调度机制的有NodeName和NodeAffinity调度插件。简单来说,节点亲和调度机制支持Pod资源定义自身对期望运行的某类节点的倾向性,倾向于运行指定类型的节点即为”亲和”关系,否则即为”反亲和”关系
  • 在Pod上定义节点亲和规则时有两种类型的节点亲和关系:强制(required)亲和和授权(preferred)亲和,或分别称为硬亲和与软亲和。强制亲和限定了调度Pod资源时必须要满足的规则,无可用节点时Pod对象会被置为pending状态,直到满足规则的节点出现。相比较来说,首选亲和规则实现的是一种柔性调度限制,它同样倾向于将Pod运行再某类特定的节点之上,但无法满足调度需求时,调度器将选择一个无法匹配规则的节点,而不是将Pod置于pending状态
  • 定义节点亲和规则的关键点有两个,一个是为节点配置合乎需求的标签,另一个是为Pod对象定义合理的标签选择器,从而能够基于标签选择出符合期望的目标节点。不过,如preferredDuringSchedulingIgnoredDuringExecution和requiredDuringSchedulingIgnoredDuringExecution名字中的后半段字符串IgnoredDuringExecution隐含的意义所指,在Pod资源基于节点亲和性规则调度至某节点之后,节点标签发生了改变而不再符合此节点亲和性规则时,调度器也不会将Pod对象从此节点上移除,它只对新建的Pod对象生效,因而亲和度调度规则仅在调度执行的过程中进行一次即时的判断,而不是持续的监视亲和规则是否能够满足。下图中给出这种亲和关系作用机制的示意图,为了简单起见,将requiredDuringSchedulingIgnoredDuringExecution字段缩写为required
    资源调度 - 图7

节点选择器

  • Pod资源可以使用spec.nodeName直接指定要运行的目标节点,也可以基于spec.nodeSelector指定的节点选择器过滤符合条件的节点作为可用目标节点将Pod对象强制调度至该节点,最终选择则基于打分机制完成。因此,后者可以称为节点选择器。用户事先为特定部分的Node资源对象设定好标签,而后即可配置Pod通过节点选择器实现类似于节点的强制亲和调度
  • kubeadm部署的K8S集群默认会给每个节点附加一些标签,可以使用kubectl get nodes --show-labels进行查看,而主节点会比其他节点多出一个node-role.kubernetes.io/master=标签,而其中kubernetes.io/hostname适合NodeName类型的调度。如果无法满足nodeSelector的调度需求时,我们可以使用kubectl label node nodename key=value命令给节点添加自定义标签。例如,下面的示例给node-4节点附加一个gpu的标签
[root@kube-master-01 manifests]# kubectl label node kube-node-04 kubernetes.io/gpu=RTX1060
node/kube-node-04 labeled
[root@kube-master-01 manifests]#
[root@kube-master-01 manifests]# kubectl get nodes -l "kubernetes.io/gpu=RTX1060"
NAME           STATUS   ROLES    AGE   VERSION
kube-node-04   Ready    worker   25h   v1.19.3
[root@kube-master-01 manifests]#
  • 下面的配置清单示例(pod-demo-nodeselector.yaml)中定义的Pod资源使用节点选择器定义了节点亲和调度,它倾向于运行在拥有GPU为RTX1060的节点上
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prod-tax-text
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tax-text
      env: prod
  template:
    metadata:
      labels:
        app: tax-text
        env: prod
    spec:
      imagePullSecrets:
      - name: harbor
      nodeSelector:
        kubernetes.io/gpu: RTX1060
      containers:
      - name: tax-taxt
        image: harbor.hub.shzhanmeng.com/tax-test/prod-tax-test:202109280909-38
        ports:
        - name: tax-text
          containerPort: 12345
        resources:
          requests:
            memory: "200Mi"
            cpu: "100m"
          limits:
            memory: "500Mi"
            cpu: "200m"
  • 按照上面的配置清单中的nodeSelector的选择器配置,该Pod资源可能仅只会运行在具有标签kubernetes.io/gpu=RTX1060的节点之上,而我们集群当中只有node-04才具有该标签,所有该Pod可能只会运行在node-04之上,我们可以将上面的配置清单中定义的Pod创建出来,然后查看其运行的节点可以判定调度效果
[root@kube-master-01 nodeselector]# kubectl apply -f pod-demo-nodeselector.yaml 
deployment.apps/prod-tax-text created
[root@kube-master-01 nodeselector]# kubectl get pods -o wide
NAME                             READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
prod-tax-text-5d4d65b59c-gqsxq   1/1     Running   0          6s    10.244.15.219   kube-node-04   <none>           <none>
[root@kube-master-01 nodeselector]#
  • 事实上,多数情况下用户都无需关西Pod对象的具体运行位置,除非Pod依赖的特殊条件仅能由部分节点满足时,例如GPU,SSD等。即便如此,也应该尽量避免使用spec.nodeName静态指定Pod对象的运行位置,而是应该让调度器基于标签和标签选择器为Pod挑选匹配的工作节点。另外,Pod规范中的spec.nodeSelector仅支持简单等值关系的节点选择器,而spec.affinity,nodeAffinity支持使用matchExpression属性构建更灵活更复杂的的标签选择机制,而且可以实现硬亲和与软亲和逻辑

强制节点亲和

  • Pod规范中的spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution字段用于定义节点的强制亲和性,它的等值是一个对象列表,可由一到多个nodeSelectorTerms对象组成,彼此之间为”逻辑或”关系,进行匹配度检查时,在多个nodeSelectorTerm之间只要满足其中之一即可。nodeSelectorTerm用于定义节点选择器条目,其值为对象列表,它可由一个或多个matchExpressions对象定义的匹配规则组成,多个规则彼此之间为”逻辑与”的关系,这就意味着对某节点的标签需要完全匹配用一个nodeSelectorTerm下所有的matchPressions对象定义的规则才算成功通过节点选择器条目的检查。而matchPressions又可由一到多个标签选择器组成,多个标签选择器彼此之间为”逻辑与”关系
  • nodeSelectorTerm的值为对象列表,它支持matchExpressions和matchFields两种复杂的表达机制
    • matchExpressions: 标签选择器表达式,基于节点标签进行过滤;可重复使用以表达不同的匹配条件,各条件之间为”或”关系
    • matchFields: 以字段选择器表达的节点选择器;可重复使用以表达不同的匹配条件,各条件间为”或”关系
  • 前面有说到nodeSelectorTerm匹配条件,每个匹配条件可由一到多个匹配规则组成,例如某个matchExpression条件下可同时存在两个表达式规则,如下面的示例所示,同一条件下的各条规则彼此间为”逻辑与”关系。这意味着某节点满足nodeSelectorTerm中的任意一个条件即可,但满足某个条件指的是可完全匹配该条件下定义的所有规则
  • 下面的配置清单示例(node-affinity-required-demo.yaml)中,Pod模板使用了强制节点亲和约束,它要求Pod只能运行在那些拥有GPU标签且不具有node-role.kubernetes.io/master标签的节点之上
apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-affinity-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demoapp
      ctlr: node-affinity-demo
  template:
    metadata:
      labels:
        app: demoapp
        ctlr: node-affinity-demo
    spec:
      imagePullSecrets:
      - name: harbor
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/gpu
                operator: Exists
              - key: node-role.kubernetes.io/master
                operator: DoesNotExist
      containers:
      - name: node-affinity-demo
        image: harbor.hub.shzhanmeng.com/tax-test/prod-tax-test:202109280909-38
        ports:
        - name: java
          containerPort: 12345
        resources:
          requests:
            memory: "200Mi"
            cpu: "100m"
          limits:
            memory: "500Mi"
            cpu: "200m"
  • 之前我们已经为node-04设置了kubernetes.io/gpu标签,而使用kubeadm部署的集群master节点默认就拥有node-role.kubernetes.io/master标签,因此按照该配置清单的期望,3个Pod副本应该都只会运行在node-04节点之上。下面的命令也证实了我们的设定
[root@kube-master-01 nodeselector]# kubectl apply -f node-affinity-required-demo.yaml 
deployment.apps/node-affinity-demo configured
[root@kube-master-01 nodeselector]#
[root@kube-master-01 nodeselector]# kubectl get pods -o wide
NAME                                  READY   STATUS    RESTARTS   AGE     IP              NODE           NOMINATED NODE   READINESS GATES
node-affinity-demo-5c79569c4d-6jgkh   1/1     Running   0          2m41s   10.244.15.222   kube-node-04   <none>           <none>
node-affinity-demo-5c79569c4d-btw7q   1/1     Running   0          2m41s   10.244.15.221   kube-node-04   <none>           <none>
node-affinity-demo-5c79569c4d-snsfj   1/1     Running   0          4m1s    10.244.15.220   kube-node-04   <none>           <none>
[root@kube-master-01 nodeselector]#
  • 由上述操作过程可知,节点硬亲和机制实现的功能与节点选择器(nodeSelector)相似,但亲和性支持使用标签匹配表达式或字段选择器来挑选节点,提供了更灵活且强大的选择机制,因此可被理解为新一代的节点选择器

节点软亲和

  • 节点软亲和也叫节点首选亲和,节点软亲和机制为节点选择提供了一种柔性控制逻辑,被调度的Pod对象不再是”必须”而是”应该”放置到某些特定节点之上,但条件不满足时,该Pod也能够接收被编排到其他不符合条件的节点之上。另外,多个软亲和条件并存时,它还支持为每个条件定义weight属性以区别它们优先级,取值范围是1~100,数字越大优先级越高
  • 下面的配置清单示例(node-affinity-preferread-demo.yaml)中,Pod模板定义了两个节点软亲和约束条件,它们有着不同的权重
apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-preferread-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demoapp
      ctlr: node-preferread-demo
  template:
    metadata:
      labels:
        app: demoapp
        ctlr: node-preferread-demo
    spec:
      imagePullSecrets:
      - name: harbor
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 60
            preference:
              matchExpressions:
              - key: kubernetes.io/ssd
                operator: Exists
                values: []
          - weight: 30
            preference:
              matchExpressions:
              - {key: zone, operator: In, values: ["foo", "bar"]}
      containers:
      - name: node-affinity-demo
        image: harbor.hub.shzhanmeng.com/tax-test/prod-tax-test:202109280909-38
        ports:
        - name: java 
          containerPort: 12345
        resources:
          requests:
            memory: "200Mi"
            cpu: "100m"
          limits:
            memory: "500Mi"
            cpu: "200m"
  • 示例中,Pod资源模板定义了节点软亲和以选择运行在拥有zone=foo、zone=bar和ssd标签(无论其值为何)的节点之上,而其中kubernetes.io/ssd是更要重要的倾向性规则,它的权重为60,而zone就没有你们关键,它的权重只有30。这么一来,如果集群中拥有足够多的节点,那么它将被此规则分为4类:同时满足zone=foo,zone=barkubernetes.io/ssd标签、仅具有zone=foo,zone=bar标签、仅具有kubernetes.io/ssd标签、以及不具备此两个标签
  • 我们将上面的Pod创建到集群之上,创建完成之后看看运行的结果
[root@kube-master-01 nodeselector]# kubectl label node kube-node-07 zone=foo
node/kube-node-07 labeled
[root@kube-master-01 nodeselector]# kubectl label node kube-node-06 zone=bar
node/kube-node-06 labeled
[root@kube-master-01 nodeselector]#
[root@kube-master-01 nodeselector]# kubectl label node kube-node-02 kubernetes.io/ssd=
node/kube-node-02 labeled
[root@kube-master-01 nodeselector]#
[root@kube-master-01 nodeselector]# kubectl apply -f node-affinity-preferread-demo.yaml 
deployment.apps/node-preferread-demo created
[root@kube-master-01 nodeselector]#
[root@kube-master-01 nodeselector]# kubectl get pods -o wide
NAME                                    READY   STATUS    RESTARTS   AGE   IP               NODE           NOMINATED NODE   READINESS GATES
node-preferread-demo-5dc564df55-754gh   1/1     Running   0          30s   10.244.15.225    kube-node-04   <none>           <none>
node-preferread-demo-5dc564df55-kc7zx   1/1     Running   0          30s   10.244.144.197   kube-node-07   <none>           <none>
node-preferread-demo-5dc564df55-w924s   1/1     Running   0          30s   10.244.236.80    kube-node-02   <none>           <none>
[root@kube-master-01 nodeselector]#
  • 结果显示,三个Pod对象分别运行在集群中的三个节点之上,而非集中运行于node-04节点之上。之所以如此,是因为使用了节点软亲和的预选方式,所有节点均能够通过调度器上的MatchNodeSelector预选策略的筛选,因此,可用节点取决于其他预选策略的筛选结果

Pod亲和度

  • 出于高效通信的需求,偶尔需要把一些Pod对象组织在相近的位置(同一节点、机架、区域或地区等),如某业务的前端Pod和后端Pod等,此时可以将这些Pod对象间的关系称为亲和性。偶尔,出于安全或分布式容灾等原因,也会需要把一些Pod对象与其所允许的位置隔离开来,例如在IDC中的区域允许某应用的某个代理Pod对象等,我们可把这类Pod对象间的关系称为反亲和
  • 当然,我们也可以通过节点亲和性来定义Pod对象间的亲和或反亲和特性,但用户必须为此明确指定Pod可运行的节点标签,显然这并非比较优的选择。较理想的实现方式是允许调度器把第一个Pod放置在任何位置,而后与其有着亲和或反亲和关系的其他Pod据此动态完成位置编排,这就是Pod亲和调度与反亲和调度功用。Pod间的亲和关系也存在强制亲和及首选亲和的区别,它们表示的约束意义同节点亲和相似
  • Pod间的亲和及反亲和关系主要由调度插件InterPodAffinity来支撑,它既要负责节点过滤,也要完成节点的优先级排序。而经典调度策略则使用内置的MatchInterPodAffinity预选策略和terPodAffinityPriority优选函数进行各节点的优先级评估

位置拓扑

  • 如果基于各节点的kubernetes.io/hostname标签作为评判标准,显然”同一位置”意味着同一个节点,而不同节点有不同位置,如下图所示
    资源调度 - 图8
  • 而如果根据下图所划分的故障转移域来判断,node01和node02属于同一个位置,而nde03和node04属于另一个意义上的同一位置
    资源调度 - 图9
  • 因而,在定义Pod对象的亲和与反亲和关系时,首先要借助标签选择器来选择同一类Pod对象,而后根据筛选出的同类现有Pod对象所在节点的标签来判定”同一位置”的具体所指,而后针对亲和关系将该Pod放置在同一位置中优先级最高的节点之上,或者针对反亲和关系将该Pod编排至不同拓扑优先级最高的节点上
  • Pod间的亲和关系定义在spec.affinity.podAffinity字段中,而反亲和关系定义在spec.affinity.podAntiAffnity字段中,它们各自的约束特性也存在强制与首选两种,它们都支持使用如下关键字段
    • topologyKey: :拓扑键,用来划分拓扑结构的节点标签,在指定的键上具有相同值的节点归属为同一拓扑;必选字段
    • labelSelector:
    • namespaces: <[]string>: 用于指示lableSelector字段的生效目标名称空间,默认为当前Pod所属的同一名称空间

Pod间的强制亲和

  • Pod间的亲和关系用于描述一个Pod对象与其具有某特征的现存Pod对象运行位置的依赖关系,因而,满足Pod亲和约束的前提是确定”被依赖的”Pod对象,以及节点拓扑位置的确定机制
  • Pod间强制约束的亲和调度也定义在requiredDuringSchedulingIgnoredDuringExecution字段中。下面的示例中先定义了一个假设为被依赖的存储应用Redis,而后定义了一个依赖该存储的demoapp应用,后者使用Pod间强制亲和约束,期望与redis运行在同一位置。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
      ctlr: redis
  template:
    metadata:
      labels: #Pod标签,将被demoapp Pod选择作为参照
        app: redis
        ctlr: redis
    spec:
      nodeSelector:
        rack: rack002
      containers:
      - name: redis
        image: redis:6.0-alpine
        ports:
        - name: redis
          containerPort: 6379
        resources:
          requests:
            memory: "56Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-affinity-requeired
spec:
  replicas: 5
  selector:
    matchLabels:
      app: demoapp
      ctlr: pod-affinity-requeired
  template:
    metadata:
      labels:
        app: demoapp
        ctlr: pod-affinity-requeired
    spec:
      containers:
      - name: demoapp
        image: ikubernetes/demoapp:v1.0
      affinity: #Pod亲和关系定义
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution: #强制亲和定义
          - labelSelector: #Pod对象标签选择器,用于确定放置当前Pod的参照系,它也将是demoapp Pod亲和关系依赖的重要因素,如果该Pod无法与表现选择器选择的Pod匹配,那么该Pod将一直出与pending状态
              matchExpressions:
              - {key: app, operator: In, values: ["Redis"]}
              - {key: ctlr, operator: In, values: ["redis"]}
            topologyKey: rack #拓扑键,用于确定节点位置拓扑的节点标签,必选
  • 通过上面的配置清单可以判断出,redis的副本会被运行在rack002标识的位置上,则demoapp的所有Pod副本都会运行在该位置的node03和node04节点上;为了测试效果,我们需要先为节点打上相应的标签
kubectl label node kube-node-01 kube-node-02 rack=rack001
kubectl label node kube-node-03 kube-node-04 rack=rack002
kubectl label node kube-node-04 kube-node-05 kube-node-06 rack=rack003
  • 将配置清单部署到k8s集群之上用于测试demoapp Pod是否与redis pod存在强制亲和关系
[root@kube-master-01 nodeselector]# kubectl apply -f pod-affinity-required.yaml 
deployment.apps/redis created
deployment.apps/pod-affinity-requeired created
[root@kube-master-01 nodeselector]#
  • 可通过资源的详细描述中的events信息或者资源规范查看redis pod副本的运行位置。下面的内容取自redis pod的详细描述中的事件,它显示redis pod对象default/redis-7bb8c899fc-lgr5b被default-scheduler调度至kube-node-04节点之上,该节点位于rack002之上
[root@kube-master-01 nodeselector]# kubectl describe pod redis-7bb8c899fc-lgr5b
...
...
...
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  74s   default-scheduler  Successfully assigned default/redis-7bb8c899fc-lgr5b to kube-node-04
  Normal  Pulled     66s   kubelet            Container image "redis:6.0-alpine" already present on machine
  Normal  Created    66s   kubelet            Created container redis
  Normal  Started    66s   kubelet            Started container redis
[root@kube-master-01 nodeselector]#
  • 因而,deployment/demoapp的所有Pod对象也必将运行在rack002的节点之上,使用下面的命令结果所示
[root@kube-master-01 nodeselector]# kubectl get pods -o wide
NAME                                      READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
pod-affinity-requeired-599f868646-flzwh   1/1     Running   0          6s    10.244.49.202   kube-node-03   <none>           <none>
pod-affinity-requeired-599f868646-s8lg2   1/1     Running   0          6s    10.244.15.198   kube-node-04   <none>           <none>
pod-affinity-requeired-599f868646-vzkfc   1/1     Running   0          6s    10.244.49.203   kube-node-03   <none>           <none>
pod-affinity-requeired-599f868646-w7b6f   1/1     Running   0          6s    10.244.15.254   kube-node-04   <none>           <none>
pod-affinity-requeired-599f868646-xnfsj   1/1     Running   0          6s    10.244.15.255   kube-node-04   <none>           <none>
redis-7bb8c899fc-n5856                    1/1     Running   0          6s    10.244.15.195   kube-node-04   <none>           <none>
[root@kube-master-01 nodeselector]#
  • 由此可见,Pod间的亲和调度能够将有密切关系或密集通信的应用约束在同一位置,通过降低通信延迟来降低性能损耗。需要注意的是,若节点上的标签在运行时发生更改导致不能再满足Pod上的亲和关系定义时,该Pod将继续在该节点上运行而不会被重新调度。另外,labelSelector属性仅匹配与被调度的Pod在同一个名称空间中的Pod资源,不过也可以通过为其添加namespace字段以指定其他名称空间

Pod间的首选亲和

  • 因满足位置关系的节点上的可分配己算资源、存储卷合节点端口等原因导致Pod间的强制亲和关系在无法得到满足时,调度器会将Pod对象置于pending状态,但是首选亲和约束则只是尽力满足这种亲和约束,当无法保证这种亲和关系时,调度器则会将Pod对象调度至集群中其他位置的节点之上。而对位置关系要求不甚严格的应用之间的部署需求,首选亲和到也部失为一种折中的选择
  • 类似于节点的首选亲和,Pod间的柔性亲和约束也使用preferredDuringSchedulingIgnoredDuringExecution字段进行定义,调度器会尽力确保满足亲和约束的调度逻辑,然而在约束条件不能得到满足时,它也允许将Pod对象调度至其他节点允许。它同样允许用户定义具有不同权重的多重亲和条件,以定义出多个不同适配级别位置。
  • 例如下面的资源配置清单示例(pod-affinity-preferred.yaml)中显示定义了一个redis应用,它由调度器自行选定目标节点,但demoapp应用Pod将以柔性亲和的方式期望与Redis Pod允许在同一个节点(带有kubernetes.io/hostname标签),当条件无法满足时,则期望允许在同一区域(rack标签),否则也能接受运行在集群中的其他任何节点之上
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-prefered
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
      ctlr: redis-preferred
  template:
    metadata:
      labels: #Pod标签,将被demoapp Pod选择作为参照
        app: redis
        ctlr: redis-preferred
    spec:
      containers:
      - name: redis
        image: redis:6.0-alpine
        ports:
        - name: redis
          containerPort: 6379
        resources:
          requests:
            memory: "56Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-affinity-preferred
spec:
  replicas: 5
  selector:
    matchLabels:
      app: demoapp
      ctlr: pod-affinity-preferred
  template:
    metadata:
      labels:
        app: demoapp
        ctlr: pod-affinity-preferred
    spec:
      containers:
      - name: demoapp
        image: ikubernetes/demoapp:v1.0
      affinity:
        podAffinity: #Pod亲和关系定义
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100  #最大权重的亲和条件
            podAffinityTerm:
              labelSelector: #用于匹配redis Pod的标签,它也将是demoapp Pod亲和关系依赖的重要因素
                matchExpressions:
                - {key: app, operator: In, values: ["redis"]}
                - {key: ctlr, operator: In, values: ["redis-preferred"]}
              topologyKey: kubernetes.io/hostname #确定节点位置拓扑的标签
          - weight: 50
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - {key: app, operator: In, values: ["redis"]}
                - {key: ctlr, operator: In, values: ["redis-preferred"]}
              topologyKey: rack #确定节点位置拓扑的第二标签,扩大了前一条位置范围
  • 我们将该配置清单部署到集群之上,redis Pod被调度至node07节点之上,则demoapp pod同样更倾向于运行在该节点,当条件无法满足时,调度器将以该节点所在的rack标签为标准来挑选同一个rack标签中的另一节点node05或者node06,最后才是其他节点
[root@kube-master-01 nodeselector]# kubectl apply -f pod-affinity-preferred.yaml 
deployment.apps/redis-prefered configured
deployment.apps/pod-affinity-preferred configured
[root@kube-master-01 nodeselector]#
[root@kube-master-01 nodeselector]# kubectl get pods -o wide
NAME                                      READY   STATUS    RESTARTS   AGE    IP               NODE           NOMINATED NODE   READINESS GATES
pod-affinity-preferred-689d6854d6-2wzpt   1/1     Running   0          18s    10.244.144.210   kube-node-07   <none>           <none>
pod-affinity-preferred-689d6854d6-4s58b   1/1     Running   0          18s    10.244.60.137    kube-node-05   <none>           <none>
pod-affinity-preferred-689d6854d6-g7xbd   1/1     Running   0          5m6s   10.244.182.1     kube-node-06   <none>           <none>
pod-affinity-preferred-689d6854d6-tt4f2   1/1     Running   0          18s    10.244.10.72     kube-node-01   <none>           <none>
pod-affinity-preferred-689d6854d6-zwtxk   1/1     Running   0          18s    10.244.15.200    kube-node-04   <none>           <none>
redis-prefered-b58fd7488-mjr8d            1/1     Running   0          5m6s   10.244.144.209   kube-node-07   <none>           <none>
[root@kube-master-01 nodeselector]#
  • 假设redis被调度的节点之上没有更多资源容纳其他依赖的Pod时,并且同一个topologyKey内再无其他节点,则相关的Pod只能被无差别的调度到其他节点之上。Pod间的柔性亲和关系尽力保证有紧密关系的Pod运行在一起的同时,避免了因强制亲和条件得不到满足时而将Pod处于”Pending”状态的局面

Pod间的反亲和关系

  • Pod间的反亲和关系(podAntiAffinity)要实现的调度目标刚好与亲和关系相反,它的主要目标在于确保存在互斥关系的Pod对象不会运行在同一位置,或者确保仅需要在指定的位置配置单个代理程序(类似于daemonset确保每个节点仅允许单个某类Pod)等应用场景。因此,反亲和调度一般用于分散同一类应用的Pod对象等,也包括把不同安全级别的Pod对象调度至不同的区域、机架或节点等。下面资源配置清单(pod-antiaffinity-required-demo.yaml)中定义了由同一deployment创建但彼此基于节点位置互斥的Pod对象
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-antiffinity-requeired
spec:
  replicas: 8
  selector:
    matchLabels:
      app: demoapp
      ctlr: pod-antiffinity-requeired
  template:
    metadata:
      labels:
        app: demoapp
        ctlr: pod-antiffinity-requeired
    spec:
      containers:
      - name: demoapp
        image: ikubernetes/demoapp:v1.0
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - {key: app, operator: In, values: ["demoapp"]}
              - {key: ctlr, operator: In, values: ["pod-antiffinity-requeired"]}
            topologyKey: kubernetes.io/hostname
  • 强制反亲和约束下,deployment/pod-antiffinity-required创建的8个Pod副本必须运行于不同的节点之上,但是我的集群环境一共有8个节点,其中还包含了一个master节点,master节点不允许Pod调度,所以必然会有一个Pod处于Pending状态,如下所示
[root@kube-master-01 nodeselector]# kubectl apply -f pod-antiaffinity-required.yaml 
deployment.apps/pod-antiffinity-requeired created
[root@kube-master-01 nodeselector]#
[root@kube-master-01 nodeselector]# kubectl get pods -o wide
NAME                                         READY   STATUS    RESTARTS   AGE     IP               NODE           NOMINATED NODE   READINESS GATES
pod-antiffinity-requeired-846dc8ccbb-46lw6   1/1     Running   0          2m13s   10.244.60.138    kube-node-05   <none>           <none>
pod-antiffinity-requeired-846dc8ccbb-89p26   1/1     Running   0          2m13s   10.244.10.73     kube-node-01   <none>           <none>
pod-antiffinity-requeired-846dc8ccbb-9hlff   1/1     Running   0          100s    10.244.182.2     kube-node-06   <none>           <none>
pod-antiffinity-requeired-846dc8ccbb-9phdq   1/1     Running   0          39s     10.244.49.204    kube-node-03   <none>           <none>
pod-antiffinity-requeired-846dc8ccbb-f2445   0/1     Pending   0          7s      <none>           <none>         <none>           <none>
pod-antiffinity-requeired-846dc8ccbb-gsq2p   1/1     Running   0          2m13s   10.244.144.211   kube-node-07   <none>           <none>
pod-antiffinity-requeired-846dc8ccbb-jwzlp   1/1     Running   0          2m13s   10.244.236.83    kube-node-02   <none>           <none>
pod-antiffinity-requeired-846dc8ccbb-vmzwt   1/1     Running   0          2m13s   10.244.15.194    kube-node-04   <none>           <none>
[root@kube-master-01 nodeselector]#
  • 类似的,Pod反亲和调度也支持使用柔性约束机制,调度器会尽量不把位置互斥的Pod对象调度到同一位置,但是,当约束关系无法得到满足时,也可以违反约束而调度,而不是把Pod置于Pending状态。可以自行测试这种柔性的反亲和机制

污点与容忍度

  • 污点是定义在节点之上的键值型数据,用于让节点有能力主动拒绝调度器将Pod调度运行到节点上,除非该Pod对象具有接纳节点污点的容忍度。容忍度(tolerations)则是定义在Pod对象上的键值型属性数据,用于配置该Pod可容忍的节点污点,而且调度器仅能将Pod对象调度至其能够容忍该节点污点的节点之上。调度器插件TaintToleration负责确保仅那些可容忍节点污点的Pod对象可调度运行在上面。经典调度机制使用PodToleratesNodeTaints预选策略和TaintTolerationPriority优选函数完成该功能。节点污点和Pod容忍度在调度中的关系如下图
    资源调度 - 图10
  • 前面的章节中,节点选择器(nodeSelector)和节点亲和性(nodeAffinity)两种调度方式都是通过在Pod对象上添加标签选择器来完成对特定类型节点标签的匹配,从而完成节点选择和绑定,相对而言,基于污点和容忍度的调度方式则是通过向节点添加污点信息来控制Pod对象的调度结果,从而给了节点控制何种Pod对象能够调度于其上的控制权。简单来说,节点亲和性使Pod对象呗吸引到一类特定的节点,而污点则相反,它为节点提供了排斥特定Pod对象的能力

污点与容忍度的基础概念

  • 污点定义在节点的nodeSpec中,而容忍度定义在Pod的podSpec中,它们都是键值型数据,但又都额外支持一个效用(effect)标识,语法格式为key=value:effect,其中key和value的用法及格式与资源注解信息相似,而污点上的效用标识则用于定义其对Pod对象的排斥等级,容忍度上的效用标识则用于定义其对污点的容忍级别。效用标识主要有以下3种类型
    • NoSchedule: 不能容忍此污点的Pod对象不可调度至当前节点,属于强制约束关系,但添加污点对节点上现存的Pod对象不产生影响
    • PreferNoSchedule: NoSchedule的柔性约束版本,即调度器尽量确保不会将那些不能容忍此污点的Pod对象调度至当前节点,除非不存在其他任何能够容忍此污点的可用节点;添加该类效用的污点同样对节点上现存的Pod对象不产生影响
    • NoExecute: 不能容忍此污点的新Pod对象不可调度至当前节点,属于强制约束性关系,而且节点上现存的Pod对象因节点污点变动或Pod容忍度变动而不再满足匹配条件时,Pod对象将会被驱逐
  • 此外,在Pod对象上定义容忍度时,它支持两种操作符:一种是等值比较,表示容忍度与污点必须在key、value和effect三者之上完全匹配,另一种是存在性判断,表示二者的key和effect必须完全匹配,而容忍度中的value字段要使用空值
  • 一个节点可以配置使用多个污点,而一个Pod对象也可以有多个容忍度,将一个Pod对象的容忍度套用到特定节点的污点之上进行匹配度检测时将遵循如下逻辑
    • 首先处理与容忍度匹配的污点
    • 对于不能匹配到的容忍度的所有污点,若存在一个污点使用了NoSchedule效用标识,则拒绝调度当前Pod至该节点
    • 对于不能匹配到容忍度的所有污点,若都不具有NoSchedule效用标识,但至少有一个污点使用了PreferNoSchedule效用标识,则调度器会尽量避免当前Pod对象调度至该节点
    • 如果至少有一个不能匹配容忍度的污点使用了NoExecute效用标识,节点将立即驱逐当前Pod对象,或者不允许该Pod调度至给定的节点;而且,即便容忍度匹配到使用了NoExecute效用标识的污点,若在Pod上定义容忍度时同时使用tolerationSeconds属性定义了容忍时限,则在超时后当前Pod也将会被节点驱逐
  • 使用kubeadm部署的集群来说,其master节点会被自动添加污点信息以阻止那些不能容忍此污点的常规工作负载类Pod对象调度至该节点上,以确保主节点仅用于处理控制平面相关的事务
[root@kube-master-01 nodeselector]# kubectl describe node kube-master-01
Name:               kube-master-01
Roles:              master
...
...
Taints:             node-role.kubernetes.io/master:NoSchedule
Unschedulable:      false
Lease:
...
...
  • 然而有些系统级应用也会在资源创建时被添加上相应的容忍度,以确保它们被DaemonSet控制创建时能调度至master节点运行一个实例,例如kube-proxy或者kube-flannel等。以任意一个kube-proxy实例为例
[root@kube-master-01 nodeselector]# kubectl describe pod kube-proxy-4vnzs -n kube-system
...
...
...
Tolerations:     op=Exists
                 CriticalAddonsOnly op=Exists
                 node.kubernetes.io/disk-pressure:NoSchedule op=Exists
                 node.kubernetes.io/memory-pressure:NoSchedule op=Exists
                 node.kubernetes.io/network-unavailable:NoSchedule op=Exists
                 node.kubernetes.io/not-ready:NoExecute op=Exists
                 node.kubernetes.io/pid-pressure:NoSchedule op=Exists
                 node.kubernetes.io/unreachable:NoExecute op=Exists
                 node.kubernetes.io/unschedulable:NoSchedule op=Exists
Events:          <none>
[root@kube-master-01 nodeselector]#
  • 运行着系统组件的Pod对象是构成kubernetes系统的关键组成部分,因而它们通常被定义了更大的容忍度。从上面的kube-proxy实例的容忍度定义来看,这种容忍度还能容忍那些报告了存在磁盘压力、内存压力和PID压力的节点,以及那些未就绪的节点和不可达的节点等,以确保它们能在任何状态下正常调度至集群节点上运行
  • 由此可见,通过污点和容忍度,可在集群中定义出某种目的专用节点、配备有特殊硬件的特殊节点,甚至可使用污点驱逐指定节点上某些Pod对象

定义污点

  • 任何符合键值规范要求的字符串均可用于定义污点信息:可使用字母、数字、连接符、点号和下划线,且仅能以字母或数字开头,其中键名的长度上限为253个字符,值最长为63个字符。之前我们有提到过,污点定义在节点的nodeSpec中,容忍度定义在Pod的podSpec中,它们都是键值型数据
  • 实践中,污点通常用描述具体的部署规划,它们的键名形如node-type、node-role、node-project或node-geo等,而且一般还会再必要时带上域名以描述一些额外信息,例如node-type.xxx.com等。kubectl taint命令可以用于管理Node对象的污点信息,该命令的语法格式为kubectl taint nodes <node-name> <key>=<value>:<effect> ...
  • 比如,定义节点node01使用node-type=production:NoScheduler这一污点,它对kube-node-01上已有的Pod对象不产生影响,但对之后调度的Pod对象来说,不能容忍该污点则意味着无法调度至该节点。
[root@kube-master-01 nodeselector]# kubectl taint nodes kube-node-01 node-type=production:NoSchedule
node/kube-node-01 tainted
[root@kube-master-01 nodeselector]#
  • node-type是具有NoSchedule效用标识的污点,类似下面的命令可以查看节点上的污点信息
[root@kube-master-01 nodeselector]# kubectl get nodes kube-node-01 -o jsonpath={.spec.taints}
[{"effect":"NoSchedule","key":"node-type","value":"production"}]
[root@kube-master-01 nodeselector]#
  • 需要注意的是,effect同样是污点的核心组成部分,即便键值数据相同但效用标识不同的污点也属于两个各自独立的污点信息。例如,将上面命令中的效用标识定义为PreferNoSchedule再添加一次
[root@kube-master-01 nodeselector]# kubectl taint nodes kube-node-01 node-type=production:PreferNoSchedule
node/kube-node-01 tainted
[root@kube-master-01 nodeselector]#
[root@kube-master-01 nodeselector]# kubectl get nodes kube-node-01 -o jsonpath={.spec.taints}
[{"effect":"PreferNoSchedule","key":"node-type","value":"production"},{"effect":"NoSchedule","key":"node-type","value":"production"}]
[root@kube-master-01 nodeselector]#
  • 删除节点上的污点仍旧可以通过kubectl taint命令完成,但它需要使用如下的命令格式kubectl taint nodes <node-name> <key>[:<effect>]-,省略效用标识则表示删除使用指定键名的所有污点,否则只删除指定键名上的对应效用标识的污点
  • 例如,下面的命令可以删除kube-node-01上的node-type键的效用标识为NoSchedule的污点信息
[root@kube-master-01 nodeselector]# kubectl taint nodes kube-node-01 node-type:NoSchedule-
node/kube-node-01 untainted
[root@kube-master-01 nodeselector]#
[root@kube-master-01 nodeselector]# kubectl get nodes kube-node-01 -o jsonpath={.spec.taints}
[{"effect":"PreferNoSchedule","key":"node-type","value":"production"}]
[root@kube-master-01 nodeselector]#
  • 若希望删除使用指定键名的所有污点,在删除命令中省略效用标识即可实现,例如下面的命令删除kube-node-01上键名为node-type的所有污点
[root@kube-master-01 nodeselector]# kubectl taint nodes kube-node-01 node-type-
node/kube-node-01 untainted
[root@kube-master-01 nodeselector]#
[root@kube-master-01 nodeselector]# kubectl get nodes kube-node-01 -o jsonpath={.spec.taints}
[root@kube-master-01 nodeselector]#
  • 若期望一次删除节点上的全部污点信息,通过kubectl patch命令直接将节点属性spec.taints的值置空即可,例如下面的命令可删除kube-node-01节点上的所有污点
[root@kube-master-01 nodeselector]# kubectl patch nodes kube-node-01 -p '{"spec":{"taints":[]}}'
node/kube-node-01 patched
[root@kube-master-01 nodeselector]#
  • 需要再次提醒的是,节点污点的变动会影响到新建Pod对象的调度结,仅使用NoExecute标识的污点变动会影响节点上现有的Pod对象,其他两个效用标识都不会影响节点上的现有Pod对象

定义容忍度

  • Pod对象的容忍度通过其spec.tolerations字段添加,根据使用的操作符不同,主要有两种可用形式:一种是与污点信息完全匹配的等值关系;另一种是判断污点信息存在性的匹配方式,它们分别使用Equal和Exists操作符表示。
  • equal(等值比较),表示容忍度与污点必须在key、value和effect三者之上完全匹配;Exists(存在性判断),表示二者的key和effect必须完全匹配,而容忍度中的value字段要使用空值
  • 下面容忍度的定义示例使用了Equal操作符,其中tolerationSeconds用于定义延迟驱逐当前Pod对象的时长
  tolerations:
  - key: "node-type"
    operator: "Equal"
    value: "production"
    effect: "NoSchedule"
    tolerationSeconds: 3600
  • 下面的示例中定义了一个使用存在性判断机制的容忍度,它表示能够容忍以node-type为键名、效用标识为NoSchedule的污点
  tolerations:
  - key: "node-type"
    operator: "Exists"
    effect: "NoSchedule"
    tolerationSeconds: 3600
  • 实践中,若集群中的一组机器专为运行非生产型的容器应用而设置,这些机器可能随时按需上下线,那么就该为其添加污点信息,确保能够容忍此污点的非生产型Pod对象可以调度其上。另外,有些有着特殊硬件的节点需要专用于运行一类有此硬件资源需求的Pod对象时,比如有GPU设备的节点也应该添加污点信息,以排除其他的Pod对象

问题节点标识

  • kubernetes自1.6版本起支持使用污点自动标识问题节点,它通过节点控制器在特定条件下自动为节点添加污点信息实现。它们都使用NoExecute效用标识,因此不能容忍此类污点的Pod对象也会遭到驱逐。目前,内置使用的此类污点有如下几个
    • node.kubernetes.io/not-ready: 节点进入NotReady状态时被自动添加的污点
    • node.alpha.kubernetes.io/unreachable: 节点进入NotReachable状态时被自动添加的污点
    • node.kubernetes.io/out-of-disk: 节点进入OutOfDisk状态时被自动添加的污点
    • node.kubernetes.io/memory-pressure: 节点内存资源面临压力
    • node.kubernetes.io/disk-pressure: 节点磁盘资源面临压力
    • node.kubernetes.io/neywork-unavailable: 节点网络不可用
    • node.cloudprovider.kubernetes.io/uninitialized: kubelet由外部的云环境程序启动时,它自动为节点添加此污点,待云控制器管理器中的控制器初始化此节点时再将此污点删除
  • 不过,kubernetes的核心组件通常都要容忍此类的污点,以确保相应的DaemonSet控制器能够无视此类污点在节点上部署相应的关键Pod对象,例如kube-proxy或kube-flannel,calico,node-exporter等

Pod优先级与抢占

  • 调度框架内置的QueueSort扩展点允许注册调度器队列排序的插件,注册到该扩展点的内置插件是PrioritySort,它根据Pod资源规范中由spec.priorityClassName字段指定的PriorityClass所属的优先级进行排序,从而优先调度级别最高的Pod对象。对于优先级别相同Pod,则根据其进入队列的时间戳执行先进先出逻辑
  • 未能找到可满足待调度Pod运行要求的节点时,调度器会将该Pod转入pending状态并为其启动”抢占”过程,,调度器会在集群中尝试通过删除某节点上的一个或多个低优先级的Pod,让节点能够满足待调度Pod的运行条件,并将待调度Pod与该节点绑定。但是,若在等待驱逐完成的过程中出现了其他可用节点,则调度器将待调度Pod绑定至该可用节点
  • Pod优先级使用32位的非负整数表示,可用值范围为[0,1000000000],值越大优先级越高,而大于1000000000的优先级预留给了系统级的关键类Pod,以防止这些Pod被驱逐。kubernetes使用集群级别的API资源类型PriorityClass完成从优先级到名称的映射,并可由Pod在其规范中按名引用。PriorityClass的资源规范及简要使用说明如下
apiVersion: scheduling.k8s.io/v1 #资源隶属于的API群组及版本
kind: PriorityClass #资源类别标识符
metadata:
  name: <string>  #资源名称
value: <integer> #优先级,必选字段
description: <string> #该优先级描述信息
globalDefault: <boolean> #是否为全局默认优先级
preemptionPolicy: <string> #抢占策略,Never为禁用,默认为PreemptLowerPriority
  • 提示:若集群上存在多个设定了全局默认优先级的PriorityClass对象,仅优先级最小的会生效
  • 完整的kubernetes集群除了API Server、Controller Manager、Scheduler和etcd等核心组件意外还有一些至关重要的组件,例如metrics-server、coreDNS和图形化管理界面等,这些组件以常规Pod形式运行在集群节点上,以免与被驱逐。为此,kubernetes默认直接附带了system-cluster-critical和system-node-critical两个特殊的PriorityClass以供这类Pod使用,前者的优先级为2000000000,而后者有着更高的优先级2000001000,它们都位于系统预留的优先级范围内
  • 下面的配置清单示例(priorityclass-demo.yaml)中定义了一个未禁用抢占机制的priorityclass/demoapp-priority资源,它仅用于为demoapp service相关的Pod提供优先级配置
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: demoapp-priority
value: 1000000
globalDefault: false
description: "jin yong yu demoapp"
preemptionPolicy: PreemptLowerPriority
  • 若希望全局禁用优先级抢占公,需要编辑kube-schedulerKubeSchedulerConfiguration配置,设定DisablePreemption参数的值为true。不过,从1.15版本开始也支持单个PriorityClass对象上设定preemptionPolicy的值为Never来禁用资源级别的优先级抢占机制,但直到1.19版本,该功能仍处于alpha级别,需要在kube-scheduler启用NonPreemptingPriority功能才能被支持。当集群资源紧张时,关键Pod需要依赖调度程序抢占功能才能完成调度,所以不建议全局禁用抢占功能,而在PriorityClass级别的抢占禁止就显得格外有用了

小结

  • 主要讲解了kube scheduler的基本工作逻辑,并详细讲解了集中高级调度功能的使用方法
    • 经典调度策略:经过预选、优选、选定和绑定步骤完成Pod调度,仅支持Predicate、Priority和Bind扩展点,且必须先由default-scheduler调度后才能生效
    • 调度框架:支持QueueSort、Prefilter、Filter、PostFilter、PreScore、Score、Reserve、Prebind、Bind和Unreserve等扩展点,将内置的预选函数和优选函数全部实现为调度插件,并支持用户自定义插件
    • NodeAffinity可用于让Pod选择期望运行的节点或节点类型
    • PodAffinity与PodAntiAffinity可用于在指定的拓扑中以特定的要求放置Pod
    • PodSpreadContraints允许在指定的拓扑间均匀的分布Pod,是更具一般性的分布形式,支持多重约束,并且能够结合PodAffinity实现更灵活的分布机制
    • 污点给节点提供了主动排斥Pod对象方式,仅那些可以容忍节点污点的Pod可运行在相关节点之上
    • 优先级和抢占功能为优化集群资源利用率、确保更重要工作负载的运行提供了可行性