GitHub Action 是構建專案CI/CD和其他自動化流程上一種方便的新方法。尤其是在閒暇時自己做的side-project,不再需要自己架設主機來監控 Github repository 並擔任 CI/CD 流程的 runner。 此外,GitHub 上的 Marketplace 上有很多有用的 GitHub Action(例如setup Java、python、go 等、構建 container image、部署到公有雲……)可以讓我們輕鬆地達成很多功能。但如果我們只能透過真正觸發 GitHub 上的 Pull Request, Push 等來測試 GitHub Actions,則有以下三個缺點:
- 不方便測試 push to master 的事件要觸發的 action(可能要先寫成被 PR 觸發,測試完,最後一個 commit 再改回來)。
- 可能超過 GitHub 每月提供的免費額度。
- 會有許多 commit 紀錄(看起來很醜,或需要額外 squash)。
綜合下來,能解決上述問題外,在 local 執行 GitHub Action 還有以下兩個好處:
Run your GitHub Actions locally! Why would you want to do this? Two reasons:
- Fast Feedback - Rather than having to commit/push every time you want to test out the changes you are making to your .github/workflows/ files (or for any changes to embedded GitHub actions), you can use act to run the actions locally. The environment variables and filesystem are all configured to match what GitHub provides.
- Local Task Runner - I love make. However, I also hate repeating myself. With act, you can use the GitHub Actions defined in your .github/workflows/ to replace your Makefile!
所以在本文中,我們將介紹如何運用 nektos/act 在 local 運行 GitHub Actions。 此外,作業運行所要耗費時間與 GitHub Action 中的 cache 機制有高度的相關性。 所以我們也會展示如何在 local 測試中開啟 cache 機制,然後檢查 cache 是否有生效與其效果如何。
事前準備
-
安裝好的 Docker deamon
-
project 根目錄中包含的
.github/workflows/{my-Jobs}.yaml
檔案(這是我的 demo Job yaml 檔)。 -
安裝 nektos/act。
GitHub 專案中有詳細的指南。基本上可以照著做即可。 我用 Homebrew 安裝時只有遇到以下一個問題:- MacOS 版本錯誤
當我執行
brew install act
後出現/usr/local/Homebrew/Library/Homebrew/os/mac/version.rb:33:in ‘block in from_symbol’: unknown or unsupported macOS version::dunno (MacOSVersionError)occurred.
然後我透過指令
brew update-reset
更新 brew 後就解決這個問題了(可以在homebrew project上看到這個相同的問題。
使用 nektos/act 觸發 GitHub Action
首先,讓我們看一下我要 demo 的 GitHub Action Job 的yaml
檔:
name: Gradle try build
on:
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
build: # Job 的名字
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: 'gradle' # 啟動 setup-java 帶的 cache 機制
- name: Build with Gradle
run: ./gradlew build --no-daemon
執行 nektos/act 命令
在命令列中移動到 project 的根目錄後,我們可以使用 act -l
來顯示哪些事件可以觸發哪一個 GitHub Action Job。
這邊我便透過 act pull_request
發出了 pull_request 事件並觸發名為 build
的 Job (上面的 yaml 檔所定義的內容)。
如下圖所示:
如果我們是第一次運行 act
,我們要選擇預設的 action runner image 的類型 (會由 Docker 啟動的 container 所要使用的 image)。
也可以稍後在 ~/.actrc 中更改它。 然後 Job 就會開始跑了!
我們可以看到定義的 Job 已經完成(在 demo 中是使用 gradlew
來建立 project)。
nektos/act 指令在觸發事件或 Job 的參數很多,可用於不同的情況,這裡就不多加贅述(請參考 Example commands)。
在 nektos/act 中使用 cache 機制
在我沒有修改 project 程式碼的情況下,每次完成 Job 裡所有的步驟 (checkout code, setup JDK, gradle build) 需要將近3分鐘。
如果我們只是想測試一些 GitHub Action 的部分,就會花費很多不必要的時間,所以我們應該可以使用 cache 機制來加速。
這邊我們先簡單了解一下 GitHub Action 的 cache 機制是如何運作的。
GitHub Action 中的 cache 機制
簡單來說,我們可以使用 actions/cache,
如 GitHub Action project 中的 examples.md
一樣的設定(如下程式區塊),這段設定根據路徑和鍵提供檔案 cache 給 Job 中各步驟可以直接使用。
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
另一方面,GitHub Action 的官方文件
也推薦使用 actions/setup-* action 作為基礎的 cache 機制。
actions/setup-{language} 中就操作包括了 actions/cache 和需要的相關設置,在這些 setup action 中,
它會把所需的依賴 (dependencies) 和其他常用的可重復使用檔案做 cache 並在執行 Job 時進行讀取,以此來避免重複下載或建置。
在 local 測試中啟用 cache
節錄自 Github Action caching dependencies 的官方文件
When using self-hosted runners, caches from workflow runs are stored on GitHub-owned cloud storage. A customer-owned storage solution is only available with GitHub Enterprise Server.
正如官方文檔描述的那樣,即使是自己運行的 Github Action Runner 也會將 cache 放在 GitHub 擁有的雲端存儲空間上,
所以我們可以在下圖中看到 Log 紀錄中指出我們沒有提供 cache server 也就是當然的事。
因此,我們的目標很明顯的就是要在 local 建立一個 cache server,讓 cache 機制能夠進行儲存及查詢。
同時,我也發現了這個 issue,和我有一樣的問題。
於是把上面給出解決方法的 Github project fork 出來,
並加入了一些調整後再執行(給參數的方法不放在~./actrc
,而是在 act
執行時帶環境變數檔案一起執行)。
綜上所述,我們要做以下步驟(詳見 README.md):
- 把 cache server 的程式碼 clone 下來
git clone https://github.com/NoahHsu/github-act-cache-server.git
2. 設定環境變數
export ACT_CACHE_AUTH_KEY=foo
- 用 docker compose 運行 cache server
docker compose up --build
4. 新增一個具有以下設定的 .env 檔案
ACTIONS_CACHE_URL=http://localhost:8080/
ACTIONS_RUNTIME_URL=http://localhost:8080/
ACTIONS_RUNTIME_TOKEN=foo
- 執行
act
並帶入步驟4建立的 .env 檔案
act {event} --env-file {path_to.env}
Cache 機制在 local 測試的效果
經過以上步驟後,我們可以看到上圖中的 act 執行的 log 中,在 “Post Set up JDK” 的步驟時,
act 執行的 Job 會開始在 cache server 上儲存文件; 而在 cache server 的 log 中(下圖),我們也可以看到很多檔案已經被寫入。
最後,我們再次執行 act 來觸發 pull_request 事件。
可以看到從 cache server 中透過路徑(path)找到了很多可重複利用的檔案, log 也顯示了 cache-hit=true
。
最重要的是, gradle build 的時間僅花了 36 秒(無 cache 情況下約為 3 分鐘)。
結論
使用 nektos/act 和一個自製的 cache server,
便可以在 local 高效率地測試我們的所定義的 GitHub Action Workflow 與 Job。
總結步驟如下:
# Step by Step summary
# 1. 安裝 nektos/act
brew install act
# 2. 從GitHub下載自製的 cache server
git clone https://github.com/NoahHsu/github-act-cache-server.git
# 3. 運行 # 2. 下載的 cache server
cd {path_to_github-act-cache-server}
export ACT_CACHE_AUTH_KEY=foo
docker compose up --build
# 4. 新增環境變數檔以連接到 cache server
echo "ACTIONS_CACHE_URL=http://localhost:8080/
ACTIONS_RUNTIME_URL=http://localhost:8080/
ACTIONS_RUNTIME_TOKEN=foo" > {env_file_name}.env
# 5. 查看可以觸發什麼事件或 Job
cd {project_root}
act -l
# 6. 使用 act 觸發目標事件或 Job,並帶入 cache 相關的環境變數
cd {project_root}
act {event} --env-file {env_file_name.env}