k8s 读书笔记 - 详解 Pod 调度(Ⅰ卷)

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了k8s 读书笔记 - 详解 Pod 调度(Ⅰ卷)相关的知识,希望对你有一定的参考价值。

上一篇 《深入掌握 Pod》 文章我们介绍了 Pod 的知识点,接下来我们来继续学习 Pod  在 k8s 中的调度原理。在 k8s 平台上,通常情况下很少直接创建一个 Pod,大多情况下都是通过 Pod 的资源管理对象 来创建,例如:RC/RS、Deployment、DaemonSet、Job & CornJob 等控制器完成对一组 Pod 创建、调度及全生命周期的自动控制管理等任务。

Pod调度

k8s 调度器(kube-scheduler)

kube-scheduler 是 Kubernetes 集群的默认调度器,并且是 集群控制面 的一部分。 如果你真的希望或者有这方面的需求,kube-scheduler 在设计上允许你自己编写一个调度组件并替换原有的 kube-scheduler

对每一个新创建的 Pod 或者是未被调度的 Pod,kube-scheduler 会选择一个最优的节点去运行这个 Pod。 然而,Pod 内的每一个容器对资源都有不同的需求, 而且 Pod 本身也有不同的需求。因此,Pod 在被调度到节点上之前, 根据这些特定的调度需求,需要对集群中的节点进行一次 过滤

在一个集群中,满足一个 Pod 调度请求的所有节点称之为 可调度节点。 如果没有任何一个节点能满足 Pod 的资源请求, 那么这个 Pod 将一直停留在未调度状态直到调度器能够找到合适的 Node。

调度器先在集群中找到一个 Pod 的所有 可调度节点,然后根据一系列函数对这些可调度节点 打分, 选出其中得分最高的节点来运行 Pod。之后,调度器将这个调度决定通知给 kube-apiserver,这个过程叫做 绑定

在做调度决定时需要考虑的因素包括:单独和整体的资源请求、硬件/软件/策略限制、 亲和以及反亲和要求、数据局部性、负载间的干扰等等

了解【集群控制面】更多信息,请查看:https://kubernetes.io/zh-cn/docs/reference/glossary/?all=true#term-control-plane

kube-scheduler 调度流程

kube-scheduler 给一个 Pod 做调度选择时包含两个步骤:过滤打分,如下流程所示:

kube-scheduler 调度流程

过滤阶段】会将所有满足 Pod 调度需求的节点选出来。 例如,PodFitsResources 过滤函数会检查候选节点的可用资源能否满足 Pod 的资源请求。 在过滤之后,得出一个节点列表,里面包含了所有可调度节点;通常情况下, 这个节点列表包含不止一个节点。如果这个列表是空的,代表这个 Pod 不可调度。

打分阶段】调度器会为 Pod 从所有可调度节点中选取一个最合适的节点。 根据当前启用的打分规则,调度器会给每一个可调度节点进行打分。

最后,kube-scheduler 会将 Pod 调度到得分最高的节点上。 如果存在多个得分最高的节点,kube-scheduler 会从中随机选取一个。

支持以下两种方式配置 调度器的过滤和打分 行为:

  1. 调度策略】 允许你配置过滤所用的 断言(Predicates) 和打分所用的 优先级(Priorities)

  2. 调度配置】允许你配置实现不同调度阶段的插件, 包括:QueueSort、Filter、Score、Bind、Reserve、Permit 等等。 你也可以配置 kube-scheduler 运行不同的配置文件。

了解 kube-scheduler 更多信息,请查看:https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/kube-scheduler/

Deployment & ReplicaSet,全自动调度

在早期的 k8s 版本中是没有这么多 Pod 副本控制器的,只有一个 Pod 副本控制器 RC(Replication Controller),RC 的设计实现:RC 独立于所控制的 Pod,并通过 Label 标签松耦合关联关系控制目标 Pod 实例的创建和销毁。在 k8s 的新版本中,RC 已经被 ReplicaSet(RS) 替换,RS 增强了 RC 的功能,RC 的标签选择器只能选择一个标签,而 RS 不但能选择标签,还拥有集合式的标签选择器,可以选择多个 Pod 标签,如下所示:

selector:
    matchLabels:
      tier: frontend
    matchExpressions: 
      - key: tier, operator: In, values: [frontend]

RS 应用场景举例

例如 frontend 应用发布了 v1v2 两个版本,此时你希望 frontend 的 Pod 副本数保持为 3 个,并且可以同时包含 v1v2 版本的 Pod,这种情况就可以使用 RS 来实现控制,写法如下:

selector:
    matchLabels:
      version: v2
    matchExpressions: 
      - key: version, operator: In, values: [v1,v2]

从这个例子中可以看出,在 k8s 中的 滚动更新 就是巧妙利用 ReplicaSet(RS) 特性来实现的。

Deployment 默认调度

前面文章介绍了 Deployment 资源对象,这里我们简单的回顾下 Deployment 对象的调用链:

kubectl(client) => Deployment => ReplicaSet => Pod => Container

在大多数情况下,通常都不会直接创建 Pod,而是使用 Deployment 对象来创建 Pod ,而 Deployment 对象底层也是通过 ReplicaSet 来控制 Pod 副本数量的,这也是 官方推荐的方式,而不是直接使用底层的 ReplicaSet 来创建 Pod。

上面举例的 Deployment 对象的 yaml 定义完整文件 nginx-deployment.yaml 如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3    # Pod 实例副本数
  selector:
    matchLabels:
      version: v2
    matchExpressions: 
      - key: version, operator: In, values: [v1,v2]
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

运行命令创建 Deployment 对象:

kubectl create -f nginx-deployment.yaml

查看 Deployment 的状态:

kubectl get deployments

查看创建的 ReplicaSet(RS)对象:

kubectl get rs

查看创建的 Pod 对象的详细信息:

kubectl get pods -o wide
# 等效命令
kubectl get pods --output=wide

k8s 中 Master 上的 Schedule 服务(kube-scheduler 进程)负责实现 Pod 的调度。整个调度过程通过执行一系列复杂的算法,最终为每个 Pod 都计算出一个最佳的目标节点,这一过程是自动完成的。

接下来我们详细的介绍 Pod 的几种调度模式,每种模式都应用于特定的场景。

NodeSelector,节点定向调度

nodeSelector 是节点选择约束的最简单推荐形式。你可以将 nodeSelector 字段添加到 Pod 的规约中设置你希望的目标节点所具有的节点标签。 **k8s 只会将 Pod 调度到拥有指定的标签的节点上**。

举例,将 Pod 调度到拥有 Lable 标签的 disktype: ssd 节点上,该 Pod 的 yaml 文件定义如下:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd

Pod 亲和性调度

亲和性调度功能包括 节点亲和性(NodeAffinity)Pod 亲和性(PodAffinity) 两个维度的设置。

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

  • 亲和性、反亲和性语言的表达能力更强。nodeSelector 只能选择拥有所有指定标签的节点。 亲和性、反亲和性为你提供对选择逻辑的更强控制能力。

  • 可以使用 “软限制” 、优先采用等限制方式,代替之前的 “硬限制”,这样调度器在无法满足优先需求的情况下,会退而求其次,继续运行该 Pod。

  • 可以根据节点上正在运行的其他 Pod 的标签来进行限制,而非节点本身的标签。这样就可以定义一种规则来描述 Pod 之间的亲和性和互斥关系。

NodeAffinityNodeSelector 类似,增强了上面前两点优势。Pod 的 PodAffinity(亲和性) 和 PodAntiAffinity(互斥性)限制则是通过 Pod 标签来实现的,而不是 Node 节点标签,Pod 亲和性和互斥性调度都具备上述提到的优点

亲和性功能由两种类型的亲和性组成:

  • NodeAffinit 功能类似于 NodeSelector 字段,但它的表达能力更强,并且允许你指定软规则。

  • Pod 间亲和性/反亲和性 允许你根据其他 Pod 的标签来约束 Pod。

NodeAffinity,节点亲和性调度

节点亲和性概念上类似于 nodeSelector, 它使你可以 根据节点上的 Label 标签来约束 Pod 可以调度到哪些节点上。 节点亲和性有两种:

  • requiredDuringSchedulingIgnoredDuringExecution:(硬性规则)调度器只有在规则被满足的时候才能执行调度。此功能类似于 nodeSelector, 但其语法表达能力更强。

  • preferredDuringSchedulingIgnoredDuringExecution:(软性规则)调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该 Pod。对软性规则可设置权重 weight,其取值范围 1- 100 之间。

使用 Pod 规约中的 .spec.affinity.nodeAffinity 字段来设置 NodeAffinity(节点亲和性)。举例: with-node-affinity.yaml 文件定义如下:

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    # 节点亲和性
    nodeAffinity:
      # 硬性规则,必须满足条件
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: topology.kubernetes.io/zone  # 地区
            operator: In
            values:
            - antarctica-east1  # 南极洲东部1
            - antarctica-west1  # 南极洲西部1
          - key: topology.kubernetes.io/arch  # 架构
            operator: In  # 设置逻辑操作符
            values:
            - amd64
            - arm64
      # 软性规则,尝试满足,优先满足,不是必须条件
      preferredDuringSchedulingIgnoredDuringExecution:
      # 权重,优先级
      - weight: 1
        preference:
          matchExpressions:
          - key: disk-type  # another-node-label-key,匹配另一个节点标签的 key
            operator: In
            values:
            - ssd   # another-node-label-value,匹配另一个节点标签的 value
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:3.1

从上面的 yaml 定义中可以看到 In 操作符,我们来回顾下 NodeAffinity 语法支持的操作符包括:In、NotIn、Exists、DoesNotExists、Gt、Lt。虽然没有节点互斥功能,但可以用 NotIn 和 DoesNotExists 来实现节点互斥功能

说明:上面的 NodeAffinity 示例中,定义了两个硬性规则条件,分别是 地区(zone)架构(arch ),另外定义了一个软性规则条件,那就是 硬盘类型(disk-type)k8s 的默认调度器 Schedule 服务(kube-scheduler 进程) 则遵循定义规则实现该 Pod 的调度,整个过程全部自动完成,无需人工干预。

逐个调度方案中设置节点亲和性

【特性状态】: Kubernetes v1.20 [beta]

在配置多个调度方案时, 如果某个调度方案仅适用于某组特殊的节点时,可以将该方案与节点亲和性关联起来, 这样做是很有用的。要实现这点,可以在 调度器配置 中为 NodeAffinity 插件的 args 字段添加 addedAffinity

示例:

apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
...
profiles:
  - schedulerName: default-scheduler
  - schedulerName: foo-scheduler
    pluginConfig:
      - name: NodeAffinity
        args:
          addedAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
              - matchExpressions:
                - key: scheduler-profile
                  operator: In
                  values:
                  - foo

这里的 addedAffinity 除遵从 Pod 规约 space 中设置的 NodeAffinity(节点亲和性) 之外, 还适用于将 .spec.schedulerName 设置为 foo-scheduler。 换言之,为了匹配 Pod,node 节点需要满足 addedAffinity 和 Pod 的 .spec.NodeAffinity

注意:由于 addedAffinity 对最终用户不可见,其行为可能对用户而言是出乎意料的。 应该使用与调度方案名称有明确关联的节点标签。

说明:DaemonSet 控制器为 DaemonSet 创建 Pods, 但该控制器不理会调度方案。 DaemonSet 控制器创建 Pod 时,默认的 Kubernetes 调度器(kube-scheduler)负责放置 Pod, 并遵从 DaemonSet 控制器中奢侈的 nodeAffinity 规则

NodeAffinity 规则设置说明

  • 如果同时定义了  nodeSelectorNodeAffinity ,这两个条件必须满足(and 关系),Pod 才能最终运行在指定的 Node 上。

  • 如果 NodeAffinity 指定了多个 nodeSelectorTerms ,其中一个条件能够匹配成功即可(or 关系)。

  • 如果在  nodeSelectorTerms  中有多个 matchExpressions ,则目标 Node 必须满足所有 matchExpressions 才能运行该 Pod(and 关系)。

节点亲和性权重(weight)

可以为 preferredDuringSchedulingIgnoredDuringExecution 亲和性类型的每个实例设置 weight 字段,其 取值范围是 1 到 100。 当调度器找到能够满足 Pod 的其他调度请求的 node 节点时,调度器会遍历 node 满足的所有的偏好性规则, 并将对应表达式的 weight 值加和。

最终的加和值会添加到该节点的其他优先级函数的评分之上。 在调度器为 Pod 作出调度决定时,总分最高的节点的优先级也最高。

Pod 规约的 with-affinity-anti-affinity.yaml 文件定义示例:

apiVersion: v1
kind: Pod
metadata:
  name: with-affinity-anti-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os
            operator: In
            values:
            - linux
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: label-1
            operator: In
            values:
            - key-1
      - weight: 50
        preference:
          matchExpressions:
          - key: label-2
            operator: In
            values:
            - key-2
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:3.1

如果存在两个候选节点,都满足 preferredDuringSchedulingIgnoredDuringExecution 规则, 其中一个节点具有标签 label-1:key-1,另一个节点具有标签 label-2:key-2, 调度器会考察各个节点的 weight 取值,并将该权重值添加到节点的其他得分值之上。

说明:如果你希望 k8s 能够成功地调度此例中的 Pod,你必须拥有打了 kubernetes.io/os=linux 标签的节点。

Pod 间亲和性和互斥性调度(PodAffinity & PodAntiAffinity)

【特性状态】: Kubernetes v1.4 引入

Pod 间亲和性(PodAffinity)与反亲和性(也叫互斥性,PodAntiAffinity)使你可以 基于已经在节点上运行的 Pod 的标签来约束 Pod 可以调度到的节点,而不是基于节点上的标签。

PodAffinityPodAntiAffinity规则格式“如果 **X** 上已经运行了一个或多个满足规则 **Y** 的 Pod, 则这个 Pod 应该(或者在反亲和性的情况下不应该)运行在 **X** 上”。 这里的 **X** 可以是节点、机架、云提供商可用区或地理区域或类似的拓扑域, **Y** 则是 Kubernetes 尝试满足的规则

  • topology.kubernetes.io/hostname,节点

  • topology.kubernetes.io/zone,机架

  • topology.kubernetes.io/region,区域

通过 标签选择算符 的形式来表达规则 (Y),并可根据需要指定选择关联的 Namespace(名字空间) 列表。 Pod 在 k8s 中是 Namespace 作用域的对象,因此 Pod 的标签也隐式地具有 Namespace 属性。 针对 Pod 标签的所有标签选择算符都要指定 Namespace,k8s 会在指定的 Namespace 内寻找 Label 标签。

通过 topologyKey 来表达 拓扑域 (X) 的概念,其取值是系统用来表示域的 节点标签键。 相关示例可参见常用 标签、注解和污点

【标签、注解和污点】参考文档:,https://kubernetes.io/zh-cn/docs/reference/labels-annotations-taints/。

【说明】

  • Pod 间亲和性和反亲和性都需要相当的计算量,因此会在大规模集群中显著降低调度速度不建议在包含数百个节点的集群中使用这类设置。

  • Pod 反亲和性需要节点上存在一致性的标签(交集)。换言之, 集群中每个节点都必须拥有与 topologyKey 匹配的标签。 如果某些或者所有节点上不存在所指定的 topologyKey 标签,调度行为可能与预期的不同。

Pod 间亲和性与反亲和性类型

与节点亲和性(NodeAffinity)类似,Pod 间的 PodAffinity & PodAntiAffinity (亲和性与反亲和性)也有两种类型:

  • requiredDuringSchedulingIgnoredDuringExecution,硬性条件,必须满足。

  • preferredDuringSchedulingIgnoredDuringExecution,软性条件,尝试满足(优先级依据权重 weight)。

要使用 Pod 间亲和性(PodAffinity),可以使用 Pod 规约 spec 中的 spec.affinity.podAffinity 字段。 对于 Pod 间反亲和性(PodAntiAffinity ),可以使用 Pod 规约中的 spec.affinity.podAntiAffinity 字段。

Pod 间亲和性与反亲和性示例

with-pod-affinity.yaml 定义文件

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: topology.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: topology.kubernetes.io/zone
  containers:
  - name: with-pod-affinity
    image: k8s.gcr.io/pause:3.1

with-pod-affinity.yaml 文件定义说明:

  • 亲和性(podAffinity.requiredDuringSchedulingIgnoredDuringExecution)规则表示,仅当节点和至少一个已运行且有 security=S1 的标签的 Pod 处于同一区域时,才可以将该 Pod 调度到节点上。 更确切的说,调度器必须将 Pod 调度到具有 topology.kubernetes.io/zone=V 标签的节点上,并且集群中至少有一个位于该可用区的节点上运行着带有 security=S1 标签的 Pod。

  • 反亲和性(podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution)规则表示,如果节点处于 Pod 所在的同一可用区且至少一个 Pod 具有 security=S2 标签,则该 Pod 不应被调度到该节点上。 更确切地说, 如果同一可用区中存在其他运行着带有 security=S2 标签的 Pod 节点, 并且节点具有标签 topology.kubernetes.io/zone=R,Pod 不能被调度到该节点上。

Pod 间亲和性与反亲和性(PodAffinity & PodAntiAffinity)为其  operator(操作符) 字段使用  In、NotIn、Exists、 DoesNotExist 、Gt、Lt 值。

topologyKey 限制规则

原则上,topologyKey(拓扑逻辑) 可以是任何合法的 标签键出于性能和安全原因,topologyKey 有一些限制

  • 对于 Pod 亲和性而言,在 requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution 中,topologyKey 不允许为空。

  • 对于 requiredDuringSchedulingIgnoredDuringExecution 要求的 Pod 反亲和性, 如果 Admission controller(准入控制器)包含了 LimitPodHardAntiAffinityTopology ,针对 requiredDuringScheduling 的 Pod 互斥性要求 topologyKey 只能是 kubernetes.io/hostname。如果要使用其他定制或自定义的 topologyKey,需要更改或禁用该 Admission controller

  • 在  requiredDuringScheduling 类型的 Pod 互斥性定义中,空的  topologyKey  会被解释为 kubernetes.io/hostname、failure-domain.beta.kubernetes.io/zone 及 failure-domain.beta.kubernetes.io/region 的组合。

  • 除开上述情况,可以使用任意合法的   topologyKey  。

PodAffinity 规则设置说明

  • 除了 labelSelectortopologyKey,你也可以指定 labelSelector 要匹配的 Namespace(命名空间)列表,方法是在 labelSelector topologyKey 所在层同一层次上设置 namespaces。 如果 namespaces 被忽略或者为空(""),则默认为 Pod 亲和性/反亲和性的定义所在的 namespaces 命名空间。

  • 在所关联的  requiredDuringSchedulingIgnoredDuringExecution  的 matchExpressions 全部满足之后,系统才能将 Pod 调度到某个 Node 上。

关于【亲和性】更多信息,请查看:https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity

Namespaces 选择算符

【特性状态】: Kubernetes v1.24 [stable]

我们也可以使用 namespaceSelector 选择匹配的 namespacesnamespaceSelector 是对名字空间集合进行标签查询的机制。 亲和性条件会应用到 namespaceSelector 所选择的名字空间和 namespaces 字段中所列举的名字空间之上。 注意,空的 namespaceSelector() 会匹配所有名字空间,而 null 或者空namespaces 列表以及 null 值 namespaceSelector 意味着 “当前 Pod 的名字空间”

生产环境中更多的实际示例

Pod 间亲和性与反亲和性在与更高级别的集合(例如 ReplicaSet、StatefulSet、 Deployment 等)一起使用时,它们可能更加有用。 这些规则使得你可以配置一组工作负载,使其位于所定义的同一拓扑中; 例如优先将两个相关的 Pod 置于相同的节点上。

以一个三 node 的集群为例,使用 Pod 间的 PodAffinity & PodAntiAffinity 来尽可能地将该 Web 服务器与缓存服务器(例如:Redis)并置,部署规格如下列表:

node-1node-2node-3
webserver-1webserver-2webserver-3
cache-1cache-2cache-3

redis-cache_web-server.yaml 文件定义如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3  # 设置 Pod 实例副本数量
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        # Pod 反亲和性(互斥性)
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis:7.0.4-alpine
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        # Pod 反亲和性(互斥性)
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          # Pod 亲和性
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.23.1-alpine

创建上面的 Deployment 对象资源:

kubectl create -f redis-cache_web-server.yaml

查看创建的 Deployment 对象信息:

kubectl get deployment redis-cache, web-server

参考文档:

  • 将 Pod 指派给节点,https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node/

Taints & Tolerations,污点和容忍调度

上面介绍的 NodeAffinity (节点亲和性),是在 Pod 上定义的一种属性,使得 Pod 能够被调度到一类特定的节点 (这可能出于一种偏好或软性条件,也可能是硬性强制要求)。接下来我们介绍的 Taints(污点)则与之相反,它 让 Node 拒绝运行 Pod(排斥一类特定的 Pod)

同样 Toleration 也是 Pod 的属性容忍度(Toleration) 是应用于 Pod 上的。容忍度允许调度器调度带有对应  Taints(污点) 的 Pod。 容忍度允许调度但并不保证调度:作为其功能的一部分, 调度器也会评估其他参数。

Taint(污点)需要和 Toleration(容忍调度)配合使用,可以用来避免 Pod 被分配到不合适的 Node 上。在每个 node 上都可以设置一个或多个 Taint ,除非 Pod 明确声明能够 Toleration(容忍)这些 Taint ,否则无法在这些 Node 上运行该 Pod。

使用命令为 Node 设置 Taint 信息:

kubectl taint nodes k8s-node-01 key=value:NoSchedule

接下来在 Pod 上申明 Toleration 属性,如下示例:

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

或者

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

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

  • operator 的值是 Exists(无需指定 value)。

  • operator 的值是 Equal 需要设置 value 并且相等。

如果不指定 operator(操作符),默认值是 Equal 。另外,还有两个特例如下:

  • 空("")的 key 配合 Exists 操作符能够匹配所有的 key 和 value。

  • 空("")的 effect 匹配所有的 effect。

在上面的示例中,effect 设置调度优先级,其取值为:NoSchedule,PreferNoSchedule、NoExecute

PreferNoSchedule 可以看作是 NoSchedule 的软限制版,一个 Pod 如果没有声明容忍这个 Taint,则系统会尽量避免把这个 Pod 调度到对应 Taint 标记的 Node 上,但不是强制的。

k8s 系统允许在同一个 Node 上设置多个 Taint,也可以在 Pod 上设置多个 Toleration。

调度器处理污点和容忍的逻辑顺序

k8s 调度器(kube-scheduler) 处理多个 Taint(污点) 和 Toleration(容忍) 的逻辑顺序

  1. 首先列出 node 中的所有 Taint;

  2. 然后忽略 Pod 的 Toleration 能够匹配的部分;

  3. 剩余的没有忽略 Taint 的 node 就是对 Pod 的效果了。

在剩余的没有忽略 Taintnode 中,有以下几种特殊情况:

  • 如果在剩余的 Taint 中存在 effect=NoSchedule,则调度器不会把该 Pod 调度到这个 node 上。

  • 如果在剩余的 Taint 中没有 NoSchedule 效果,但是有 PreferNoSchedule 效果,则调度器会尝试不把该 Pod 指派给这个 node 上。

  • 如果在剩余的 Taint 中有 NoExecute 效果,并且这个 Pod 已经在该 Node 上运行,则会被 驱逐;如果没有在该 Node 上运行,则也不会再被调度到该 Node 上运行。

使用 Toleration 的 Pod 示例

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "example-key"
    operator: "Exists"
    effect: "NoSchedule"

Taint 和 Toleration 应用示例

举例,我们使用一个 node(名称为 k8s-node-01) 进行 Taint 设置:

kubectl taint nodes k8s-node-01 key1=value1:NoSchedule
kubectl taint nodes k8s-node-01 key1=value1:NoExecute
kubectl taint nodes k8s-node-01 key2=value2:NoSchedule

然后在 Pod 上设置两个 Toleration :

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

这样的 Toleration 的设置结果是 Pod 无法调度到 k8s-node-01 上,因为上面命令行中第三个 Taint 没有匹配的 Toleration。但是如果该 Pod 已经在  k8s-node-01 上运行了,那么在运行时设置第三个 Taint,该 Pod 还能继续在  k8s-node-01 上运行,因为该 Pod 可以容忍前两个 Taint

一般情况,如果给 Node 加上 effect=NoSchedule 的 Taint,那么在该 Node 上运行的所有无对应 Toleration 的 Pod 都会被立刻驱逐,而具有相应 Toleration 属性的 Pod 永远不会被驱逐。不过,k8s 系统允许给具有 NoExecute 效果的 Toleration 加入一个可选的 tolerationSeconds 字段,这个字段设置表明 Pod 可以在 Taint  添加到 Node 之后还能在这个 Node 上运行多久(单位:s)。如下示例:

tolerations: 
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
  tolerationSeconds: 3600

上面 yaml 定义的意思是,如果 Pod 正在运行,所在 node 都被加入一个匹配的 Taint,这个 Pod 会持续在该 node 上存活 3600s 后被驱逐出。如果在这个宽限期内 Taint 被移除,则不会触发驱逐事件。

Taint 和 Toleration 常见用例

通过上面的示例,可以得知 Taint 和 Toleration 是一种处理 node 并且让 Pod 进行规避或者驱逐 Pod 的弹性处理方式,接下来我们列举一些常见的用例。

1. 专用(或独占)node

如果想想要拿出一部分 node 专门给特定的应用使用,则可以为 node 添加这样的 Taint,如下所示:

kubectl taint nodes nodename dedicated=groupName:NoSchedule

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

通过自定义 Admission Controller 也可以实现这一目标,如果希望让这些应用独占一批 node,并且确保它们只能使用这些 node,则还可以给这些 Taint 节点加人类似的标签 dedicated=groupName,然后 Admission Controller 需要加入 NodeAffinity(节点亲和性) 设置,要求 Pod 只会被调度到具有这一标签的 node 上。

2. 具有特殊硬件设备的 node

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

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

kubectl taint nodes nodename special=true:Roschedule
kubectl taint nodes nodename special=true:PrefezNoschedule

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

和上面的 独占 node 的示例类似,使用 Admission Controller 来完成这一任务会更方便。例如,Admission Controller 使用 Pod 的一些特征来判断这些 Pod,如果可以使用这些硬件,就添加 Toleration 来完成这一工作。要保障需要使用特殊硬件的 Pod 只被调度到安装这些硬件的节点上,则还需要一些额外的工作,比如:将这些特殊资源使用 opaque-int-resource 的方式对自定义资源进行量化,然后在 PodSpec 中进行请求;也可以使用标签的方式来标注这些安装有特别硬件的节点,然后在 Pod 中定义 NodeAffinity(节点亲和性)来实现这个目标。

3. 基于 Taint(污点)的驱逐

【特性状态】: Kubernetes v1.18 [stable]

定义 Pod 驱逐行为,以应对 node 故障,这是在每个 Pod 中配置的在 node 出现问题时的驱逐行为。前面提到的 NoExecute 这个 Taint 效果对 node 上正在运行的 Pod 有以下影响。

  • 没有设置 Toleration 的 Pod 会被立刻驱逐。

  • 配置了对应 Toleration 的 Pod,如果没有为 tolerationSeconds 赋值,则会一直留在这一 node 中。

  • 配置了对应 Toleration 的 Pod 且指定了 tolerationSeconds 值,则会在指定时间段后驱逐。

举例,当网络出现故障时(网络中断),对于一个与节点本地状态有着深度绑定的应用而言,仍然停留在当前节点上运行一段较长的时间,以等待网络恢复以避免被驱逐。 此时可以为 Pod 设置的容忍度,如下所示:

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

说明: Kubernetes 会自动给 Pod 添加针对 node.kubernetes.io/not-readynode.kubernetes.io/unreachable 的容忍度,且配置 tolerationSeconds=300, 除非用户自身或者某控制器显式设置此容忍度。 这些自动添加的容忍度意味着 Pod 可以在检测到对应的问题之一时,在 5 分钟内保持绑定在该节点上。

DaemonSet 中的 Pod 被创建时,针对以下污点自动添加的 NoExecute 的容忍度将不会指定 tolerationSeconds

  • node.kubernetes.io/unreachable

  • node.kubernetes.io/not-ready

这保证了出现上述问题时 DaemonSet 中的 Pod 永远不会被驱逐

4.  基于 node 状态添加 Taint(污点)

控制平面使用 节点控制器 自动创建 与 节点状况 对应的、效果为 NoSchedule 的 Taint(污点)。

调度器在进行调度时检查污点,而不是检查节点状况。这确保节点状况不会直接影响调度。

例如:

  • 如果 DiskPressure 节点状况处于活跃状态,则控制平面添加 node.kubernetes.io/disk-pressure 污点并且不会调度新的 Pod 到受影响的节点。

  • 如果 MemoryPressure 节点状况处于活跃状态,则控制平面添加 node.kubernetes.io/memory-pressure 污点并且不会调度新的 Pod 到受影响的节点。

推荐参考文档:

  • 《污点和容忍度》https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/taint-and-toleration/

  • 《众所周知的标签、注解和污点》https://kubernetes.io/zh-cn/docs/reference/labels-annotations-taints/

未完待续

“夜好深了,纸窗里怎么亮着?”  好了先分享到这里,下一篇文章我们继续讲解,比如:Pod 优先级调度、DaemonSet 调度,Job 批处理调度,CronJob 定时任务 以及 自定义调度器。感兴趣的小伙伴,欢迎关注我,一起学习,一起成长哦!

以上是关于k8s 读书笔记 - 详解 Pod 调度(Ⅰ卷)的主要内容,如果未能解决你的问题,请参考以下文章

k8s 读书笔记 - 深入掌握 Pod

java数据结构之HashSet和HashMap(java核心卷Ⅰ读书笔记)

java数据结构之链表(java核心卷Ⅰ读书笔记)

k8s实战读书笔记

k8s 实践经验pod 详解

k8s 读书笔记 - Pod 的升级和回滚