Update Kubernetes调度机制.md

This commit is contained in:
benjas 2020-04-19 12:16:51 +08:00
parent 56f32d38fd
commit 350ce84cf3

View File

@ -137,4 +137,122 @@ spec:
这样该Pod就会绑定在2个独占的CPU核上具体是哪两个CPU由kubelet 分配
基于上面的情况建议将DaemonSet亦或者类似的的 Pod 都设置为 Guaranteed 的 QoS 类型,否则一旦被资源紧张被回收,又立即会在宿主机上重建出来,这样资源回收的动作就没有意义了
基于上面的情况建议将DaemonSet亦或者类似的的 Pod 都设置为 Guaranteed 的 QoS 类型,否则一旦被资源紧张被回收,又立即会在宿主机上重建出来,这样资源回收的动作就没有意义了
### Kubernetes默认的调度策略
![1587049585883](assets/1587049585883.png)
> 调度机制的工作原理示意图
**默认的几种调度策略:**
1. **第一种类型GeneralPredicates**这一组过滤规则,负责的是最基础的调度策略,计算的就是宿主机的 CPU 和内存资源等是否够用。
2. **第二种类型,与 Volume 相关的过滤规则:**这一组过滤规则,负责的是跟容器持久化 Volume 相关的调度策略。
3. **第三种类型,是宿主机相关的过滤规则:**这一组规则,主要考察待调度 Pod 是否满足 Node 本身的某些条件。比如PodToleratesNodeTaints负责检查的就是我们前面经常用到的 Node 的“污点”机制。
4. **第四种类型,是 Pod 相关的过滤规则:**这一组规则,跟 GeneralPredicates 大多数是重合的。而比较特殊的,是 PodAffinityPredicate。这个规则的作用是检查待调度 Pod 与 Node 上的已有 Pod 之间的亲密affinity和反亲密anti-affinity关系
**在具体执行的时候, 当开始调度一个 Pod 时Kubernetes 调度器会同时启动 16 个 Goroutine来并发地为集群里的所有 Node 计算 Predicates最后返回可以运行这个 Pod 的宿主机列表。**
> **Goroutine**go语言的“线程”比传统线程对资源的占用更合理
在 Predicates 阶段完成了节点的“过滤”之后Priorities 阶段的工作就是为这些节点打分。这里打分的范围是 0-10 分,得分最高的节点就是最后被 Pod 绑定的最佳节点。
Priorities 里最常用到的一个打分规则,是 LeastRequestedPriority。它的计算方法可以简单地总结为如下所示的公式
~~~
score = (cpu((capacity-sum(requested))10/capacity) + memory((capacity-sum(requested))10/capacity))/2
~~~
这个算法实际上就是在选择空闲资源CPU 和 Memory最多的宿主机。
与 LeastRequestedPriority 一起发挥作用的,还有 BalancedResourceAllocation。它的计算公式如下所示
~~~
score = 10 - variance(cpuFraction,memoryFraction,volumeFraction)*10
~~~
每种资源的 Fraction 的定义是 Pod 请求的资源 / 节点上的可用资源。而 variance 算法的作用,则是计算每两种资源 Fraction 之间的“距离”。而最后选择的,则是资源 Fraction 差距最小的节点。
**也就是调度完成后,所有节点里各种资源分配最均衡的那个节点,从而避免一个节点上 CPU 被大量分配、而 Memory 大量剩余的情况**
此外,还有 NodeAffinityPriority、TaintTolerationPriority 和 InterPodAffinityPriority 这三种 Priority。这里就不一一介绍了除了默认的调度策略还有很多默认不会开启的策略可以通过为 kube-scheduler 指定一个配置文件或者创建一个 ConfigMap ,来配置哪些规则需要开启、哪些规则需要关闭。并且,还可以通过为 Priorities 设置权重,来控制调度器的调度行为。
### 调度器的优先级与强制机制
工作中我们也需要一些Pod有优先级和抢占机制比如Pod调度失败后会被“搁置”知道Pod被更新或集群状态发生变化调度器才会对Pod进行重新调度我们希望高优先级的Pod调度失败后不被搁置而“挤走”某些低优先级的。
1.11版本后这个特性已经是Beta用法如下
先在Kubernetes里提交一个PriorityClass如下:
~~~
apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for high priority service pods only."
~~~
> 这个YAML文件名为high-priority
>
> **value**值越大代表优先级越高最大1000000000/10亿
>
> **globalDefault**声明使用该PriorityClass的Pod拥有值为1000000 的优先级没有声明则为0。 true 的话,那就意味着这个 PriorityClass 的值会成为系统的默认值
然后我们在Pod就可以声明使用如下
~~~
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
~~~
> 提交后Kubernetes 的 PriorityAdmissionController 就会自动将这个 Pod 的 spec.priority 字段设置为 1000000
调度器里维护着一个调度队列,高优先级的 Pod 就可能会比低优先级的 Pod 提前出队,从而尽早完成调度过程。**这个过程,就是“优先级”这个概念在 Kubernetes 里的主要体现。**
而当一个高优先级的 Pod 调度失败的时候,调度器的抢占能力就会被触发。这时,调度器就会试图从当前集群里寻找一个节点,使得当这个节点上的一个或者多个低优先级 Pod 被删除后,待调度的高优先级 Pod 就可以被调度到这个节点上。**这个过程,就是“抢占”这个概念在 Kubernetes 里的主要体现。**
### kubelet
![img](https://static001.geekbang.org/resource/image/91/03/914e097aed10b9ff39b509759f8b1d03.png)
**kubelet 调用下层容器运行时的执行过程,并不会直接调用 Docker 的 API而是通过一组叫作 CRIContainer Runtime Interface容器运行时接口的 gRPC 接口来间接执行的。**为什么由CRI来间接执行是关乎历史原因搭配CRI之后Kubernetes 以及 kubelet 本身的架构,就可以用如下所示的一幅示意图来描述。
![img](https://static001.geekbang.org/resource/image/51/fe/5161bd6201942f7a1ed6d70d7d55acfe.png)
可以看到,当 Kubernetes 通过编排能力创建了一个 Pod 之后,调度器会为这个 Pod 选择一个具体的节点来运行比如创建一个Pod。此时kubelet 实际上就会调用一个叫作 GenericRuntime 的通用组件来发起创建 Pod 的 CRI 请求。如果使用容器项目是 Docker 的话,那么负责响应这个请求的就是一个叫作 dockershim 的组件。它会把 CRI 请求里的内容拿出来,然后组装成 Docker API 请求发给 Docker Daemon。
**CRI 这个接口的定义**如下图
![1587263685501](assets/1587263685501.png)
- 第一组,是 RuntimeService。它提供的接口主要是跟容器相关的操作。比如创建和启动容器、删除容器、执行 exec 命令等等。
- 而第二组,则是 ImageService。它提供的接口主要是容器镜像相关的操作比如拉取镜像、删除镜像等等。
**CRI 设计的一个重要原则,就是确保这个接口本身,只关注容器,不关注 Pod**
**第一**Pod 是 Kubernetes 的编排概念,而不是容器运行时的概念。所以,我们就不能假设所有下层容器项目,都能够暴露出可以直接映射为 Pod 的 API。
**第二**,如果 CRI 里引入了关于 Pod 的概念,那么接下来只要 Pod API 对象的字段发生变化,那么 CRI 就很有可能需要变更。而在 Kubernetes 开发的前期Pod 对象的变化还是比较频繁的,但对于 CRI 这样的标准接口来说,这个变更频率就有点麻烦了。
所以,在 CRI 的设计里,并没有一个直接创建 Pod 或者启动 Pod 的接口。