前面已经说了调度的亲和性,其实都比较含蓄,比如软硬亲和性,污点和容忍。
固定节点就比较粗暴了,可以通过节点名称去选择,也可以通过节点的标签去选择,固定在某个节点上运行。这就是所谓固定节点的调度。
固定节点调度有两种方式,一种是使用Pod.spec.nodeName节点名称去选择,或者使用节点的标签去选择。通过这两种方式将pod固定在节点上运行。
Kubernetes Pod调度说明
简介
Scheduler 是 Kubernetes 的调度器,主要任务是把定义的Pod分配到集群的节点上,听起来非常简单,但要考虑需要方面的问题:
-
公平:如何保证每个节点都能被分配到资源
-
资源高效利用:集群所有资源最大化被使用
-
效率:调度性能要好,能够尽快的对大批量的Pod完成调度工作
-
灵活:允许用户根据自己的需求控制调度的流程
Scheduler 是作为单独的服务运行的,启动之后会一直监听API Server,获取 podSpec.NodeName为空的Pod,对每个Pod都会创建一个binding,表明该Pod应该放在哪个节点上。
调度过程
调度流程:
- 首先过滤掉不满足条件的节点,这个过程称为predicate
- 然后对通过的节点按照优先级的顺序,这个是priority
- 最后从中选择优先级最高的节点。如果中间有任何一步报错,则直接返回错误信息。(对于调度过程分为两部分,一部分是预选,一部分是优选)
Predicate有一系列的算法可以使用:
-
PodFitsResources:节点上剩余的资源是否大于 Pod 请求的资源
-
PodFitsHost:如果Pod指定了nodeName,检查节点名称是否和nodeName匹配
-
PodFitsHostPort:节点上已经使用的port是否和Pod申请的port冲突
-
PodSelectorMatches:过滤和Pod指定的 label 不匹配的节点
-
NoDiskConflict:已经 mount 的 volume 和 Pod 指定的volume不冲突,除非他们都是只读
如果在predicate过程中没有适合的节点,Pod会一直处于Pending状态,不断重新调度,直到有节点满足条件,经过这个步骤,如果多个节点满足条件,就会进入priority过程:按照优先级大小对节点排序,优先级由一系列键值对组成,键是该优先级的名称,值是它的权重,这些优先级选项包括:
-
LeastRequestedPriority:通过计算CPU和Memory的使用率来决定权重,使用率越低权重越高,换句话说,这个优先级倾向于资源使用率低的节点
-
BalanceResourceAllocation:节点上CPU和Memory使用率非常及接近,权重就越高,这个要和上边的一起使用,不可单独使用
-
ImageLocalityPriority:倾向于已经要使用镜像的节点,镜像的总大小值越大,权重越高
通过算法对所有的优先级项目和权重进行计算,得出最终的结果
自定义调度器
除了Kubernetes自带的调度器,也可以编写自己的调度器,通过spec.schedulername参数指定调度器的名字,可以为Pod选择某个调度器进行调度,比如下边的Pod选择my-scheduler进行调度,而不是默认的default-scheduler。
apiVersion: v1
kind: Pod
metadata:
name: scheduler-test
labels:
name: example-scheduler
spec:
schedulername: my-scheduler
containers:
- name: Pod-test
image: nginx:v1
指定调度节点
I、Pod.spec.nodeName 将 Pod 直接调度到指定的 Node 节点上,会跳过 Scheduler 的调度策略,该匹配规则是强制匹配
[root@k8s-master ~]# cat test1.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: default
labels:
app: busybox
spec:
replicas: 8
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
nodeName: k8s-node1
containers:
- name: buxybox
image: busybox
ports:
- containerPort: 22
command: ["/bin/sh","-c","sleep 3600"]
[root@k8s-master ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-544cb6f798-5fjdh 1/1 Running 0 88s 10.244.1.9 k8s-node1 <none> <none>
myapp-544cb6f798-7wfjz 1/1 Running 0 88s 10.244.1.10 k8s-node1 <none> <none>
myapp-544cb6f798-chlxz 1/1 Running 0 88s 10.244.1.13 k8s-node1 <none> <none>
myapp-544cb6f798-cl8x8 1/1 Running 0 7m12s 10.244.1.7 k8s-node1 <none> <none>
myapp-544cb6f798-gdqbg 1/1 Running 0 88s 10.244.1.12 k8s-node1 <none> <none>
myapp-544cb6f798-q54fk 1/1 Running 0 88s 10.244.1.8 k8s-node1 <none> <none>
myapp-544cb6f798-tdhxz 1/1 Running 0 88s 10.244.1.11 k8s-node1 <none> <none>
myapp-544cb6f798-tq448 1/1 Running 0 7m12s 10.244.1.6 k8s-node1 <none> <none>
可以看到全部在node1上面,公平调度没有发挥作用,不经过调度器,即使节点上有污点也忽略,因为不经过调度器。
前面亲和性,nodeselector,污点都是经过调度器。之前配置相关调度的属性它都不起作用了,如果调度器出现故障,希望pod可以快速部署到某个节点,可以使用nodename。
nodeSelector
II、Pod.spec.nodeSelector:通过 kubernetes 的 label-selector 机制选择节点,由调度器调度策略匹配 label,而后调度 Pod 到目标节点,该匹配规则属于强制约束
nodeSelector:用于将Pod调度到匹配Label的Node上,如果没有匹配的标签会调度失败。
作用:
• 约束Pod到特定的节点运行
• 完全匹配节点标签
应用场景:
• 专用节点:根据业务线将Node分组管理
• 配备特殊硬件:部分Node配有SSD硬盘、GPU
默认配置下,Scheduler 会将 Pod 调度到所有可用的 Node。不过有些情况我们希望将 Pod 部署到指定的 Node,比如将有大量磁盘 I/O 的 Pod 部署到配置了 SSD 的 Node,或者 Pod 需要 GPU,需要运行在配置了 GPU 的节点上。
Kubernetes 是通过 label 来实现这个功能的。
label 是 key-value 对,各种资源都可以设置 label,灵活添加各种自定义属性。比如执行如下命令标注 k8s-node2 是配置了 SSD 的节点。
[root@k8s-master ~]# kubectl label node k8s-node2 disktype=ssd
node/k8s-node2 labeled
然后通过 kubectl get node --show-labels
查看节点的 label。
[root@k8s-master ~]# kubectl get node --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8s-master Ready <none> 51d v1.18.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master,kubernetes.io/os=linux
k8s-node1 Ready <none> 50d v1.18.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node1,kubernetes.io/os=linux
k8s-node2 Ready <none> 50d v1.18.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node2,kubernetes.io/os=linux
disktype=ssd
已经成功添加到 k8s-node2,除了 disktype
,Node 还有几个 Kubernetes 自己维护的 label。
有了 disktype
这个自定义 label,接下来就可以指定将 Pod 部署到 k8s-node2。编辑 nginx.yml:
[root@k8s-master ~]# cat test2.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: default
labels:
app: nginx
spec:
replicas: 8
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
nodeSelector:
disktype: ssd
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
[root@k8s-master ~]# kubectl apply -f test2.yml
deployment.apps/myapp created
在 Pod 模板的 spec
里通过 nodeSelector
指定将此 Pod 部署到具有 label disktype=ssd
的 Node 上。
部署 Deployment 并查看 Pod 的运行节点:
[root@k8s-master ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-9bf5d74f5-4jlhr 1/1 Running 0 112s 10.244.2.12 k8s-node2 <none> <none>
myapp-9bf5d74f5-5b94h 1/1 Running 0 2m39s 10.244.2.9 k8s-node2 <none> <none>
myapp-9bf5d74f5-6wvrv 1/1 Running 0 3m 10.244.2.5 k8s-node2 <none> <none>
myapp-9bf5d74f5-7tbpk 1/1 Running 0 3m 10.244.2.6 k8s-node2 <none> <none>
myapp-9bf5d74f5-l5h9w 1/1 Running 0 3m 10.244.2.7 k8s-node2 <none> <none>
myapp-9bf5d74f5-n8zs8 1/1 Running 0 2m23s 10.244.2.10 k8s-node2 <none> <none>
myapp-9bf5d74f5-sf8j6 1/1 Running 0 3m 10.244.2.8 k8s-node2 <none> <none>
myapp-9bf5d74f5-vs4v6 1/1 Running 0 2m8s 10.244.2.11 k8s-node2 <none> <none>
全部 8个副本都运行在 k8s-node2上,符合我们的预期。
要删除 label disktype
,执行如下命令:
[root@k8s-master ~]# kubectl label node k8s-node2 disktype-
node/k8s-node2 labeled
[root@k8s-master ~]# kubectl get node k8s-node2 --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8s-node2 Ready <none> 51d v1.18.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node2,kubernetes.io/os=linux
-
即删除。
[root@k8s-master ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-9bf5d74f5-4jlhr 1/1 Running 0 5m23s 10.244.2.12 k8s-node2 <none> <none>
myapp-9bf5d74f5-5b94h 1/1 Running 0 6m10s 10.244.2.9 k8s-node2 <none> <none>
myapp-9bf5d74f5-6wvrv 1/1 Running 0 6m31s 10.244.2.5 k8s-node2 <none> <none>
myapp-9bf5d74f5-7tbpk 1/1 Running 0 6m31s 10.244.2.6 k8s-node2 <none> <none>
myapp-9bf5d74f5-l5h9w 1/1 Running 0 6m31s 10.244.2.7 k8s-node2 <none> <none>
myapp-9bf5d74f5-n8zs8 1/1 Running 0 5m54s 10.244.2.10 k8s-node2 <none> <none>
myapp-9bf5d74f5-sf8j6 1/1 Running 0 6m31s 10.244.2.8 k8s-node2 <none> <none>
myapp-9bf5d74f5-vs4v6 1/1 Running 0 5m39s 10.244.2.11 k8s-node2 <none> <none>
不过此时 Pod 并不会重新部署,依然在 k8s-node2 上运行。除非在 nginx.yml 中删除 nodeSelector
设置,然后通过 kubectl apply
重新部署。Kubernetes 会删除之前的 Pod 并调度和运行新的 Pod。
如果多个节点都有该标签会选择一个最合适的,因为这几个节点都可以匹配,根据其调度算法选择最合适的匹配。如果没有集群当中没有该标签,那么就无法调度,会一直处于pending。
[root@k8s-master ~]# cat pod-nodeselector.yml
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeselector2
spec:
nodeSelector:
gpu: "nvidia"
containers:
- name: web
image: nginx
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 36s 0/3 nodes are available: 3 node(s) didn't match node selector.
Warning FailedScheduling 36s 0/3 nodes are available: 3 node(s) didn't match node selector.