為了能夠使程式碼在不同專案複用,我司抽象、編寫了許多私有 libraries。它們的原始碼被統一存放在 GitLab,由 CI 確保程式碼風格一致,並執行單元測試和靜態檢查等。由於倉庫數量眾多,如何有效地組織和管理 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
其中,only
和 except
是結構相同但作用剛好相反的一對欄位。使用 &
+ 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 production
和 deploy 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
:
隨後在需要使用的專案中,將 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 協議》,轉載必須註明作者和本文連結