kube-scheduler 是 kubernetes 的核心组件之一,主要负责整个集群资源的调度功能,根据特定的调度算法和策略,将 Pod 调度到最优的工作节点上面去,从而更加合理、更加充分的利用集群的资源,这也是我们选择使用 kubernetes 一个非常重要的理由。如果一门新的技术不能帮助企业节约成本、提供效率,我相信是很难推进的。

调度流程

kube-scheduler 提供的默认调度器能够满足我们绝大多数的要求,我们前面和大家接触的示例也基本上用的默认的策略,都可以保证我们的 Pod 可以被分配到资源充足的节点上运行。但是在实际的线上项目中,可能我们自己会比 kubernetes 更加了解我们自己的应用,比如我们希望一个 Pod 只能运行在特定的几个节点上,或者这几个节点只能用来运行特定类型的应用,这就需要我们的调度器能够可控。

  1. 发起创建Deployment请求->API Server,这个时候APIServer会进行一系列的逻辑处理,例如: 鉴权、查看你是否有权限操作、Deployment创建是否合法等等,然后将请求存储到etcd当中并且转发给Controller Manager
  2. Controller Manager会监听API Server,这个时候假设监听到的是一个创建Deployment的请求,则会把请求转发到Deployment Controller
  3. Deployment Controller接受到请求后创建ReplicaSet,然后ReplicaSet Controller会根据yaml当中定义的template模板来进行创建Pod,然后返回给API Server
  4. 在创建之初的Pod属性中nodeName为空,也就是没有被调度过的,这个时候调度器就会对它进行调度,调度去watchPod对象,然后分析那个节点最适合这个Pod,然后将节点的名字通过类似于bind的这种方法写入到nodeName当中。
  5. 然后该节点的kubelet会进行一系列的判断,然后进入Create Pod的流程,然后进行一系列的CNICSI的过程。

这也就是我们常说的往往越简单的东西,背后实现的越复杂。

调度阶段

kube-scheduler调度分为两个阶段

  1. predicate: 过滤阶段,过滤不符合条件的节点。
  2. priority: 优先级排序,选择优先级最高的节点,也就是给节点打分。

Predicates策略

  1. PodFitsHostPorts: 检查是否有Host Ports冲突
  2. PodFitsPorts: 同上
  3. PodFitsResources: 检查Node的资源是否充足,包括允许的Pod数量、CPU、内存、GPU个数以及其他的OpaqueIntResources。
  4. HostName:检查pod.Spec.NodeName是否与候选节点一致
  5. MatchNodeSelector:检查候选节点的pod.Spec.NodeSelector是否匹配
  6. NoVolumeZoneConflict:检查volume zone是否冲突

Priority策略

  1. SelectorSpreadPriority: 优先减少节点上属于同一个ServiceReplication Controller的Pod数量。
  2. InterPodAffinityPriority: 优先将Pod调度到相同的拓扑上
  3. LeastRequestedPriority:优先调度到请求资源少的节点上
  4. BalancedResourceAllocation: 优先平衡各节点的资源使用
  5. NodePreferAvoidPodsPriority:权重判断

太多了可以自己去官网了解一下,这些策略都可以通过scheduler配置文件去配置,其实一般来说我们不太需要,我觉得kubernetes的调度是最让我们省心的。

资源需求

  • requests:属于调度器调度的时候所参考的指标,也就是说我这个应用最少需要250m的cpu和256m的内存才能运行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
kind: Deployment
apiVersion: apps/v1
metadata:
name: nginx-deployment
namespace: default
labels:
app: nginx
version: qa
spec:
replicas: 2
selector:
matchLabels:
app: nginx
version: qa
template:
metadata:
creationTimestamp: null
labels:
app: nginx
version: qa
spec:
volumes:
- name: host-time
hostPath:
path: /etc/localtime
type: ''
containers:
- name: nginx
image: nginx:latest
ports:
- name: http-web
containerPort: 80
protocol: TCP
resources:
limits:
cpu: '1'
memory: 2Gi
requests:
cpu: 250m
memory: 256Mi

可以查看你节点的一些资源状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@master1 ~]# kubectl get nodes -o yaml
allocatable:
cpu: 15600m
ephemeral-storage: 104278276Ki
hugepages-1Gi: "0"
hugepages-2Mi: "0"
memory: "38390677064"
pods: "330"
capacity:
cpu: "16"
ephemeral-storage: 104278276Ki
hugepages-1Gi: "0"
hugepages-2Mi: "0"
memory: 40003048Ki
pods: "330"

可以看看这个deployment运行以后我们的cgroup对他做了如何的限制

1
"CgroupParent": "/kubepods/burstable/pod1ceee26d-2ec2-43a8-96ef-5aa9ac99779b"

进入这个目录

1
2
3
[root@node1 ~]# cd /sys/fs/cgroup/cpu/kubepods/burstable/pod1ceee26d-2ec2-43a8-96ef-5aa9ac99779b
[root@node1 pod1ceee26d-2ec2-43a8-96ef-5aa9ac99779b]# cat cpu.shares
358

kubernetes对于不同的QOS的处理方式是不一样的。

Limit-range

一个 LimitRange(限制范围) 对象提供的限制能够做到:

  • 在一个命名空间中实施对每个 Pod 或 Container 最小和最大的资源使用量的限制。
  • 在一个命名空间中实施对每个 PersistentVolumeClaim 能申请的最小和最大的存储空间大小的限制。
  • 在一个命名空间中实施对一种资源的申请值和限制值的比值的控制。
  • 设置一个命名空间中对计算资源的默认申请/限制值,并且自动的在运行时注入到多个 Container 中。

当某命名空间中有一个 LimitRange 对象时,将在该命名空间中实施 LimitRange 限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-resource-constraint
spec:
limits:
- default: # 此处定义默认限制值
cpu: 500m
defaultRequest: # 此处定义默认请求值
cpu: 500m
max: # max 和 min 定义限制范围
cpu: "1"
min:
cpu: 100m
type: Container

这东西其实是不太常用的

生产环境需要考虑的问题

  • 是否公平调度
  • 资源是否高效利用
  • QOS
  • affinityanti-affinity
  • 数据本地化
  • 内部负载干扰(inter-workload interference)
  • deadlines