Kubernetes 资源配额
什么是资源?
在 kubernetes 中,有两个基础但是非常重要的概念:node 和 pod。node 翻译成节点,是对集群资源的抽象;pod 是对容器的封装,是应用运行的实体。node 提供资源,而 pod 使用资源,这里的资源分为计算(cpu、memory、gpu)、存储(disk、ssd)、网络(network bandwidth、ip、ports)。这些资源提供了应用运行的基础,正确理解这些资源以及集群调度如何使用这些资源,对于大规模的 kubernetes 集群来说至关重要,不仅能保证应用的稳定性,也可以提高资源的利用率。
在这篇文章,我们主要介绍 CPU 和内存这两个重要的资源,它们虽然都属于计算资源,但也有所差距。CPU 可分配的是使用时间,也就是操作系统管理的时间片,每个进程在一定的时间片里运行自己的任务(另外一种方式是绑核,也就是把 CPU 完全分配给某个 pod 使用,但这种方式不够灵活会造成严重的资源浪费,kubernetes 中并没有提供);而对于内存,系统提供的是内存大小。
CPU 的使用时间是可压缩的,换句话说它本身无状态,申请资源很快,也能快速正常回收;而内存大小是不可压缩的,因为它是有状态的(内存里面保存的数据),申请资源很慢(需要计算和分配内存块的空间),并且回收可能失败(被占用的内存一般不可回收)。
把资源分成可压缩和不可压缩,是因为在资源不足的时候,它们的表现很不一样。对于不可压缩资源,如果资源不足,也就无法继续申请资源(内存用完就是用完了),并且会导致 pod 的运行产生无法预测的错误(应用申请内存失败会导致一系列问题);而对于可压缩资源,比如 CPU 时间片,即使 pod 使用的 CPU 资源很多,CPU 使用也可以按照权重分配给所有 pod 使用,虽然每个人使用的时间片减少,但不会影响程序的逻辑。
在 kubernetes 集群管理中,有一个非常核心的功能:就是为 pod 选择一个主机运行。调度必须满足一定的条件,其中最基本的是主机上要有足够的资源给 pod 使用。
资源除了和调度相关之外,还和很多事情紧密相连,这正是这篇文章要解释的。
kubernetes 资源的表示
用户在 pod 中可以配置要使用的资源总量,kubernetes 根据配置的资源数进行调度和运行。目前主要可以配置的资源是 CPU 和 memory,对应的配置字段是 spec.containers[].resource.limits/request.cpu/memory
。
需要注意的是,用户是对每个容器配置 request 值,所有容器的资源请求之和就是 pod 的资源请求总量,而我们一般会说 pod 的资源请求和 limits。
limits
和 requests
的区别我们下面会提到,这里先说说比较容易理解的 cpu 和 memory。
CPU
一般用核数来标识,一核CPU 相对于物理服务器的一个超线程核,也就是操作系统 /proc/cpuinfo
中列出来的核数。因为对资源进行了池化和虚拟化,因此 kubernetes 允许配置非整数个的核数,比如 0.5
是合法的,它标识应用可以使用半个 CPU 核的计算量。CPU 的请求有两种方式,一种是刚提到的 0.5
,1
这种直接用数字标识 CPU 核心数;另外一种表示是 500m
,它等价于 0.5
,也就是说 1 Core = 1000m
。
内存比较容易理解,是通过字节大小指定的。如果直接一个数字,后面没有任何单位,表示这么多字节的内存;数字后面还可以跟着单位, 支持的单位有 E
、P
、T
、G
、M
、K
,前者分别是后者的 1000
倍大小的关系,此外还支持 Ei
、Pi
、Ti
、Gi
、Mi
、Ki
,其对应的倍数关系是 2^10 = 1024
。比如要使用 100M 内存的话,直接写成 100Mi
即可。
节点可用资源
理想情况下,我们希望节点上所有的资源都可以分配给 pod 使用,但实际上节点上除了运行 pods 之外,还会运行其他的很多进程:系统相关的进程(比如 sshd、udev等),以及 kubernetes 集群的组件(kubelet、docker等)。我们在分配资源的时候,需要给这些进程预留一些资源,剩下的才能给 pod 使用。预留的资源可以通过下面的参数控制:
--kube-reserved=[cpu=100m][,][memory=100Mi][,][ephemeral-storage=1Gi]
:控制预留给 kubernetes 集群组件的 CPU、memory 和存储资源--system-reserved=[cpu=100mi][,][memory=100Mi][,][ephemeral-storage=1Gi]
:预留给系统的 CPU、memory 和存储资源
这两块预留之后的资源才是 pod 真正能使用的,不过考虑到 eviction 机制(下面的章节会提到),kubelet 会保证节点上的资源使用率不会真正到 100%,因此 pod 的实际可使用资源会稍微再少一点。主机上的资源逻辑分配图如下所示:
NOTE:需要注意的是,allocatable 不是指当前机器上可以分配的资源,而是指能分配给 pod 使用的资源总量,一旦 kubelet 启动这个值是不会变化的。
allocatable 的值可以在 node 对象的 status
字段中读取,比如下面这样:
status:
allocatable:
cpu: "2"
ephemeral-storage: "35730597829"
hugepages-2Mi: "0"
memory: 3779348Ki
pods: "110"
capacity:
cpu: "2"
ephemeral-storage: 38770180Ki
hugepages-2Mi: "0"
memory: 3881748Ki
pods: "110"
kubernetes 资源对象
在这部分,我们来介绍 kubernetes 中提供的让我们管理 pod 资源的原生对象。
请求(requests)和上限(limits)
前面说过用户在创建 pod 的时候,可以指定每个容器的 Requests 和 Limits 两个字段,下面是一个实例:
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
Requests
是容器请求要使用的资源,kubernetes 会保证 pod 能使用到这么多的资源。请求的资源是调度的依据,只有当节点上的可用资源大于 pod 请求的各种资源时,调度器才会把 pod 调度到该节点上(如果 CPU 资源足够,内存资源不足,调度器也不会选择该节点)。
需要注意的是,调度器只关心节点上可分配的资源,以及节点上所有 pods 请求的资源,而不关心节点资源的实际使用情况,换句话说,如果节点上的 pods 申请的资源已经把节点上的资源用满,即使它们的使用率非常低,比如说 CPU 和内存使用率都低于 10%,调度器也不会继续调度 pod 上去。
Limits
是 pod 能使用的资源上限,是实际配置到内核 cgroups 里面的配置数据。对于内存来说,会直接转换成 docker run
命令行的 --memory
大小,最终会配置到 cgroups 对应任务的 /sys/fs/cgroup/memory/……/memory.limit_in_bytes
文件中。
NOTE:如果 limit 没有配置,则表明没有资源的上限,只要节点上有对应的资源,pod 就可以使用。
使用 requests 和 limits 概念,我们能分配更多的 pod,提升整体的资源使用率。但是这个体系有个非常重要的问题需要考虑,那就是怎么去准确地评估 pod 的资源 requests?如果评估地过低,会导致应用不稳定;如果过高,则会导致使用率降低。这个问题需要开发者和系统管理员共同讨论和定义。
limit range(默认资源配置)
为每个 pod 都手动配置这些参数是挺麻烦的事情,kubernetes 提供了 LimitRange
资源,可以让我们配置某个 namespace 默认的 request 和 limit 值,比如下面的实例:
apiVersion: "v1"
kind: "LimitRange"
metadata:
name: you-shall-have-limits
spec:
limits:
- type: "Container"
max:
cpu: "2"
memory: "1Gi"
min:
cpu: "100m"
memory: "4Mi"
default:
cpu: "500m"
memory: "200Mi"
defaultRequest:
cpu: "200m"
memory: "100Mi"
如果对应 namespace 创建的 pod 没有写资源的 requests 和 limits 字段,那么它会自动拥有下面的配置信息:
内存请求是 100Mi,上限是 200Mi
CPU 请求是 200m,上限是 500m
当然,如果 pod 自己配置了对应的参数,kubernetes 会使用 pod 中的配置。使用 LimitRange 能够让 namespace 中的 pod 资源规范化,便于统一的资源管理。
资源配额(resource quota)
前面讲到的资源管理和调度可以认为 kubernetes 把这个集群的资源整合起来,组成一个资源池,每个应用(pod)会自动从整个池中分配资源来使用。默认情况下只要集群还有可用的资源,应用就能使用,并没有限制。kubernetes 本身考虑到了多用户和多租户的场景,提出了 namespace 的概念来对集群做一个简单的隔离。
基于 namespace,kubernetes 还能够对资源进行隔离和限制,这就是 resource quota 的概念,翻译成资源配额,它限制了某个 namespace 可以使用的资源总额度。这里的资源包括 cpu、memory 的总量,也包括 kubernetes 自身对象(比如 pod、services 等)的数量。通过 resource quota,kubernetes 可以防止某个 namespace 下的用户不加限制地使用超过期望的资源,比如说不对资源进行评估就大量申请 16核 CPU 32G内存的 pod。
下面是一个资源配额的实例,它限制了 namespace 只能使用 20核 CPU 和 1G 内存,并且能创建 10 个 pod、20个 rc、5个 service,可能适用于某个测试场景。
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota
spec:
hard:
cpu: "20"
memory: 1Gi
pods: "10"
replicationcontrollers: "20"
resourcequotas: "1"
services: "5"
resource quota 能够配置的选项还很多,比如 GPU、存储、configmaps、persistentvolumeclaims 等等,更多信息可以参考官方的文档。
Resource quota 要解决的问题和使用都相对独立和简单,但是它也有一个限制:那就是它不能根据集群资源动态伸缩。一旦配置之后,resource quota 就不会改变,即使集群增加了节点,整体资源增多也没有用。kubernetes 现在没有解决这个问题,但是用户可以通过编写一个 controller 的方式来自己实现。
- END -
往期推荐:
Kubernetes + Jenkins + Helm + Springboot 实践
🔥 1万位K8S爱好者都在这里,您值得关注!
点亮,为你2020年锦上添花