Helm是Kubernetes的包管理工具,用的人很多,但用久了就会发现,随着Chart越来越多,集群越来越大,Helm的操作会变得越来越慢,有时候安装一个Release要等好几分钟,甚至超时失败。
最近我们就遇到了Helm性能问题,随着业务的发展,我们的K8s集群越来越大,部署的服务越来越多,Helm的操作也越来越慢,尤其是安装和升级大的Chart的时候,经常要等好几分钟,CI/CD流水线也经常因为Helm超时而失败,严重影响了开发效率。
经过一番排查和优化,我们把Helm安装的平均时间,从原来的3分多钟,降到了20秒以内,效果非常明显。今天来分享一下这次性能优化的过程和经验,从问题定位到原因分析,再到具体的优化措施,希望能给用Helm的朋友一些参考。
一、背景和问题
先说说项目背景和遇到的问题。
我们的团队,用K8s做容器编排,用Helm做应用的包管理和部署,大大小小的服务,加起来有一百多个,每个服务都有自己的Helm Chart,通过CI/CD流水线,自动部署到K8s集群。
最开始,服务少的时候,Helm用得很顺畅,安装一个服务,几十秒就搞定了。但随着服务越来越多,Chart越来越复杂,集群越来越大,Helm的操作就开始变慢了,尤其是安装和升级大的Chart的时候,经常要等好几分钟,有时候还会超时失败。
最严重的时候,CI/CD流水线,因为Helm操作超时,每天都有好几次失败,开发人员要反复重试,非常影响效率。而且,Helm操作慢的时候,还会占用大量的资源,影响集群上其他服务的稳定性。
我们的目标是,把Helm安装的平均时间,降到30秒以内,最好能做到20秒以内,同时保证稳定性,不再出现超时失败的情况。
二、第一步:性能 profiling,找到瓶颈
性能优化的第一步,还是老规矩,先做性能 profiling,找到瓶颈在哪里,不要上来就瞎优化。
我们用了几种方法,来分析Helm操作的整个过程:
1. 加时间戳日志
在Helm操作的各个关键节点,加了时间戳日志,包括:
- Helm命令开始执行
- Chart加载和渲染完成
- 与K8s API建立连接
- 资源创建/更新开始
- 每个资源创建完成
- 等待资源就绪
- Helm操作完成
通过这些日志,我们就能清楚地看到,时间都花在了哪个环节。
2. Helm的--debug参数
用Helm的--debug参数,可以看到更详细的执行过程,包括渲染后的模板、API请求、错误信息等,帮助我们定位问题。
3. K8s API Server的日志
查看K8s API Server的日志,看API请求的响应时间,有没有慢请求,有没有错误。
4. Helm和Tiller的资源监控
监控Helm客户端和Tiller(Helm 2的服务端组件)的CPU、内存、网络使用情况,看是不是资源瓶颈。
经过一周的 profiling,我们终于找到了瓶颈所在,时间主要花在了这几个地方:
- Chart渲染,占了约30秒:大的Chart,模板渲染很慢,尤其是有很多依赖和条件判断的时候。
- K8s API请求,占了约60秒:创建和更新大量资源的时候,API请求很多,而且是串行的,一个一个来,很慢。
- 等待资源就绪,占了约80秒:创建完资源后,Helm会等待资源就绪,这个等待时间很长,尤其是有状态服务和InitContainer多的服务。
- Tiller和API Server的交互,占了约40秒:Tiller和API Server之间的通信,有很多 overhead,尤其是网络延迟和序列化反序列化。
加起来,总共3分多钟,和实际测的差不多。
找到了瓶颈,就可以针对性地优化了。
三、优化一:Chart渲染优化,从30秒降到5秒
Chart渲染是第一个瓶颈,大的Chart,模板渲染很慢。
问题1:模板太复杂,嵌套太深
我们的Chart,为了复用,做了很多嵌套的模板和子模板,还有大量的条件判断和循环,导致渲染的时候,要做很多计算,很慢。
优化措施:
- 简化模板,减少不必要的嵌套和条件判断,能合并的合并,能去掉的去掉。
- 把一些复杂的计算,从模板里移出去,放到Chart的values里,或者用Helm的--set参数传入,减少模板渲染时的计算。
- 减少子模板的调用,子模板调用有开销,能直接写的就直接写,不要为了复用而过度抽象。
- 用helm template命令,提前渲染模板,检查渲染结果,优化慢的部分。
优化之后,模板渲染的时间,从30秒降到了10秒左右。
问题2:依赖太多,下载慢
我们的Chart,有很多依赖,每次安装的时候,都要下载依赖的Chart,很慢。
优化措施:
- 把依赖的Chart,提前下载好,放到本地的Chart仓库,或者直接打包到主Chart里,减少下载时间。
- 用Helm的依赖缓存,避免重复下载。
- 清理不需要的依赖,只保留真正用到的。
优化之后,依赖下载的时间,大大减少了。
问题3:values文件太大,解析慢
我们的values文件,因为要支持很多环境和配置,变得很大,解析起来很慢。
优化措施:
- 拆分values文件,按环境和模块拆分,用的时候合并,减少每次解析的文件大小。
- 去掉不需要的默认值,只保留必要的。
- 用--values参数,只传入需要的配置,不要每次都加载整个大文件。
经过这一系列优化,Chart渲染的总时间,从30秒降到了5秒左右,效果非常明显。
四、优化二:K8s API请求优化,从60秒降到10秒
K8s API请求是第二大瓶颈,创建和更新大量资源的时候,请求很多,而且是串行的,很慢。
问题1:请求串行执行
Helm默认是串行执行API请求的,一个资源创建完了,才创建下一个,资源多的时候,就很慢。
优化措施:
- 升级到Helm 3,Helm 3去掉了Tiller,架构更轻量,API请求的处理也更高效,而且支持一定程度的并行。
- 对于没有依赖关系的资源,尽量合并成一个API请求,或者用Helm的钩子(hook)来控制顺序,减少串行等待。
- 把多个小的资源,合并成一个大的资源清单,减少API请求的次数。
问题2:API Server压力大,响应慢
因为我们的集群很大,API Server的压力也很大,尤其是在CI/CD集中部署的时候,大量的API请求同时过来,API Server响应变慢。
优化措施:
- 优化API Server的配置,增加CPU和内存,调整并发参数,提高处理能力。
- 用API Server的缓存,减少重复请求。
- 错峰部署,不要所有服务都在同一时间部署,分散API请求的压力。
- 用kubectl的--dry-run参数,提前验证资源清单,减少无效的API请求。
问题3:资源更新策略不合理
很多资源,其实不需要每次都更新,但Helm默认每次都会去更新,导致不必要的API请求。
优化措施:
- 用Helm的--no-hooks参数,跳过不需要的钩子。
- 用labels和annotations,标记不需要更新的资源,Helm会跳过。
- 合理设置资源的更新策略,比如用RollingUpdate而不是Recreate,减少更新时间。
经过优化,K8s API请求的时间,从60秒降到了10秒左右。
五、优化三:等待资源就绪优化,从80秒降到5秒
等待资源就绪,是最大的瓶颈,占了80秒。
问题1:等待时间设置不合理
Helm默认会等待资源就绪,等待时间很长,尤其是有状态服务,要等Pod完全启动、健康检查通过,才认为就绪。
优化措施:
- 用Helm的--wait参数,但配合--timeout,设置合理的超时时间,不要等太久。
- 对于不需要等待就绪的服务,不用--wait参数,提交了就返回,后台异步等待。
- 优化健康检查,把就绪探针(readinessProbe)的初始延迟和间隔调小,让服务更快通过检查。
问题2:镜像拉取慢
Pod启动慢,很多时候是因为镜像拉取慢,尤其是大的镜像,或者跨机房拉取的时候。
优化措施:
- 用镜像预热,提前把镜像拉到节点上,Pod启动的时候就不用等了。
- 用私有镜像仓库,离集群近一点,减少拉取时间。
- 优化镜像大小,用多阶段构建,去掉不需要的文件,减小镜像体积。
- 用镜像拉取策略IfNotPresent,避免每次都重新拉取。
问题3:InitContainer太多,启动慢
我们的很多服务,有好几个InitContainer,每个都要执行完,主容器才能启动,导致启动很慢。
优化措施:
- 合并InitContainer,能合并的合并,减少数量。
- 优化InitContainer的执行逻辑,减少执行时间。
- 把一些不需要在启动时做的初始化,移到主容器里异步做,或者做成定时任务。
问题4:资源不足,调度慢
有时候,Pod启动慢,是因为集群资源不足,调度不到合适的节点,一直在Pending状态。
优化措施:
- 合理设置资源请求和限制,不要申请过多的资源,提高调度效率。
- 提前扩容集群,保证有足够的资源。
- 用节点亲和性和污点容忍,让Pod调度到合适的节点,减少调度时间。
经过这一系列优化,等待资源就绪的时间,从80秒降到了5秒左右,大部分服务,提交之后很快就就绪了。
六、优化四:架构升级,从Helm 2到Helm 3
这次优化,我们还做了一个重要的决定,就是从Helm 2升级到Helm 3。
Helm 2有一个Tiller组件,部署在K8s集群里,作为服务端,Helm客户端通过gRPC和Tiller通信,Tiller再和K8s API Server交互。这个架构,有很多问题:
- Tiller是一个额外的组件,需要维护,增加了复杂度。
- Tiller和API Server之间的通信,有额外的开销,影响性能。
- Tiller的权限很大,有安全风险。
- Tiller本身也可能成为瓶颈,尤其是并发高的时候。
Helm 3去掉了Tiller,变成了纯客户端的架构,Helm客户端直接和K8s API Server交互,架构更简单,更轻量,性能也更好。
升级到Helm 3之后,我们明显感觉到,Helm的操作快了很多,因为少了Tiller这一层的开销,API请求更直接,延迟更低。而且,Helm 3的并发处理也更好,同时处理多个资源的时候,效率更高。
升级的过程,虽然花了一些时间,但还是值得的,性能提升很明显,而且安全性也更好了。
七、优化效果汇总
经过这一系列的优化,我们来汇总一下效果:
| 环节 | 优化前 | 优化后 | 降幅 |
|---|---|---|---|
| Chart渲染 | 30s | 5s | 83% |
| K8s API请求 | 60s | 10s | 83% |
| 等待资源就绪 | 80s | 5s | 94% |
| Tiller交互开销 | 40s | 0s(Helm 3去掉了Tiller) | 100% |
| 总计 | 210s(3分半) | 20s | 90% |
总的安装时间,从原来的3分半钟,降到了20秒左右,降了90%,远远超出了我们的预期目标(30秒以内)。
而且,稳定性也大大提高了,CI/CD流水线,因为Helm超时而失败的情况,几乎没有了,开发效率大大提升。同时,Helm操作占用的资源也减少了,对集群上其他服务的影响也小了。
八、性能优化的经验总结
这次Helm性能优化,积累了一些经验,分享给大家:
1. 先 profiling,再优化
还是那句话,性能优化一定要先做 profiling,找到瓶颈,再针对性地优化,不要凭感觉,不要上来就改。我们这次就是先花了一周时间 profiling,找到了真正的瓶颈,然后优化起来事半功倍。
2. 从最大的瓶颈开始
优化要抓主要矛盾,先优化占时间最多的环节。我们这次,等待资源就绪占了80秒,是最大的瓶颈,我们先优化这块,很快就看到了明显的效果。
3. 升级到新版本
很多时候,性能问题,是因为旧版本的架构或者实现有问题,升级到新版本,可能就解决了。我们这次从Helm 2升级到Helm 3,去掉了Tiller,性能提升很明显。所以,不要一直用旧版本,适时升级,可能会有意想不到的收获。
4. 简化是最好的优化
很多性能问题,都是因为太复杂了,模板太复杂,依赖太多,InitContainer太多。简化是最好的优化,去掉不必要的东西,减少复杂度,性能自然就上去了。
5. 建立性能监控
性能优化不是一次性的,优化完了,还要建立监控,持续关注性能,防止性能回退。我们在CI/CD流水线里,加了Helm操作的耗时统计,如果超过阈值,就会告警,及时发现性能问题。
6. 不要过度优化
优化要适度,不要为了追求极致的性能,把代码和配置搞得很复杂,难以维护。达到目标就好,剩下的精力,放在更有价值的事情上。
九、写在最后
这次Helm性能优化,把安装时间从3分半降到了20秒,效果还是很显著的,也让我对K8s和Helm的底层机制,有了更深的理解。
性能优化,是一个持续的过程,没有最好,只有更好。随着业务的发展,可能还会遇到新的性能问题,需要不断地优化。但只要掌握了正确的方法,先 profiling,找到瓶颈,再针对性地优化,就没有解决不了的问题。
希望这篇分享,能给用Helm的朋友一些参考。如果你也遇到了Helm性能问题,或者有其他的优化经验,欢迎在评论区交流讨论。
最后,性能优化之路,道阻且长,行则将至,与大家共勉。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录