K8s系列之:深入理解Pod调度

K8s系列之:深入理解Pod调度

  • 一、Deployment/RC:全自动调度
  • 二、NodeSelector:定向调度
  • 三、NodeAffinity:Node亲和性调度
  • 四、PodAffinity:Pod亲和与互斥调度策略
  • 五、Taints和Tolerations(污点和容忍)

在K8s系统中,Pod在大部分场景下都只是容器的载体而已,通常需要通过Deployment、DaemonSet、RC、Job等对象来完成一组Pod的调度与自动控制功能。

一、Deployment/RC:全自动调度

Deployment或RC的主要功能之一就是自动部署一个容器应用的多份副本,以及持续监控副本的数量,在集群内始终维持用户指定的副本数量。

下面是一个Deployment配置的例子,使用这个配置文件可以创建一个ReplicaSet,这个ReplicaSet会创建3个Nginx应用的Pod。

nginx-deployment.yaml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

运行kubectl create命令创建这个Deployment:

kubectl create -f nginx-deployment.yaml
deployment  "nginx-deployment" created

查看Deployment的状态

kubectl get deployments
NAME            DESIRED   CURRENT     UP-TO-DATE  AVAILABLE   AGE
nginx-deployment   3         3            3          3         18s   

该状态说明Deployment已创建好所有3个副本,并且所有副本都是最新的而且是可用的。

运行kubectl get rs和kubectl get pods可以查看已创建的ReplicaSet(RS)和Pod的信息

kubectl get rs
NAME                           DESIRED   CURRENT  READY  AGE
nginx-deployment-4087004473      3          3       3     53s
kubectl get pods
NAME           READY         STATUS         RESTARTS       AGE
nginx-deployment-4087004473-9jqqs  1/1     Running  0     1m
nginx-deployment-4087004473-cq0cf  1/1     Running  0     1m
nginx-deployment-4087004473-vxn56  1/1     Running  0     1m

从调度策略上来说,这3个Nginx Pod由系统全自动完成调度。各自最终运行在哪个节点上,完全由Master的Scheduler经过一系列算法计算得出,用户无法干预调度过程和结果。

  • 除了使用系统自动调度算法完成一组Pod的部署,K8s提供了多种丰富的调度策略。
  • 用户只需在Pod的定义中使用NodeSelector、NodeAffinity、PodAffinity、Pod驱逐等更加细粒度的调度策略设置,就能完成对Pod的精准调度。

二、NodeSelector:定向调度

K8s Master上的Scheduler服务(kube-scheduler进程)负责实现Pod的调度,整个调度过程通过执行一系列复杂的算法,最终为每个Pod计算出一个最佳的目标节点,这一过程是自动完成的,通常无法知道Pod最终会被调度到哪个节点上。在实际情况中,也可能需要将Pod调度到指定的一些Node上,可以通过Node的标签(Label)和Pod的nodeSelector属性相匹配,来达到上述目的。

1.首先通过kubectl label命令给目标Node打上一些标签:

kubectl label nodes <node-name> <label-key>=<label-value>

为k8s-node-1节点打上一个zone=north的标签,表明它是北方的一个节点:

kubectl label nodes k8s-node-1 zone=north
NAME             LABELS                                      STATUS
k8s-node-1     kubernetes.io/hostname=k8s-node-1,zone=north   Ready

上述命令操作也可以通过修改资源定义文件的方式,并执行kubectl replace -f xxx.yaml命令来完成。

2.在Pod的定义中加上nodeSelector的设置,以redis-master-controller.yaml为例:

apiVersion: v1
kind: ReplicationController
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  replicas: 1
  selector:
    name: redis-master
  template:
    metadata:
      labels:
        name: redis-master
    spec:
      containers:
      - name: master
        image: kubeguide/redis-master
        ports:
        - containerPort: 6379
      nodeSelector:
        zone: north

运行kubectl create -f命令创建Pod,scheduler就会将该Pod调度到拥有zone=north标签的Node上。
使用kubectl get pods -o wide命令可以验证Pod所在的Node:

kubectl get pods -o wide
NAME                 READY    STATUS   RESTARTS   AGE     NODE
redis-master-f0rqj    1/1    Running       0      19s    k8s-node-1

如果给多个Node都定义了相同的标签(例如zone=north),则scheduler将会根据调度算法从这组Node中挑选一个可用的Node进行Pod调度。

通过基于Node标签的调度方式,可以把集群中具有不同特点的Node贴上不同的标签。例如"role=frontend",“role=backend”,"role=database"等标签,在部署应用时就可以根据应用的需求设置NodeSelector来进行指定Node范围的调度。

需要注意的是:指定了Pod的nodeSelector条件,且集群中不存在包含相应标签的Node,则即使集群中还有其他可供使用的Node,这个Pod也无法被成功调度。

除了用户可以自行给Node添加标签,K8s也会给Node预定义一些标签,包括:

  • kubernetes.io/hostname
  • failure-domain.beta.kubernetes.io/zone
  • failure-domain.beta.kubernetes.io/region
  • beta.kubernetes.io/instance-type
  • beta.kubernetes.io/os
  • beta.kubernetes.io/arch
    用户可以使用这些系统标签进行Pod的定向调度。

NodeSelector通过标签的方式,简单地实现了限制Pod所在节点的方法。亲和性调度则极大地扩展了Pod的调度能力,主要的增强功能如下:

  • 更具表达力
  • 可以使用软限制、优先采用等限制方式,代替之前的硬限制,这样调度器在无法满足优先需求的情况下,会退而求其次,继续运行该Pod。
  • 可以根据节点上正在运行的其他Pod的标签来进行限制,而非节点本身的标签。这就可以定义一种规则来描述Pod之间的亲和或互斥关系。

亲和性调度功能包括节点亲和性(NodeAffinity)和Pod亲和性(PodAffinity)两个维度的设置。Pod的亲和与互斥限制则通过Pod标签而不是节点标签来实现。

三、NodeAffinity:Node亲和性调度

两种节点亲和性表达:

  • RequiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod到Node上(功能与nodeSelector很像,但是使用的是不同的语法),相当于硬限制。
  • PreferredDuringSchedulingIgnoredDuringExecution:强调优先满足指定规则,调度器会尝试度Pod到Node上,但并不强求,相当于软限制。多个优先级规则还可以设置权重值,以定义执行的先后顺序。

IgnoredDuringExecution的意思是:如果一个Pod所在的节点在Pod运行期间标签发生了变更,不再符合该Pod的节点亲和性需求,则系统将忽略Node上Label的变化,该Pod能继续在该节点运行。

下面的例子设置了NodeAffinity调度的如下规则:

  • requiredDuringSchedulingIgnoredDuringExecution:要求只运行在amd64的节点上(beta.kubernetes.io/arch In amd64)
  • preferredDuringSchedulingIgnoredDuringExecution:要求是尽量运行在磁盘类型为ssd(disk-type In ssd)的节点上。
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: beta.kubernetes.io/arch
            operator: In
            values:
            - amd64
	  preferredDuringSchedulingIgnoredDuringExecution:
	  - weight: 1
	    preference:
	      matchExpressions:
	      - key: disk-type
	        operator: In
	        values:
	        - ssd
  containers:
  - name: with-node-affinity
    image: gcr.io/google_containers/pause:2.0

从上面的配置中可以看到In操作符,NodeAffinity语法支持的操作符包括In、NotIn、Exists、DoesNotExist、Gt、Lt,用NotIn和DoesNotExist就可以实现排斥的功能了。

NodeAffinity规则设置的注意事项如下:

  • 如果同时定义了nodeSelector和NodeAffinity,必须两个条件都得到满足,Pod才能最终运行在指定的Node上。
  • 如果nodeAffinity指定了多个nodeSelectorTerms,只需要其中一个能够匹配即可。
  • 如果nodeSelectorTerms中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该Pod。

四、PodAffinity:Pod亲和与互斥调度策略

限制Pod所能运行的节点:

  • 根据节点上正在运行的Pod的标签而不是节点的标签进行判断和调度,要求对节点和Pod两个条件进行匹配。

Pod亲和与互斥的条件设置也是requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution。Pod亲和性定义于PodSpec的affinity字段的podAffinity子字段里。Pod间的互斥性则定义于同一层次的podAntiAffinity子字段中。

下面通过实例来说明Pod间的亲和性和互斥性策略设置。

1.参照目标Pod
创建一个名为pod-flag的Pod,带有标签security=S1和app=nginx,后面的例子将使用pod-flag作为Pod亲和与互斥的目标Pod。

apiVersion: v1
kind: Pod
metadata:
  name: pod-flag
  labels:
    security: "S1"
    app: "nginx"
spec:
  containers:
  - name: nginx
    image: nginx

2.Pod的亲和性调度
下面创建第2个Pod来说明Pod的亲和性调度,定义的亲和标签是security=S1,对应上面的Pod“pod-flag”,topologyKey的值被设置为"kubernetes.io/hostname"。

apiVersion: v1
kind: Pod
metadata:
  name: pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
        matchExpressions:
        - key: security
          operator: In
          values:
          - S1
        topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pode-affinity
    image: gcr.io/google_containers/pause:2.0

创建Pod之后,使用kubectl get pods -o wide命令可以看到,这两个Pod处于同一个Node之上运行。

3.Pod的互斥性调度
创建第三个Pod,希望不与参照目标Pod运行在同一个Node上。

apiVersion: v1
kind: Pod
metadata:
  name: anty-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
        matchExpressions:
        - key: security
          operator: In
          values:
          - S1
        topologyKey: failure-domain.beta.kubernetes.io/zone
	podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
        matchExpressions:
        - key: app
          operator: In
          values:
          - nginx
        topologyKey: kubernetes.io/hostname
  containers:
 - name: anti-affinity
    image: gcr.io/google_containers/pause:2.0
  • 要求新Pod与security=S1的Pod为同一个zone,但是不与app=nginx的Pod为同一个Node。

创建Pod后,同样用kubectl get pods -o wide来查看,会看到新的Pod被调度到了同一个zone内的不同的Node上去。

与节点亲和性类似,Pod亲和性的操作符包括In、NotIn、Exists、DoesNotExists、Gt、Lt。

原则上,topologyKey可以使用任何合法的标签Key赋值,但是出于性能和安全方面的考虑,对topologyKey有如下限制。

  • 在Pod亲和性和RequiredDuringScheduling的Pod互斥性的定义中,不允许使用空的topologyKey。
  • 如果Admission controller包含了LimitPodHardAntiAffinityTopology,针对Required DuringScheduling的Pod互斥性定义就被限制为kubernetes.io/hostname,要使用自定义的topologyKey,就要改写或禁用该控制器
  • 在preferredDuringScheduling类型的Pod互斥性的定义中,空的topologyKey会被解释为kubernetes.io/hostname、failure-domain.beta.kubernetes.io/zone及failure-domain.beta.kubernetes.io/region的组合。
  • 如果不是上述情况,就可以采用任意合法的topologyKey了

PodAffinity规则设置的注意事项如下:

  • 除了设置Label Selector和topologyKey,用户还可以指定namespace列表来进行限制,同样使用Label Selector对namespace进行选择。namespace的定义和Label Selector及topologyKey同级。省略namespace的设置,表示使用了affinity/anti-affinity的Pod所在的namespace。如果namespace设置为空值(""),则表示所有namespace。
  • 在所有关联requiredDuringSchedulingIgnoredDuringExecution的matchExpressions全都满足之后,系统才能将Pod调度到某个Node上。

五、Taints和Tolerations(污点和容忍)

  • NodeAffinity节点亲和性,是在Pod上定义的一种属性,使得Pod能够被调度到某些Node上运行(优先选择或强制要求)。
  • Taint让Node拒绝Pod的运行。在Node上设置一个或多个Taint之后,除非Pod明确声明能够容忍这些污点,否则无法在这些Node上运行。
  • Taint需要和Toleration配合使用,Toleration是Pod的属性,让Pod能够运行在标注了Taint的Node上。

可以用kubectl taint命令为Node设置Taint信息:

  • kubectl taint nodes node1 key=value:NoSchedule
  • 这个设置为node1加上了一个Taint。该Taint的键为key,值为value,Taint的效果是NoSchedule。
  • 意味着除非Pod明确声明可以容忍这个Taint,否则就不会被调度到node1上去。
  • 需要在Pod上声明Toleration。

下面的两个Toleration都设置为可以容忍(Toleration)具有该Taint的Node,使得Pod能够被调度到node1上:

tolerations:
- key: "key"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"

tolerations:
 - key: "key"
  operator: "Exists"
  effect: "NoSchedule"

Pod的Toleration声明中的key和effect需要与Taint的设置保持一致,并且满足以下条件之一。

  • operator的值是Exists(无须指定value)
  • operator的值是Equal并且value相等
  • 如果不指定operator,则默认值为Equal

还有两个特例:

  • 空的key配合Exists操作符能够匹配所有的键和值。
  • 空的effect匹配所有的effect。

上面的例子中,effect的取值为NoSchedule,还可以取值为PreferNoSchedule,这个值的意思是优先,也可以算作NoSchedule的软限制版本,一个Pod如果没有声明容忍这个Taint,则系统会尽量避免把这个Pod调度到这一节点上去,但不是强制的。

系统允许在同一个Node上设置多个Taint,也可以在Pod上设置多个Toleration。K8s调度器处理多个Taint和Toleration的逻辑顺序为:首先列出节点中所有的Taint,然后忽略Pod的Toleration能够匹配的部分,剩下的没有忽略掉的Taint就是对Pod的效果了。

下面是几种特殊情况:

  • 如果剩余的Taint中存在effect=NoSchedule,则调度器不会把该Pod调度到这一节点上。
  • 如果剩余Taint中没有NoSchedule效果,但是有PreferNoSchedule效果,则调度器会尝试把这个Pod指派给这个节点。
  • 如果剩余Taint的效果有NoExecute的,并且这个Pod已经在该节点上运行,则会被驱逐。如果没有在该节点上运行,也不会再被调度到该节点上。

例子:
对一个节点进行Taint设置:

kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 key2=value2:NoSchedule

然后在Pod上设置两个Toleration:

tolerations:
 - key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
  key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
  • 结果是该Pod无法被调度到node1上去,因为第3个Taint没有匹配的Toleration。如果该Pod已经在node1上运行了,在运行时设置第3个Taint,还能继续在node1上运行,因为Pod可以容忍前两个Taint。
  • 给Node加上effect=NoExecute的Taint,那么该Node上正在运行的所有无对应Toleration的Pod都会被立刻驱逐哦,而具有相应Toleration的Pod则永远不会被逐出。
  • 系统允许给具有NoExecute效果的Toleration加入一个可选的tolerationSeconds字段,这个设置表明Pod可以在Taint添加到Node之后还能在这个Node上运行多久(单位为s)
tolerations:
 - key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
  tolerationSeconds: 3600
  • Pod正在运行,所在节点加入一个匹配的Taint,则这个Pod会持续在这个节点上存活3600s后被逐出
  • 在宽限期内,Taint被移除,则不会触发驱逐事件

Taint和Toleration是一种处理节点并且让Pod进行规避或者驱逐Pod的弹性方式。下面列举一些常见的用例。

1.独占节点
想要拿出一部分节点,专门给一些特定应用使用,则可以为节点添加这样的Taint:

kubectl taint nodes nodename dedicated=groupName:NoSchedule

给这些应用的Pod加入对应的Toleration。带有合适Toleration的Pod就会被允许同使用其他节点一样使用Taint的节点。

2.具有特殊硬件设备的节点
在集群里可能有一小部分节点安装了特殊的硬件设备(如CPU芯片),用户自然会希望把不需要占用这类硬件的Pod排除在外,以确保对这类硬件有需求的Pod能够顺利调度到这些节点上。

可以用下面的命令为节点设置Taint:

kubectl taint nodes nodename special=true:NoSchedule
kubectl taint nodes nodename special=true:PreferNoSchedule

在Pod中利用对应的Toleration来保障特定的Pod能够使用特定的硬件。

3.定义Pod驱逐行为,以应对节点故障
NoExecute这个Taint效果对节点上正在运行的Pod有以下影响:

  • 没有设置Toleration的Pod会被立刻驱逐
  • 配置了对应Toleration的Pod,如果没有为tolerationSeconds赋值,则会一直留在这个节点中
  • 配置了对应Toleration的Pod且指定了tolerationSeconds值,则会在指定时间后驱逐。

把节点故障标记为Taint,在节点故障情况下,系统将会以限速的模式逐步给Node设置Taint,防止在一些特定情况下造成大量Pod被驱逐,允许Pod定义节点故障时持续多久才被逐出。

例如一个包含很多本地状态的应用可能需要在网络发生故障时,还能持续在节点上运行,期望网络能够快速恢复,从而避免这个Node上被驱逐。

Pod的Toleration可以这样定义:

tolerations:
 - key: "node.alpha.kubernetes.io/unreachable"
  operator: "Exists"
  affect: "NoExecute"
  tolerationSeconds: 6000

对于Node为就绪状态,可以把Key设置为node.alpha.kubernetes.io/notReady。

没有为Pod指定node.alpha.kubernetes.io/notReady的Toleration,Kubernetes会自动为Pod加入tolerationSeconds=300的node.alpha.kubernetes.io/notReady类型的Toleration。
Pod没有定义node.alpha.kubernetes.io/unreachable的Toleration,系统会自动为其加入tolerationSeconds=300的node.alpha.kubernetes.io/unreachable类型的Toleration。

这些系统自动设置的toleration用于在Node发现问题时,能够为Pod确保驱逐前再运行5min,这两个默认的Toleration由Admission Controller “DefaultTolerationSeconds”自动加入。

你可能感兴趣的:(日常分享专栏,K8s系列,深入理解Pod调度)