前情提要
- 在当前工作经验中,从未限制过
namespace的资源,这次的实施工作中,使用的是第三方定制的 k8s集群,在namespace被创建时,因为yaml文件没有配置limits和requests两个参数,当yaml文件被apply后,自动对pod配置了LimitRange中定义的limits和requests,结果资源不够使用,导致容器启动过程中出现OOMKilled报错- 后续在
yaml中加上limits和requests依然有报错,导致控制器无法创建pod,原因是LimitRange中的limits.maxLimitRequestRatio配置,对于limits和requests的比例有限制
开始复盘
下面两个是 k8s 官方文档
借用一下官方文档
什么是限制范围
LimitRange是限制namespace(命名空间)内可为每个适用的对象类别 (例如Pod或PersistentVolumeClaim)指定的资源分配量(limits和requests)的策略对象- 一个
LimitRange(限制范围)对象提供的限制能够做到: - 在一个
namespace(命名空间)中实施对每个Pod或Container最小和最大的资源使用量的限制。 - 在一个
namespace(命名空间)中实施对每个PersistentVolumeClaim能申请的最小和最大的存储空间大小的限制。 - 在一个
namespace(命名空间)中实施对一种资源的requests(申请值)和limits(限制值)的比值的控制。 - 设置一个
namespace(命名空间)中对计算资源的默认requests(申请值) / limits(限制值),并且自动的在运行时注入到多个 Container 中。 - 当某
namespace(命名空间)中有一个LimitRange对象时,将在该namespace(命名空间)中实施LimitRange限制 LimitRange的名称必须是合法的DNS 子域名
资源限制和请求的约束
- 管理员在一个
namespace(命名空间)内创建一个LimitRange对象。 - 用户在此
namespace(命名空间)内创建(或尝试创建)Pod和PersistentVolumeClaim等对象。 - 首先,
LimitRanger准入控制器对所有没有设置计算资源需求的所有 Pod(及其容器)设置默认请求值与限制值。 - 其次,
LimitRange跟踪其使用量以保证没有超出命名空间中存在的任意LimitRange所定义的最小、最大资源使用量以及使用量比值。 - 若尝试创建或更新的对象(
Pod和PersistentVolumeClaim)违反了LimitRange的约束, 向 API 服务器的请求会失败,并返回 HTTP 状态码403 Forbidden以及描述哪一项约束被违反的消息。 - 若你在
namespace(命名空间)中添加LimitRange启用了对cpu和memory等计算相关资源的限制, 你必须指定这些值的请求使用量与限制使用量。否则,系统将会拒绝创建 Pod。 LimitRange的验证仅在Pod 准入阶段进行,不对正在运行的 Pod进行验证。 如果你添加或修改LimitRange,namespace(命名空间)中已存在的Pod将继续不变。
- 如果命名空间中存在两个或更多
LimitRange对象,应用哪个默认值是不确定的
实践出真知
- 这里图省事,就直接拿 k8s 自带的
default这个namespace来做演示
这里图省事,就直接部署 pod ,没有使用任何控制器(deployment,statefulset,daemonset 这一类)
- 如果是控制器启动的,以下创建
pod失败的场景,通过kubectl get pod命令会查看不到pod被创建的,使用kubectl describe <控制器名称>命令能查看到同理的报错
- 以下的示例都是拿
cpu做演示,内存是同理的
场景1
pod配置的requests超出了LimitRange配置的limits
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: LimitRange metadata: name: cpu-resource-constraint spec: limits: - default: # 此处定义默认限制值(limits) cpu: 500m defaultRequest: # 此处定义默认请求值(requests) cpu: 500m type: Container EOF
可以通过 kubectl get limitrange 命令查看是否创建成功
创建一个 pod 来验证
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: busybox namespace: default spec: containers: - name: busybox image: busybox:1.28.3 command: - sleep - "36000" imagePullPolicy: IfNotPresent resources: requests: cpu: 700m restartPolicy: Always EOF
这个时候就会有报错出现 The Pod "busybox" is invalid: spec.containers[0].resources.requests: Invalid value: "700m": must be less than or equal to cpu limit
- 只需要把
cpu: 700m调小一点,低于limitrange里面配置的default就可以重新运行 pod 了
场景2
继续使用场景1的 limitrange ,但是创建的 pod 同时配置了 limits 和 requests ,并且均超过 limitrange 里面配置的资源限制
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: busybox-resource-limits-requests namespace: default spec: containers: - name: busybox image: busybox:1.28.3 command: - sleep - "36000" imagePullPolicy: IfNotPresent resources: limits: cpu: 700m requests: cpu: 700m restartPolicy: Always EOF
此时,pod 是可以被创建的
场景3
继续使用
场景1的limitrange,但是创建的pod没有配置limits和requests
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: busybox-no-resource-set namespace: default spec: containers: - name: busybox image: busybox:1.28.3 command: - sleep - "36000" imagePullPolicy: IfNotPresent restartPolicy: Always EOF
pod 是肯定可以创建的,并且会自动给 pod 赋值 limits 和 requests
kubectl get pod busybox-no-resource-set -o yaml | grep 'resources' -A 4
可以看出来,limits 和 requests 都是 limitrange 内配置的 500m
resources: limits: cpu: 500m requests: cpu: 500m
场景4
继续使用
场景1的limitrange,但是创建的pod只配置了limits,并且比limitrange里面的值要高
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: busybox-just-limits namespace: default spec: containers: - name: busybox image: busybox:1.28.3 command: - sleep - "36000" imagePullPolicy: IfNotPresent resources: limits: cpu: 700m restartPolicy: Always EOF
pod 可以被成功创建,且 requests 被赋值和 limits 的值一致
kubectl get pod busybox-just-limits -o yaml | grep 'resources' -A 4
resources: limits: cpu: 700m requests: cpu: 700m
场景5
- 这里图省事,就拿内存来做示例了
limitrange配置了maxLimitRequestRatio,创建的pod的limits/requests的值不等于 1cat <<EOF | kubectl apply -f - apiVersion: v1 kind: LimitRange metadata: name: cpu-resource-constraint spec: limits: - default: # 此处定义默认限制值(limits) memory: 256Mi defaultRequest: # 此处定义默认请求值(requests) memory: 256Mi maxLimitRequestRatio: # 此处定义 limits/requests 的值必须等于 1 memory: 1 type: Container EOF
创建一个 pod 来验证
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: busybox-ratio-ne-one namespace: default spec: containers: - name: busybox image: busybox:1.28.3 command: - sleep - "36000" imagePullPolicy: IfNotPresent resources: limits: memory: 700Mi requests: memory: 70Mi restartPolicy: Always EOF
这个 pod 是无法被创建的,会返回 Error from server (Forbidden): error when creating "STDIN": pods "busybox-ratio-ne-one" is forbidden: memory max limit to request ratio per Container is 1, but provided ratio is 10.000000 这样的报错
这个 10.000000 就是 700/70=10 得来的
只需要把 limits 和 requests 的值改成一样的,就可以成功启动 pod 了
学习总结
- 当
namespace配置了limitrange,并且pod创建时没有指明limits或/和requests时,pod被创建后由limitrang的配置来指明pod的limits或/和requests - 当
namespace配置了limitrange,并且pod创建时的requests超出了limitrange配置的limits时,会有报错must be less than or equal to xxx limit
当 namespace 配置了 limitrange 以及 maxLimitRequestRatio ,并且 pod 创建时的 limits/requests 值大于 maxLimitRequestRatio 配置的值,会有报错 is forbidden: xxx max limit to request ratio per Container is xxx, but provided ratio is xxx
关于 limits , requests , maxLimitRequestRatio 的取值,主要是围绕 cpu 和 memroy 的单位来的
- 在 k8s 中,
cpu的单位为m,1000m=1核
0.1m将向上取整为1m
- 在 k8s 中,
memory的单位为k | M | G | T | P | E或者Ki | Mi | Gi | Ti | Pi | Ei
- 区别在于换算不同,一个是
1:1000,另一个是1:1024:
1m表示1000k1Mi表示1024k- 程序员眼中的整数,必须是
1024
最后,大家也可以去验证,当 memory 这里写 1.5 的时候,pod 创建完,再去 get -o yaml,就会发现 1.5 变成了 1500m