學習目標
- 會用
cache:paths保存套件下載目錄。 - 會用
cache:key:files讓相依清單改變時自動換新快取。 - 會用
policy: pull讓只讀的 job 不上傳快取。 - 會用隱藏 job(
.開頭)與extends共用設定。
觀念
第一次:install 下載 PyYAML ──上傳快取──▶ [lv05-pip-<requirements 的雜湊>]
第二次:install 下載快取 ──▶ pip 顯示 Using cached ──▶ 更快
requirements.txt 改了 ──▶ 雜湊不同 ──▶ 新的快取 key,從頭下載| 設定 | 說明 |
|---|---|
cache:paths | 要快取的目錄,一定要在專案目錄裡,所以把 PIP_CACHE_DIR 指到 $CI_PROJECT_DIR/.cache/pip |
cache:key:files | 依檔案內容算 key,最多兩個檔案 |
cache:key:prefix | 加在 key 前面,避免和別的快取撞名 |
cache:policy | pull-push(預設)、pull(只讀)、push(只寫) |
extends | 把隱藏 job 的設定合併進來,hash 會深層合併 |
check-yaml 用 extends 拿到 .pip-cache 的 key、paths,自己只多加 policy: pull,兩者會合併。
動手做
- 連續跑兩次 pipeline,比較第二次
install的紀錄:會出現Using cached與Successfully extracted cache。 - 把
requirements.txt的版本改成別的,觀察快取 key 改變。 - 在「Build → Pipelines」右上角「Clear runner caches」可以清掉所有快取。
常見錯誤
- 快取專案目錄外的路徑(例如
~/.cache/pip):GitLab 不會保存。 - 用 cache 傳建置產物:cache 可能沒命中,下一個 job 會壞掉,要用 artifacts。
- key 用
$CI_COMMIT_REF_SLUG:每個分支一份,新分支第一次一定沒命中。依相依清單算 key 比較划算。
下一關
Lv.6 用 rules 決定 job 什麼時候要跑。
範例檔案
資料夾 lv05-cache/ 的全部檔案。.gitlab-ci.yml 可以直接複製到自己的專案根目錄執行。
lv05-cache/.gitlab-ci.yml
lv05-cache/.gitlab-ci.yml
# Lv.5 Cache:把下載過的套件留下來,下次 pipeline 直接用
# cache 是「盡力而為」:沒命中也要能跑,只是比較慢。要可靠傳遞的檔案請用 artifacts(Lv.3)。
stages:
- install
- check
default:
image: python:3.13-alpine
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" # cache 只能存專案目錄裡的檔案
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PIP_ROOT_USER_ACTION: "ignore"
# 以 . 開頭的是隱藏 job,不會執行,只拿來給別人引用
.pip-cache:
cache:
key:
files: [lv05-cache/requirements.txt] # 檔案內容一變,key 就變,自動換一份新快取
prefix: lv05-pip
paths:
- .cache/pip/
install:
stage: install
extends: .pip-cache
script:
- du -sh .cache/pip 2>/dev/null || echo "快取還是空的(第一次執行)"
- pip install -r lv05-cache/requirements.txt
- python3 -c "import yaml; print('PyYAML', yaml.__version__)"
check-yaml:
stage: check
extends: .pip-cache
cache:
policy: pull # 只讀不寫,job 結束時不用再上傳快取
script:
- pip install -r lv05-cache/requirements.txt # 命中快取時看得到 Using cached
- python3 lv05-cache/check_yaml.pylv05-cache/check_yaml.py
lv05-cache/check_yaml.py
"""Lv.5:解析每一關的 .gitlab-ci.yml,確認 YAML 語法正確,並列出 job 數量。"""
from pathlib import Path
import sys
import yaml
class GitLabLoader(yaml.SafeLoader):
"""GitLab 專用的 !reference 標籤(Lv.7 會用到),一般 YAML 解析器不認得,這裡當成普通陣列。"""
GitLabLoader.add_constructor('!reference', lambda loader, node: loader.construct_sequence(node))
ROOT = Path(__file__).resolve().parent.parent
RESERVED = {'stages', 'variables', 'default', 'include', 'workflow', 'image', 'services', 'cache', 'before_script', 'after_script'}
bad = 0
for path in sorted(ROOT.glob('lv*/.gitlab-ci.yml')):
try:
doc = yaml.load(path.read_text(encoding='utf-8'), Loader=GitLabLoader) or {} # nosemgrep: 自訂的 SafeLoader 子類別
except yaml.YAMLError as err:
bad += 1
print('語法錯誤', path.relative_to(ROOT), err)
continue
jobs = [k for k in doc if k not in RESERVED and not str(k).startswith('.')]
print(f'{path.parent.name:<22} {len(jobs)} 個 job')
sys.exit(1 if bad else 0)lv05-cache/requirements.txt
lv05-cache/requirements.txt
# Lv.5 只用一個套件示範快取;固定版本,才不會哪天自己變
PyYAML==6.0.3