《Java应用提速(速度与激情)》——三、docker构建提速

简介: 《Java应用提速(速度与激情)》——三、docker构建提速

1. 背景

 

自从阿里巴巴集团容器化后,把构建镜像做为发布构建的一步后开发人员经常被镜像构建速度困扰,每天要发布很多次的应用体感尤其不好。

 

为了让应用的镜像构建尽量的少,我们几年前已经按最佳实践推荐每个应用要把镜像拆分成两部分,一部分是基础镜像,包含低频修改的部分。另一部分是应用镜像,包含高频修改的部分,比如应用的代码构建产物。但是很多应用按我们提供的最佳实践修改后,高频修改部分的构建速度依然不尽如人意。

 

现在CICD平台和集团很多镜像构建场景用的还是pouch的前身。它只支持顺序构建,对多阶段并发构建的支持也不完整,我们设想的很多优化方法也因为它的技术过于老旧而无法实施。它中很多对低版本内核和富容器的支持也让一些镜像包含了一些现在运行时用不到的文件。

 

为了跟上主流技术的发展,我们计划把CICD平台的构建工具升级到moby-buildkit,docker的最新版本也计划把构建切换到moby-buildkit了,这个也是业界的趋势。同时在buildkit基础上我们作了一些增强。

 

2. 增强

 

1) 新语法SYNC

 

我们先用增量的思想,相对于COPY增加了一个新语法SYNC。

 

我们分析Java应用高频构建部分的镜像构建场景,高频情况下只会执行Dockerfile中的一个指令:

COPY appName.tgz /home/appName/target/appName.tgz

发现大多数情况下Java应用每次构建虽然会生成一个新的app.war目录,但是里面的大部分jar文件都是从maven等仓库下载的,它们的创建和修改时间虽然会变化但是内容的都是没有变化的。

 

对于一个1G大小的war,每次发布变化的文件平均也就三十多个,大小加起来2-3 M,但是由于这个appName.war目录是全新生成的,这个copy指令每次都需要全新执行,如果全部拷贝,对于稍微大点的应用这一层就占有1G大小的空间,镜像的copy push pull都需要处理很多重复的内容,消耗无谓的时间和空间。

 

如果我们能做到定制dockerfile中的copy指令,拷贝时像Linux上面的rsync一样只做增量copy的话,构建速度、上传速度、增量下载速度、镜像的占用的磁盘空间都能得到很好的优化。因为moby-buildkit的代码架构分层比较好,我们基于dockerfile前端定制了内部的SYNC指令。

 

我们扫描到SYNC语法时,会在前端生成原生的两个指令,一个是从基线镜像中link拷贝原来那个目录(COPY),另一个是把两个目录做比较(DIFF),把有变化的文件和删除的文件在新的一层上面生效,这样在基线没有变化的情况下,就做到了高频构建每次只拷贝上传下载几十个文件仅几兆内容的这一层。

 

而用户要修改的,只是将原来的COPY语法修改成SYNC就行了。

 

如将:

COPY appName.tgz /home/admin/appName/target/appName.tgz

修改为:

SYNC appName.dir /home/admin/appName/target/appName.war

我们再来看看SYNC的效果。集团最核心的热点应用A切换到moby-buildkit以及我们的sync指令后90分位镜像构建速度已经从140秒左右降低到80秒左右

 

image.png

2) none-gzip实现

 

为了让moby- buildkit能在CICD平台上面用起来,首先要把none-gzip支持起来。

 

这个需求在docker社区也有很多讨论https://github.com/moby/moby/issues/1266

 

内部环境网络速度不是问题,如果有gzip会导致90%的时间都花在压缩和解压缩上面,构建和下载时间会加倍,发布环境拉镜像的时候主机上一些CPU也会被gzip解压打满,影响同主机其它容器的运行。

 

虽然none-gzip后,CPU不会高,但会让上传下载等传输过程变慢,因为文件不压缩变大了。但相对于CPU资源来说,内网情况下带宽资源不是瓶颈。

 

只需要在上传镜像层时按配置跳过gzip逻辑去掉,并把镜像层的MediaType

 

application/vnd.docker.image.rootfs.diff.tar.gzip

 

改成

application/vnd.docker.image.rootfs.diff.tar

 

就可以在内网环境下充分提速了。

 

3) 单层内并发下载

 

在CICD过程中,即使是同一个应用的构建,也可能会被调度到不同的编译机上。即使构建调度有一定的亲和性。

 

为了让新构建机,或应用换构建机后能快速拉取到基础镜像,由于我们以前的最佳实践是要求用户把镜像分成两个(基础镜像与应用镜像)。换编译机后需要在新的编译机上面把基础镜像拉下来,而基础镜像一般单层就有超过1G大小的,多层并发拉取对于单层特别大的镜像已经没有效果。

 

所以我们在层内并发拉取的基础上,还增加了同一层镜像的并发拉取,让拉镜像的速度提升了4倍左右。默认每100M一个分片,用户也可以通过参数来设置分片大小。

 

当然实现这层内并发下载是有前提的,即镜像的存储需要支持分段下载。因为我们公司是用了阿里云的OSS来存储docker镜像,它有很好的分段下载或多线程下载的性能。

 

4) 无中心P2P下载

 

现在都是用containerd中的content store来存储镜像原始数据,也就是说每个节点本身就存储了一个镜像的所有原始数据manifest和layers。所以如果多个相邻的节点,都需要拉镜像的话,可以先看到中心目录服务器上查看邻居节点上面是否已经有这个镜像了

 

如果有的话就可以直接从邻居节点拉这个镜像而不需要走镜像仓库去取镜像layer,manifest数据还必须从仓库获取是为了防止镜像名对应的数据已经发生了变化了,只要取到manifest后其它的layer数据都可以从相邻的节点获取,每个节点可以只在每一层下载后的五分钟内(时间可配置)提供共享服务,这样大概率还能用到本地page cache,而不用真正读磁盘。

 

image.png 

 

中心OSS服务总共只能提供最多20G的带宽,从历史拉镜像数据能看到每个节点的下载速度都很难超过30M,但是我们现在每个节点都是50G网络,节点相互之间共享镜像层数据可以充分利用到节点本地的50G网络带宽,当然为了不影响其它服务,我们把镜像共享的带宽控制在200M以下。

 

5) 镜像ONBUILD支持

 

社区的moby-buidkit已经支持了新的schema2格式的镜像的ONBUILD了,但是集团内部还有很多应用FROM的基础镜像是schema1格式的基础镜像,这些基础镜像中很多都很巧妙的用了一些ONBUILD指令来减少FROM它的Dockerfile中的公共构建指令。

 

如果不能解析schema1格式的镜像,这部分应用的构建虽然会成功,但是其实很多应该执行的指令并没有执行,对于这个能力缺失,我们在内部补上的同时也把这些修改回馈给了社区(https://github.com/moby/buildkit/pull/3053)。

相关文章
|
4天前
|
前端开发 Java 测试技术
Java一分钟之Spring MVC:构建Web应用
【5月更文挑战第15天】Spring MVC是Spring框架的Web应用模块,基于MVC模式实现业务、数据和UI解耦。常见问题包括:配置DispatcherServlet、Controller映射错误、视图解析未设置、Model数据传递遗漏、异常处理未配置、依赖注入缺失和忽视单元测试。解决这些问题可提升代码质量和应用性能。注意配置`web.xml`、`@RequestMapping`、`ViewResolver`、`Model`、`@ExceptionHandler`、`@Autowired`,并编写测试用例。
51 3
|
1天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【5月更文挑战第18天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,应用场景,以及如何优化线程池的性能。通过实例分析,我们将看到线程池如何提高系统性能,减少资源消耗,并提高系统的响应速度。
11 5
|
1天前
|
算法 搜索推荐 Java
滚雪球学Java(33):数组算法大揭秘:应用案例实战分享
【5月更文挑战第8天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
21 8
滚雪球学Java(33):数组算法大揭秘:应用案例实战分享
|
2天前
|
Kubernetes 持续交付 Docker
构建高效微服务架构:Docker与Kubernetes的完美搭档
【5月更文挑战第17天】在当今云计算和微服务架构的大潮中,Docker容器化技术和Kubernetes容器编排系统成为了后端开发领域的热门技术栈。本文将探讨如何通过Docker和Kubernetes的结合使用来构建一个高效、可扩展且易于管理的微服务环境。我们将从基础概念出发,深入到实际操作层面,最后讨论这种组合对持续集成和持续部署(CI/CD)流程的影响,旨在为开发者和企业提供一种可靠的后端服务解决方案。
|
2天前
|
自然语言处理 Java API
Java 8的Stream API和Optional类:概念与实战应用
【5月更文挑战第17天】Java 8引入了许多重要的新特性,其中Stream API和Optional类是最引人注目的两个。这些特性不仅简化了集合操作,还提供了更好的方式来处理可能为空的情况,从而提高了代码的健壮性和可读性。
24 7
|
3天前
|
Java 数据库连接 Spring
K8S+Docker理论与实践深度集成java面试jvm原理
K8S+Docker理论与实践深度集成java面试jvm原理
|
3天前
|
缓存 IDE Java
Java一分钟之-Gradle:构建自动化工具
【5月更文挑战第16天】本文介绍了Gradle,一个基于Groovy的灵活构建工具,强调其优于Maven的灵活性和性能。文中通过示例展示了基本的`build.gradle`文件结构,并讨论了常见问题:版本冲突、缓存问题和构建速度慢,提供了相应的解决策略。此外,还提醒开发者注意插件ID、语法错误和源代码目录等易错点。掌握这些知识能提升开发效率,使构建过程更顺畅。
21 2
|
3天前
|
安全 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【5月更文挑战第16天】 在移动开发领域,性能一直是开发者关注的焦点。随着Kotlin语言的普及,其与Java在Android应用中的性能表现成为热门话题。本文将深入分析Kotlin和Java在Android平台上的性能差异,并通过实际测试数据来揭示二者在编译速度、应用启动时间以及运行效率方面的表现。我们的目标是为开发者提供一个参考依据,以便在选择合适的编程语言时做出更加明智的决策。
|
7月前
|
存储 分布式计算 Hadoop
基于docker的Hadoop环境搭建与应用实践(脚本部署)
本文介绍了Hadoop环境的搭建与应用实践。对Hadoop的概念和原理进行了简要说明,包括HDFS分布式文件系统和MapReduce计算模型等,主要通过脚本的方式进行快捷部署,在部署完成后对HDFS和mapreduce进行了测试,确保其功能正常。
|
JavaScript Linux 应用服务中间件
Docker部署Node应用简单实践
本文将从零至一,介绍如何在云服务器上通过 Docker 容器运行一个简单的Node应用。
1365 0