空中都市
经费到手,工作室终于有了一台作为开发支持的服务器。
这是早在六月份的事情,其它组使用服务器已有两个月之久,但直到最近,我们组的 CI/CD 服务才被建立起来。
原因无它,太麻烦了。
早在代码托管于 GitHub 时,通过 GitHub Action 实现 CI/CD 便是一段回想就觉得心累的记忆。现在的业务代码从 GitHub 转存到自建的 GitLab 服务中,好在 GitHub 上的服务已经成功运行,抽空根据官方文档搭建 CI/CD 服务,难度应该不大。
然而,理想与现实,分立于银河的两端。
Docker
搭基础环境,就不是很顺利了。
原本构建采用的基础镜像是 adoptopenjdk/openjdk8:debian
。结果在运行 30.0.1 版本 Build Tools 中的 apksigner
对 APK 进行签名时,抛出了如下异常:
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.UnsupportedClassVersionError: com/android/apksigner/ApkSignerTool has been compiled by a more recent version of the Java Runtime (class file version 53.0), this version of the Java Runtime only recognizes class file versions up to 52.0
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
鉴于 Android 不少工具无法作业于高版本的 Java,甚至 Android Framework 都未能提供对 Java 8 完整的支持,apksigner
最低运行环境为 Java 9 着实有种见了鬼的感觉。
好在这并不是一个多难解决的问题,在基础镜像更改为 adoptopenjdk/openjdk11:debian
后,apksigner
成功运行。
Download
内网拉取镜像的速度明显要快于从 Google 下载 Android SDK 的速度,为了减少 CI/CD 的运行时间,Docker 镜像在构建时便提前预置了 SDK,但这么做也有弊端,新旧项目采用 SDK 版本不尽相同,所需构建镜像的数量也就不可避免地多了起来,加上包含 SDK 的镜像数据量都不小,每次上传镜像,运维老哥总怀疑我把服务器当网盘使。
至少,运行环境完成了。测试用项目开始了第一次 CI。
Downloading https://services.gradle.org/distributions/gradle-6.1.1-all.zip
...
傻了。
哪怕出门溜个弯回来,省略号仍旧只有那零星的几个点。试用 wget
下载,提示接近四个小时的下载时间让我们怀疑人生。基本只有在某云才能见到可与之一拼的速度。
因为 Gradle 的更新速度较快,集成到镜像里并不现实。
最终采用的处理方式是,在内网 Nexus 服务中创建 Gradle Distributions 的代理仓库,并手动上传组内项目所需几个 Gradle 版本的缓存,当代理仓库缓存未命中时才从原地址下载 Gradle。
之后 .gitlab-ci.yml
文件中手动将 gradle-wrapper.properties
中的链接替换为代理仓库的链接。
before_script:
...
- sed -i -e "s/https\\\:\/\/services\.gradle\.org\/distributions/<Studio Proxy Repository Path>/g" gradle/wrapper/gradle-wrapper.properties
...
Gradle
在经过了各种调整后,第一个 Pipeline 终于跑通。
耗时 1:37:43。
( _ _)ノ|
如果单纯作为自动化流程,对于服务器性能暂时存在盈余的我们来说并不是什么大问题,但作为持续集成服务,在这样的耗时下保证持续,不存在的。
查看日志发现缓存并没有生效。Gradle 的缓存虽然在 Job 中有成功上传,但 Job 在重新执行时缓存并没有下载。后来问运维老哥得知,因为 GitLab 与相关环境基于 Kubernetes,每次执行 Job 都会创建一组新的容器,因此在单纯在 .gitlab-ci.yml
内设置缓存无效。
最后采用的方式是通过 minIO 服务实现缓存。除此之外,项目依赖的下载源也通过 Nexus 进行代理,提高下载速度。
至此解决 Gradle 的构建速度问题,平均每个 Job 的执行时间下降到 2 分钟左右,Pipeline 总时间不超过 10 分钟。