26、CI/CD集成:GitHub Actions、Jenkins、GitLab CI配置

说实话,Gradle Kotlin DSL 写起来再爽,如果每次构建都要手动敲命令,那跟骑自行车送外卖有什么区别?

我这些年带过的团队,几乎每个项目都会踩一遍 CI 配置的坑。有的同学把 Jenkins 配成了“手动触发器”,有的在 GitHub Actions 里把 Gradle 缓存搞丢了,构建一次要 20 分钟……

今天我们就来聊聊,怎么把 Gradle Kotlin DSL 的构建脚本,优雅地接入主流的 CI/CD 平台。

核心要点:CI/CD 配置的核心不是“跑通”,而是“跑得快 + 跑得稳”。Gradle 守护进程、构建缓存、并行任务,这三个东西缺一不可。

26.1 通用配置原则

不管用哪个 CI 平台,有几个原则是通用的。我个人习惯先把这些基础打好,再去看平台特有的配置。

  • 使用 Gradle Wrapper:永远不要直接装 Gradle 版本。用 gradlew 可以锁定版本,避免“在我机器上能跑”的尴尬。
  • 开启构建缓存--build-cache 参数能省掉大量重复编译。我在项目中遇到过,没开缓存时全量构建要 8 分钟,开了之后增量构建只要 40 秒。
  • 并行执行--parallel 参数让多模块项目并行编译,效果立竿见影。
  • 守护进程策略:CI 环境里建议用 --no-daemon,避免守护进程残留导致资源泄漏。

小技巧:可以在 gradle.properties 里统一设置这些参数,这样 CI 脚本里就不用重复写了。

org.gradle.parallel=true
org.gradle.caching=true
org.gradle.daemon=false

26.2 GitHub Actions 配置

GitHub Actions 是我用得最多的 CI 平台。它的 YAML 语法很直观,而且社区有现成的 Gradle Action,省了不少事。

先看一个完整的配置示例:

# .github/workflows/build.yml
name: Build with Gradle Kotlin DSL

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v3

      - name: Build with Gradle
        run: ./gradlew build --build-cache --parallel

      - name: Upload build reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-reports
          path: build/reports/tests/

这里有几个关键点:

  • gradle/actions/setup-gradle 这个 Action 会自动处理 Gradle 缓存,包括依赖缓存和构建缓存。我曾经手写过缓存配置,结果经常缓存失效,后来直接用官方 Action 就稳了。
  • if: always() 确保即使构建失败,测试报告也能上传。这个细节我踩过坑——有一次测试挂了,但报告没上传,排查问题全靠猜。
  • JDK 版本要和你的 build.gradle.kts 里配置的 Java 版本一致,否则会出现奇怪的编译错误。

注意:GitHub Actions 的免费额度有限,如果项目比较大,建议把 on.push 限制在主要分支,不要每个分支都触发全量构建。

26.3 Jenkins 配置

Jenkins 虽然老,但在企业里还是主流。它的配置方式比较灵活,可以用 Pipeline 脚本,也可以用 Freestyle 项目。

我个人推荐用 Pipeline,因为脚本可以版本化管理。下面是一个声明式 Pipeline 的例子:

// Jenkinsfile
pipeline {
    agent any

    tools {
        jdk 'JDK17'
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Build') {
            steps {
                sh './gradlew build --build-cache --parallel --no-daemon'
            }
        }

        stage('Test') {
            steps {
                sh './gradlew test --build-cache --parallel --no-daemon'
            }
            post {
                always {
                    junit 'build/test-results/**/*.xml'
                    publishHTML(target: [
                        reportName: 'Test Report',
                        reportDir: 'build/reports/tests/test',
                        reportFiles: 'index.html'
                    ])
                }
            }
        }

        stage('Publish') {
            when {
                branch 'main'
            }
            steps {
                sh './gradlew publish --build-cache --no-daemon'
            }
        }
    }

    post {
        failure {
            emailext(
                subject: "Build Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: "请查看构建日志:${env.BUILD_URL}",
                to: 'team@example.com'
            )
        }
    }
}

Jenkins 里有个容易忽略的点:Gradle 用户目录的持久化。默认情况下,每次构建都会重新下载依赖,非常慢。我建议在 Jenkins 全局配置里设置 GRADLE_USER_HOME 到一个持久化目录,比如 /var/jenkins_home/.gradle

避坑指南:我曾经在 Jenkins 里遇到过 Gradle 守护进程残留的问题。多个构建同时跑,守护进程互相抢占资源,导致构建失败。解决方案就是在 gradlew 命令后面加上 --no-daemon,一劳永逸。

26.4 GitLab CI 配置

GitLab CI 的配置文件和 GitHub Actions 类似,也是 YAML 格式。但它有一个独特的优势:内置的制品管理环境管理

来看一个典型的 .gitlab-ci.yml

# .gitlab-ci.yml
image: gradle:8.5-jdk17

variables:
  GRADLE_OPTS: "-Dorg.gradle.daemon=false"
  GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"

cache:
  key: "$CI_COMMIT_REF_SLUG"
  paths:
    - .gradle/wrapper
    - .gradle/caches

stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - ./gradlew assemble --build-cache --parallel
  artifacts:
    paths:
      - build/libs/
    expire_in: 1 week

test:
  stage: test
  script:
    - ./gradlew test --build-cache --parallel
  artifacts:
    reports:
      junit: build/test-results/**/TEST-*.xml

deploy:
  stage: deploy
  script:
    - ./gradlew publish --build-cache
  only:
    - main

GitLab CI 的缓存机制做得不错。上面配置里,我把 .gradle/caches.gradle/wrapper 都缓存了,这样每次构建不用重新下载依赖。不过要注意,缓存 key 用 $CI_COMMIT_REF_SLUG 可以按分支隔离,避免不同分支的缓存冲突。

还有一个细节:artifacts 的过期时间。我建议设置 expire_in,否则 GitLab 会一直保留所有构建产物,磁盘很快就满了。1 周是比较合理的值。

对比总结:

特性 GitHub Actions Jenkins GitLab CI
配置格式 YAML Groovy / 声明式 YAML
缓存管理 官方 Action 自动处理 需手动配置持久化目录 内置 cache 关键字
制品管理 upload-artifact Action Archive Artifacts 插件 内置 artifacts 关键字
并行构建 矩阵策略 Declarative Parallel parallel 关键字
适合场景 开源项目、小团队 企业级、复杂流水线 DevOps 一体化

26.5 核心流程示意图

下面这张图展示了 Gradle Kotlin DSL 项目在 CI/CD 中的完整流程。从代码提交到最终部署,每一步都离不开 Gradle 的构建能力。

Gradle Kotlin DSL CI/CD 集成流程 代码提交 Git Push / PR CI 触发 Actions/Jenkins/GitLab CI Gradle 构建 assemble / build 自动化测试 单元测试 / 集成测试 质量检查 lint / detekt / ktlint 制品生成 JAR / AAR / APK 部署 publish / deploy 通知 邮件 / Slack / 钉钉 构建缓存 依赖缓存 / 任务缓存 三个 CI 平台均可实现上述流程,区别在于配置语法和缓存策略

26.6 一些实战建议

最后分享几个我在实际项目中总结的经验:

  • 不要迷信“一键配置”:每个 CI 平台的 Gradle 缓存策略都不一样。GitHub Actions 的缓存是自动的,但 Jenkins 需要手动配。花点时间理解缓存机制,比抄别人的配置要靠谱得多。
  • 测试报告一定要保留:不管是 JUnit XML 还是 HTML 报告,都建议作为构建产物保存。我曾经因为没保留测试报告,线上出了问题还得重新跑一遍构建,浪费时间。
  • CI 脚本也要版本化:把 .github/workflows/Jenkinsfile.gitlab-ci.yml 都放在代码仓库里。这样改配置的时候可以走 PR 流程,避免直接在 CI 平台上改导致“配置漂移”。
  • 善用 Gradle 的 --scan 参数:如果构建速度突然变慢,加上 --scan 生成构建扫描报告,能直观看到每个任务的耗时。我在排查构建性能问题时,这个功能帮了大忙。

一句话总结:CI/CD 配置的核心不是“跑通”,而是“跑得快 + 跑得稳”。把 Gradle 的缓存、并行、守护进程这三个点吃透,不管换哪个 CI 平台,你都能快速上手。

公众号:蓝海资料掘金营,微信deep3321