主讲内容:docker/kubernetes 云原生技术,大数据架构,分布式微服务,自动化测试、运维。
视频地址:ke.qq.com/course/419718
全栈工程师开发手册 (作者:栾鹏)
架构系列文章
scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上。听起来非常简单,但有很多要考虑的问题:
sheduler 是作为单独的程序运行的,启动之后会一直坚挺 API Server,获取 PodSpec.NodeName
为空的 pod,对每个 pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上。
Scheduler 的参数是相对因为比较少的,因为它做的事情比较清晰,而且需要配置的地方比较少。下面是常见的参数列表和解释(不同版本 kubernetes 提供的参数可能会有出入,请以实际为准):
参数 | 意思 | 默认值 |
---|---|---|
–address | 监听地址 | “0.0.0.0” |
–port | 调度器监听的端口 | 10251 |
–algorithm-provider | 提供调度算法的对象 | “DefaultProvider” |
–master | kubernetes API Server 的 HTTP API 地址 | |
–profiling | 是否开启 profiling,开启后可以在 host:port/debug/pprof 访问 profile 信息 |
true |
–scheduler-name | 调度器名称,用来唯一确定该调度器 | “default-scheduler” |
–kube-api-burst | 和 API Server 通信的时候最大 burst 值 | 100 |
–kube-api-qps | 和 API Server 通信的时候 QPS 值 | 50 |
–log_dir | 日志保存的目录 | |
–policy-config-file | json 配置文件,用来指定调度器的 filters 和 priorities,可以参考 examples/scheduler-policy-config.json 文件 |
scheduler 的安装比较简单,它重要的参数就是 master 的 ip 地址,也是唯一必须指定的地址:
/opt/kubernetes/bin/kube-scheduler \
--logtostderr=false \
--v=4 \
--log_dir=/var/log/kubernetes \
--master=172.17.8.100:8080
运行之后,我们可以创建 pod 来测试。前一篇文章我们讲到需要手动指定 pod 要放到哪台 node 上,这次就不需要我们来做了,因为调度系统帮我们自动完成了这个步骤。来看看配置文件:
apiVersion: v1
kind: Pod
metadata:
name: nginx-server
spec:
containers:
- name: nginx-server
image: 172.16.1.41:5000/nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /var/log/nginx
name: nginx-logs
- name: log-output
image: 172.16.1.41:5000/busybox
command:
- bin/sh
args: [-c, 'tail -f /logdir/access.log']
volumeMounts:
- mountPath: /logdir
name: nginx-logs
volumes:
- name: nginx-logs
emptyDir: {}
这里我们没有指定 pod 的 nodeName
属性,创建看看:
# kubectl create -f nginx-log.yml
等一段时间,可以使用 kubectl describe pod
来查看 pod 的信息,在输出的最后部分的事件列表中,我们可以看到第一条就是调度信息。下面这条记录说明了调度的时间,使用的调度器(default-scheduler
),以及调度的最终结果(172.17.8.101
):
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
11m 11m 1 {default-scheduler } Normal Scheduled Successfully assigned nginx-server to 172.17.8.101
11m 11m 1 {kubelet 172.17.8.101} spec.containers{nginx-server} Normal Pulling pulling image "172.16.1.41:5000/library/nginx"
......
NOTE:这篇文章运行的实体都是 pod,但是实际上 ReplicationController(1.5 之后的版本更名为 ReplicaSet) 在实际更常用,我们会在后面的文章介绍。
调度分为几个部分:首先是过滤掉不满足条件的节点,这个过程称为 predicate
;然后对通过的节点按照优先级排序,这个是 priority
;最后从中选择优先级最高的节点。如果中间任何一步骤有错误,就直接返回错误。
predicate 有一系列的算法可以使用:
PodFitsResources
:节点上剩余的资源是否大于 pod 请求的资源PodFitsHost
:如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配PodFitsHostPorts
:节点上已经使用的 port 是否和 pod 申请的 port 冲突PodSelectorMatches
:过滤掉和 pod 指定的 label 不匹配的节点NoDiskConflict
:已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读如果在 predicate 过程中没有合适的节点,pod 会一直在 pending
状态,不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程:
按照优先级大小对节点排序。
优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。这些优先级选项包括:
LeastRequestedPriority
:通过计算 CPU 和 Memory 的使用率来决定权重,使用率越低权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点BalancedResourceAllocation
:节点上 CPU 和 Memory 使用率越接近,权重越高。这个应该和上面的一起使用,不应该单独使用ImageLocalityPriority
:倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高通过算法对所有的优先级项目和权重进行计算,得出最终的结果。
参考:https://blog.csdn.net/luanpeng825485697/article/details/84900192
使用kubectl taint命令可以给某个Node节点设置污点,Node被设置上污点之后就和Pod之间存在了一种相斥的关系,可以让Node拒绝Pod的调度执行,甚至将Node已经存在的Pod驱逐出去。
每个污点的组成如下:
key=value:effect
每个污点有一个key和value作为污点的标签,其中value可以为空,effect描述污点的作用。当前taint effect支持如下三个选项:
使用kubectl设置和去除污点的命令示例如下:
# 设置污点
kubectl taint nodes node1 key1=value1:NoSchedule
# 去除污点
kubectl taint nodes node1 key1:NoSchedule-
接下来看一个具体的例子,使用kubeadm部署和初始化的Kubernetes集群,master节点被设置了一个node-role.kubernetes.io/master:NoSchedule
的污点,可以使用kubectl describe node
命令查看。这个污点表示默认情况下master节点将不会调度运行Pod,即不运行工作负载。对于使用二进制手动部署的集群设置和移除这个污点的命令如下:
kubectl taint nodes node-role.kubernetes.io/master=:NoSchedule
kubectl taint nodes node-role.kubernetes.io/master:NoSchedule-
注意:kubeadm初始化的Kubernetes集群,master节点也被打上了一个node-role.kubernetes.io/master=的label,标识这个节点的角色为master。给Node设置Label和设置污点是两个不同的操作。设置Label和移除Label的操作命令如下
# 设置Label
kubectl label node node1 node-role.kubernetes.io/master=
# 移除Label
kubectl label node node1 node-role.kubernetes.io/master-
kubectl get node
NAME STATUS ROLES AGE VERSION
node1 Ready master 10d v1.9.8
node2 Ready master 10d v1.9.8
node3 Ready master 10d v1.9.8
node4 Ready 10d v1.9.8
node5 Ready 10d v1.9.8
设置了污点的Node将根据taint的effect:NoSchedule、PreferNoSchedule、NoExecute和Pod之间产生互斥的关系,Pod将在一定程度上不会被调度到Node上。 但我们可以在Pod上设置容忍(Toleration),意思是设置了容忍的Pod将可以容忍污点的存在,可以被调度到存在污点的Node上。
通过在Pod的spec中设置tolerations字段,给Pod设置上容忍点Toleration:
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"
下面看一下在Pod上设置容忍的两个特例:
示例1: 当不指定key值时,表示容忍所有的污点key:
tolerations:
- operator: "Exists"
示例2:当不指定effect值时,表示容忍所有的污点作用:
tolerations:
- key: "key"
operator: "Exists"
除了 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/google_containers/pause:2.0
调度器的编写请参考 kubernetes 默认调度器的实现,最核心的内容就是读取 apiserver 中 pod 的值,根据特定的算法找到合适的 node,然后把调度结果会写到 apiserver。
自定义调度语言可以用任何语言编写,调度策略根据需要可以简单也可以复杂。这是一个非常简单的例子,它使用Bash编写,它可以为Pod随机分配一个Node。请注意,您需要让它与kubectl proxy一起运行:
#!/bin/bash
SERVER='localhost:8001' # Proxy address for apiServer
while true;
do
# Get all pods, and pod's properties
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
# Get all nodes
NODES=($(kubectl --server $SERVER get nodes -o json
| jq '.items[].metadata.name'
| tr -d '"'))
NUMNODES=${#NODES[@]}
# Choose a node randomly
CHOSEN=${NODES[$[ $RANDOM % $NUMNODES ]]}
# Bind a pod($PODNAME) to a node($CHOSEN)
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
https://cizixs.com/2017/03/10/kubernetes-intro-scheduler/
https://www.kubernetes.org.cn/1890.html