CI/CD 完全指南:GitHub Actions 与 GitLab CI 对比实战

还在手动 SSH 连服务器拉代码?还在因为本地环境和线上不一致焦头烂额?是时候把这些重复劳动交给机器了。本文不整虚的,直接带你用 GitHub Actions 和 GitLab CI 把自动化流水线搭起来。


一、 别背定义了,看本质

1.1 到底什么是 CI/CD?

很多教程上来就甩一堆缩写,其实没那么复杂。

CI (Continuous Integration) 持续集成
说白了就是**“别把烂代码合进去”**。你提交代码,机器自动跑测试、查格式。挂了?那就修好再合。

CD (Continuous Delivery/Deployment) 持续交付/部署
说白了就是**“别让人工操作毁了发布”**。测试通过了?机器自动打包、自动扔到服务器上。

缩写 全称 核心目标
CI Continuous Integration 频繁合并,自动验证
CD Continuous Delivery/Deployment 自动发布,随时可用

CI(持续集成)

持续集成的核心思想是:开发者频繁地(每天多次)将代码集成到共享的主分支,每次集成都通过自动化构建和测试来验证,从而尽早发现问题。

1
开发者提交代码 → 自动触发构建 → 运行测试 → 代码检查 → 反馈结果

CD(持续交付/部署)

  • 持续交付(Continuous Delivery):代码随时处于可发布状态,但需要手动触发部署
  • 持续部署(Continuous Deployment):代码通过所有测试后,自动部署到生产环境
1
CI 通过 → 构建产物 → 自动/手动部署 → 生产环境

1.2 为什么你需要它

想象一下两种场景:

  • 传统模式:写完代码 -> 本地跑测试(可能忘了) -> 手动打包 -> FTP/SCP 传服务器 -> SSH 登录 -> 重启服务 -> 发现配置错了 -> 回滚 -> 崩溃。
  • CI/CD 模式:写完代码 -> git push -> 去倒杯咖啡 -> 回来发现新功能已经上线了。

这就叫解放生产力

1.3 主流 CI/CD 平台

平台 特点 配置文件位置
GitHub Actions GitHub 原生,生态丰富 .github/workflows/*.yml
GitLab CI/CD GitLab 内置,功能全面 .gitlab-ci.yml
Jenkins 自托管,高度可定制 Jenkinsfile
CircleCI 云原生,配置简洁 .circleci/config.yml

本文重点讲解 GitHub ActionsGitLab CI/CD


二、 为什么我劝你一定要上 CI/CD

2.1 专治各种“不服”

❌ 那些年我们踩过的坑

  1. “在我电脑上能跑啊!”
    • 这是最经典的扯皮。CI 环境是干净且统一的,它说不行,那就是不行,别怪环境。
  2. “哎呀,忘了执行数据库迁移了”
    • 手动部署总会漏掉一两步。人是不可靠的,脚本才是。
  3. “谁把测试环境搞挂了?”
    • 没有 CI 挡在前面,垃圾代码很容易混进主分支,导致大家一起停工修 bug。

✅ 躺平开发的快乐

  • 快速反馈:提交代码几分钟后,钉钉/Slack 就弹消息告诉你挂了,趁老板没发现赶紧修好。
  • 睡个好觉:自动化测试覆盖了核心逻辑,周五发布也不慌。
  • 一键回滚:部署脚本里写好了回滚逻辑,出问题点一下按钮就回到上一版。

2.2 别心疼那点配置时间

很多人觉得写 YAML 麻烦,宁愿手动搞。但账不是这么算的:

  • 投入:花半天写配置文件。
  • 回报:以后每一天、每一次提交、每一次发布,都在省时间。
  • 隐形收益:减少了 90% 的人为低级错误(比如传错文件、连错库)。

三、 核心概念对比

在深入配置之前,先理解两个平台的核心概念对应关系:

概念 GitHub Actions GitLab CI/CD 说明
配置文件 .github/workflows/*.yml .gitlab-ci.yml 定义自动化流程
流水线 Workflow Pipeline 一次完整的执行流程
阶段 - (通过 needs 控制) Stage 逻辑分组
作业 Job Job 具体执行的任务
步骤 Step Script 单个命令或动作
执行器 Runner Runner 执行任务的服务器
可复用组件 Action - (用 extends/include) 封装的功能模块
触发器 on rules/only/except 何时触发
敏感信息 Secrets Variables (masked) 存储密码等

四、 GitHub Actions 实战

4.1 目录结构

1
2
3
4
5
6
7
your-repo/
├── .github/
│ └── workflows/
│ ├── ci.yml # CI 工作流
│ └── cd.yml # CD 工作流
├── src/
└── package.json

4.2 配置文件语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# ============ 工作流名称 ============
name: CI # 在 GitHub Actions 页面显示的名称

# ============ 触发条件 ============
on:
push:
branches: [main, develop] # 哪些分支的 push 会触发
pull_request:
branches: [main, develop] # 哪些分支的 PR 会触发

# ============ 作业定义 ============
jobs:
job-name: # 作业的唯一标识符
runs-on: ubuntu-latest # 运行环境
needs: [other-job] # 依赖的作业(可选)
if: condition # 条件执行(可选)

# 服务容器(可选)
services:
service-name:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432

# 步骤列表
steps:
- name: Step Name # 步骤名称
uses: action/name@v1 # 使用 Action
with: # Action 的参数
param: value

- name: Run Command
run: | # 运行命令
echo "Hello"
npm install
env: # 环境变量
NODE_ENV: production

4.3 触发器详解(on)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
on:
# 1. 指定分支的 push
push:
branches:
- main
- develop
- 'release/**' # 支持通配符

# 2. 指定路径变更才触发
push:
paths:
- 'src/**'
- '!src/**/*.md' # 排除 md 文件

# 3. Pull Request
pull_request:
branches: [main]

# 4. 手动触发
workflow_dispatch:
inputs:
environment:
description: 'Deploy environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production

# 5. 定时触发(UTC 时间)
schedule:
- cron: '0 2 * * *' # 每天凌晨 2 点

4.4 作业依赖与条件执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
jobs:
lint:
runs-on: ubuntu-latest
steps: ...

test:
runs-on: ubuntu-latest
needs: lint # 等待 lint 完成后才执行
steps: ...

build:
runs-on: ubuntu-latest
needs: [lint, test] # 等待多个作业完成
steps: ...

deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main' # 只在 main 分支执行
steps: ...

条件表达式

1
2
3
4
5
6
if: github.ref == 'refs/heads/main'           # 只在 main 分支
if: github.event_name == 'push' # 只在 push 事件
if: success() # 前置作业成功
if: failure() # 前置作业失败
if: always() # 无论如何都执行
if: contains(github.event.head_commit.message, '[skip ci]')

4.5 环境变量与 Secrets

1
2
3
4
5
6
7
8
9
10
11
12
jobs:
deploy:
runs-on: ubuntu-latest
env:
NODE_ENV: production # 作业级别

steps:
- name: Deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }} # 从 Secrets 获取
COMMIT_SHA: ${{ github.sha }} # 从上下文获取
run: echo "Deploying..."

设置 SecretsGitHub 仓库 → Settings → Secrets and variables → Actions

4.6 服务容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
jobs:
test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

4.7 常用 Actions

Action 用途
actions/checkout@v4 检出代码
actions/setup-node@v4 安装 Node.js
actions/setup-python@v5 安装 Python
astral-sh/setup-uv@v4 安装 uv(Python)
pnpm/action-setup@v4 安装 pnpm
docker/build-push-action@v5 构建推送 Docker 镜像
appleboy/ssh-action@v1 SSH 远程执行

4.8 完整 CI 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
name: CI

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
# ============ 后端检查 ============
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

- name: Set up Python
run: uv python install 3.12

- name: Install dependencies
run: |
cd backend
uv sync --all-extras

- name: Run Ruff linter
run: |
cd backend
uv run ruff check .

- name: Run Ruff formatter check
run: |
cd backend
uv run ruff format --check .

# ============ 测试 ============
test:
runs-on: ubuntu-latest
needs: lint

services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Set up Python
run: uv python install 3.12

- name: Install dependencies
run: |
cd backend
uv sync --all-extras

- name: Run migrations
env:
DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/test_db
run: |
cd backend
uv run alembic upgrade head

- name: Run tests
env:
DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/test_db
run: |
cd backend
uv run pytest -v --tb=short

# ============ 构建 ============
build:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v4

- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .

# ============ 前端检查(与后端并行)============
frontend-check:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
cache-dependency-path: frontend/pnpm-lock.yaml

- name: Install dependencies
run: |
cd frontend
pnpm install

- name: Lint & Type Check
run: |
cd frontend
pnpm lint
npx vue-tsc --noEmit

- name: Build
run: |
cd frontend
pnpm build

4.9 完整 CD 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
name: CD - Deploy to Production

on:
push:
branches: [main]
workflow_dispatch: # 允许手动触发

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Deploy to Server via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SERVER_PORT || 22 }}
script: |
set -e

cd ${{ secrets.PROJECT_PATH || '/home/user/project' }}

echo "📥 Pulling latest code..."
git fetch origin main
git reset --hard origin/main

echo "🔨 Building and restarting services..."
docker compose build
docker compose up -d --remove-orphans

echo "🧹 Cleaning up..."
docker image prune -f

echo "✅ Deployment complete!"
docker compose ps

五、 GitLab CI/CD 实战

5.1 配置文件位置

GitLab CI/CD 使用项目根目录下的 .gitlab-ci.yml 文件。

5.2 配置文件语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# ============ 阶段定义 ============
stages:
- check
- test
- build
- deploy

# ============ 全局变量 ============
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
POETRY_VIRTUALENVS_IN_PROJECT: "true"

# ============ 全局缓存 ============
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .cache/pip
- .venv

# ============ 默认设置 ============
default:
image: python:3.11
before_script:
- pip install poetry
- poetry install

# ============ 作业定义 ============
lint:
stage: check
script:
- poetry run ruff check .
- poetry run ruff format --check .
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

5.3 阶段(Stages)

1
2
3
4
5
stages:
- check # 第一阶段:代码检查
- test # 第二阶段:测试
- build # 第三阶段:构建
- deploy # 第四阶段:部署
  • 同一阶段的作业并行执行
  • 不同阶段按顺序执行
  • 前一阶段全部成功后才执行下一阶段

5.4 规则(Rules)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
job:
rules:
# MR 事件触发
- if: $CI_PIPELINE_SOURCE == "merge_request_event"

# 主分支触发
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# 标签触发
- if: $CI_COMMIT_TAG

# 文件变更触发
- if: $CI_COMMIT_BRANCH
changes:
- "backend/**/*"
- "pyproject.toml"

# 手动触发
- if: $CI_COMMIT_BRANCH == "develop"
when: manual

# 永不运行
- when: never

5.5 缓存与产物

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 缓存:跨流水线复用
cache:
key:
files:
- poetry.lock
paths:
- .venv/
- .cache/

# 产物:在作业间传递、保存报告
test:
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
junit: junit.xml
paths:
- dist/
expire_in: 1 week
when: always

5.6 作业依赖(needs)

1
2
3
4
5
6
7
8
9
test:
stage: test
needs: ["lint"] # 必须等待 lint 完成

deploy:
stage: deploy
needs:
- job: build
artifacts: true # 获取 build 的产物

5.7 服务容器(Services)

1
2
3
4
5
6
7
8
9
10
11
test:
services:
- name: postgres:14
alias: db
- name: redis:7
alias: cache
variables:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password
DATABASE_URL: "postgresql://test_user:test_password@db:5432/test_db"

5.8 预定义变量

变量 说明
$CI_COMMIT_SHA 完整的 commit SHA
$CI_COMMIT_SHORT_SHA 短 commit SHA
$CI_COMMIT_BRANCH 分支名
$CI_COMMIT_TAG 标签名
$CI_DEFAULT_BRANCH 默认分支名
$CI_PIPELINE_SOURCE 触发源
$CI_MERGE_REQUEST_IID MR 编号
$CI_PROJECT_DIR 项目目录
$CI_REGISTRY 容器仓库地址
$CI_REGISTRY_IMAGE 项目镜像地址

5.9 YAML 锚点复用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 定义可复用的脚本片段
.setup_python: &setup_python
- pip install poetry
- poetry install

# 定义作业模板
.backend_template:
before_script:
- *setup_python
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# 使用模板
lint:
stage: check
extends: .backend_template
script:
- poetry run ruff check .

5.10 完整 CI 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
stages:
- check
- test
- build
- deploy

variables:
PYTHON_VERSION: "3.11"
POETRY_VIRTUALENVS_IN_PROJECT: "true"
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

cache:
key:
files:
- poetry.lock
paths:
- .cache/pip
- .venv

default:
tags:
- docker
retry:
max: 2
when:
- runner_system_failure

# ============ 模板 ============
.setup_python: &setup_python
- pip install poetry
- poetry install --no-interaction

.backend_template:
image: python:3.11
before_script:
- *setup_python
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "backend/**/*"
- "pyproject.toml"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# ============ Check 阶段 ============
lint:
stage: check
extends: .backend_template
script:
- poetry run ruff check backend/
- poetry run ruff format --check backend/

type-check:
stage: check
extends: .backend_template
script:
- poetry run mypy backend/
allow_failure: true

# ============ Test 阶段 ============
unit-test:
stage: test
extends: .backend_template
services:
- name: postgres:14
alias: postgres
variables:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db"
script:
- poetry run pytest
--cov=backend
--cov-report=xml
--cov-report=term-missing
--cov-fail-under=85
coverage: '/TOTAL.*?(\d+%)/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
when: always
needs: ["lint"]

# ============ Build 阶段 ============
build-docker:
stage: build
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- |
if [ "$CI_COMMIT_TAG" ]; then
docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
fi
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
needs: ["unit-test"]

# ============ Deploy 阶段 ============
deploy-staging:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | ssh-add -
script:
- ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_HOST "
cd /path/to/project &&
git pull origin main &&
docker compose up -d --build
"
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
needs: ["build-docker"]

deploy-production:
stage: deploy
extends: deploy-staging
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_TAG
when: manual

六、 GitHub Actions vs GitLab CI:怎么选?

6.1 语法对比

功能 GitHub Actions GitLab CI
触发条件 on: push/pull_request rules: if
阶段定义 无(用 needs 控制) stages: [check, test]
作业依赖 needs: [job-name] needs: [job-name]
条件执行 if: expression rules: when: manual
环境变量 env: variables:
密钥 ${{ secrets.NAME }} $NAME(在 Settings 配置)
服务容器 services: services:
缓存 Action 内置 cache:
产物 actions/upload-artifact artifacts:
复用 uses: action@v1 extends: .template

6.2 同功能示例对比

触发条件

1
2
3
4
5
6
7
8
9
10
11
# GitHub Actions
on:
push:
branches: [main]
pull_request:
branches: [main]

# GitLab CI
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "merge_request_event"

依赖管理

1
2
3
4
5
6
7
8
9
10
# GitHub Actions
jobs:
test:
needs: lint
runs-on: ubuntu-latest

# GitLab CI
test:
stage: test
needs: ["lint"]

服务容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# GitHub Actions
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432

# GitLab CI
services:
- name: postgres:16
alias: postgres
variables:
POSTGRES_PASSWORD: postgres

6.3 掏心窝子的建议

别看网上对比图一大堆,其实选择很简单:

  1. 代码在哪,CI 就用哪

    • 代码在 GitHub?无脑用 GitHub Actions。生态太好了,你想干的任何事(比如发版自动生成 Release Note、自动打 Tag、自动推 Docker Hub),基本都有现成的 Action,uses 一下完事。
    • 代码在 GitLab(通常是公司内网)?那就用 GitLab CI。它的 Runner 机制非常成熟,权限控制也更细致,适合企业级复杂的发布流程。
  2. 个人项目选 GitHub Actions

    • 免费额度够你用到天荒地老。而且它的 Marketplace 简直是宝库,能让你少写很多脚本。
  3. 复杂流水线 GitLab CI 略胜一筹

    • GitLab 的 stagesneeds 配合起来,对于那种几十个微服务、互相依赖的构建流程,可视化效果和管理体验会更好一些。

七、 避坑指南与最佳实践

7.1 别把所有东西塞进一个文件

1
2
3
4
5
6
7
8
9
# GitHub Actions
.github/workflows/
├── ci.yml # 持续集成
├── cd.yml # 持续部署
├── cd-staging.yml # 测试环境部署
└── release.yml # 版本发布

# GitLab CI(单文件,可用 include 拆分)
.gitlab-ci.yml

7.2 性能优化

使用缓存

1
2
3
4
5
6
7
8
9
10
11
12
# GitHub Actions
- uses: actions/setup-node@v4
with:
cache: 'pnpm'

# GitLab CI
cache:
key:
files:
- pnpm-lock.yaml
paths:
- node_modules/

并行执行

1
2
# 无依赖的作业会自动并行执行
# 合理拆分作业,充分利用并行

7.3 安全建议

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 使用 Secrets 存储敏感信息
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}

# 2. 限制权限(GitHub Actions)
permissions:
contents: read
packages: write

# 3. 固定 Action/镜像版本
uses: actions/checkout@v4 # 不要用 @latest
image: python:3.11 # 不要用 :latest

7.4 调试技巧

1
2
3
4
5
6
7
8
9
10
# GitHub Actions - 打印调试信息
- run: |
echo "Event: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"

# GitLab CI - 打印调试信息
script:
- echo "Branch: $CI_COMMIT_BRANCH"
- echo "Pipeline: $CI_PIPELINE_SOURCE"

本地测试

1
2
3
4
5
6
# GitHub Actions - 使用 act
brew install act # macOS
act push

# GitLab CI - 使用 gitlab-runner
gitlab-runner exec docker job-name

八、 抄作业:完整模板速查

8.1 GitHub Actions 通用模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
name: CI/CD

on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv sync --all-extras
- run: uv run ruff check .

test:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv sync --all-extras
- run: uv run pytest

deploy:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /path/to/project
git pull && docker compose up -d --build

8.2 GitLab CI 通用模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
stages:
- check
- test
- deploy

variables:
POETRY_VIRTUALENVS_IN_PROJECT: "true"

cache:
key:
files:
- poetry.lock
paths:
- .venv

default:
image: python:3.11

.setup:
before_script:
- pip install poetry
- poetry install

lint:
stage: check
extends: .setup
script:
- poetry run ruff check .

test:
stage: test
extends: .setup
script:
- poetry run pytest
needs: ["lint"]

deploy:
stage: deploy
script:
- echo "Deploying..."
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
needs: ["test"]

总结

CI/CD 不是什么高大上的黑科技,它就是程序员的**“外骨骼”**。它帮你扛住了重复、枯燥、易错的脏活累活,让你能专注于最有价值的代码逻辑。

别纠结是先学 GitHub Actions 还是 GitLab CI,先跑起来。哪怕只是加一个最简单的“提交代码自动跑 lint”,你的开发体验也会有质的飞跃。

记住:任何需要重复两次以上的操作,都应该被自动化。


相关文章