域安全 | K8s调度策略
调度
在K8s中,调度是指将Pod放置到合适的节点上。调度器通过 K8s 的监测机制来发现集群中新创建且尚未被调度到节点上的Pod。调度器会将所发现的每一个未调度的Pod调度到一个合适的节点上来运行。
kube-scheduler调度器
kube-scheduler组件是K8s集群的默认调度器,并且是集群控制面的一部分。对每一个新创建的Pod或者是未被调度的 Pod,kube-scheduler 会选择一个最优的节点去运行这个Pod。然而,Pod内的每一个容器对资源都有不同的需求,而且Pod本身也有不同的需求。因此,Pod 在被调度到节点上之前,根据这些特定的调度需求,需要对集群中的节点进行一次过滤。在一个集群中满足一个 Pod调度请求的所有节点称之为可调度节点。如果没有任何一个节点能满足 Pod 的资源请求,那么这个 Pod 将一直停留在未调度状态直到调度器能够找到合适的Node节点。kube-scheduler调度器先在集群中找到一个Pod的所有可调度节点,然后根据一系列函数对这些可调度节点打分,选出其中得分最高的节点来运行 Pod。之后,调度器将这个调度决定通知给 kube-apiserver,这个过程叫做绑定。
在做调度决定时需要考虑的因素包括:单独和整体的资源请求、硬件/软件/策略限制、 亲和以及反亲和要求、数据局部性、负载间的干扰等等。
kube-scheduler调度流程
kube-scheduler 给一个 Pod 做调度选择时包含两个步骤:
- 过滤
- 打分
过滤阶段会将所有满足Pod调度需求的节点选出来。PodFitsResources 过滤函数会检查候选节点的可用资源能否满足 Pod 的资源请求。在过滤之后,得出一个节点列表,里面包含了所有可调度节点;通常情况下, 这个节点列表包含不止一个节点。如果这个列表是空的,代表这个 Pod 不可调度。
在打分阶段,调度器会为 Pod 从所有可调度节点中选取一个最合适的节点。根据当前启用的打分规则,调度器会给每一个可调度节点进行打分。最后,kube-scheduler 会将 Pod 调度到得分最高的节点上。如果存在多个得分最高的节点,kube-scheduler 会从中随机选取一个。
支持以下两种方式配置调度器的过滤和打分行为:
- 调度策略:允许你配置过滤所用的 断言(Predicates) 和打分所用的 优先级(Priorities)。
- 调度配置:允许你配置实现不同调度阶段的插件, 包括:QueueSort、Filter、Score、Bind、Reserve、Permit 等等。你也可以配置 kube-scheduler 运行不同的配置文件。
将Pod分配给指定节点
可以通过一些手段约束一个Pod以便限制其只能在特定的节点上运行,或优先在特定的节点上运行。有几种方法可以实现这点:
- 节点标签
- 亲和性与反亲和性
- nodeName字段
- Pod拓扑分布约束
- 污点和容忍度
节点标签
标签(Labels)是附加到 K8s 对象(比如 Pod)上的键值对。标签旨在用于指定对用户有意义且相关的对象的标识属性,但不直接对核心系统有语义含义。标签可以用于组织和选择对象的子集。标签可以在创建时附加到对象,随后可以随时添加和修改。每个对象都可以定义一组键/值标签。每个键对于给定对象必须是唯一的,如下:
"metadata": { "labels": { "key1" : "value1", "key2" : "value2" }}
与很多其他 K8s 对象类似,节点也有标签(Labels),也可以手动给节点添加标签。K8s 也会为集群中所有节点添加一些标准的标签。
给节点添加标签
执行如下命令给指定的k8s-node1节点添加标签 key1=value1。
#给指定的节点打标签kubectl label nodes k8s-node1 key1=value1#移除标签kubectl label nodes k8s-node1 key1-
创建一个将被调度到你选择的节点的 Pod
”
如下Pod 配置文件描述了一个拥有节点选择器 key1: value1 的 Pod。这表明该 Pod 将被调度到有 key1: value1 标签的节点上。
apiVersion: v1kind: Podmetadata: name: nginx labels: env: testspec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent nodeSelector: key1: value1
可以看到该pod确实创建在具有该标签的node节点上。
亲和性和反亲和性
nodeSelector 提供了一种最简单的方法来将 Pod 约束到具有特定标签的节点上,而亲和性和反亲和性扩展了你可以定义的约束类型。使用亲和性与反亲和性的一些好处有如下:
- 亲和性、反亲和性语言的表达能力更强。nodeSelector 只能选择拥有所有指定标签的节点,而亲和性、反亲和性为你提供对选择逻辑的更强控制能力。
- 你可以标明某规则是“软需求”或者“偏好”,这样调度器在无法找到匹配节点时仍然调度该 Pod。
- 你可以使用节点上(或其他拓扑域中)运行的其他 Pod 的标签来实施调度约束, 而不是只能使用节点本身的标签。这个能力让你能够定义规则允许哪些 Pod 可以被放置在一起。
亲和性功能由两种类型的亲和性组成:
- 节点亲和性功能类似于 nodeSelector 字段,但它的表达能力更强,并且允许你指定软规则。
- Pod 间亲和性、反亲和性允许你根据其他 Pod 的标签来约束 Pod。
节点亲和性
”
节点亲和性概念上类似于 nodeSelector, 它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点上。节点亲和性有两种:
- requiredDuringSchedulingIgnoredDuringExecution:调度器只有在规则被满足的时候才能执行调度。此功能类似于 nodeSelector, 但其语法表达能力更强。
- preferredDuringSchedulingIgnoredDuringExecution:调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该 Pod。
注:在上述类型中,IgnoredDuringExecution 意味着如果节点标签在 Kubernetes 调度 Pod 后发生了变更,Pod 仍将继续运行。
Pod 间亲和性与反亲和性
”
Pod间亲和性与反亲和性使你可以基于已经在节点上运行的Pod的标签来约束Pod可以调度到的节点,而不是基于节点上的标签。
Pod间亲和性与反亲和性的规则格式为“如果 X 上已经运行了一个或多个满足规则 Y 的 Pod, 则这个 Pod 应该(或者在反亲和性的情况下不应该)运行在 X 上”。这里的 X 可以是节点、机架、云提供商可用区或地理区域或类似的拓扑域, Y 则是 K8s 尝试满足的规则。
你可以通过标签选择算符的形式来表达规则(Y),并可根据需要指定关联的名字空间列表。Pod 在 K8s 中是名字空间作用域的对象,因此 Pod 的标签也隐式地具有名字空间属性。针对 Pod 标签的所有标签选择算符都要指定名字空间,K8s 会在指定的名字空间内寻找标签。
你会通过 topologyKey 来表达拓扑域(X)的概念,其取值是系统用来标示域的节点标签键。
注:Pod 间亲和性和反亲和性都需要相当的计算量,因此会在大规模集群中显著降低调度速度。我们不建议在包含数百个节点的集群中使用这类设置。Pod 反亲和性需要节点上存在一致性的标签。换言之, 集群中每个节点都必须拥有与 topologyKey 匹配的标签。如果某些或者所有节点上不存在所指定的 topologyKey 标签,调度行为可能与预期的不同。
用节点亲和性把Pod分配到节点
”
执行如下命令给指定的k8s-node1节点添加标签 key1=value1。
#给指定的节点打标签kubectl label nodes k8s-node1 key1=value1
下面清单描述了一个 Pod,它有一个节点亲和性配置 requiredDuringSchedulingIgnoredDuringExecution,key1=value1。这意味着 pod 只会被调度到具有 key1=value1 标签的节点上。
apiVersion: v1kind: Podmetadata: name: nginx4spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: key1 operator: In values: - value1 containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent
本清单描述了一个Pod,它有一个节点亲和性设置 preferredDuringSchedulingIgnoredDuringExecution,key1=value1。这意味着 pod 将首选具有 key1=value1 标签的节点。如果没有找到key1=value1 标签的节点,则会随机调度到可用的Node节点上。
apiVersion: v1kind: Podmetadata: name: nginx5spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: key1 operator: In values: - value1 containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent
nodeName字段
可以通过设置 nodeName 将某个 Pod 调度到特定的节点,且该 Pod 将只能被调度到特定节点。
apiVersion: v1kind: Podmetadata: name: nginx2spec: nodeName: k8s-node2 containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent
Pod 拓扑分布约束
可以使用拓扑分布约束(Topology Spread Constraints)来控制Pod在集群内故障域之间的分布,故障域的示例有区域(Region)、可用区(Zone)、节点和其他用户自定义的拓扑域。这样做有助于提升性能、实现高可用或提升资源利用率。详细:Pod 拓扑分布约束: https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/topology-spread-constraints/
污点(Taint)和容忍度(Toleration)
节点亲和性是Pod的一种属性,它使Pod被吸引到一类特定的节点(这可能是出于一种偏好,也可能是硬性要求)。而污点(Taint)则相反,污点(Taint)是应用在Node节点之上的它使节点能够排斥一类特定的Pod,具有污点Taint的node和pod是互斥关系;而容忍度(Toleration)是应用于 Pod 上的,容忍度允许(但不要求)调度器调度带有对容忍度的Pod到Node节点上,污点和容忍度的目的是优化pod在集群间的调度。作为其功能的一部分, 调度器也会评估其他参数。
污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod, 是不会被该节点接受的。
给Node节点打污点
”
给节点k8s-master增加一个污点,它的键名是key1,键值是value1,效果是NoSchedule。这表示只有拥有和这个污点相匹配的容忍度的 Pod 才有可能够被分配到k8s-node1这个节点上。
#查看节点信息kubectl describe node k8s-node1#添加污点kubectl taint nodes k8s-node1 key1=value1:NoSchedule#移除污点kubectl taint nodes k8s-node1 key1=value1:NoSchedule-
在pod中定义容忍度
”
可以在 PodSpec 中定义 Pod 的容忍度。下面两个容忍度均与上面例子中使用kubectl taint命令创建的污点相匹配, 因此如果一个 Pod 拥有其中的任何一个容忍度都可能能够被分配到node1:
tolerations:- key: "key1" operator: "Equal" value: "value1" effect: "NoSchedule"
或
tolerations:- key: "key1" operator: "Exists" effect: "NoSchedule"
实例
apiVersion: v1kind: Podmetadata: name: myapp labels: env: testspec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent tolerations: - key: "key1" operator: "Exists" effect: "NoSchedule"
一个容忍度和一个污点相“匹配”是指它们有一样的键名和效果。
operator 的参数含义:
- 如果 operator 是 Exists (此时容忍度不能指定 value)
- 如果 operator 是 Equal ,则它们的 value 应该相等。
- 如果 operator 不指定,则默认为Equal。
注:如果一个容忍度的 key 为空且 operator 为 Exists, 表示这个容忍度与任意的 key、value 和 effect 都匹配,即这个容忍度能容忍任何污点。
effect 的参数含义:
- 如果effect是NoSchedule,则新的不能容忍的pod不能再调度过来,但是之前运行在node节点中的Pod不受影响。
- 如果effect是NoExecute,则新的不能容忍的pod不能调度过来,老的pod也会被驱逐。
- 如果effect是PreferNoScheduler,则表示尽量不调度到污点节点中去。
注:如果 effect 为空,则可以与所有键名 key1 的效果相匹配。
多个污点的匹配规则
”
可以给一个节点添加多个污点,也可以给一个Pod添加多个容忍度。K8s处理多个污点和容忍度的过程就像一个过滤器:从一个节点的所有污点开始遍历, 过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的effect 值决定了 Pod 是否会被分配到该节点,特别是以下情况:
- 如果未被过滤的污点中存在至少一个effect值为NoSchedule的污点, 则 Kubernetes 不会将Pod分配到该节点。
- 如果未被过滤的污点中不存在 effect 值为NoSchedule的污点, 但是存在 effect 值为PreferNoSchedule的污点, 则 Kubernetes 会尝试不将 Pod 分配到该节点。
- 如果未被过滤的污点中存在至少一个 effect 值为NoExecute 的污点, 则 Kubernetes 不会将 Pod 分配到该节点(如果 Pod 还未在节点上运行), 或者将 Pod 从该节点驱逐(如果 Pod 已经在节点上运行)。
例如,假设您给一个节点添加了如下污点:
kubectl taint nodes node1 key1=value1:NoSchedulekubectl taint nodes node1 key1=value1:NoExecutekubectl taint nodes node1 key2=value2:NoSchedule
假定有一个 Pod,它有两个容忍度:
tolerations:- key: "key1" operator: "Equal" value: "value1" effect: "NoSchedule"- key: "key1" operator: "Equal" value: "value1" effect: "NoExecute"
上述 Pod 不会被分配到 node1 节点,因为其没有容忍度和第三个污点相匹配。
但是如果在给节点添加上述污点之前,该 Pod 已经在上述节点运行, 那么它还可以继续运行在该节点上,因为第三个污点是三个污点中唯一不能被这个 Pod 容忍的。
通常情况下,如果给一个节点添加了一个 effect 值为 NoExecute 的污点, 则任何不能忍受这个污点的 Pod 都会马上被驱逐,任何可以忍受这个污点的 Pod 都不会被驱逐。但是,如果 Pod 存在一个 effect 值为 NoExecute 的容忍度指定了可选属性tolerationSeconds 的值,则表示在给节点添加了上述污点之后, Pod 还能继续在节点上运行的时间。例如:
tolerations:- key: "key1" operator: "Equal" value: "value1" effect: "NoExecute" tolerationSeconds: 3600
这表示如果这个 Pod 正在运行,同时一个匹配的污点被添加到其所在的节点, 那么 Pod 还将继续在节点上运行 3600 秒,然后被驱逐。如果在此之前上述污点被删除了,则 Pod 不会被驱逐。
说明
- 默认情况下,只有master节点会有污点(role.kubernetes.io/master:NoSchedule),Node节点默认没有污点。这也是为什么默认情况下Pod不会被分配到master节点的原因。
- 当只有一个节点没污点,其他节点都有污点时,创建pod的yml文件的容忍度没匹配上所有的污点时,创建的pod节点在没有污点的节点上。
- 当只有一个节点没污点,其他节点都有污点时,创建pod的yml文件的容忍度匹配上污点时,创建的pod节点也在没有污点的节点上。
- 当所有node的节点都有污点,创建pod的yml文件的容忍度能匹配上污点时,创建的pod节点在匹配的污点的节点上。
- 当所有node节点都有污点,但是创建pod的yml文件的容忍度没匹配上所有的污点时,创建的pod节点的NODE这里为空。
3
给master节点分配pod
默认情况下创建的pod是不会被分配到master节点上的,因为master节点默认被打上了node-role.kubernetes.io/master:NoSchedule的污点。
#查询k8s-master节点的Taintskubectl describe node k8s-master | grep Taints#取消污点kubectl taint nodes k8s-master role.kubernetes.io/master:NoSchedule-
注:其实也可以省略取消master节点污点这一步。
然后使用nodeName字段指定k8s-master
apiVersion: v1kind: Podmetadata: name: nginxspec: nodeName: k8s-master containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent