在Kubernetes中可以从容器、Pod和名称空间级别来实现资源限制
在Pod内的容器上可以对cpu和内存资源定义需求和限制,主要通过pod.spec.containers.resources.requests和pod.spec.containers.resources.limits两个字段实现:
资源需求会影响Pod调度结果,如果一个节点的可用资源小于一个Pod内所有容器的资源需求的和,那么这个Pod不会被调度到这个节点上。资源限制并不影响Pod的调度结果,即一个节点上所有Pod对象资源限制数量之和可以大于节点拥有的资源量,即支持资源过载使用。不过,一旦内存资源耗尽,几乎必然的会有容器因OOMKilled而终止
配置容器的cpu和内存限制可以参考官方文档:https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/assign-memory-resource/和https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/assign-cpu-resource/
下面是一个示例:
kind: Deployment
metadata:
name: pod-with-requests-limits
spec:
replicas: 1
selector:
matchLabels:
app: stress-ng
template:
metadata:
labels:
app: stress-ng
spec:
containers:
- name: stress-ng
image: lorel/docker-stress-ng
imagePullPolicy: IfNotPresent
args: ["--vm", "3", "--vm-bytes", "256M"]
resources:
requests: #设置容器的资源需求
cpu: 500m #最少需要0.5个cpu,1cpu=1000m
memory: 256Mi #最少需要256MB内存
limits: #设置容器的资源限制
cpu: 1 #最多可以使用1个cpu
memory: 512Mi #最多可以有使用512MB内存
查看Pod的资源利用率,cpu和内存的使用量是不会超过limits限制的
如果Pod内容器的资源需求之和超过节点的可用资源,Pod是无法被调度成功的,例如将容器的内存需求修改为100G,再进行创建,Pod会调度失败:
kubectl describe可以看到Pod调度失败
对于压缩性资源CPU来说,若未定义容器的资源需求量,在资源紧缺的情况下,该Pod占用的CPU资源可能会被压缩至极低的水平,甚至导致Pod无法运行。对于非压缩性资源内存来说,在资源紧缺的情况下可能会导致相关业务容器被杀死。所以,对于运行关键业务的Pod,建议一定要使用requests指定资源需求,或者为Pod定义高优先级避免这种情况。
另外,k8s仅确保Pod可以获得它请求的cpu时间额度,它能否获得额外的cpu时间额度,则取决与其它正在运行作业的cpu资源占用情况。例如对于总数1000m的cpu资源来说,容器A请求使用200m,容器b请求使用500m,在不超出它们最大限额的情况下,剩余的的300m会在双方都需要时以2:5(200m:500m)的比例进行分配。
容器的limits可以大于requests,这表节点资源可以被Pod过载使用,那么在内存资源紧张时,应该以什么顺序终结哪些Pod就成为一个问题。k8s借助Pod的服务质量类别和Pod优先级完成判定。k8s根据Pod内容器的requests和limits属性将Pod分为BestEffort、Burstable和Guaranteed三个服务质量类别:
一旦内存资源紧张,BestEffort类别的Pod首先被终止,因为系统不为其提供任何级别的资源保证,但相应的这类Pod可以尽量占用更多的资源。如果此时已经没有BestEffort类别的Pod,接下来会接着终止Burstable类别的Pod。Guaranteed类别的Pod拥有最高优先级,它们不会被杀死,除非其内存资源超过限制,或者没有更低服务质量类别的Pod。
每个运行状态的Pod都有其OOM评分,评分越高越优先被杀死。OOM评分主要根据两个维度进行计算:从服务质量类别继承的默认分值和容器的可用内存资源比例。同等类别的Pod对象的默认分值相同,Guaranteed类别Pod的默认分值为-998,BestEffort类别Pod的默认分值为1000,Burstable类别未定义默认分值,其OOM分值会通过相应的算法计算得出。
因此,同等级别的Pod资源,在内存资源紧缺别终止时,与自身的requests属性相比,内存占用比例最大的Pod会优先被杀死。例如:同属于Burstable类别,PodA内存使用比例为%95,PodB内存占用比例为%80,此时PodA会被终止
LimitRange官方文档:https://kubernetes.io/zh-cn/docs/concepts/policy/limit-range/
虽然可以在Pod内的容器上定义cpu和内存的需求和限制,但这不是强制性要求,那些未定义明确需求的Pod有可能会占用节点上的所有资源。这个时候可以使用LimitRange资源在名称空间中限制每个容器的最小和最大资源用量,以及为那些没有设置资源需求和资源限制属性的容器设置默认的资源需求和限制。
LimitRange支持在Pod级别和容器级别分别设置cpu和内存的可用范围。一旦在名称空间上启用了LimitRange,该名称空间中Pod或容器的requests和limits属性值必须在LimitRange定义的可用的范围之内,否则请求会被拒绝。而未显式指定requests和limits属性的容器,将会从LimitRange资源上自动继承相应的默认设置。这都是LimitRange准入控制器的功能。
另外,LimitRange也支持对PVC的存储空间进行限制,它用于限制创建PVC时请求的存储空间不能超过指定范围。同样的,未指定requests和limits属性值的PVC也会从LimitRange继承默认值
需要注意,LimitRange是名称空间级别资源,需要在每个名称空间都定义;定义的限制仅对LimitRange创建后的Pod和PVC创建请求生效,对之前已存在的资源无效。
下面是一个示例:
apiVersion: v1
kind: LimitRange
metadata:
name: limitrange-demo
namespace: default #针对default名称空间生效
spec:
limits: #定义限制
- type: Container #针对容器的限制
min: #单个容器最小可用的资源,容器的requests属性值要>=这里的设置
cpu: 100m #cpu最少申请100m
memory: 128Mi #内存最少申请128Mi
max: #单个容器最大可用的资源,容器的limits属性值要<=这里的设置
cpu: 2 #cpu最多使用2核
memory: 2Gi #内存最多使用2G
default: #容器limits属性的默认值设置,在容器未设置limits属性时生效
cpu: 500m #默认单容器cpu限制使用500m
memory: 512Mi #默认单容器内存限制使用512Mi
defaultRequest: #容器requests属性的默认值设置,在容器未设置requests属性时生效
cpu: 200m #默认单容器cpu申请200m
memory: 256Mi #默认单容器内存申请256Mi
maxLimitRequestRatio: #容器limits和requests属性的最大比值
cpu: 4 #limits.cpu/requests.cpu不能超过4倍
memory: 4 #limits.memory/requests.memory不能超过4倍
- type: Pod #针对Pod的限制,
max: #单Pod内所有容器最大可用资源限制
cpu: 4 #单Pod内所有容器最多可用4个cpu
memory: 4Gi #Pod内所有容器最多可用4G
min: #单Pod内所有容器最小可用资源限制
cpu: 200m #单Pod内所有容器最少需要申请200m cpu
memory: 256Mi #单Pod内所有容器最少需要申请256Mi内存
- type: PersistentVolumeClaim #针对PVC资源存储空间的限制
min:
storage: 1Gi #单个PVC最少申请1Gi空间
max:
storage: 10Gi #单个PVC最多申请10Gi空间
default: #PVC的默认limits属性值,PVC未设置limits属性时生效
storage: 5Gi
defaultRequest: #PVC的默认requests属性值,PVC未设置requests属性时生效
storage: 2Gi
maxLimitRequestRatio: #PVC资源的limits属性和requests属性的最大比值
storage: 5 #PVC子资源limits.storage/requests.storage最多为5倍
注意:Pod级别不支持设置默认值,一般情况下只设置最大限制,不设置最小限制,这里只是一个示例。
创建不符合limitrange-demo限制的Pod,看是否可以创建成功,验证LimitRange资源的限制是否生效。部署文件如下,其中容器的limits.cpu/requests.cpu超过4倍,不符合LimitRange的要求。
apiVersion: apps/v1
kind: Deployment
metadata:
name: limitrange-test-deploy
spec:
replicas: 2
selector:
matchLabels:
app: limitrange-test-deploy
template:
metadata:
labels:
app: limitrange-test-deploy
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1
memory: 512Mi
创建之后,查询没有Pod被创建出来,如下图:
然后通过kubectl get deploy/limitrange-test-deploy -o yaml的输出可以看到Pod违反了资源限制,所以创建失败
再创建一些未设置资源需求和限制的容器,验证LimitRange资源是否会为容器设置默认的资源需求和限制。部署文件如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: limitrange-test-deploy1
spec:
replicas: 2
selector:
matchLabels:
app: limitrange-test-deploy1
template:
metadata:
labels:
app: limitrange-test-deploy1
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
创建之后,通过kubectl describe的输出可以看到容器被添加了limits和requests属性值,和LimitRange资源设定的默认值一致。
ResourceQuota官方文档:https://kubernetes.io/zh-cn/docs/concepts/policy/resource-quotas/
ResourceQuota资源能够在名称空间级别定义资源的配额,它支持以资源类型来限制可在名称空间中创建的相关资源对象的数量,以及这些资源对象消耗的计算资源总量。ResourceQuota的功能由同名的ResourceQuota准入控制器实现。
ResourceQuota支持针对计算资源配额、存储资源配额和资源对象数量配额。
计算资源配额,ResourceQuota可以限制名称空间中所有非终止状态的所有Pod对象的计算资源需求和计算资源限制的总量。可以使用下面这些字段进行定义:
另外也可以对扩展资源进行限制,比如GPU,对于扩展资源而言,目前仅允许使用前缀为 requests. 的配额项:
存储资源配额,ResourceQuota资源支持为名称空间中的所有PVC资源设置存储空间需求和存储空间限制的总量,它支持从名称空间中的全部PVC、隶属与特定存储类的PVC和基于临时存储的PVC进行限制。可以使用下面这些字段进行定义:
对象数量配额,ResourceQuota支持对名称空间中可创建的资源对象的数量进行限制,通过以下字段实现
下面是一个示例:
apiVersion: v1
kind: ResourceQuota
metadata:
name: resource-quota-demo
namespace: default
spec:
hard:
requests.cpu: 2
requests.memory: 4Gi
limits.cpu: 4
limits.memory: 8Gi
persistentvolumeclaims: 10
requests.storage: 100Gi
managed-nfs-storage.storageclass.storage.k8s.io/persistentvolumeclaims: 5 #限制在managed-nfs-storage这个存储类上最多创建5个pvc
managed-nfs-storage.storageclass.storage.k8s.io/requests.storage: 30Gi
pods: 5
services: 5
count/deployments.apps: 3
count/statefulsets.apps: 3
count/roles.rbac.authorization.k8s.io: 10
创建一个具有6副本的deployment,验证ResourceQuotai资源对象对于pod数量配额是否生效:
apiVersion: apps/v1
kind: Deployment
metadata:
name: resourcequota-test-deploy
spec:
replicas: 6
selector:
matchLabels:
app: resourcequota-test-deploy
template:
metadata:
labels:
app: resourcequota-test-deploy
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
创建之后查看只创建了5个Pod,deployment的当前副本数为5
然后通过kubecetl get deploy/resourcequota-test-deploy -o yaml命令查看status字段,提示Pod数量超出限制,请求被拒绝
再创建两个Pod,它们的requests.cpu之和超过2,验证ResourceQuota资源对象对于cpu和内存的配额是否生效
apiVersion: apps/v1
kind: Deployment
metadata:
name: resourcequota-test-deploy1
spec:
replicas: 2
selector:
matchLabels:
app: resourcequota-test-deploy1
template:
metadata:
labels:
app: resourcequota-test-deploy1
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 1.5
memory: 1Gi
limits:
cpu: 1.5
memory: 1Gi
创建之后查看只创建了一个Pod,另一个Pod未创建
同样查看deployment的status字段,可以看到requests.cpu超出配额,所以另一个Pod创建失败
最后需要注意,和LimitRange不同,ResourceQuota资源对象创建之前已存在的资源对象也会在被ResourceQuota资源对象计入配额统计范围,即之前已存在的资源对象也会占用配额。
关于ResourceQutoa的更多使用方式,比如配额作用域、基于优先级设置配额等可以参考官方文档:https://kubernetes.io/zh-cn/docs/concepts/policy/resource-quotas/