From 350ce84cf35d8eba2386ad9ead796dffe0337135 Mon Sep 17 00:00:00 2001 From: benjas <909336740@qq.com> Date: Sun, 19 Apr 2020 12:16:51 +0800 Subject: [PATCH] =?UTF-8?q?Update=20Kubernetes=E8=B0=83=E5=BA=A6=E6=9C=BA?= =?UTF-8?q?=E5=88=B6.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 原理及源码解析/Kubernetes调度机制.md | 120 ++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/原理及源码解析/Kubernetes调度机制.md b/原理及源码解析/Kubernetes调度机制.md index 4ab6678..635707a 100644 --- a/原理及源码解析/Kubernetes调度机制.md +++ b/原理及源码解析/Kubernetes调度机制.md @@ -137,4 +137,122 @@ spec: 这样,该Pod就会绑定在2个独占的CPU核上,具体是哪两个CPU,由kubelet 分配 -基于上面的情况,建议将DaemonSet(亦或者类似的)的 Pod 都设置为 Guaranteed 的 QoS 类型,否则一旦被资源紧张被回收,又立即会在宿主机上重建出来,这样资源回收的动作就没有意义了 \ No newline at end of file +基于上面的情况,建议将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,而是通过一组叫作 CRI(Container 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 的接口。 +