掌握 Kubernetes 资源请求与限制,实现最佳性能
了解 Kubernetes 中 CPU 和内存资源请求与限制的关键区别。本指南解释了这些设置如何决定服务质量(QoS)类别(Guaranteed、Burstable、BestEffort),防止节点不稳定,并优化集群调度效率。包含实用的 YAML 示例和性能调优的最佳实践。
掌握 Kubernetes 资源请求与限制,实现最佳性能
Kubernetes 资源请求和限制在 YAML 中看起来很简单,但它们几乎决定了集群中的所有第二天行为:Pod 落在哪里、哪些工作负载首先被驱逐、应用在负载下是否会被 CPU 节流、以及你认为有多少空闲容量。一个错误的设置可能让一个健康的应用程序看起来有问题。一个缺失的设置可能让调度器将 Pod 打包到一个节点上,直到第一次流量高峰变成嘈杂邻居事件。
让团队困惑的是,请求和限制被不同的系统使用。请求主要是调度承诺。限制是执行边界。将它们视为同一件事会导致奇怪的结果,尤其是在 CPU 方面。
理解核心概念:请求与限制
在 Kubernetes 中,容器可以使用 resources.requests 和 resources.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 单位定义
1CPU 单位等于 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_total 和 container_cpu_cfs_periods_total 等指标可以帮助显示节流是否是问题的一部分。
如果内存限制过低,Pod 可能会以 Reason: OOMKilled 重启。应用程序日志可能突然结束,因为进程没有正常关闭。在这种情况下,kubectl describe pod 通常比应用日志更快地揭示真相。
如果请求过高,Pod 可能处于 Pending 状态,即使仪表板显示平均节点使用率很低。调度器不是基于平均实际使用率放置 Pod;它比较请求的资源与可分配资源。这就是为什么集群可能看起来使用不足但仍然拒绝新 Pod。
如果请求缺失,工作负载在平静时期可能看起来正常,然后在节点繁忙时成为第一个被挤压的对象。这对于可丢弃的作业可能是可接受的,但对于面向用户的服务来说是一个糟糕的默认设置。
更安全的调优工作流
一个实际的调优循环如下:
- 为每个生产容器(包括 Sidecar)设置明确的 CPU 和内存请求。
- 根据观察到的峰值加上余量设置内存限制,然后监控 OOMKilled 重启。
- 根据策略或工作负载风险决定是否需要 CPU 限制。如果使用,监控节流。
- 每周或每月将请求的 CPU 和内存与实际使用情况进行比较,尤其是在重大发布之后。
- 将调整大小视为变更管理。较低的请求可以增加装箱密度,但也可以改变节点压力期间的故障行为。
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。限制告诉内核在哪里停止它。好的值来自真实指标、负载测试以及关于每个工作负载更看重什么(密度、隔离、延迟还是成本)的明确决策。在流量变化、依赖关系变化和运行时升级后重新审视这些值。过时的资源设置是集群性能逐渐下降的最安静方式之一。