持续交付(CD)的核心原则之一是,测试需要在“类似生产”的环境中运行,以保证发布版本的稳定性。类似相同的环境可以减少“在我的机器上可以运行”之类的问题,并能轻松地在本地复现问题。我们可以避免由于平台差异引起的 bug,比如证书丢失或过期。
Docker 是实现这种对等性的最佳工具之一。它还允许我们将构建与 Jenkins 基础设施分离。比如,使用基于 Docker 的构建,我们无需担心预先安装在 Jenkins 代理上的 Java 版本。无论代理上安装了什么,构建都将使用我们指定的版本运行。尝试新的 JVM 版本如同变更 Docker 标签一样容易,可以让我们走地更快。
在本文中,我们将要学习如何使用Docker配置Jenkins,来确保构建环境与生产环境是相同的。我们还将学习如何通过在构建过程中共享缓存数据,来保持最小的构建时间。
Java 构建的简单概述
首先,从预先安装了 Docker 的 Jenkins 代理开始构建。该代理通过签出源代码编译的方式获取,并在预先安装了 JDK 的 Docker 容器中运行测试。然后,富 JAR 将在生产环境中运行在一个 Docker 镜像,该镜像具有和构建时相同的 Java 版本。
不幸的是,我们需要考虑很多细节才能解决这个问题。下面我们将深入地讨论这些细节。
使用 Docker 构建的详细指南
首先,需要预先安装一个带有 Docker 二进制文件的 Jenkins 代理。因为这是唯一的要求,所以这个代理可以在整个公司内共享,没有必要为每个团队都构建自己的代理。在本例中,klarna-jenkins-agent 是附带了 Docker 的通用代理。它还有一个 ID 为 11000 的 Jenkins 用户,该用户属于 ID 为 12000 的 Docker 组。
如果你想先看下结果,请阅读本文末尾部分,以了解此处讨论的完整 Jenkinsfile。
我们在 pipeline 的根目录处配置代理,以确保所有的 Docker 镜像都能在签出源代码的同一台机器上运行。
我们使用 amazonlinux-java-jdk:11 镜像来运行构建。考虑到安全性,镜像安装了 JDK 并使用非 root 帐户来运行。该步骤应该在同一代理(1,3)上运行,以便可以访问项目的源代码。
或者,如果集成测试需要 Docker,则可以安装 docker.sock(2)。
如果项目需要,也可以在自定义的 Docker 镜像中运行构建。该过程,仅需指定要使用的 Dockerfile 即可,而无需指定镜像:
镜像将会缓存在代理上,只有当 Dockerfile 的内容发生变更时,镜像才会重新生成。
提高性能
现在我们已经运行了一个基本的构建,我们可以通过在运行之间共享 Gradle 缓存来提高性能。
我们为 Gradle 缓存准备了一个“home”。在将它挂载之前,必须先创建一个.gradle_home 文件夹(4),才能确保 Jenkins 用户可以对其进行写入。否则,该文件夹将由 Docker 创建,只有 root 帐户才有权访问它。
最后一步,我们修改 docker args 部分,以挂载新创建的文件夹。完整的 args 参数如下所示:
Jenkinsfile 的最终版本如下:
或者,我们也可以使用名为 volumes 的 Docker 作为 Gradle 缓存,但是 volume 文件夹由容器内的 root 拥有,如果我们使用非 root 用户运行构建,则会使设置变得更加复杂。
好处
除了可以获得相同的构建和生产环境之外,还有其他一些好处:
不需要为每个团队或项目维护一个定制的 Jenkins 代理。只需一个 Docker 通用代理就足够了。
不受 Jenkins 代理上的 Java 版本限制。
如果项目在 pipeline 的特定步骤中需要一些自定义的软件,我们可以为它创建一个 Dockerfile,而无需创建自定义的代理,并受那些预先安装在代理上的软件的约束。如果以后可能会切换到其他构建工具上,我们也有很多选择。
升级到下一个 JDK 版本很容易。只需将 Jenkinsfile 中的标签 11 替换为 12 即可。我们甚至可以将构建参数化来验证兼容性。
结论
我们需要付出一些努力才能确保构建环境和生产环境是相同的,并且花费的这些时间是值得的,因为它增强了我们对发布的信心。
使用 Docker 能够使用我们将 Jenkins 代理从构建环境中解耦出来,从而使我们能够精确地控制构建所需的 Java、Node 或 Python 版本,而不需要在代理上预先安装什么。通过使用公司中的标准化组件,可以减少长期维护。
原文链接:
评论