Kubernetes的默认调度器以预选、优选、选定机制完成将每个新的Pod资源绑定至为其选出的目标节点上,不过,它只是Pod对象的默认调度器,默认情况下调度器考虑的是资源足够,并且负载尽量平均。
在使用中,用户还可以自定义调度器插件,并在定义Pod资源配置清单时通过spec.schedulerName指定即可使用,这就是亲和性调度。

1、Node亲和性调度

NodeAffinity意为Node节点亲和性的调度策略,是用于替换NodeSelector的全新调度策略。
这些规则基于节点上的自定义标签和Pod对象上指定的标签选择器进行定义 。 节点亲和性允许Pod对象定义针对一组可以调度于其上的节点的亲和性或反亲和性,不过,它无法具体到某个特定的节点 。
例如,将Pod调度至有着特殊CPU的节点或一个可用区域内的节点之上 。

定义节点亲和性规则时有两种类型的节点亲和性规则 :硬亲和性required和软亲和性preferred。 硬亲和性实现的是强制性规则,它是Pod调度时必须要满足的规则,而在不存在满足规则的节点时 , Pod对象会被置为Pending状态。 而软亲和性规则实现的是一种柔性调度限制,它倾向于将Pod对象运行于某类特定的节点之上,而调度器也将尽量满足此需求,但在无法满足调度需求时它将退而求其次地选择一个不匹配规则的节点。

定义节点亲和规则的关键点有两个,一是为节点配置合乎需求的标签,另一个是为Pod对象定义合理的标签选择器,从而能够基于标签选择出符合期望的目标节点。不过,如preferredDuringSchedulinglgnoredDuringExecutionrequiredDuringSchedulinglgnoredDuringExecution名字中的后半段符串lgnoredDuringExecution隐含的意义所指,在Pod资源基于节点亲和性规则调度至某节点之后,节点标签发生了改变而不再符合此节点亲和性规则时 ,调度器不会将Pod对象从此节点上移出,因为,它仅对新建的Pod对象生效。 节点亲和性模型如图所示:

1.1、Node硬亲和性

Pod对象使用nodeSelector属性可以基于节点标签匹配的方式将Pod对象强制调度至某一类特定的节点之上 ,不过它仅能基于简单的等值关系定义标签选择器,而nodeAffinity中支持使用 matchExpressions属性构建更为复杂的标签选择机制。例如,下面的配置清单示例中定义的Pod对象,其使用节点硬亲和规则定义可将当前Pod对象调度至拥有zone标签且其值为foo的节点之上

apiVersion: v1
kind: Pod
metadata:
  name: with-required-nodeaffinity
spec:
  affinity:
    nodeAffinity: 
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - {key: zone, operator: In, values: ["foo"]}
  containers:
  - name: nginx
    image: nginx

将上面配置清单中定义的资源创建于集群之中,由其状态信息可知它处于Pending阶段,这是由于强制型的节点亲和限制场景中不存在能够满足匹配条件的节点所致:

# kubectl apply -f required-nodeAffinity-pod.yaml 
pod/with-required-nodeaffinity created
# kubectl get pods with-required-nodeaffinity 
NAME                         READY   STATUS    RESTARTS   AGE
with-required-nodeaffinity   0/1     Pending   0          8s

通过describe查看对应的events

# kubectl describe pods with-required-nodeaffinity
...
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.

规划为各节点设置节点标签 ,这也是设置节点亲和性的前提之一

# kubectl label node k8s-node-01 zone=foo
node/k8s-node-01 labeled
# kubectl label node k8s-node-02 zone=foo
node/k8s-node-02 labeled
# kubectl label node k8s-node-03 zone=bar
node/k8s-node-03 labeled

查看调度结果

# kubectl describe pods with-required-nodeaffinity
...
Events:
  Type     Reason            Age        From                  Message
  ----     ------            ----       ----                  -------
  Warning  FailedScheduling  <unknown>  default-scheduler     0/3 nodes are available: 3 node(s) didn't match node selector.
  Normal   Scheduled         <unknown>  default-scheduler     Successfully assigned default/with-required-nodeaffinity to k8s-node-01

在定义节点亲和性时,requiredDuringSchedulinglgnoredDuringExecution字段的值是一个对象列表,用于定义节点硬亲和性,它可由一到多个nodeSelectorTerm定义的对象组成, 彼此间为“逻辑或”的关系,进行匹配度检查时,在多个nodeSelectorTerm之间只要满足其中之一 即可。nodeSelectorTerm用于定义节点选择器条目,其值为对象列表,它可由一个或多个matchExpressions对象定义的匹配规则组成,多个规则彼此之间为“逻辑与”的关系, 这就意味着某节点的标签需要完全匹配同一个nodeSelectorTerm下所有的matchExpression对象定义的规则才算成功通过节点选择器条目的检查。而matchExmpressions又可由 一到多 个标签选择器组成,多个标签选择器彼此间为“逻辑与”的关系 。

下面的资源配置清单示例中定义了调度拥有两个标签选择器的节点挑选条目,两个标签选择器彼此之间为“逻辑与”的关系,因此,满足其条件的节点为node01node03

apiVersion: v1
kind: Pod
metadata:
  name: with-required-nodeaffinity-2
spec:
  affinity:
    nodeAffinity: 
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - {key: zone, operator: In, values: ["foo", "bar"]}
          - {key: ssd, operator: Exists, values: []}
  containers:
  - name: nginx
    image: nginx

构建标签选择器表达式中支持使用操作符有InNotlnExistsDoesNotExistLtGt

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

另外,调度器在调度Pod资源时,节点亲和性MatchNodeSelector仅是其节点预选策 略中遵循的预选机制之一,其他配置使用的预选策略依然正常参与节点预选过程。 例如将上面资源配置清单示例中定义的Pod对象容器修改为如下内容并进行测试

apiVersion: v1
kind: Pod
metadata:
  name: with-required-nodeaffinity-3
spec:
  affinity:
    nodeAffinity: 
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - {key: zone, operator: In, values: ["foo", "bar"]}
          - {key: ssd, operator: Exists, values: []}
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        cpu: 6
        memory: 20Gi

在预选策略PodFitsResources根据节点资源可用性进行节点预选的过程中,它会获取给定节点的可分配资源量(资源问题减去已被运行于其上的各Pod对象的requests属性之和),去除那些无法容纳新Pod对象请求的资源量的节点,如果资源不够,同样会调度失败。

由上述操作过程可知,节点硬亲和性实现的功能与节点选择器nodeSelector相似, 但亲和性支持使用匹配表达式来挑选节点,这一点提供了灵活且强大的选择机制,因此可被理解为新一代的节点选择器。

1.2、Node软亲和性

节点软亲和性为节点选择机制提供了一种柔性控制逻辑,被调度的Pod对象不再是“必须”而是“应该”放置于某些特定节点之上,当条件不满足时它也能够接受被编排于其他不符合条件的节点之上。另外,它还为每种倾向性提供了weight属性以便用户定义其优先级,取值范围是1 ~ 100,数字越大优先级越高 。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deploy-with-node-affinity
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 60
            preference:
              matchExpressions:
              - {key: zone, operator: In, values: ["foo"]}
          - weight: 30
            preference:
              matchExpressions:
              - {key: ssd, operator: Exists, values: []}
      containers:
      - name: nginx
        image: nginx

Pod资源模板定义了节点软亲和性以选择运行在拥有zone=foossd标签(无论其值为何)的节点之上, 其中zone=foo是更为重要的倾向性规则, 它的权重为60,相比较来说,ssd标签就没有那么关键, 它的权重为30。 这么一来, 如果集群中拥有足够多的节点,那么它将被此规则分为四类 : 同时满足拥有zone=foossd标签、仅具有zoo=foo标 签、 仅具有ssd标签, 以及不具备此两个标签, 如图所示

示例环境共有三个节点,相对于定义的节点亲和性规则来说,它们所拥有的倾向性权重分别如图所示。在创建需要3Pod对象的副本时,其运行效果为三个Pod对象被分散运行于集群中的三个节点之上,而非集中运行于某一个节点 。

之所以如此,是因为使用了节点软亲和性的预选方式,所有节点均能够通过调度器上MatchNodeSelector预选策略的筛选,因此,可用节点取决于其他预选策略的筛选结果。在第二阶段的优选过程中,除了NodeAffinityPriority优选函数之外,还有其他几个优选函数参与优先级评估,尤其是SelectorSpreadPriority,它会将同一个ReplicaSet控制器管控的所有Pod对象分散到不同的节点上运行以抵御节点故障带来的风险 。不过,这种节点亲和性的权重依然在发挥作用,如果把副本数量扩展至越过节点数很多,如15个, 那么它们将被调度器以接近节点亲和性权重比值90:60:30的方式分置于相关的节点之上。

2、Pod亲和性调度

2.1、位置拓扑

Pod亲和性调度需要各相关的Pod对象运行于“同一位置”, 而反亲和性调度则要求它们不能运行于“同一位置” 。同一位置取决于节点的位置拓扑, 拓扑的方式不同。

如果以基于各节点的kubernetes.io/hostname标签作为评判标准,那么很显然,“同一位置” 意味着同一个节点,不同节点即不同的位置, 如图所示

如果是基于所划分的故障转移域来进行评判,同一位置, 而server2server3属于另一个意义上的同一位置

因此,在定义Pod对象的亲和性与反亲和性时,需要借助于标签选择器来选择被依赖的Pod对象,并根据选出的Pod对象所在节点的标签来判定“同一位置”的具体意义。

2.2、Pod硬亲和

Pod强制约束的亲和性调度也使用requiredDuringSchedulinglgnoredDuringExecution属性进行定义。Pod亲和性用于描述一个Pod对象与具有某特征的现存Pod对象运行位置的依赖关系,因此,测试使用Pod亲和性约束,需要事先存在被依赖的Pod对象,它们具有特别的识别标签。例如创建一个有着标签app=tomcatDeployment资源部署一个Pod对象:

kubectl run tomcat -l app=tomcat --image tomcat:alpine

再通过资源清单定义一个Pod对象,它通过labelSelector定义的标签选择器挑选感兴趣的现存Pod对象, 而后根据挑选出的Pod对象所在节点的标签kubernetes. io/hostname来判断同一位置的具体含义,并将当前Pod对象调度至这一位置的某节点之上:

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity-1
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - {key: app, operator: In, values: ["tomcat"]}
        topologyKey: kubernetes.io/hostname
  containers:
  - name: nginx
    image: nginx

事实上,kubernetes.io/hostname标签是Kubernetes集群节点的内建标签,它的值为当前节点的节点主机名称标识,对于各个节点来说,各有不同。因此,新建的Pod象将被部署至被依赖的Pod对象的同一节点上,requiredDuringSchedulingIgnoredDuringExecution表示这种亲和性为强制约束。

基于单一节点的Pod亲和性只在极个别的情况下才有可能会用到,较为常用的通常是基于同一地区 region、区域zone或机架rack的拓扑位置约束。例如部署应用程序服务myapp与数据库db服务相关的Pod时,db Pod可能会部署于如上图所示的foobar这两个区域中的某节点之上,依赖于数据服务的myapp Pod对象可部署于db Pod所在区域内的节点上。当然,如果db Pod在两个区域foobar中各有副本运行,那么myapp Pod将可以运行于这两个区域的任何节点之上。

依赖于亲和于这两个Pod的其他Pod对象可运行于zone标签值为foobar的区域内的所有节点之上。资源配置清单如下

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-with-pod-affinity
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      name: myapp
      labels:
        app: myapp
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - {key: app, operator: In, values: ["db"]}
            topologyKey: zone
      containers:
      - name: nginx
        image: nginx

在调度示例中的Deployment控制器创建的Pod资源时,调度器首先会基于标签选择器 查询拥有标签app=db的所有Pod资源,接着获取到它们分别所属 的节点的zone标签值,接下来再查询拥有匹配这些标签值的所有节点,从而完成节点预选。而后根据优选函数计算这些节点的优先级,从而挑选出运行新建Pod对象的节点。

需要注意的是,如果节点上的标签在运行时发生了更改,以致它不再满足Pod上的亲和性规则,但该Pod还将继续在该节点上运行,因此它仅会影响新建的Pod资源;另外,labelSelector属性仅匹配与被调度器的Pod在同一名称空间中的Pod资源,不过也可以通过为其添加 namespace字段以指定其他名称空间 。

2.3、Pod软亲和

类似于节点亲和性机制,Pod也支持使用preferredDuringSchedulinglgnoredDuringExecution属性定义柔性亲和机制,调度器会尽力确保满足亲和约束的调度逻辑,然而在约束条 件不能得到满足时,它也允许将Pod对象调度至其他节点运行。下面是一个使用了Pod软亲和性调度机制的资源配置清单示例

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-with-preferred-pod-affinity
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      name: myapp
      labels:
        app: myapp
    spec:
      affinity:
        podAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 80
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - {key: app, operator: In, values: ["cache"]}
              topologyKey: zone
          - weight: 20
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - {key: app, operator: In, values: ["db"]}
              topologyKey: zone
      containers:
      - name: nginx
        image: nginx

它定义了两组亲和性判定机制,一个是选择cache Pod所在节点的zone标签,并赋予了较高的权重80,另一个是选择db Pod所在节点的 zone标签,它有着略低的权重20。于是,调度器会将目标节点分为四类 :cache Poddb Pod同时所属的zonecache Pod单独所属的zonedb Pod单独所属的zone,以及其他所有的zone

2.4、Pod反亲和

podAffinity用于定义Pod对象的亲和约束,对应地,将其替换为podAntiAffinty即可用于定义Pod对象的反亲和约束。不过,反亲和性调度一般用于分散同一类应用的Pod对象等,也包括将不同安全级别的Pod对象调度至不同的区域、机架或节点等。下面的资源配置清单中定义了由同一Deployment创建但彼此基于节点位置互斥的Pod对象:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-with-pod-anti-affinity
spec:
  replicas: 4
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      name: myapp
      labels:
        app: myapp
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - {key: app, operator: In, values: ["myapp"]}
            topologyKey: kubernetes.io/hostname
      containers:
      - name: nginx
        image: nginx

由于定义的强制性反亲和约束,因此,创建的4Pod副本必须运行于不同的节点中。不过,如果集群中一共只存在3个节点,因此,必然地会有一个Pod对象处于Pending状态。

类似地,Pod反亲和性调度也支持使用柔性约束机制,在调度时,它将尽量满足不把位置相斥的Pod对象调度于同一位置,但是,当约束关系无法得到满足时,也可以违反约束而调度。可参考podAffinity的柔性约束示例将上面的Deployment资源myapp-with-pod-anti-affinity修改为柔性约束并进行调度测试。

文章参考来源:《kubernetes进阶实战》