掌握 Kubernetes 资源请求与限制,实现最佳性能

了解 Kubernetes 中 CPU 和内存资源请求与限制的关键区别。本指南解释了这些设置如何决定服务质量(QoS)类别(Guaranteed、Burstable、BestEffort),防止节点不稳定,并优化集群调度效率。包含实用的 YAML 示例和性能调优的最佳实践。

掌握 Kubernetes 资源请求与限制,实现最佳性能

Kubernetes 资源请求和限制在 YAML 中看起来很简单,但它们几乎决定了集群中的所有第二天行为:Pod 落在哪里、哪些工作负载首先被驱逐、应用在负载下是否会被 CPU 节流、以及你认为有多少空闲容量。一个错误的设置可能让一个健康的应用程序看起来有问题。一个缺失的设置可能让调度器将 Pod 打包到一个节点上,直到第一次流量高峰变成嘈杂邻居事件。

让团队困惑的是,请求和限制被不同的系统使用。请求主要是调度承诺。限制是执行边界。将它们视为同一件事会导致奇怪的结果,尤其是在 CPU 方面。

理解核心概念:请求与限制

在 Kubernetes 中,容器可以使用 resources.requestsresources.limits 定义预期的资源消耗。除非你的集群使用了 LimitRange 或准入控制等策略,否则它们不是强制性的,但生产工作负载通常至少应定义 CPU 和内存的请求。没有请求,调度器几乎没有有用信息,Pod 会落入较弱的服务质量状态。

1. 资源请求(requests

请求代表容器在调度时保证获得的资源量。这是 kube-scheduler 在决定将 Pod 放置到哪个节点时使用的最小资源量。

  • 调度: 在将新 Pod 调度到节点之前,节点必须有足够的可用可分配资源来满足所有 Pod 请求的总和。
  • 运行时优先级: 请求影响 CPU 份额和内存驱逐决策。它们不是防止任何减速的神奇预留,但确实在争用期间为 Kubernetes 和内核提供了更好的信息。

2. 资源限制(limits

限制定义了容器允许消耗的最大资源量。超过这些限制会导致针对 CPU 和内存的特定行为。

  • CPU 限制: 如果容器尝试使用超过其限制的 CPU,Linux 内核的 cgroups 将节流其使用,阻止其消耗更多周期。
  • 内存限制: 如果容器超过其内存限制,内核可以终止容器中的进程。当记录到终止原因是 OOMKilled 时,Kubernetes 会报告此情况。

CPU 与内存行为

理解 Kubernetes 如何执行 CPU 与内存边界之间的定性差异至关重要:

资源 超出限制时的行为 执行机制
CPU 节流(减慢) cgroups(CPU 带宽控制)
内存 终止(OOMKill) 内核 OOM Killer

实际注意事项: CPU 限制可以保护节点免受失控进程的影响,但如果设置过低,也可能造成延迟问题。许多平台团队一致地设置内存限制,并对延迟敏感的服务更谨慎地设置 CPU 限制,具体取决于他们的风险承受能力和集群策略。

在 Pod 规范中定义资源

资源在 spec.containers[*].resources 块中定义。数量使用标准的 Kubernetes 后缀(例如,m 表示毫核 CPU,Mi 表示 Mebibytes)。

CPU 单位定义

  • 1 CPU 单位等于 1 个完整核心(或云提供商上的 vCPU)。
  • 1000m(毫核)等于 1 CPU 单位。

内存单位定义

  • Mi(Mebibytes)或 Gi(Gibibytes)是常用单位。
  • 1024Mi = 1Gi

示例 YAML 配置

考虑一个容器需要保证最低 500m CPU 和 256Mi 内存,但绝不应超过 1 CPU 和 512Mi:

resources:
  requests:
    memory: "256Mi"
    cpu: "500m"
  limits:
    memory: "512Mi"
    cpu: "1"

数字应来自观察到的行为,而非猜测。对于小型 HTTP API,初始请求可能基于正常业务流量期间的 p50 或 p90 使用量,然后在负载测试后调整。对于 JVM 服务,内存需要包括堆、元空间、本地内存、线程栈、直接缓冲区和 Sidecar 开销。对于批处理作业,最大输入期间的峰值内存可能比平均内存更重要。

服务质量(QoS)类别

请求和限制之间的关系决定了分配给 Pod 的服务质量(QoS)类别。此类别决定了当资源稀缺且节点需要回收内存(驱逐)时 Pod 的优先级。

Kubernetes 定义了三种 QoS 类别:

1. Guaranteed

定义: Pod 中的所有容器必须对 CPU 和内存都有相同且非零的请求和限制。

  • 优势: 这些 Pod 在资源压力下最后被驱逐,确保最大稳定性。
  • 用例: 需要严格性能隔离的关键系统组件或数据库。

2. Burstable

定义: Pod 中至少有一个容器定义了请求,但要么所有容器的请求和限制不相等,要么某些资源没有限制(尽管强烈建议设置限制)。

  • 优势: 允许容器超出其请求,利用节点上的空闲容量,直到其定义的限制。
  • 驱逐优先级: 在 BestEffort Pod 之前被驱逐,但在 Guaranteed Pod 之后。
  • 用例: 大多数标准无状态应用程序,其中轻微延迟变化是可接受的。

3. BestEffort

定义: Pod 没有为任何容器定义请求或限制。

  • 优势: 除了简单之外,没有其他优势。
  • 风险: 当节点遇到内存压力时,这些 Pod 是首先被驱逐的候选者。它们也可能在 CPU 竞争中表现不佳,因为没有声明请求。
  • 用例: 可以轻松重启的非关键批处理作业或日志代理。

实际优化策略

有效的资源管理需要测量、迭代和仔细规划。

策略 1:准确测量和设置请求

请求应反映应用程序在大多数时间可接受运行所需的资源量。如果请求设置过高,你会浪费集群容量,因为调度器将该容量视为已被占用。如果设置过低,调度器可能将太多 Pod 放在一个节点上,工作负载在争用期间更可能受到影响。

使用 Prometheus 和 Grafana 等监控工具将请求值与实际使用情况进行比较。一个常见的起点是查看几天正常流量,忽略明显的一次性事件,并将请求设置在持续百分位附近,而不是单个最高峰值。确切的百分位是策略选择;重点是使用数据并重新审视。

例如,如果一个服务通常使用 180m CPU,在部署预热期间峰值约为 450m,并且在已知批处理任务期间有罕见的峰值达到 900m,那么设置 900m 请求可能会全天浪费容量。设置 50m 请求可能使 Pod 调度便宜,但在争用下不稳定。在正常持续范围附近的请求,加上对批处理路径的单独处理,通常是更好的讨论方向。

策略 2:定义保守的限制

限制作为安全边界,但并非免费。对于内存,略高于测量峰值使用量的限制可以防止一个容器消耗整个节点。对于 CPU,限制可以防止失控进程使用无限 CPU,但激进的限制可以在节点有空闲核心时仍然节流服务。

关于 CPU 限制的警告: 将 CPU 限制设置低于实际需求会导致通过节流产生可见的延迟。Burstable QoS 适合许多无状态服务,而 Guaranteed QoS 更适合有意进行隔离权衡的工作负载。

策略 3:利用垂直 Pod 自动缩放器(VPA)

手动调整资源困难且耗时。垂直 Pod 自动缩放器(VPA) 监控运行时使用情况,并可以根据其模式推荐或更新资源请求。在许多设置中,VPA 首先在推荐模式下使用,以便团队在允许自动更新之前审查建议的请求。

将 VPA 与水平 Pod 自动缩放器(HPA)结合使用时需谨慎。HPA 通常根据相对于请求的利用率进行缩放,因此更改请求可能会改变缩放行为。它可以很好地工作,但应有意测试。

策略 4:命名空间的资源配额

为了防止跨团队或环境的资源占用,管理员应在命名空间级别使用资源配额。ResourceQuota 对该命名空间内可以存在的 CPU/内存请求和限制的总量施加聚合限制,确保公平性。

示例命名空间配额

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: development
spec:
  hard:
    requests.cpu: "10"
    limits.memory: "20Gi"

这确保了 development 命名空间中所有 Pod 的总请求 CPU 不能超过 10 核,总内存限制不能超过 20Gi。

错误设置如何在真实集群中显现

资源错误很少以“资源错误”的形式出现。它们通常看起来像应用程序事件。

如果 CPU 限制过低,你可能会看到高请求延迟,而 CPU 使用率图显示上限。容器需要更多 CPU,但 cgroup 节流阻止了它。在基于 Prometheus 的设置中,诸如 container_cpu_cfs_throttled_periods_totalcontainer_cpu_cfs_periods_total 等指标可以帮助显示节流是否是问题的一部分。

如果内存限制过低,Pod 可能会以 Reason: OOMKilled 重启。应用程序日志可能突然结束,因为进程没有正常关闭。在这种情况下,kubectl describe pod 通常比应用日志更快地揭示真相。

如果请求过高,Pod 可能处于 Pending 状态,即使仪表板显示平均节点使用率很低。调度器不是基于平均实际使用率放置 Pod;它比较请求的资源与可分配资源。这就是为什么集群可能看起来使用不足但仍然拒绝新 Pod。

如果请求缺失,工作负载在平静时期可能看起来正常,然后在节点繁忙时成为第一个被挤压的对象。这对于可丢弃的作业可能是可接受的,但对于面向用户的服务来说是一个糟糕的默认设置。

更安全的调优工作流

一个实际的调优循环如下:

  1. 为每个生产容器(包括 Sidecar)设置明确的 CPU 和内存请求。
  2. 根据观察到的峰值加上余量设置内存限制,然后监控 OOMKilled 重启。
  3. 根据策略或工作负载风险决定是否需要 CPU 限制。如果使用,监控节流。
  4. 每周或每月将请求的 CPU 和内存与实际使用情况进行比较,尤其是在重大发布之后。
  5. 将调整大小视为变更管理。较低的请求可以增加装箱密度,但也可以改变节点压力期间的故障行为。

Sidecar 值得关注。服务网格代理、日志收集器或安全代理可能消耗足够的 CPU 或内存来改变 Pod 的实际占用空间。如果只调整主应用容器,Pod 仍然可能向调度器提供错误信息。

示例:修复由 CPU 节流引起的延迟峰值

假设一个 API 容器具有以下配置:

resources:
  requests:
    cpu: "100m"
    memory: "256Mi"
  limits:
    cpu: "200m"
    memory: "512Mi"

在促销活动期间,延迟激增。节点仍有空闲 CPU,但容器被限制在 200m。增加副本可能有所帮助,但每个 Pod 仍然被单独节流。更好的修复可能是提高或移除 CPU 限制,增加请求以匹配正常持续需求,并使用 HPA 以便服务在延迟变差之前进行缩放。

重要的教训是,图中低 CPU 使用率并不总是意味着应用程序空闲。它可能意味着应用程序不被允许使用更多。

最终检查

请求告诉 Kubernetes 如何放置和优先处理 Pod。限制告诉内核在哪里停止它。好的值来自真实指标、负载测试以及关于每个工作负载更看重什么(密度、隔离、延迟还是成本)的明确决策。在流量变化、依赖关系变化和运行时升级后重新审视这些值。过时的资源设置是集群性能逐渐下降的最安静方式之一。