Pod资源模型

Pod资源调度模型

cpu和内存

CPU 这样的资源被称作“可压缩资源”,当可压缩资源不足时,Pod 只会“饥饿”,但不会退出。

内存这样的资源,则被称作“不可压缩资源,当不可压缩资源不足时,Pod 就会因为 OOM(Out-Of-Memory)被内核杀掉。

由于 Pod 可以由多个 Container 组成,所以 CPU 和内存资源的限额,是要配置在每个Container 的定义上的。这样,Pod 整体的资源配置,就由这些 Container 的配置值累加得到。

Kubernetes 里为 CPU 设置的单位是“CPU 的个数”。比如,cpu=1 指的就是,这个 Pod 的 CPU 限额是 1 个 CPU。也可以直接把这个配置写成 cpu=0.5。但在实际使用时,我还是推荐你使用500m 的写法,毕竟这才是 Kubernetes 内部通用的 CPU 表示方式。

内存资源来说,它的单位自然就是 bytes。Kubernetes 支持你使用 Ei、Pi、Ti、Gi、Mi、Ki(或者 E、P、T、G、M、K)的方式来作为 bytes 的值。这里要注意区分MiB(mebibyte)和 MB(megabyte)的区别。

备注:1Mi=1024*1024;1M=1000*1000

limit和request

Kubernetes 里 Pod 的 CPU 和内存资源,实际上还要分为 limitsrequests 两种情况,

在 Kubernetes 中,requestslimits 是用于定义 Pod 中容器资源分配的关键字。

requests 表示容器在运行时所需的资源数量,包括 CPU 和内存等。Kubernetes 系统会基于容器的 requests 属性为 Pod 分配所需的资源,以确保 Pod 能够正常启动并运行。当 Pod 启动后,Kubernetes 系统会为该 Pod 中的容器保留与其 requests 属性匹配的资源,确保容器能够稳定运行。

limits 表示容器能够使用的最大资源数量,包括 CPU 和内存等。Kubernetes 系统会监控每个容器的资源使用情况,并确保其不超过 limits 属性所指定的资源量。如果容器的资源使用量超过 limits 属性所指定的值,那么 Kubernetes 系统将会限制容器的资源使用量,防止容器崩溃或影响其他容器和节点的正常运行。

因此,requestslimits 的区别在于:

  • requests 表示容器所需的资源数量,确保 Pod 能够启动和运行。
  • limits 表示容器能够使用的最大资源数量,确保容器的资源使用量不会超过指定的限制。

在实践中,应该根据容器的资源使用情况和应用程序的需求来设置 requestslimits 属性,以充分利用资源并确保应用程序的稳定运行。

QoS 模型

当 Pod 里的每一个 Container 都同时设置了 requests 和 limits,并且 requests 和limits 值相等的时候,这个 Pod 就属于Guaranteed 类别。当 Pod 仅设置了 limits 没有设置 requests 的时候,Kubernetes 会自动为它设置与 limits 相同的 requests 值,所以,这也属于 Guaranteed情况。

而当 Pod 不满足 Guaranteed 的条件,但至少有一个 Container 设置了 requests。那么这个 Pod 就会被划分到 Burstable 类别。

而如果一个 Pod 既没有设置 requests,也没有设置 limits,那么它的 QoS 类别就是BestEffort

QoS 划分的主要应用场景,是当宿主机资源紧张的时候,kubelet 对 Pod 进行Eviction(即资源回收)时需要用到的。当 Kubernetes 所管理的宿主机上不可压缩资源短缺时,就有可能触发Eviction。

Eviction 在 Kubernetes 里其实分为 SoftHard 两种模式。

Soft Eviction 允许你为 Eviction 过程设置一段“优雅时间”,比如imagefs.available=2m,就意味着当 imagefs 不足的阈值达到 2 分钟之后,kubelet 才会开始 Eviction 的过程。而 Hard Eviction 模式下,Eviction 过程就会在阈值达到之后立刻开始。

Eviction 发生的时候,kubelet 具体会挑选哪些 Pod 进行删除操作,就需要参考这些Pod 的 QoS 类别了。

删除顺序 BestEffort -> Burstable -> Guaranteed

事实上,当发生 Eviction 时,kubelet 会依据一定的策略来选择要删除的 Pod。具体来说,kubelet 会按照以下顺序进行判断:

  1. 命名空间优先级:kubelet 会先删除优先级较低的命名空间中的 Pod。
  2. Pod 生命周期阶段:kubelet 会先删除已经被标记为删除的 Pod。
  3. Pod QoS 策略:kubelet 会先删除低优先级的 Pod,通常是 BestEffort 类型的 Pod。
  4. Pod 创建时间:kubelet 会先删除最早创建的 Pod。

注意,kubelet 只会删除处于 Running 状态的 Pod,而不会删除已经处于 Terminated 状态的 Pod。

cpuset

我们知道,在使用容器的时候,你可以通过设置 cpuset 把容器绑定到某个 CPU 的核上,而不是像 cpushare 那样共享 CPU 的计算能力。这种情况下,由于操作系统在 CPU 之间进行上下文切换的次数大大减少,容器里应用的性 能会得到大幅提升。

在 Kubernetes 里又该如何实现呢?

首先,你的 Pod 必须是 Guaranteed 的 QoS 类型;

然后,你只需要将 Pod 的 CPU 资源的 requests 和 limits 设置为同一个相等的整数值即可。

1
2
3
4
5
6
7
8
9
10
11
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "2"
requests:
memory: "200Mi"
cpu: "2"

这时候,该 Pod 就会被绑定在 2 个独占的 CPU 核上。当然,具体是哪两个 CPU 核,是由 kubelet 为你分配的。

kube-scheduler

Kubernetes 默认的调度器是 kube-scheduler

kube-scheduler 是 Kubernetes 的核心组件之一,负责根据定义的 Pod 的调度要求和节点的可用资源情况,将 Pod 调度到适当的节点上。kube-scheduler 的工作流程如下:

  1. 监听新建的 Pod 和更新的 Pod,当发现新建或更新的 Pod 时,会将其放入调度队列中。
  2. 遍历调度队列中的 Pod,为其选择一个合适的节点。
  3. 将 Pod 绑定到所选择的节点上。

在选择节点时,kube-scheduler 会根据 Pod 的调度要求和节点的资源情况进行匹配。其中,调度要求包括 Pod 对节点的要求(如特定的节点名称或节点标签)和 Pod 的资源需求(如 CPU 和内存等)。而节点的资源情况则包括节点的负载和资源限制等。

默认调度器的主要职责,就是为一个新创建出来的 Pod,寻找一个最合适的节点(Node)。

1698969395565

Kubernetes 的调度器的核心,实际上就是两个相互独立的控制循环。

第一个控制循环,我们可以称之为 Informer Path。它的主要目的,是启动一系列 Informer,用来监听(Watch)Etcd 中 Pod、Node、Service 等与调度相关的 API 对象的变化。

在默认情况下,Kubernetes 的调度队列是一个 PriorityQueue(优先级队列),并且当某些集群信息发生变化的时候,调度器还会对调度队列里的内容进行一些特殊操作。

此外,Kubernetes 的默认调度器还要负责对调度器缓存(即:scheduler cache)进行更新。

第二个控制循环,是调度器负责 Pod 调度的主循环,我们可以称之为 Scheduling Path。

Scheduling Path 的主要逻辑,就是不断地从调度队列里出队一个 Pod。然后,调用Predicates 算法进行“过滤”。这一步“过滤”得到的一组 Node,就是所有可以运行这个 Pod 的宿主机列表。当然,Predicates 算法需要的 Node 信息,都是从 Scheduler Cache 里直接拿到的,这是调度器保证算法执行效率的主要手段之一。

接下来,调度器就会再调用 Priorities 算法为上述列表里的 Node 打分,分数从 0 到 10。得分最高的 Node,就会作为这次调度的结果。

调度算法执行完成后,调度器就需要将 Pod 对象的 nodeName 字段的值,修改为上述Node 的名字。这个步骤在 Kubernetes 里面被称作 Bind。这种基于“乐观”假设的API 对象更新方式,在 Kubernetes 里被称作 Assume。

除了上述的“Cache 化”和“乐观绑定”,Kubernetes 默认调度器还有一个重要的设计,那就是“无锁化”。

在 Scheduling Path 上,调度器会启动多个 Goroutine 以节点为粒度并发执行 Predicates算法,从而提高这一阶段的执行效率。而与之类似的,Priorities 算法也会以 MapReduce的方式并行计算然后再进行汇总。而在这些所有需要并发的路径上,调度器会避免设置任何全局的竞争资源,从而免去了使用锁进行同步带来的巨大的性能损耗。

Kubernetes 默认调度器调度策略

Kubernetes 默认调度器 kube-scheduler 的调度策略是以最佳节点为目标的贪心算法。该算法将 Pod 的调度看作一个优化问题,其目标是将 Pod 调度到最佳的节点上,以尽量满足 Pod 的资源需求和其他调度要求,同时保证节点的资源利用率和负载均衡。

该算法的基本流程如下:

  1. Predicates:根据 Pod 的调度要求和节点的资源情况,为 Pod 筛选出一组可用的节点。其中,调度要求包括 Pod 对节点的要求(如特定的节点名称或节点标签)和 Pod 的资源需求(如 CPU 和内存等)。节点的资源情况则包括节点的负载和资源限制等。
  2. Priorities:对于可用节点的集合,计算出每个节点的得分(score)。得分是根据节点的资源利用率、距离等因素计算出来的一个分值,表示该节点对于当前 Pod 的匹配程度。
  3. 将得分最高的节点选作 Pod 的调度目标,将 Pod 绑定到该节点上。

需要注意的是,Kubernetes 默认调度器的算法是基于一些默认的调度策略进行计算的。这些调度策略包括节点的资源利用率、Pod 与节点的亲和性和互斥性、节点的距离等因素。同时,Kubernetes 也支持用户自定义调度策略,用户可以通过修改 kube-scheduler 的配置文件,定义自己的调度器算法。

Predicates

在 Kubernetes 中,为 Pod 筛选可用节点的方法通常包括以下几种:

  1. 静态匹配:根据 Pod 的 nodeSelector 字段,为 Pod 指定一个或多个目标节点。这种方式适用于有特定节点要求的 Pod,如需要运行在特定的节点或特定类型的节点上。
  2. 亲和性规则:根据 Pod 的 affinity/anti-affinity 字段,为 Pod 指定一个或多个节点亲和或互斥的标签,然后在节点标签中匹配这些标签。这种方式适用于需要控制 Pod 和节点之间关系的场景,如需要将相关的 Pod 调度到同一个节点上。
  3. 资源匹配:根据节点的资源使用情况,筛选出空闲的节点,然后将 Pod 调度到其中。这种方式适用于资源敏感的 Pod,如需要大量 CPU 和内存资源的 Pod。
  4. 路由匹配:根据节点的位置和拓扑结构,筛选出网络拓扑上最近的节点。这种方式适用于需要快速访问其他节点的 Pod。

kube-scheduler 中,这些方法会组合起来,对每个 Pod 进行筛选和评分,然后选择得分最高的节点作为调度目标。同时,Kubernetes 还支持用户自定义调度策略,可以通过修改 kube-scheduler 的配置文件,定义自己的调度器算法。

Priorities

在 Kubernetes 中,为了将 Pod 调度到最优的节点上,调度器会根据一定的规则对每个节点进行评分,最终选择得分最高的节点。评分规则的定义在 kube-scheduler 的配置文件中,可以通过修改该文件来定义自己的评分规则。

一般而言,Kubernetes 的评分规则会包括以下几个方面:

  1. 节点资源的可用性:调度器会根据节点的 CPU、内存、存储等资源使用情况,评估节点资源的可用性。这个评估过程一般包括计算出节点的资源使用率,以及与 Pod 的资源需求进行比较。
  2. Pod 亲和性规则的匹配情况:如果 Pod 定义了亲和性规则,调度器会根据这些规则,评估节点与 Pod 之间的亲和性匹配程度。例如,如果 Pod 需要调度到和另一个 Pod 在同一个节点上,调度器会评估节点上是否已经有符合条件的 Pod。
  3. 节点和 Pod 之间的拓扑关系:如果 Pod 和节点之间存在网络拓扑关系,调度器会考虑这些关系,评估节点和 Pod 之间的网络距离。例如,如果 Pod 需要快速访问某个服务,调度器会评估距离最近的节点是否可以满足这个需求。
  4. 其他因素:除了以上几个方面,调度器还可以考虑其他因素,如节点的稳定性、负载均衡等。例如,如果节点已经运行了过多的 Pod,调度器可能会选择其他节点,以保证集群的负载均衡。

常见问题排查手段

ssh 到内网节点

  • 创建一个支持ssh的pod
  • 并通过负载均衡器转发ssh请求

pod日志查看

1698970788742

刘小恺(Kyle) wechat
如有疑问可联系博主