k8s 调度器

目录

一 、调度说明

简介

调度过程

自定义调度器

二、调度亲和性

pod跟node节点亲和性

pod 跟pod亲和性

三、污点和容忍

Taint 和 Toleration

污点(Taint)

Ⅰ、 污点 ( Taint ) 的组成

Ⅱ、污点的设置、查看和去除

容忍(Tolerations)

四、固定节点


一 、调度说明

简介

Scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上。听起来非常简单,但有很多要考虑的问题:

公平:如何保证每个节点都能被分配资源

资源高效利用:集群所有资源最大化被使用

效率:调度的性能要好,能够尽快地对大批量的 pod 完成调度工作

灵活:允许用户根据自己的需求控制调度的逻辑

我们的Scheduler就是根据上面的设计出来的

Sheduler 是作为单独的程序运行的,启动之后会一直监听API Server(这里的监听其实就是持续连接的状态),获取 PodSpec.NodeName 为空的 pod,也就是说这个字段里面有值的话,我们就不需要对Sheduler进行调度的,因为PodSpec.NodeName 其实就是指定我们的pod要去哪个地方运行,都指定了也就不需要进行调度了,对每个 pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上。。。

调度过程

调度分为几个部分:首先是过滤掉不满足条件的节点,这个过程称为  predicate(预选) ;然后对通过的节点按照优先级排序,这个是  priority (优选);最后从中选择优先级最高的节点。如果中间任何一步骤有错误,就直接返回错误。

Predicate 有一系列的算法可以使用:

PodFitsResources :节点上剩余的资源是否大于 pod 请求的资源

PodFitsHost :如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配

PodFitsHostPorts :节点上已经使用的 port 是否和 pod 申请的 port 冲突

PodSelectorMatches :过滤掉和 pod 指定的 label 不匹配的节点

NoDiskConflict :已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读

如果在 predicate 过程中没有合适的节点,pod 会一直在  pending  状态,不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程: 按照优先级大小对节点排序。

优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。这些优先级选项包括:

1. LeastRequestedPriority :通过计算 CPU 和 Memory 的使用率来决定权重,使用率越低权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点

2. BalancedResourceAllocation :节点上 CPU 和 Memory 使用率越接近,权重越高。这个应该和上面的一起使用,不应该单独使用

3. ImageLocalityPriority :倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高

通过算法对所有的优先级项目和权重进行计算,得出最终的结果。

下面的是自己定义一个属于自己的调度器

自定义调度器

除了 kubernetes 自带的调度器,你也可以编写自己的调度器。通过 spec:schedulername 参数指定自定义的调度器的名字即可,可以为 pod 选择某个调度器进行调度,比如下面的 pod 选择 my-scheduler 进行调度,而不是默认的default-scheduler 。

apiVersion: v1
kind: Pod
metadata:
  name: annotation-second-scheduler
  labels:
    name: multischeduler-example
spec:
  schedulername: my-scheduler
  containers:
 - name: pod-with-second-annotation-container
   image: gcr.io/google_containers/pause:2.0

二、调度亲和性

什么是调度亲和性?

生活中的示例,张三一定要去3班和张三想去3班,前者是必须去,后者是可去可不去,前者是硬的,后者是软的。

pod跟node节点亲和性

在我们k8s中,我们的节点亲和是通过pod下面的spec下面的nodeAffinity去实现的。

pod.spec.nodeAffinity(下面的是两种类型)

1. preferredDuringSchedulingIgnoredDuringExecution:软策略

2. requiredDuringSchedulingIgnoredDuringExecution:硬策略

下面通过实验测试一下

首先是requiredDuringSchedulingIgnoredDuringExecution(硬策略)

apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity
   image: busybox:alpine
  affinity:                        #亲和性
   nodeAffinity:                    #node的亲和性
     requiredDuringSchedulingIgnoredDuringExecution:    #硬亲和性
       nodeSelectorTerms:
       - matchExpressions:
         - key: kubernetes.io/hostname
           operator: NotIn
           values:
           - master2

分析:上面的简单分析一下,我们的NotIn的含义是,只要我们的hostname不等于master2即可,也就是说我们的pod不会运行在master2这个节点中,而运行在其他节点中,我们通过下面的查看labels的命令可以看到kubernetes.io/hostname等于哪个节点名字。

上面的案例中key其实就是我们node节点的标签,如下我们可以看到每个node的标签:

k8s 调度器

preferredDuringSchedulingIgnoredDuringExecution 软策略

 软亲和性性需要加上权重这个参数,权重越大越亲和,即越可能被调度

apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity
   image: hub.atguigu.com/library/myapp:v1
 affinity:
   nodeAffinity:
     preferredDuringSchedulingIgnoredDuringExecution:
     - weight: 1               #权重为1,权重越大越亲和
       preference:
         matchExpressions:
         - key: kubernetes.io/hostname
           operator: In
           values:
           - master3

分析:上面的的话就是期望运行在我们的master3,有的话最好,没有的话就去其他的node。

软策略和硬策略合并

[root@master1 affinity]# cat pod5.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity
    image: busybox:apline
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: NotIn
            values:
            - master1
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: source
            operator: In
            values:
            - test

[root@master1 affinity]# 
[root@master1 affinity]# kubectl get pod  -o wide
NAME       READY   STATUS         RESTARTS   AGE   IP           NODE      NOMINATED NODE   READINESS GATES
affinity   0/1     ErrImagePull   0          49s   10.244.1.3   master2   <none>           <none>
[root@master1 affinity]# 

必须先满足我们的硬策略,才能去执行我们的软策略。分析一下上面的yaml文件,我们的运算关系是NotIn,所以我们的运行的节点可能是node-01,node-03......,然后在这些节点中如果再满足source等于test的节点,则优先调度。

键值运算关系

In:label 的值在某个列表中

NotIn:label 的值不在某个列表中

Gt:label 的值大于某个值

Lt:label 的值小于某个值

Exists:某个 label 存在

DoesNotExist:某个 label 不存在

注意:如果nodeSelectorTerms下面有多个选项的话,满足任何一个条件即可;如果matchExpressions有多个选项的话,则必须同时满足这些条件才能正常调度pod。

Pod 跟pod亲和性

pod.spec.affinity.podAffinity/podAntiAffinity(在这个下面声明)

preferredDuringSchedulingIgnoredDuringExecution:软策略

requiredDuringSchedulingIgnoredDuringExecution:硬策略

我们先创建一个pod

[root@master1 affinity]# cat pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: master1
  labels:
    app: master1
spec:
  containers:
  - name: with-node-affinity
    image: nginx:alpine
[root@master1 affinity]# kubectl apply -f pod.yaml 
pod/master1 created
[root@master1 affinity]# 
[root@master1 affinity]# kubectl get pod -o wide
NAME      READY   STATUS    RESTARTS   AGE   IP             NODE      NOMINATED NODE   READINESS GATES
master1   1/1     Running   0          7s    10.244.0.186   master1   <none>           <none>
[root@master1 affinity]# 

然后先创建我们的硬策略pod

[root@master1 affinity]# cat  pod2.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-3
  labels:
    app: pod-3
spec:
  containers:
  - name: pod-3
    image: nginx:alpine
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - master1
        topologyKey: kubernetes.io/hostname
[root@master1 affinity]# 
[root@master1 affinity]# 
[root@master1 affinity]# kubectl apply -f   pod2.yaml 
pod/pod-3 created
[root@master1 affinity]# 
[root@master1 affinity]# kubectl get pod -o wide
NAME      READY   STATUS    RESTARTS   AGE     IP             NODE      NOMINATED NODE   READINESS GATES
master1   1/1     Running   0          3m30s   10.244.0.186   master1   <none>           <none>
pod-3     1/1     Running   0          7s      10.244.0.187   master1   <none>           <none>

分析:当我们的集群中的某个pod有app=master1这个标签的时候,我们 -o wide得到该pod所处的node节点,由于这里是硬策略,所以我们上面创建出来的pod-3就会运行在这个node节点上的,判断是否在同一个node的标准就是topologyKey字段了,即kubernetes.io/hostname要是一样的。

即如果想要pod运行在同一个node上,就用podAffinity;

如果不想在同一节点的话,就用podAntiAffinity。案例如下:

我们先删除之前创建的pod

[root@master1 affinity]# kubectl delete -f pod2.yaml 
pod "pod-3" deleted
[root@master1 affinity]#

增加yaml文件podAntiAffinity字段

[root@master1 affinity]# cat  pod2.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-3
  labels:
    app: pod-3
spec:
  containers:
  - name: pod-3
    image: nginx:alpine
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - master1
        topologyKey: kubernetes.io/hostname
[root@master1 affinity]# kubectl apply -f  pod2.yaml 
pod/pod-3 created
[root@master1 affinity]# kubectl get pod 
NAME      READY   STATUS              RESTARTS   AGE
master1   1/1     Running             0          8m27s
pod-3     0/1     ContainerCreating   0          2s
[root@master1 affinity]# kubectl get pod  -o wide
NAME      READY   STATUS              RESTARTS   AGE     IP             NODE      NOMINATED NODE   READINESS GATES
master1   1/1     Running             0          8m36s   10.244.0.191   master1   <none>           <none>
pod-3     0/1     ContainerCreating   0          11s     <none>         master2   <none>           <none>
[root@master1 affinity]# 

上面的 “同一个node” 其实不规范,应该是同一个拓扑域,即根据我们的topologyKey来决定的,例如上面的yaml中kubernetes.io/hostname,这个是具有唯一性,所以同一个拓扑域就是同一个节点了。

关于同一个拓扑域的解释说明如下:‘

例如我们有以下三个node,其中两个node的标签一样,都有disk=1,当我们把topologyKey改成disk的时候,此时我们的pod放在node01和node02都可以,这个就叫做同一拓扑域

k8s 调度器

亲和性/反亲和性调度策略比较如下:

k8s 调度器

官方解释:

如果该X已经在运行一个或多个满足规则Y的Pod,则该Pod应该(或者在非亲和性的情况下不应该)在X中运行

Y表示为LabelSelector规则

X是一个拓扑域,例如节点,机架,云提供者区域,云提供者区域等。您可以使用topologyKey这是系统用来表示这种拓扑域的节点标签的密钥。

关于topologyKey字段可参考https://www.jianshu.com/p/d906e819245c

这里补充一个有意思的玩法:k8s每节点相同服务只部署一个进程

三、污点和容忍

Taint 和 Toleration

节点亲和性,是 pod 的一种属性(偏好或硬性要求),它使 pod 被吸引到一类特定的节点。Taint 则相反,它使节点 能够 排斥 一类特定的 pod。

Taint 和 toleration 相互配合,可以用来避免 pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个taint ,这表示对于那些不能容忍这些 taint 的 pod,是不会被该节点接受的。如果将 toleration 应用于 pod上,则表示这些 pod 可以(但不要求)被调度到具有匹配 taint 的节点上。

举例来说,我们去相亲的话,女方的要求是,不能容忍是抽烟的,那么打上抽烟这一类标签的人就不可能成功;反过来说,女方可以接受抽烟的,那么天下那么多抽烟的,只能有一个。即,我们的污点和容忍的概念就是,如果能容忍这个污点,可能发生故事,但不一定会发生故事;如果不能容忍的话,那么只要那个node有一个污点的话,就不会被调度到这个node上去。

污点(Taint)

Ⅰ、 污点 ( Taint ) 的组成

使用 kubectl taint 命令可以给某个 Node 节点设置污点,Node 被设置上污点之后就和 Pod 之间存在了一种相斥的关系,可以让 Node 拒绝 Pod 的调度执行,甚至将 Node 已经存在的 Pod 驱逐出去。

每个污点的组成如下:

key=value:effect

每个污点有一个 key 和 value 作为污点的标签,其中 value 可以为空,effect 描述污点的作用。当前 taint effect 支持如下三个选项:

NoSchedule :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上

PreferNoSchedule :表示 k8s 将尽量避免将 Pod 调度到具有该污点的 Node 上

NoExecute :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的 Pod 驱逐出去。。即把该Node上的Pod全部排出出去,如果是静态pod的话,驱逐就不会被创建了,相当于delete命令,如果是控制器控制的pod,则会进入到其他的Node上去,因为要维持副本的数目。

Ⅱ、污点的设置、查看和去除

# 设置污点
kubectl taint nodes node1 key1=value1:NoSchedule
# 节点说明中,查找 Taints 字段
kubectl describe pod pod-name
# 去除污点
kubectl taint nodes node1 key1:NoSchedule-

举例:下面的是我们的一个master节点的信息(kubectl describe node k8s-master)

k8s 调度器

这个的意思是,k8s不会将Pod调度到该节点上,也就是为什么我们的pod永远不会在master节点上。(这里省略了value)

容忍(Tolerations)

设置了污点的 Node 将根据 taint 的 effect:NoSchedule、PreferNoSchedule、NoExecute 和 Pod 之间产生互斥的关系,Pod 将在一定程度上不会被调度到 Node 上。 但我们可以在 Pod 上设置容忍 ( Toleration ) ,意思是设置了容忍的 Pod 将可以容忍污点的存在,可以被调度到存在污点的 Node 上。

在pod.spec.tolerations字段定义

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
  tolerationSeconds: 3600          #容忍时间
- key: "key1"
  operator: "Equal"
  value: "value1"
 effect: "NoExecute"
- key: "key2"
  operator: "Exists"
  effect: "NoSchedule"

如果我们设置tolerationSeconds的时候,必须effect为NoExecute,如下图

k8s 调度器

其中 key, value, effect 要与 Node 上设置的 taint 保持一致,operator 的值为 Exists 将会忽略 value 值

tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在 Pod 上继续保留运行的时间,我们之前说过,有个NoExecute 的驱逐机制,如果我们设置了这里的时间的话,虽然pod会被驱逐,但是在设置的时间之后才会被驱逐,例如上面的3600s之后被驱逐。

k8s 调度器

注意以下几个点

Ⅰ、当不指定 key 值时,表示容忍所有的污点 key:

tolerations:
- operator: "Exists"

Ⅱ、当不指定 effect 值时,表示容忍所有的污点作用

tolerations:
- key: "key"
  operator: "Exists"

Ⅲ、有多个 Master 存在时,防止资源浪费,可以如下设置

kubectl taint nodes Node-Name node-role.kubernetes.io/master=:PreferNoSchedule

分析:PreferNoSchedule,这个的含义是“尽可能不在这个node上运行”,当我们后边所有的pod出现资源不够用的情况,这样的话,就可以在我们的master节点上运行对应的pod了。

总结:上面就有个运维的关系,就是假如有一天,我们的某个node需要维护或者是更新的话,但是该node上去已经运行了很多的pod,如果我们直接把node关闭的话,就会造成我们的网路灯访问的影响,这个时候我们可以对该node打上一个污点,就用NoExecute,让所有的pod被驱逐出去,去别的node上面去运行,这样就可以更新该node了。

四、固定节点

即调度到指定的节点中上

Ⅰ、Pod.spec.nodeName 将 Pod 直接调度到指定的 Node 节点上,会跳过 Scheduler 的调度策略,该匹配规则是强制匹配。

[root@master1 affinity]# cat pod4.yaml 
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: myweb
spec:
  replicas: 7
  template:
    metadata:
      labels:
        app: myweb
    spec:
      nodeName: master1
      containers:
      - name: myweb
        image: nginx:alpine
        ports:
        - containerPort: 80
[root@master1 affinity]# 
[root@master1 affinity]# kubectl apply -f  pod4.yaml
deployment.extensions/myweb created
[root@master1 affinity]# 

分析:上面的七个pod全部运行在我们的master2上 

k8s 调度器

Ⅱ、Pod.spec.nodeSelector:通过 kubernetes 的 label-selector 机制选择节点,由调度器调度策略匹配 label,而后调度 Pod 到目标节点,该匹配规则属于强制约束。

[root@master1 affinity]# cat pod4.yaml 
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: myweb
spec:
  replicas: 7
  template:
    metadata:
      labels:
        app: myweb
    spec:
      nodeSelector: 
        disk: ssd
      containers:
      - name: myweb
        image: nginx:alpine
        ports:
        - containerPort: 80
[root@master1 affinity]# 
[root@master1 affinity]# kubectl apply -f  pod4.yaml 
deployment.extensions/myweb created
[root@master1 affinity]# 
[root@master1 affinity]# kubectl get pod 
NAME                     READY   STATUS    RESTARTS   AGE
myweb-74b45c5575-4h9hm   0/1     Pending   0          4s
myweb-74b45c5575-5mjjk   0/1     Pending   0          4s
myweb-74b45c5575-hsvlf   0/1     Pending   0          4s
myweb-74b45c5575-l59fr   0/1     Pending   0          4s
myweb-74b45c5575-mcf8c   0/1     Pending   0          4s
myweb-74b45c5575-v6kzb   0/1     Pending   0          4s
myweb-74b45c5575-w58lk   0/1     Pending   0          4s
[root@master1 affinity]# 

开始我们node的标签不是disk=ssd,所有一直处于Pending状态,后来打上了标签就好了

[root@master1 affinity]# kubectl label node master1 disk=ssd
node/master1 labeled
[root@master1 affinity]#
[root@master1 affinity]# kubectl get pod  -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP             NODE      NOMINATED NODE   READINESS GATES
myweb-74b45c5575-4h9hm   1/1     Running   0          54s   10.244.0.203   master1   <none>           <none>
myweb-74b45c5575-5mjjk   1/1     Running   0          54s   10.244.0.200   master1   <none>           <none>
myweb-74b45c5575-hsvlf   1/1     Running   0          54s   10.244.0.199   master1   <none>           <none>
myweb-74b45c5575-l59fr   1/1     Running   0          54s   10.244.0.205   master1   <none>           <none>
myweb-74b45c5575-mcf8c   1/1     Running   0          54s   10.244.0.201   master1   <none>           <none>
myweb-74b45c5575-v6kzb   1/1     Running   0          54s   10.244.0.202   master1   <none>           <none>
myweb-74b45c5575-w58lk   1/1     Running   0          54s   10.244.0.204   master1   <none>           <none>
[root@master1 affinity]# 

上一篇:k8s下线node节点


下一篇:Rancher管理看k8s集群