深入理解Kubernetes资源限制:内存

写在前面
当我开始大范围使用Kubernetes的时候,我开始考虑一个我做实验时没有遇到的问题:当集群里的节点没有足够资源的时候,Pod会卡在Pending状态。你是没有办法给节点增加CPU或者内存的,那么你该怎么做才能将这个Pod从这个节点拿走?最简单的办法是添加另一个节点,我承认我总是这么干。最终这个策略无法发挥出Kubernetes最重要的一个能力:即它优化计算资源使用的能力。这些场景里面实际的问题并不是节点太小,而是我们没有仔细为Pod计算过资源限制。

资源限制是我们可以向Kubernetes提供的诸多配置之一,它意味着两点:工作负载运行需要哪些资源;最多允许消费多少资源。第一点对于调度器而言十分重要,因为它要以此选择合适的节点。第二点对于Kubelet非常重要,每个节点上的守护进程Kubelet负责Pod的运行健康状态。大多数本文的读者可能对资源限制有一定的了解,实际上这里面有很多有趣的细节。在这个系列的两篇文章中我会先仔细分析内存资源限制,然后第二篇文章中分析CPU资源限制。

资源限制
资源限制是通过每个容器containerSpec的resources字段进行设置的,它是v1版本的ResourceRequirements类型的API对象。每个指定了"limits"和"requests"的对象都可以控制对应的资源。目前只有CPU和内存两种资源。第三种资源类型,持久化存储仍然是beta版本,我会在以后的博客里进行分析。大多数情况下,deployment、statefulset、daemonset的定义里都包含了podSpec和多个containerSpec。这里有个完整的v1资源对象的yaml格式配置:


这个对象可以这么理解:这个容器通常情况下,需要5%的CPU时间和50MiB的内存(requests),同时最多允许它使用10%的CPU时间和100MiB的内存(limits)。我会对requests和limits的区别做进一步讲解,但是一般来说,在调度的时候requests比较重要,在运行时limits比较重要。尽管资源限制配置在每个容器上,你可以认为Pod的资源限制就是它里面容器的资源限制之和,我们可以从系统的视角观察到这种关系。

内存限制
通常情况下分析内存要比分析CPU简单一些,所以我从这里开始着手。我的一个目标是给大家展示内存在系统中是如何实现的,也就是Kubernetes对容器运行时(docker/containerd)所做的工作,容器运行时对Linux内核所做的工作。从分析内存资源限制开始也为后面分析CPU打好了基础。首先,让我们回顾一下前面的例子:


单位后缀Mi表示的是MiB,所以这个资源对象定义了这个容器需要50MiB并且最多能使用100MiB的内存。当然还有其他单位可以进行表示。为了了解如何用这些值是来控制容器进程,我们首先创建一个没有配置内存限制的Pod:

$ kubectl run limit-test --image=busybox --command -- /bin/sh -c "while true; do sleep 2; done"

deployment.apps "limit-test" created

用Kubectl命令我们可以验证这个Pod是没有资源限制的:

$ kubectl get pods limit-test-7cff9996fc-zpjps -o=jsonpath=‘{.spec.containers[0].resources}‘

map[]

Kubernetes最酷的一点是你可以跳到系统以外的角度来观察每个构成部分,所以我们登录到运行Pod的节点,看看Docker是如何运行这个容器的:

$ docker ps | grep busy | cut -d‘ ‘ -f1

5c3af3101afb

$ docker inspect 5c3af3101afb -f "{{.HostConfig.Memory}}"

0

这个容器的.HostConfig.Memory域对应了docker run时的--memory参数,0值表示未设定。Docker会对这个值做什么?为了控制容器进程能够访问的内存数量,Docker配置了一组control group,或者叫cgroup。Cgroup在2008年1月时合并到Linux 2.6.24版本的内核。它是一个很重要的话题。我们说cgroup是容器的一组用来控制内核如何运行进程的相关属性集合。针对内存、CPU和各种设备都有对应的cgroup。Cgroup是具有层级的,这意味着每个cgroup拥有一个它可以继承属性的父亲,往上一直直到系统启动时创建的root cgroup。

Cgroup可以通过/proc和/sys伪文件系统轻松查看到,所以检查容器如何配置内存的cgroup就很简单了。在容器的Pid namespace里,根进程的pid为1,但是namespace以外它呈现的是系统级pid,我们可以用来查找它的cgroups:

*$ ps ax | grep /bin/sh

9513 ? Ss 0:00 /bin/sh -c while true; do sleep 2; done

$ sudo cat /proc/9513/cgroup

...

6:memory:/kubepods/burstable/podfbc202d3-da21-11e8-ab5e-42010a80014b/0a1b22ec1361a97c3511db37a4bae932d41b22264e5b97611748f8b662312574
*
我列出了内存cgroup,这正是我们所关注的。你在路径里可以看到前面提到的cgroup层级。一些比较重要的点是:首先,这个路径是以kubepods开始的cgroup,所以我们的进程继承了这个group的每个属性,还有burstable的属性(Kubernetes将Pod设置为burstable QoS类别)和一组用于审计的Pod表示。最后一段路径是我们进程实际使用的cgroup。我们可以把它追加到/sys/fs/cgroups/memory后面查看更多信息:

$ ls -l /sys/fs/cgroup/memory/kubepods/burstable/podfbc202d3-da21-11e8-ab5e-42010a80014b/0a1b22ec1361a97c3511db37a4bae932d41b22264e5b97611748f8b662312574

...

-rw-r--r-- 1 root root 0 Oct 27 19:53 memory.limit_in_bytes

-rw-r--r-- 1 root root 0 Oct 27 19:53 memory.soft_limit_in_bytes

再一次,我只列出了我们所关心的记录。我们暂时不关注memory.soft_limit_in_bytes,而将重点转移到memory.limit_in_bytes属性,它设置了内存限制。它等价于Docker命令中的--memory参数,也就是Kubernetes里的内存资源限制。我们看看:

$ sudo cat /sys/fs/cgroup/memory/kubepods/burstable/podfbc202d3-da21-11e8-ab5e-42010a80014b/0a1b22ec1361a97c3511db37a4bae932d41b22264e5b97611748f8b662312574/memory.limit_in_bytes

9223372036854771712

这是没有设置资源限制时我的节点上显示的情况。这里有对它的一个简单的解释(https://unix.stackexchange.com/questions/420906/what-is-the-value-for-the-cgroups-limit-in-bytes-if-the-memory-is-not-restricte)。所以我们看到如果没有在Kubernetes里设置内存限制的话,会导致Docker设置HostConfig.Memory值为0,并进一步导致容器进程被放置在默认值为"no limit"的memory.limit_in_bytes内存cgroup下。我们现在创建使用100MiB内存限制的Pod:

$ kubectl run limit-test --image=busybox --limits "memory=100Mi" --command -- /bin/sh -c "while true; do sleep 2; done"

deployment.apps "limit-test" created

我们再一次使用kubectl验证我们的资源配置:

$ kubectl get pods limit-test-5f5c7dc87d-8qtdx -o=jsonpath=‘{.spec.containers[0].resources}‘

map[limits:map[memory:100Mi] requests:map[memory:100Mi]]

你会注意到除了我们设置的limits外,Pod还增加了requests。当你设置limits而没有设置requests时,Kubernetes默认让requests等于limits。如果你从调度器的角度看这是非常有意义的。我会在下面进一步讨论requests。当这个Pod启动后,我们可以看到Docker如何配置的容器以及这个进程的内存cgroup:

$ docker ps | grep busy | cut -d‘ ‘ -f1

8fec6c7b6119

$ docker inspect 8fec6c7b6119 --format ‘{{.HostConfig.Memory}}‘

104857600

$ ps ax | grep /bin/sh

29532 ? Ss 0:00 /bin/sh -c while true; do sleep 2; done

$ sudo cat /proc/29532/cgroup
...
6:memory:/kubepods/burstable/pod88f89108-daf7-11e8-b1e1-42010a800070/8fec6c7b61190e74cd9f88286181dd5fa3bbf9cf33c947574eb61462bc254d11

$ sudo cat /sys/fs/cgroup/memory/kubepods/burstable/pod88f89108-daf7-11e8-b1e1-42010a800070/8fec6c7b61190e74cd9f88286181dd5fa3bbf9cf33c947574eb61462bc254d11/memory.limit_in_bytes

104857600

正如你所见,Docker基于我们的containerSpec正确地设置了这个进程的内存cgroup。但是这对于运行时意味着什么?Linux内存管理是一个复杂的话题,Kubernetes工程师需要知道的是:当一个宿主机遇到了内存资源压力时,内核可能会有选择性地杀死进程。如果一个使用了多于限制内存的进程会有更高几率被杀死。因为Kubernetes的任务是尽可能多地向这些节点上安排Pod,这会导致节点内存压力异常。如果你的容器使用了过多内存,那么它很可能会被oom-killed。如果Docker收到了内核的通知,Kubernetes会找到这个容器并依据设置尝试重启这个Pod。

所以Kubernetes默认创建的内存requests是什么?拥有一个100MiB的内存请求会影响到cgroup?可能它设置了我们之前看到的memory.soft_limit_in_bytes?让我们看看:

$ sudo cat /sys/fs/cgroup/memory/kubepods/burstable/pod88f89108-daf7-11e8-b1e1-42010a800070/8fec6c7b61190e74cd9f88286181dd5fa3bbf9cf33c947574eb61462bc254d11/memory.soft_limit_in_bytes

9223372036854771712

你可以看到软限制仍然被设置为默认值“no limit”。即使Docker支持通过参数--memory-reservation进行设置,但Kubernetes并不支持这个参数。这是否意味着为你的容器指定内存requests并不重要?不,不是的。requests要比limits更重要。limits告诉Linux内核什么时候你的进程可以为了清理空间而被杀死。requests帮助Kubernetes调度找到合适的节点运行Pod。如果不设置它们,或者设置得非常低,那么可能会有不好的影响。

例如,假设你没有配置内存requests来运行Pod,而配置了一个较高的limits。正如我们所知道的Kubernetes默认会把requests的值指向limits,如果没有合适的资源的节点的话,Pod可能会调度失败,即使它实际需要的资源并没有那么多。另一方面,如果你运行了一个配置了较低requests值的Pod,你其实是在鼓励内核oom-kill掉它。为什么?假设你的Pod通常使用100MiB内存,你却只为它配置了50MiB内存requests。如果你有一个拥有75MiB内存空间的节点,那么这个Pod会被调度到这个节点。当Pod内存消耗扩大到100MiB时,会让这个节点压力变大,这个时候内核可能会选择杀掉你的进程。所以我们要正确配置Pod的内存requests和limits。

相关服务请访问:
https://support.huaweicloud.com/cce/index.html?utm_content=cce_helpcenter_2019

原文地址:https://blog.51cto.com/14051317/2359925

时间: 2024-07-30 15:17:06

深入理解Kubernetes资源限制:内存的相关文章

深入理解 Kubernetes 资源限制:CPU

原文地址:https://www.yangcs.net/posts/understanding-resource-limits-in-kubernetes-cpu-time/ 在关于 Kubernetes 资源限制的系列文章的第一篇文章中,我讨论了如何使用 ResourceRequirements 对象来设置 Pod 中容器的内存资源限制,以及如何通过容器运行时和 linux control group(cgroup)来实现这些限制.我还谈到了 Requests 和 Limits 之间的区别,其

深入理解Kubernetes资源限制:CPU

写在前面在上一篇关于Kubernetes资源限制的文章我们讨论了如何通过ResourceRequirements设置Pod中容器内存限制,以及容器运行时是如何利用Linux Cgroups实现这些限制的.也分析了requests是用来通知调度器Pod所需资源需求和limits是在宿主机遇到内存压力时帮助内核限制资源二者的区别. 在本文中,我会继续深入探讨CPU时间的requests和limits.你是否阅读过第一篇文章并不会影响本文的学习,但是我建议你两篇文章都读一读,从而得到工程师或者集群管理

kubernetes资源对象与基本概念解析

Objects 以下列举的内容都是 kubernetes 中的 Object,这些对象都可以在 yaml 文件中作为一种 API 类型来配置. Pod Node Namespace Service Volume PersistentVolume Deployment Secret StatefulSet DaemonSet ServiceAccount ReplicationController ReplicaSet Job CronJob SecurityContext ResourceQuo

深入理解JVM之JVM内存区域与内存分配

深入理解JVM之JVM内存区域与内存分配 在学习jvm的内存分配的时候,看到的这篇博客,该博客对jvm的内存分配总结的很好,同时也利用jvm的内存模型解释了java程序中有关参数传递的问题. 博客出处: http://www.cnblogs.com/hellocsl/p/3969768.html?utm_source=tuicool&utm_medium=referral 看了此博客后,发现应该去深入学习下jvm的内存模型,就是去认真学习下<深入理解Java虚拟机>,其内容可能会<

深入理解JMM(Java内存模型) --(一)

并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信. 同步是指程序用于控制不同线程之间操作发生相对顺序的机制.在共享内存并发

如何理解“不要通过共享内存来通信,而应该通过通信来共享内存”?

不要通过共享内存来通信,而应该通过通信来共享内存 这是一句风靡golang社区的经典语,对于刚接触并发编程的人,该如何理解这句话? 如何理解"不要通过共享内存来通信,而应该通过通信来共享内存"? >> golang 这个答案描述的挺清楚的:http://www.goodpm.net/postreply/golang/1010000008937789/如何理解不要通过共享内存来通信而应该通过通信来共享内存.html

【读书笔记::深入理解linux内核】内存寻址

我对linux高端内存的错误理解都是从这篇文章得来的,这篇文章里讲的 物理地址 = 逻辑地址 – 0xC0000000:这是内核地址空间的地址转换关系. 这句话瞬间让我惊呆了,根据我的CPU的知识,开启分页之后,任何寻址都要经过mmu的转换,也就是一个二级查表的过程(386) 难道内核很特殊,当mmu看到某个逻辑地址是内核传来的之后,就不查表了,直接减去0xC0000000,然后就传递给内存控制器了??? 我发现网上也有人和我问了同样的问题,看这个问题 这句话太让人费解了,让人费解到以至于要怀疑

kubefuse 让Kubernetes 资源成为fuse 文件系统

kubefuse 是基于fuse 开发的文件系统,我们可以像访问文件系统一样访问Kubernetes 资源,使用python开发 支持以下特性: 可以使用方便的linux tools: ls. vim .cat 像文件系统一样查看Kubernetes 资源 像文件系统一样访问Kubernetes 资源描述cat ~/kubernetes/default/pod/postgres-aazm1/describe 方便的备份,导出信息 一种可选的持续部署方案 说明 使用kubefuse 工具挂载我们的

kubernetes资源清单定义

kubernetes资源清单定义 工作负载型资源(workload): Pod ReplicaSet Deployment StatefulSet DaemonSet Job CronJob (ReplicationController在v1.11版本被废弃) 服务发现及负载均衡型资源: ServiceDiscovery LoadBalance Service Ingress, ... 配置与存储型资源: Volume(存储卷) CSI(容器存储接口,可以扩展各种各样的第三方存储卷) 特殊类型的