12.资源限制

cpu、内存资源限制

https://www.jianshu.com/p/3ec8657b01a1
https://blog.csdn.net/kozazyh/article/details/79518921

介绍

Kubernetes是一个容器集群管理平台,Kubernetes需要统计整体平台的资源使用情况,合理地将资源分配给容器使用,并且要保证容器生命周期内有足够的资源来保证其运行。 更进一步,如果资源发放是独占的,即资源已发放给了个容器,同样的资源不会发放给另外一个容器,对于空闲的容器来说占用着没有使用的资源比如CPU是非常浪费的,Kubernetes需要考虑如何在优先度和公平性的前提下提高资源的利用率。
kubernetes中Pod的保障机制核心如下:

  • 通过资源限额来确不同的Pod只能占用指定的资源。
  • 允许集群的资源被超额分配,以提高集群的资源利用率。
  • 为Pod划分等级,确保不同等级的Pod有不同的服务质量(QoS),资源不足时,低等级的Pod会被清理,以确保高等级的Pod稳定运行。
    CPU和Memory是被Pod使用的,因此在配置Pod时可以通过参数CPU Request及Memory Request为其中的每个容器指定所需使用的CPU与Memory量,kubernetes会根据Request的值去查找有足够资源的Node来调度此Pod,如果没有,则调度失败。

计算资源管理

Request和Limits参数

尽管Request和Limits只能被设置到容器上,但是设置Pod级别的Request和Limits能大大提高管理Pod的便利性和灵活性。Pod的Request和Limits是指该Pod中所有容器的Request和Limits的总和(对于没有设置的该项被当作0或者按照集群配置的默认值来计算)。
CPU的Request和Limits是通过CPU数(cpus)来度量的,CPU的值及绝对值。
内存的Request和Limits计量单位是字节数。使用整数或者定点整数加上国际单位制来标示内存值。KB是十进制单位,KiB是二进制单位,两者都可以使用,但是大小有差别。
以下面的Pod中的资源配置为例:

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: db
    image: mysql
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: wp
    image: wordpress
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"        

该Pod包含连个容器,每个容器配置的Requests都是0.25CPU和64MiB内存,而配置的Limits都是0.5CPU和128MiB内存。 这个Pod的Requests是0.5CPU和128MiB内存、Limits是1CPU和256MiB内存。

基于Request和Limits的Pod调度机制

对于每种计算资源而言,每个节点都有一个能用于运行Pod的最大容量值。在调度时,首先确保调度后该节点上所有Pod的CPU和内存的Request总和不超过该节点能提供给Pod使用的CPU和Memory的最大容量值。
可能某个节点上的实际资源使用量非常低,但是已运行Pod配置的Requests值的总和非常高,再加上需要调度的Pod的Requests值,会场过该节点提供给Pod的资源容量上限,这是Kubernetes仍然不会讲Pod调度到该及节点上。

Request和Limits的背后机制

kubelet在启动Pod的某个容器时,会将容器的Requests和Limits值转化为相应的容器启动参数传递个容器执行器(Docker或者rkt)。
下面以Docker环境举例:

  1. resources.requests.cpu: 这个参数会转化为core数,然后乘以1024,再将这个结果作为–cpu-shares参数的值传递给docker run 命令。–cpu-shares参数是一个相对权重值,决定了Docker在资源竞争时分配各容器的资源比例。
  2. resources.limits.cpu: 这个参数会被转化为millicore数,将辞职乘以100,然后将结果作为–cpu-quota参数的值传递给docker run命令。docker run命令中另外一个参数–cpu-period默认被设置为100000,标示Docker重新计量和分配CPU的使用时间间隔为100000微秒(100毫秒)。

Docker的–cpu-quota参数和–cpu-period参数一起配合完成对容器CPU的使用限制。比如Kubernetes中的配置容器的CPU Limits为0.1,那么计算后–cpu-quata为10000,而–cpu-period为100000,这意味着Docker在100ms内最多给该容器分配10ms*core的计算资源用量。

  1. resources.requests.memory: 这个参数值只提供给Kubernetes调度器作为调度和管理的依据,不会作为任何参数传递给Docker。
  2. resources.limits.memory: 这个参数会转化为单位为Betys的整数,数值会作为–memory参数传递给docker run命令。

计算资源相关问题分析

Pod的状态为Pending,错误信息为FailedScheduling。
如果Kubernetes调度器在集群中找不到合适的节点来运行Pod,那么这个Pod会一直处于未调度状态,直到调度器找到合适的节点为止。
如果一个或者多个Pod调度失败且有这类错误,可以尝试以下几种解决办法:

  • 添加更多的节点到集群中。
  • 停止一些不必要的运行中的Pod,释放资源。
  • 检查Pod的配置,错误的配置可能导致该Pod永远无法被调度执行。

可以通过kubectl describe nodes的命令查看集群中节点的计算资源容量和已使用量。另外还可以配置针对一组Pod的Requests和Limits总量的限制,这种限制可以作用于命名空间,可以通过这种方式防止一个命名空间下的用户将所有资源据为己有。

容器被强行终止(Terminated),如果容器使用的资源超过了它配置的Limits,那么该容器可能会被强制终止。可以通过kubectl describe pod命令来确认容器是否因为这个原因被终止。

资源配置范围管理(LimitRange)

默认情况下Kubernetes不会对Pod加上CPU和内存限制,这意味着Kubernetes系统中任何Pod都可以使用其所在节点的所有可用的CPU和内存。但为了保证Pod的稳定性,Kubernetes集群管理员需要配置Requests和Limits。更多的时候,我们需要对集群内Requests和Limits的配置做一个全局限制。
常见的配置场景如下:

  • 集群管理员希望能在系统管理功能中设置禁止Pod申请超过一定阈值的内存。
  • 针对不同的命名空间,设置不同的资源限制。
  • 集群管理员希望设置每个Pod都必须至少使用集群平均资源值的20%,来提供更好的资源一致性调度,从而减少资源浪费。
    针对这些需求,Kubernetes提供了LimitRange机制对Pod和容器的Requests和Limits配置进一步作出限制。

为Namespace设置LimitRange

为Namespace:“gaotu” 创建一个简单的LimitRange。

[root@master01 demo]# cat gaotu-limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: gaotu-limits
spec:
  limits:
  - max:
      cpu: "4"
      memory: 2Gi
    min:
      cpu: 200m
      memory: 6Mi
    maxLimitRequestRatio:
      cpu: 3
      memory: 2
    type: Pod
  - default:
      cpu: 300m
      memory: 200Mi
    defaultRequest:
      cpu: 200m
      memory: 200Mi
    max:
      cpu: "2"
      memory: 1Gi
    min:
      cpu: 100m
      memory: 3Mi
    maxLimitRequestRatio:
      cpu: 5
      memory: 4
    type: Container
[root@master01 demo]# kubectl create -f gaotu-limitrange.yaml --namespace=gaotu
limitrange/gaotu-limits created

查看gaotu namespace的LimitRange:

[root@master01 demo]# kubectl describe limits gaotu-limits --namespace=gaotu
Name:       gaotu-limits
Namespace:  gaotu
Type        Resource  Min   Max  Default Request  Default Limit  Max Limit/Request Ratio
----        --------  ---   ---  ---------------  -------------  -----------------------
Pod         cpu       200m  4    -                -              3
Pod         memory    6Mi   2Gi  -                -              2
Container   cpu       100m  2    200m             300m           5
Container   memory    3Mi   1Gi  200Mi            200Mi          4

不伦是CPU还是内存,在LimitRange中,Pod和container都可以设置Mix、Min和Max Limit/Request Ratio参数。但是Pod不能设置Default Request和Default Limit参数。
Pod和Container参数说明如下:

  • Container的Min是Pod中所有容器的Requests值下限;Max是Pod中所有容器的Limits值上限;Container的Default Request是Pod中所有未指定Requests值的容器的默认Request值;Container的Default Limits是Pod中所有未指定Limits执行的容器的默认Limits值。对于同一资源类型,这4个参数必须满足以下关系: Min <= Default Request <=Default Limit <= Max。
  • Pod的Min是Pod中所有容器的Requests值的中和下限;Max是Pod中所有容器的Limits指的总和上限。当容器未指定Requests值或者Limits值时,将使用Container的Default Requests值或者Default Limits值。
  • Container的Max Limit/Requests Ratio限制了Pod中所有容器的Limits值与Requests值的比例上限。而Pod的Max limit/Requests Ratio 限制了Pod中所有容器的Limits值总和与Requests值总和的比例上限。
    如果设置了Container的Max,那么对于该类资源而言,整个集群中的所有容器都必须设置Limits,否则无法成功创建。Pod内的容器为设置Limits是,将使用Default Limit的值,如果也为配置Default,则无法成功创建。
    如果设置了Container的Min,那么对于该类型资源而言,整个集群中的所有容器都必须设置Requests。如果创建容器时未配置该类资源的Requests,那么在创建过程中会报验证错误。Pod里容器的Requests未配置时,可以使用默认值Default Request;如果未配置而又没有使用默认值Default Request,那么会默认等于该容器的Limits;如果此时Limits也未定义,就会报错。
    对于任意一个Pod而言,该Pod中所有容器的内存Requests总和必须大于或等于6MiB,而且所有容器的Limits总和必须小于或等于1GiB;同样容器的CPU Requests总和必须大于或等于200m,而且所有容器的CPU Limits总和必须小于或等于2。
    Pod里任何容器的Limits与Requests的比例都不能超过Container的Max Limit/Requests Ratio;Pod里所有容器的Limits总和与Requests总和的比例不能超过Pod的Max Limits/Request Ratio。

创建Pod时触发LimitRanger限制

命名空间中的LimitRange只会在Pod创建或者更新时执行检查。如果手动修改LimitRange为一个新的值,那么这个新的值不会去检查或者限制之前在该命名空间中创建好的Pod。
创建一个单容器的Pod来展示默认限制:

[root@master01 demo]# kubectl run nginx --image=nginx:1.14 --replicas=1 -n gaotu
deployment.apps/nginx created
[root@master01 demo]#kubectl get pod -n gaotu
NAME                     READY   STATUS    RESTARTS   AGE
nginx-78ffb4cd8c-nldtc   1/1     Running   0          4s
[root@master01 demo]# kubectl get pod nginx-78ffb4cd8c-nldtc -n gaotu -o yaml  | grep resources -C 8
  resourceVersion: "12131243"
  selfLink: /api/v1/namespaces/gaotu/pods/nginx-78ffb4cd8c-nldtc
  uid: 7b1d486c-9b12-11e9-9a87-fa163ec28bf8
spec:
  containers:
  - image: nginx:1.14
    imagePullPolicy: IfNotPresent
    name: nginx
    resources:
      limits:
        cpu: 300m
        memory: 200Mi
      requests:
        cpu: 200m
        memory: 200Mi
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File

由于Pod没有配置资源的Requests和Limits,所有使用了namespace中默认的CPU和内存定义的Requests和Limits值。
下面创建一个超出资源限制的Pod:

[root@master01 demo]# cat invalid-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: invalid-pod
  namespace: gaotu
spec:
  containers:
  - name: nginx
    image: nginx:1.14
    resources:
      limits:
        cpu: "3"
        memory: 100Mi
[root@master01 demo]# kubectl create -f invalid-pod.yaml
Error from server (Forbidden): error when creating "invalid-pod.yaml": pods "invalid-pod" is forbidden: maximum cpu usage per Container is 2, but limit is 3.

下面展示下LimitRange对MaxLimitRequestRatio的限制过程:

[root@master01 demo]# cat limit-test-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: gaotu
  labels:
    name: nginx-limit-test
spec:
  containers:
  - name: nginx
    image: nginx:1.14
    resources:
      limits:
        cpu: "1"
        memory: 512Mi
      requests:
        cpu: "0.8"
        memory: 250Mi
[root@master01 demo]# kubectl create -f limit-test-nginx.yaml
Error from server (Forbidden): error when creating "limit-test-nginx.yaml": pods "nginx" is forbidden: memory max limit to request ratio per Pod is 2, but provided ratio is 2.048000.

由于上面Pod的全部内存Limits总和与Requests总和的比例为512:250,大于LimitRange中定义Pod的最大比率2(maxLimitRequestsRatio.memory=2),因此创建失败。
下面创建一个满足LimitRange条件的Pod:

[root@master01 demo]# cat valid-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: valid-pod
  namespace: gaotu
  labels:
    name: valid-pod
spec:
  containers:
  - name: nginx
    image: nginx:1.14
    resources:
      limits:
        cpu: "1"
        memory: 512Mi
[root@master01 demo]# kubectl create -f valid-pod.yaml
pod/valid-pod created

查看该Pod的资源信息:

[root@master01 demo]# kubectl get pod -n gaotu
NAME        READY   STATUS    RESTARTS   AGE
valid-pod   1/1     Running   0          36s
[root@master01 demo]# kubectl get pod valid-pod -n gaotu -o yaml | grep resources -C 8
  resourceVersion: "12133643"
  selfLink: /api/v1/namespaces/gaotu/pods/valid-pod
  uid: a3f4efa5-9b15-11e9-974d-fa163eeed95b
spec:
  containers:
  - image: nginx:1.14
    imagePullPolicy: IfNotPresent
    name: nginx
    resources:
      limits:
        cpu: "1"
        memory: 512Mi
      requests:
        cpu: "1"
        memory: 512Mi
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File

该Pod配置了明确的Limits和Requests,因此该Pod不会使用LimitRange中定义的default和defaultRequest。

资源服务质量管理(Resource QoS)

在Kubernetes的资源QoS体系中,需要保证高可靠性的Pod可以申请可靠资源,而一些不需要搞可靠性的Pod可以申请可靠性较低或者不可靠的资源。Pod级别的资源配置是通过计算Pod内所有容器的资源配置的总和得出来的。
Kubernetes中Pod的Requests和Limits资源配置有如下特点:

  • 如果Pod配置Requests值等于Limits值,那么该Pod可以获得的资源是完全可靠的。
  • 如果Pod的Requests值小于Limits值,那么该Pod获得的资源可分为两部分:完全可靠的资源,大小等于Requests值;不可靠资源,资源量最大等于Limits与Requests的差额。这份不可靠的资源能够申请到多少,取决于当时主机上容器可用资源的余量。

Requests和Limits对不同计算资源类型的限制机制

根据前面的内容,容器的资源配置满足以下两个条件:

  • Requests <= 节点可用资源
  • Request <= Limits
    Requests和Limits针对不同计算资源类型的限制机制存在差异,这种差异主要取决于计算资源类型是可压缩资源还是不可压缩资源。
    可压缩资源:
  • Kubernetes目前支持的可压缩资源是CPU。
  • Pod可得到Requests配置的CPU使用量,而能否使用超过Requests值的部分取决于系统的负责和调度。
  • 空闲CPU资源按照容器Requests值的比例分配。
  • 如果Pod使用了超过Limits中配置的CPU用量,那么cgroups会对Pod中容器的CPU使用进行限流(Throttled);如果没有配置Limits,那么Pod将会尝试抢占所有空闲的CPU资源。
    不可压缩资源:
  • Kubernetes目前支持的不可压缩的资源是内存。
  • Pod可以得到在Requests中配置的内存。当Pod使用的内存量超过了它的Requests的配置,那么这个Pod有可能被Kubernetes杀掉,判断是否杀掉的依据主要是节点的可用内存是否满足其他Pod的需求。
  • 如果Pod使用的内存量超过了他的Limits设置,那么操作系统的内核会杀掉Pod所有容器中进程使用内存最多的一个,直到内存不超过Limits为止。

对调度策略的影响

Kubernetes的kubelet通过计算Pod中所有容器的Requests的总和来决定对Pod的调度。
不管是CPU还是内存,Kubernetes调度器和kubelet都会确保节点上所有Pod的Request的总和不会超过在该节点上可分配给容器使用的资源容量上限。

服务质量等级(QoS Classes)

在一个潮涌(Over Commited,容器Limits总和大于系统容量上限)系统中,由于容器负载的波动可能导致操作系统的资源不足,最终可能导致部分容器被杀掉。Kubernetes将容器划分为3个QoS等级:Guaranteed(完全可靠的)、Burstable(弹性波动、较可靠的)和BestEffort(尽力而为,不太可靠的),三种优先级依次递减。QoS级别直接由Requests和Limits来定义。Kubernetes中的容器的QoS级别等于容器所在Pod的QoS级别。
Guaranteed
如果Pod中的所有容器对所有资源类型都定义了Limits和Requests,并且所有容器的Limits值都和Requests值全部相等。那么该Pod的QoS级别就是Guaranteed。在这种情况下容器可以不定义Requests,因为Requests值在未定义时默认等于Limits。
BestEffort
如果Pod中所有容器都未定义资源配置(Requests和Limits都未定义),那么该Pod的QoS级别就是BestEffort。
Burstable
Burstable级别的Pod包含两种情况:第一种 Pod中的一部分容器在一种或者多种资源类型的资源配置中定义了Requests值和Limits值,且Requests值小于Limits值;第二种 Pod中的一部分容器未定义资源配置。在容器未定义Limits时,Limits值默认等于节点资源容量上限。

QoS的工作特点

Pod的CPU Requests无法得到满足时,容器得到的CPU会被压缩限流。
BestEffort Pod的优先级最低,在这类Pod中运行的进程会在系统内存紧缺时被第一优先杀掉。
Burstable Pod的优先级居中,这类Pod初始时会分配较少的可靠资源,当整个系统内存紧缺又没有BestEffort容器可以被杀掉以释放资源,那么这类Pod中的进程可能会被杀掉。
Guaranteed Pod的优先级最高,当整个系统内存紧缺,有没有其他更低优先级的容器可以被杀掉以释放资源,那么这类Pod的进程也可能会被杀掉。

OOM计分系统

OOM(Out Of Memory)计分规则包括如下内容:

  • OOM计分的计算方法为:计算进程使用内存在系统中占的百分比,取其中不含百分号的数值,再乘以10的结果,这个结果是进程OOM的基础分;将进程OOM的基础分的分值再加上这个进程的OOM分数调整值OOM_SCORE_ADJ的值,作为进程OOM的最终分值(除root启动的进程外)。在系统发生OOM时,优先杀掉OOM计分更高的进程。
  • 进程的OOM计分的基本分数范围在0~1000,如果A进程的调整值减去B进程的调整值的结果大于1000,那么A进程会优先被杀掉。
  • 不伦调整值为多少,任何进程的最终分值范围也是0~1000。
    不同QoS的OOM计分调整值规则如下:
    ||QOS等级||OOM_SCORE_ADJ||
    ||Guaranteed||-998||
    ||BestEffort||1000||
    ||Burstable||min(max(2,1000-(1000*memoryRequestBytes)/machineMemoryCapacityBytest),999)||
    其中:
  • BestEffort Pod设置OOM_SCORE_ADJ调整值为1000.因此容器里所有进程的OOM最终得分肯定是1000。
  • Guaranteed Pod设置OOM_SCORE_ADJ调整值为-998,因为容器里所有进程的OOM最终得分一般是0或者1。
  • Burstable Pod规则分以下情况:
    ◦ 如果Burstable Pod的内存Request超过了系统可用内存的99.8%,那个这个Pod的调整值固定为2。
    ◦ 如果内存Requests为0,那么调整值固定为999。
    ◦ 其他情况下OOM_SCORE_ADJ调整值为1000-10*(% of memory requested)。
    ◦ 这样规则可以保证调整值的范围为2~999,Burstable Pod中所有进程的OOM最终分数范围为2~1000。

如果一个Burstable Pod使用的内存比它内存的Requests少,那么可以肯定的是它的所有进程的OOM最终分数会小于1000,此时能确保它的优先级高于BestEffort Pod。如果一个Burstable Pod的某个容器中某个进程使用的内存比容器的Requests值高,那么这个进程的OOM最终分数回事1000,否则它的OOM最终分会小于1000。
OOM还有一些特殊的计分规则:

  • kubelet进程和Docker进程的调整值为-998。
  • 如果配置进程调整值为-999,那么这类进程不会被杀掉。

资源配额管理(Resource Quotas)

Resource Quota对象可以定义资源配额,它可以限制命名空间中某种类型的对象的总数目上限,也可以设置命名空间中Pod可以使用的计算资源的总上限。
典型的资源配额使用方式如下:

  • 不同团队工作在不同的命名空间下,目前这是非约束性的,在未来的版本中可能会通过ACL(Access Control List)来实现强制性约束。
  • 为每个命名空间都创建一个或者多个资源配额项。
  • 如果命名空间中的计算资源配额启用,那么用户必须为相应的资源类型设置Requests或Limits;否则配额系统可能会直接拒绝Pod的创建。这里可以使用LimitRange机制来为没有配置资源的Pod提供默认资源配置。
    使用资源配额时,需要注意以下两点:
  • 如果集群总的可用资源小于各命名空间中资源配额的总和,那么可能会导致资源竞争。在资源竞争时,Kubernetes系统会遵循先到先得的原则。
  • 不管是资源竞争合适配额的修改,都不会影响已经创建的资源使用对象。

在Master中开启资源配额

资源配额可以通过在kube-apiserver的–admission-control参数中添加ResourceQuota参数进行开启。如果在某个命名空间的定义中存在ResourceQuota,那么对于这个命名空间而言资源配额就是开启的。一个命名空间可以有多个ResourceQuota配置项。
1. 计算资源配额
ResourceQuota支持限制的计算资源类型:
资源名称|说明
-------|------:
Cpu|所有非中止状态的Pod,Cpu Requests的总和不能超过该值
limits.Cpu|所有非中止状态的Pod,Cpu Limits的总和不能超过该值
requests.Cpu|所有非中止状态的Pod,Cpu Requests的总和不能超过该值
Memory|所有非中止状态的Pod,内存 Requests的总和不能超过该值
limits.Memory|所有非中止状态的Pod,内存 limits的总和不能超过该值
request.Memory|所有非中止状态的Pod,内存 Requests的总和不能超过该值
2. 存储资源配额
目前支持的存储资源名称如下:
资源名称|说明
-------|------:
requests.storage|所有PVC,存储请求总量不能超过此值
Persistentvolumeclaims|在该命名空间中能存在的持久盘的总数上限
.storageclass.storage.k8s.io/requests.storage|和该存储类关联的所有PVC,存储请求总和不能超过此值
.storageclass.storage.k8s.io/persistentvolumesclaims|和该存储类关联的所有PVC的总数
3. 对象数量配额
目前支持限制的对象类型:
资源名称|说明
-------|------:
Configmaps|在该命名空间中能存在的ConfigMap的总数上限
Pods|在该命名空间中能存在的非终止状态Pod的总数上限,Pod终止状态等价于Pod的status.phase in (failed,succeeded)= true
Replicationcontrollers|在该命名空间中能存在的RC的总数上限
Resourcequotas|在该命名空间中能存在的资源配额项的总数上限
Services|在该命名空间中能存在的Services的总数上限
services.loadbalancers|在该命名空间中能存在的负载均衡的总数上限
services.nodeports|在该命名空间中能存在的NodePort的总数上限
Secrets|在该命名空间中能存在的Secret的总数上限

配额的作用域

每项资源配额都可以单独配置一组作用域,配置了作用域的资源配额只会对符合其作用域的资源使用情况进行计量和限制,作用域范围内超过了资源配额的请求都会报验证错误。
ResourceQuota的四种作用域:
作用域|说明
-----|----:
Terminating|匹配所有spec.activeDeadineSeconds 不小于0的Pod
NotTerminating|匹配所有spec.activeDeadlineSeconds是nil的Pod
BestEffort|匹配所有QoS是BestEffort的Pod
NotBestEffort|匹配所有QoS不是BestEffort的Pod
其中,BestEffort作用域可以限制资源配额来追踪Pod资源的使用,Terminating、NotTerminating、NotBestEffort这三种作用域可以限定资源配额来追踪以下资源的使用:

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

在资源配额中设置Requests和Limits

如果在资源配额中指定了requests.cpu和requests.memory,那么它会强制要求每个容器都配置自己的CPU Requests和内存 Requests。同理如果在资源配额中指定了limits.cpu和limits.memory,那么它会强制要求每个容器都配置自己的CPU Requests和内存 Limits。两者可以使用LimitRange提供的默认值。

资源配额的定义

与LimitRange相似,ResourceQuota也被设置在Namespace中。创建名为wenzai的Namespace:

[root@master01 ~]# kubectl create namespace wenzai
namespace/wenzai created
[root@master01 ~]# kubectl get namespace wenzai
NAME     STATUS   AGE
wenzai   Active   7s

创建ResourceQuota配置文件compute-resources.yaml,用于设置计算资源的配额:

[root@master01 demo]# cat compute-resources.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-resources
  namespace: wenzai
spec:
  hard:
    pods: "4"
    requests.cpu: "1"
    requests.memory: "1Gi"
    limits.cpu: "2"
    limits.memory: "2Gi"
[root@master01 demo]# kubectl create -f compute-resources.yaml
resourcequota/compute-resources created

创建另外一个名为object-counts.yaml的文件,用于设置对象数量的配额:

[root@master01 demo]# cat object-counts.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: obejct-counts
  namespace: wenzai
spec:
  hard:
    configmaps: "10"
    persistentvolumeclaims: "4"
    replicationcontrollers: "20"
    secrets: "10"
    services: "10"
    services.loadbalancers: "2"
[root@master01 demo]# kubectl create -f object-counts.yaml
resourcequota/obejct-counts created

查看各ResourceQuota的详细信息:

[root@master01 demo]# kubectl describe quota compute-resources -n wenzai
Name:            compute-resources
Namespace:       wenzai
Resource         Used  Hard
--------         ----  ----
limits.cpu       0     2
limits.memory    0     2Gi
pods             0     4
requests.cpu     0     1
requests.memory  0     1Gi
[root@master01 demo]# kubectl describe quota obejct-counts -n wenzai
Name:                   obejct-counts
Namespace:              wenzai
Resource                Used  Hard
--------                ----  ----
configmaps              0     10
persistentvolumeclaims  0     4
replicationcontrollers  0     20
secrets                 1     10
services                0     10
services.loadbalancers  0     2

资源配额与集群资源总量的关系
资源配额与集群资源总量是完全独立的。资源配额是通过绝对的单位来配置的。
资源配额将整个集群中的资源总量做了一个静态划分,但它并没有对集群中的节点做任何限制:不同命名空间中的Pod仍然可以运行在同一个节点上。

总结

Kubernetes中资源管理的基础是容器和Pod的资源配置(Requests和Limits)。容器的资源配置指定了容器请求的资源和容器能使用的资源上限,Pod的资源配置则是Pod中所有容器的资源配置总和上限。
通过资源配额机制,我们可以对命名空间中所有Pod使用资源的总量进行限制,也可以对这个命名空间中指定类型的对象的数量进行限制。使用作用域可以让资源配额只对符合特定范围的对象加以限制。
LimitRange可以有效限制Pod和容器的资源配置的最大、最小范围,也可以限制Pod和容器的Limits与Requests的最大比例上限,此外LimitRange还可以为Pod中的容器提供默认的资源配置。
Kubernetes基于Pod的资源配置实现了资源服务质量(QoS),不同QoS级别的Pod在系统中拥有不同的优先级:高优先级的Pod有更高的可靠性,可以用于运行可靠行要求较高的服务;低优先级的Pod可以实现集群资源超售,有效地提高了集群资源利用率。

上面主要介绍了Kubernetes集群中Node、Label、资源的管理以及Namespace的共享与隔离,下面将会介绍Pod的驱逐机制和审计机制。


介绍

Kubernetes是一个容器集群管理平台,Kubernetes需要统计整体平台的资源使用情况,合理地将资源分配给容器使用,并且要保证容器生命周期内有足够的资源来保证其运行。那么如何在系统硬件资源紧缺的情况下保证Node的稳定性,是kubelet需要解决的另一个重要问题。尤其对于内存和磁盘这种不可压缩的资源,紧缺就意味着不稳定。下面对驱逐的策略、信号、阈值、监控频率和驱逐操作进行详细说明。

资源紧缺时的Pod驱逐机制

驱逐策略

kubelet持续监控主机的资源使用情况,并尽量防止计算资源被耗尽。一旦出现资源紧缺的情况,kubelet就会主动终止一个或者多个Pod的运行,以回收紧缺的资源。当一个Pod被终止时,其中的容器会全部停止,Pod的状态会被设置为Failed。

驱逐信号

在下表中提到一些信号,kubelet能够利用这些信号作为决策依据来触发驱逐行为。每个信号都支持整数值或者百分比的表示方法,百分比的分母部分就是各个信号相关资源的总量。
驱逐信号及其描述:
||驱逐信号||描述||
||memory.available||memory.availble:=node.status.capacity[memory]-node.status.memory.workingSet||
||nodefs.available||nodefs.availble: =node.stats.fs.availble||
||nodefs.inodesFree||nodefs.inodesFree:=node.stats.fs.inodesFree||
||imagefs.available||imagefs.availbe: =node.status.runtime.imagefs.availble||
||imagefs.inodesFress||imagefs.inodesFree:=node.stats.runtime.imagefs.inodesFree||
memory.available的值取自cgroupfs,而不是free -m命令,这是因为free -m不支持在容器内工作。如果用户使用了node allocatable功能,则除了节点自身的内存需要判断,还需要利用cgroup根据用户Pod部分的情况进行判断。
kubelet 计算memory.available的过程:

#!/bin/bash
#!/usr/bin/env bash

# current memory usage
memory_capacity_in_kb=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}')
memory_capacity_in_bytes=$((memory_capacity_in_kb * 1024))
memory_usage_in_bytes=$(cat /sys/fs/cgroup/memory/memory.usage_in_bytes)
memory_total_inactive_files=$(cat /sys/fs/cgroup/memory/memory.stat | grep total_inactive_file | awk '{print $2}')

memory_working_set=$memory_usage_in_bytes
if [ "$memory_working_set" -lt "$memory_total_inactive_file" ];
then
    memory_working_set=0
else
    memory_working_set=$((memory_usage_in_bytes - memory_total_inactive_file))
fi

memory_available_in_bytes=$((memory_capacity_in_bytes - memory_working_set))
memory_available_in_kb=$((memory_available_in_bytes / 1024))
memory_available_in_mb=$((memory_available_in_kb / 1024))

echo "memory.capacity_in_bytes $memory_capacity_in_bytes"
echo "memory.usage_in_bytes $memory_usage_in_bytes"
echo "memory.total_inactive_file $memory_total_inactive_files"
echo "memory.working_set $memory_working_set"
echo "memory.available_in_bytes $memory_available_in_bytes"
echo "memory.available_in_kb $memory_available_in_kb"
echo "memory.available_in_mb $memory_available_in_mb"

kubelet假设inactive_file(不活跃LRU列表中的file-backed内存,以字节为单位)在紧缺的情况下可以回收,因此对其进行了排除。
kubelet支持以下两种文件系统:

  • nodefs: 保存kubelet的卷和守护进程日志等。
  • imagefs: 在容器运行时保存镜像及可写入层。
    kubelet使用cAdvisor自动监控这些文件系统。磁盘压力相关的资源回收机制正在逐渐被驱逐策略接管,未来会停止对现有垃圾收集方式的支持。

驱逐阈值

kubelet可以定义驱逐阈值,一旦超过阈值,就会触发kubelet进行资源回收操作。
阈值的定义方式:
<eviction-signal><operator><quantity>
其中:

  • 上表中已经列出了驱逐信号的名称及描述
  • 当前仅支持一个operator:< (小于).
  • quantity 需要符合kubernetes的数量表达式,也可以用%结尾的百分比表示。
    软驱逐
    驱逐软阈值由一个驱逐阈值和一个管理员设定的宽限期共同定义。当系统资源消耗达到软阈值时,在这一情况的持续时间达到宽限期之前,kubelet不会触发驱逐动作。如果没有定义宽限期,kubelet会拒绝启动。
    在定义终止Pod的宽限期。如果定义了这一宽限期,那么kubelet会使用pod.Spec.TerminationGracePeriodSeconds 和最大宽限期这两个值之间较小的数值进行宽限,如果没有定义,则kubelet会立即杀掉Pod。
    软阈值的定义包含如下几个参数:
  • –eviction-soft:描述驱逐阈值(例如memory.available<1.5GiB),如果满足这一条件的持续时间超过宽限期,就会触发对Pod的驱逐动作。
  • –eviction-soft-grace-period:驱逐宽限期(例如memory.available=1m30s),用于定义达到软阈值之后持续时间超过多久才进行驱逐。
  • –eviction-max-pod-grace-period:达到软阈值后,终止Pod的最大宽限时间,单位为秒。
    硬驱逐
    硬阈值没有宽限期,如果达到了硬阈值,则kubelet会立即杀掉Pod并进行资源回收。
    kubelet的默认硬阈值定义如下:
    –eviction-hard=memory.available<100Mi

驱逐监控频率

kubelet的–housekeeping-interval参数定义了一个时间间隔,kubelet每隔一个这样的时间间隔就会对驱逐阈值进行评估。

节点的状况

kubelet会将一个或多个驱逐信号与节点的状况对应起来。无论触发了软阈值还是硬阈值,kubelet都会认为节点的压力太大。
节点状况与驱逐信号的对应关系:
||~节点状况||驱逐信号||
||MemoryPressure||memory.available||
||DiskPressure||nodefs.available,nodefs.inodesFree,imagefs.available,imagefs.inodesFree||
节点的root文件系统或者镜像文件系统的可用空间达到了驱逐阈值
kubelet会持续向Master节点报告状态的更新过程,这一频率由参数–node-status-update-frequency指定。默认为10s。

节点状态的抖动

如果一个节点的状况在软阈值的上下抖动,但是又没有超过宽限期,则会导致该节点的相应状态在True和False之间不断变换,可能会对调度的决策过程产生负面影响。
要防止这种状况,可以使用参数–eviction-pressure-transition-period 脱离压力状态前需要等待的时间,默认为5分钟。这样,kubelet在把压力状态设置为FALSE之前,会确认在检测周期之内该节点没有达到驱逐阈值。

回收Node级别的资源

kubelet在驱逐用户Pod之前,会尝试回收Node级别的资源。在观察到磁盘压力的情况下,基于服务是否为容器运行时定义了独立的imagefs,会导致不同的资源回收过程。
有imagefs的情况:

  • 如果nodefs文件系统达到了驱逐阈值,则kubelet会删掉死掉的Pod、容器来清理空间。
  • 如果imagefs文件系统达到了驱逐阈值,则kubelet会删掉所有无用的镜像来清理空间。
    无imagefs的情况:
    如果nodefs文件系统达到了驱逐阈值,则kubelet会按照下面的顺序来清理空间。
  • 删除死掉的Pod、容器。
  • 删除所有无用的镜像。

驱逐用户的Pod

如果kubelet无法通过节点级别的资源回收获取足够的资源,就会驱逐用户的Pod。
kubelet会按照下面的标注对Pod的驱逐行为进行判断。

  • Pod要求的服务质量。
  • 根据Pod调度请求的被耗尽资源的消耗量。
    接下来,kubelet按照下面的顺序驱逐Pod
  • BestEffort: 紧缺资源消耗最多的Pod最先被驱逐
  • Burstable:根据相对请求来判断,紧缺资源消耗最多的Pod最先被驱逐,如果没有Pod超出他们的请求,则策略会瞄准资源消耗量最大的Pod。
  • Guaranteed: 根据相对请求来判断,紧缺资源消耗最多的Pod最先被驱逐,如果没有Pod超出他们的请求,则策略会瞄准资源消耗量最大的Pod。
    Guaranteed Pod永远不会因为其他Pod的资源消费被驱逐。如果系统进程(kubelet、docker、journald等)消耗了超出system-reserved或者kube-reserved的资源,而在这一节点上只运行了Guaranteed Pod,那么为了保证节点的稳定运行并降低异常消耗对其他Guaranteed Pod的影响,必须选择一个Guaranteed Pod进行驱逐。
    本地磁盘是一种BestEffort资源,如有必要,kubelet会在DiskPressure的情况下,对Pod进行驱逐以回收磁盘资源。kubelet会按照QoS进行评估,如果kubelet判定缺乏inode资源,就会通过驱逐最低QoS的Pod的方式回收inodes。如果kubelet判断缺乏自盘空间,就会在相同QoS的Pod中,选择消耗最多磁盘空间的Pod进行驱逐。
    有imagefs的情况:
    如果nodefs触发了驱逐,则kubelet会按照nodefs的使用情况(Pod中所有容器的本地卷和日志所占的空间进行计算)对Pod进行排序。
    如果imagefs触发了驱逐,则kubelet会根据Pod中所有容器消耗的可写入层的使用空间进行排序。
    无imagefs的情况:
    如果nodefs触发了驱逐,则kubelet会对各个Pod中所有容器的总体磁盘消耗(以本地卷+日志+所有容器的写入层所占的空间进计算)进行排序。

资源最少回收量

在某些场景下,驱逐Pod可能只回收了很少的资源,导致kubelet反复触发驱逐阈值。另外,回收磁盘资源需要消耗时间。
要缓和这种状况,kubelet可以对每种资源都定义minimum-reclaim。kubelet一旦检测到了资源压力,就会试着回收不少于minimum-reclaim的资源数量,使得资源消耗量回到期望的范围。
例如:

--eviction-hard=memory.available<500Mi,nodefs.available>1Gi,imagefs.available<100Gi
--eviction-minimum-reclaim="memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi"

当memory.available超过了阈值触发了驱逐操作时,kubelet会启动资源回收,并保证memory.available至少有500MiB。
如果是nodefs.available超过阈值并触发了驱逐操作,则kubelet会恢复nodefs.available到至少1.5GiB。
对于imagefs.available超过阈值并触发了驱逐操作的情况,kubelet会保证imagefs.available恢复到至少102GiB。

节点资源紧缺情况下的系统行为

调度器的行为
在节点资源紧缺的情况下,节点会向master报告这一情况。在master上运行的调度器以此为信号,不再继续向该节点调度新的Pod。
节点状况与调度行为的对应关系:
||~节点状况||调度行为||
||MemoryPressure||不再调度新的BestEffort Pod到这个节点||
||DiskPressure||不再向这一节点调度Pod||
Node的OOM行为
如果节点在kubelet能够回收内存之前遭遇了系统的OOM,节点则依赖oom_killer的设置进行响应。
kubelet根据Pod的QoS为每个容器设置了一个oom_score_adj值:
||QoS等级||oom_score_adj||
||Guaranteed||-998||
||BestEffort||1000||
||Burstable||min(max(2,1000-(1000*memoryRequestBytes)/machineMemoryCapacityBytes),999)||
如果kubelet无法在系统OOM之前回收足够的内存,则oom_killer会根据内存使用比率来计算oom_socre,将得出的结果和oom_score_adj相加,得分最高的Pod首先被驱逐。
这个策略的思路是,QoS最低且相对于调度的Request来说消耗最多内存的Pod会首先被驱逐,来保证内存的回收。与Pod驱逐不同。如果一个Pod的容器被OOM杀掉,则可能被kubelet根据RestartPolicy重启。
对DaemonSet类型的Pod驱逐的考虑
通过DaemonSet创建的Pod具有在节点自动重启的特性,然而kubelet目前并没有能力分辨DaemonSet的Pod。所以强烈建议不要在DaemonSet类型中创建BestEffort类型的Pod,避免产生驱逐方面的问题。

可调度的资源和驱逐策略实践

假设一个集群的资源管理需求如下:

  • 节点内存容量: 10GiB。
  • 保留10%的内存给系统守护进程(内核、kubelet等),系统预留内存也包含资源驱逐阈值。
  • 在内存使用率达到95%是驱逐Pod,以此减低系统压力并防止系统OOM。
    为满足这些需求,kubelet应该设置如下参数:
--eviction-hard=memory.available<500Mi
--system-reserved=memory=1.5Gi

在这种情况下,节点一旦开始接近内存压力,调度器就不会向该节点部署Pod,并且假定这些Pod使用的资源数量少于其请求的资源数量。

主动驱逐保护(Pod Disruption Budget)

在kubelet集群的运行过程中,许多管理操作都可能对Pod进行主动驱逐,“主动”一词意味着这一操作可以安全地延迟一段时间。
目前主要针对以下两种场景:

  • 节点的维护或升级时(kubectl drain)。
  • 对应用的自动缩容操作(autoscaling down)。
    由于节点不可用(Not Ready)导致的Pod驱逐就不能被称为主动了。
    对于主动主动驱逐的场景来说,应用如果能够保持存活的Pod的数量,则会非常有用。通过使用PodDisruptionBudget,应用可以保证那些会主动移除Pod的集群操作永远不会在同一时间停掉太多Pod,从而导致服务终端或者服务降级等。例如在对某些Node进行维护时,系统应该保证应用以不低于一定数量的Pod保证服务的正常运行。kubectl drain操作将遵循PodDisruptionBudget的设定,如果在该节点上运行了属于同一个服务的多个Pod,则为了保证最少存活数量,系统将确保每终止一个Pod后,一定会在另一台健康的Node上启动新的Pod,再继续终止下一个Pod。
    PodDisruptionBudget资源对象用于指定一个Pod集合在一段时间内存活的最小实例数量或者百分比。一个PodDisruptionBudget作用于一组被同一个控制器管理的Pod,例如RS或RC。与通常的Pod删除不同,驱逐Pod的控制器将使用/eviction接口对Pod进行驱逐,如果这一主动驱逐行为违反了PodDisruptionBudget的约定,就会被API Server拒绝。
    PodDisruptionBudget本身无法真正保证指定数量或百分比的Pod的存活。PodDisruptionBudget对象的保护作用仅仅针对于主动驱逐的场景,而非所有场景。
    对于PodDisruptionBudget的定义包括如下两部分:
  • Label Selector:用于筛选被管理的Pod。
  • minAvailable:指定驱动过程需要保障的最少Pod数量。minAvailable可以是一个数字,也可以是一个百分比。
    PodDisruptionBudget示例如下:
    首先创建一个Deployment,Pod的数量为3个:
[root@of-bj-k8s-deploy01 demo]# vim nginx-deployment.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      name: nginx
  template:
    metadata:
      labels:
        name: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14
        ports:
        - containerPort: 80
          protocol: TCP
[root@of-bj-k8s-deploy01 demo]# kubectl create -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
[root@of-bj-k8s-deploy01 demo]# kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-fb77d6c84-8dkjp   1/1     Running   0          7s
nginx-deployment-fb77d6c84-9chsj   1/1     Running   0          7s
nginx-deployment-fb77d6c84-wrz2s   1/1     Running   0          7s
[root@of-bj-k8s-deploy01 demo]#

创建一个PodDisruptionBudget对象:

[root@of-bj-k8s-deploy01 demo]# vim nginx-PodDisruptionBudget.yaml
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: nginx
spec:
  minAvailable: 3
  selector:
    matchLabels:
      name: nginx
[root@of-bj-k8s-deploy01 demo]# kubectl create -f nginx-PodDisruptionBudget.yaml
poddisruptionbudget.policy/nginx created

PodDisruptionBudget使用的是和Deployment一样的Label Seletor,并且设置存活Pod的数量不得少于3个。
对Pod的主动驱逐操作通过驱逐API(/eviction)来完成。可以将这个API看做受策略控制的对Pod的Delete操作。要实现一次主动驱逐,则需要Post一个JSON请求,以eviction.json文件格式表示,内容如下:

{
    "apiVersion": "policy/v1beta1",
    "kind": "Eviction",
    "metadata": {
        "name": "nginx-deployment-fb77d6c84-8dkjp"
        "namespace": "default"
    }
}

用curl命令执行eviction操作:

curl -v -H 'Content-type: application/json' https://172.21.141.22:8443/api/v1/namespaces/default/pods/nginx-deployment-fb77d6c84-8dkjp/eviction -d @eviction.json

由于PodDisruptionBudget设置存活的Pod的数量不能少于3个,因为驱逐操作会失败,在返回的错误信息中会包含如下内容:

"message": "Cannot evict pod as it would violate the Pods distruption budget."

使用kubectl get pods查看Pod列表,会看到Pod的数量和名称都没有发生变化。
最后使用kubectl delete pdb nginx命令删除pdb对象,再执行上文中的curl指令,会执行成功。通过kubectl get pods查看Pod列表,会发现Pod的数量虽然没有发生变化,但是指定的Pod已消失,取而代之的是一个新的Pod。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值