本文译自Kirill Goltsman所写的Assigning Computing Resources to Containers and Pods in Kubernetes,原文地址为 https://supergiant.io/blog/assigning-computing-resources-to-containers-and-pods-in-kubernetes/ 。为使文章只关注于k8s,翻译时移除了原文中关于supergiant的部分。译文也同时参考了k8s官方文档中关于Quality of Service for Pods的部分。
Kubernetes的资源模型(resource model)被设计成既能优化容器对资源的利用又能保证Pods的高效调度和应用的高可用。Kubernetes实现这一方法的核心是在Pod创建时规定容器的resource requests和limits(资源请求和限制)。本文将介绍Kubernetes resource model的内部机制,并带着你使用Kubernetes内置的工具和API为容器分配计算资源(CPU和RAM)。希望本文能让你对如何给容器分配资源获得更加深入的理解。
在Kubernetes中,我们可以通过设定 CPU 和 Memory(RAM) 的requests和limits,来为Pod中的每个容器分配资源。这些设定将作为调度器(scheduler)的依据,为Pods选择合适的运行节点(node)。
Kubernetes文档将resource limit定义为 Kubernetes确保能够赋予给容器的计算资源(CPU或memory)的总量。相应的,resource limit 被定义为 Kubernetes允许容器所消耗的资源的最大量。
需要注意的是,一个Pod是否能被调度到某个节点,取决于这个Pod内所有的容器的resource requests和limits的总和。也就是说,如果Pod内的某个容器所需求的资源量与某个Node可分配的最大的资源量不匹配,这个Pod将无法被调度到该节点上。确保所有容器的资源需求总量在Node的容量范围内,既是调度器又是 kubelet 的任务。
容器所声明的resource requests应该大于等于0并且不超过节点的可分配容量。这条规则可用以下公式总结:0 <= request <= Node Allocatable。而resource limit 则应该大于等于 resource request 并且其值无上限:request <= limit <= Infinity。
应该记住的是,Pods的调度依赖的是 requests 而不是 limits。换句话说,如果一个Pod的resource request在Node的可分配容量之内,即使该Pod的 resource limit过大,Pod也是可以被调度到该Node上的。同样要注意的是,即使一个Node的实际memory或CPU利用率不高,如果pod的resource request超过该Node的容量了,调度器也会拒绝把pod放置在这个Node上。这个特性防止了在突然出现流量高峰的情况下,系统出现资源短缺。
Kubernetes将底层处理器架构抽象为了计算资源,将它们按照需求暴露为原始值或基本单位。对于CPU资源来说,这些基本单位是基于核心(cores)的;对于内存来说,则是基于字节的。内存资源可以使用单纯的数值或带有后缀(E、P、T、G、M、K)的定点整数表示。而一个CPU则相当于:
一个AWS vCPU
一个GCP Core
一个Azure vCore
英特尔处理器上一个 Hyperthread(处理器要支持Hyperthreading)
Kubernetes允许按小数来指定CPU (比如 0.5 CPU)。举个例子,一个 resource request 为 0.5 CPU的容器,将会被确保获得一半CPU的算力。0.5 CPU 等同于 500m,后者的意思是 "500 millicpu" 或者 "500 minicores"。需要注意的是,在Kubernetes中,CPU资源用的是绝对量,意思是无论是在单核、双核或其它机器上,0.1 对应的量是一样的(译者解释: 对于2核的cpu来说,500m意味着用半核;2000m意味着2核都用;0.5意味着用半核;2意味着2核都用;以此类推)。
根据 resource requests和limit的 min/max 比例,我们最终会得到3类不同的Pods:guaranted Pods、burstable Pods 和 best-effort Pods。
(译者解释,pod在创建后,会有一个 spec.qosClass字段,其值可以是 Guaranteed、Burstable和BestEffort)
Guaranteed Pods
当一个Pod内的每个容器,其 request.memory等于limit.memory且request.cpu等于limit.cpu时,这个Pod被认为是 Guaranteed。
Example:
containers:
name: first
resources:
limits:
cpu: 20m
memory: 1Gi
requests:
cpu: 20m
memory: 1Gi
name: second
resources:
limits:
cpu: 300m
memory: 400Mi
requests:
cpu: 300m
memory: 400Mi
复制
在本例中,名为first和second的容器,各自内部的request和limit都是相等的。这使得它们所处的Pod 为 guaranted。
需要满足2个条件:
a. 不是 Guaranteed Pod;
b. Pod内至少有一个容器设置了 memory 或 cpu request。
Example:
containers:
name: first
resources:
limits:
memory: 2Gi
requests:
memory: 1Gi
name: second
resources:
limits:
cpu: 500m
requests:
cpu: 300m
复制
Best-Effort Pods
如果一个Pods内的所有容器都没有设置resource requests和limits,则这个pod被认为是 best-effort。
Example:
containers:
name: first
resources:
name: second
resources:
复制
Kubernetes根据上述不同类型的pod,将给出不同的资源使用权和优先级。Best-Effort Pods 有着最低的优先级,在系统内存不足时,它们是第一批被清理的对象。Guaranteed Pods有着最高的优先级,通常不会被杀死或节流,除非资源使用超过了limits的限制并且没有其它更低优先级的pods可清理了。最后,Burstable Pods有着最小的资源保证(译者注,因为设置了resource request)但是条件允许时允许使用更多的计算资源。在没有Best-Effort Pods存在并且系统容量不足时,Burstable Pods将是集群中第一批被杀死的。
下面的规则适用于所有这3类pods:
如果节点中内存充足,容器可以使用比memory request规定的更多的内存。但不能超过limit所限制的量。这类容器有被终止的可能。如果终止的容器被设置为可重启的,kubelet 会负责这一块。
容器的cpu使用可能会被允许超限一小段时间。是否允许取决于容器的运行时环境(Docker、rkt)。因此有些容器在CPU使用上可以超限运行一小段时间,而有些则不能。CPU是否能超限使用还取决于Node中剩余多少空闲CPU。如果有其它容器也在争取这些资源,则要参考容器的resource request。如果没有别的容器想要这些资源,容器可以使用尽可能多的资源。
对于 resource requests和limits如何影响Pods的命运,我们现在有了基本的理解。然而,我们为什么要使用它们呢?
我们有2个主要动机:安全高效地使用计算资源;确保高优先级的Pods正常运行。更确切地说:
CPU和memory requests比较低的pods,有更好的机会被调度
如果你把resource limits设置的高于resource requests,则在资源充足时,Pods可以利用更多的资源。同时,设定resource limits后,可以保证资源不会过度使用
在创建Pod时,也允许用户不设定resource requestst和limits,这种情况则符合以下规则:
容器可以使用Node上的所有资源
如果容器所在的namespace设置了memory或CPU limit,集群管理员可以使用 LimitRange 为所有容器指定limit的默认值
接下来,我们将向你展示,使用Kubernetes内置的工具给容器分配resource requests和limits是多么容易。
要完成本示例,得满足以下前提:
一个运行中的Kubernetes集群。你可以使用 Minikube安装一个单节点集群
一个配置好了的可以与集群通信的 kubectl 命令行工具
集群中需要运行着 Heapster service。可通过 kubectl get services --namespace=kube-system 来查看是否安装了该服务
好了,我们可以开始给Kubernetes中的容器分配资源了。
创建一个新的namespace是一个好的实践,它能帮我们将项目中的计算资源和集群中其它的部分进行分离。我们使用以下命令来创建namespace:
kubectl create namespace assigning-resources-tut
复制
控制台会输出以下内容:
namespace "assigning-resources-tut" created
复制
本教程将使用单容器Pod,这也是Kubernetes中最常见的Pod类型。用的容器镜像是NGINX。为了指定 CPU和memory request,我们在Pod manifest中使用 spec.containers.resources.requests。对于resource limits,我们使用 spec.containers.resources.limits字段。我们决定把NGINX容器的resource request设置为 0.5 CPU、CPU limit设置为 1 CPU。同时 memory request 设置为 500MiB 且 memory limit 为 700MiB。
下面是Pod的配置文件:
apiVersion: v1
kind: Pod
metadata:
name: test-pod
namespace: assigning-resources-tut
spec:
containers:
- name: nginx
image: nginx:latest
resources:
limits:
cpu: "1"
memory: "700Mi"
requests:
cpu: "0.5"
memory: "500Mi"
复制
执行以下命令:
kubectl create -f test-pod.yaml --namespace=assigning-resources-tut
复制
运行以下命令:
kubectl get pod test-pod --namespace=assigning-resources-tut
复制
可以看到如下输出:
NAME READY STATUS RESTARTS AGE
test-pod 1/1 Running 0 8s
复制
这意味着 test-pod 在运行中,没重启过并且存活了8秒了。
也可以运行以下命令,以yaml格式查看pod的详细信息:
kubectl get pod test-pod --output=yaml --namespace=assigning-resources-tut
复制
一部分输出如下:
spec:
containers:
- image: nginx:latest
imagePullPolicy: Always
name: nginx
resources:
limits:
cpu: "1"
memory: 700Mi
requests:
cpu: 500m
memory: 500Mi
复制
能够追踪Pod的资源使用情况是很方便的。我们可以使用 Heapster service 做检查。Heapster可以对Kubernetes进行集群监控和性能分析。
要使用Heapster,我们首先要启动一个proxy:
kubectl proxy
复制
该proyx在当前终端窗口运行。为了使用Heapster,我们需要再开启一个终端窗口。要获取CPU使用率,在新的终端窗口中执行:
curl http://localhost:8001/api/v1/namespaces/kube-system/services/heapster/proxy/api/v1/model/namespaces/assigning-resources-tut/pods/test-pod/metrics/cpu/usage_rate
复制
可以看到,我们访问了指向了我们namespace下的test-pod下的 cpu/usage_rate 接口。得到的结果类似于下面:
{
"metrics": [
{
"timestamp": "2018-05-13T21:43:40Z",
"value": 0.1
},
{
"timestamp": "2018-05-13T21:44:40Z",
"value": 0.2
},
{
"timestamp": "2018-05-13T21:45:00Z",
"value": 0.22
}
复制
它展示的是每个时间点上的CPU使用情况。这便于我们追踪CPU随时间的变化。
同样,要获取内存使用情况,访问 memory/usage :
curl http://localhost:8001/api/v1/namespaces/kube-system/services/heapster/proxy/api/v1/model/namespaces/assigning-resources-tut/pods/test-pod/metrics/memory/usage
复制
下面的输出意味着Pod使用了13246454的RAM。
{
"timestamp": "2018-05-13T22:00:40Z",
"value": 13246464
},
{
"timestamp": "2018-05-13T22:01:00Z",
"value": 13246464
}
复制
本文快要结束了,我们把Pod清理掉吧。
kubectl delete pod test-pod --namespace=assigning-resources-tut
复制
希望本文能帮你理解Kubernetes resource model实际上是如何工作的。如我们所学的这样,resource requests 和 limits这一概念简单且强大。合理地使用它们能帮助我们控制pods如何利用集群中的计算资源。Kubernetes也提供了灵活的方式来创建不同优先级的pods,方便最有效的方式来分发有限的资源;同时也确保了最重要的应用和容器总是处于运行状态,降低了突发的流量高峰和node故障带来的影响。