利用 glci 在 Local 環境運行 GitLab CI

在先前的文章,艦長曾說到 GitLab CI 的一項缺點,即是我們無法輕易的在 Local 環境運行 GitLab CI,往往需要反覆修改 .gitlab-ci.yml、送出 Commit 並等待 CI Pipeline 顯示出執行結果。這冗長又反覆的過程實在是為規劃 CI Pipeline 及撰寫 CI Job 帶來困擾,也大幅拖慢針對 CI Pipeline 本身的測試與驗證速度。

然而,這項長久困擾 GitLab 使用者的困擾,似乎浮現了一線曙光!為我們帶來希望的即是名為 glci 的工具。

(本文同步發表於 Medium。)

TL;DR

glci 目前還在 v0.4.3,尚未進入 v1.0,是個 2021/2/17 才開始沒多久時間的年輕工具。因此尚無法在 Local 完整的還原 GitLab CI 的所有功能與行為。

但我必須說,能在 Local 執行 .gitlab-ci.yml 中的 CI Pipeline 與 CI Job 實在是很有吸引力的一件事,希望 glci 能持續發展下去,有興趣的朋友除了試用之外,不妨也考慮考慮發揮一下開源精神為此工具貢獻己力吧!

試用記錄

以下僅記錄這次試用過程的各項操作。

根據 README 的簡短註記,glci 可透過 yarn 安裝,所以先在 mac 安裝 yarn

# macOS 使用 Homebrew 安裝是最省力的
brew install yarn

接著用 yarn 安裝 glci

yarn global add glci

由於 glci 會運用 Docker container 幫你在 Local 環境運行 CI Pipeline,因此也需要準備好 Docker。但艦長的 mac 早就安裝過 Docker Desktop for Mac,因此直接略過安裝 Docker 的動作。

(艦長的 mac 已經安裝過 Docker。)

接下來找個有 .gitlab-ci.yml 的 Project 來當白老鼠。先隨手弄一個空白資料夾,裡面放一個 .gitlab-ci.yml 試試。

stages:
- build
- test
- deploy

build:
  stage: build
  script:
    - echo "build"

首次執行 glci,結果失敗, glci 不讓我們在沒有任何 Git Repository 及 Commit 記錄的資料夾中執行。

山不轉路轉,即然它需要 Repository 及 Commit 記錄,我們就弄一個給它。

git init
git add .
git commit -m "try glci"

再次執行 glci,這次就順利得到結果。

由上圖可以看出,glci 確實能正確的解讀 .gitlab-ci.yml 中的 stages: 及 CI Job,雖然我們的測試 Project 只有一個 Job,但我們有設置三個 Stage,而這些 Stage 在上圖中都有正確呈現。

另外,從上圖也可以發現 glci 必定是以 Docker 來作為執行 CI Job 的 Executor。因為我在 .gitlab-ci.yml 並未指定要使用哪個 image:,但它還是有一個 Using existing image "ruby:2.5" 的動作。

在執行期間同步用 docker ps 查看,確實有一個 ruby:2.5 的 Container 運行中,所以我的 CI Job 就是在這個 Container 內被執行的。

下一步找個複雜一點的 Project 測試,我們就拿 gitlab.com 提供的 template 建立一個 Project 來測試。

(就拿 Ruby on Rails 當白老鼠。)

將 Project clone 至 Local 後,執行指令 glci

結果失策,Template Project 居然沒有內含 .gitlab-ci.yml。只好自己新增一個,但一樣直接使用 GitLab 提供的 .gitlab-ci.yml Template - Ruby。

(一樣是 Ruby 白老鼠。)

.gitlab-ci.yml 檔案內容精簡之後如下。

# 完整檔案可參閱 https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml

image: "ruby:2.5"

services:
  - mysql:latest
  - redis:latest
  - postgres:latest

variables:
  POSTGRES_DB: database_name

cache:
  paths:
    - vendor/ruby

before_script:
  - ruby -v
  - bundle install -j $(nproc) --path vendor

rubocop:
  script:
    - rubocop

rspec:
  script:
    - rspec spec

rails:
  variables:
    DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
  script:
    - rails db:migrate
    - rails db:seed
    - rails test

deploy:
  type: deploy
  environment: production
  script:
    - gem install dpl
    - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY

這次的 .gitlab-ci.yml 就複雜了一些,並且有用到 GitLab CI 的各種常用功能,像是 services:variables:before_script:type:environment:

在執行 glci 前,讓我們先看一下這個 .gitlab-ci.yml,在 GitLab 的 Web UI 會呈現什麽面貌與結果。

如上圖,會分成 Test 與 Deploy 兩個 Stage,然後 Stage - Test 的三個 CI Job 都亮紅燈;Stage - Deploy 則是因為 Test 亮紅燈,所以是 skipped 的狀態。

接著讓 glci 上場,結果繪製出來的 CI Pipeline 與 GitLab Web UI 的不同,CI Job - deploy 一樣被放在 Stage - Test 之下。

接著開始執行第一個 CI Job - rubocop,而且確實有先執行 before_script:

before_script:
  - ruby -v
  - bundle install -j $(nproc) --path vendor

在漫長等待 bundle install 跑完後,在執行 rubocop 時失敗了。

rubocop:
  script:
    - rubocop

但這項失敗並非 glci 的錯,而是提供給 CI Job 的環境 ruby:2.5 缺少了 rubocop 這個指令。

既然如此,不如自製一個 Docker image。於是艦長就利用 ruby:2.5 為基底,再 build 了一個名為 demo/ruby:2.5 已經安裝好 rubocop 的 Docker image。

#修改 .gitlab-ci.yml 的 image: "ruby:2.5"

image: "demo/ruby:2.5"

但依然錯誤,因為 glci 就是會有一個 docker pull 的動作,並不會因為 Local 已經有同名的 Docker image 就直接拿來使用。由於 Docker Hub 上沒有名為 demo/ruby:2.5 的 Docker image 因此執行失敗。

【補充說明】如果你的 Docker image 不是公開存放在 Docker Hub,glci 也有提供方法讓你可以指定從 Private registry 取得 Image。

(READMD 有提到如何改用 Private registry。)

既然如此,只好換個方式改為在 before_script: 補上安裝 rubocop 的動作。

before_script:
  - ruby -v
  - bundle install -j $(nproc) --path vendor
  - gem install rubocop
  - gem install rspec #rspec 也順便裝一下

這次 CI Job - rubocop 就有順利執行。

然後 rubocop 很盡責完成工作,於是 Job failed。

讓我們試試另一個 CI Job,這次利用 glci 的另一個功能 --only-jobs,加上此 Option 之後,即可指定 glci 只需執行哪些 CI Job。

glci --only-jobs=rspec,rails

如下圖,被指定的 CI Job 會顯示為亮白色,未被選中的 Job 則顯示為暗灰色,可以明顯知道哪些 Job 會被執行。

順利執行 CI Job - rspec

由於後續的 CI Job - rails 依然是 failed,因此決定要換個方式繼續試用 glci。

再次採取手動撰寫 .gitlab-ci.yml 做幾個小實驗。

  • glci 是否有模擬 GitLab CI 的 Variables 呢?

答案是有的,試著在 CI Job 中執行 env,確實有列出多項 GitLab CI Variables。

(上圖為其中一段 Variables。)

Variables 的覆寫功能,也能正常使用。

variables:
  TOP_VAR: var1

try-var:
  script:
    - echo $TOP_VAR

try-var2:
  variables:
    TOP_VAR: var2

  script:
    - echo $TOP_VAR

($TOP_VAR 確實被覆寫為 var2。)

  • glci 是否支援 default: 呢?
default:
  image: ruby:2.6

try-image:
  script:
    - echo 2.6

try-image2:
  image: ruby:2.5
  script:
    - echo 2.5

結果 default: 似乎被當成是一個 CI Job 了。

但實際上 try-image 與 try-image2 依然有正確使用不同的 Image。

看來案情並沒有這麼單純,將 .gitlab-ci.yml 改寫如下再試一次。

default:
  image: ruby:2.6

stages:
  - try

try-image:
  stage: try
  script:
    - echo 2.6

try-image2:
  stage: try
  image: ruby:2.5
  script:
    - echo 2.5

這次 default: 就沒有被當成 CI Job 了。看來 Stages: 不能隨意省略,glci 會無法正確判讀。

  • 是否能支援 include: 呢?

大致測試了幾種,似乎只有 local: 可以正常使用。

stages:
  - try

include:
  - local: 'try_include.yml'
  • 是否支援 extends: 呢?
.try:
  stage: try
  only:
    - triggers
  script:
   - echo ok

stages:
  - try

try-extend:
  extends: .try

答案也是可以,多層 extend 也能正常運作。

測試到此告一個段落。

結語

glci 目前仍是一個發展中的工具,雖然尚無法 100% 的支援 GitLab CI 所有的功能,但能在 Local 環境執行 .gitlab-ci.yml 內的 CI Job 依然是十分有吸引力的一件事。

希望 glci 能持續開發下去增加更多功能,搞不好在未來某一天 glci 會直接被官方收編為官方工具也不一定?總之對 glci 有興趣的朋友,不妨也安裝試用看看吧。

參考資料

轉貼本文時禁止修改,禁止商業使用,並且必須註明來自「艦長,你有事嗎?」原創作者 Cheng Wei Chen,及附上原文連結。

工商服務

更多文章