啃k8s的一块硬骨头resource对象和驱逐策略

集群管理员参考

资源配额

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

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

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

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

  • 集群管理员可以为每个命名空间创建一个或多个 ResourceQuota 对象。
  • 当用户在命名空间下创建资源(如 Pod、Service 等)时,Kubernetes 的配额系统会 跟踪集群的资源使用情况,以确保使用的资源用量不超过 ResourceQuota 中定义的硬性资源限额。
  • 如果资源创建或者更新请求违反了配额约束,那么该请求会报错(HTTP 403 FORBIDDEN), 并在消息中给出有可能违反的约束。
  • 如果命名空间下ResourceQuota的计算资源 (如 cpu 和 memory)的配额被启用,则用户在pod中必须为 这些资源设定请求值(request)和约束值(limit),否则配额系统将拒绝 Pod 的创建。 提示: 可使用 LimitRanger 准入控制器来为没有设置计算资源需求的 Pod 设置默认值。

为命名空间配置内存和 CPU 总额

这里给出一个 ResourceQuota 对象的配置文件mem-cpu-demo.yaml。cpu的单位也可以使用m表示,1表示1核,1核=1000m:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: laravel-resource
  namespace: laravel
spec:
  hard:
    requests.cpu: "1.5"
    requests.memory: 2Gi
    limits.cpu: "2"
    limits.memory: 3Gi

查看yaml文件

kubectl get quota -n laravel -o yaml laravel-resource

可以看到如下一段,used配置项中

spec:
  hard:
    limits.cpu: "2"
    limits.memory: 3Gi
    requests.cpu: 1500m
    requests.memory: 2Gi
status:
  hard:
    limits.cpu: "2"
    limits.memory: 3Gi
    requests.cpu: 1500m
    requests.memory: 2Gi
  used:
    limits.cpu: "0"
    limits.memory: "0"
    requests.cpu: "0"
    requests.memory: "0"

该ResourceQuota 在 laravel 命名空间中设置了如下要求:

  • 该命名空间下的每个容器必须有内存请求和限制,以及 CPU 请求和限制。
  • 该命名空间下的所有容器的内存请求总和不能超过2 GiB。
  • 该命名空间下的所有容器的内存限制总和不能超过3 GiB。
  • 该命名空间下的所有容器的 CPU 请求总和不能超过1.5 cpu。
  • 该命名空间下的所有容器的 CPU 限制总和不能超过2 cpu。

由上面的介绍我们可以知道resourceQuota资源对象约束的是该命名空间下所有容器的资源总和

然后我们新建一个deployment,注意replicas为2

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nginx-php7
  namespace: laravel
  labels:
    name: nginx-php7
spec:
  replicas: 2
  selector:
    matchLabels:
      name: nginx-php7
  template:
    metadata:
      labels:
        name: nginx-php7
    spec:
      containers:
        - name: nginx-php7
          image: harbor.maigengduo.com/laravel/nginx-php7:20201119062847
          resources:
            limits:
              memory: "200Mi"
              cpu: "400m"
            requests:
              memory: "100Mi"
              cpu: "200m"

我们在调度该pod的节点上查看该pod的资源使用情况,可以查看到该pod内的资源配置已经生效为limit memory为200Mi,我们当前使用了45.34Mi,cpu使用; 0.00%,100%表示1核。

docker stats d2b44c799158
CONTAINER ID        NAME                                                                                        CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
d2b44c799158        k8s_nginx-php7_nginx-php7-55447877c6-l75h6_laravel_8157fec1-04c2-46e4-8c9e-018dc69e9058_0   0.00%               45.34MiB / 200MiB   22.67%              0B / 0B             3.38MB / 0B         7

接着我们在查看下quota资源对象,显示如下。由于上述pod的replicas为2,所以可以看到used配置中所有资源使用量翻倍。

kubectl get quota -n laravel -o yaml laravel-resource
·····
spec:
  hard:
    limits.cpu: "2"
    limits.memory: 3Gi
    requests.cpu: 1500m
    requests.memory: 2Gi
status:
  hard:
    limits.cpu: "2"
    limits.memory: 3Gi
    requests.cpu: 1500m
    requests.memory: 2Gi
  used:
    limits.cpu: 800m
    limits.memory: 400Mi
    requests.cpu: 400m
    requests.memory: 200Mi
····

我们看到目前所有pod的memory的请求量为200Mi,而总的请求量为2Gi,接下来我们继续创建一个pod

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nginx-php7-backup
  namespace: laravel
  labels:
    name: nginx-php7-backup
spec:
  replicas: 1
  selector:
    matchLabels:
      name: nginx-php7-backup
  template:
    metadata:
      labels:
        name: nginx-php7-backup
    spec:
      containers:
        - name: nginx-php7-backup
          image: harbor.maigengduo.com/laravel/nginx-php7:20201119062847
          resources:
            limits:
              memory: "2Gi"
              cpu: "600m"
            requests:
              memory: "1.9Gi"
              cpu: "400m"

由于配置文件中的1.9Gi+200Mi>2Gi,所以查看该deployment的yaml文件

 message: 'pods "nginx-php7-backup-6785856766-6bp55" is forbidden: exceeded quota:
      laravel-resource, requested: requests.memory=2040109465600m, used: requests.memory=200Mi,
      limited: requests.memory=2Gi'
    reason: FailedCreate

可以看到返回Forbidden,表示当前的memory超出了resource资源的中的requests.memory,所以构建失败。

注意:ResourceQuota资源对象不影响已经运行的pod资源,也就是说和secret类似,当ResourceQuota资源对象更改,必须重新部署pod才能生效。

配置命名空间下 Pod 配额 API 对象配额

下面配置文件只是列举一些常见的配额,其他配额请参考

apiVersion: v1
kind: ResourceQuota
metadata:
  name: pod-demo
spec:
  hard:
    pods: "2"
    persistentvolumeclaims: "1"
    services.loadbalancers: "2"
    services.nodeports: "0"

为命名空间配置默认内存请求和限制

上面介绍的ResourceQuota资源类型是限制该命名空间内的所有pod的资源总和,而且该命名空间下面的所有pod都需要设置limits和requests的cpu和memory。

现在有一个资源类型为LimitRange,该类型的作用域为Container,可以为命名空间配置默认内存请求和限制。

使用LimitRange资源通常需要和ResourceQuota资源配合使用。我们这里默认部署了ResourceQuota资源,以上节中的代码为例。

部署LimitRange资源对象代码

apiVersion: v1
kind: LimitRange
metadata:
  name: laravel-limit-range
  namespace: laravel
spec:
  limits:
    - default:
        memory: 100Mi
        cpu: 100m
      defaultRequest:
        memory: 100Mi
        cpu: 100m
      type: Container

然后我们在部署一个deployment,注意该deployment并没有resources相关配置。

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nginx
  namespace: laravel
  labels:
    name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      name: nginx
  template:
    metadata:
      labels:
        name: nginx
    spec:
      containers:
        - name: nginx
          image: harbor.maigengduo.com/laravel/nginx-php7:20201119062847

然后查看该pod的yaml文件,有下面一段代码,显示默认使用了LimitRange的相关配置。

[root@master ~]# kubectl get pod -n laravel -o yaml nginx-php7-ingress-76b78bd46d-2m5jh
......
    resources:
      limits:
        cpu: 100m
        memory: 100Mi
      requests:
        cpu: 100m
        memory: 100Mi
......

查看该pod的负载情况,该pod使用内存45.3M

[root@worker2 ~]# docker stats cf5fd9c6c1bf
CONTAINER ID        NAME                                                                                                        CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
cf5fd9c6c1bf        k8s_nginx-php7-ingress_nginx-php7-ingress-76b78bd46d-2m5jh_laravel_283235dd-ecf9-4208-a6b7-7e58b569350c_0   0.00%               45.34MiB / 100MiB   45.34%              0B / 0B             0B / 0B             7

注意:LimitRange资源对象不影响已经运行的pod资源,也就是说和secret类似,当LimitRange资源对象更改,必须重新部署pod才能生效。

为命名空间配置内存限制的最小值和最大值

在ReourceQuota对象所规定的requests和limits的范围内,开发人员随意定义pod中的requests和limits的大小,这是个很严重的问题呀。

还是以最上面的ResourceQuota资源为例。

比如直接定义一个下面这样的pod, requests. memory等于ResourceQuota资源中的requests. memory。

也就是这个命名空间下只能有下面这一个pod了,后面在生产pod将会包403Forbbin。

apiVersion: v1
kind: Pod
metadata:
  name: default-cpu-demo-2
spec:
  containers:
  - name: default-cpu-demo-2-ctr
    image: nginx
    resources:
      limits:
        memory: "3Gi"
        cpu: "1"
      requests:
        memory: "2Gi"
        cpu: "400m"

上面这个例子是个极端情况,但不排除。我们的目的是让pod中的resource值更加贴切真实场景,而不是随意设置大小,所以我们需要给pod的resource值设置一个范围。于是就有了本小结,如题为命名空间配置内存限制的最小值和最大值

实现该功能同样使用的是LimitRange资源。将上节中的LimitRange资源重新整合如下

apiVersion: v1
kind: LimitRange
metadata:
  name: laravel-limit-range
  namespace: laravel
spec:
  limits:
    - default:
        memory: 100Mi
        cpu: 100m
      defaultRequest:
        memory: 100Mi
        cpu: 100m
      max:
        memory: 500Mi
        cpu: 600m
      min:
        memory: 100Mi
        cpu: 100m
      type: Container

现在,只要在laravel 命名空间中创建容器,Kubernetes 就会执行下面的步骤:

  • 如果 Container 未指定自己的内存请求和限制,将为它指定默认的内存请求和限制。
  • 验证 Container 的内存请求是否大于或等于100 MiB,如果小于则报forbidden。
  • 验证 Container 的内存限制是否小于或等于500MiB,如果大于则报forbidden。
  • 验证 Container 的cpu请求是否大于或等于100m,如果大于则报forbidden。
  • 验证 Container 的cpu限制是否小于或等于600MiB,如果大于则报forbidden。

上面介绍的是内存设置最大和最小值,其实cpu也是同理,请参考该文章

开发人员参考

为容器和 Pod 分配内存资源

其实为容器分配内存资源在上面已经介绍过了,我们本节主要学习以下几个知识点

  • 了解metrics-server,它是使用Horizontal Pod Autoscaler资源类型的基础,以及用于一些监控资源命令。
# 查看节点的资源消耗情况
kubectl top node
# 类似于 docker stats containerId
kubectl top pod -n namespace
  • 超过容器限制的内存

做第一个实验,当容器内产生了一个bug或者某个进程疯狂消耗内存时,该容器会发生什么情况?

下面这个容器可以看到limit.memory为3G.

apiVersion: v1
kind: Pod
metadata:
  name: default-cpu-demo-2
spec:
  containers:
  - name: default-cpu-demo-2-ctr
    image: nginx
    resources:
      limits:
        memory: "3Gi"
        cpu: "1"
      requests:
        memory: "2Gi"
        cpu: "400m"

我们进入这个容器后写一个脚本memory.sh,如下看到是个耗内存的无限循环。

#!/bin/bash
str="dfsfsfwrwwwewe1"
while [ TRUE ]
do
  str="$str$str"
  echo "===="
  sleep 0.2
done

执行该脚本后,会看到该容器的MEN USAGE会立马飙升直至接近3G然后恢复成最初的值,接着就看到该脚本进程以及被killed掉了。

[root@nginx-php7-backup-668d86fc8b-kk7vt home]# sh memory.sh 
====
====
====
====
====
====
====
====
====
====
Killed

做第二个实验:pod使用内存超过现在内存。

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo-2
  namespace: mem-example
spec:
  containers:
  - name: memory-demo-2-ctr
    image: polinux/stress
    resources:
      requests:
        memory: "50Mi"
      limits:
        memory: "100Mi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]

该pod内存请求为50Mi,内存限制为100Mi,在配置文件的 args 部分中,你可以看到容器会尝试分配 250 MiB 内存,这远高于 100 MiB 的限制,启动后发生什么呢?

[root@master ~]# kubectl get pod -n laravel                             
NAME                                    READY   STATUS             RESTARTS   AGE
memory-demo-2-758bc658                  0/1     OOMKilled          173        15h

过了一段时间查看,发现重启了173篇,可怕,最后查看下具体原因,发现为OOMKilled。。。

[root@master ~]# kubectl describe pod -n mysql-758bc658c8-jk968
......
......
Containers:
  mysql:
    Container ID:   docker://6dddad72f7e1af9149e6bef5ce537b782a57f7a28cdce650c5fd6f81880ce035
    Image:          harbor.maigengduo.com/laravel/mysql5.7:202007071543
    Image ID:       docker-pullable://harbor.maigengduo.com/laravel/mysql5.7@sha256:82bc22792429e6ff0af3ade4dbf8e5e30656030fb3347defc5fcbb5ff06fdd66
    Port:           3306/TCP
    Host Port:      0/TCP
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137
      Started:      Fri, 04 Dec 2020 08:54:14 +0800
      Finished:     Fri, 04 Dec 2020 08:54:19 +0800
......
......

总结:由第一个实验我们可以看到container中的某个进程(非主进程)使用内存接近limit.memory值时也就是内存不够时,会killed掉该进程。

由第二个实验可以看出,当节点拥有足够的可用内存时,容器可以使用其请求的内存。 但是,容器不允许使用超过其限制的内存。 如果容器分配的内存超过其限制,该容器会成为被终止的候选容器。 如果容器继续消耗超出其限制的内存,则终止容器。 如果终止的容器可以被重启,则 kubelet 会重新启动它,就像其他任何类型的运行时失败一样。

配置resource资源的重要性:如果不配置,container默认的limit限制内存是所在节点的可用内存,如果该container中某个进程疯狂消耗内存,将会耗尽所有可用节点内存为止,这样会造成其他的服务不可用,也有可能会杀死一些系统守护进程,所以这是一个非常可怕的事情

  • 超过整个节点容量的内存

内存请求和限制是与容器关联的,但将 Pod 视为具有内存请求和限制,也是很有用的。Pod 的内存请求是 Pod 中所有容器的内存请求之和。 同理,Pod 的内存限制是 Pod 中所有容器的内存限制之和

Pod 的调度基于请求。只有当节点拥有足够满足 Pod 内存请求的内存时,才会将 Pod 调度至节点上运行,这点一定要明白。看下面的代码

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo-3
  namespace: mem-example
spec:
  containers:
  - name: memory-demo-3-ctr
    image: polinux/stress
    resources:
      limits:
        memory: "1000Gi"
      requests:
        memory: "1000Gi"
  - name: memory-demo-4-ctr
      image: polinux/stress
      resources:
        limits:
          memory: "1000Gi"
        requests:
          memory: "1000Gi"

如果该pod想要被调度,被调度的节点需要有2000Gi的可用内存,很明显这是不可能的,所以该pod不会被调度到任何的节点上,会一直处于pendding状态。

kubectl get pod  -n mem-example
NAME            READY     STATUS    RESTARTS   AGE
memory-demo-3   0/1       Pending   0          25s

查看关于 Pod 的详细信息,包括事件:

kubectl describe pod memory-demo-3 --namespace=mem-example

输出结果显示:由于节点内存不足,该容器无法被调度:

Events:
  ...  Reason            Message
       ------            -------
  ...  FailedScheduling  No nodes are available that match all of the following predicates:: Insufficient memory (3).

为容器和 Pod 分配CPU资源

在这节中我们主要测试容器内的cpu使用量超过limit.cpu时,容器的表现情况。

我们创建一个如下的pod,limit.cpu为2,2表示2核cpu,1核为1000m,100%表示1核,如果拿百分比表示就是200%。

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo-3
  namespace: cpu-example
spec:
  containers:
  - name: cpu-demo
    image: polinux/stress
    resources:
      limits:
        cpu: 2"
      requests:
        cpu: "0.5"

我们进入该容器内执行如下语句,大概意思将空数据持续输出到/dev/null,相当于copy操作,所以该命令会跑满cpu。

 dd if=/dev/zero of=/dev/null &

dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。
if=文件名:输入文件名,缺省为标准输入。即指定源文件。< if=input file >
of=文件名:输出文件名,缺省为标准输出。即指定目的文件。< of=output file >

执行完上面这个命令后,我们查看该container的资源消耗情况,**我们会看到该容器的cpu会立即跑满为100%,并一直持续在1000%,表明已经占用1核了。

docker status 8f40fe0fd2b7
CONTAINER ID        NAME                                                                                                      CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
8f40fe0fd2b7        k8s_nginx-php7-backup_nginx-php7-backup-668d86fc8b-kk7vt_laravel_74aa3803-0794-4f8d-8e2a-2bc9beba3920_0   100%              71.81MiB / 200MiB   35.90%              0B / 0B             105MB /

此时我们在执行一次dd命令,发现cpu的使用率达到200%,说明已经使用2核,然后再次执行dd命令,发现cpu使用率持续在200%保持不动了,重点是container并没有像memory那样killed掉该进程。由此得出如下结论

memory为不可压缩资源,memory不够时会立即killed掉使用最多内存的进程。cpu为可压缩资源,在cpu达到最大值时不会kill进程。

配置 Pod 的服务质量

本节介绍怎样配置 Pod 让其获得特定的服务质量(QoS)类。Kubernetes 使用 QoS 类来决定 Pod 的调度和驱逐策略

pod的QoS类有如下3中情况

  • Guaranteed (可靠)
  • Burstable(基本可靠)
  • BestEffort(不可靠)

Guaranteed

对于 QoS 类为 Guaranteed 的 Pod:

  • Pod 中的每个容器,包含初始化容器,必须指定内存请求和内存限制,并且两者要相等
  • Pod 中的每个容器,包含初始化容器,必须指定 CPU 请求和 CPU 限制,并且两者要相等

查看pod的yaml文件,在最末尾会看到qosClass关键字

spec:
  containers:
    ...
    resources:
      limits:
        cpu: 700m
        memory: 200Mi
      requests:
        cpu: 700m
        memory: 200Mi
  ...
status:
  qosClass: Guaranteed

Burstable

对于 QoS 类为 Guaranteed 的 Pod:

  • Pod 不符合 Guaranteed QoS 类的标准。
  • Pod 中至少一个容器具有内存或 CPU 请求

BestEffort

对于 QoS 类为 BestEffort 的 Pod,Pod 中的容器必须没有设置内存和 CPU 限制或请求。例如下面这个pod

piVersion: v1
kind: Pod
metadata:
  name: qos-demo-3
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-3-ctr
    image: nginx

pod驱逐策略

Kubelet主动监测和防止worker节点上计算资源的全面短缺。在worker节点资源短缺时,kubelet 可以主动地结束一个或多个 Pod 以回收短缺的资源。 当 kubelet 结束一个 Pod 时,它将终止 Pod 中的所有容器,而 Pod 的 Phase 将变为 Failed。 如果被驱逐的 Pod 由 Deployment 管理,这个 Deployment 会创建另一个 Pod 给 Kubernetes 来调度。

驱逐信号

什么是驱逐信号呢?简单理解就是,kubelet什么时候什么场景下开始驱逐pod呢?总的有几个参考值吧,这就是驱逐信号,k8s提供了以下几种驱逐信号

驱逐信号 描述
memory.available node.status.capacity[memory] - node.stats.memory.workingSet
nodefs.available node.stats.fs.available
nodefs.inodesFree node.stats.fs.inodesFree
imagefs.available node.stats.runtime.imagefs.available
imagefs.inodesFree node.stats.runtime.imagefs.inodesFree

memory.available是针对内存资源,其余4个驱逐信号都是针对磁盘空间的。

上面的每个信号都支持字面值或百分比的值。基于百分比的值的计算与每个信号对应的总容量相关。

memory.available 的值从 cgroupfs 获取,而不是通过类似 free -m 的工具。 这很重要,因为 free -m 不能在容器中工作

kubelet 只支持两种文件系统分区

  • nodefs 文件系统,kubelet 将其用于卷和守护程序日志等。
  • imagefs 文件系统,容器运行时用于保存镜像和容器可写层。

imagefs 可选。kubelet 使用 cAdvisor 自动发现这些文件系统。 kubelet 不关心其它文件系统。当前不支持配置任何其它类型。 例如,在专用 filesytem 中存储卷和日志是 不可以 的。

可能看到这里对那倆文件系统分区有点懵逼。我们可以想想worker节点中什么最耗磁盘空间,无非也就下面几项

  • 镜像文件:有的很大,几百M。每次更新deployment,都会拉取新的镜像,长此以往,不通版本的镜像肯定要消耗很大磁盘空间的。imagefs文件系统正是管理这些镜像的,如果我们配置的驱逐信号中有imagefs.available或者imagefs.inodesFree,且触发驱逐阀值后,k8s将会主动清理一些没有使用的版本镜像,以此来释放更多的磁盘空间。
  • 日志:随着应用的长时间运行,日志会越来越庞大,volume不管你使用的是emptyDir,hostPath,还是nfs,clustersfs,都会将日志存储在节点上的,占用worker节点的磁盘空间也将会越来越大。docker中的日志分为俩种,docker主进程的标准输出日志和项目中用户产生的文件日志,正好对应nodefs文件系统中的卷和守护程序日志。如果我们配置的驱逐信号中有nodefs.available或者nodefs.inodesFree且触发驱逐阀值后,k8s将会清理这些日志,清理这些的日志的方式只能是驱逐pod了。

软驱逐阈值

使用一对由驱逐阈值和宽限期组成的配置对。在宽限期内持续超过阀值则进行驱逐。比如下面这俩行配置,表示的意义是当节点的memory.available小于500Mi,且在1分30内持续小于500Mi时,开启触发驱逐。下面这俩行是成对出现的。

软驱逐阈值使用关键字eviction-soft

--eviction-soft=memory.available<500Mi
--eviction-soft-grace-period=memory.available=1m30s

硬驱逐阈值

相对于软驱逐阈值,区别就是没有缓冲时间,只要触发驱逐信号,就立即开始驱逐而不是优雅的终止。

硬驱逐阈值使用关键字eviction-hard。比如下面例子

--eviction-hard=memory.available<500Mi,nodefs.available<20%

kubelet 有如下所示的默认硬驱逐阈值:

memory.available<100Mi
nodefs.available<10%
nodefs.inodesFree<5%
imagefs.available<15%

节点状态振荡

如果使用的是软驱逐阈值,则会有下面这种情况发生:
节点在软驱逐阈值的上下振荡,但没有超过关联的宽限期时,将引起对应节点的状态持续在 true 和 false 间跳变,并导致不好的调度结果。

为了防止这种振荡,可以定义下面的标志,用于控制 kubelet 从压力状态中退出之前必须等待的时间。

  • eviction-pressure-transition-period 是 kubelet 从压力状态中退出之前必须等待的时长。

kubelet 将确保在设定的时间段内没有发现和指定压力条件相对应的驱逐阈值被满足时,才会将状态变回 false。

--eviction-pressure-transition-period=1m30s

最小驱逐回收

在某些场景,驱逐 pod 只回收了少量资源。这将有可能导致 kubelet 反复碰到驱逐阈值,然后反复驱逐pod。

为了减少这类问题,kubelet可以为每个资源配置一个 minimum-reclaim。 当 kubelet 发现资源压力时,kubelet将尝试至少回收驱逐阈值之下 minimum-reclaim 数量的资源。

例如使用下面的配置:

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

如果 memory.available 驱逐阈值被触发,kubelet 将保证 memory.available 至少为 (500+100)Mi。 如果 nodefs.available驱逐阈值被触发,kubelet 将保证 nodefs.available 至少为 (1Gi+500Mi)。如果 imagefs.available驱逐阈值被触发,kubelet 将保证 imagefs.available 至少为 (100+2)Gi, 直到不再有相关资源报告压力为止。

所有资源的默认 eviction-minimum-reclaim 值为 0。

磁盘空间紧缺

如果满足驱逐阈值并超过了宽限期,kubelet将启动回收压力资源的过程,直到它发现低于设定阈值的信号为止。

kubelet 将尝试在驱逐pod前回收节点层级资源,发现磁盘压力时,通过会根据下面俩个步骤执行

  1. 回收层级资源

如果节点针对容器运行时配置有独占的 imagefs,kubelet回收节点层级资源的方式将会不同,这句话理解有点不太明白,如果你知道,请留言指教。

我的理解是在配置驱逐信号时是否使用imagefs相关的信号:

  • 使用 imagefs

    • 如果 nodefs 文件系统满足驱逐阈值,kubelet通过驱逐 pod 及其容器来释放磁盘空间。
    • 如果 imagefs 文件系统满足驱逐阈值,kubelet通过删除所有未使用的镜像来释放磁盘空间。
  • 未使用 imagefs

    • 如果 nodefs 满足驱逐阈值,kubelet将以下面的顺序释放磁盘空间:
    1. 删除停止运行的 pod/container
    2. 删除全部没有使用的镜像
  1. 驱逐pod

如果 kubelet 在节点上无法回收足够的层级资源,kubelet将开始驱逐 pod。

还记得我们上面章节中提到的pod服务质量吗?有Guaranteed,Burstable和BestEffort 3种服务质量。

驱逐pod的顺序就是根据pod的服务质量来排序的,大致顺序如下,这是我理解的:

  • 找出BestEffort类型的pod和Burstable类型超过出请求的pod,这类pod按照优先级排序,优先级低的优先被驱逐,以此类推,直至满足驱逐信号。如果pod未设置的优先级则默认为0,驱逐顺序如下(当前所有pod的优先级都为0)
    • 先驱逐BestEffort类型的pod,驱逐顺序为pod请求内存从高到低依次逐出。
    • 如果逐出所有BestEffort类型的pod还没有满足,则开始逐出Burstable类型超出请求的pod,逐出顺序同样为请求从高到低。
  • 剩下的都是Guaranteed和 Burstable类型且其使用率低于请求的pod,正常低于请求值的pod是不会被驱逐的。但是有一种特殊情况:如果系统守护进程(例如 kubelet、docker、和 journald)消耗的资源多于通过 system-reserved 或 kube-reserved 分配保留的资源时,为保持节点的稳定性并限制意外消耗对其他 pod 的影响,此时节点就会驱逐这些低于请求的pod,驱逐顺序为优先级由低到高。

优先级和服务质量的关系:

正常情况下逐出操作不会逐出 Pod 资源用量未超出其请求值的 Pod,如果优先级较低的 Pod 未超出其请求值,它们不会被逐出。其他优先级较高的 且用量超出请求值的 Pod 则可能被逐出。

内存空间紧缺

如果节点在 kubelet 回收内存之前经历了系统 OOM(内存不足)事件,它将基于 oom-killer 做出响应。

kubelet 基于 pod 的 service 质量为每个容器设置一个 oom_score_adj 值。

Service 质量 oom_score_adj
Guaranteed -998
BestEffort 1000
Burstable min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)

如果 kubelet 在节点经历系统 OOM 之前无法回收内存,oom_killer 将基于它在节点上 使用的内存百分比算出一个 oom_score,并加上 oom_score_adj 得到容器的有效 oom_score,然后结束得分最高的容器。

预期的行为应该是拥有最低服务质量并消耗和调度请求相关内存量最多的容器第一个被结束,以回收内存。

和 pod 驱逐不同,如果一个 Pod 的容器是被 OOM 结束的,基于其 RestartPolicy, 它可能会被 kubelet 重新启动。

内存OOM时,结束顺序大致如下

  1. 结束BestEffort类型的pod,结束顺序为使用内存从高到低。
  2. 结束Burstable类型的pod,先结束超过请求内存的pod,超出请求内存的pod逐出顺序也是请求内存从高到低;其次再结束未超过请求的pod,结束的顺序都是内存占用从高到低。
  3. 结束Guaranteed类型的pod,结束顺序为使用内存从高到低。

为系统守护进程预留计算资源

默认情况下 pod 能够使用节点全部可用容量。 这是个问题,因为节点自己通常运行了不少驱动 OS 和 Kubernetes 的系统守护进程。 除非为这些系统守护进程留出资源,否则它们将与 pod 争夺资源并导致节点资源短缺问题。

resource最佳实践

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
EvictionSoft:
  - memory.available < 300Mi
EvictionSoftGracePeriod:
  - memory.available=1m30s
SystemReserved:
  - memory=500Mi
EvictionPressureTransitionPeriod:
  - memory.available=1m30s
EvictionMinimumReclaim:
  - memory.available=100Mi
EvictionHard:
  - nodefs.available<1Gi
  - imagefs.available<1Gi 

上述场景如下:

  • 节点内存容量300Mi
  • 操作员希望为系统守护进程保留 200Mi 内存容量(内核、kubelet等),这个配置的暗示是理解系统保留应该包含被驱逐阈值覆盖的内存数量
  • 别的参数就不解释了上面都有。

具体的部署kubelet可参考该文章

延伸

伪装超额现象

前提:某命名空间ResourceQuota的limit.cpu为2,该命名空间下面有4个pod,每个pod的limit.cpu都为450m。

450x4=1800=1.8G ,1.8G<2G

由上面的运算我们知道4个pod满足该命名空间下ResourceQuota资源限制,所以在第一次部署4个pod时候都可以成功,pod显示running状态。

随后更新podA,执行完kubeclt apply -f yaml之后查看pod状态,发现并没有更新,状态还是第一次部署的pod,是不是很奇怪。。。

然后我查看所属deployment信息,发现如下

[root@master docker]# kubectl get deploy -n laravel -o yaml nginx-php7-ingress.
.......
.......
  - lastTransitionTime: "2020-11-25T07:35:37Z"
    lastUpdateTime: "2020-11-25T07:35:37Z"
    message: 'pods "nginx-php7-ingress-7cf795845f-x9lgs" is forbidden: exceeded quota:
      laravel-resource, requested: limits.cpu=450m, used: limits.cpu=1800m, limited:
      limits.cpu=2'
    reason: FailedCreate
    status: "True"
    type: ReplicaFailure
......

提示信息已经很明显了,就是超出了ResourceQuota资源类型中的limit.cpu了,可是明明是1.8G,为什么会超出2G呢?

其实很简单,deployment默认的部署方式是滚动部署,简单的说就是新pod running之前,旧的pod还存在,新pod处于running状态了,旧的pod才会开始Terminating状态。

现在就明白了,为什么提示超过2G:其实更新podA时使用的滚动部署方式,也就是说更新时是相当于存在5个pod,

450x5=2250=2.25G, 2.25G>2G



到这里我们在延伸一点东西。针对上面这种情况,我们ResourceQuota和pod该设置多大呢?
比如我们每次k8s自动化部署时都会更新x,y,z3个deployment,且该deployment只有一个pod,n表示ResourceQuota中的限制,表达式如下

2(x+y+z)<=n

到这里我们就明白了,也希望各位小伙伴注意哦,笔芯。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容