文章总结了使用GitHub Actions自动化校验和部署Java项目的实践经验,提高效率和标准化CI/CD流程。
GitHub Action 简介
GitHub Actions是GitHub的CI/CD服务。它可以自动和规范项目的CI/CD流程,减少人工成本,降低人人因风险。
例如,我们可以在项目P有新的Pull Request时,对PR作格式校验、构建检测等来检测PR是否符合要求; 在发布Tag时部署到容器或发布到公共仓库等。
下面以Gradle方式构建的Java项目为例,说明如何使用GitHub Actions优化项目CI/CD流程。
优化CI流程
为了保证代码质量,项目的PR需要符合下述需求:
- 符合Java代码规范
- 通过Gradle Build
怎么用GitHub Actions实现?
- 定义一个workflow.yml文件
- 在里面加入需要的流程
下面是在PR时对代码进行Gradle Build的workflow.yml:
代码语言:javascript复制name: Java CI
# 触发事件,此处为PR
on: [pull_request]
# 一个workflow可包含多个jobs,每个jobs运行在不同的虚拟机,可根据需求设置并行或者串行运行
jobs:
# 该job的名称
build:
# 在ubuntu下运行
runs-on: ubuntu-latest
# 一个job可以有多个step(步骤),每个step按顺序执行,每个step可以运行一个action(动作)。
steps:
- uses: actions/checkout@v2
# 步骤名称
- name: Set up JDK 11
# 使用哪个action,可以在MarketPlace上查找需要的action
uses: actions/setup-java@v2
# 指定action的参数
with:
java-version: '11'
distribution: 'adopt'
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
- name: Build with Gradle
run: ./gradlew build
在项目的.github/workflows文件夹增加这个文件,就可以在PR时自动触发。对代码进行校验同理。
GitHub文档还是很友善的,可以在官方文档中找到示例、 workflow语法等。 结合文档搜索框和Stack overflow使用更佳。
小tips:为了测试workflow,需要多次触发PR、Push等行为,很不方便。可以使用act等工具在本地触发。
优化CD流程
Java项目要被其他开发者使用,需要部署到Maven中央仓库。此外,项目需要更新和发布tag,在release中说明更新的特性、变更记录等。
因此我们要做2件事情:发布release、部署到Maven仓库。
怎么结合Github Actions?
方案可以有两种:
- PR合进主干时触发发布release(包含创建tag)、部署到Maven仓库操作。
- 发布release时触发部署到Maven仓库操作。
方案一可以实现全流程自动部署。在评审人将代码合进主干时,需要按照一定格式填写合代码的信息,让action从里面拿去需要修改的版本号和release说明。
这种方案对评审人不友好,因为他需要了解此次变更细节,按照格式填写release信息;实现成本高,workflow也更复杂,需要从触发事件中获取数据、使用自动增加版本号、发布release等action,还要考虑异常防护措施。
方案二需要在Github上发布release,这个事件自动触发部署到Maven仓库操作。这种方式需要人工在页面操作,但是可以由熟悉变更的人发布release(自动创建tag),实现成本低,workflow更简单。
经过讨论,我们选择了方案二,实现发布release时触发部署到Maven仓库操作。GitHbu非常贴心,提供了示例, 但还需要进行以下补充:
- 对tag进行格式检测
- 使用sign插件对项目文件签名
第一点,因为项目版本号变更遵循语义化版本,所以要先校验tag是否符合规范。官方非常贴心地提供了校验的正则表达式, 因此我们可以用在正则来校验。
第二点,是发布到maven中央仓库的必需项,因此不能跳过。 人工发布时使用的命令是:
代码语言:javascript复制./gradlew publish -Psigning.keyId=${signing_key_id} -Psigning.password=${PASSWORD} -Psigning.secretKeyRingFile=${file_path}
在命令中传入参数和私钥文件进行签名,网上搜到的教程大部分也是这样做的。这种做法会存在安全隐患:
- workflow工作流会打印出敏感信息(尽管是加密后的)
- 虚拟机中会存在私钥文件
可以从这篇文章中看到,当程序被恶意入侵,有很多种方式可以窃取密钥。
因此,我们选择将所有密钥都放进环境变量中,来可能保证密钥的安全性。
在gradle文档中可以找到使用环境变量中的密钥签名方式。因此,我们把密钥放进GitHub Secrets中, 并在Github Actions中使用它。
另外,为了防止变更中忘记修改版本号,在PR时触发自动发布task lists,来提醒开发者更新版本。
实践
整体思路:
- PR时检测代码规范
- PR时校验Gradle Build是否通过
- PR时发布task lists
- Release tag时自动部署
1、2比较简单,搜索即可。此处给出3、4的workflow文件以作参考。
PR时发布task lists:
代码语言:javascript复制name: Comment on Pull Request
on:
pull_request:
jobs:
# pr时自动创建待完成任务
comment-on-pr:
runs-on: ubuntu-latest
steps:
- name: comment PR
uses: unsplash/comment-on-pr@v1.3.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
msg: "请完成以下事项:n - [ ] 更新build.gradle中的version "
效果如图:
Release tag时自动部署:
代码语言:javascript复制name: Gradle Publish
on:
release:
types: published
jobs:
# 校验tag是否满足语义化版本格式
check-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Regex Match
id: regex-match
run: |
result=$(printf ${{github.ref_name}} | perl -ne 'printf if /^v(?P<major>0|[1-9]d*).(?P<minor>0|[1-9]d*).(?P<patch>0|[1-9]d*)(?:-(?P<prerelease>(?:0|[1-9]d*|d*[a-zA-Z-][0-9a-zA-Z-]*)(?:.(?:0|[1-9]d*|d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?: (?P<buildmetadata>[0-9a-zA-Z-] (?:.[0-9a-zA-Z-] )*))?$/')
echo "::set-output name=result::$result"
- name: Check Tag
if: ${{ steps.regex-match.outputs.result != github.ref_name }}
uses: actions/github-script@v4
with:
script: core.setFailed('Invalid Tag:${{github.ref_name}}')
# Push Tag时自动发布新版本到Maven中央仓库
publish:
needs: [check-tag]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Java
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'adopt'
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
# 发布项目
- name: Publish
run: ./gradlew publish
env:
SONATYPE_NEXUS_USERNAME: ${{secrets.SONATYPE_NEXUS_USERNAME}}
SONATYPE_NEXUS_PASSWORD: ${{secrets.SONATYPE_NEXUS_USERNAME}}
ORG_GRADLE_PROJECT_signingKey: ${{secrets.SIGNING_KEY}}
ORG_GRADLE_PROJECT_signingPassword: ${{secrets.SIGNING_PASSWORD}}
小结
文章总结了使用GitHub Actions自动化校验和部署Java项目的实践经验,提高效率和标准化CI/CD流程。
这个过程中,一些看似“就该这么做”的决策其实经过很多讨论和思考。例如,GPG加密方式,我开始没考虑到安全问题,还是经过别人的提点才意识到。
我们总是会想,“这个东西好像没有难点,当时就只有这种方案。”
真是这样吗?
换做经验丰富的工程师,他会提出你想不到的质疑;换做经验尚浅的学生, 他会提出更不成熟的方案。而这些想不到的质疑、被默认否认的方案,就是经过思考得到的结果。
结果很重要,思考更重要。忘记思考,下次的结果只会更差,不会更好。这就是我很菜,但仍然坚持总结的原因。