作为一个开发者,让某样东西运转起来是你能拥有的最伟大的感觉之一。特别是当你花了几个小时、几天或几个月的时间来实现它。最后一英里可能是最痛苦和最有价值的经历之一,所有这些都被包裹在同一天或两天里。
我最近在为JHipster做的Spring Native项目中经历了这种情况。如果我回顾一下,花了一年的时间来实现它的愿望、研究和坚持不懈的努力。当我们最终实现了它的工作和自动化时,你可以想象我的兴奋之情。
在几天的兴奋之后,我认为使用GitHub Actions为每个操作系统(Linux、macOS和Windows)创建本地构建很容易。我错了。
如果你想跟着学习如何配置GitHub Actions来创建本地二进制文件,你需要一些先决条件。
先决条件:
目录
- 配置一个JHipster应用程序以使用GitHub动作
- 用JHipster的CI/CD自动等待
- GraalVM构建的环境影响
- 使用GitHub Actions的GraalVM的最佳实践
- 用GraalVM和GitHub Actions运行夜间测试
- 在GitHub上发布时如何构建和上传本地二进制文件
- 在本地运行你发布的二进制文件
- 了解更多关于CI、JHipster和GraalVM的信息
你可以通过下面的推特阅读完整的对话(也就是这篇文章的浓缩版),或者继续阅读,了解我在GitHub Actions和GraalVM上经历的考验和磨难。希望我的经验能够为你节省几个小时的时间。
上周,当我得到这个工作时,我非常兴奋,我决定探索让@graalvm的构建与GitHub Actions一起工作。
这种体验是缓慢而痛苦的。
当前状态:它只在macOS上工作。
Windows说命令太长,Linux说内存用完了。https://t.co/sfLWQCVzaY
- Matt Raible (@mraible)2022年2月23日
配置一个JHipster应用来使用GitHub Actions
我将为你加快进度,只告诉你如何为现有的JHipster应用配置GitHub Actions。在这种情况下,它是一个全栈的React + Spring Boot应用。
使用npm安装其依赖项。
git clone -b jhipster-native-1.1.2 \
https://github.com/oktadev/auth0-full-stack-java-example.git flickr2
cd flickr2
npm install
然后,在GitHub上创建一个新的 repo。例如,jhipster-flickr2 。
接下来,把这个例子项目推送给它。
USERNAME=<your-github-username>
git remote rm origin
git remote add origin git@github.com:${USERNAME}/jhipster-flickr2.git
git branch -M main
git push -u origin main
使用JHipster的CI/CD自动等待
用GraalVM构建原生镜像让我回到了21世纪初用Ant和XDoclet构建Java应用的日子。我们会开始构建,然后去做其他事情,因为要花几分钟时间来构建工件。
原生二进制文件的另一个经常被忽视的问题是,你必须为每个操作系统构建一个。这不像Java,你可以构建一个JAR(Java ARchive)并在任何地方运行它。
接下来,使用JHipster的CI/CD子生成器生成持续集成(CI)工作流程。
npx jhipster ci-cd
这个命令会提示你选择一个CI/CD管道。选择GitHub Actions。

当提示你这个快速例子的任务/集成(Sonar, Docker, Snyk, Heroku, 和Cypress Dashboard)时,不要选择任何任务。子生成器将创建三个文件。
-
.github/workflows/main.yml -
.github/workflows/native.yml -
.github/workflows/native-artifact.yml
我将在下面的章节中向你展示每个文件的内容。让我们首先检查一下main.yml 。
main.yml 工作流文件将配置 GitHub Actions 来检查你的项目,配置 Node 16,配置 Java 11,运行你项目的后端/前端单元测试,以及运行其端到端的测试。不仅如此,它还会在Docker中启动你依赖的容器(例如Keycloak)。你可以看到大部分的功能都隐藏在npm run 命令后面。
name: Application CI
on: [push, pull_request]
jobs:
pipeline:
name: flickr2 pipeline
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.pull_request.title, '[skip ci]') && !contains(github.event.pull_request.title, '[ci skip]')"
timeout-minutes: 40
env:
NODE_VERSION: 16.14.0
SPRING_OUTPUT_ANSI_ENABLED: DETECT
SPRING_JPA_SHOW_SQL: false
JHI_DISABLE_WEBPACK_LOGS: true
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16.14.0
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 11
- name: Install node.js packages
run: npm install
- name: Run backend test
run: |
chmod +x mvnw
npm run ci:backend:test
- name: Run frontend test
run: npm run ci:frontend:test
- name: Package application
run: npm run java:jar:prod
- name: 'E2E: Package'
run: npm run ci:e2e:package
- name: 'E2E: Prepare'
run: npm run ci:e2e:prepare
- name: 'E2E: Run'
run: npm run ci:e2e:run
env:
CYPRESS_ENABLE_RECORD: false
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
- name: 'E2E: Teardown'
run: npm run ci:e2e:teardown
要在你的新仓库上测试这个功能,你需要创建一个分支和拉动请求(PR),其中包括你的改动。
git checkout -b actions
git add .
git commit -m "Add GitHub Actions"
git push origin actions
你应该在你的终端看到一个链接来创建一个拉动请求(PR)。
remote: Create a pull request for 'actions' on GitHub by visiting:
remote: https://github.com/mraible/jhipster-flickr2/pull/new/actions
如果你观察PR中的测试运行,你会很高兴,直到它进入E2E:打包阶段。它可能会失败,出现以下错误。
Found orphan containers (docker_keycloak_1) for this project. If you removed or renamed
this service in your compose file, you can run this command with the --remove-orphans flag
to clean it up.
我在JHipster中报告了这个问题,因为--remove-orphans 最近被从docker:db:down 和docker:keycloak:down 命令中删除。该解释为我提供了足够的信息来关闭该问题。把它们重新添加到package.json ,作为一种变通方法。
"scripts": {
...
"docker:db:down": "... --remove-orphans",
...
"docker:keycloak:down": "... --remove-orphans",
...
}
提交并推送这些修改。现在一切都应该通过了。

把这个PR合并到main 分支。
GraalVM构建的环境影响
这给我们带来了一个有趣的两难问题。如果你要创建本地镜像作为你的应用程序的发布工件,你是不是应该使用setup-graalvm动作来配置GraalVM和你的Java SDK?
我不这么认为。如果你这样做,每次你创建一个PR并提交给它,它就会运行一个本地构建。这个项目的GraalVM构建对我来说在本地需要3-4分钟。而使用GitHub Actions则需要30多分钟。
对我来说,这似乎和加密货币一样对环境不利。如果你使用的是私有回购,这也会让你后悔几年前买了加密货币。对于私人仓库,你只能获得2000分钟的免费GitHub操作。之后的任何分钟,你都会被收费。
是的,我知道加密货币的话题是有争议的。不过我确实喜欢拿它开玩笑。在我看来,每次提交的原生构建和挖掘比特币似乎是相似的。话说回来,单纯的上网对环境来说也是很糟糕的。
使用GitHub Actions的GraalVM的最佳实践
当我第一次开始研究GraalVM的GitHub动作时,JHipster Native蓝图修改了package.json 中的命令,以便总是构建本地镜像,并在运行端到端测试时使用它们。这意味着,当你第一次尝试添加GitHub Actions支持时,构建会失败,因为没有找到GRAALVM_HOME 。为了解决这个问题,你可以从actions/setup-java 切换到graalvm/setup-graalvm ,但这不是很环保的做法。
此后,我们修改了蓝图,生成了两个新的工作流程,反映了(在我看来)GitHub Actions和GraalVM的最佳实践。
-
native.yml:在午夜使用GraalVM运行夜间测试 -
native-artifact.yml: 构建并上传用于发布的本地二进制文件
main.yml ,保持与JHipster默认的一样,在JVM上持续测试。
使用GraalVM和GitHub Actions运行夜间测试
native.yml 工作流文件执行类似于main.yml 的操作,但使用GraalVM。它每天在UTC午夜按计划运行。目前不支持添加时区。
name: Native CI
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
permissions:
contents: read
jobs:
pipeline:
name: flickr2 native pipeline
runs-on: ${{ matrix.os }}
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.pull_request.title, '[skip ci]') && !contains(github.event.pull_request.title, '[ci skip]')"
timeout-minutes: 90
env:
SPRING_OUTPUT_ANSI_ENABLED: DETECT
SPRING_JPA_SHOW_SQL: false
JHI_DISABLE_WEBPACK_LOGS: true
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-2019]
graalvm-version: ['22.0.0.2']
java-version: ['11']
include:
- os: ubuntu-latest
executable-suffix: ''
native-build-args: --verbose -J-Xmx10g
- os: macos-latest
executable-suffix: ''
native-build-args: --verbose -J-Xmx13g
- os: windows-2019
executable-suffix: '.exe'
# e2e is disabled due to unstable docker step
e2e: false
native-build-args: --verbose -J-Xmx10g
steps:
# OS customizations that allow the builds to succeed on Linux and Windows.
# Using hash for better security due to third party actions.
- name: Set up swap space
if: runner.os == 'Linux'
# v1.0 (49819abfb41bd9b44fb781159c033dba90353a7c)
uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c
with:
swap-size-gb: 10
- name:
Configure pagefile
# v1.2 (7e234852c937eea04d6ee627c599fb24a5bfffee)
uses: al-cheb/configure-pagefile-action@7e234852c937eea04d6ee627c599fb24a5bfffee
if: runner.os == 'Windows'
with:
minimum-size: 10GB
maximum-size: 12GB
- name: Set up pagefile
if: runner.os == 'Windows'
run: |
(Get-CimInstance Win32_PageFileUsage).AllocatedBaseSize
shell: pwsh
- name: 'SETUP: docker'
run: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask docker
sudo /Applications/Docker.app/Contents/MacOS/Docker --unattended --install-privileged-components
open -a /Applications/Docker.app --args --unattended --accept-license
#echo "We are waiting for Docker to be up and running. It can take over 2 minutes..."
#while ! /Applications/Docker.app/Contents/Resources/bin/docker info &>/dev/null; do sleep 1; done
if: runner.os == 'macOS'
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16.14.0
- name: Set up GraalVM (Java ${{ matrix.java-version }})
uses: graalvm/setup-graalvm@v1
with:
version: '${{ matrix.graalvm-version }}'
java-version: '${{ matrix.java-version }}'
components: 'native-image'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Cache Maven dependencies
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven
- name: Cache npm dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Install node.js packages
run: npm install
- name: 'E2E: Package'
run: npm run native-package -- -B -ntp "-Dnative-build-args=${{ matrix.native-build-args }}"
- name: 'E2E: Prepare'
if: matrix.e2e != false
run: npm run ci:e2e:prepare
- name: 'E2E: Run'
if: matrix.e2e != false
run: npm run native-e2e
如果你将native.yml 与main.yml 进行比较,你会发现它并不运行单元测试(因为Spring Native还不支持Mockito)。它确实构建了一个本地可执行文件,并针对它运行端到端的测试。
如果你等到UTC午夜之后,你可以在Repo的Actions标签中查看这个工作流的结果。它也有一个workflow_dispatch 事件触发器,所以你可以从你的浏览器手动触发它。

| 由于Docker镜像无法启动,目前Windows系统的端到端测试被禁用。 |
在GitHub上发布时如何构建和上传本地二进制文件
native-artifact.yml 工作流文件在发布时为 macOS、Linux 和 Windows 创建了二进制文件。这个工作流将Linux和Windows配置成有足够的内存,将工件上传到action job,并将本地二进制文件上传到GitHub上的release。它只在你创建一个版本(又称标签)时执行。
name: Generate Executables
on:
workflow_dispatch:
release:
types: [published]
permissions:
contents: write
jobs:
build:
name: Generate executable - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 90
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-2019]
graalvm-version: ['22.0.0.2']
java-version: ['11']
include:
- os: ubuntu-latest
executable-suffix: ''
native-build-args: --verbose -J-Xmx10g
- os: macos-latest
executable-suffix: ''
native-build-args: --verbose -J-Xmx13g
- os: windows-2019
executable-suffix: '.exe'
native-build-args: --verbose -J-Xmx10g
steps:
# OS customizations that allow the builds to succeed on Linux and Windows.
# Using hash for better security due to third party actions.
- name: Set up swap space
if: runner.os == 'Linux'
# v1.0 (49819abfb41bd9b44fb781159c033dba90353a7c)
uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c
with:
swap-size-gb: 10
- name:
Configure pagefile
# v1.2 (7e234852c937eea04d6ee627c599fb24a5bfffee)
uses: al-cheb/configure-pagefile-action@7e234852c937eea04d6ee627c599fb24a5bfffee
if: runner.os == 'Windows'
with:
minimum-size: 10GB
maximum-size: 12GB
- name: Set up pagefile
if: runner.os == 'Windows'
run: |
(Get-CimInstance Win32_PageFileUsage).AllocatedBaseSize
shell: pwsh
- uses: actions/checkout@v3
- id: executable
run: echo "::set-output name=name::flickr2-${{ runner.os }}-${{ github.event.release.tag_name || 'snapshot' }}-x86_64"
- uses: actions/setup-node@v3
with:
node-version: 16.14.0
- name: Set up GraalVM (Java ${{ matrix.java-version }})
uses: graalvm/setup-graalvm@v1
with:
version: '${{ matrix.graalvm-version }}'
java-version: '${{ matrix.java-version }}'
components: 'native-image'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Cache Maven dependencies
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven
- name: Cache npm dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- run: npm install
- name: Build ${{ steps.executable.outputs.name }} native image
run: npm run native-package -- -B -ntp "-Dnative-image-name=${{ steps.executable.outputs.name }}" "-Dnative-build-args=${{ matrix.native-build-args }}"
- name: Archive binary
uses: actions/upload-artifact@v3
with:
name: ${{ steps.executable.outputs.name }}
path: target/${{ steps.executable.outputs.name }}${{ matrix.executable-suffix }}
- name: Upload release
if: github.event.release.tag_name
run: gh release upload ${{ github.event.release.tag_name }} target/${{ steps.executable.outputs.name }}${{ matrix.executable-suffix }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Linux和Windows的问题和解决方案
当我第一次开始尝试用GraalVM构建本地二进制文件时,我很快在Linux和Windows上遇到了问题。
-
Linux上:
java.lang.OutOfMemoryError: GC overhead limit exceeded -
Windows:
The command line is too long.
我很高兴地说,通过在native-maven-plugin 插件的构建参数中指定-J-Xmx10g ,我能够修复Linux上的OOM错误。JHipster Native现在默认配置了这个设置,并在构建本地工件时针对你的操作系统进行优化。
<native-image-name>native-executable</native-image-name>
<native-build-args>--verbose -J-Xmx10g</native-build-args>
...
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
..
<configuration>
<imageName>${native-image-name}</imageName>
<buildArgs>
<buildArg>--no-fallback ${native-build-args}</buildArg>
</buildArgs>
</configuration>
</plugin>
Windows的问题已经被本地构建工具0.9.10所修复。
我们使用windows-2019 ,而不是windows-latest ,因为我在尝试时已经没有磁盘空间了。
在GitHub上发布一个版本
在你喜欢的浏览器中打开你的仓库页面,点击创建一个新版本。创建一个新的v0.0.1 标签,将该版本命名为v0.0.1 ,并在描述中添加一些有趣的文字。点击发布版本。

点击Actions标签,看你的发布是否执行。我想提醒你,这需要一些时间。我的第一次成功发布只用了不到一个小时。
-
macOS:31m 30s
-
Linux系统:33m 50s
-
窗口系统:59m 45s
我想你会对结果感到满意的。🤠

如果你的构建失败了,你可以通过运行git push origin :v0.0.1 删除发布的标签。然后你的发布版就会变成一个草案,你可以使用GitHub的用户界面轻松地再次创建发布版。 |
在本地运行你发布的二进制文件
如果你从GitHub下载这些二进制文件并试图在本地运行它们,你会得到失败的结果,因为它们无法连接到Keycloak或PostgreSQL的实例。
为了启动一个PostgreSQL数据库让应用程序与之对话,你可以从你的flickr2 目录中运行以下命令。
docker-compose -f src/main/docker/postgresql.yml up -d
你可以为Keycloak做同样的事情。
docker-compose -f src/main/docker/keycloak.yml up -d
Okta CLI使它变得如此简单,你可以在几分钟内完成。
在你开始之前,你需要一个免费的Okta开发者账户。安装Okta CLI并运行okta register ,以注册一个新账户。如果您已经有一个账户,运行okta login 。然后,运行okta apps create jhipster 。选择默认的应用程序名称,或根据您的需要进行更改。 接受为您提供的默认重定向URI值。
Okta CLI是做什么的?
Okta CLI简化了对JHipster应用程序的配置,并为您做了几件事。
- 创建一个具有正确重定向URI的OIDC应用程序。
- 登录:
http://localhost:8080/login/oauth2/code/oidc和http://localhost:8761/login/oauth2/code/oidc - 注销:
http://localhost:8080和http://localhost:8761
- 登录:
- 创建JHipster期望的
ROLE_ADMIN和ROLE_USER组 - 将你的当前用户添加到
ROLE_ADMIN和ROLE_USER组中。 - 在你的默认授权服务器中创建一个
groups,并将用户的组添加到其中。
注意:http://localhost:8761* 重定向URI是为JHipster注册处准备的,在用JHipster创建微服务时经常使用。Okta CLI默认会添加这些。
完成后,你会看到如下的输出。
Okta application configuration has been written to: /path/to/app/.okta.env
运行cat .okta.env (或Windows上的type .okta.env ),查看你的应用程序的发行者和凭证。它将看起来像这样(除了占位符的值将被填充)。
export SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI="/oauth2/default"
export SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID="{clientId}"
export SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET="{clientSecret}"
注意:您也可以使用Okta管理控制台来创建您的应用程序。请参阅在Okta上创建JHipster应用程序以了解更多信息。
然后,通过设置环境变量(.okta.env )和执行二进制文件来启动该应用程序。比如说。
source .okta.env
chmod +x flickr2-macOS-v0.0.1-x86_64
./flickr2-macOS-v0.0.1-x86_64
# verify in System Preferences > Security & Privacy and run again
如果你是在Windows上,你可能需要安装Windows Subsystem for Linux来使这些命令成功。或者,你可以在文件中把.okta.env 重命名为okta.bat ,把export 改为set 。然后,从你的终端运行它来设置变量。 |
一切都应该按预期工作。很狡猾,你不觉得吗?
你可以在auth0-full-stack-java-example的发布页面上看到该工件的发布版本。
了解更多关于CI、JHipster和GraalVM的信息
我希望你喜欢这个关于如何配置GitHub Actions以创建Java应用程序的GeralVM二进制文件的旅游。原生二进制文件的启动速度比JARs快一些,但它们的构建时间要长很多。这就是为什么把这些过程外包给持续集成服务器是个好主意。