kubernetes学习(九)调度器
1、调度器概念
在kubernetes中,调度是指将Pod放在合适的节点上,以便节点上的kubelet能够运行这些Pod。并且可以自己开发调度器替代它使用,但是这么多年了依然存在说明它是最好的
Schedule是作为单独的程序运行的,启动后会一直监听API Server,获取‘‘PodSpec.NodeName’为空的Pod,调度器会为每个 Pod 创建一个 Binding 对象,记录该 Pod 应该运行在哪个节点上。但在做出这个决策前,调度器需要综合多个因素:
-
公平调度
-
资源高效利用
-
QoS(Quality of Service,服务质量)
-
affinity 和 anti-affinity
-
数据本地化(data locality)
-
内部负载干扰(inter-workload interference)
-
deadlines(Pod 生命周期中的时间限制机制)
自定义调度器
这个实验是更加方便理解调度器的一个过程,不指定的话默认用内置调度器,如下图

apiVersion: apps/v1
kind: Deployment
metadata:
name: custom-scheduler
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
schedulerName: custom-scheduler
containers:
- name: contaienr
image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/busybox:1.28
imagePullPolicy: IfNotPresent
command:
- "sleep"
- "3600"

创建后可以看到pod是pending状态,也就是待处理。因为Pod中指定的scheduler是自定义的,还有没进行自定义调度器的创建,无法对pod进行调度,所以紧接着进行scheduler创建
先创建一个代理服务,用于调度器访问API使用,
kubectl proxy --port=8001
#这个命令的作用是在本地启动一个代理服务,可以通过 localhost:8001 访问API Server,这个服务默认只监听本地主机(127.0.0.1),不会公开给外部访问。如果需要其他主机访问,使用 --address=0.0.0.0。
#kubectl proxy 适合开发和调试环境,在生产环境中访问API Server,通常通过 RBAC 和网络策略实现更安全的方式。说了一大串,通俗的说就是测试用这个方式


用curl进行代理访问就会看到apiserver暴露的所有的接口,再用shell写个自定义调度器脚本,脚本模拟调度器的分配
#!/bin/bash
SERVER='localhost:8001'
while true;
do
for PODNAME in $(kubectl --server $SERVER get pods -o json | jq '.items[] | select(.spec.schedulerName == "my-scheduler") | select(.spec.nodeName == null) | .metadata.name' | tr -d '"')
do
NODES=($(kubectl --server $SERVER get nodes -o json | jq '.items[].metadata.name' | tr -d '"'))
NUMNODES=${#NODES[@]}
CHOSEN=${NODES[$[ $RANDOM % $NUMNODES ]]}
curl --header "Content-Type:application/json" \
--request POST \
--data '{"apiVersion":"v1","kind":"Binding","metadata":{"name":"'$PODNAME'"},"target":{"apiVersion":"v1","kind":"Node","name":"'$CHOSEN'"}}' \
http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/
echo "Assigned $PODNAME to $CHOSEN"
done
sleep 1
done


可以看到脚本的执行结果,和是否调度成功
调度过程
调度分为几个部分:首先是过滤掉不满足条件的节点,整个过程成为“预选”;然后对通过预选的节点按照优先级排序,这是“优选”;最后从优选中在选择优先级最高的节点。如果中间任何一步骤有错误,就直接返回付错
预选和优选都是有算法的,并且算法随着k8s版本的更新在不停的进行迭代。如果‘预选’中没有合适的节点,Pod就会一直在“pending”状态,不断重试调度。经过预选后,如果有多个节点满足条件,就继续“优选”。
哪个节点满足调度算法的越多,就越容易被分配到哪个节点参考下图

2、亲和性
Kubernetes 的调度器亲和性主要分三种类型
- Node Affinity(节点亲和性),不支持topologyKey
- Pod Affinity(Pod亲和性)
- Pod AntiAffinity(Pod反亲和性)
并且每种类型都有两种策略
- preferredDuringSchedulingIgnoredDuringExecution:软性策略
- 优先执行调度,没办法完成就算了
- requiredDuringSchedulingIgnoredDuringExecution:硬性策略
- 必须执行调度
它们用于控制 Pod 的调度规则,使 Pod 能够根据特定的条件运行在满足要求的节点上或与其他 Pod 有特定关系。意思就是概念中提到的,将Pod自定义分配node节点。
节点亲和性
pod.spec.affinity.nodeAffinity
软性策略实验1
apiVersion: v1 # API版本,指定Kubernetes资源的版本,此处为v1
kind: Pod # 资源类型,此处是Pod
metadata: # Pod的元数据部分
name: node-preferredDuringSchedulingIgnoredDuringExecution # Pod的名称
labels: # Pod的标签部分
app: node-preferredDuringSchedulingIgnoredDuringExecution # 自定义标签,用于选择或分类Pod
spec: # Pod的规格定义
containers: # 定义Pod中的容器列表
- name: node-affinity-preferred-pod # 容器的名称
image: nginx # 容器的镜像,此处为nginx
imagePullPolicy: IfNotPresent # 镜像拉取策略,当本地不存在时才拉取镜像
affinity: # Pod的亲和性规则
nodeAffinity: # 节点亲和性规则
preferredDuringSchedulingIgnoredDuringExecution: # 首选的调度规则,在调度时生效,忽略执行时的约束
- weight: 1 # 权重值,用于表示优先级,范围为1到100
preference: # 节点选择的偏好条件
matchExpressions: # 定义节点选择条件的表达式
- key: domain # 节点的标签键,此处为"domain"
operator: In # 操作符,表示值在给定的列表中
values: # 列表,表示标签值的匹配项
- qutamade # 匹配的标签值,此处为"qutamade"
看匹配运算符,是用来匹配标签的。In的意思是在列表中,只要能匹配到标签值为qutamde,就把Pod分配到这个节点。来看下node节点标签

node节点标签没有qutamade这个键值,但是Pod还是running了。由此得出软性策略就是:不行就算了

软性策略实验2
来个更深层次的验证,让Pod在软策略的前提下在两个节点跳动,看看是不是在满足不了亲和性的情况下,软策略就不考虑Pod的分配要求
while true ;
do
kubectl delete -f 2.pod.yaml
kubectl create -f 2.pod.yaml
kubectl get po -o wide
sleep 1
done
#写个死循环,不停的删除创建,查看pod分配结果
执行循环可能会出现pod一直分配到一个节点的情况,比如下面这样。这是因为node1节点在系统层面比如cpu、memory等各种资源,k8s的调度算法认为node1节点权重比node2高,所以才会一直在一个节点,可以试着给权重高的节点加点压力,比如占用资源使用

加码占用资源
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: node1
name: node1
spec:
replicas: 10
selector:
matchLabels:
app: node1
template:
metadata:
labels:
app: node1
spec:
nodeName: k8s-node1 #将Pod调度到特定节点
containers:
- image: nginx
name: nginx
在试下循环,来回跳动

软性策略实验3
前面验证了软性策略含义:“不行就算了”,现在要实验如果行!Pod会怎么分配
给node节点先加标签
kubectl label node k8s-node2 domain=qutamade
#这个键值对就是实验1中的键值对

在进行死循环,全部分配在node2上了

硬性策略实验
apiVersion: v1 # API版本,指定资源的版本,此处为v1
kind: Pod # 资源类型,此处是Pod
metadata: # 元数据部分,描述Pod的相关信息
name: node-affinity-required-pod # Pod的名称,必须唯一
labels: # 标签部分,定义该Pod的标识,方便选择和分组
app: node-affinity-required-pod # 自定义的标签键值对
spec: # 规格部分,定义Pod的行为和配置
containers: # 定义Pod内的容器列表
- name: node-affinity-required-pod # 容器名称
image: nginx # 使用的容器镜像,这里是nginx
imagePullPolicy: IfNotPresent # 镜像拉取策略,表示如果本地没有镜像则拉取
affinity: # Pod的亲和性规则,用于节点选择
nodeAffinity: # 节点亲和性规则部分
requiredDuringSchedulingIgnoredDuringExecution: # 强制性规则,调度时必须满足
nodeSelectorTerms: # 节点选择器条件列表
- matchExpressions: # 条件表达式列表
- key: kubernetes.io/hostname # 标签键,此处是节点的主机名
operator: In # 操作符,表示值必须在给定列表中
values: # 值列表,表示匹配的标签值
- k8s-node3 # 匹配的节点名称为"k8s-node3"

通过上图可以看到pod一直处于pending,因为运算符选择的标签是node3,而集群中只有一个master和node1、node2。修改下键值吧,然后给node随便一个节点加标签
key :disktype
values:
- ssd

调度成功

Pod亲和性
pod.spec.affinity.podAffinity
Pod的匹配流程是先匹配符合要求的Pod,然后再匹配看哪些节点符合拓扑域要求,比节点亲和性稍微复杂一丢丢
- topologykey(拓扑域):也是一种标签,但是匹配的是标签的key。假设朋友在北京大兴,我要去找朋友玩,是不是要先去北京,然后再去昌平?这里北京和大兴就是一个键值对。而北京就是拓扑域
软性策略实验1
apiVersion: v1 # 指定 API 的版本为 v1。
kind: Pod # 声明 Kubernetes 资源的类型为 Pod。
metadata:
name: pod-aff-prefer # 定义 Pod 的名称。
labels: # 定义 Pod 的标签。
app: pod-aff # 添加标签键值对,用于标识该 Pod。
spec:
containers:
- name: myapp # 容器的名称。
image: nginx # 使用的镜像为 nginx。
imagePullPolicy: IfNotPresent # 镜像拉取策略,若本地已存在镜像则不重新拉取。
affinity: # 定义亲和性规则。
podAffinity: # Pod 亲和性规则,要求与特定 Pod 靠近。
preferredDuringSchedulingIgnoredDuringExecution: # 定义优先级规则,非强制。
- weight: 1 # 亲和性的权重,值越大优先级越高。
podAffinityTerm: # 定义亲和性的具体匹配条件。
labelSelector: # 标签选择器,用于匹配目标 Pod。
matchExpressions: # 表达式形式匹配标签。
- key: app # 目标 Pod 必须包含的标签键。
operator: In # 匹配运算符,表示标签值在指定值列表中。
values: # 标签的值列表。
- pod-1 # 匹配的目标 Pod 的标签值为 pod-1。
topologyKey: kubernetes.io/hostname # 拓扑域键,表示亲和性作用的拓扑域。
分析下流程,创建对象后要先去各个节点寻找标签为pod-1的Pod,如果有一个或多个节点符合要求再继续往下看拓扑域。如果拓扑域有一个或多个符合要求,在根据调度器算法进行优选

看上图,框选就是拓扑域。再看pod,有且只有一个刚创建的Pod,并没有符合代码中app=pod-1标签的pod在任何节点,但是pod还是运行了
因为使用的是软策略,不行就算了
软性策略实验2
紧接上面的实验,随便新创建一个pod
apiVersion: v1
kind: Pod
metadata:
name: test-pod
labels:
app: pod-1
spec:
containers:
- name: test-container
image: nginx
imagePullPolicy: IfNotPresent


创建对象后看标签,如上图,现在pod标签和拓扑域都符合实验1代码的要求了,写个死循环创建软策略pod试下,看看节点分配

它会一直被调度到node1节点
硬性策略实验
apiVersion: v1 # 定义 API 版本,v1 表示核心组中的资源。
kind: Pod # 资源类型为 Pod。
metadata: # 元数据部分,定义 Pod 的基本信息。
name: pod-aff-prefer # Pod 的名称,必须唯一。
labels: # 给 Pod 设置的标签,用于分类或选择。
app: pod-aff # 标签的键值对,key 是 "app",value 是 "pod-aff"。
spec: # Pod 的规格定义。
containers: # 定义 Pod 中包含的容器列表。
- name: myapp # 容器的名称。
image: nginx # 使用的镜像,这里是官方 Nginx 镜像。
imagePullPolicy: IfNotPresent # 拉取镜像的策略,如果本地没有才拉取。
affinity: # 定义调度的亲和性规则。
podAffinity: # 定义 Pod 的亲和性规则。
requiredDuringSchedulingIgnoredDuringExecution: # 调度时必须满足的亲和性规则。
- labelSelector: # 用于匹配目标 Pod 的标签。
matchExpressions: # 匹配表达式,用于更复杂的匹配逻辑。
- key: app # 匹配的标签键。
operator: In # 操作符,表示标签值必须在下面的值列表中。
values: # 标签值列表。
- pod-1 # 要匹配的值,这里是 "pod-1"。
topologyKey: kubernetes.io/hostname # 拓扑键,表示调度在相同主机名的节点上。
把之前实验的pod都删除然后创建

可以看到pod处于pending状态,因为有没有符合它要求的标签pod-1,随便创建一个pod,让它分配到node2节点,然后再看
apiVersion: v1
kind: Pod
metadata:
name: test-pod
labels:
app: pod-1
spec:
nodeName: k8s-node2
containers:
- name: test-container
image: nginx
imagePullPolicy: IfNotPresent

成功调度
Pod反亲和性
pod.spec.affinity.podAntiAffinity
这个就很简单易懂了,和pod亲和性相反嘛,比如node1节点有个标签pod-1的pod,那我就不和它在一起
软性策略实验1
先随便创建个pod,标签为pod-2
apiVersion: v1
kind: Pod
metadata:
name: test-pod
labels:
app: pod-2
spec:
containers:
- name: test-container
image: nginx
imagePullPolicy: IfNotPresent
然后在创建测试pod
apiVersion: v1 # 定义 API 版本,v1 表示核心组中的资源。
kind: Pod # 资源类型为 Pod。
metadata:
name: pod-antiaff-prefer # Pod 的名称,必须唯一。
labels:
app: pod-aff # 标签的键值对,key 是 "app",value 是 "pod-aff"。
spec:
containers:
- name: myapp # 容器名称。
image: nginx # 容器镜像,使用官方的 Nginx 镜像。
imagePullPolicy: IfNotPresent # 拉取策略:仅当本地没有镜像时才拉取。
affinity:
podAntiAffinity: # 定义 Pod 的反亲和性规则。
preferredDuringSchedulingIgnoredDuringExecution: # 定义软调度规则。
- weight: 1 # 软规则的权重,值在 1-100 之间。
podAffinityTerm: # 定义亲和条件。
labelSelector: # 用于匹配 Pod 标签的选择器。
matchExpressions:
- key: app # 匹配的标签键。
operator: In # 操作符,表示标签值必须包含在列表中。
values:
- pod-2 # 要匹配的值,这里是 "pod-2"。
topologyKey: kubernetes.io/hostname # 拓扑键,表示调度到不同主机节点上。
看下图,不在一个节点

软性策略实验2
接着上面的实验,在两个节点都创建标签为pod-2的pod,看看结果

没办法了,只能选一个节点调度。(ps:和讨厌的人在一起是真的烦)
硬性策略实验
apiVersion: v1
kind: Pod
metadata:
name: pod-antiaff-prefer
labels:
app: pod-aff
spec:
containers:
- name: myapp
image: nginx
imagePullPolicy: IfNotPresent
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- pod-2
topologyKey: kubernetes.io/hostname
看下图,测试pod处于pending状态,两个节点都有讨厌的人。我选择狗带

3、污点和容忍
kubectl taint node nodeName key:value:effect
#添加污点
kubectl taint node nodeName key:value:effect-
kubectl taint node nodeName key=:effect-
#在污点后加“-”就是删除污点了,value为空的话key后面要加“=”
也可以用edit,随便啦,怎么方便怎么来
节点亲和性 是Pod的一种属性,它使 Pod 被吸引到一类特定的节点(这可能出于一种偏好,也可能是硬性要求)。
污点(Taint) 则相反——它使节点能够排斥一类特定的 Pod。用来避免Pod被分配到不适合的节点上。每个节点上都可以应用一个或多个taint,这表示对于那些不能容忍这些taint的pod,是不会被该节点所接受的。
容忍度(Toleration) 是应用于 Pod 上的。容忍度允许调度器调度带有对应污点的 Pod。 容忍度允许调度但并不保证调度:作为其功能的一部分, 调度器也会评估其他参数
污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod, 是不会被该节点接受的
举个例子
- A去相亲,缺点是打呼噜。B看了介绍说自己睡觉深,可以容忍。但是!可以容忍是一回事,会不会在一起是另一回事,这就是污点和容忍
污点
每个污点有一个key和value作为污点的标签,其中value可以为空
key=value:effect #value不为空
key:effect #value为空
effect描述污点的作用。当前taint effect支持如下三个选项
- NoSchedule:表示k8s不会将Pod调度到具有该污点的Node上
- PreferNoSchedule:表示k8s将尽量避免将Pod调度到具有该污点的Node上
- NoExecute:表示k8s不会将Pod调度到具有该污点的Node上,同时会将Node上已经存在的Pod驱逐出去
污点实验
前面做过的所有实验中,master节点上从来没有被调度过pod,这是因为master节点上的污点存在。如下图所示,它的value就为空
关于怎么查这个信息不用再说了吧,-o yaml或者describe都行
现在将这个污点删掉
kubectl taint node k8s-master node-role.kubernetes.io/control-plane=:NoSchedule-
写个DaemonSet控制器,这个控制器的特性是每个节点都会分配一个Pod
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: test-contorller
spec:
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: test-container
image: nginx
imagePullPolicy: IfNotPresent
如上图三个节点都被分配了Pod
容忍
对于Pod的容忍来说,就在清单中设置比较方便了,设置方式如下
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
#允许 Pod 容忍一个特定的污点,从而可以调度到带有该污点的节点上,要和
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoExecute"
tolerationSeconds: 3600
#允许 Pod 容忍一个带有 NoExecute 效果的污点(Taint),并设置了一个时限,表示如果在这个时间内没有移除该污点,Pod 会被驱逐
特殊类型
tolerations:
- key: "key"
operator: "Exists"
effect: "NoSchedule"
#当不指定污点value时,兼容所有value。只要key存在,策略是NoSchedule,都可以容忍
tolerations:
- operator: "Exists"
#污点通通容忍
tolerations:
- key: "key"
operator: "Exists"
#只要key存在,不管什么污点效果,都可以容忍
在有多个master存在时,防止资源浪费,可以如下设置
kubectl taint nodes nodeName node-role.kubernetes.io/master=:PreferNoSchedule
#尽可能不被调度,但是在node节点压力大撑不住时候可以调度
容忍实验
将刚才master节点删除的污点在添加进来
kubectl taint node k8s-master node-role.kubernetes.io/control-plane:NoSchedule
删除刚才的ds控制器,在重新创建
- 如果 master 节点上的污点是
NoSchedule类型,Pod 不会因为污点的还原而被删除。 - 如果污点是
NoExecute类型,并且 DaemonSet Pod 没有相应的容忍度,那么 Pod 会被驱逐

如上图,这是正常情况,现在在pod中添加容忍度
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: test-contorller
spec:
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: test-container
image: nginx
imagePullPolicy: IfNotPresent
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
如下图,又在master分配Pod了

4、固定节点调度
前面的的亲和性啦、污点啦、容忍啦,相对于固定节点调度来说都太过于花哨。看看固定节点调度如何简单朴实
指定节点调度
pod.spec.nodeName
将pod直接调度到指定的Node节点上,跳过Scheduler的调度策略,该匹配规则是强制匹配,不受污点和亲和性的干扰,来试下
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-test
spec:
replicas: 10
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
nodeName: k8s-master
containers:
- name: myweb
image: nginx
imagePullPolicy: IfNotPresent

指定节点标签调度
pod.spec.nodeSelector
通过k8s的label-selector机制选择节点,由调度器调度策略匹配label,而后调度Pod到目标节点,该匹配规则是强制匹配
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-test
spec:
replicas: 10
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
nodeSelector:
test:nodeselector
containers:
- name: myweb
image: nginx
imagePullPolicy: IfNotPresent

出问题了,为啥?因为所有节点都没有这个标签,尝试在master节点打标签看结果

如上图,master节点打了标签还是pending,又为啥呢?
因为它和nodeName关键字不一样,nodeselector关键字虽然提供了约束,但它本质上还是由Scheduler来进行调度的。既然还是它调度就绕不开污点的策略,mater的污点效果是NoSchedule
在尝试在node1节点打标签,成功。因为node节点没有NoSchedule污点效果

更多推荐
所有评论(0)