之前由Mohamed Ahmed在Magalix博客上发表
Kubernetes的调度器是什么?
如果你阅读过Kubernetes的任何文档、书籍或文章,那么毫无疑问,你会在“Pod被调度到下一个可用节点”之类的短语中看到调度“schedule”这个词。Kubernetes的调度不仅仅是在一个节点上放置一个pod。在本文中,我们将讨论Kubernetes在需要处理新pod时所遵循的不同机制,以及该过程中涉及的组件。
当你在Kubernetes集群上创建一个Pod时会发生什么?
在几秒钟内,Pod就会启动,并在一个集群节点上运行。然而,在这几秒钟里发生了很多事情。让我们来看看:
Kubernetes如何选择正确的节点?
以上步骤中最困难的部分可能是调度器决定应该选择哪个节点来运行pod。实际上,这一部分的工作量最大,因为调度器必须使用几种算法来进行决策。其中一些算法依赖于用户提供的选项,而Kubernetes自己计算其它的。可以将它们解释为调度器要求节点决定的一组问题。
你具备运行这个pod所需的条件吗(谓词)?
一个节点可能超载了许多繁忙的Pod,消耗了它的大部分CPU和内存。因此,当调度器需要部署Pod时,它将确定节点是否具有必要的资源。如果将Pod部署到没有足够内存(作为例子)供Pod请求的节点,承载的应用程序可能会出现意外甚至崩溃。
有时候,用户需要代表Kubernetes做出这个决定。假设你最近购买了几台配备了SSD磁盘的机器,并且希望显式地将它们用于应用程序的MongoDB部分。为此,你可以通过pod定义中的节点标签选择节点。当节点与提供的标签不匹配时,不选择它来部署Pod。
如上图所示,谓词决策解析为True(是的,在该节点上部署pod)或False(不,不要在该节点上部署pod)。
你是拥有这个pod的更好人选吗(优先级)?
除了正确/错误的决定,称为谓词,调度器执行一些计算(或函数)来确定哪个节点更适合承载有关的pod。
例如,已经存在pod镜像的节点(像在以前的部署中被拉取过一样)有更好的机会将pod调度到它,因为不会浪费时间重新下载镜像。
另一个例子是,调度器倾向于不包含相同服务的其它Pod的节点。该算法帮助尽可能多地将服务 Pod分散到多个节点上,这样一个节点故障就不会导致整个服务宕机。这种决策方法称为扩散函数。
将几个决策(如上面的示例)分组,并根据最终决策为每个节点计算权重。具有最高优先级的节点将获得pod部署。
最后的决定
你可能会问,如果Kubernetes调度器在选择部署pod的节点之前必须考虑许多因素,那么它如何才能选择正确的节点呢?
嗯,决策过程如下:
如果那不是最好的决定呢?
在繁忙的Kubernetes集群中,调度器选择正确的节点与执行pod的节点上的kubelet之间的时间可能足以使节点上发生更改。即使时间不超过几毫秒,pod也可能在由于内存不足而被过滤掉的某个节点上终止。只有在当时没有超载的情况下,该节点才可能在优先级测试中获得更高的分数。但现在,可能是选择了一个不太合适的节点。
有些项目旨在解决这种情况,例如Kubernetes Descheduler项目。在这个应用程序中,如果另一个节点被证明是更好的点调度选择,那么pod将自动从节点中移除。pod返回到调度过程中,再次将其部署到正确的节点。
当相反的情况发生时,可能会出现更困难的情况。假设对一个节点进行了测试,看它是否能够提供2GB的内存。在调度器执行谓词检查时,节点确实有一些空闲RAM。然而,当kubelet对节点执行pod时,DaemonSet被部署到相同的节点。这个守护进程需要一些资源密集型的操作,需要消耗剩余的2GB。现在,当pod试图运行时,由于它缺少正确运行所需的内存,所以它失败了。如果这个pod仅使用一个pod定义进行部署,那么它所运行的应用程序将无法启动,Kubernetes对此无能为力。但是,如果这个pod是pod控制器,如Deployment或ReplicaSet,的一部分,那么一旦它失败,控制器将检测到比它应该处理的副本数量少。因此,控制器将请求安排另一个pod。调度器将再次运行所有检查并将pod调度到另一个节点。这就是为什么总是建议在创建pod时使用更高级别的对象(如Deployment)的原因之一。
用户定义的决策
在本文前面,我们提到用户可以使用pod定义或模板中的.spec.nodeSelector参数在特定节点上运行pod。节点选择器选择具有一个或多个特定标签的节点。然而,有时用户需求会变得更加复杂。例如,节点选择器选择在参数中定义了所有标签的节点。如果你想做出更灵活的选择呢?
节点关联(Node Affinity)
让我们考虑一下前面的示例,当时我们希望将pod安排在具有SSD磁盘的机器上运行。假设我们想让它们也使用八核主机。节点关联允许这样的灵活决策。以下pod模板选择标签为feature=ssd或feature=eight-cores的节点:
apiVersion: v1
kind: Pod
metadata:
name: mongo
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: feature
operator: In
values:
- ssd
- eight-cores
containers:
- name: mongodb
image: mogo
requiredDuringSchedulingIgnoredDuringExecution选项
这里有一个新选项:requiredDuringSchedulingIgnoredDuringExecution。这比看起来容易。这意味着我们只需要在标记为feature=ssd或feature=eight-cores的节点上运行这些pod。我们不希望调度器在这组节点之外做出决策。这与节点选择器的行为相同,但是语法更富表现力。
preferredDuringSchedulingIgnoredDuringExecution选项
假设我们希望在选定的节点上运行pod。但是,由于启动该pod是有绝对优先级的,所以我们需要运行它,即使所选的节点不可用。在这种情况下,我们可以使用preferredDuringSchedulingIgnoredDuringExecution选项。此选项将尝试在选择器指定的节点上运行pod。但是如果这些节点不可用(测试失败),调度器将尝试在次佳节点上运行pod。
节点反关联(Node Anti-Affinity)
有些场景要求不使用一个或多个节点,但特定的pod除外。可用考虑托管监视应用程序的节点为例子。由于其角色的性质,这些节点不应该有很多资源。因此,如果有监控应用程序之外的其它pod被调度到这些节点,它们会影响监控,也会降低它们所承载的应用程序的性能。在这种情况下,你需要使用节点反关联来避免pod与一组节点接触。以下是之前添加了反关联的pod定义:
apiVersion: v1
kind: Pod
metadata:
name: mongo
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: feature
operator: In
values:
- ssd
- eight-cores
- key: role
operator: NotIn
values:
- monitoring
containers:
- name: mongodb
image: mogo
使用操作符NotIn向matchexpression添加另一个键,将避免在任何标记为role=monitoring的节点上调度mongo pod。
学习如何持续优化K8s集群
节点污点(taint)和容忍(toleration)
虽然节点反关联模式允许你阻止pod在特定节点上运行,但是它们有一个缺点:pod定义必须明确声明不应该在这些节点上运行。那么,如果一个新成员加入了开发团队,为她的应用程序编写了一个Deployment,但是忘记将监视节点排除在目标节点之外,该怎么办?Kubernetes的管理员需要一种方法来击退节点上的pod,而不必修改每个pod的定义。这就是污点和容忍的作用。
当你点污一个节点时,它将自动从pod调度中排除。当调度在受污点的节点上运行谓词测试时,它们将失败,除非pod能够容忍该节点。例如,让我们点污监测节点mon01:
kubectl taint nodes mon01 role=monitoring:NoSchedule
现在,要是一个pod要在这个节点上运行,它必须有一定的容忍。例如,以下.spec.toleration:
tolerations:
- key: "role"
operator: "Equal"
value: "monitoring"
effect: "NoSchedule"
匹配mon01上污点的键、值和效果(effect)。这意味着当调度器决定是否可以使用mon01来部署这个pod时,mon01将通过谓词测试。
需要注意的一件重要的事情是,容忍可使受点污的节点接受pod,但不能保证该pod在特定节点上运行。换句话说,受点污的节点mon01将被视为运行pod的候选节点之一。但是,如果另一个节点的优先级更高,则会选择它。对于这种情况,你需要将容忍与节点选择器或节点关联参数结合起来。
总结