资源配额

当多个用户或团队共享具有固定数量节点的集群时,需要关注的是一个团队可能会使用超过其公平份额的资源。

资源配额是管理员解决此问题的工具。

资源配额由 ResourceQuota 对象定义,它提供了限制每个命名空间聚合资源消耗的约束。它可以限制命名空间中可以创建的对象类型的数量,以及该命名空间中资源可以消耗的计算资源总量。

资源配额的工作原理如下

  • 不同的团队在不同的命名空间中工作。这可以通过 RBAC 来强制执行。

  • 管理员为每个命名空间创建一个 ResourceQuota。

  • 用户在命名空间中创建资源(Pod、服务等),配额系统会跟踪使用情况,以确保不超过 ResourceQuota 中定义的硬资源限制。

  • 如果创建或更新资源违反了配额约束,请求将失败,并显示 HTTP 状态代码 403 FORBIDDEN,并显示一条消息,说明将违反的约束。

  • 如果在命名空间中为 cpumemory 等计算资源启用了配额,则用户必须为这些值指定请求或限制;否则,配额系统可能会拒绝创建 Pod。提示:使用 LimitRanger 准入控制器为没有计算资源需求的 Pod 强制默认值。

    有关如何避免此问题的示例,请参阅演练

ResourceQuota 对象的名称必须是有效的DNS 子域名

可以使用命名空间和配额创建的策略示例如下

  • 在一个容量为 32 GiB RAM 和 16 核的集群中,让 A 团队使用 20 GiB 和 10 核,让 B 团队使用 10 GiB 和 4 核,并预留 2 GiB 和 2 核供将来分配。
  • 将“测试”命名空间限制为使用 1 核和 1 GiB RAM。让“生产”命名空间使用任意数量。

如果集群的总容量小于命名空间配额的总和,则可能会出现资源争用。这是按照先到先得的原则处理的。

争用和配额更改都不会影响已创建的资源。

启用资源配额

许多 Kubernetes 发行版默认启用资源配额支持。当API 服务器 --enable-admission-plugins= 标志的参数之一为 ResourceQuota 时,将启用该功能。

当特定命名空间中存在 ResourceQuota 时,将在该命名空间中强制执行资源配额。

计算资源配额

您可以限制在给定命名空间中可以请求的计算资源的总和。

支持以下资源类型

资源名称描述
limits.cpu在处于非终止状态的所有 Pod 中,CPU 限制的总和不能超过此值。
limits.memory在处于非终止状态的所有 Pod 中,内存限制的总和不能超过此值。
requests.cpu在处于非终止状态的所有 Pod 中,CPU 请求的总和不能超过此值。
requests.memory在处于非终止状态的所有 Pod 中,内存请求的总和不能超过此值。
hugepages-<size>在处于非终止状态的所有 Pod 中,指定大小的巨页请求数量不能超过此值。
cpurequests.cpu 相同
memoryrequests.memory 相同

扩展资源的资源配额

除了上述资源之外,在 1.10 版本中,还添加了对扩展资源的配额支持。

由于不允许对扩展资源进行超额分配,因此在配额中为同一扩展资源同时指定 requestslimits 是没有意义的。因此,对于扩展资源,目前只允许使用前缀为 requests. 的配额项。

以 GPU 资源为例,如果资源名称为 nvidia.com/gpu,并且您希望将命名空间中请求的 GPU 总数限制为 4,则可以定义如下配额

  • requests.nvidia.com/gpu: 4

有关更多详细信息,请参阅查看和设置配额

存储资源配额

您可以限制在给定命名空间中可以请求的存储资源的总和。

此外,您还可以根据关联的存储类限制存储资源的消耗。

资源名称描述
requests.storage在所有持久卷声明中,存储请求的总和不能超过此值。
persistentvolumeclaims命名空间中可以存在的PersistentVolumeClaims总数。
<storage-class-name>.storageclass.storage.k8s.io/requests.storage在与 <storage-class-name> 关联的所有持久卷声明中,存储请求的总和不能超过此值。
<storage-class-name>.storageclass.storage.k8s.io/persistentvolumeclaims在与 <storage-class-name> 关联的所有持久卷声明中,命名空间中可以存在的持久卷声明总数。

例如,如果操作员希望使用 gold 存储类对存储进行配额,并与 bronze 存储类分开,则操作员可以定义如下配额

  • gold.storageclass.storage.k8s.io/requests.storage: 500Gi
  • bronze.storageclass.storage.k8s.io/requests.storage: 100Gi

在 1.8 版本中,对本地临时存储的配额支持作为 alpha 功能添加

资源名称描述
requests.ephemeral-storage在命名空间中的所有 Pod 中,本地临时存储请求的总和不能超过此值。
limits.ephemeral-storage在命名空间中的所有 Pod 中,本地临时存储限制的总和不能超过此值。
ephemeral-storagerequests.ephemeral-storage 相同。

对象计数配额

您可以使用以下语法为 Kubernetes API 中特定资源类型的总数设置配额

  • 对于非核心组中的资源,使用 count/<resource>.<group>
  • 对于核心组中的资源,使用 count/<resource>

以下是用户可能希望将其置于对象计数配额下的一组示例资源

  • count/persistentvolumeclaims
  • count/services
  • count/secrets
  • count/configmaps
  • count/replicationcontrollers
  • count/deployments.apps
  • count/replicasets.apps
  • count/statefulsets.apps
  • count/jobs.batch
  • count/cronjobs.batch

如果您以这种方式定义配额,它将应用于作为 API 服务器一部分的 Kubernetes API,以及由 CustomResourceDefinition 支持的任何自定义资源。如果您使用API 聚合添加未定义为 CustomResourceDefinitions 的其他自定义 API,则核心 Kubernetes 控制平面不会对聚合 API 强制执行配额。如果扩展 API 服务器适合自定义 API,则应提供配额强制执行。例如,要在 example.com API 组中为 widgets 自定义资源创建配额,请使用 count/widgets.example.com

使用此类资源配额(几乎适用于所有对象类型)时,如果对象类型存在于(在控制平面中定义),则该对象将计入配额。这些类型的配额对于防止存储资源耗尽非常有用。例如,您可能希望限制服务器中 Secret 的数量,因为它们的大小很大。集群中的 Secret 太多实际上会阻止服务器和控制器启动。您可以为 Job 设置配额,以防止配置错误的 CronJob。在命名空间中创建过多 Job 的 CronJob 可能会导致拒绝服务。

还有另一种语法仅用于为某些资源设置相同类型的配额。支持以下类型

资源名称描述
configmaps命名空间中可以存在的 ConfigMap 总数。
persistentvolumeclaims命名空间中可以存在的PersistentVolumeClaims总数。
pods命名空间中可以存在的处于非终止状态的 Pod 总数。如果 .status.phase in (Failed, Succeeded) 为 true,则 Pod 处于终止状态。
replicationcontrollers命名空间中可以存在的 ReplicationController 总数。
resourcequotas命名空间中可以存在的 ResourceQuota 总数。
services命名空间中可以存在的 Service 总数。
services.loadbalancers命名空间中可以存在的 LoadBalancer 类型 Service 总数。
services.nodeports分配给命名空间中可以存在的 NodePortLoadBalancer 类型 Service 的 NodePort 总数。
secrets命名空间中可以存在的 Secret 总数。

例如,pods 配额会统计并强制限制单个命名空间中创建的非终止状态 pods 的数量。您可能希望在命名空间上设置 pods 配额,以避免用户创建许多小型 Pod 并耗尽集群的 Pod IP 地址供应的情况。

您可以在查看和设置配额中找到更多示例。

配额范围

每个配额都可以有一组关联的 scopes。只有当资源的使用量与枚举范围的交集匹配时,配额才会对其进行度量。

将范围添加到配额时,它会将其支持的资源数量限制为与该范围相关的资源。在允许集之外的配额上指定的资源会导致验证错误。

范围描述
Terminating(终止中)匹配 .spec.activeDeadlineSeconds >= 0 的 Pod
NotTerminating(非终止中)匹配 .spec.activeDeadlineSeconds is nil 的 Pod
BestEffort(尽力而为)匹配具有尽力而为服务质量的 Pod。
NotBestEffort(非尽力而为)匹配不具有尽力而为服务质量的 Pod。
PriorityClass匹配引用了指定优先级类的 Pod。
CrossNamespacePodAffinity(跨命名空间 Pod 亲和性)匹配具有跨命名空间 Pod (反)亲和性术语的 Pod。

BestEffort 范围将配额限制为仅跟踪以下资源:

  • pods

TerminatingNotTerminatingNotBestEffortPriorityClass 范围将配额限制为仅跟踪以下资源:

  • pods
  • cpu
  • memory
  • requests.cpu
  • requests.memory
  • limits.cpu
  • limits.memory

请注意,您不能在同一个配额中同时指定 TerminatingNotTerminating 范围,也不能在同一个配额中同时指定 BestEffortNotBestEffort 范围。

scopeSelectoroperator 字段中支持以下值:

  • In(包含)
  • NotIn(不包含)
  • Exists(存在)
  • DoesNotExist(不存在)

在定义 scopeSelector 时,如果使用以下值之一作为 scopeName,则 operator 必须为 Exists

  • Terminating(终止中)
  • NotTerminating(非终止中)
  • BestEffort(尽力而为)
  • NotBestEffort(非尽力而为)

如果 operatorInNotIn,则 values 字段必须至少有一个值。例如:

  scopeSelector:
    matchExpressions:
      - scopeName: PriorityClass
        operator: In
        values:
          - middle

如果 operatorExistsDoesNotExist,则 values 字段不得指定。

每个优先级类的资源配额

功能状态: Kubernetes v1.17 [稳定]

可以以特定的优先级创建 Pod。您可以使用配额规范中的 scopeSelector 字段,根据 Pod 的优先级来控制 Pod 对系统资源的消耗。

仅当配额规范中的 scopeSelector 选择了 Pod 时,才会匹配和消耗配额。

当使用 scopeSelector 字段为优先级类设置配额范围时,配额对象仅限于跟踪以下资源:

  • pods
  • cpu
  • memory
  • ephemeral-storage
  • limits.cpu
  • limits.memory
  • limits.ephemeral-storage
  • requests.cpu
  • requests.memory
  • requests.ephemeral-storage

此示例创建一个配额对象,并将其与特定优先级的 Pod 进行匹配。该示例的工作原理如下:

  • 集群中的 Pod 具有“低”、“中”、“高”三种优先级类之一。
  • 为每个优先级创建一个配额对象。

将以下 YAML 保存到文件 quota.yml 中。

apiVersion: v1
kind: List
items:
- apiVersion: v1
  kind: ResourceQuota
  metadata:
    name: pods-high
  spec:
    hard:
      cpu: "1000"
      memory: 200Gi
      pods: "10"
    scopeSelector:
      matchExpressions:
      - operator : In
        scopeName: PriorityClass
        values: ["high"]
- apiVersion: v1
  kind: ResourceQuota
  metadata:
    name: pods-medium
  spec:
    hard:
      cpu: "10"
      memory: 20Gi
      pods: "10"
    scopeSelector:
      matchExpressions:
      - operator : In
        scopeName: PriorityClass
        values: ["medium"]
- apiVersion: v1
  kind: ResourceQuota
  metadata:
    name: pods-low
  spec:
    hard:
      cpu: "5"
      memory: 10Gi
      pods: "10"
    scopeSelector:
      matchExpressions:
      - operator : In
        scopeName: PriorityClass
        values: ["low"]

使用 kubectl create 应用 YAML。

kubectl create -f ./quota.yml
resourcequota/pods-high created
resourcequota/pods-medium created
resourcequota/pods-low created

使用 kubectl describe quota 验证 Used 配额是否为 0

kubectl describe quota
Name:       pods-high
Namespace:  default
Resource    Used  Hard
--------    ----  ----
cpu         0     1k
memory      0     200Gi
pods        0     10


Name:       pods-low
Namespace:  default
Resource    Used  Hard
--------    ----  ----
cpu         0     5
memory      0     10Gi
pods        0     10


Name:       pods-medium
Namespace:  default
Resource    Used  Hard
--------    ----  ----
cpu         0     10
memory      0     20Gi
pods        0     10

创建一个优先级为“高”的 Pod。将以下 YAML 保存到文件 high-priority-pod.yml 中。

apiVersion: v1
kind: Pod
metadata:
  name: high-priority
spec:
  containers:
  - name: high-priority
    image: ubuntu
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo hello; sleep 10;done"]
    resources:
      requests:
        memory: "10Gi"
        cpu: "500m"
      limits:
        memory: "10Gi"
        cpu: "500m"
  priorityClassName: high

使用 kubectl create 应用它。

kubectl create -f ./high-priority-pod.yml

验证“高”优先级配额 pods-high 的“Used”统计信息是否已更改,而其他两个配额保持不变。

kubectl describe quota
Name:       pods-high
Namespace:  default
Resource    Used  Hard
--------    ----  ----
cpu         500m  1k
memory      10Gi  200Gi
pods        1     10


Name:       pods-low
Namespace:  default
Resource    Used  Hard
--------    ----  ----
cpu         0     5
memory      0     10Gi
pods        0     10


Name:       pods-medium
Namespace:  default
Resource    Used  Hard
--------    ----  ----
cpu         0     10
memory      0     20Gi
pods        0     10

跨命名空间 Pod 亲和性配额

功能状态: Kubernetes v1.24 [稳定]

操作员可以使用 CrossNamespacePodAffinity 配额范围来限制哪些命名空间允许具有跨命名空间的亲和性术语的 Pod。具体来说,它控制哪些 Pod 允许在 Pod 亲和性术语中设置 namespacesnamespaceSelector 字段。

可能需要阻止用户使用跨命名空间的亲和性术语,因为具有反亲和性约束的 Pod 可能会阻止所有其他命名空间中的 Pod 在故障域中被调度。

通过在该命名空间中创建一个具有 CrossNamespacePodAffinity 范围和 0 的硬限制的资源配额对象,操作员可以使用此范围来防止某些命名空间(以下示例中的 foo-ns)拥有使用跨命名空间 Pod 亲和性的 Pod:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: disable-cross-namespace-affinity
  namespace: foo-ns
spec:
  hard:
    pods: "0"
  scopeSelector:
    matchExpressions:
    - scopeName: CrossNamespacePodAffinity
      operator: Exists

如果操作员希望默认情况下禁止使用 namespacesnamespaceSelector,并且只允许特定命名空间使用,则可以通过将 kube-apiserver 标志 --admission-control-config-file 设置为以下配置文件的路径,将 CrossNamespacePodAffinity 配置为受限资源:

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: "ResourceQuota"
  configuration:
    apiVersion: apiserver.config.k8s.io/v1
    kind: ResourceQuotaConfiguration
    limitedResources:
    - resource: pods
      matchScopes:
      - scopeName: CrossNamespacePodAffinity
        operator: Exists

使用上述配置,只有当 Pod 创建所在的命名空间具有 CrossNamespacePodAffinity 范围和大于或等于使用这些字段的 Pod 数量的硬限制的资源配额对象时,Pod 才可以在 Pod 亲和性中使用 namespacesnamespaceSelector

请求与限制的比较

分配计算资源时,每个容器都可以为 CPU 或内存指定请求值和限制值。可以将配额配置为对任一值进行配额。

如果配额为 requests.cpurequests.memory 指定了值,则要求每个传入容器都对这些资源进行显式请求。如果配额为 limits.cpulimits.memory 指定了值,则要求每个传入容器都为这些资源指定显式限制。

查看和设置配额

Kubectl 支持创建、更新和查看配额:

kubectl create namespace myspace
cat <<EOF > compute-resources.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-resources
spec:
  hard:
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "2"
    limits.memory: 2Gi
    requests.nvidia.com/gpu: 4
EOF
kubectl create -f ./compute-resources.yaml --namespace=myspace
cat <<EOF > object-counts.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: object-counts
spec:
  hard:
    configmaps: "10"
    persistentvolumeclaims: "4"
    pods: "4"
    replicationcontrollers: "20"
    secrets: "10"
    services: "10"
    services.loadbalancers: "2"
EOF
kubectl create -f ./object-counts.yaml --namespace=myspace
kubectl get quota --namespace=myspace
NAME                    AGE
compute-resources       30s
object-counts           32s
kubectl describe quota compute-resources --namespace=myspace
Name:                    compute-resources
Namespace:               myspace
Resource                 Used  Hard
--------                 ----  ----
limits.cpu               0     2
limits.memory            0     2Gi
requests.cpu             0     1
requests.memory          0     1Gi
requests.nvidia.com/gpu  0     4
kubectl describe quota object-counts --namespace=myspace
Name:                   object-counts
Namespace:              myspace
Resource                Used    Hard
--------                ----    ----
configmaps              0       10
persistentvolumeclaims  0       4
pods                    0       4
replicationcontrollers  0       20
secrets                 1       10
services                0       10
services.loadbalancers  0       2

Kubectl 还支持使用语法 count/<resource>.<group> 对所有标准命名空间资源进行对象计数配额。

kubectl create namespace myspace
kubectl create quota test --hard=count/deployments.apps=2,count/replicasets.apps=4,count/pods=3,count/secrets=4 --namespace=myspace
kubectl create deployment nginx --image=nginx --namespace=myspace --replicas=2
kubectl describe quota --namespace=myspace
Name:                         test
Namespace:                    myspace
Resource                      Used  Hard
--------                      ----  ----
count/deployments.apps        1     2
count/pods                    2     3
count/replicasets.apps        1     4
count/secrets                 1     4

配额和集群容量

ResourceQuotas 独立于集群容量。它们以绝对单位表示。因此,如果您向集群添加节点,这并不会自动赋予每个命名空间消耗更多资源的能力。

有时可能需要更复杂的策略,例如:

  • 在多个团队之间按比例分配集群总资源。
  • 允许每个租户根据需要增加资源使用量,但要有一个宽松的限制,以防止意外耗尽资源。
  • 检测来自一个命名空间的需求,添加节点并增加配额。

可以通过编写一个“控制器”来实现此类策略,该控制器将 ResourceQuotas 作为构建块,监视配额使用情况,并根据其他信号调整每个命名空间的配额硬限制。

请注意,资源配额会划分聚合集群资源,但不会围绕节点创建任何限制:来自多个命名空间的 Pod 可以在同一个节点上运行。

默认情况下限制优先级类的消耗

可能希望只有在存在匹配的配额对象的情况下,才允许在命名空间中使用特定优先级的 Pod,例如“cluster-services”。

通过这种机制,操作员能够将某些高优先级类的使用限制在有限数量的命名空间内,并且默认情况下并非每个命名空间都能够消耗这些优先级类。

要强制执行此操作,应使用 kube-apiserver 标志 --admission-control-config-file 传递以下配置文件的路径:

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: "ResourceQuota"
  configuration:
    apiVersion: apiserver.config.k8s.io/v1
    kind: ResourceQuotaConfiguration
    limitedResources:
    - resource: pods
      matchScopes:
      - scopeName: PriorityClass
        operator: In
        values: ["cluster-services"]

然后,在 kube-system 命名空间中创建一个资源配额对象:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: pods-cluster-services
spec:
  scopeSelector:
    matchExpressions:
      - operator : In
        scopeName: PriorityClass
        values: ["cluster-services"]
kubectl apply -f https://k8s.io/examples/policy/priority-class-resourcequota.yaml -n kube-system
resourcequota/pods-cluster-services created

在这种情况下,如果满足以下条件,则允许创建 Pod:

  1. 未指定 Pod 的 priorityClassName
  2. 将 Pod 的 priorityClassName 指定为除 cluster-services 以外的值。
  3. 将 Pod 的 priorityClassName 设置为 cluster-services,并且要在 kube-system 命名空间中创建它,并且它已通过资源配额检查。

如果 Pod 创建请求的 priorityClassName 设置为 cluster-services,并且要在 kube-system 以外的命名空间中创建它,则该请求将被拒绝。

下一步

上次修改时间:2024 年 5 月 30 日下午 2:06 PST:更新了 NodePort 和 LoadBalancer 的 ResourceQuota 文档 (c87b19193d)