关于Kubernetes RequestsLimits设置的一些细节

328次阅读
没有评论

先思考一个问题,当你为一个 workload 定义资源配额时,一般是如何确认它的 requests 和 limits 的?

问题:服务很卡,但CPU使用率不高?

我们编写一个webhook的 yaml,部署进集群,用作 demo 测试

apiVersion: apps/v1 
kind: Deployment
metadata:
   name: webhook 
   namespace: webhook
spec:
   replicas: 1
   selector:
     matchLabels:
       qcloud-app: webhook 
   template:
     metadata:
       labels:
         qcloud-app: webhook 
     spec:
       containers:
       - image: ccr.ccs.tencentyun.com/mervingz/webhook-demo:v3
         imagePullPolicy: IfNotPresent 
         name: webhook
         ports:
           - name: http
             containerPort: 8887

正常运行后,访问服务

关于Kubernetes

可以看到,服务接口的请求响应时间为 300ms(这里做了一些圆周率的计算)

增加Limit

resources:
  requests:
    memory: 100Mi
    cpu: "0.1"
  limits:
    memory: 100Mi
    cpu: "0.5"

正常运行后,访问服务

关于Kubernetes

可以看到,接口的响应时间变成了 550ms,是因为 Pod 负载变高了导致吗?

NAME                       CPU(cores)   MEMORY(bytes)   
webhook-5967f65849-pz55n   4m           15Mi 

关于Kubernetes

看起来并不是这个原因,单纯从CPU使用率来看,Limit的配置是十分充足的。

K8S中的CPU

我们知道2核2线程的 CPU ,可被系统识别为4个逻辑 CPU,在 Kubernetes 中对 CPU 的分配限制是对 逻辑CPU 做分片限制的。也就是说分配给容器一个 CPU,实际是分配一个 逻辑CPU。 而且1个逻辑CPU还可被单独划分子单位,即 1个逻辑CPU,还可被划分为 1000 个millicore(毫核),简单说就是1个逻辑CPU,继续逻辑分割为1000个豪核心。 豪核:可简单理解为将 CPU 的时间片做逻辑分割,每一段时间片就是一个豪核心。 所以,500m 就是500豪核心,即 0.5个 逻辑CPU

requests 和 limits

resources:
  requests:
    memory: 200Mi
    cpu: "0.1"
  limits:
    memory: 300Mi
    cpu: "0.4"

先看这个 yaml 文件,其中,requests是给调度看的,调度会确保节点上所有负载的CPU request合计与内存request合计分别都不大于节点本身能够提供的 CPU 和内存。

limit是给节点(kubelet)看的,节点会保证负载在节点上只使用这么多 CPU 和内存。

该配置意味着单个负载会调度到一个剩余CPU request大于 0.1 核,剩余request内存大于 200MB 的节点,并且负载运行时的CPU使用率不能高于0.4核(超过将被限流),内存使用不多余300MB(超过将被OOM Kill并重启)

CPU和内存不一样,它是量子化的,只有使用中空闲两个状态。

当我们说内存的使用率是 60% 时,我们是在说内存有60%在空间上已被使用,还有40%的空间可以放入负载。

但是,当我们说CPU的某个核的使用率是60%时,我们是在说采样时间段内,CPU的这个核在时间上有60%的时间在忙,40%的时间处于空闲状态。

你设定负载的CPU limit时,可能会带来一个让你意想不到的效果——过分的降速限流, 节点CPU明明不忙,但是节点故意不让你的负载全速使用CPU,服务延时上升。

什么是CPU限流

Kubernetes 使用CFS(Completely Fair Scheduler,完全公平调度)限制负载的CPU使用率,CFS本身的机制比较复杂,但是 Kubernetes 的文档中给了一个简明的解释,要点如下:

  • CPU使用量的计量周期为100ms;
  • CPU limit决定每计量周期(100ms)内容器可以使用的CPU时间的上限;
  • 本周期内若容器的CPU时间用量达到上限,CPU限流开始,容器只能在下个周期继续执行;
  • 1 CPU = 100ms CPU时间每计量周期,以此类推,0.2 CPU = 20ms CPU时间每计量周期,2.5 CPU = 250ms CPU时间每计量周期;
  • 如果程序用了多个核,CPU时间会累加统计

回到开始的问题,未增加 Limit 限制时,可以得知接口的正常处理响应时长在 300ms 左右,随后增加 Limit 为 500m,即每 100ms 内最多使用 50ms CPU时间,API服务会受到限流。

没有限制,响应时间为300ms

关于Kubernetes

如果CPU limit被设为 0.5 核,即每 100ms 内最多使用 50ms CPU时间,服务会受到多次限流(灰色部分),服务在550ms后响应:

关于Kubernetes

关于Kubernetes

container_cpu_cfs_throttled_seconds_total这个指标记录了 CPU 被限流的时间

关于Kubernetes

通过监控可以看到,CPU限流的情况在大部分时候都存在,限流比例在10%上下浮动,这意味着服务的工作没能全速完成,在速度上打了9折。

如何更好的设置Requests和Limits

所有容器都应该设置 request

request 的值并不是指给容器实际分配的资源大小,它仅仅是给调度器看的,调度器会 “观察” 每个节点可以用于分配的资源有多少,也知道每个节点已经被分配了多少资源。

被分配资源的大小就是节点上所有 Pod 中定义的容器 request 之和,它可以计算出节点剩余多少资源可以被分配(可分配资源减去已分配的 request 之和)。

如果发现节点剩余可分配资源大小比当前要被调度的 Pod 的 reuqest 还小,那么就不会考虑调度到这个节点,反之,才可能调度。

所以,如果不配置 request,那么调度器就不能知道节点大概被分配了多少资源出去,调度器得不到准确信息,也就无法做出合理的调度决策,很容易造成调度不合理,有些节点可能很闲,而有些节点可能很忙,甚至 NotReady。

所以,建议是给所有容器都设置 request,让调度器感知节点有多少资源被分配了,以便做出合理的调度决策,让集群节点的资源能够被合理的分配使用,避免陷入资源分配不均导致一些意外发生。

老是忘记设置怎么办

有时候我们会忘记给部分容器设置 request 与 limit,其实我们可以使用 LimitRange 来设置 namespace 的默认 request 与 limit 值,同时它也可以用来限制最小和最大的 request 与 limit。 示例:

apiVersion: v1
kind: LimitRange
metadata:
  name: mem-limit-range
  namespace: test
spec:
  limits:
  - default:
      memory: 512Mi
      cpu: 500m
    defaultRequest:
      memory: 256Mi
      cpu: 100m
    type: Container

重要的线上应用改如何设置

节点资源不足时,会触发自动驱逐,将一些低优先级的 Pod 删除掉以释放资源让节点自愈。

  • 没有设置 request,limit 的 Pod 优先级最低,容易被驱逐;

  • request 不等于 limit 的其次;

  • request 等于 limit 的 Pod 优先级较高,不容易被驱逐。

  • 通过 container_cpu_cfs_throttled_seconds_total 指标,监控容器CPU是否被限流, Limit 的设置是否合理。

    所以如果是重要的线上应用,不希望在节点故障时被驱逐导致线上业务受影响,就建议将 request 和 limit 设成一致。

怎样设置才能提高资源利用率

如果给应用设置较高的 request 值,而实际占用资源长期远小于它的 request 值,导致节点整体的资源利用率较低。

当然这对时延非常敏感的业务除外,因为敏感的业务本身不期望节点利用率过高,影响网络包收发速度。

所以对一些非核心,并且资源不长期占用的应用,可以适当减少 request 以提高资源利用率。

如果你的服务支持水平扩容,单副本的 request 值一般可以设置到不大于 1 核,CPU 密集型应用除外。比如 coredns,设置到 0.1 核就可以,即 100m。

尽量避免设置过大的 requests 和 limit

如果你的服务使用单副本或者少量副本,给很大的 request 与 limit,让它分配到足够多的资源来支撑业务,那么某个副本故障对业务带来的影响可能就比较大,并且由于 request 较大,当集群内资源分配比较碎片化,如果这个 Pod 所在节点挂了,其它节点又没有一个有足够的剩余可分配资源能够满足这个 Pod 的 request 时,这个 Pod 就无法实现漂移,也就不能自愈,加重对业务的影响。

相反,建议尽量减小 request 与 limit,通过增加副本的方式来对你的服务支撑能力进行水平扩容,让你的系统更加灵活可靠。

mervinwang
版权声明:本站原创文章,由 mervinwang2021-12-23发表,共计3590字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
载入中...