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污点效果