深入理解K8s资源限制
资源限制是 Kubernetes 中可配置的重要选项之一,它包含两方面内容:
工作负载的资源需求:用于定义工作负载运行时所需的最低资源。调度器根据这一信息选择合适的节点来部署工作负载。
资源的最大限制:用于规定工作负载可以消耗的资源上限。Kubelet 节点守护进程依赖这一配置来管理 Pod 的运行和健康状态。
换句话说,资源需求确保工作负载能够正常运行,而资源限制则避免单个工作负载过度消耗节点资源。
资源限制
资源限制通过每个容器的 containerSpec 中的 resources 字段进行设置,该字段是 v1 版本的 ResourceRequirements 类型的 API 对象。通过设置 limits 和 requests,可以分别定义资源的上限和需求。
当前支持的资源类型主要有 CPU 和 内存。通常情况下,deployment、statefulset 和 daemonset 的定义中都会包含 podSpec,而 podSpec 内部会定义一个或多个 containerSpec。
以下是一个完整的 v1 资源对象的 YAML 配置示例:
resources:
requests:
cpu: 50m
memory: 50Mi
limits:
cpu: 100m
memory: 100Mi
可以这样理解:该容器通常需要 5% 的 CPU 时间 和 50MiB 的内存(由 requests 定义),但在高峰时允许其最多使用 10% 的 CPU 时间 和 100MiB 的内存(由 limits 定义)。
稍后我会更详细地解释 requests 和 limits 的区别,但一般来说:
- requests 在调度阶段更为关键,因为调度器会根据 requests 判断节点是否有足够的资源来运行容器。
- limits 在运行阶段更重要,Kubelet 会使用它来限制容器的资源消耗,确保单个容器不会过度占用节点资源。
虽然资源限制是为每个容器配置的,但 Pod 的整体资源限制可以视为其所有容器资源限制的总和。从系统的角度来看,这种关系非常直观。
内存限制
CPU资源限制比内存资源限制更复杂,但它们都是通过cgroup控制的,所以我们可以用类似的方法来处理。接下来,我们重点看它们的不同之处。
首先,我们在之前的 YAML 文件中加入 CPU 的资源限制:
resources:
requests:
memory: 50Mi
cpu: 50m
limits:
memory: 100Mi
cpu: 100m
这里的 m 表示千分之一核。例如,50m 代表 0.05 核,100m 代表 0.1 核,而 2000m 就是 2 核。这样配置后,容器需要至少 5% 的 CPU 资源才能运行,同时最多能使用 10% 的 CPU 资源。
接着,我们创建一个只配置了 CPU requests 的 Pod:
kubectl run limit-test --image=busybox --requests "cpu=50m" --command -- /bin/sh -c "while true; do sleep 2; done"
用以下命令可以验证 Pod 的资源配置:
kubectl get pods limit-test-5b4c495556-p2xkr -o=jsnotallow='{.spec.containers[0].resources}'
输出:
map[requests:map[cpu:50m]]
同时,用 Docker 查看容器对应的 CPU 配置:
docker ps | grep busy | cut -d' ' -f1
f2321226620e
docker inspect f2321226620e --format '{{.HostConfig.CpuShares}}'
51
这里显示 51 而不是 50,是因为 Kubernetes 把 CPU 核心划分为 1000 个份额(shares),而 Linux 内核用 1024 个时间片表示 CPU 的分配比例。CPU 的 shares 是一个相对值,用来划分 CPU 使用权:
- 如果有两个 cgroup(A 和 B),A 的 shares 是 1024,B 是 512,那么 A 获得 66% 的 CPU 资源,B 获得 33%。
- 如果 CPU 空闲,B 可以使用更多资源。
- 如果新增一个 cgroup C,A 和 B 的占比会减少。
接下来,再看看设置了 CPU limits 的 Pod 会发生什么:
kubectl run limit-test --image=busybox --requests "cpu=50m" --limits "cpu=100m" --command -- /bin/sh -c "while true; do sleep 2; done"
再次用 kubectl 查看资源限制:
kubectl get pods limit-test-5b4fb64549-qpd4n -o=jsnotallow='{.spec.containers[0].resources}'
输出:
map[limits:map[cpu:100m] requests:map[cpu:50m]]
而对应的 Docker 配置:
docker inspect f2321226620e --format '{{.HostConfig.CpuShares}} {{.HostConfig.CpuQuota}} {{.HostConfig.CpuPeriod}}'
51 10000 100000
CpuShares 是对应 requests 的 CPU 份额。
CpuQuota
和
CpuPeriod
是用来实现
limits
的:
- CpuPeriod 表示一个时间周期(默认为 100 毫秒,即 100,000 微秒)。
- CpuQuota 表示每周期允许使用的 CPU 时间(100m 对应 10,000 微秒)。
这些值最终映射到 cgroup:
cat /sys/fs/cgroup/cpu,cpuacct/.../cpu.cfs_period_us
100000
cat /sys/fs/cgroup/cpu,cpuacct/.../cpu.cfs_quota_us
10000
例子:
限制 1 核(每 250ms 内用 250ms CPU 时间):
echo 250000 > cpu.cfs_quota_us
echo 250000 > cpu.cfs_period_us
限制 2 核(每 500ms 内用 1000ms CPU 时间):
echo 1000000 > cpu.cfs_quota_us
echo 500000 > cpu.cfs_period_us
限制 1 核的 20%(每 50ms 用 10ms CPU 时间):
echo 10000 > cpu.cfs_quota_us
echo 50000 > cpu.cfs_period_us
简单总结:
- requests 保证容器最少能用的 CPU 资源。
- limits 确保容器最多使用的 CPU 时间不会超过限制。
默认限制
要为命名空间中的 Pod 设置默认的资源限制,可以使用 Kubernetes 提供的 LimitRange 资源。通过配置 LimitRange,可以为每个命名空间设置默认的 requests 和 limits,从而确保 Pod 的资源分配有合理的默认值和边界限制。以下是如何实现的说明和示例:
创建一个 LimitRange 示例:
以下 YAML 文件定义了一个 LimitRange 资源:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limit
spec:
limits:
- default:
memory: 100Mi
cpu: 100m
defaultRequest:
memory: 50Mi
cpu: 50m
- max:
memory: 512Mi
cpu: 500m
- min:
memory: 50Mi
cpu: 50m
type: Container
字段解析
default:
- 设置默认的 limits 值。
- 如果 Pod 未明确指定 limits,则系统自动分配 100Mi 内存和 100m CPU。
defaultRequest:
- 设置默认的 requests 值。
- 如果 Pod 未明确指定 requests,则系统自动分配 50Mi 内存和 50m CPU。
max 和 min:
- max: 定义 limits 的最大值。如果 Pod 资源分配超过这个值,Pod 将被拒绝创建。
- min: 定义 requests 的最小值。如果 Pod 资源分配低于这个值,Pod 也将被拒绝创建。
type:
- 指定适用范围为 Container。
工作机制
Kubernetes 的LimitRanger准入控制器负责应用这些限制:
- 在创建 Pod 之前,如果 Pod 的 limits 或 requests 未设置,则自动添加 LimitRange 中的默认值。
- 如果 Pod 的资源配置超出 max 或低于 min,则拒绝创建。
示例 Pod 及 LimitRanger 插件设置的注释
apiVersion: v1
kind: Pod
metadata:
annotations:
kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu request for container limit-test'
name: limit-test
namespace: default
spec:
containers:
- name: limit-test
image: busybox
args:
- /bin/sh
- -c
- while true; do sleep 2; done
resources:
requests:
cpu: 100m
annotations: 显示 LimitRanger 插件已经为 Pod 自动添加了默认的资源 requests。
总结
1、使用 LimitRange,可以为命名空间设置默认的 requests 和 limits,避免 Pod 没有资源限制带来的风险。
2、max 和 min 设置了资源的上下限,确保 Pod 的资源分配符合命名空间的约束。
3、LimitRanger 插件会在 Pod 创建时检查并自动设置默认值,使资源限制更加自动化和规范化