(七)k8s集群调度

一、调度说明

1.1、简介

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

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

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

    比如节点1上面内存和cpu都已经到达峰值了,而节点2内存和cpu都使用10%不到,结果后续还是把pod运行在节点1上,这样显然是不合理的。

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

    不能说,后续调度一个节点需要花费10分钟,这肯定是不可以接受的。

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

    用户可以指定pod在某一个节点运行,或者定义一些规则后再让Sheduler 帮我们选择满足条件的节点。

Sheduler 是作为单独的程序运行的,启动之后会一直坚挺 API Server,获取 PodSpec.NodeName 为空的 pod,对每个 pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上,因为如果PodSpec.NodeName不为空,则说明,你已经指定当前的pod运行在指定的节点上了,就不再需要Sheduler 来帮你选择节点了,它就不需要参与这个过程了。

1.2、调度过程

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

Predicate 有一系列(这里只是列举了一部分,更多的可以去k8s官网查看)的算法可以使用:

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

    这个很嗨理解,当前运行的pod需要内存2G,那么运行的pod肯定最少也要有2G的内存。

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

    这就是说明当亲Pod已经指定了具体的node节点,不需要判断其它规则了

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

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

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

如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程: 按照优先级大小对节点排序优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。这些优先级选项包括:

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

    比如节点1上面内存和cpu都已经到达峰值了,而节点2内存和cpu都使用10%不到,这样节点2的权重肯定更高

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

    比如节点1 cpu 和内存都是使用了40%,而节点2cpu使用20%,内存使用了60%,那么节点1的权重会更高,因为节点1的CPU 和 Memory 使用率更接近。

  • ImageLocalityPriority :倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高通过算法对所有的优先级项目和权重进行计算,得出最终的结果

1.3、自定义调度器

除了 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/goo

二、亲和性

举例说明,比如分班,现在有两个老师的带班,分别是张三老师的一班和王五老师的二班,那么这个班级就是节点,小明和小红同学就是pod

  • 节点硬策略:那就是说,小明说我一定要去张三老师的一班,如果去不了我就不上学了,即pod不运行
  • 节点软策略:那就是说,小明说我很想去张三老师的一班,但是如果实在去不了,我就去其它班级上学也是可以的。
  • pod硬策略:那就是说,小明说我一定要去小红(相当于另一个pod)同学在的班级,如果去不了我就不上学了
  • pod软策略:那就是说,小明说我很想要去小红(相当于另一个pod)同学在的班级,但是如果实在去不了,我就去其它班级上学也是可以的。

这里需要说明一点,这个亲和性很是灵活,如果你发现这个pod没有找到你想要找到的pod/node,是因为标签不符合,你可以给对应的pod/node再打上一个标签,这样就可以了。

2.1、节点亲和性

pod.spec.nodeAffinity

  • preferredDuringSchedulingIgnoredDuringExecution:软策略
  • requiredDuringSchedulingIgnoredDuringExecution:硬策略

键值运算关系

项目 Value
In label 的值在某个列表中
NotIn label 的值不在某个列表中
Gt label 的值大于某个值
Lt label 的值小于某个值
Exists 某个 label 存在
DoesNotExist 某个 label 不存在

2.1.1、requiredDuringSchedulingIgnoredDuringExecution 节点硬策略

下面这个案例就是说叫做 affinity 的pod必须不运行在节点k8s-node02上。

apiVersion: v1
kind: Pod
metadata:
  name: affinity  # pod 的名称
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity # pod内部第一个容器的名称
    image: hub.atguigu.com/library/myapp:v1
  affinity:
    nodeAffinity:  # 使用节点亲和性
      requiredDuringSchedulingIgnoredDuringExecution:  # 使用硬策略
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: NotIn
            values:
            - k8s-node02

至于 kubernetes.io/hostname,是我们k8s安装成功之后,会自动获取服务器的hostname,把它设置为对应的名称,如下
(七)k8s集群调度_第1张图片
运行如下
在这里插入图片描述
你会发现这个pod会一直运行在 k8s-node01上,我们把它删除重新建立也是一样,因为我们现在集群节点只有3个,但是master节点上面是有污点的,是不允许运行pod的,所以,可供选择的只有node1 和node2,但是我们前面有设置了硬策略,一定不能运行在node2上面,所以,只能运行在node1上面,无论你删除创建多少次都是一样的。
(七)k8s集群调度_第2张图片

当然这里在多做说明一下,如果在上面的硬策略中指定的节点为一定在node3中,但是我们又没有这个节点,那么这个pod永远都无法成功运行。

注意硬策略可以写多个,当你写多个时,则说明这个pod必须都满足这些硬策略才能运行成功。

2.1.2、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:
          matchExpression
          - key: source
            perator: In
            values:
            - qikqiak

上面我们指定这个pod运行在节点有 key=source,value=qikqiak的节点上,如果没有,那就算了,则按照其它选择,选择一个其它节点运行即可。

这里有一点需要注意的就是软策略里面有 weight权重,意思是当你为一个pod写了多个软策略时,你可以指定那个软策略优先,比如,小明去张三老师的一班的权重是4,而小明去王五老师的二班的权重是3,当两者都可以满足的时候,优先去张三老师的班级,因为它的权重更高。当然,如果去不了张三老师的班级,那么看看去王五老师的班级条件是否满足,如果满足则去王五老师的班级。

2.1.3、软策略组合硬策略

即可以选择硬策略和软策略一起使用,则硬策略一定要满足,再考虑软策略。示例如下

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:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: NotIn
            values:
            - k8s-node02
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: source
            operator: In
            values:
            - qikqiak

2.2、pod亲和性

我们先运行一个pod,用于测试pod亲和性,如下,这个pod有标签 app=gkj

apiVersion: v1
kind: Pod
metadata:
  name: otherPod
  labels:
    app: gkj
spec:
  containers:
  - name: otherPod-c1
    image: hub.atguigu.com/library/myapp:v1

并且查看这个pod 是运行在 k8s-node01上面的。

2.2.1、requiredDuringSchedulingIgnoredDuringExecution pod硬策略

apiVersion: v1
kind: Pod
metadata:
  name: pod-3
  labels:
    app: pod-3
spec:
  containers:
  - name: pod-3
    image: hub.atguigu.com/library/myapp:v1
  affinity:
    podAffinity:  # 注意这里是podAffinity 而不是 nodeAffinity
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:   # 匹配规则必须运行在不同的hostName中有运行的pod中标签带有 app=pod-1的节点
          - key: app
            operator: In
            values:
            - gkj
        topologyKey: kubernetes.io/hostname

这里有两个地方需要注意

  • 第一个这里的亲和性参数是 podAffinity,不是 nodeAffinity
  • 第二个这里有一个参数 topologyKey,这个是区分 拓扑域的,那什么是拓扑域

如下,是指以同一个某一种标签区分的范围划分(七)k8s集群调度_第3张图片这里有三台节点,每个节点上面都存有标签,如果以标签划分这个三台节点的拓扑域,那么第一台和第二台就属于同一个拓扑域,比如命名为拓扑域一号,第三台独属于另一个,比如命名为拓扑域二号。所以我们上面以kubernetes.io/hostname区分拓扑域,则表示,每一台主机为一个拓扑域(如果你没有为两台节点设置同一个hostname的话,估计真设置一样,k8s也搭建不起来)

结果如下,即使删除再创建也是node01
(七)k8s集群调度_第4张图片

其它情况说明

情况 结果说明
对于上面的拓扑域来说 如果是以我们上面那种图片来作为拓扑域,假设otherPod运行在第一个拓扑域,那么我们后续再运行的pod-3这个是可以运行在node1(disk=1)或者node2(disk=1)上面的,因为此时的node1和node2在同一个拓扑域
如果此时的将pod-3的pod亲和性的标签改为不存在的 则这个pod-3是永远都运行不起来的,其实硬策略,软策略的概念都差不多,只是区分了是节点还是pod。
pod软策略 软策略里面有 weight权重,意思是当你为一个pod写了多个软策略时,你可以指定那个软策略优先,比如,小明(pod)小红(pod) 的班级的权重是4,而小明去小绿的班级的权重是3,当两者都可以满足的时候,优先去小红的班级,因为它的权重更高。当然,如果去不了小红班级,那么看看去小绿的班级条件是否满足,如果满足则去小绿班级

2.2.2、preferredDuringSchedulingIgnoredDuringExecution pod软策略

对与软策略呢,其实没啥太多好说的,看完前面的,你就知道了,但是这里借助pod软策略,再说一个是pod独有的,叫做 podAntiAffinity ,前面的叫做 podAffinity,作用是 POD与指定POD不在同一拓扑域

apiVersion: v1
kind: Pod
metadata:
  name: pod-3
  labels:
    app: pod-3
spec:
  containers:
  - name: pod-3
    image: hub.atguigu.com/library/myapp:v1
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - gkj
          topologyKey: kubernetes.io/hostname

这个上面的意思就是说最好运行在存在 app=gkj的 pod的节点上,且不在这个pod的拓扑域中。

2.2.3、软策略组合硬策略

省略,这个和上面的节点的组合是一样的,只是参数名称不一样,这里不再举例说明。

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

调度策略 匹配标签 操作符 拓扑域支持 调度目标
nodeAffinity 主机 In, NotIn, Exists, DoesNotExist, Gt, Lt 指定主机
podAffinity POD In, NotIn, Exists, DoesNotExist POD与指定POD同一拓 扑域
podAnitAffinity POD In, NotIn, Exists, DoesNotExist POD与指定POD不在同一拓扑域

三、污点与容忍

注重: 污点是打标签在节点(node)上面的,由pod来选择是否容忍。

节点亲和性,是 pod 的一种属性(偏好或硬性要求),它使 pod 被吸引到一类特定的节点。Taint(污点) 则相反,它使节点 能够 排斥 一类特定的 pod,Taint 和 toleration(容忍) 相互配合,可以用来避免 pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个 taint ,这表示对于那些不能容忍这些 taint 的 pod,是不会被该节点接受的。如果将 toleration 应用于 pod上,则表示这些 pod 可以(但不要求)被调度到具有匹配 taint 的节点上

3.1、污点(Taint)

其实我们在搭建完成k8s的集群的时候,master节点上面就已经打上了标签,effect的值是NoSchedule
,这也是为什么我们在创建pod的时候,不会运行在master节点上的原因,因为我们创建的pod并没有设置可以容忍这个污点,所以schedule在分配的时候,不会分配到master节点上面。

kubectl describe node k8s-master01

(七)k8s集群调度_第5张图片

3.1.1、污点 ( Taint ) 的组成

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

每个污点的组成如下:

key=value:effect

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

  • NoSchedule :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上
  • PreferNoSchedule :表示 k8s 将尽量避免将 Pod 调度到具有该污点的 Node 上
  • NoExecute :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的 Pod 驱逐出去

注意这里的key和value,可以随意指定,后面你在修改的时候,也要保证一样即可

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

# 设置污点
kubectl taint nodes pod-name key1=value1:NoSchedule
 
# 节点说明中,查找 Taints 字段
kubectl describe pod  pod-name  
 
# 去除污点  去除的时候,只要在之前的污点后面加上一个减号(-)即可
kubectl taint nodes pod-name key1:NoSchedule-

3.1.3、举例

现在有两个pod运行在k8s-node01上面,我们把node01打上污点,并设置effect为NoExecute,注意这里的key和value,可以随意指定,后面你在修改的时候,也要保证一样即可
(七)k8s集群调度_第6张图片

我们再看,驱逐之后,没有pod了,因为pod是自主式的,不会重新建立重启等等,而如果我们创建是控制器的pod,如果是deploment,则会在node02上面再创建pod,因为它要维持它的副本数,这个可以看前面的控制器章节文章,说的很清楚了。
(七)k8s集群调度_第7张图片

那我们再给k8s-node02也打上污点,即使我们现在再创建一个pod,它也不会启动了,因为现在三个节点都有污点了,master01本身就有污点,node01(前面刚刚打过污点),node02(现在打了污点)
(七)k8s集群调度_第8张图片

3.2、容忍(Tolerations)

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

3.2.1、说明

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"
  • 其中 key, vaule, effect 要与 Node 上设置的 taint 保持一致
  • operator 的值为 Exists 将会忽略 value 值
  • tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在 Pod 上继续保留运行的时间,即先运行指定时间后再被驱除。

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

tolerations:
- operator: "Exists"

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

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

3、有多个 Master 存在时,防止资源浪费,可以如下设置
最好不在master节点中运行,除非其它节点实在压力过大。

kubectl taint nodes k8s-master02 node-role.kubernetes.io/master=:PreferNoSchedule

3.2.2、举例

创建可以容忍污点的pod,表示 pod-3可以容忍污点 key1=value1:NoSchedule,并在驱逐的时候,继续保持运行3600秒后在被驱除。
(七)k8s集群调度_第9张图片
最后你就会发现它可以允许在一些污点为 key1=value1:NoSchedule 的节点上面了。

四、固定节点

4.1、通过指定节点名称 Pod.spec.nodeName

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

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: myweb
spec:
  replicas: 7
  template:
    metadata:
      labels:
        app: myweb
    spec:
      nodeName: k8s-node01   # 指定当前Deployment 运行在那个节点上面
      containers:
      - name: myweb
        image: hub.atguigu.com/library/myapp:v1
        ports:
        - containerPort: 80

我们指定的运行的节点为node01,而且运行了7个副本书,创建
在这里插入图片描述
如果按照正常情况,这个7个副本数会分散一部分到node02上面,因为要保证调度策略负载等等,结果,全都是在k8s-node01上面。
(七)k8s集群调度_第10张图片

4.2、通过标签选择节点 Pod.spec.nodeSelector

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

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: myweb
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: myweb11111
    spec:
      nodeSelector:
        disk: ssd      # 运行在有标签为 disk=ssd 的节点上面
      containers:
      - name: myweb11111
        image: harbor/tomcat:8.5-jre8
        ports:
        - containerPort: 80

我们指定标签,使其运行在有标签为 disk=ssd 的节点上面,如下,一开始我们发现没有运行成功,一直处于pending,因为我们没有节点有这个标签
(七)k8s集群调度_第11张图片
后面我们手动为节点node01增加这个标签,结果就可以看到运行起来了,并运行在k8s-node01上面

kubectl label node ks8-node01  disk=ssd

在这里插入图片描述

而此时,我们修改其副本数为8个,并为node02也设置相同的标签,此时scuedule就会将8个pod散列的分配到node01和node02上面
(七)k8s集群调度_第12张图片

你可能感兴趣的:(k8s,kubernetes,k8s)