Scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上。听起来非常简单,但有很多要考虑的问题:
公平:如何保证每个节点都能被分配资源
资源高效利用:集群所有资源最大化被使用
比如节点1上面内存和cpu都已经到达峰值了,而节点2内存和cpu都使用10%不到,结果后续还是把pod运行在节点1上,这样显然是不合理的。
效率:调度的性能要好,能够尽快地对大批量的 pod 完成调度工作
不能说,后续调度一个节点需要花费10分钟,这肯定是不可以接受的。
灵活:允许用户根据自己的需求控制调度的逻辑
用户可以指定pod在某一个节点运行,或者定义一些规则后再让Sheduler 帮我们选择满足条件的节点。
Sheduler 是作为单独的程序运行的,启动之后会一直坚挺 API Server,获取 PodSpec.NodeName 为空的 pod,对每个 pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上,因为如果PodSpec.NodeName不为空,则说明,你已经指定当前的pod运行在指定的节点上了,就不再需要Sheduler 来帮你选择节点了,它就不需要参与这个过程了。
调度分为几个部分:首先是过滤掉不满足条件的节点,这个过程称为 predicate
;然后对通过的节点按照优先级排序,这个是 priority
;最后从中选择优先级最高的节点。如果中间任何一步骤有错误,就直接返回错误
Predicate 有一系列(这里只是列举了一部分,更多的可以去k8s官网查看)的算法可以使用:
PodFitsResources
:节点上剩余的资源是否大于 pod 请求的资源
这个很嗨理解,当前运行的pod需要内存2G,那么运行的pod肯定最少也要有2G的内存。
PodFitsHost
:如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配
这就是说明当亲Pod已经指定了具体的node节点,不需要判断其它规则了
PodFitsHostPorts
:节点上已经使用的 port 是否和 pod 申请的 port 冲突
PodSelectorMatches
:过滤掉和 pod 指定的 label 不匹配的节点
NoDiskConflict
:已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读
如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程: 按照优先级大小对节点排序优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。这些优先级选项包括:
LeastRequestedPriority
:通过计算 CPU 和 Memory 的使用率来决定权重,使用率越低权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点
比如节点1上面内存和cpu都已经到达峰值了,而节点2内存和cpu都使用10%不到,这样节点2的权重肯定更高
BalancedResourceAllocation
:节点上 CPU 和 Memory 使用率越接近,权重越高。这个应该和上面的一起使用,不应该单独使用
比如节点1 cpu 和内存都是使用了40%,而节点2cpu使用20%,内存使用了60%,那么节点1的权重会更高,因为节点1的CPU 和 Memory 使用率更接近。
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/goo
举例说明,比如分班,现在有两个老师的带班,分别是张三老师的一班和王五老师的二班,那么这个班级就是节点,小明和小红同学就是pod
这里需要说明一点,这个亲和性很是灵活,如果你发现这个pod没有找到你想要找到的pod/node,是因为标签不符合,你可以给对应的pod/node再打上一个标签,这样就可以了。
pod.spec.nodeAffinity
键值运算关系
项目 | Value |
---|---|
In | label 的值在某个列表中 |
NotIn | label 的值不在某个列表中 |
Gt | label 的值大于某个值 |
Lt | label 的值小于某个值 |
Exists | 某个 label 存在 |
DoesNotExist | 某个 label 不存在 |
下面这个案例就是说叫做 affinity
的pod必须不运行在节点k8s-node02
上。
apiVersion: v1
kind: Pod
metadata:
name: affinity # pod 的名称
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity # pod内部第一个容器的名称
image: hub.atguigu.com/library/myapp:v1
affinity:
nodeAffinity: # 使用节点亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 使用硬策略
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- k8s-node02
至于 kubernetes.io/hostname,是我们k8s安装成功之后,会自动获取服务器的hostname,把它设置为对应的名称,如下
运行如下
你会发现这个pod会一直运行在 k8s-node01上,我们把它删除重新建立也是一样,因为我们现在集群节点只有3个,但是master节点上面是有污点的,是不允许运行pod的,所以,可供选择的只有node1 和node2,但是我们前面有设置了硬策略,一定不能运行在node2上面,所以,只能运行在node1上面,无论你删除创建多少次都是一样的。
当然这里在多做说明一下,如果在上面的硬策略中指定的节点为一定在node3中,但是我们又没有这个节点,那么这个pod永远都无法成功运行。
注意硬策略可以写多个,当你写多个时,则说明这个pod必须都满足这些硬策略才能运行成功。
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:
matchExpression
- key: source
perator: In
values:
- qikqiak
上面我们指定这个pod运行在节点有 key=source,value=qikqiak的节点上,如果没有,那就算了,则按照其它选择,选择一个其它节点运行即可。
这里有一点需要注意的就是软策略里面有 weight
权重,意思是当你为一个pod写了多个软策略时,你可以指定那个软策略优先,比如,小明去张三老师的一班的权重是4,而小明去王五老师的二班的权重是3,当两者都可以满足的时候,优先去张三老师的班级,因为它的权重更高。当然,如果去不了张三老师的班级,那么看看去王五老师的班级条件是否满足,如果满足则去王五老师的班级。
即可以选择硬策略和软策略一起使用,则硬策略一定要满足,再考虑软策略。示例如下
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:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- k8s-node02
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: source
operator: In
values:
- qikqiak
我们先运行一个pod,用于测试pod亲和性,如下,这个pod有标签 app=gkj
apiVersion: v1
kind: Pod
metadata:
name: otherPod
labels:
app: gkj
spec:
containers:
- name: otherPod-c1
image: hub.atguigu.com/library/myapp:v1
并且查看这个pod 是运行在 k8s-node01上面的。
apiVersion: v1
kind: Pod
metadata:
name: pod-3
labels:
app: pod-3
spec:
containers:
- name: pod-3
image: hub.atguigu.com/library/myapp:v1
affinity:
podAffinity: # 注意这里是podAffinity 而不是 nodeAffinity
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions: # 匹配规则必须运行在不同的hostName中有运行的pod中标签带有 app=pod-1的节点
- key: app
operator: In
values:
- gkj
topologyKey: kubernetes.io/hostname
这里有两个地方需要注意
topologyKey
,这个是区分 拓扑域的,那什么是拓扑域如下,是指以同一个某一种标签区分的范围划分这里有三台节点,每个节点上面都存有标签,如果以标签划分这个三台节点的拓扑域,那么第一台和第二台就属于同一个拓扑域,比如命名为拓扑域一号,第三台独属于另一个,比如命名为拓扑域二号。所以我们上面以kubernetes.io/hostname区分拓扑域,则表示,每一台主机为一个拓扑域(如果你没有为两台节点设置同一个hostname的话,估计真设置一样,k8s也搭建不起来)
其它情况说明
情况 | 结果说明 |
---|---|
对于上面的拓扑域来说 | 如果是以我们上面那种图片来作为拓扑域,假设otherPod运行在第一个拓扑域,那么我们后续再运行的pod-3这个是可以运行在node1(disk=1)或者node2(disk=1)上面的,因为此时的node1和node2在同一个拓扑域 |
如果此时的将pod-3的pod亲和性的标签改为不存在的 | 则这个pod-3是永远都运行不起来的,其实硬策略,软策略的概念都差不多,只是区分了是节点还是pod。 |
pod软策略 | 软策略里面有 weight 权重,意思是当你为一个pod写了多个软策略时,你可以指定那个软策略优先,比如,小明(pod) 去 小红(pod) 的班级的权重是4,而小明去小绿的班级的权重是3,当两者都可以满足的时候,优先去小红的班级,因为它的权重更高。当然,如果去不了小红班级,那么看看去小绿的班级条件是否满足,如果满足则去小绿班级 |
对与软策略呢,其实没啥太多好说的,看完前面的,你就知道了,但是这里借助pod软策略,再说一个是pod独有的,叫做 podAntiAffinity
,前面的叫做 podAffinity
,作用是 POD与指定POD不在同一拓扑域
apiVersion: v1
kind: Pod
metadata:
name: pod-3
labels:
app: pod-3
spec:
containers:
- name: pod-3
image: hub.atguigu.com/library/myapp:v1
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- gkj
topologyKey: kubernetes.io/hostname
这个上面的意思就是说最好运行在存在 app=gkj
的 pod的节点上,且不在这个pod的拓扑域中。
省略,这个和上面的节点的组合是一样的,只是参数名称不一样,这里不再举例说明。
调度策略 | 匹配标签 | 操作符 | 拓扑域支持 | 调度目标 |
---|---|---|---|---|
nodeAffinity | 主机 | In, NotIn, Exists, DoesNotExist, Gt, Lt | 否 | 指定主机 |
podAffinity | POD | In, NotIn, Exists, DoesNotExist | 是 | POD与指定POD同一拓 扑域 |
podAnitAffinity | POD | In, NotIn, Exists, DoesNotExist | 是 | POD与指定POD不在同一拓扑域 |
注重: 污点是打标签在节点(node)上面的,由pod来选择是否容忍。
节点亲和性,是 pod 的一种属性(偏好或硬性要求),它使 pod 被吸引到一类特定的节点。Taint(污点) 则相反,它使节点 能够 排斥 一类特定的 pod,Taint 和 toleration(容忍) 相互配合,可以用来避免 pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个 taint ,这表示对于那些不能容忍这些 taint 的 pod,是不会被该节点接受的。如果将 toleration 应用于 pod上,则表示这些 pod 可以(但不要求)被调度到具有匹配 taint 的节点上
其实我们在搭建完成k8s的集群的时候,master节点上面就已经打上了标签,effect的值是NoSchedule
,这也是为什么我们在创建pod的时候,不会运行在master节点上的原因,因为我们创建的pod并没有设置可以容忍这个污点,所以schedule在分配的时候,不会分配到master节点上面。
kubectl describe node k8s-master01
使用 kubectl taint
命令可以给某个 Node 节点设置污点,Node 被设置上污点之后就和 Pod 之间存在了一种相斥的关系,可以让 Node 拒绝 Pod 的调度执行,甚至将 Node 已经存在的 Pod 驱逐出去
每个污点的组成如下:
key=value:effect
每个污点有一个 key 和 value 作为污点的标签,其中 value 可以为空,effect 是指污点的作用。当前 effect 支持如下三个选项:
注意这里的key和value,可以随意指定,后面你在修改的时候,也要保证一样即可
# 设置污点
kubectl taint nodes pod-name key1=value1:NoSchedule
# 节点说明中,查找 Taints 字段
kubectl describe pod pod-name
# 去除污点 去除的时候,只要在之前的污点后面加上一个减号(-)即可
kubectl taint nodes pod-name key1:NoSchedule-
现在有两个pod运行在k8s-node01上面,我们把node01打上污点,并设置effect为NoExecute,注意这里的key和value,可以随意指定,后面你在修改的时候,也要保证一样即可
我们再看,驱逐之后,没有pod了,因为pod是自主式的,不会重新建立重启等等,而如果我们创建是控制器的pod,如果是deploment,则会在node02上面再创建pod,因为它要维持它的副本数,这个可以看前面的控制器章节文章,说的很清楚了。
那我们再给k8s-node02也打上污点,即使我们现在再创建一个pod,它也不会启动了,因为现在三个节点都有污点了,master01本身就有污点,node01(前面刚刚打过污点),node02(现在打了污点)
设置了污点的 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"
1、当不指定 key 值时,表示容忍所有的污点 key
tolerations:
- operator: "Exists"
2、当不指定 effect 值时,表示容忍所有的污点作用
tolerations:
- key: "key"
operator: "Exists"
3、有多个 Master 存在时,防止资源浪费,可以如下设置
最好不在master节点中运行,除非其它节点实在压力过大。
kubectl taint nodes k8s-master02 node-role.kubernetes.io/master=:PreferNoSchedule
创建可以容忍污点的pod,表示 pod-3可以容忍污点 key1=value1:NoSchedule
,并在驱逐的时候,继续保持运行3600秒后在被驱除。
最后你就会发现它可以允许在一些污点为 key1=value1:NoSchedule
的节点上面了。
Pod.spec.nodeName
将 Pod 直接调度到指定的 Node 节点上,会跳过 Scheduler 的调度策略,该匹配规则是强制匹配
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: myweb
spec:
replicas: 7
template:
metadata:
labels:
app: myweb
spec:
nodeName: k8s-node01 # 指定当前Deployment 运行在那个节点上面
containers:
- name: myweb
image: hub.atguigu.com/library/myapp:v1
ports:
- containerPort: 80
我们指定的运行的节点为node01,而且运行了7个副本书,创建
如果按照正常情况,这个7个副本数会分散一部分到node02上面,因为要保证调度策略负载等等,结果,全都是在k8s-node01上面。
Pod.spec.nodeSelector:通过 kubernetes 的 label-selector 机制选择节点,由调度器调度策略匹配 label,而后调度 Pod 到目标节点,该匹配规则属于强制约束。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: myweb
spec:
replicas: 2
template:
metadata:
labels:
app: myweb11111
spec:
nodeSelector:
disk: ssd # 运行在有标签为 disk=ssd 的节点上面
containers:
- name: myweb11111
image: harbor/tomcat:8.5-jre8
ports:
- containerPort: 80
我们指定标签,使其运行在有标签为 disk=ssd 的节点上面,如下,一开始我们发现没有运行成功,一直处于pending,因为我们没有节点有这个标签
后面我们手动为节点node01增加这个标签,结果就可以看到运行起来了,并运行在k8s-node01上面
kubectl label node ks8-node01 disk=ssd
而此时,我们修改其副本数为8个,并为node02也设置相同的标签,此时scuedule就会将8个pod散列的分配到node01和node02上面