跨專案 GitLab CI 配置複用與管理

Wi1dcard發表於2020-04-26

為了能夠使程式碼在不同專案複用,我司抽象、編寫了許多私有 libraries。它們的原始碼被統一存放在 GitLab,由 CI 確保程式碼風格一致,並執行單元測試和靜態檢查等。由於倉庫數量眾多,如何有效地組織和管理 CI 配置成了問題。經過長時間的探索和優化,我整理了一些經驗,希望對你有所幫助。

跨專案 GitLab CI 配置複用與管理

YAML 的小技巧

整整 70 多頁的 YAML 1.2 Specification 定義了 YAML 靈活、豐富的語法。這其中一項名為 Node Anchors 的特效能夠幫我們實現純 YAML 的配置複用。

例如:

deploy production:
  script:
    - echo "deploying to production"
  only: &foo_anchor
    variables:
      - $CI_COMMIT_TAG

deploy staging:
  script:
    - echo "deploying to staging"
  except: *foo_anchor

其中,onlyexcept 是結構相同但作用剛好相反的一對欄位。使用 & + anchor 識別符號即可設定錨點,在需要複用的欄位填寫 * + anchor 識別符號即可,它們的值將會保持一致。

YAML 1.1 中還定義了 Merge Key 特性,雖然在 YAML 1.2 中已經沒有明確規定,但在 GitLab CI 配置中仍然可以繼續使用。相比之下,我們更加推薦使用 extends 欄位,因此該此處不再贅述。

extends 欄位

在 GitLab CI 配置文件中描述了一個名為 extends 的欄位,可類比地理解為物件導向中的「繼承」。例如:

.foo_template_job:
  script:
    - echo "Deploying ${DEPLOY_ENV}"

deploy production:
  extends: .foo_template_job
  variables:
    DEPLOY_ENV: production

deploy staging:
  extends: .foo_template_job
  variables:
    DEPLOY_ENV: staging

其中,.foo_template_job 是一個 template job,顧名思義,它不會被當作真正的 job 出現在 pipelines 中,而是作為模板被其它 jobs 複用、擴充套件。同時我們分別定義了 deploy productiondeploy staging 兩個 job 以及不同的 variables,最終被 template job 內的 script 使用。

這樣我們就能達成各個環境可「繼承」同一份部署指令碼的目的,只需擴充套件並修改變數即可。

extends 還支援多個 template jobs 以及在普通 job 中覆蓋模板欄位等特性:

.yarn:
  image: node:latest
  before_script:
    - yarn install --production
    - export PATH="$PATH:$PWD/node_modules/.bin"

.cached:
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - dist/

build:
  extends:
    - .yarn
    - .cached
  image: node:12 # 覆蓋 .yarn 中定義的 image
  script:
    - echo "Building artifacts"

include 欄位與 Git Subtree

上文中我們介紹瞭如何在 .gitlab-ci.yml 中實現內部複用,這在單一專案中有效。為了能夠跨專案複用,我們使用了 include 欄位和 Git subtree 來實現。

通過 include 欄位我們可以將複雜的 CI 配置拆分成多個檔案,例如管理多個 stages:

# .gitlab-ci.yml
stages:
  - build
  - deploy
include:
  - local: .gitlab/build.yml # 倉庫內的配置檔案路徑
  - local: .gitlab/deploy.yml

# .gitlab/build.yml
build binaries:
  stage: build
  script:
    - echo "Building binaries"

# .gitlab/deploy.yml
deploy production:
  stage: deploy
  script:
    - echo "Deploying production"

我們把常用的 template jobs 集中到了一個名為 ci-templates 的倉庫中,例如 PHP 專案必備的 composer install

跨專案 GitLab CI 配置複用與管理

隨後在需要使用的專案中,將 ci-templates 倉庫的內容通過 subtree 引入:

git subtree add --prefix=.gitlab/ci-templates git@gitlab:devops/ci-templates.git master --squash

再使用 include 指令包含即可:

include:
  - local: .gitlab/ci-templates/templates/composer.yml

php-cs-fixer:
  extends: .composer # Template job 位於被 include 的檔案中
  script:
    - php-cs-fixer fix --dry-run --diff 1>&2

關於 subtree 的相關知識不再贅述,請參考相關文件。

不使用 Git Subtree 的 include

通過 Git subtree 引入的檔案不會隨著上游(ci-templates)倉庫的更新而同步,需要執行 git subtree pull 命令將變更 Pull 到專案倉庫中。這種方式的好處是 DevOps 修改 templates 時,不會造成潛在的、可能造成其它專案 CI 損壞的變更。同一 Git ref(例如 tags 或 commits)執行 pipelines 的結果能夠保持一致。

但隨著引用 templates 的倉庫越來越多,每次更新 ci-templates 時,我們不得不在各個專案倉庫內手動 pull。隨著 GitLab 11.7 釋出,此問題終於得到了解決,include 欄位支援直接引入來自其它倉庫的 templates 檔案了(參考 include:file)。以下是一個簡單的例子:

include:
  - project: devops/ci-templates # GitLab 專案全稱
    file: /templates/composer.yml # Template 檔案在倉庫內的路徑

phpstan:
  extends: .composer
  script:
    - phpstan analyse --ansi

結語

由於我們內部的 PHP 擴充套件包 CI/CD 流程極為相似且較為簡單,出現潛在「多米諾骨牌效應」的可能性較低;考慮到維護成本,我們最終選擇在擴充套件包倉庫使用 include:file 的方式引入 templates。CI/CD 複雜嚴謹的專案型別倉庫,則保留使用 Git subtree,以降低意外行為的可能性。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

Former WinForm and PHP engineer. Now prefer Golang and Rust, and mainly working on DevSecOps and Kubernetes.

相關文章