第三章:Jobs 与 Stages¶
Stages 详解¶
默认 Stages¶
自定义 Stages¶
Stage 执行顺序¶
┌─────────────────────────────────────────────────────────────────┐
│ Pipeline │
├─────────────────┬─────────────────┬─────────────────────────────┤
│ Stage: lint │ Stage: build │ Stage: test │
│ │ │ │
│ Job: eslint │ Job: build:app │ Job: test:unit │
│ Job: prettier │ Job: build:lib │ Job: test:integration │
│ (并行) │ (并行) │ Job: test:e2e │
│ │ │ (并行) │
└─────────────────┴─────────────────┴─────────────────────────────┘
↓ ↓ ↓
lint 完成 build 完成 test 完成
阶段依赖¶
stages:
- build
- test
- deploy
build_app:
stage: build
script: npm run build
test_unit:
stage: test
script: npm test
needs: [build_app] # 不等待整个 build 阶段完成
deploy:
stage: deploy
script: echo "Deploy"
needs: [test_unit]
Jobs 详解¶
Job 名称规则¶
# 有效名称
build_app:
script: echo "Build"
test-unit:
script: echo "Test"
deploy_to_production:
script: echo "Deploy"
# 无效名称(保留字)
image:
script: echo "Invalid" # 错误:image 是关键字
services:
script: echo "Invalid" # 错误:services 是关键字
隐藏 Job¶
# 以 . 开头的 Job 不会执行
.hidden_job:
script:
- echo "This job is hidden"
# 用于继承模板
.base_job:
image: node:18
before_script:
- npm install
build:
extends: .base_job
script:
- npm run build
Job 模板继承¶
# 定义模板
.build_template:
image: node:18
before_script:
- npm ci
script:
- npm run build
# 继承模板
build:app:
extends: .build_template
script:
- npm run build:app
build:lib:
extends: .build_template
script:
- npm run build:lib
# 多重继承
build:all:
extends:
- .build_template
- .deploy_template
Job 执行控制¶
# 条件执行
deploy:production:
stage: deploy
script:
- echo "Deploy to production"
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
- when: never
# 失败后执行
cleanup:
stage: .post
script:
- echo "Cleanup after failure"
when: on_failure
# 总是执行
notify:
stage: .post
script:
- echo "Send notification"
when: always
并行执行¶
并行 Job 实例¶
test:
parallel: 5
script:
- echo "Instance $CI_NODE_INDEX of $CI_NODE_TOTAL"
- npm run test -- --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL
矩阵构建¶
test:
parallel:
matrix:
- NODE_VERSION: [16, 18, 20]
OS: [linux, windows]
image: node:${NODE_VERSION}
script:
- echo "Testing Node $NODE_VERSION on $OS"
tags:
- ${OS}
多平台构建¶
build:
parallel:
matrix:
- ARCH: [amd64, arm64]
PLATFORM: [linux, darwin]
script:
- GOOS=$PLATFORM GOARCH=$ARCH go build -o app-$PLATFORM-$ARCH
artifacts:
paths:
- app-$PLATFORM-$ARCH
Job 依赖管理¶
needs 关键字¶
# 无依赖,立即开始
lint:
stage: lint
script: npm run lint
needs: []
# 依赖特定 Job
test:unit:
stage: test
script: npm test
needs: [build]
# 依赖多个 Job
deploy:
stage: deploy
script: echo "Deploy"
needs:
- build
- test:unit
- test:integration
# 可选依赖
deploy:
stage: deploy
script: echo "Deploy"
needs:
- job: test:unit
optional: true
DAG 执行¶
# 传统阶段执行
# lint → build → test → deploy(串行)
# DAG 执行(needs)
# lint(立即开始)
# build(立即开始)
# test:unit(等待 build)
# test:integration(等待 build)
# deploy(等待所有 test)
stages:
- lint
- build
- test
- deploy
lint:
stage: lint
script: npm run lint
needs: []
build:
stage: build
script: npm run build
needs: []
test:unit:
stage: test
script: npm run test:unit
needs: [build]
test:integration:
stage: test
script: npm run test:integration
needs: [build]
deploy:
stage: deploy
script: echo "Deploy"
needs:
- lint
- test:unit
- test:integration
制品传递¶
生成制品¶
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
- build/
exclude:
- dist/*.log
expire_in: 1 week
when: always
使用制品¶
test:
stage: test
script:
- ls dist/
dependencies:
- build # 只下载 build 的制品
deploy:
stage: deploy
script:
- rsync -avz dist/ server:/app/
needs:
- job: build
artifacts: true
缓存策略¶
# 全局缓存
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
# Job 级别缓存
build:
cache:
key: ${CI_COMMIT_REF_SLUG}-build
paths:
- node_modules/
- .npm/
policy: pull-push # 默认
# 只拉取缓存
test:
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull
# 只推送缓存
setup:
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: push
实战示例¶
前端项目¶
stages:
- install
- lint
- build
- test
- deploy
variables:
NODE_VERSION: "18"
.node_template:
image: node:${NODE_VERSION}
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
install:
extends: .node_template
stage: install
script:
- npm ci
cache:
policy: push
artifacts:
paths:
- node_modules/
expire_in: 1 hour
lint:
extends: .node_template
stage: lint
script:
- npm run lint
needs: [install]
build:
extends: .node_template
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
needs: [install]
test:unit:
extends: .node_template
stage: test
script:
- npm run test:unit -- --coverage
coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
needs: [install]
test:e2e:
extends: .node_template
stage: test
image: cypress/included:latest
script:
- npm run test:e2e
artifacts:
paths:
- cypress/screenshots/
- cypress/videos/
when: on_failure
needs:
- job: install
- job: build
deploy:staging:
stage: deploy
script:
- echo "Deploy to staging"
environment:
name: staging
only:
- develop
when: manual
needs: [build]
deploy:production:
stage: deploy
script:
- echo "Deploy to production"
environment:
name: production
only:
- main
when: manual
needs:
- build
- test:unit
- test:e2e
后端项目¶
stages:
- lint
- build
- test
- security
- deploy
variables:
PYTHON_VERSION: "3.11"
.lint_template:
image: python:${PYTHON_VERSION}
before_script:
- pip install -r requirements-dev.txt
flake8:
extends: .lint_template
stage: lint
script:
- flake8 app/
allow_failure: true
mypy:
extends: .lint_template
stage: lint
script:
- mypy app/
build:
image: python:${PYTHON_VERSION}
stage: build
script:
- pip install build
- python -m build
artifacts:
paths:
- dist/
expire_in: 1 week
test:
image: python:${PYTHON_VERSION}
stage: test
services:
- postgres:15
- redis:7
variables:
POSTGRES_DB: test_db
POSTGRES_USER: test
POSTGRES_PASSWORD: test
DATABASE_URL: postgres://test:test@postgres:5432/test_db
script:
- pip install -r requirements-dev.txt
- pytest --cov=app tests/ --junitxml=report.xml
coverage: '/TOTAL.*\s+(\d+%)$/'
artifacts:
reports:
junit: report.xml
security:
image: python:${PYTHON_VERSION}
stage: security
script:
- pip install safety bandit
- safety check
- bandit -r app/
allow_failure: true
deploy:
stage: deploy
image: docker:latest
services:
- docker:dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
when: manual
小结¶
本章学习了:
- ✅ Stages 详解
- ✅ Jobs 详解
- ✅ 并行执行
- ✅ Job 依赖管理
- ✅ 制品传递
- ✅ 缓存策略
- ✅ 实战示例
下一章¶
第四章:变量与缓存 - 学习变量和缓存的高级用法。