原文连接:优化生产环境中的 Kubernetes 资源分配html
我和 Kubernetes 的初次接触就涉及到将应用容器化并部署到生产环境集群中,当时个人工做重点是把 buffer 吞吐量最高(低风险)的某个端点从单个应用程序中分离出来,由于这个特殊的端点会给咱们带来很大的困扰,偶尔还会影响到其余更高优先级的流量。git
在使用 curl
进行一些手动测试以后,咱们决定将这个剥离出来的端点部署在 Kubernetes
上。当有 1%
的流量打进来时,服务运行正常,一切看起来都是那么地美好;当流量增长到 10%
时,也没有什么大问题;最后我将流量增长到 50%
,麻烦来了,这时候服务忽然陷入了 crash 循环状态。当时个人第一反应是将该服务的副本数扩到 20
个,扩完以后有一点成效,但没过多久 Pod 仍然陷入 crash 循环状态。经过 kubectl describe
查看审计日志,我了解到 Kubelet 由于 OOMKilled
杀掉了 Pod,即内存不足。深刻挖掘后,我找到了问题根源,当时我从另外一个 deployment 文件中复制粘贴 YAML 内容时设置了一些严格的内存限制,从而致使了上述一系列问题。这段经历让我开始思考如何才能有效地设置资源的 requests
和 limits
。github
Kubernetes 容许在 CPU
,内存和本地存储(v1.12 中的 beta 特性)等资源上设置可配置的请求和限制。像 CPU
这样的资源是可压缩的,这意味着对 CPU 资源的限制是经过 CPU 管理策略来控制的。而内存等其余资源都是不可压缩的,它们都由 Kubelet
控制,若是超过限制就会被杀死。使用不一样的 requests 和 limits 配置,能够为每一个工做负载实现不一样的服务质量(QoS)。正则表达式
limits
表示容许工做负载消耗资源的上限,若是资源的使用量越过配置的限制阈值将会触发 Kubelet 杀死 Pod。若是没有设置 limits
,那么工做负载能够占用给定节点上的全部资源;若是有不少工做负载都没有设置 limits
,那么资源将会被尽最大努力分配。apache
调度器使用 requests
来为工做负载分配资源,工做负载可使用全部 requests
资源,而无需 Kubernetes 的干预。若是没有设置 limits
而且资源的使用量超过了 requests
的阈值,那么该容器的资源使用量很快会被限制到低于 requests
的阈值。若是只设置了 limits
,Kubernetes 会自动把对应资源的 requests
设置成和 limits
同样。微信
在 Kubernetes 中经过资源和限制能够实现三种基本的 QoS
,QoS 的最佳配置主要仍是取决于工做负载的需求。app
经过只设置 limits 而不设置 requests 就能够实现 Guaranteed QoS
,这意味着容器可使用调度器为其分配的全部资源。对于绑定 CPU 和具备相对可预测性的工做负载(例如,用来处理请求的 Web 服务)来讲,这是一个很好的 QoS 等级。curl
经过配置 CPU 或内存的 limits 和 requests,而且 requests < limits
,就能够实现 Burstable QoS
。这意味着容器的资源使用量能够达到 requests 阈值,同时若是该容器运行的节点上资源充足,那么容器能够继续使用资源,只要不超过 limits 阈值就行。这对短期内须要消耗大量资源或者初始化过程很密集的工做负载很是有用,例如:用来构建 Docker 容器的 Worker 和运行未优化的 JVM
进程的容器均可以使用该 QoS 等级。工具
经过既不设置 limits 也不设置 requests,能够实现 Best effort QoS
。这意味着容器可使用宿主机上任何可用的资源。从调度器的角度来看,这是最低优先级的任务,而且会在 Burstable QoS Pod
和 Guaranteed QoS Pod
以前被先杀掉。这对于可中断和低优先级的工做负载很是有用,例如:迭代运行的幂等优化过程。post
设置 limits 和 requests 的关键是找到单个 Pod 的断点。经过使用几种不一样的负载测试技术,能够在应用程序部署到生产环境以前对应用程序的故障模式有一个全面的了解。当资源使用量达到限制阈值时,几乎每一个应用程序都有本身的一组故障模式。
在准备测试以前,请确保将 Pod 的副本数设置为 1,而且将 limits
设置为一组保守的数字,例如:
# limits might look something like
replicas: 1
...
cpu: 100m # ~1/10th of a core
memory: 50Mi # 50 Mebibytes
复制代码
**注意:**在测试过程当中设置 limits 很是重要,它可让咱们看到预期的效果(在内存较高时限制 CPU 并杀死 Pod)。在测试的迭代过程当中,最好每次只更改一种资源限制(CPU 或内存),不要同时更改。
负载增长测试会随着时间的推移增长负载,直到负载下的服务忽然失败或测试完成。
若是负载增长测试忽然失败,则代表资源限制过于严格,这是一个很好的迹象。当观察到图像有明显抖动时,将资源限制增长一倍并重复,直到测试成功完成。
当资源限制接近最优时,性能应该随着时间的推移而可预测地下降(至少对于 Web 服务而言应该是这样)。
若是在增长负载的过程当中性能并无太大的变化,则说明为工做负载分配了太多的资源。
在运行负载增长测试并调整资源限制以后,下一步就开始进行负载不变测试。负载不变测试会在一段很长的时间内(至少 10 分钟,时间再长一点更好)对应用施加相同的负载,至于加多少负载,最好选择在图像出现断点以前的压力值(例如:客户端数量)。
此测试的目的是识别内存泄漏和隐藏的排队机制,由于这些机制在负载增长测试中很难被捕获到。到了这个阶段,即便还要对资源限制进行调整,调整的幅度也应该很小。理想状况下,该阶段测试期间性能应该会保持稳定。
在测试过程当中,记录服务失败时作了哪些操做是相当重要的。能够将发现的故障模式添加到相关的书籍和文档中,这对分类生产环境中出现的问题颇有用。下面是咱们在测试过程当中发现的一些故障模式:
你最好将这些发现都收集起来,以备不时之需,由于有一天它们可能会为你或团队节省一成天的时间。
虽然你可使用 Apache Bench 等工具来增长负载,也可使用 cAdvisor 来可视化资源使用率,但这里我要介绍一些更适合负载测试的工具。
Loader.io 是一个在线负载测试工具,它容许你配置负载增长测试和负载不变测试,在测试过程当中可视化应用程序的性能和负载,并能快速启动和中止测试。它也会保存测试结果的历史记录,所以在资源限制发生变化时很容易对结果进行比较。
Kubescope cli 是一个能够运行在本地或 Kubernetes 中的工具,可直接从 Docker Daemon 中收集容器指标并可视化。和 cAdvisor
等其余集群指标收集服务同样, kubescope cli
收集指标的周期是 1 秒(而不是 10-15 秒)。若是周期是 10-15 秒,你可能会在测试期间错过一些引起性能瓶颈的问题。若是你使用 cAdvisor 进行测试,每次都要使用新的 Pod 做为测试对象,由于 Kubernetes 在超过资源限制时就会将 Pod 杀死,而后从新启动一个全新的 Pod。而 kubescope cli
就没有这方面的忧虑,它直接从 Docker Daemon 中收集容器指标(你能够自定义收集指标的时间间隔),并使用正则表达式来选择和过滤你想要显示的容器。
我发如今搞清楚服务何时会出现故障以及为何会出现故障以前,不该该将其部署到生产环境中。我但愿您能从个人错误中吸收教训,并经过一些技术手段来设置应用的资源 limits
和 requests
。这将会为你的系统增长弹性能力和可预测性,使你的客户更满意,并有望帮助你得到更多的睡眠。