kubernetes学习(九)调度器
1、调度器概念在kubernetes中调度是指将Pod放在合适的节点上以便节点上的kubelet能够运行这些Pod。并且可以自己开发调度器替代它使用但是这么多年了依然存在说明它是最好的Schedule是作为单独的程序运行的启动后会一直监听API Server获取‘‘PodSpec.NodeName’为空的Pod调度器会为每个 Pod 创建一个 Binding 对象记录该 Pod 应该运行在哪个节点上。但在做出这个决策前调度器需要综合多个因素公平调度资源高效利用QoSQuality of Service服务质量affinity 和 anti-affinity数据本地化data locality内部负载干扰inter-workload interferencedeadlinesPod 生命周期中的时间限制机制自定义调度器这个实验是更加方便理解调度器的一个过程不指定的话默认用内置调度器如下图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 --port8001 #这个命令的作用是在本地启动一个代理服务可以通过 localhost:8001 访问API Server这个服务默认只监听本地主机127.0.0.1不会公开给外部访问。如果需要其他主机访问,使用 --address0.0.0.0。 #kubectl proxy 适合开发和调试环境在生产环境中访问API Server通常通过 RBAC 和网络策略实现更安全的方式。说了一大串通俗的说就是测试用这个方式用curl进行代理访问就会看到apiserver暴露的所有的接口再用shell写个自定义调度器脚本脚本模拟调度器的分配#!/bin/bash SERVERlocalhost: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节点亲和性不支持topologyKeyPod AffinityPod亲和性Pod AntiAffinityPod反亲和性并且每种类型都有两种策略preferredDuringSchedulingIgnoredDuringExecution软性策略优先执行调度没办法完成就算了requiredDuringSchedulingIgnoredDuringExecution硬性策略必须执行调度它们用于控制 Pod 的调度规则使 Pod 能够根据特定的条件运行在满足要求的节点上或与其他 Pod 有特定关系。意思就是概念中提到的将Pod自定义分配node节点。节点亲和性pod.spec.affinity.nodeAffinity软性策略实验1apiVersion: 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 domainqutamade #这个键值对就是实验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.podAffinityPod的匹配流程是先匹配符合要求的Pod然后再匹配看哪些节点符合拓扑域要求比节点亲和性稍微复杂一丢丢topologykey拓扑域也是一种标签但是匹配的是标签的key。假设朋友在北京大兴我要去找朋友玩是不是要先去北京然后再去昌平这里北京和大兴就是一个键值对。而北京就是拓扑域软性策略实验1apiVersion: 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并没有符合代码中apppod-1标签的pod在任何节点但是pod还是运行了因为使用的是软策略不行就算了软性策略实验2紧接上面的实验随便新创建一个podapiVersion: 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 是 appvalue 是 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-2apiVersion: v1 kind: Pod metadata: name: test-pod labels: app: pod-2 spec: containers: - name: test-container image: nginx imagePullPolicy: IfNotPresent然后在创建测试podapiVersion: v1 # 定义 API 版本v1 表示核心组中的资源。 kind: Pod # 资源类型为 Pod。 metadata: name: pod-antiaff-prefer # Pod 的名称必须唯一。 labels: app: pod-aff # 标签的键值对key 是 appvalue 是 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可以为空keyvalueeffect #value不为空 keyeffect #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控制器这个控制器的特性是每个节点都会分配一个PodapiVersion: 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: testnodeselector containers: - name: myweb image: nginx imagePullPolicy: IfNotPresent出问题了为啥因为所有节点都没有这个标签尝试在master节点打标签看结果如上图master节点打了标签还是pending又为啥呢因为它和nodeName关键字不一样nodeselector关键字虽然提供了约束但它本质上还是由Scheduler来进行调度的。既然还是它调度就绕不开污点的策略mater的污点效果是NoSchedule在尝试在node1节点打标签成功。因为node节点没有NoSchedule污点效果