#yyds干货盘点# 如何保障你的 Kubernetes 集群资源不会被打爆(18)

厌伴老儒烹瓠叶,强随举子踏槐花。这篇文章主要讲述#yyds干货盘点# 如何保障你的 Kubernetes 集群资源不会被打爆(18)相关的知识,希望能为你提供帮助。
前面的章节中,我们曾提到通过 HPA 控制业务的资源水位,通过 ClusterAutoscaler 自动扩充集群的资源。但如果集群资源本身就是受限的情况下,或者一时无法短时间内扩容,那么我们该如何控制集群的整体资源水位,保障集群资源不会被“打爆”?
今天我们就来看看 Kubernetes 中都有哪些能力可以帮助我们保障集群资源?
设置 Requests 和 LimitsKubernetes 中对容器的资源限制实际上是通过 CGroup 来实现的。CGroup 是 Linux 内核的一个功能,用来限制、控制与分离一个进程组的资源(如 CPU、内存、磁盘输入输出等)。每一种资源比如 CPU、内存等,都有对应的 CGroup 。如果我们没有给 Pod 设置任何的 CPU 和 内存限制,这就意味着 Pod 可以消耗宿主机节点上足够多的 CPU 和 内存。
所以一般来说,我们都会对 Pod 进行资源限制, Kubernetes 通过给 Pod 设置资源请求(Requests)和资源限制(Limits)来实现这个资源限制。

  • Requests 表示容器可以得到的资源,或者可以理解为 Pod 运行的最低资源要求。
  • Limits 表示着容器最多可以得到的资源。Pod 运行过程中,比如 CPU 使用量会增加,那么最多能使用多少内存,这就是资源限制。
这里有一点需要注意的就是,Limits 永远不要低于 Requests,如果设置不对,Kubernetes 也会拒绝 Pod 的创建。
通过设置 Requests 和 Limits,我们既保证了 Pod 可以运行,又限制 Pod 能使用多少资源。这样能避免某些恶意的容器“吞噬”宿主机的资源,也可以避免某些容器异常导致宿主机 OOM,从而引起该节点上的所有 Pod 异常,甚至导致整个集群“雪崩”。
我们来看看个 Requests 和 Limits 的例子:
apiVersion: v1 kind: Pod metadata: name: pod-resource-demo namespace: demo spec: containers: - name: demo-container-1 image: nginx:1.19 resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m" - name: demo-container-2 image: nginx:1.19 resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m"

如上所示,Pod 中的每个容器都可以设置自己的 Requests 和 Limits,每个容器使用的资源都不能超过各自的限制。当 Pod 在调度时,会把这些容器的 Requests 和 Limits 进行相加,当作整个 Pod 的资源申请量。因此在上面的示例中,Pod 的总 Requests 为 500 mCPU,128 MiB 内存,总 Limits 为 1 CPU和 256 MiB。关于单位的含义,官方文档有更详细的说明。
一旦 Pod 成功被调度后,Kubernetes 会将其调度到可以为其提供该资源的节点上。
而根据设置的 Requests 和 Limit,Kubernetes 又将其分为不同的 QoS (Quality of Service)级别。Kubernetes 中 Pod 是最小的单元,所以 QoS 是对整个 Pod 而言而非某个容器。
Kubernetes 支持了三种 QoS 级别,分别为BestEffort、Burstable 和 Guranteed,当资源紧张时 Kubernetes 会根据它们的分级决定调度和驱逐策略(这个我会在后面的课程中单独说明,在此略过),这三个分级分别代表:
  • BestEffort 表示 Pod 中没有一个容器设置了 Requests 或 Limits,它的优先级最低;
  • Burstable 表示 Pod 中每个容器至少定义了 CPU 或 Memory 的 Requests,或者 Requests 和 Limits 不相等,它属于中等优先级;
  • Guranteed 则表示 Pod 中每个容器 Requests 和 Limits 都相等,这类 Pod 的运行优先级最高。简单来说就是cpu.limits = cpu.requests,memory.limits = memory.requests。
你可以通过 QoS 的代码来研究下 Kubernetes 是如何确定 Pod 对应的 QoS 的。这里,我们通过一个 Burstable Pod 的例子,来直观感受下 Kubernetes 的资源限制能力。
一个 Burstable Pod 的例子
这是一个 Burstable Pod 的 YAML 文件,该 Pod 内只有一个容器,且为容器的内存设置了 Requests 和 Limits,分别为 50 Mi 和 100 Mi。
apiVersion: v1 kind: Pod metadata: name: memory-burstable-demo namespace: demo spec: containers: - name: memory-demo image: polinux/stress resources: requests: memory: "50Mi" limits: memory: "100Mi" command: ["stress"] args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]

我们通过kubectl create创建好了以后,来查看该 Pod 的状态:
$ kubectl -n demo get po NAMEREADYSTATUSRESTARTSAGE memory-burstable-demo0/1OOMKilled111s

可以看到该 Pod 被 OOM 杀掉了,因为限制使用100M,而实际使用 250M。那么如果是 CPU 使用超过了 Limits 呢?
这是一个为容器的 CPU 资源设置了 Requests 和 Limits 的 Pod YAML 文件:
apiVersion: v1 kind: Pod metadata: name: cpu-burstable-demo namespace: demo spec: containers: - name: cpu-demo image: vish/stress resources: limits: cpu: "1" requests: cpu: "0.5" args: - -cpus - "2"

这里我们同样先用kubectl create创建,然后用kubectl top来查看容器 cpu-demo 的资源使用情况:
kubectl -n demo top cpu-burstable-demo cpu-demo NAMECPU(cores)MEMORY(bytes) cpu-demo1000m0Mi

可以看到 Pod 的内存使用虽然超过了 Limits,实际使用的 CPU 被限制只有 1000 m,但是不会被 OOM 掉,这是因为 CPU 不同于内存,CPU 是可压缩资源(Compressible Resource),而内存是不可压缩资源(Incompressible Resource)。
如果只是为了限制资源,用 Requests 和 Limits 就足够了,那么为何 Kubernetes 还要单独引入 QoS 的概念呢?要回答这个问题,我们就要来看看 QoS 的主要作用。
QoS 的主要作用
集群运行一段时间以后,Node 上会有很多 Running 的 Pod。当 Node 上的资源紧张时,可能由于某些BestEffort的 Pod 使用的 CPU 和 Memory 越来越多,或者宿主机某些进程(例如 Kubelet、Docker)占用了 CPU 和 Memory,这个时候Kubernetes 就会根据 QoS 的优先级来选择 Kill 掉一部分 Pod,哪些会先被 Kill 掉呢?
当然是优先级最低的,即BestEffort类型的 Pod,占用的资源越多越优先被 Kill 掉。如果所有 BestEffort 的 Pod 都被杀死了但是资源依旧紧张,那么接下来会选择 Kill 中等优先级的,即 Burstable 类型的,之后以此类推。
这里 QoS 的一个作用就是跟[oom_score](https://www.baidu.com/s?ie=utf-8&tn=baidu&wd=oom score)进行挂钩。Kubernetes 会根据 QoS 设置 OOM 的评分调整参数oom_score_adj,有兴趣可以阅读详细的计算代码。当发生 OOM 时,oom_score_adj 数值越高就越优先被 Kill。
这里我给你展示了三个 QoS 对应的 oom_score_adj 计算公式。
#yyds干货盘点# 如何保障你的 Kubernetes 集群资源不会被打爆(18)

文章图片

除此之外,QoS 还与 Pod 驱逐有关系。当节点的内存、CPU 资源不足时,Kubelet 会开始驱逐节点上的 Pod,它会依据 QoS 的优先级确定驱逐的顺序,跟上面 OOM kill 的次序一样。
在实际使用的时候,我们可能会担心某些 Pod 申请了过大的资源,恶意占用,那么我们又该如何避免呢?
通过 LimitRange 设置资源防线Kubernetes 提供了 LimitRange 可以帮助你限定 CPU 和 Memory 的申请范围。
这是一个完整的 LimitRange 定义,你可以根据需要按需选择进行配置。
apiVersion: v1 kind: LimitRange metadata: name: mem-limit-range namespace: example spec: limits: - default:# 默认 limit memory: 512Mi cpu: 2 defaultRequest:# 默认 request memory: 256Mi cpu: 0.5 max:# 最大 limit memory: 800Mi cpu: 3 min:# 最小 request memory: 100Mi cpu: 0.3 maxLimitRequestRatio:# limit/request 的最大比率 memory: 2 cpu: 2 type: Container # 支持 Container / Pod / PersistentVolumeClaim 三种类型

  • default 字段可以设置 Pod 中容器的默认 Limits;
  • defaulRequest 字段可以设置 Pod 中容器的默认 Requests;
  • max 字段可以设置 Pod 中容器可以设置的最大 Limits,default 字段不能高于此值。同样,在容器上设置的 Limits 也不能高于此值。在使用的时候需要注意的是,如果设置了该字段而又没有设置 default,那么所有未显式设置这些值的容器都将使用此处的最大值作为 Limits。
  • min 字段可以设置 Pod 中容器可以设置的最小 Requests。defaulRequest 字段不能低于此值。同样,在容器上设置的 Requests 也不能低于此值。同样需要注意的是,如果设置了该字段而又没有设置 defaulRequest,那么所有未显式设置这些值的容器都将使用此处的最小值作为 Requests。
【#yyds干货盘点# 如何保障你的 Kubernetes 集群资源不会被打爆(18)】LimitRange 会设置默认的申请、限制的值,它会自动在 Pod 创建时就注入 Container 中。
除了对单个 Pod、Container、PVC 做资源限制外,我们还可以对某个 namespace 下的资源总量进行限制。
ResourceQuota 设置资源总量限制我们可以使用 ResourceQuota 对 namespace 内的资源总量进行限制,比如这个例子:
apiVersion: v1 kind: ResourceQuota metadata: name: compute-resources namespace: demo#在demo空间下 spec: hard: requests.cpu: "10"#cpu预配置10 requests.memory: 100Gi#内存预配置100Gi limits.cpu: "40"#cpu最大不超过40 limits.memory: 200Gi#内存最大不超过200Gi

你可以看到它有四个部分,每个部分都是可选的,你可以根据自己的需要进行组合。
  • requests.cpu 是该命名空间中所有容器的 CPU Requests 总和。在上面的例子中,你可以拥有10 个具有 1 个 CPU 请求的容器,或者 5 个具有 2 个 CPU 请求的容器。只要命名空间 demo 中所有容器的 CPU Requests 总和小于 10 即可。
  • requests.memory 是该命名空间中所有容器的 Memory Requests 总和。同 CPU 一样,只要该命名空间中内存的总请求小于100Gi 即可。
  • limits.cpu 是命名空间中所有容器的 CPU Limits 的总和。和 requests.cpu 一样,只不过这里是 Limits。
  • limits.memory 是命名空间中所有容器的内存 Limits 的总和。和 requests.memory 一样,这里也是指 Limits。
除了 CPU 和内存这类资源以外,ResourceQuota 还支持扩展资源,详见官方文档的说明。
ResourceQuota 的功能非常强大,还可以对对象的数量进行限制。比如这个例子:
apiVersion: v1 kind: ResourceQuota metadata: name: object-counts namespace: demo#在demo命名空间下 spec: hard: configmaps: "10"#最多10个configmap pods: "20"#最多20个pod persistentvolumeclaims: "4"#最多10个pvc replicationcontrollers: "20"#最多20个rc secrets: "10"#最多10个secrets services: "10"#最多10个service services.loadbalancers: "2"#最多10个lb类型的service requests.nvidia.com/gpu: 4#最多10个GPU

我们就可以限制该命名空间下最多可以创建 20 个 Pod,10 个 Configmap 等。
小结对于一些重要的线上应用,我们要合理地设置 Requests 和 Limits,且最好使两者的设置相等,当节点资源不足时,Kubernetes 会优先保证这些 Pod 的正常运行。
此外,你可以用 ResourceQuota 限制命名空间中所有容器的内存请求总量、内存限制总量、CPU 请求总量、CPU 限制总量等。而如果你想对单个容器而不是所有容器进行限制,就可以使用 LimitRange。
欢迎大家扫码关注,获取更多信息
#yyds干货盘点# 如何保障你的 Kubernetes 集群资源不会被打爆(18)

文章图片


    推荐阅读