动手实践:创建第一个 ResourceQuota

理论讲完了,咱们直接上手。这一节我会带你从零创建一个 ResourceQuota,限制命名空间里的 CPU 和内存总量。别怕,操作很简单,但背后的逻辑值得你细品。

准备工作:确认环境

动手之前,先确认你的集群状态。我个人习惯用 kubectl version 看一眼客户端和服务端版本,确保都在 v1.10 以上——ResourceQuota 这个特性从那个版本开始就稳定了。

kubectl version --short
# 输出示例:
# Client Version: v1.28.2
# Server Version: v1.28.2

然后检查一下集群有没有启用 ResourceQuota 准入控制器。大部分发行版默认都开着,但万一你用的是自建集群,最好确认一下:

kubectl -n kube-system get pod -l component=kube-apiserver -o yaml | grep -A5 "enable-admission-plugins"

如果输出里没有 ResourceQuota,嗯,那得先加上再重启 API Server。不过这种情况我很少遇到,主流云厂商的托管集群都默认启用了。

第一步:创建测试命名空间

我建议你单独搞一个命名空间来做实验,别在生产命名空间里瞎折腾。我曾经手滑在 default 空间里删过东西,那叫一个酸爽……

kubectl create namespace quota-demo

或者用 YAML 也行:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
  name: quota-demo
EOF

第二步:编写 ResourceQuota 配置

这是核心。咱们先写一个最基础的 ResourceQuota,限制 CPU 和内存的总量:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: quota-demo
spec:
  hard:
    requests.cpu: "4"
    requests.memory: "8Gi"
    limits.cpu: "8"
    limits.memory: "16Gi"
EOF

你可能会问:为什么同时设 requests 和 limits?

说白了,requests 是“最低保障”,limits 是“最高上限”。集群调度器根据 requests 决定 Pod 能不能调度到某个节点,而 limits 控制 Pod 实际能用多少。两个都限制,才能做到真正的“软硬兼施”。

我的经验:刚开始做资源治理时,我只设了 limits 没设 requests。结果 Pod 虽然跑起来了,但调度器觉得节点资源充足,疯狂往一个节点上堆 Pod,最后那个节点 OOM 了。从那以后,我都是 requests 和 limits 一起设。

第三步:验证 ResourceQuota 是否生效

创建完马上查一下:

kubectl get resourcequota compute-quota -n quota-demo -o yaml

你会看到类似这样的输出:

status:
  hard:
    limits.cpu: "8"
    limits.memory: 16Gi
    requests.cpu: "4"
    requests.memory: 8Gi
  used:
    limits.cpu: "0"
    limits.memory: 0Gi
    requests.cpu: "0"
    requests.memory: 0Gi

注意那个 used 字段,现在全是 0。这说明集群已经认了这个配额,只是还没人用。

第四步:测试配额是否真的能拦住超额

光创建不算完,得试试它到底能不能拦住超额的 Pod。咱们先创建一个合规的 Pod:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: normal-pod
  namespace: quota-demo
spec:
  containers:
  - name: nginx
    image: nginx:latest
    resources:
      requests:
        cpu: "1"
        memory: "1Gi"
      limits:
        cpu: "2"
        memory: "2Gi"
EOF

这个 Pod 的 requests 是 1C/1Gi,limits 是 2C/2Gi,远低于配额上限。创建后看看配额使用情况:

kubectl get resourcequota compute-quota -n quota-demo

输出会变成:

NAME            AGE   REQUEST                    LIMIT
compute-quota   5m    requests.cpu: 1/4          limits.cpu: 2/8
                       requests.memory: 1Gi/8Gi   limits.memory: 2Gi/16Gi

看到没?used 字段从 0 变成了 1/4 和 1Gi/8Gi。配额系统在实时跟踪。

接下来,试试创建一个超额的 Pod:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: greedy-pod
  namespace: quota-demo
spec:
  containers:
  - name: stress
    image: polinux/stress
    resources:
      requests:
        cpu: "3"
        memory: "6Gi"
      limits:
        cpu: "6"
        memory: "12Gi"
    command: ["stress"]
    args: ["--cpu", "1", "--vm", "1", "--vm-bytes", "500M"]
EOF

这个 Pod 的 requests 是 3C/6Gi,加上之前那个 1C/1Gi,总共 4C/7Gi。CPU 刚好卡在配额线上,但内存超了——配额是 8Gi,这里已经用了 1Gi,再申请 6Gi 就变成 7Gi,没超。等等,我算错了?

嗯,重新算一下:已用 1Gi + 新申请 6Gi = 7Gi,确实没超 8Gi。那这个 Pod 应该能创建成功。咱们换个真正超额的:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: greedy-pod-2
  namespace: quota-demo
spec:
  containers:
  - name: stress
    image: polinux/stress
    resources:
      requests:
        cpu: "4"
        memory: "8Gi"
      limits:
        cpu: "8"
        memory: "16Gi"
    command: ["stress"]
    args: ["--cpu", "1", "--vm", "1", "--vm-bytes", "500M"]
EOF

这次会收到一个错误:

Error from server (Forbidden): error when creating "STDIN": pods "greedy-pod-2" is forbidden: exceeded quota: compute-quota, requested: requests.cpu=4,requests.memory=8Gi, used: requests.cpu=1,requests.memory=1Gi, limited: requests.cpu=4,requests.memory=8Gi

完美!配额系统直接拒绝了创建请求。这就是 ResourceQuota 的威力——它不是在 Pod 运行后去 kill,而是在创建时就拦住。

注意:ResourceQuota 是“准入控制”,不是“运行时限制”。如果配额设得太紧,可能会导致新 Pod 创建失败,影响业务扩缩容。我曾经在生产环境把 CPU 配额设得太死,结果 HPA 触发扩容时新 Pod 全被拒了,差点出事故。所以,配额要留余量,别卡得太死。

核心逻辑:一张图看懂

下面这张 SVG 图展示了 ResourceQuota 的完整工作流程:

ResourceQuota 准入控制流程 用户发起创建请求 API Server 接收请求 ResourceQuota 准入控制器拦截 检查配额是否充足? 是 允许创建,更新配额用量 否 拒绝创建,返回 Forbidden 关键点:配额检查发生在 Pod 创建阶段,而非运行时。一旦拒绝,Pod 不会进入 Pending 状态。

清理实验环境

实验做完了,记得清理:

kubectl delete namespace quota-demo

删除命名空间会连带删除里面所有的 Pod、ResourceQuota 和其他资源。干净利落。

常见问题与避坑

  • Pod 创建失败但没报错? 检查一下是不是用了 kubectl run 命令,它有时候不会显示配额拒绝的详细信息。建议用 kubectl create -f 或 kubectl apply -f。
  • 配额设了但没生效? 确认 ResourceQuota 和 Pod 在同一个命名空间。配额是命名空间级别的,跨空间不生效。
  • Pod 运行中突然被 kill? ResourceQuota 不会 kill 运行中的 Pod。如果 Pod 被 kill,通常是节点资源不足触发了 OOM Killer,跟配额无关。

核心要点回顾:

  • ResourceQuota 是准入控制,在创建时拦截超额请求
  • 同时限制 requests 和 limits 才能做到完整管控
  • 配额用尽后,新 Pod 创建会返回 Forbidden 错误
  • 配额是命名空间级别的,不同空间互不影响

好了,第一个 ResourceQuota 已经跑起来了。你可以在自己的集群里多试试不同的配额组合,比如只限制 CPU 不限制内存,或者反过来。实践出真知,动手试试比看十遍文档都管用。