醋醋百科网

Good Luck To You!

涨薪技术|Kubernetes(k8s)之调度解释


在 Kubernetes 中,调度 (scheduling) 指的是确保 Pod 匹配到合适的节点, 以便 kubelet 能够运行它们。抢占 (Preemption) 指的是终止低优先级的 Pod 以便高优先级的 Pod 可以调度运行的过程。驱逐 (Eviction) 是在资源匮乏的节点上,主动让一个或多个 Pod 失效的过程。


01调度概述

在 Kubernetes 中,调度是指将 Pod 放置到合适的节点上,以便对应节点上的 Kubelet 能够运行这些 Pod。

调度器通过 Kubernetes 的监测(Watch)机制来发现集群中新创建且尚未被调度到节点上的 Pod。调度器会将所发现的每一个未调度的 Pod 调度到一个合适的节点上来运行。调度器会依据下文的调度原则来做出调度选择。

kube-scheduler

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

Kube-scheduler 选择一个最佳节点来运行新创建的或尚未调度(unscheduled)的 Pod。由于 Pod中的容器和 Pod 本身可能有不同的要求,调度程序会过滤掉任何不满足 Pod 特定调度需求的节点。或者,API 允许你在创建 Pod 时为它指定一个节点,但这并不常见,并且仅在特殊情况下才会这样做。

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

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

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

kube-scheduler 调度流程

kube-scheduler 给一个 Pod 做调度选择时包含两个步骤:过滤和打分;

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

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

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

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

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

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


02定向调度

定向调度,指的是利用在pod上声明nodeName或者nodeSelector,以此将Pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使要调度的目标Node不存在,也会向上面进行调度,只不过pod运行失败而已。

定向调度一般有两种方式:NodeName和NodeSelector两种方式。

1、NodeName

NodeName用于强制约束将Pod调度到指定的Name的Node节点上。这种方式,其实是直接跳过Scheduler的调度逻辑,直接将Pod调度到指定名称的节点。

#创建编辑pod-nodename.yaml,并填入如下内容:
apiVersion: v1
kind: Pod
metadata:
name: pod-nodename
namespace: test
spec:
containers:
- name: nginx
image: nginx:1.17.1
nodeName: node1

创建pod,并察看pod信息

# 使用 yaml 文件创建 pod
[root@k8s-master pod-files]# kubectl apply -f pod-nodename.yaml
pod/pod-nodename created
# 查看 pod 详情
[root@k8s-master pod-files]# kubectl get pods -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED
NODE READINESS GATES
pod-nodename 0/1 Pending 0 3s  k8s-node3 

2、NodeSelector

NodeSelector用于将pod调度到添加了指定标签的node节点上。它是通过kubernetes的labelselector机制实现的,也就是说,在pod创建之前,会由scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后将pod调度到目标节点,该匹配规则是强制约束。

# 先给两个节点分别打上标签
[root@k8s-master ~]# kubectl label nodes k8s-node1 nodeenv=pro
node/k8s-node1 labeled
[root@k8s-master ~]# kubectl label nodes k8s-node2 nodeenv=test
node/k8s-node2 labeled

创建一个pod-nodeselector.yaml文件

apiVersion: v1
kind: Pod
metadata:
name: pod-nodeselector
namespace: test
spec:
containers:
- name: nginx
image: nginx:1.17.1
nodeSelector:
nodeenv: test

创建pod,并察看pod信息

# 创建 pod
[root@k8s-master ~]# kubectl apply -f yaml-files/pod-files/pod-nodeselector.yaml
pod/pod-nodeselector created
# 查看 pod,是被调度到了 k8s-node1,符合预期
[root@k8s-master ~]# kubectl get pod -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE
NOMINATED NODE READINESS GATES
pod-nodeselector 1/1 Running 0 7s 10.244.0.113 k8s-node1
 
# 接下来,删除pod,修改nodeSelector的值为nodeenv: propro(不存在打有此标签的节点)
[root@k8s-master ~]# kubectl delete -f yaml-files/pod-files/podnodeselector.
yaml
pod "pod-nodeselector" deleted
[root@k8s-master ~]# kubectl apply -f yaml-files/pod-files/pod-nodeselector.yaml
pod/pod-nodeselector created
# 再次查看,发现 pod 无法正常运行,Node 的值为 none
[root@k8s-master ~]# kubectl get pods -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED
NODE READINESS GATES
pod-nodeselector 0/1 Pending 0 18s   

# 查看详情,发现 node selector 匹配失败的提示:没有可以匹配到的节点,所以是 pending 状态
[root@k8s-master ~]# kubectl describe pods/pod-nodeselector -n dev
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling  default-scheduler 0/2 nodes are
available: 2 node(s) didn't match node selector.


03亲和性调度

一般情况下部署的 Pod 是通过集群的自动调度策略来选择节点的,默认情况下调度器考虑的是资源足够,并且负载尽量平均,但是有的时候我们需要能够更加细粒度的去控制 Pod 的调度,比如内部的一些服务 gitlab 之类的也是跑在Kubernetes集群上的,就不希望对外的一些服务和内部的服务跑在同一个节点上了,担心内部服务对外部的服务产生影响;但是有的时候服务之间交流比较频繁,又希望能够将这两个服务的 Pod 调度到同一个的节点上。

这就需要用到 Kubernetes 里面的一个概念:亲和性和反亲和性。

默认情况下创建Pod是根据Kubernetes scheduler的默认调度规则来进行调度,但有些时候,有些应用有一些特殊的需求。比如指定部署到对应的节点,多个Pod之间需要部署在不同的一个节点,需要互斥、Pod和Pod间互相交流比较频繁需要跑在一个节点,这里就需要用到Kubernetes里面的一个概念: 亲和性和反亲和性

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

NodeAffinity node亲和性调度

NodeAffinity意为Node亲和性的调度策略,是用于替换NodeSelector的全新调度策略,目前有两种节点亲和性表达。

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

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

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

apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity-required
namespace: test
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: nodeenv
operator: In
values:
- test
- aaa
containers:
- name: nginx
image: nginx:1.17.1

创建pod,并察看pod信息

#创建pod
[root@master ~]# kubectl apply -f pod-nodeaffinity-required.yaml
pod/pod-nodeaffinity-required created
#查看pod信息
#pod-nodeaffinity-required运行在node2节点上
[root@master ~]# kubectl get pod pod-nodeaffinity-required -n test -o wide
NAME READY STATUS RESTARTS AGE IP
NODE NOMINATED NODE READINESS GATES
pod-nodeaffinity-required 1/1 Running 0 3m37s 10.244.104.4
node2  

下面是

PrefeeredDuringSchedulingIgonredDuringExecution

调度的策略使用上面一样的方法可以测试pod的调度情况

apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity-preferred
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: nodeenv
operator: In
values:
- bbb
- aaa
containers:
- name: nginx
image: nginx:1.17.1

NodeAffinity语法支持的操作符包括In、NotIn、Exists、DoesNotExist、Gt、Lt。

虽然没有节点排斥功能,但是NotIn和DoesNotExist就可以实现排斥的功能了。

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

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

PodAffinity Pod亲和性与互斥调度策略

根据在节点上正在运行的Pod的标签而不是节点的标签进行判断和调度,要求对节点和Pod两个条件进行匹配。这种规则可以描述为:如果在具有标签X的Node上运行了一个或多个符合条件Y的Pod,那么Pod应该(如果是互斥的情况,那么就变成拒绝)运行在这个Node上。

与节点不同的是,Pod是属于某个命名空间的,所以条件Y表达的是一个或者全部命名空间中的一个LabelSelector。

和节点亲和性相同,Pod亲和性与互斥的条件设置也是


requiredDuringSchedulingIgnoredDuringExecution和


preferredDuringSchedulingInnoredDuringExecution。

Pod的亲和性被定义为PodSpec的affinity字段下的podAffinity子字段中。

Pod间的互斥性则被定义于同一层次的podAntiAffinity子字段中。

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

参照目标Pod

首先,创建一个名为pod-flag的Pod,带有标签security=S1和app=nginx,后面的例子将使用pod-flag

作为Pod亲和性与互斥的目标Pod:

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

Pod的亲和性调度

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

apiVersion: v1
kind: Pod
metadata:
name: pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelectors:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx

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

查看node labels命令:kubectl get nodes --show-labels

Pod的互斥性调度

创建第3个Pod,我们希望它不与目标Pod运行在同一个Node上:

apiVersion: v1
kind: Pod
metadata:
name: anti-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operation: In
values:
- S1
topologyKey: failure-domain.beta.kubernetes.io/zone
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operation: 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、DoesNotExist、Gt、Lt。

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

  • 在Pod亲和性和RequiredDuringScheduling的Pod互斥性的定义中,不允许使用空的topologyKey。
  • 如果Admission controlloer 包含了LimitPodHardAntiAffinityTopology,那么针对RequiredDuringScheduling的Pod互斥性定义就被限制为kubernetes.io/hostname,要使用自定义的topologyKey,就要改写或禁用该控制器。
  • 在preferredDuringScheduling类型的Pod互斥性定义中,空的topologyKey会被解释为
  • kebernetes.io/hostname、failure-domain.beat.kubernetes.io/zone及failuredomain.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上。

下期分享污点与容忍知识,敬请关注!

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言