pod 的高级调度

简述

Kubernetes允许你去影响pod被调度到哪个节点。 起初, 只能通过在pod规范里指定节点选择器来实现,其实还有更高级的机制比如利用污点和亲缘性来实现pod 的调度。

污点和容忍度

首先要介绍的高级调度的两个特性是节点污点, 以及pod对于污点的容忍度,前者配置在节点上后者配置在Pod上。这些特性被用于限制哪些pod可以被调度到某一个节点。 只有当 一个pod 具有容忍某个节点的污点的容忍度, 这个pod才能被调度到该节点否则不能被调度到该节点。

区别

这与使用节点选择器和节点亲缘性有些许不同,污点是在不修改巳有pod信息的前提下,通过在节点上添加污点信息,来拒绝pod在某些节点上的部署。 

污点和污点容忍度的使用场景

1、默认情况下,一个集群中的主节点需要设置污点,这样才能保证只有控制面板pod才能部署在主节点上。 
2、假设你有一个单独的 Kubernetes 集群,上面同时有生产环境和非生产环境的流量。其中最重要的一点是, 非生产环境的 pod不能运行在生产环境的节点上。可以通过在生产环境的节点上添加污点来满足这个要求。
3、可以将一个集群分成多个部分, 只允许开发团队将pod调度到他们特定的节点上。 当你的部分节点提供了某种特殊硬件, 并且只有部分pod需要使用到这些硬件的时候, 也可以通过设置污点和容忍度的方式来实现。

查看节点上的污点属性和pod 的污点容忍度

节点上的污点属性
    kubectl describe node master.k8s
     ...  ...
       Taints: node-role.kubernetes.io/master:NoSchedule 

    信息显示格式:
      节点包含一个污点,污点包含了 一 个key、value, 以及一个effect, 表现为<key>=<value>:<effect>。上面显示的主节点的污点信息,包含一 个为node­-role.kubernet      es.io/master的key, 一个空的value, 以及值为NoSchedule的effect。这个污点将阻止pod调度到这个节点上,除非有pod能容忍这个污点,而通常容忍这个污点的pod都是系      统级别pod
      

pod 的污点容忍度  
    kubectl describe po kube-proxy-80asd -n kube-system
       ...  ...
       Tolerations:  node-role.kubernetes.io/master=:NoSchedule
                 node.alpha.kubernetes.io/notReady=:Exists:NoExecute
                node.alpha.kubernetes.io/unreachable=:Exists:NoExecute 
       ...  ...
       
       第一个污点容忍度匹配了某个节点的污点,表示允许这个 pod被调度到具有该污点的节点上。

    kubectl get po kube-proxy-80asd -o yaml -n kube-system
      ... ...


注意:
    尽管在pod的污点容忍度中显示了等号,但是在节点的污点信息中却没有。当污点或者污点容忍度中的value为null时,kubectl故意将污点和污点容忍度进行不同形式的显示。
View Code

污点的效果 

如上所示另外两个在kube-proxy pod 上的污点定义了当节点状态是没有ready 或者是 unreachable 时,该 pod 允许运行在该节点多长时间(时间用秒来表示,这里没有显示但是在podYAML 中可以看到)。 这两个污点容忍度使用的效果是 NoExecute 而不是 NoSchedule。
每一个污点都可以关联一个效果, 效果包含了以下三种:
• NoSchedule 表示如果 pod 没有容忍这些污点, pod 则不能被调度到包含这些污点的节点上。

• PreferNoSchedule 是NoSchedule 的一个宽松的版本, 表示尽量阻止pod 被调度到这个节点上,但是如果没有其他节点可以调度,pod依然会被调度到这个节点上。

• NoExecute 不同于 NoSchedule 以及 PreferNoSchedule, 后两者只在调度期间起作用, 而 NoExecute 也会影响正在节点上运行着的 pod。 如果在一个节点上添加了 
  NoExecute 污点, 那些在该节点上运行着的 pod, 如果没有容忍这个 NoExecute 污点, 将会从这个节点去除。
View Code

在节点上添加自定义污点AND在pod上添加污点容忍度

在节点上添加自定义污点

    假设你有一个单独的 Kubernetes 集群, 上面同时有生产环境和非生产环境的流量。 其中最重要的一点是, 非生产环境的 pod 不能运行在生产环境的节点上。 
    可以通过在生产环境的节点上添加污点来满足这个要求
     
    $ kubect1 taint node nodel.k8s node-type=production:NoSchedule
      这个命令添加了一个 taint,其中key为node-type, value为 production,效果为NoSchedule。


在pod上添加污点容忍度

    apiVersion: extensions/vlbetal
    kind: Deployment
    metadata: 
      name: prod 
    spec: 
      replicas: 5 
      template:
        spec: 
          ... ...
          tolerations: 
          - key: node-type
            operator: Equal 
            value: production 
            effect: NoSchedule
View Code

另外

1、节点可以拥有多个污点信息,而pod也可以有多个污点容忍度。正如你所见,污点可以只有一个key和 一 个效果,而不必设置value。污点容忍度可以通过设置Equal操作符来指定匹配的value (默认情况下的操作符),或者也可以通过设置Exists操作符来匹配污点的key。

2、配置节点失效之后的 pod 重新调度最长等待时间你也可以配置一个容忍度, 用于当某个pod运行所在的节点变成unready或者unreachable状态时,Kubemetes可以等待该pod被调度到其他节点的最长等待时间。
如果查看其中一个pod的容忍度信息, 你将看到两条容忍度信息,如下所示:
$ kubect1 get po prod-350605-lphSh -o yaml 
...
  tolerations:
  - effect: NoExecute
    key: node.alpha.kubernetes.io/notReady
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.alpha.kubernetes.io/unreachable 
    operator: Exists 
    tolerationSeconds: 300 

这两个容忍度表示, 该pod将容忍所在节点处于notReady或者unreachable状态维持300秒。 当Kubemetes控制器检测到有节 点处于notReady或者unreachable状态时,将会
等待300秒, 如果状态持续的话, 之后将把该pod重新调度到其他节点上。当没有定义这两个容忍度时, 他们会自动添加到pod上。 如果你觉得对于你的pod来说,5分钟
太长的话,可以在pod描述中显式地将这两个容忍度设置得更短一些。
View Code

 notice

当前这是一个 alpha阶段的特性,在未来的Kubemetes版本中可能会有所改变。基于污点信息的pod剔除也不是默认启用的, 如果要启用这个特性, 需要在运行控制器管理器时使用--feature-gates=TaintBasedEvictions=true 选项

亲缘性及与非亲缘性

节点亲缘性与非亲缘性都是根据节点的标签来进行选择, 这点跟节点选择器是一致的。与节点选择器类似, 每个 pod 可以定义自己的节点亲缘性规则。 这些规则可以允许你指定硬性限制或者偏好。 如果指定一种偏好的话, 你将告知 Kubemetes 对于某个特定的 pod, 它更倾向于调度到某些节点上, 之后 Kubemetes 将尽量把这个pod调度到这些节点上面。 如果没法实现的话, pod将被调度到其他某个节点上。
 
使用节点亲缘性将pod调度到特定节点上
污点可以用来让 pod 远离特定的几点。 现在, 你将学习一种更新的机制, 叫作节点亲缘性 (node affinity), 这种机制允许你通知 Kubemetes将 pod 只调度到某个节点子集上面。
 

默认的节点标签 

$ kubectl describe node gke-kubia-default-pool-db274c5a-mjnf
Name: gke-kubia-default-pool-db2 74c5a-mjnf 
Role: 
Labels: beta.kubernetes.io/arch=amd64 
        beta.kubernetes.io/fluentd 
        beta.kubernetes.io/instance-type=fl-micro
        beta.kubernetes.io/os=linux
        cloud.google.com/gke-nodepool=default-pool
        failure-domain.beta.kubernetes.io/region=europe-westl
        failure-domain.beta.kubernetes.io/zone=europe-westl-d
        kubernetes.io/hostname=gke-kubia-default-pool-db274c5a-msd
这个节点有很多标签,但涉及节点亲缘性和 pod 亲缘性时,最后3个标签是最重要的。这3个标签的含义如下:
  failure domain beta.kubernetes /region 表示该节点所在的地理地域
  failure domain beta kubernetes.io/zone 表示该节点所在的可用性区域( availibility zone )
  kubernetes.io hostname 很显然是该节点的主机名
View Code

 

指定强制性节点亲缘性规则

apiVersion : 
ki nd: Pod 
metadata: 
name: kubia-gpu 
spec: 
  affinity : 
    nodeAffinity: 
      requiredDuringSchedulingignoredDuringExecution: 
        nodeSelectorTerms :
        - matchExpressions: 
            - key: gpu 
              operator: In 
              values: 
              - ”true”

解释:
requiredDuringScheduling ... 表明了该宇段下定义的规则,为了让pod能调度到该节点上,明确指出了该节点必须包含的标签,也就是强制性的要求必须调度到规则匹配的标签的节点

...IgnoredDuringExecution    表明了该字段下定义的规则, 不会影响己经在节点上运行着的 pod

nodeSelectorTerrns 和 matchExpressions 字段,这两个宇段定义了节点的标签必须满足哪一种表达式,才能满足 pod 调度的条件。样例中的单个表达式比较容易理解,节点必须包含一个叫作 gpu 的标签,并且这个标签的值必须是 true
View Code

 

调度 pod 时优先考虑某些节点

当调度某 pod 时,指定调度器可以优先考虑哪些节点,这个功能是通过 preferredDuringSchedulingignoredDuringExecution 字段来实现的
给节点加上标签
    $ kubectl label node nodel.k8s availability-zone=zonel 

    $ kubectl label node nodel.k8s share-type=dedicated

    $ kubectl label node node2.k8s availability-zone=zone2

    $ kubectl label node node2.k8s share-type=shared 

    $ kubectl get node -L availability-zone -L share-type 
    NAME         STATUS     AGE     VERSION     AVAILABILITY-ZONE     SHARE-TYPE 
    master.k8s     Ready     4d         vl.6.4         <none>                 <none> 
    nodel.k8s     Ready     4d         vl.6.4         zonel                 dedicated 
    node2.k8s     Ready     4d         vl.6.4         zone2                 shared


指定优先级节点亲缘性规则
    当这些节点的标签设置好,现在可以创建一个Deployment, 其中优先选择zonel中的dedicated节点。
    apiVersion: extensions/vlbetal
    kind: Deployment 
    metadata: 
    name: pref 
    spec: 
      template:
        spec: 
          affinity: 
            nodeAffinity: 
              preferredDuringSchedulingignoredDuringExecution: 
              - weight: 80 
                preference: 
                  matchExpressions: 
                  - key: availability-zone
                    operator: In 
                    values: 
                    - zonel
              - weight: 20 
                preference: 
                  matchExpressions: 
                  - key: share-type
                    operator: in
                    values:
                    - dedicated        

    上面配置定义了一个节点亲缘性优先级,而不是强制要求。你想要pod被调度到包含标签availability-zone =zonel以及weight设置为80,而第二个优先级规则
    就不那么重要(weight设置为20)。
View Code

 

了解节点优先级是如何工作的

如果你的集群包含多个节点, 当调度上面的代码清单中的Deployment pod时, 节点将会分成4个组, 如图所示。 那些包含Availability-zone以及share-type 标签, 并且匹配pod亲缘性的节点, 将排在最前面。 然后, 由于pod的节点亲缘性规则配置的权重, 接下来是zonel的shared节点, 然后是其他区域的dedicated节点, 优先级最低的是剩下的其他节点。

 

 

在一个包含两个节点的集群中部署节点

如果你在一个包含两个节点的集群中创建该部署, 你看到的最多的应该是pod被部署在了nodel上面。检查下面的代码清单看情况是否属实。 
$ kubectl get po -o wide 
NAME         READY  STATUS   RESTARTS AGE   IP       NODE 
pref-607515-lrnwv 1/1   Running   0     4m   10.47.0.1   node2 kBs 
pref-607515-27wp0 1/1   Running   0     4m   10.44.0.8   nodel.k8s 
pref-607515-SxdOz 1/1   Running   0     4m   10.44.0.5   nodel.kBs 
pref-607515-jx9ww 1/1   Running   0     4m   10.44.0.4   nodel.k8s 
pref-607515-mlgqm 1/1   Running   0     4m   10.44.0.6   nodel. k8s
View Code
5个pod被创建,其中4个部署在了nodel, 1个部署在了node2。为什么会 有1个pod会被调度到node2而不是nodel ? 原因是除了节点亲缘性的优先级函数,调度器还是使用其他的优先级函数来决定节点被调度到哪。其中之一就是Selector SpreadPrio豆ty函数,这个函数确保了属于同一个ReplicaSet或者Service 的pod,将分散部署在不同节点上,以避免单个节点失效导致这个服务也宕机。这就是有1个pod被调度到node2 的最大可能。

 

使用pod亲缘性与非亲缘性对pod进行协同部署
你已经了解了节点亲缘性规则是如何影响pod被调度到哪个节点。但是, 这些规则只影响了pod和节点之间的亲缘性。然而,有些时候你也希望能有能力指定pod自身之间的亲缘性。
想象一下你有一个前端pod和一个后端pod, 将这些节点部署得比较靠近,可以降低延时,提高应用的性能。可以使用节点亲缘性规则来确保这两个pod被调度到同一个节点、同一个机架、同一个数据中心。但是,之后还需要指定调度到具体哪个节点、哪个机架或者哪个数据中心。因此,这不是一个最佳的解决方案。更好的做法应该是,让Kubemetes将你的pod部署在任何它觉得合适的地方,同时确保 2个pod是靠近的。这种功能可以通过pod亲缘性来实现
 

使用pod间亲缘性将多个pod部署在同一个节点上

你将部署1个后端pod和5个包含pod亲缘性配置的前端pod实例,使得这些前端实例将被部署在后端pod所在的同一个节点上。
首先,我们来部署后端pod:
    $ kubectl run backend -1 app=backend --image busybox -- sleep 999999 
      deployment "backend" created
      
在 pod 定义中指定 pod 亲缘性
    apiversion: extensions/vlbetal 
    kind: Deployment
    metadata: 
      name: frontend 
    spec: 
      replicas: 5 
      template:
        spec:
          affinity:
            podAffinity:
              requiredDuringSchedulingignoredDuringExecution:
              - topologykey: kubernetes.io/hostname
                labelSelector:
                  matchLabels:
                    app: backend
          
    代码清单显示了,该部署将创建包含强制性要求的pod, 其中要求pod将被调度到和其他包含app =backend标签的pod所在的相同节点上(通过topologyKey字段指定)

所有的前端pod 确实都被调度到了和后端pod 相同的节点上。当调度前端pod时,调度器首先找出所有匹配前端pod的podAffinity配置中labelSelector的pod,之后将前端pod调度到相同的节点上。
View Code
 如果现在你删除了后端 pod, 调度器会将该 pod 调度到与前端相同的节点, 即便后端 pod 本身没有定义任何 pod 亲缘性规则(只有前端 pod 设置了规则)。 这种情况很合理, 因为假设后端 pod被误删除而被调度到其他节点上, 前端 pod的亲缘性规则就被打破了

 

将pod部署在同一机柜、 可用性区域或者地理地域

在前面的例子中, 使用了pod.Affin江y将前端pod和后端pod部署在了同一个节点上。你可能不希望所有的前端pod都部署在同一个节点上, 但仍希望和后端pod保持足够近, 比如在同一个可用性区域中。 
在同一个可用性区域中协同部署pod
  如果节点运行在不同的可用性区域中, 那么需要将topologyKey属性设置为failure-domain.beta.kubernetes.io/zone, 以确保前端pod和后端pod运行在同一个可用性区域中。

在同—个地域中协同部署pod
  为了允许你将pod部署在同一个地域而不是区域内(云服务提供商通常拥有多个地理地域的数据中心, 每个地理地域会被划分成多个可用性区域), 那么需要将topologyKey属性设置   为failure-domain.be迳.kubernetes.io/region。
View Code

 

topologyKey是如何工作的

topologyKey字段决定了pod不能被调度(非亲缘性)或能被调度(亲缘性)的范围。
 目前提到的3个键并没有什么特别的。如果你愿意,可以任意设置自定义的键,例如rack,为了让pod能部署到同一个机柜。唯一的前置条件就是,在你的节点上加上rack标签。
 
举例来说,你有20个节点,每10个节点在同一个机柜中,你将前10个节点加上标签rack=rackl,另外10个加上标签rack=rack2。接着,当定义pod的podAffinity时,将toplogyKey设置为rack。
 
当调度器决定pod调度到哪里时,它首先检查pod的podAffinity配置,找出那些符合标签选择器的pod, 接着查询这些pod运行在哪些节点上。特别的是,它会寻找标签能匹配podAffinity配置中topologyKey的节点(也就是说topologykey 配置的属性是表示节点的标签,通过标签选择器匹配的pod 所在的所有节点中标签与topologykey配置一致的就符合pod 调度的条件 )。接着,它会优先选择所有的标签匹配pod的值的节点。
 

表达pod亲缘性优先级取代强制性要求

前面谈论了节点亲缘性,你了解了nodeAffinity可以表示一种强制性要求 ,表示pod只能被调度到符合节点亲缘性规则的节点上。它也可以表示一种节点优先级,用于告知调度器将pod调度到某些节点上, 同时也满足当这些节点出于各种原因无法满足 pod 要求时,将pod调度到其他节点上。这种特性同样适用于podAffinity, 你可以告诉调度器,优先将前端pod调度到和后端pod相同的节点上,但是如果不满足需求,调度到其他节点上也是可以的。 
 
apiVersion: extensions/vlbetal
kind: Deployment
metadata: 
  name: frontend
spec: 
  replicas: 5 
  template: 
    spec: 
      affinity: 
        podAffinity: 
          preferredDuringSchedulingignoredDuringExecution:             # preferred 替代了required 
          - weight: 80
            podAffinityTerm:
                topologyKey: kubernetes.io/hostname
                labelSelector: 
                  matchLabels: 
                    app: backend 
      containers: ...
      
            
跟nodeAffinity优先级规则一样,需要为一个规则设置一个权重。同时也需要设置topologyKey和labelSelector, 正如pod.Affinty规则中的强制性要求一样。
View Code

 

利用pod的非亲缘性分开调度pod

现在已经知道了如何告诉调度器对pod进行协同部署, 但有时候你的需求却恰恰相反, 你可能希望pod远离彼此。 这种特性叫作pod非亲缘性。 它和pod亲缘性的表示方式一样, 只不过是将podAffinty字段换成podAntiAffinty, 这将导致调度器永远不会选择那些有包含podAntiAffinity匹配标签的pod所在的节点
 
就是当两个集合的pod, 如果运行在同一个节点上会影响彼此的性能。 在这种情况下, 你需要告知调度器永远不要将这些pod部署在同一个节点上。 另 一个例子是强制让调度器将同一组的pod分在在不同的可用性区域或者地域, 这样让整个区域或地域失效之后, 不会使得整个服务完全不可用。
posted @ 2020-02-04 16:04  fanggege  阅读(566)  评论(0编辑  收藏  举报