目录
一 、调度说明
简介
Scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上。听起来非常简单,但有很多要考虑的问题:
公平:如何保证每个节点都能被分配资源
资源高效利用:集群所有资源最大化被使用
效率:调度的性能要好,能够尽快地对大批量的 pod 完成调度工作
灵活:允许用户根据自己的需求控制调度的逻辑
我们的Scheduler就是根据上面的设计出来的
Sheduler 是作为单独的程序运行的,启动之后会一直监听API Server(这里的监听其实就是持续连接的状态),获取 PodSpec.NodeName 为空的 pod,也就是说这个字段里面有值的话,我们就不需要对Sheduler进行调度的,因为PodSpec.NodeName 其实就是指定我们的pod要去哪个地方运行,都指定了也就不需要进行调度了,对每个 pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上。。。
调度过程
调度分为几个部分:首先是过滤掉不满足条件的节点,这个过程称为 predicate(预选) ;然后对通过的节点按照优先级排序,这个是 priority (优选);最后从中选择优先级最高的节点。如果中间任何一步骤有错误,就直接返回错误。
Predicate 有一系列的算法可以使用:
PodFitsResources :节点上剩余的资源是否大于 pod 请求的资源
PodFitsHost :如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配
PodFitsHostPorts :节点上已经使用的 port 是否和 pod 申请的 port 冲突
PodSelectorMatches :过滤掉和 pod 指定的 label 不匹配的节点
NoDiskConflict :已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读
如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程: 按照优先级大小对节点排序。
优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。这些优先级选项包括:
1. LeastRequestedPriority :通过计算 CPU 和 Memory 的使用率来决定权重,使用率越低权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点
2. BalancedResourceAllocation :节点上 CPU 和 Memory 使用率越接近,权重越高。这个应该和上面的一起使用,不应该单独使用
3. ImageLocalityPriority :倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高
通过算法对所有的优先级项目和权重进行计算,得出最终的结果。
下面的是自己定义一个属于自己的调度器
自定义调度器
除了 kubernetes 自带的调度器,你也可以编写自己的调度器。通过 spec:schedulername 参数指定自定义的调度器的名字即可,可以为 pod 选择某个调度器进行调度,比如下面的 pod 选择 my-scheduler 进行调度,而不是默认的default-scheduler 。
apiVersion: v1
kind: Pod
metadata:
name: annotation-second-scheduler
labels:
name: multischeduler-example
spec:
schedulername: my-scheduler
containers:
- name: pod-with-second-annotation-container
image: gcr.io/google_containers/pause:2.0
二、调度亲和性
什么是调度亲和性?
生活中的示例,张三一定要去3班和张三想去3班,前者是必须去,后者是可去可不去,前者是硬的,后者是软的。
pod跟node节点亲和性
在我们k8s中,我们的节点亲和是通过pod下面的spec下面的nodeAffinity去实现的。
pod.spec.nodeAffinity(下面的是两种类型)
1. preferredDuringSchedulingIgnoredDuringExecution:软策略
2. requiredDuringSchedulingIgnoredDuringExecution:硬策略
下面通过实验测试一下
首先是requiredDuringSchedulingIgnoredDuringExecution(硬策略)
apiVersion: v1
kind: Pod
metadata:
name: affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: busybox:alpine
affinity: #亲和性
nodeAffinity: #node的亲和性
requiredDuringSchedulingIgnoredDuringExecution: #硬亲和性
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- master2
分析:上面的简单分析一下,我们的NotIn的含义是,只要我们的hostname不等于master2即可,也就是说我们的pod不会运行在master2这个节点中,而运行在其他节点中,我们通过下面的查看labels的命令可以看到kubernetes.io/hostname等于哪个节点名字。
上面的案例中key其实就是我们node节点的标签,如下我们可以看到每个node的标签:
preferredDuringSchedulingIgnoredDuringExecution 软策略
软亲和性性需要加上权重这个参数,权重越大越亲和,即越可能被调度
apiVersion: v1
kind: Pod
metadata:
name: affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: hub.atguigu.com/library/myapp:v1
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1 #权重为1,权重越大越亲和
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- master3
分析:上面的的话就是期望运行在我们的master3,有的话最好,没有的话就去其他的node。
软策略和硬策略合并
[root@master1 affinity]# cat pod5.yaml
apiVersion: v1
kind: Pod
metadata:
name: affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: busybox:apline
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- master1
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: source
operator: In
values:
- test
[root@master1 affinity]#
[root@master1 affinity]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
affinity 0/1 ErrImagePull 0 49s 10.244.1.3 master2 <none> <none>
[root@master1 affinity]#
必须先满足我们的硬策略,才能去执行我们的软策略。分析一下上面的yaml文件,我们的运算关系是NotIn,所以我们的运行的节点可能是node-01,node-03......,然后在这些节点中如果再满足source等于test的节点,则优先调度。
键值运算关系
In:label 的值在某个列表中
NotIn:label 的值不在某个列表中
Gt:label 的值大于某个值
Lt:label 的值小于某个值
Exists:某个 label 存在
DoesNotExist:某个 label 不存在
注意:如果nodeSelectorTerms下面有多个选项的话,满足任何一个条件即可;如果matchExpressions有多个选项的话,则必须同时满足这些条件才能正常调度pod。
Pod 跟pod亲和性
pod.spec.affinity.podAffinity/podAntiAffinity(在这个下面声明)
preferredDuringSchedulingIgnoredDuringExecution:软策略
requiredDuringSchedulingIgnoredDuringExecution:硬策略
我们先创建一个pod
[root@master1 affinity]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: master1
labels:
app: master1
spec:
containers:
- name: with-node-affinity
image: nginx:alpine
[root@master1 affinity]# kubectl apply -f pod.yaml
pod/master1 created
[root@master1 affinity]#
[root@master1 affinity]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
master1 1/1 Running 0 7s 10.244.0.186 master1 <none> <none>
[root@master1 affinity]#
然后先创建我们的硬策略pod
[root@master1 affinity]# cat pod2.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-3
labels:
app: pod-3
spec:
containers:
- name: pod-3
image: nginx:alpine
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- master1
topologyKey: kubernetes.io/hostname
[root@master1 affinity]#
[root@master1 affinity]#
[root@master1 affinity]# kubectl apply -f pod2.yaml
pod/pod-3 created
[root@master1 affinity]#
[root@master1 affinity]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
master1 1/1 Running 0 3m30s 10.244.0.186 master1 <none> <none>
pod-3 1/1 Running 0 7s 10.244.0.187 master1 <none> <none>
分析:当我们的集群中的某个pod有app=master1这个标签的时候,我们 -o wide得到该pod所处的node节点,由于这里是硬策略,所以我们上面创建出来的pod-3就会运行在这个node节点上的,判断是否在同一个node的标准就是topologyKey字段了,即kubernetes.io/hostname要是一样的。
即如果想要pod运行在同一个node上,就用podAffinity;
如果不想在同一节点的话,就用podAntiAffinity。案例如下:
我们先删除之前创建的pod
[root@master1 affinity]# kubectl delete -f pod2.yaml
pod "pod-3" deleted
[root@master1 affinity]#
增加yaml文件podAntiAffinity字段
[root@master1 affinity]# cat pod2.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-3
labels:
app: pod-3
spec:
containers:
- name: pod-3
image: nginx:alpine
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- master1
topologyKey: kubernetes.io/hostname
[root@master1 affinity]# kubectl apply -f pod2.yaml
pod/pod-3 created
[root@master1 affinity]# kubectl get pod
NAME READY STATUS RESTARTS AGE
master1 1/1 Running 0 8m27s
pod-3 0/1 ContainerCreating 0 2s
[root@master1 affinity]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
master1 1/1 Running 0 8m36s 10.244.0.191 master1 <none> <none>
pod-3 0/1 ContainerCreating 0 11s <none> master2 <none> <none>
[root@master1 affinity]#
上面的 “同一个node” 其实不规范,应该是同一个拓扑域,即根据我们的topologyKey来决定的,例如上面的yaml中kubernetes.io/hostname,这个是具有唯一性,所以同一个拓扑域就是同一个节点了。
关于同一个拓扑域的解释说明如下:‘
例如我们有以下三个node,其中两个node的标签一样,都有disk=1,当我们把topologyKey改成disk的时候,此时我们的pod放在node01和node02都可以,这个就叫做同一拓扑域
亲和性/反亲和性调度策略比较如下:
官方解释:
如果该X已经在运行一个或多个满足规则Y的Pod,则该Pod应该(或者在非亲和性的情况下不应该)在X中运行
Y表示为LabelSelector规则
X是一个拓扑域,例如节点,机架,云提供者区域,云提供者区域等。您可以使用topologyKey这是系统用来表示这种拓扑域的节点标签的密钥。
关于topologyKey字段可参考https://www.jianshu.com/p/d906e819245c
这里补充一个有意思的玩法:k8s每节点相同服务只部署一个进程
三、污点和容忍
Taint 和 Toleration
节点亲和性,是 pod 的一种属性(偏好或硬性要求),它使 pod 被吸引到一类特定的节点。Taint 则相反,它使节点 能够 排斥 一类特定的 pod。
Taint 和 toleration 相互配合,可以用来避免 pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个taint ,这表示对于那些不能容忍这些 taint 的 pod,是不会被该节点接受的。如果将 toleration 应用于 pod上,则表示这些 pod 可以(但不要求)被调度到具有匹配 taint 的节点上。
举例来说,我们去相亲的话,女方的要求是,不能容忍是抽烟的,那么打上抽烟这一类标签的人就不可能成功;反过来说,女方可以接受抽烟的,那么天下那么多抽烟的,只能有一个。即,我们的污点和容忍的概念就是,如果能容忍这个污点,可能发生故事,但不一定会发生故事;如果不能容忍的话,那么只要那个node有一个污点的话,就不会被调度到这个node上去。
污点(Taint)
Ⅰ、 污点 ( Taint ) 的组成
使用 kubectl taint 命令可以给某个 Node 节点设置污点,Node 被设置上污点之后就和 Pod 之间存在了一种相斥的关系,可以让 Node 拒绝 Pod 的调度执行,甚至将 Node 已经存在的 Pod 驱逐出去。
每个污点的组成如下:
key=value:effect
每个污点有一个 key 和 value 作为污点的标签,其中 value 可以为空,effect 描述污点的作用。当前 taint effect 支持如下三个选项:
NoSchedule :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上
PreferNoSchedule :表示 k8s 将尽量避免将 Pod 调度到具有该污点的 Node 上
NoExecute :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的 Pod 驱逐出去。。即把该Node上的Pod全部排出出去,如果是静态pod的话,驱逐就不会被创建了,相当于delete命令,如果是控制器控制的pod,则会进入到其他的Node上去,因为要维持副本的数目。
Ⅱ、污点的设置、查看和去除
# 设置污点
kubectl taint nodes node1 key1=value1:NoSchedule
# 节点说明中,查找 Taints 字段
kubectl describe pod pod-name
# 去除污点
kubectl taint nodes node1 key1:NoSchedule-
举例:下面的是我们的一个master节点的信息(kubectl describe node k8s-master)
这个的意思是,k8s不会将Pod调度到该节点上,也就是为什么我们的pod永远不会在master节点上。(这里省略了value)
容忍(Tolerations)
设置了污点的 Node 将根据 taint 的 effect:NoSchedule、PreferNoSchedule、NoExecute 和 Pod 之间产生互斥的关系,Pod 将在一定程度上不会被调度到 Node 上。 但我们可以在 Pod 上设置容忍 ( Toleration ) ,意思是设置了容忍的 Pod 将可以容忍污点的存在,可以被调度到存在污点的 Node 上。
在pod.spec.tolerations字段定义
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
tolerationSeconds: 3600 #容忍时间
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
- key: "key2"
operator: "Exists"
effect: "NoSchedule"
如果我们设置tolerationSeconds的时候,必须effect为NoExecute,如下图
其中 key, value, effect 要与 Node 上设置的 taint 保持一致,operator 的值为 Exists 将会忽略 value 值
tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在 Pod 上继续保留运行的时间,我们之前说过,有个NoExecute 的驱逐机制,如果我们设置了这里的时间的话,虽然pod会被驱逐,但是在设置的时间之后才会被驱逐,例如上面的3600s之后被驱逐。
注意以下几个点
Ⅰ、当不指定 key 值时,表示容忍所有的污点 key:
tolerations:
- operator: "Exists"
Ⅱ、当不指定 effect 值时,表示容忍所有的污点作用
tolerations:
- key: "key"
operator: "Exists"
Ⅲ、有多个 Master 存在时,防止资源浪费,可以如下设置
kubectl taint nodes Node-Name node-role.kubernetes.io/master=:PreferNoSchedule
分析:PreferNoSchedule,这个的含义是“尽可能不在这个node上运行”,当我们后边所有的pod出现资源不够用的情况,这样的话,就可以在我们的master节点上运行对应的pod了。
总结:上面就有个运维的关系,就是假如有一天,我们的某个node需要维护或者是更新的话,但是该node上去已经运行了很多的pod,如果我们直接把node关闭的话,就会造成我们的网路灯访问的影响,这个时候我们可以对该node打上一个污点,就用NoExecute,让所有的pod被驱逐出去,去别的node上面去运行,这样就可以更新该node了。
四、固定节点
即调度到指定的节点中上
Ⅰ、Pod.spec.nodeName 将 Pod 直接调度到指定的 Node 节点上,会跳过 Scheduler 的调度策略,该匹配规则是强制匹配。
[root@master1 affinity]# cat pod4.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: myweb
spec:
replicas: 7
template:
metadata:
labels:
app: myweb
spec:
nodeName: master1
containers:
- name: myweb
image: nginx:alpine
ports:
- containerPort: 80
[root@master1 affinity]#
[root@master1 affinity]# kubectl apply -f pod4.yaml
deployment.extensions/myweb created
[root@master1 affinity]#
分析:上面的七个pod全部运行在我们的master2上
Ⅱ、Pod.spec.nodeSelector:通过 kubernetes 的 label-selector 机制选择节点,由调度器调度策略匹配 label,而后调度 Pod 到目标节点,该匹配规则属于强制约束。
[root@master1 affinity]# cat pod4.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: myweb
spec:
replicas: 7
template:
metadata:
labels:
app: myweb
spec:
nodeSelector:
disk: ssd
containers:
- name: myweb
image: nginx:alpine
ports:
- containerPort: 80
[root@master1 affinity]#
[root@master1 affinity]# kubectl apply -f pod4.yaml
deployment.extensions/myweb created
[root@master1 affinity]#
[root@master1 affinity]# kubectl get pod
NAME READY STATUS RESTARTS AGE
myweb-74b45c5575-4h9hm 0/1 Pending 0 4s
myweb-74b45c5575-5mjjk 0/1 Pending 0 4s
myweb-74b45c5575-hsvlf 0/1 Pending 0 4s
myweb-74b45c5575-l59fr 0/1 Pending 0 4s
myweb-74b45c5575-mcf8c 0/1 Pending 0 4s
myweb-74b45c5575-v6kzb 0/1 Pending 0 4s
myweb-74b45c5575-w58lk 0/1 Pending 0 4s
[root@master1 affinity]#
开始我们node的标签不是disk=ssd,所有一直处于Pending状态,后来打上了标签就好了
[root@master1 affinity]# kubectl label node master1 disk=ssd
node/master1 labeled
[root@master1 affinity]#
[root@master1 affinity]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myweb-74b45c5575-4h9hm 1/1 Running 0 54s 10.244.0.203 master1 <none> <none>
myweb-74b45c5575-5mjjk 1/1 Running 0 54s 10.244.0.200 master1 <none> <none>
myweb-74b45c5575-hsvlf 1/1 Running 0 54s 10.244.0.199 master1 <none> <none>
myweb-74b45c5575-l59fr 1/1 Running 0 54s 10.244.0.205 master1 <none> <none>
myweb-74b45c5575-mcf8c 1/1 Running 0 54s 10.244.0.201 master1 <none> <none>
myweb-74b45c5575-v6kzb 1/1 Running 0 54s 10.244.0.202 master1 <none> <none>
myweb-74b45c5575-w58lk 1/1 Running 0 54s 10.244.0.204 master1 <none> <none>
[root@master1 affinity]#