一个损失巨大的错误(关于Kubernetes和GKE)
【编者的话】云计算时代,对于Kubernetes来说,规模化的扩展带来了方便,但是规模化带来的成本也是不容小觑的,如何更低成本更高效的使用Kubernetes集群,本文将揭晓答案。
不幸的是,标题并没有夸张。作为免责声明,我首先要补充一点,这是一个非常愚蠢的错误,显示了我在管理自动伸缩部署方面缺乏经验。然而,这一切都始于一个没有答案的问题,我觉得有义务分享我的经验来帮助其他人避免类似的陷阱。
使用100x n1-standard-1 (1 vCPU) VMs的Kubernetes集群与使用1x n1-standard-96 (vCPU 96)或使用6x n1-standard-16 VMs (vCPU 16)的Kubernetes集群有什么区别?
这个问题我在Kubernetes社区问过很多次,没有人给出建设性的答案。如果你对答案不确定,那么你可以从我的经验中学到一些东西(如果没有耐心可以直接跳到答案部分)。
前提(序)
某一天,我半夜醒来,决定降低我们的基础设施成本。
我们正在运行一个大型Kubernetes集群。当然,“大”是相对的。在我们的例子中,正常工作时间是600个vcpu。这个数字在高峰时间翻倍,在晚上的某些时候接近于0。
上个月的发票是3500美元。
考虑到我们所获得的计算能力,这已经相当不错了,但是谷歌Kubernetes引擎(GKE)使成本管理变得非常简单:
- 我们使用最便宜的数据中心(europe-west2 (London)就比europe-west4 (Netherlands)贵15%)
- 我们为不同的部署使用不同的机器类型(偏重内存vs偏重CPU)
- 我们使用水平Pod自动调度器(HPA)和自定义指标来扩展部署
- 我们使用cluster-autoscaler来扩展节点池
- 我们使用可抢占的vm
使用专门可抢占的vm使我们能够保持低成本。为了说明节省的费用,对于在europe-west4中托管的n1-standard-1机器类型,专属VM和可抢占VM之间的差异是26.73美元/月和8.03美元/月,这样成本就降低了3.25倍。当然,可抢占的vm有其局限性,你需要熟悉和克服这些局限性,但这是一个完全不同的主题。
有了以上所有的条件,我觉得我们正在做所有正确的事情来保持低成本。然而,我总是有一种挥之不去的感觉,觉得事情不对劲,不吐不快。
主要的红色标志
关于那种烦人的情况:
每个节点的平均CPU使用量很低(10%-20%),这似乎是不对的。
我的第一个想法是我错误地配置了 计算资源 。所需的资源完全取决于正在运行的程序。因此,最好的方法是在没有资源限制的情况下部署程序,观察程序在空闲/常规负载和峰值负载期间的行为,并根据观察到的值设置请求/限制资源。
我将通过单个部署“admdesl”的示例来说明我的错误。
我们的使用情况是,所需资源是零星分布的:
NAME CPU(cores) MEMORY(bytes) admdesl-5fcfbb5544-lq7wc 3m 112Mi admdesl-5fcfbb5544-mfsvf 3m 118Mi admdesl-5fcfbb5544-nj49v 4m 107Mi admdesl-5fcfbb5544-nkvk9 3m 103Mi admdesl-5fcfbb5544-nxbrd 3m 117Mi admdesl-5fcfbb5544-pb726 3m 98Mi admdesl-5fcfbb5544-rhhgn 83m 119Mi admdesl-5fcfbb5544-rhp76 2m 105Mi admdesl-5fcfbb5544-scqgq 4m 117Mi admdesl-5fcfbb5544-tn556 49m 101Mi admdesl-5fcfbb5544-tngv4 2m 135Mi admdesl-5fcfbb5544-vcmjm 22m 106Mi admdesl-5fcfbb5544-w9dsv 180m 100Mi admdesl-5fcfbb5544-whwtk 3m 103Mi admdesl-5fcfbb5544-wjnnk 132m 110Mi admdesl-5fcfbb5544-xrrvt 4m 124Mi admdesl-5fcfbb5544-zhbqw 4m 112Mi admdesl-5fcfbb5544-zs75s 144m 103Mi
其中,平均5m的pod是“空闲”的:队列中有一个任务等待它们处理,但是我们正在等待某些(外部)条件清除后再继续。在这种特殊的部署情况下,这些pods每分钟将在空闲/活动状态之间多次改变,并在空闲状态中花费70%以上的时间。
一分钟后,相同的pods看起来就不一样了:
NAME CPU(cores) MEMORY(bytes) admdesl-5fcfbb5544-lq7wc 152m 107Mi admdesl-5fcfbb5544-mfsvf 49m 102Mi admdesl-5fcfbb5544-nj49v 151m 116Mi admdesl-5fcfbb5544-nkvk9 105m 100Mi admdesl-5fcfbb5544-nxbrd 160m 119Mi admdesl-5fcfbb5544-pb726 6m 103Mi admdesl-5fcfbb5544-rhhgn 20m 109Mi admdesl-5fcfbb5544-rhp76 110m 103Mi admdesl-5fcfbb5544-scqgq 13m 120Mi admdesl-5fcfbb5544-tn556 131m 115Mi admdesl-5fcfbb5544-tngv4 52m 113Mi admdesl-5fcfbb5544-vcmjm 102m 104Mi admdesl-5fcfbb5544-w9dsv 18m 125Mi admdesl-5fcfbb5544-whwtk 173m 122Mi admdesl-5fcfbb5544-wjnnk 31m 110Mi admdesl-5fcfbb5544-xrrvt 91m 126Mi admdesl-5fcfbb5544-zhbqw 49m 107Mi admdesl-5fcfbb5544-zs75s 87m 148Mi
看到上面的内容,我认为有一个配置是有意义的,比如:
resources: requests: memory: '150Mi' cpu: '20m' limits: memory: '250Mi' cpu: '200m'
这意味着:
- 闲置的pods消耗不超过20m
- 活跃(健康)的pods在200m时达到峰值
然而,当我使用这个配置时,它使部署变得繁忙了。
admdesl-78fc6f5fc9-xftgr 0/1 Terminating 3 21m admdesl-78fc6f5fc9-xgbcq 0/1 Init:CreateContainerError 0 10m admdesl-78fc6f5fc9-xhfmh 0/1 Init:CreateContainerError 1 9m44s admdesl-78fc6f5fc9-xjf4r 0/1 Init:CreateContainerError 0 10m admdesl-78fc6f5fc9-xkcfw 0/1 Terminating 0 20m admdesl-78fc6f5fc9-xksc9 0/1 Init:0/1 0 10m admdesl-78fc6f5fc9-xktzq 1/1 Running 0 10m admdesl-78fc6f5fc9-xkwmw 0/1 Init:CreateContainerError 0 9m43s admdesl-78fc6f5fc9-xm8pt 0/1 Init:0/1 0 10m admdesl-78fc6f5fc9-xmhpn 0/1 CreateContainerError 0 8m56s admdesl-78fc6f5fc9-xn25n 0/1 Init:0/1 0 9m6s admdesl-78fc6f5fc9-xnv4c 0/1 Terminating 0 20m admdesl-78fc6f5fc9-xp8tf 0/1 Init:0/1 0 10m admdesl-78fc6f5fc9-xpc2h 0/1 Init:0/1 0 10m admdesl-78fc6f5fc9-xpdhr 0/1 Terminating 0 131m admdesl-78fc6f5fc9-xqflf 0/1 CreateContainerError 0 10m admdesl-78fc6f5fc9-xrqjv 1/1 Running 0 10m admdesl-78fc6f5fc9-xrrwx 0/1 Terminating 0 21m admdesl-78fc6f5fc9-xs79k 0/1 Terminating 0 21m
每当一个新节点被加入/移出集群时都会发生这种情况(由于自动伸缩而经常发生这种情况)。
因此,我不断增加请求的pod资源,直到我完成以下配置:
resources: requests: memory: '150Mi' cpu: '100m' limits: memory: '250Mi' cpu: '500m'
通过这种配置,集群可以顺利地运行,但这意味着即使是空闲的pod也会预先分配比它们所需的更多的CPU时间。这就是每个节点的平均CPU使用量较低的原因。但是,我不知道解决方案是什么(减少请求的资源会导致繁忙的集群状态/中断),因此我为所有部署提供了一种慷慨的资源分配变体。
答案
回到我的问题:
使用100x n1-standard-1 (1 vCPU) VMs的Kubernetes集群与使用1x n1-standard-96 (vCPU 96)或使用6x n1-standard-16 VMs (vCPU 16)的Kubernetes集群有什么区别?
对于初学者来说,n1-standard-1和n1-standard-96之间没有单个vcpu的价格差异。因此,我认为使用具有较少vcpu的机器将使我能够更好地控制价格。
我考虑的另一个问题是集群自动伸缩的速度有多快,也就是说,如果突然出现激增,集群自动伸缩器为计划外的pod提供新节点的速度有多快。不过,这并不是我们所关心的问题- -我们对资源的需求是逐渐增加和减少的。
所以我用了1个vCPU节点,结果我在前提中已经描述过了。
回顾过去,这是一个明显的错误:使用单个vCPU在节点上分布pod,在空闲和活动状态之间的单个部署更改时,不允许有效地利用资源。换句话说,在同一台机器上的vcpu越多,就可以将许多pod打包得越紧,因为当一部分pod超出了它们所需的配额时,就有现成的资源可供使用。
有效的工作是:
- 我切换到16-vCPU机器,因为它们提供了一种平衡的解决方案,可以在自动扩展集群时进行良好的资源控制,也可以在每台机器上提供足够的资源来实现对处于空闲/活动状态的pod的严格调度。
- 我使用的资源配置请求仅比空闲状态所需的资源多一点点,但是有很大的限制。当大多数pods处于空闲状态时,它允许在同一台机器上调度多个pods,但仍然允许资源密集型突发事件。
- 我切换到n2机器类型:n2机器更贵,但是它们有2.8 GHz的基频(与n1-*机器可用的~2.2 GHz相比)。我们正在利用更高的时钟频率来尽可能快地处理资源密集型任务,并将pod尽可能快地置于前面描述的空闲状态。
当前节点vCPU的平均使用率高达60%。这听起来是对的,需要一些时间才能得出节省了多少。然而,惊喜的是,今天我们使用的vcpu还不到昨天的一半。
作者:Gajus Kuizinas。
原文链接: Mistake that cost thousands (Kubernetes, GKE)
译者:Mr.lzc,软件研发工程师、DevOpsDays深圳组织者&志愿者,目前供职于华为,从事云存储工作,以Cloud Native方式构建云文件系统服务,专注于K8s、微服务领域。