CI/CD 十關

LV.7/10

Needs 與 DAG

用 needs 讓 job 準備好就開跑,用 matrix 一次測多個版本,用 extends 與 !reference 重用設定

進階約 25 分鐘needs、DAG、parallel:matrix、extends、!reference

學習目標

  • 用 needs 讓 job 只等它真正需要的 job,縮短整條 pipeline 的時間。
  • 知道 needs 也決定要下載哪些產物。
  • 會用 parallel:matrix 同時測多個版本。
  • 會用 extends 與 !reference 重用設定。

觀念:stage 與 DAG 的差別

只用 stage 時,test-api 要等 build-web(慢 30 秒)也做完才開始。加了 needs:

build-api(5s) ──▶ test-api ──┐
build-web(30s) ─────▶ test-web ──┴▶ package
lint(needs: [],一開始就跑)
compat: [3.12] [3.13](needs: [],一開始就跑)

pipeline 頁面切到「Job dependencies」可以看到這張圖。

寫法意思
needs: [build-api]只等 build-api,也只下載它的產物
needs: []什麼都不等,pipeline 一開始就跑
needs: [{ job: x, artifacts: false }]等 x 成功,但不下載產物
parallel: { matrix: [...] }每組變數各產生一個 job,名稱像 compat: [3.12]

重用設定

  • extends: .python:把隱藏 job .python 整段合併進來(hash 深層合併,陣列整個取代)。
  • !reference [.python, before_script]:只拿某一段,可以和自己的指令混在同一個陣列。

動手做

  1. 看 pipeline 的時間軸:test-api 在 build-web 還沒結束時就開始了。
  2. 把 test-api 的 needs 拿掉,比較整條 pipeline 多花多久。
  3. 在 matrix 加上 "3.11",多出一個 job。

常見錯誤

  • needs 指到同一個或之後階段的 job:一樣可以(同階段 needs 已支援),但不能形成循環。
  • needs 指到被 rules 排除的 job:pipeline 會建立失敗。可以加 optional: true。
  • 用了 needs 卻沒拿到產物:needs 只下載列出的 job 的產物,其他階段的不會自動下載。

下一關

Lv.8 在 job 旁邊開資料庫:services。

範例檔案

資料夾 lv07-needs-dag/ 的全部檔案。.gitlab-ci.yml 可以直接複製到自己的專案根目錄執行。

lv07-needs-dag/.gitlab-ci.yml
lv07-needs-dag/.gitlab-ci.yml
# Lv.7 Needs 與 DAG:job 不用等整個階段,只等它真正需要的 job
# 再加上 parallel:matrix(同一個 job 用不同參數跑多份)與 extends、!reference(重用設定)。

stages:
  - build
  - test
  - package

.python:
  image: python:3.13-alpine
  before_script:
    - python3 --version

build-api:
  stage: build
  extends: .python
  script:
    - sleep 5
    - mkdir -p out && echo "api 建置於 $(date -u +%T)" > out/api.txt
  artifacts:
    paths: [out/api.txt]
    expire_in: 1 day

build-web:
  stage: build
  extends: .python
  script:
    - sleep 30   # 故意比較慢
    - mkdir -p out && echo "web 建置於 $(date -u +%T)" > out/web.txt
  artifacts:
    paths: [out/web.txt]
    expire_in: 1 day

test-api:
  stage: test
  extends: .python
  # 只等 build-api:build-web 還在跑,這個 job 就已經開始了
  needs: [build-api]
  script:
    - cat out/api.txt
    - test ! -f out/web.txt && echo "needs 只下載 build-api 的產物"

test-web:
  stage: test
  extends: .python
  needs: [build-web]
  script:
    - cat out/web.txt

# needs: [] = 不等任何 job,pipeline 一開始就跑
lint:
  stage: test
  image: alpine:3.22
  needs: []
  script:
    - echo "風格檢查不需要建置產物,一開始就跑"

# 同一個 job 用兩個 Python 版本各跑一份
compat:
  stage: test
  needs: []
  image: python:${PY_VERSION}-alpine
  parallel:
    matrix:
      - PY_VERSION: ["3.12", "3.13"]
  script:
    - python3 -c "import sys; print('相容性測試', sys.version.split()[0])"

package:
  stage: package
  extends: .python
  needs:
    - job: test-api
      artifacts: false          # 只等它成功,不下載它的產物
    - job: test-web
      artifacts: false
    - job: build-api            # 要用產物的 job 才要列 artifacts: true(預設就是 true)
    - job: build-web
  before_script:
    - !reference [.python, before_script]
    - echo "!reference 可以只拿別的 job 的某一段設定"
  script:
    - cat out/api.txt out/web.txt
    - echo "打包完成"