用Github Actions 持續整合 Ruby on Rails
Kevin Luo
Posted on June 21, 2021
最近研究了一下用 Github Actions 做 Rails 的 CI ,分享一下經驗 : )
Github Actions 是 Github 的自動化工具。
Github Actions 只要在你的專案根目錄新增 .github/workflows
,再新增任意名稱的 Yaml 檔。
Git push 到 Github 後,即會根據你寫的 workflow 的內容自動執行了。
可以直接看下面分享的 YAML 檔,不過建議還是先看一下 Github Action 的文檔 Introduction to GitHub Actions - GitHub Docs ,會比較有概念喔(常常更新的也滿快的)
先分享 .github/workflows/ci.yml
,後面會再說明每一列是代表什麼意思
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.2
bundler-cache: true
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: '12.16.x'
- name: Restore cached ./node_modules
uses: actions/cache@v2
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-lock-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-lock-
- name: Yarn Install
run: yarn install
test:
needs: build
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8
ports: ['3306:3306']
env:
MYSQL_ROOT_PASSWORD: 'my-root-pw'
MYSQL_DATABASE: test_db
MYSQL_USER: username_you_like
MYSQL_PASSWORD: password_you_like
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
redis:
image: redis
ports: ['6379:6379']
options: --entrypoint redis-server
steps:
- uses: actions/checkout@v1
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.2
bundler-cache: true
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: '12.16.x'
- name: Restore cached ./node_modules
uses: actions/cache@v2
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-lock-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-lock-
- name: Yarn Install
run: yarn install
- name: Prepare Database
env:
RAILS_ENV: test
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
run: bundle exec rails db:prepare
- name: Run tests
env:
REDIS_URL: redis://localhost:6379/1
RAILS_ENV: test
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
run: |
bundle exec rspec --format RspecJunitFormatter --out ./reports/rspec.xml
- name: Publish Test Report
uses: mikepenz/action-junit-report@v2
with:
report_paths: './reports/rspec.xml'
說明版
# 此 workflow 的名稱,可任意取,到時會出現在 Github Actions 中
name: CI
# on 是控制何時要執行這個 workflow
# * push: 有 commit push 時
# * pull_request: PR 更新時
on: [push, pull_request]
# 一個 workflow 可以有很多jobs 組成,它們通常是同時(平行)執行的,
#不過可以設定先後順序,下面一點會提到。
#job 的名稱可任意取名,我這裡叫它 build
# `runs-on` 是 job 要在什麼 OS 上執行, github 上的 Github Actions 好像得用 ubuntu-latest,我也是照教學沿用了
jobs:
build:
runs-on: ubuntu-latest
# `step` 是 Github Actions 的最小單位
# steps 裡每個 step 可以執行一或多個指令或一個 Action
# 要執行指令,則使用 `run`
# `run: echo "hello world!"`
# 要執行 Action,用 `uses`
# `uses: actions的名字`
# 每個 step 的 name 並不是必填,但填了就像註解一樣,方便理解
steps:
# Action 其實就預先寫好的腳本
# GitHub Marketplace 上有一堆,可以想像是想把自動化的腳本當成在 App Store 上賣
# [GitHub Marketplace · Actions to improve your workflow · GitHub](https://github.com/marketplace?type=actions)
#`actions/checkout@v1` 就是一個常用的 Action,可以把你的 git repository 下載下來
- uses: actions/checkout@v1
# ruby/setup-ruby@v1 是用來安裝 Ruby 的 Action
# 下載的 ruby-version: 2.7.2 是要裝 2.7.2 的 Ruby
# bundler-cache: true 是要快取 bundler 下載的 gem,下面再說明快取
# 基本上這些 Action 都可以在 Github Marketplace 上查到用法
# [Setup Ruby, JRuby and TruffleRuby · Actions · GitHub Marketplace · GitHub](https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby)
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.2
bundler-cache: true
# 同上,安裝 Node
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: '12.16.x'
# `actions/cache@v2` 是專門來做快取的 Action
# 這裡寫 path: ./node_modules 就是要把 ./node_modules 下的檔案全 cache 起來
# 快取的結果會用 key 的設定去命名。
# 這個 action 會先依照 restore-keys 去找可以回復的快取
# 最後的 step 純粹就是跑 yarn install
# 如果光看這個 順序應該有人會疑惑:
# 快取的 action 是放在 yarn install 前,或更上方 ruby 的部分有 bundle install 前,
# 在安裝依賴前,先「讀取」快取好理解,
# 但重要的是「存」快取的時機怎麼沒看到?
# 執行一次就知道,這類快取的 Action 都有掛個動作在 PostJob 的 callback,
# 當這個 job 結束後會把該快取的 path 存起來
# 另外,每個倉庫有 5GB 的快取空間,正常來說應該是用不完啦。
# 所以 gems, npm 這種安裝的程式庫都把它們快取起來吧
- name: Restore cached ./node_modules
uses: actions/cache@v2
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-lock-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-lock-
- name: Yarn Install
run: yarn install
# 這是第二個 job ,我命名為 test,因為準備要跑 rspec 了
# 上面說 job 其實是平行執行的,`needs` 可以控制先後順序,needs: build 的意思就是 build 跑完才會跑 test
# 雖然以目前這個 workflow 來看,其實可以把steps 全寫在同一個 job ,沒什麼差別。
# 但寫成這樣,可以方便未來加入不同類的 test,比如 js 的 test, capybara 的 test,可以在 build 後,所有的 test 同時執行。
test:
needs: build
runs-on: ubuntu-latest
# `services` 可以用 docker image 架起需要用的服務,設定好 port mapping
# 我這裡架了 mysql 和 redis
# 那些 env 是去 DockerHub 上去找官方的 image 的說明才知道有什麼可以加的
# 順帶一提,CI 時 `config/database.yml` 可以兩種處理方式
# * 直接加入 git,內容再用 ENV 去替換
# * 新增一個 database.yml.ci ,在 workflow 裡加一個 step 去 `run: cp config/database.yml.ci config/database.yml`
services:
mysql:
image: mysql:8
ports: ['3306:3306']
env:
MYSQL_ROOT_PASSWORD: 'my-root-pw'
MYSQL_DATABASE: test_db
MYSQL_USER: test_user
MYSQL_PASSWORD: test_pw
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
redis:
image: redis
ports: ['6379:6379']
options: --entrypoint redis-server
# 下面這段到 yarn install 跟 build 是做完全一模一樣的事,執行時會直接取 build 快取的結果,所以很快就會跑完。
# 看起來很冗,不過如果未來可以用 YAML 的 Anchor 功能,這一塊可以直接寫成一個可複用的 block,
# 目前就直接重複吧!
steps:
- uses: actions/checkout@v1
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.2
bundler-cache: true
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: '12.16.x'
- name: Restore cached ./node_modules
uses: actions/cache@v2
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-lock-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-lock-
- name: Yarn Install
run: yarn install
# 如果要開始跑 rails 的指令時,要帶一些`環境變數`時,例如我這樣會用 RAILS_ENV 跟 RAILS_MASTER_KEY
# 是密碼類的字串可以存在Github倉庫 >Settings>Secrets 裡,workflow 裡可以直接 `${{ secrets.RAILS_MASTER_KEY }}` 去讀取
- name: Prepare Database
env:
RAILS_ENV: test
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
run: bundle exec rails db:prepare
# 這邊是跑 rspec
# 有裝 rspec_junit-formatter ,所以可以產生一個 XML 檔紀錄測試結果
- name: Run tests
env:
REDIS_URL: redis://localhost:6379/1
RAILS_ENV: test
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
run: bundle exec rspec --format RspecJunitFormatter --out ./reports/rspec.xml
- name: Publish Test Report
uses: mikepenz/action-junit-report@v2
with:
report_paths: './reports/rspec.xml'
最後用 mikepenz/action-junit-report@v2
去讀取 XML 的測試結果並秀出在頁面上
心得
我覺得自動化最煩的就是一堆小細節要顧,
比如說也可以完全不快取硬讓它跑,但就會很慢。
像 CircleCI 的 orb
就是把每個語言、框架常用的自動化指令整理起來變成一組指令集。
但 Github Actions 直接更進一步讓社群製作指令集,並準備好市集,方便搜尋跟分享,野心勃勃要幹掉其它 CI。
整體上還滿容易用,連 Rails 都能輕易導入(回想一下把 Rails 裝進 Docker 的夜晚),滿推薦試試看的。
而且Github(微軟)滿佛的,每個月給2000小時,個人練習或小型專案應該是用不完啦,可以去帳號 Billing & Plans 下查用量。
參考資料
Posted on June 21, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.