这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

策略

通过策略管理安全性和最佳实践。

Kubernetes 策略是管理其他配置或运行时行为的一些配置。 Kubernetes 提供了各种形式的策略,具体如下所述:

使用 API 对象应用策略

一些 API 对象可用作策略。以下是一些示例:

使用准入控制器应用策略

准入控制器运行在 API 服务器上, 可以验证或变更 API 请求。某些准入控制器用于应用策略。 例如,AlwaysPullImages 准入控制器会修改新 Pod,将镜像拉取策略设置为 Always

Kubernetes 具有多个内置的准入控制器,可通过 API 服务器的 --enable-admission-plugins 标志进行配置。

关于准入控制器的详细信息(包括可用准入控制器的完整列表),请查阅专门的章节:

使用 ValidatingAdmissionPolicy 应用策略

验证性的准入策略允许使用通用表达式语言 (CEL) 在 API 服务器中执行可配置的验证检查。 例如,ValidatingAdmissionPolicy 可用于禁止使用 latest 镜像标签。

ValidatingAdmissionPolicy 对请求 API 进行操作,可就不合规的配置执行阻止、审计和警告用户等操作。 有关 ValidatingAdmissionPolicy API 的详细信息及示例,请查阅专门的章节:

使用动态准入控制应用策略

动态准入控制器(或准入 Webhook)作为单独的应用在 API 服务器之外运行, 这些应用注册自身后可以接收 Webhook 请求以便对 API 请求进行验证或变更。

动态准入控制器可用于在 API 请求上应用策略并触发其他基于策略的工作流。 动态准入控制器可以执行一些复杂的检查,包括需要读取其他集群资源和外部数据的复杂检查。 例如,镜像验证检查可以从 OCI 镜像仓库中查找数据,以验证容器镜像签名和证明信息。

有关动态准入控制的详细信息,请查阅专门的章节:

实现

Kubernetes 生态系统中正在开发作为灵活策略引擎的动态准入控制器,例如:

使用 Kubelet 配置应用策略

Kubernetes 允许在每个工作节点上配置 Kubelet。一些 Kubelet 配置可以视为策略:

1 - 限制范围(LimitRange)

默认情况下, Kubernetes 集群上的容器运行使用的计算资源没有限制。 使用 Kubernetes 资源配额, 管理员(也称为集群操作者)可以在一个指定的命名空间内限制集群资源的使用与创建。 在命名空间中,一个 Pod 最多能够使用命名空间的资源配额所定义的 CPU 和内存用量。 作为集群操作者或命名空间级的管理员,你可能也会担心如何确保一个 Pod 不会垄断命名空间内所有可用的资源。

LimitRange 是限制命名空间内可为每个适用的对象类别 (例如 Pod 或 PersistentVolumeClaim) 指定的资源分配量(限制和请求)的策略对象。

一个 LimitRange(限制范围) 对象提供的限制能够做到:

  • 在一个命名空间中实施对每个 Pod 或 Container 最小和最大的资源使用量的限制。
  • 在一个命名空间中实施对每个 PersistentVolumeClaim 能申请的最小和最大的存储空间大小的限制。
  • 在一个命名空间中实施对一种资源的申请值和限制值的比值的控制。
  • 设置一个命名空间中对计算资源的默认申请/限制值,并且自动的在运行时注入到多个 Container 中。

只要特定命名空间中至少有一个 LimitRange 对象,Kubernetes 就会限制对该命名空间中的 Pod 的资源分配。

LimitRange 的名称必须是合法的 DNS 子域名

资源限制和请求的约束

  • 管理员在一个命名空间内创建一个 LimitRange 对象。
  • 用户在此命名空间内创建(或尝试创建) Pod 和 PersistentVolumeClaim 等对象。
  • 首先,LimitRange 准入控制器对所有没有设置计算资源需求的所有 Pod(及其容器)设置默认请求值与限制值。
  • 其次,LimitRange 跟踪其使用量以保证没有超出命名空间中存在的任意 LimitRange 所定义的最小、最大资源使用量以及使用量比值。
  • 若尝试创建或更新的对象(Pod 和 PersistentVolumeClaim)违反了 LimitRange 的约束, 向 API 服务器的请求会失败,并返回 HTTP 状态码 403 Forbidden 以及描述哪一项约束被违反的消息。
  • 若你在命名空间中添加 LimitRange 启用了对 cpumemory 等计算相关资源的限制, 你必须指定这些值的请求使用量与限制使用量。否则,系统将会拒绝创建 Pod。
  • LimitRange 的验证仅在 Pod 准入阶段进行,不对正在运行的 Pod 进行验证。 如果你添加或修改 LimitRange,命名空间中已存在的 Pod 将继续不变。
  • 如果命名空间中存在两个或更多 LimitRange 对象,应用哪个默认值是不确定的。

Pod 的 LimitRange 和准入检查

LimitRange 检查所应用的默认值的一致性。 这意味着 LimitRange 设置的 limit 的默认值可能小于客户端提交给 API 服务器的规约中为容器指定的 request 值。 如果发生这种情况,最终 Pod 将无法调度。

例如,你使用如下清单定义一个 LimitRange:

apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-resource-constraint
spec:
  limits:
  - default: # 此处定义默认限制值
      cpu: 500m
    defaultRequest: # 此处定义默认请求值
      cpu: 500m
    max: # max 和 min 定义限制范围
      cpu: "1"
    min:
      cpu: 100m
    type: Container

以及一个声明 CPU 资源请求为 700m 但未声明限制值的 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: example-conflict-with-limitrange-cpu
spec:
  containers:
  - name: demo
    image: registry.k8s.io/pause:2.0
    resources:
      requests:
        cpu: 700m

那么该 Pod 将不会被调度,失败并出现类似以下的错误:

Pod "example-conflict-with-limitrange-cpu" is invalid: spec.containers[0].resources.requests: Invalid value: "700m": must be less than or equal to cpu limit

如果你同时设置了 requestlimit,那么即使使用相同的 LimitRange,新 Pod 也会被成功调度:

apiVersion: v1
kind: Pod
metadata:
  name: example-no-conflict-with-limitrange-cpu
spec:
  containers:
  - name: demo
    image: registry.k8s.io/pause:2.0
    resources:
      requests:
        cpu: 700m
      limits:
        cpu: 700m

资源约束示例

能够使用限制范围创建的策略示例有:

  • 在一个有两个节点,8 GiB 内存与16个核的集群中,限制一个命名空间的 Pod 申请 100m 单位,最大 500m 单位的 CPU,以及申请 200Mi,最大 600Mi 的内存。
  • 为 spec 中没有 cpu 和内存需求值的 Container 定义默认 CPU 限制值与需求值 150m,内存默认需求值 300Mi。

在命名空间的总限制值小于 Pod 或 Container 的限制值的总和的情况下,可能会产生资源竞争。 在这种情况下,将不会创建 Container 或 Pod。

竞争和对 LimitRange 的改变都不会影响任何已经创建了的资源。

接下来

关于使用限值的例子,可参阅:

有关上下文和历史信息,请参阅 LimitRanger 设计文档

2 - 资源配额

当多个用户或团队共享具有固定节点数目的集群时,人们会担心有人使用超过其基于公平原则所分配到的资源量。

资源配额是帮助管理员解决这一问题的工具。

资源配额,通过 ResourceQuota 对象来定义,对每个命名空间的资源消耗总量提供限制。 它可以限制命名空间中某种类型的对象的总数目上限,也可以限制命名空间中的 Pod 可以使用的计算资源的总上限。

资源配额的工作方式如下:

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

  • 集群管理员可以为每个命名空间创建一个或多个 ResourceQuota 对象。

  • 当用户在命名空间下创建资源(如 Pod、Service 等)时,Kubernetes 的配额系统会跟踪集群的资源使用情况, 以确保使用的资源用量不超过 ResourceQuota 中定义的硬性资源限额。

  • 如果资源创建或者更新请求违反了配额约束,那么该请求会报错(HTTP 403 FORBIDDEN), 并在消息中给出有可能违反的约束。

  • 如果命名空间下的计算资源(如 cpumemory)的配额被启用, 则用户必须为这些资源设定请求值(request)和约束值(limit),否则配额系统将拒绝 Pod 的创建。 提示: 可使用 LimitRanger 准入控制器来为没有设置计算资源需求的 Pod 设置默认值。

    若想避免这类问题,请参考 演练示例。

ResourceQuota 对象的名称必须是合法的 DNS 子域名

下面是使用命名空间和配额构建策略的示例:

  • 在具有 32 GiB 内存和 16 核 CPU 资源的集群中,允许 A 团队使用 20 GiB 内存 和 10 核的 CPU 资源, 允许 B 团队使用 10 GiB 内存和 4 核的 CPU 资源,并且预留 2 GiB 内存和 2 核的 CPU 资源供将来分配。
  • 限制 "testing" 命名空间使用 1 核 CPU 资源和 1GiB 内存。允许 "production" 命名空间使用任意数量。

在集群容量小于各命名空间配额总和的情况下,可能存在资源竞争。资源竞争时,Kubernetes 系统会遵循先到先得的原则。

不管是资源竞争还是配额的修改,都不会影响已经创建的资源使用对象。

启用资源配额

ResourceQuota 的支持在很多 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 相同。

扩展资源的资源配额

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

由于扩展资源不可超量分配,因此没有必要在配额中为同一扩展资源同时指定 requestslimits。 对于扩展资源而言,仅允许使用前缀为 requests. 的配额项。

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

  • requests.nvidia.com/gpu: 4

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

存储资源配额

用户可以对给定命名空间下的存储资源 总量进行限制。

此外,还可以根据相关的存储类(Storage Class)来限制存储资源的消耗。

资源名称描述
requests.storage所有 PVC,存储资源的需求总量不能超过该值。
persistentvolumeclaims在该命名空间中所允许的 PVC 总量。
<storage-class-name>.storageclass.storage.k8s.io/requests.storage在所有与 <storage-class-name> 相关的持久卷申领中,存储请求的总和不能超过该值。
<storage-class-name>.storageclass.storage.k8s.io/persistentvolumeclaims在与 storage-class-name 相关的所有持久卷申领中,命名空间中可以存在的持久卷申领总数。

例如,如果你想要将 gold StorageClass 与 bronze StorageClass 分开进行存储配额配置, 则可以按如下方式定义配额:

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

在 Kubernetes 1.8 版本中,本地临时存储的配额支持已经是 Alpha 功能:

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

对象数量配额

你可以使用以下语法为 Kubernetes API 中“一种特定资源类型的总数”设置配额:

  • count/<resource>.<group>:用于非核心(core)组的资源
  • 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 而导致集群拒绝服务。

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

支持以下类型:

资源名称描述
configmaps在该命名空间中允许存在的 ConfigMap 总数上限。
persistentvolumeclaims在该命名空间中允许存在的 PVC 的总数上限。
pods在该命名空间中允许存在的非终止状态的 Pod 总数上限。Pod 终止状态等价于 Pod 的 .status.phase in (Failed, Succeeded) 为真。
replicationcontrollers在该命名空间中允许存在的 ReplicationController 总数上限。
resourcequotas在该命名空间中允许存在的 ResourceQuota 总数上限。
services在该命名空间中允许存在的 Service 总数上限。
services.loadbalancers在该命名空间中允许存在的 LoadBalancer 类型的 Service 总数上限。
services.nodeports在该命名空间中允许存在的 NodePort 或 LoadBalancer 类型的 Service 的 NodePort 总数上限。
secrets在该命名空间中允许存在的 Secret 总数上限。

例如,pods 配额统计某个命名空间中所创建的、非终止状态的 pods 个数并确保其不超过某上限值。 用户可能希望在某命名空间中设置 pods 配额,以避免有用户创建很多小的 Pod, 从而耗尽集群所能提供的 Pod IP 地址。

你可以在查看和设置配额节查看更多示例。

配额作用域

每个配额都有一组相关的 scope(作用域),配额只会对作用域内的资源生效。 配额机制仅统计所列举的作用域的交集中的资源用量。

当一个作用域被添加到配额中后,它会对作用域相关的资源数量作限制。 如配额中指定了允许(作用域)集合之外的资源,会导致验证错误。

作用域描述
Terminating匹配所有 spec.activeDeadlineSeconds 不小于 0 的 Pod。
NotTerminating匹配所有 spec.activeDeadlineSeconds 是 nil 的 Pod。
BestEffort匹配所有 Qos 是 BestEffort 的 Pod。
NotBestEffort匹配所有 Qos 不是 BestEffort 的 Pod。
PriorityClass匹配所有引用了所指定的优先级类的 Pod。
CrossNamespacePodAffinity匹配那些设置了跨名字空间(反)亲和性条件的 Pod。

BestEffort 作用域限制配额跟踪以下资源:

  • pods

TerminatingNotTerminatingNotBestEffortPriorityClass 这些作用域限制配额跟踪以下资源:

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

需要注意的是,你不可以在同一个配额对象中同时设置 TerminatingNotTerminating 作用域,你也不可以在同一个配额中同时设置 BestEffortNotBestEffort 作用域。

scopeSelector 支持在 operator 字段中使用以下值:

  • In
  • NotIn
  • Exists
  • DoesNotExist

定义 scopeSelector 时,如果使用以下值之一作为 scopeName 的值,则对应的 operator 只能是 Exists

  • Terminating
  • NotTerminating
  • BestEffort
  • NotBestEffort

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

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

如果 operatorExistsDoesNotExist,则可以设置 values 字段。

基于优先级类(PriorityClass)来设置资源配额

特性状态: Kubernetes v1.17 [stable]

Pod 可以创建为特定的优先级。 通过使用配额规约中的 scopeSelector 字段,用户可以根据 Pod 的优先级控制其系统资源消耗。

仅当配额规范中的 scopeSelector 字段选择到某 Pod 时,配额机制才会匹配和计量 Pod 的资源消耗。

如果配额对象通过 scopeSelector 字段设置其作用域为优先级类, 则配额对象只能跟踪以下资源:

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

本示例创建一个配额对象,并将其与具有特定优先级的 Pod 进行匹配,其工作方式如下:

  • 集群中的 Pod 可取三个优先级类之一,即 "low"、"medium"、"high"。
  • 为每个优先级创建一个配额对象。

将以下 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 命令运行以下操作。

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

创建优先级为 "high" 的 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

确认 "high" 优先级配额 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 [stable]

集群运维人员可以使用 CrossNamespacePodAffinity 配额作用域来限制哪个名字空间中可以存在包含跨名字空间亲和性规则的 Pod。 更为具体一点,此作用域用来配置哪些 Pod 可以在其 Pod 亲和性规则中设置 namespacesnamespaceSelector 字段。

禁止用户使用跨名字空间的亲和性规则可能是一种被需要的能力, 因为带有反亲和性约束的 Pod 可能会阻止所有其他名字空间的 Pod 被调度到某失效域中。

使用此作用域操作符可以避免某些名字空间(例如下面例子中的 foo-ns)运行特别的 Pod, 这类 Pod 使用跨名字空间的 Pod 亲和性约束,在该名字空间中创建了作用域为 CrossNamespacePodAffinity 的、硬性约束为 0 的资源配额对象。

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

如果集群运维人员希望默认禁止使用 namespacesnamespaceSelector, 而仅仅允许在特定命名空间中这样做,他们可以将 CrossNamespacePodAffinity 作为一个被约束的资源。方法是为 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: CrossNamespacePodAffinity
        operator: Exists

基于上面的配置,只有名字空间中包含作用域为 CrossNamespacePodAffinity 且硬性约束大于或等于使用 namespacesnamespaceSelector 字段的 Pod 个数时,才可以在该名字空间中继续创建在其 Pod 亲和性规则中设置 namespacesnamespaceSelector 的新 Pod。

请求与限制的比较

分配计算资源时,每个容器可以为 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

配额和集群容量

ResourceQuota 与集群资源总量是完全独立的。它们通过绝对的单位来配置。 所以,为集群添加节点时,资源配额不会自动赋予每个命名空间消耗更多资源的能力。

有时可能需要资源配额支持更复杂的策略,比如:

  • 在几个团队中按比例划分总的集群资源。
  • 允许每个租户根据需要增加资源使用量,但要有足够的限制以防止资源意外耗尽。
  • 探测某个命名空间的需求,添加物理节点并扩大资源配额值。

这些策略可以通过将资源配额作为一个组成模块、手动编写一个控制器来监控资源使用情况, 并结合其他信号调整命名空间上的硬性资源配额来实现。

注意:资源配额对集群资源总体进行划分,但它对节点没有限制:来自不同命名空间的 Pod 可能在同一节点上运行。

默认情况下限制特定优先级的资源消耗

有时候可能希望当且仅当某名字空间中存在匹配的配额对象时,才可以创建特定优先级 (例如 "cluster-services")的 Pod。

通过这种机制,操作人员能够限制某些高优先级类仅出现在有限数量的命名空间中, 而并非每个命名空间默认情况下都能够使用这些优先级类。

要实现此目的,应设置 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 之外的别的名字空间,则 Pod 创建请求也被拒绝。

接下来

3 - 进程 ID 约束与预留

特性状态: Kubernetes v1.20 [stable]

Kubernetes 允许你限制一个 Pod 中可以使用的进程 ID(PID)数目。 你也可以为每个节点预留一定数量的可分配的 PID, 供操作系统和守护进程(而非 Pod)使用。

进程 ID(PID)是节点上的一种基础资源。很容易就会在尚未超出其它资源约束的时候就已经触及任务个数上限, 进而导致宿主机器不稳定。

集群管理员需要一定的机制来确保集群中运行的 Pod 不会导致 PID 资源枯竭, 甚而造成宿主机上的守护进程(例如 kubelet 或者 kube-proxy 乃至包括容器运行时本身)无法正常运行。 此外,确保 Pod 中 PID 的个数受限对于保证其不会影响到同一节点上其它负载也很重要。

你可以配置 kubelet 限制给定 Pod 能够使用的 PID 个数。 例如,如果你的节点上的宿主操作系统被设置为最多可使用 262144 个 PID, 同时预期节点上会运行的 Pod 个数不会超过 250,那么你可以为每个 Pod 设置 1000 个 PID 的预算,避免耗尽该节点上可用 PID 的总量。 如果管理员系统像 CPU 或内存那样允许对 PID 进行过量分配(Overcommit),他们也可以这样做, 只是会有一些额外的风险。不管怎样,任何一个 Pod 都不可以将整个机器的运行状态破坏。 这类资源限制有助于避免简单的派生炸弹(Fork Bomb)影响到整个集群的运行。

在 Pod 级别设置 PID 限制使得管理员能够保护 Pod 之间不会互相伤害, 不过无法确保所有调度到该宿主机器上的所有 Pod 都不会影响到节点整体。 Pod 级别的限制也无法保护节点代理任务自身不会受到 PID 耗尽的影响。

你也可以预留一定量的 PID,作为节点的额外开销,与分配给 Pod 的 PID 集合独立。 这有点类似于在给操作系统和其它设施预留 CPU、内存或其它资源时所做的操作, 这些任务都在 Pod 及其所包含的容器之外运行。

PID 限制是与计算资源 请求和限制相辅相成的一种机制。不过,你需要用一种不同的方式来设置这一限制: 你需要将其设置到 kubelet 上而不是在 Pod 的 .spec 中为 Pod 设置资源限制。 目前还不支持在 Pod 级别设置 PID 限制。

节点级别 PID 限制

Kubernetes 允许你为系统预留一定量的进程 ID。为了配置预留数量,你可以使用 kubelet 的 --system-reserved--kube-reserved 命令行选项中的参数 pid=<number>。你所设置的参数值分别用来声明为整个系统和 Kubernetes 系统守护进程所保留的进程 ID 数目。

Pod 级别 PID 限制

Kubernetes 允许你限制 Pod 中运行的进程个数。你可以在节点级别设置这一限制, 而不是为特定的 Pod 来将其设置为资源限制。每个节点都可以有不同的 PID 限制设置。 要设置限制值,你可以设置 kubelet 的命令行参数 --pod-max-pids,或者在 kubelet 的配置文件中设置 PodPidsLimit

基于 PID 的驱逐

你可以配置 kubelet 使之在 Pod 行为不正常或者消耗不正常数量资源的时候将其终止。这一特性称作驱逐。 你可以针对不同的驱逐信号配置资源不足的处理。 使用 pid.available 驱逐信号来配置 Pod 使用的 PID 个数的阈值。 你可以设置硬性的和软性的驱逐策略。不过,即使使用硬性的驱逐策略, 如果 PID 个数增长过快,节点仍然可能因为触及节点 PID 限制而进入一种不稳定状态。 驱逐信号的取值是周期性计算的,而不是一直能够强制实施约束。

Pod 级别和节点级别的 PID 限制会设置硬性限制。 一旦触及限制值,工作负载会在尝试获得新的 PID 时开始遇到问题。 这可能会也可能不会导致 Pod 被重新调度,取决于工作负载如何应对这类失败以及 Pod 的存活性和就绪态探测是如何配置的。 可是,如果限制值被正确设置,你可以确保其它 Pod 负载和系统进程不会因为某个 Pod 行为不正常而没有 PID 可用。

接下来

4 - 节点资源管理器

Kubernetes 提供了一组资源管理器,用于支持延迟敏感的、高吞吐量的工作负载。 资源管理器的目标是协调和优化节点资源,以支持对 CPU、设备和内存(巨页)等资源有特殊需求的 Pod。

硬件拓扑对齐策略

**拓扑管理器(Topology Manager)**是一个 kubelet 组件,旨在协调负责这些优化的组件集。 整体资源管理过程通过你指定的策略进行管理。 要了解更多信息,请阅读控制节点上的拓扑管理策略

为 Pod 分配 CPU 的策略

特性状态: Kubernetes v1.26 [stable] (enabled by default: true)

一旦 Pod 绑定到节点,该节点上的 kubelet 可能需要多路复用现有硬件(例如,在多个 Pod 之间共享 CPU), 或者通过专门划分一些资源来分配硬件(例如,为 Pod 独占使用分配一个或多个 CPU)。

默认情况下,kubelet 使用 CFS 配额 来强制执行 Pod 的 CPU 限制。当节点运行许多 CPU 密集型 Pod 时,工作负载可能会移动到不同的 CPU 核, 具体取决于 Pod 执行是否受到抑制以及调度时刻哪些 CPU 核可用。 许多工作负载对这种迁移不敏感,因此无需任何干预即可正常工作。

但是,对于某些工作负载而言,CPU 缓存亲和性和调度延迟会显著影响其性能;针对这些工作负载, kubelet 允许使用不同的 CPU 管理策略来确定节点上的一些放置偏好。 这是使用 CPU 管理器(CPU Manager) 及其策略实现的。 有两种可用的策略:

  • nonenone 策略显式启用现有的默认 CPU 亲和性方案,除了操作系统调度器自动执行的操作外,不提供任何亲和性。 使用 CFS 配额强制为 Guaranteed PodBurstable Pod 实施 CPU 使用限制。
  • staticstatic 策略允许具有整数 CPU requestsGuaranteed Pod 中的容器访问节点上的独占 CPU。 这种独占性是使用 cpuset cgroup 控制器 来强制保证的。

CPU 管理器不支持在运行时热插拔 CPU。

静态策略

静态策略可实现更精细的 CPU 管理和独占性的 CPU 分配。 此策略管理一个共享 CPU 池,该池最初包含节点中的所有 CPU。 可独占分配的 CPU 数量等于节点中的 CPU 总数减去 kubelet 配置所设置的所有预留 CPU。 kubelet 选项所预留的 CPU 以整数数量按物理核心 ID 的升序从初始共享池中取用。 此共享池是供 BestEffortBurstable Pod 中的所有容器运行使用的 CPU 集。 CPU requests 为小数值的 Guaranteed Pod 中的容器也在共享池中的 CPU 上运行。 只有属于 Guaranteed Pod 且具有整数 CPU requests 的容器才会被分配独占 CPU。

当容器满足静态分配要求的 Guaranteed Pod 被调度到节点时,kubelet 会从共享池中删除 CPU 并将其放入容器的 cpuset 中。 kubelet 不使用 CFS 配额来限制这些容器的 CPU 使用率,因为它们的使用率受调度域本身限制。 换句话说,容器 cpuset 中的 CPU 数量等于 Pod 规约中指定的整数 CPU limit。 这种静态分配会提高 CPU 亲和性,并减少因 CPU 密集型工作负载下因为限流而导致的上下文切换。

考虑以下 Pod 规约中的容器:

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
      requests:
        memory: "100Mi"

上面的 Pod 以 BestEffort QoS 类运行,因为它没有指定资源 requestslimits。 它在共享池中运行。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"
      requests:
        memory: "200Mi"
        cpu: "2"

上面的 Pod 以 Guaranteed QoS 类运行,因为其 requests 等于 limits。 并且 CPU 资源的容器资源限制是大于或等于 1 的整数。 nginx 容器被授予 2 个独占 CPU。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "1.5"
      requests:
        memory: "200Mi"
        cpu: "1.5"

上面的 Pod 以 Guaranteed QoS 类运行,因为其 requests 等于 limits。 但 CPU 资源的容器资源限制是一个小数。它在共享池中运行。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"

上面的 Pod 以 Guaranteed QoS 类运行,因为仅指定了 limits,并且在未显式指定时 requests 会被设置为等于 limits。 并且 CPU 资源的容器资源限制是大于或等于 1 的整数。 nginx 容器被授予 2 个独占 CPU。

策略选项

你可以使用 CPU 管理器策略选项微调 static 策略的行为。 static CPU 管理策略存在以下策略选项: {{/* options in alphabetical order */}}

align-by-socket(Alpha,默认隐藏):
以物理芯片/插槽为边界(而不是逻辑 NUMA 边界)对齐 CPU(自 Kubernetes v1.25 起可用)
distribute-cpus-across-cores(Alpha,默认隐藏):
跨多个不同的物理核心分配虚拟核心(有时称为硬件线程)(自 Kubernetes v1.31 起可用)
distribute-cpus-across-numa(Alpha,默认隐藏):
跨多个不同的 NUMA 域分配 CPU,力求在所选域之间实现均匀平衡(自 Kubernetes v1.23 起可用)
full-pcpus-only(Beta,默认可见):
始终分配完整的物理核心(自 Kubernetes v1.22 起可用)
strict-cpu-reservation(Alpha,默认隐藏):
阻止所有 Pod(无论其服务质量类别如何)在预留的 CPU 上运行(自 Kubernetes v1.32 起可用)
prefer-align-cpus-by-uncorecache(Alpha,默认隐藏):
尽可能通过非核心(最后一级)高速缓存边界对齐 CPU(自 Kubernetes v1.32 起可用)

你可以使用以下特性门控根据选项组的成熟度级别来启用或禁止它们:

  • CPUManagerPolicyBetaOptions(默认启用)。禁用以隐藏 Beta 级选项。
  • CPUManagerPolicyAlphaOptions(默认禁用)。启用以显示 Alpha 级选项。 你仍然必须使用 kubelet 配置文件中的 cpuManagerPolicyOptions 字段启用每个选项。

有关可以配置的各个选项的更多详细信息,请继续阅读。

full-pcpus-only

如果指定了 full-pcpus-only 策略选项,则 static 策略将始终分配完整的物理核心。 默认情况下,如果没有此选项,static 策略将使用拓扑感知的最佳匹配策略来分配 CPU。 在启用 SMT 的系统上,该策略可以分配与硬件线程对应的一个个虚拟核心。 这样做会导致不同的容器共享相同的物理核;这种行为反过来会导致吵闹的邻居问题。 启用该选项后,仅当可以通过分配完整的物理核心来满足某 Pod 中所有容器的 CPU 请求时,kubelet 才会接受该 Pod。 如果 Pod 未通过准入,则系统会将其置于 Failed 状态,并显示消息 SMTAlignmentError。

distribute-cpus-across-numa

如果指定了 distribute-cpus-across-numa 策略选项,则在需要多个 NUMA 节点来满足分配的情况下, static 策略将跨多个 NUMA 节点均匀分配 CPU。 默认情况下,CPUManager 会将 CPU 打包到一个 NUMA 节点上,直到它被填满,剩余的所有 CPU 会溢出到下一个 NUMA 节点。 这可能会导致依赖于障碍(和类似的同步原语)的并行代码出现不希望的瓶颈,、因为这种类型的代码往往只会以其最慢的工作程序的速度运行(这一工作程序因为至少一个 NUMA 节点上的可用 CPU 较少而被减速)。 通过在跨多个 NUMA 节点均匀分配 CPU,应用程序开发人员可以更轻松地确保没有单个工作程序比所有其他工作程序受 NUMA 影响更严重,从而提高这些类型的应用的整体性能。

align-by-socket

如果指定了 align-by-socket 策略选项,则在决定如何将 CPU 分配给容器时,CPU 将被视为以插槽为边界对齐。 默认情况下,CPUManager 会在 NUMA 边界处对齐 CPU 分配,如果需要从多个 NUMA 节点提取 CPU 才能满足分配,则可能会导致性能下降。 虽然它试图确保所有 CPU 都从_最少_数量的 NUMA 节点中分配,但无法保证这些 NUMA 节点会在同一插槽上。 通过指示 CPUManager 以插槽为边界而不是以 NUMA 节点为边界显式对齐 CPU,我们可以避免此类问题。 请注意,此策略选项与 TopologyManager 的 single-numa-node 策略不兼容, 并且不适用于插槽数量大于 NUMA 节点数量的硬件。

distribute-cpus-across-cores

如果指定了 distribute-cpus-across-cores 策略选项,则 static 策略将尝试跨多个不同的物理核来分配虚拟核(硬件线程)。 默认情况下,CPUManager 倾向于将 CPU 打包到尽可能少的物理核上,这可能会导致同一物理核上的 CPU 之间发生争用,并导致性能瓶颈。 通过启用 distribute-cpus-across-cores 策略,static 策略可确保 CPU 分布在尽可能多的物理核上, 从而减少同一物理核上的争用,从而提高整体性能。 但是,重要的是要注意,当系统负载过重时,此策略的效果可能会降低。 在这种情况下,减少争用的好处会减少。 相反,默认行为可以帮助减少处理器核之间的通信开销,从而可能在高负载条件下提供更好的性能。

strict-cpu-reservation

KubeletConfiguration 中的 reservedSystemCPUs 参数 或已弃用的 kubelet 命令行选项 --reserved-cpus 定义显式的 CPU 集合, 用来运行操作系统系统守护进程和 Kubernetes 系统守护进程。 有关此参数的更多详细信息,请参见显式预留 CPU 列表页面。 默认情况下,此隔离仅针对 CPU 请求数量为整数的 Guaranteed 类的 Pod 实现, 而不适用于 Burstable 和 BestEffort 类的 Pod (以及具有小数 CPU 请求的保证型 Pod)。准入仅将 CPU 请求与可分配的 CPU 进行比较。 由于 CPU 限制数量高于请求数量,因此默认行为允许 Burstable 和 BestEffort 类的 Pod 占用 reservedSystemCPUs 所预留的容量,并在实际部署中导致主机 OS 服务资源不足。 如果启用了 strict-cpu-reservation 策略选项,则 static 策略将不允许任何工作负载使用 reservedSystemCPUs 中指定的 CPU 核。

prefer-align-cpus-by-uncorecache

如果指定了 prefer-align-cpus-by-uncorecache 策略,则 static 策略为各个容器分配 CPU 资源时, 会让分配给容器的所有 CPU 共享同一个非处理核缓存块(也称为最后一级缓存或 LLC)。 默认情况下,CPUManager 会压缩打包 CPU 分配,这可能会导致分配给容器的 CPU 使用来自多个非核心的高速缓存块。 此选项使 CPUManager 能够在分配 CPU 时将非核心缓存的有效利用率最大化。 分配是在尽力而为的,目的是使共享同一非核心高速缓存的 CPU 个数尽可能多。 如果容器的 CPU 需求超过了单个非核心缓存对应的 CPU 个数,则 CPUManager 会尽量减少所使用的非核高速缓存数量,以保持最佳的非核高速缓存对齐。 某些的工作负载可以从降低缓存级别的缓存间延迟,减少嘈杂邻居的影响中受益。 如果 CPUManager 在节点具有足够资源的情况下无法最佳地对齐,则仍将使用默认的打包行为接受该容器。

内存管理策略

特性状态: Kubernetes v1.32 [stable] (enabled by default: true)

Kubernetes 内存管理器(Memory Manager) 为 Guaranteed QoS 类 中的 Pod 启用有保证的内存(和巨页)分配能力。

内存管理器采用提示生成协议,为 Pod 生成最合适的 NUMA 亲和性。 内存管理器将这些亲和性提示提交到中央管理器,即拓扑管理器(Topology Manager)。 取决于提示信息和拓扑管理器的策略,Pod 将被拒绝或允许进入节点。

此外,内存管理器可确保 Pod 请求的内存是从最少数量的 NUMA 节点中分配的。

其他资源管理器

各个管理器的配置方式会在专项文档中详细阐述: