3.1 ResourceQuota Controller 简介
ResourceQuota Controller 名为资源配额管理。主要作用在于:限制指定资源对象对于系统物理资源的占用量,保证系统物理资源不会被过量使用。
资源配额管理分为 3 个层次:
- Container: 限制 CPU 和 Memory。使用 LimitRange 资源对象进行限制。
- Pod: 对 Pod 内所有容器的可用资源进行限制。使用 LimitRange 资源对象进行限制。
- Namespace: 这里可以限制的资源对象比较多,包括:Pod 数量、Replication Controller 数量、Service 数量、ResourceQuota 数量、Secret 数量、PV 数量。使用 ResourceQuota 资源对象限定各类资源的使用总额。
当创建 Pod 时,也定义了该 Pod 的 LimitRange 资源对象,API Server 在执行创建前会计算当前资源是否满足 LimitRange 的限制,如果满足则成功创建 Pod,如果不满足则不会创建 Pod。
当创建 Namespace 时,也定义了该 Namespace 的 ResourceQuota 资源对象,系统会定期统计该命名空间下的各类资源使用总量,并写入 ETCD 中。当在该命名空间下创建新资源对象时,系统会对比资源使用限额,如果满足则创建新资源对象,如果不满足就不会在该命名空间下创建新资源对象了。
计算资源管理
为一个 Pod 配置资源的预期使用量和最大使用量是 Pod 定义中的重要部分,通过设置这两组参数,可以确保 Pod 公平的使用 Kubernetes 集群资源,同时也影响着整个集群中 Pod 的调度方式。
在配置 Pod 的时候,可以为其中的每个容器指定需要使用的计算资源(CPU 和内存)。计算资源是可以被计量的,它们是能够被申请、分配和使用的基础资源,这区别于 API 资源(比如:Pod、services 等)。
计算资源分为两种:
- CPU:基本单位为核心数(Cores);资源值支持最多三位小数,如果值为 1 就代表着一个 CPU,0.5 CPU 就代表着半个 CPU,也可以表示为 500m CPU(500 millicpu)。
- 内存(Memory):基本单位为字节数(Bytes);内存值使用整数加上国际单位制来表示,其中国际单位制包括十进制的 E、P、T、G、M、K、m,或是二进制的 Ei、Pi、Ti、Gi、Mi、Ki。它们的换算如下:1 KB = 1000 bytes = 8000 bits,1KiB = 2^10 bytes = 1024 bytes = 8192 bits。
计算资源的配置分为两类:
- 资源请求(Requests):容器希望被分配到的、可完全保证的资源量,Requests 的值会提供给 Kubernetes 调度器用于优化基于资源请求的容器调度。
- 资源限制(Limits):容器最多能够使用到的资源量的上限,这个上限值会影响节点上发生资源竞争时的解决策略。
资源的服务质量管理(QoS)
当一个节点运行多个容器的时候,可能会出现这么一种情况:一个节点不能提供所有 Pod 所指定的资源 Limits 之和那么多的资源量。比如一个 Node 有 PodA 和 PodB,PodA 使用了节点内存的 95%,这个时候 PodB 又需要更多的内存容量了,很明显这个节点已经无法提供所需的内存量了,那么这个时候应该 kill 掉那个容器呢?是 PodA 还是 PodB?依据是什么呢?
这个时候就需要根据某种规则进行处理了,在 Kubernetes 中依据的规则为服务质量管理 QoS。
Pod 按照 QoS 可以划分为 3 种等级:
- BestEffort(优先级最低)
- Burstable(优先级中等)
- Guaranteed(优先级最高)
QoS 等级来源于 Pod 所包含的容器的资源 Requests 和 Limits 的配置,以下是具体划分等级的方法。
BestEffort(优先级最低)
这个等级会分配给没有为任何容器设置任何 Requests 和 Limits 的 Pod。这个等级运行的容器没有任何的资源保证,在最差的情况下,它们分配不到任何 CPU,同时当节点内存不足时它们是第一批被 kill 的;当节点内存充足时,这些容器也可以使用任意多的内存。
Guaranteed(优先级最高)
这个等级会分配给那些所有资源 Requests 和 Limits 相等的 Pod。它们需要满足以下几个条件:
- CPU 和内存都要设置 Requests 和 Limits。
- 每个容器都必须设置资源量。
- 每个容器的每种资源的 Requests 和 Limits 都必须相等。
如果没有显式设置容器的资源 Requests,则默认 Requests 和 Limits 相等,所以如果只设置 Pod 内每个容器的每种资源的 Limits,那个这个 Pod 就为最高优先级,其中的容器可以使用它所申请的等额资源,但是却无法消耗更多资源(因为 Requests 和 Limits 相等)。
Burstable(优先级中等)
Burstable 介于优先级最低和优先级最高之间,除了属于优先级最低和最高的这两种情况以外,属于其它情况的 Pod 就等属于优先级中等。可能包括的情况有:
- 容器的 Requests 和 Limits 不相同的单容器 Pod。
- 至少有一个容器只定义了 Requests 但没有定义 Limits 的 Pod。
- 一个容器的 Requests 和 Limits 相等,但是另一个容器不指定 Requests 和 Limits 的 Pod。
这种类型的 Pod 可以获得它们所申请的等额资源,而且可以使用不超过 Limits 的额外的资源。
需要注意的是:QoS 是属于 Pod 的属性,并不是容器的属性。
我们可以简单的讨论一下单容器 Pod 的 QoS 判断,因为当是单容器时,单个容器的资源状态就等同于该 Pod 的资源状态:
| CPU Requests vs Limits | 内存的 Requests vs Limits | 对应容器的 QoS 等级 |
|---|---|---|
| 未设置 | 未设置 | BestEffort(优先级最低) |
| 未设置 | Requests < Limits | Burstable(优先级中等) |
| 未设置 | Requests = Limits | Burstable(优先级中等) |
| Requests < Limits | 未设置 | Burstable(优先级中等) |
| Requests < Limits | Requests < Limits | Burstable(优先级中等) |
| Requests < Limits | Requests = Limits | Burstable(优先级中等) |
| Requests = Limits | Requests = Limits | Guaranteed(优先级最高) |
而对于多容器 Pod,如果所有容器的 QoS 等级相同,那么这个等级就是 Pod 的 QoS 等级。如果至少有一个容器的 QoS 等级与其他容器的等级不同,无论这个容器是什么等级,那么这个 Pod 的等级都是 Burstable(优先级中等)。
在判断完 Pod 的 QoS 等级后,我们再回到之前的那个问题,当系统内存不足时,需要 kill 掉容器释放资源给高优先级的 Pod 使用,那么 kill 的顺序就是按照 QoS 等级进行排序,即:BestEffort(优先级最低) 最先被 kill 掉,其次是 Burstable(优先级中等),最后才是 Guaranteed(优先级最高)。
3.2 Container 资源配额
Pod 中的每个容器都可以配置以下 4 个参数:
- spec.container[].resources.requests.cpu
- spec.container[].resources.limits.cpu
- spec.container[].resources.requests.memory
- spec.container[].resources.limits.memory
这 4 个参数分别对应容器的 CPU 和内存的 Requests 和 Limits:
- Requests 和 Limits 都是可选的。如果没有设置资源请求值或是限制值,就会使用系统提供的默认值。
- 如果没有配置 Requests,那么就默认等于 Limits。
- Limits 必须大于或等于 Requests。
下面以一个 Pod 中的两个容器的资源配置为例,新建 container-resource-test.yaml 文件并向其中写入如下内容:
apiVersion: v1
kind: Pod
metadata:
name: container-resource-test
spec:
containers:
- name: db
image: mysql:5.7
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
- name: wp
image: wordpress:5.3
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
执行创建:
$ kubectl create -f container-resource-test.yaml
pod/container-resource-test created
查看容器的资源配置:
$ kubectl describe pod/container-resource-test
Name: container-resource-test
Namespace: dev
...
Containers:
db:
Container ID: docker://9d1a82ddb33a0dd5f389e4e79c290676b7eb13fb76eb7d61880f72b740d4f588
Image: mysql:5.7
...
Limits:
cpu: 500m
memory: 128Mi
Requests:
cpu: 250m
memory: 64Mi
Environment:
MYSQL_ROOT_PASSWORD: 123456
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-v769q (ro)
wp:
Container ID: docker://5ca645f85f42f0fb3b65784f3a5b0479de569176e05428dcb7fed800b6960b4b
Image: wordpress:5.3
...
Limits:
cpu: 500m
memory: 128Mi
Requests:
cpu: 250m
memory: 64Mi
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-v769q (ro)
...
这个 Pod 包含两个容器,每个容器配置的 Requests 都是 64MiB 的内存和 0.25CPU,配置的 Limits 都是 128MiB 内存和 0.5CPU。那么这个 Pod 的 Requests 和 Limits 等于 Pod 中所有容器对应配置的总和。
在定义了 Requests 和 Limits 之后,我们来了解一下基于 Requests 和 Limits 的调度机制。当一个 Pod 创建成功时,Kubernetes 会为该 Pod 选择一个节点(Node)来执行,对于每种计算资源(CPU 和内存),每个节点都有一个能用于运行 Pod 的最大容量值。调度器在调度时,首先要保证调度后该节点上所有 Pod 的 CPU 和内存的 Requests 总和不能超过该节点能够提供给 Pod 使用的 CPU 和内存的最大容量值。
比如某个节点上 CPU 资源充足,而内存为 6GB,其中 4GB 可以用来运行 Pod,某 Pod 的内存 Requests 为 1GB、Limits 为 2GB,那么这个节点上最多运行 3 个这种 Pod。
3.3 Pod 资源配额
设置 Pod 级别的 Requests 和 Limits 能极大程度上提高我们对 Pod 管理的便利性和灵活性,对于 CPU 和内存而言,Pod 的 Requests 或 Limits 是指该 Pod 中所有容器的 Requests 或 Limits 总和。而这种设置需要使用到 LimitRange 资源。
LimitRange 资源允许用户为每个命名空间指定能给容器配置的每种资源的最小和最大限额,并支持在没有显式指定资源 Requests 时为容器设置默认值。LimitRange 资源中的 Limits 应用于同一个命名空间中每个独立的 Pod、容器或其它类型资源对象,它不会限制这个命名空间中所有 Pod 可用资源的总量,总量是通过 ResourceQuota 对象指定的。
创建一个 LimitRange 资源,新建 pod-resource-test.yaml 文件并向其中写入如下内容:
apiVersion: v1
kind: LimitRange
metadata:
name: pod-resource-test
spec:
limits:
- type: Pod
min:
cpu: 50m
memory: 5Mi
max:
cpu: 1
memory: 1Gi
- type: Container
defaultRequest:
cpu: 100m
memory: 10Mi
default:
cpu: 200m
memory: 100Mi
min:
cpu: 50m
memory: 5Mi
max:
cpu: 1
memory: 1Gi
maxLimitRequestRatio:
cpu: 4
memory: 10
- type: PersistentVolumeClaim
min:
storage: 1Gi
max:
storage: 10Gi
下面逐一进行分析:
- type: Pod
min:
cpu: 50m
memory: 5Mi
max:
cpu: 1
memory: 1Gi
这是配置整个 Pod 资源限制的最小值和最大值,它对应的是 Pod 内所有容器的 Requests 和 Limits 之和:
- min 参数表示 Pod 中所有容器的 CPU 和内存的请求量之和的最小值。
- max 参数表示 Pod 中所有容器的 CPU 和内存的请求量之和的最大值。
- type: Container
defaultRequest:
cpu: 100m
memory: 10Mi
default:
cpu: 200m
memory: 100Mi
min:
cpu: 50m
memory: 5Mi
max:
cpu: 1
memory: 1Gi
maxLimitRequestRatio:
cpu: 4
memory: 10
在容器级别中,不仅可以设置最小值和最大值,还可以为没有显式指定的容器设置默认的 Requests 和 Limits 的值。
- defaultRequest 参数表示如果容器没有指定 CPU 或内存请求量时设置的默认值。
- default 参数表示如果容器没有指定 Limits 时设置的默认值。
- maxLimitRequestRatio 参数表示每种资源 Requests 与 Limits 的最大比值,比如上面设置的 cpu: 4 表示 CPU Limits 不能超过 CPU Requests 的 4 倍,而对于内存这个倍数就为 10。
- type: PersistentVolumeClaim
min:
storage: 1Gi
max:
storage: 10Gi
这里指定 PVC(持久化内存) 存储容量的最小值和最大值。
假如想要创建一个大于 LimitRange 限制的容器,就会创建失败并报错。如果创建的容器没有设置默认的 Requests 或 Limits,就会使用 LimitRange 中设置的默认值。
执行创建并查看具体的配额限制:
$ kubectl create -f pod-resource-test.yaml -n dev
limitrange/pod-resource-test created
$ kubectl describe limitrange/pod-resource-test -n dev
Name: pod-resource-test
Namespace: dev
Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio
---- -------- --- --- --------------- ------------- -----------------------
Pod cpu 50m 1 - - -
Pod memory 5Mi 1Gi - - -
Container memory 5Mi 1Gi 10Mi 100Mi 10
Container cpu 50m 1 100m 200m 4
PersistentVolumeClaim storage 1Gi 10Gi - - -
3.4 Namespace 资源配额
LimitRange 只是应用于单独的 Pod,而如果是需要限制命名空间中的可用资源总量,可以通过 ResourceQuata 对象来实现。
限制命名空间中所有 Pod
比如限制所有 Pod 允许使用的 CPU 和内存总量:
apiVersion: v1
kind: ResourceQuota
metadata:
name: ns-resource-test
spec:
hard:
requests.cpu: 400m
requests.memory: 200Mi
limits.cpu: 600m
limits.memory: 500Mi
在这个 yaml 文件中设置了命名空间中所有 Pod 最多可申请的 CPU 数量为 400 豪核,Limits 最大总量为 600 豪核。对于内存,设置所有 Requests 最大总量为 200MiB,Limits 为 500MiB。
需要注意的是,当创建了 ResourceQuata 后,在创建 Pod 时必须为这些资源分别指定 Requests 或 Limits,否则 API 服务器不会接收该 Pod 的创建请求。
为持久化存储指定配额
ResourceQuata 也可以限制命名空间中的持久化存储总量:
apiVersion: v1
kind: ResourceQuota
metadata:
name: ns-resource-test
spec:
hard:
requests.storage: 500Mi
ssd.storageclass.storage.k8s.io/requests.storage: 300Mi
standard.storageclass.storage.k8s.io/requests.storage: 1Gi
在上面这个例子中,命名空间中可以申请的 PVC 总量为 500MiB,限制可申请的 SSD 存储总量为 300MiB,低性能的 HDD 存储限制为 1GiB。
限制可创建对象的个数
ResourceQuata 还可以用来限制单个命名空间中可以创建的其它对象的个数,比如:
apiVersion: v1
kind: ResourceQuota
metadata:
name: ns-resource-test
spec:
hard:
pods: 10
replicationcontrollers: 5
secrets: 10
configmaps: 10
persistentvolumeclaims: 4
services: 5
services.loadbalancers: 1
services.nodeports: 2
ssd.storageclass.storage.k8s.io/persistentvolumeclaims: 2
上面的例子限制了这个命名空间中最多可以创建 10 个 Pod、5 个 ReplicationController、10 个 Secret、10 个 ConfigMap、4 个 PVC 等等。
对象个数配额目前可以应用于以下这些对象:
- Pod
- ReplicationController
- Secret
- ConfigMap
- Persistent Volume Claim
- Service
- ResourceQuata 对象本身
将上面的代码合并写入一个文件,命名为 ns-resource-test.yaml,并执行创建:
$ kubectl create -f ns-resource-test.yaml -n dev
resourcequota/ns-resource-test created
$ kubectl describe resourcequota/ns-resource-test -n dev
Name: ns-resource-test
Namespace: dev
Resource Used Hard
-------- ---- ----
configmaps 0 10
limits.cpu 1 600m
limits.memory 256Mi 500Mi
persistentvolumeclaims 0 4
pods 3 10
replicationcontrollers 0 5
requests.cpu 500m 400m
requests.memory 128Mi 200Mi
requests.storage 0 500Mi
secrets 2 10
services 0 5
services.loadbalancers 0 1
services.nodeports 0 2
ssd.storageclass.storage.k8s.io/persistentvolumeclaims 0 2
ssd.storageclass.storage.k8s.io/requests.storage 0 300Mi
standard.storageclass.storage.k8s.io/requests.storage 0 1Gi
为特定的 Pod 状态或者 QoS 等级指定配额
比如只想将配额应用于 BestEffort、NotTerminating(没有设置有效期) 的 Pod,并且最多允许创建 4 个这样的 Pod,可以定义如下的 yaml 文件:
apiVersion: v1
kind: ResourceQuota
metadata:
name: ns-resource-test
spec:
scopes:
- BestEffort
- NotTerminating
hard:
pods: 4
评论区