传统开发流程的缺点
以前我们写完代码,往往是这样一套流程:本地改完 → 手动打包 → 手动测试 → 再手动丢到服务器上部署。看着简单,但项目一变大、人一变多,问题就出来了:
重复劳动多、容易出错
每次发版都要敲一堆命令,换台机器环境还可能不一样。别人机器能跑、你机器跑不了,服务器上更是玄学。一不小心少跑一步测试,线上就炸了。
这时 CI/CD 便应运而生。
什么是 CI/CD
CI(Continuous Integration,持续集成):代码合并后自动跑构建、测试,尽早发现问题。
CD(Continuous Delivery / Deployment,持续交付/部署):通过测试后,自动发布到测试环境或生产环境。
简单点说:把「打包、测试、部署」这些重复步骤写成脚本,交给机器自动干,人只负责写代码和看结果。
常见的 CI/CD 工具有 Jenkins、GitLab CI、Travis CI,以及今天要讲的 GitHub Actions。
GitHub Actions
GitHub Actions 是 GitHub 提供的持续集成与持续交付平台,可以自动完成构建、测试和部署等流水线任务。你通过 YAML 文件定义工作流(Workflow),在指定事件发生时(比如 push、pull request)自动执行。
简单翻译下就是:
在仓库里放一份配置文件,告诉 GitHub:「什么时候跑、在什么环境跑、跑哪些命令」。事件一触发,GitHub 就帮你在云端机器上把这些事干完。
官方文档:GitHub Actions 文档
GitHub Actions 的核心是 Workflow(工作流),它由以下几部分组成
- Workflow(工作流):一次完整的自动化流程,对应一个 YAML 文件
- Event(事件):触发工作流的条件,如 push、pull_request、定时任务
- Job(任务):工作流里的一组步骤,默认在同一台机器上顺序执行
- Step(步骤):Job 里的最小执行单元,可以是一条命令,也可以是一个 Action
- Action(动作):可复用的封装步骤,比如 checkout 代码、安装 Node
- Runner(运行器):真正执行任务的机器,可以是 GitHub 托管的,也可以是自己的
可以粗暴理解成:
| 概念 | 类比 |
|---|---|
| Workflow | 一整条流水线 |
| Event | 按下流水线的开关 |
| Job | 流水线上的一个工位 |
| Step | 工位里的每一道工序 |
| Action | 别人做好的半成品工序 |
| Runner | 真正干活的那台机器 |
配置文件放哪
Workflow 配置文件统一放在仓库根目录下:
.github/workflows/
├── ci.yml
├── deploy.yml
└── release.yml- 目录必须是
.github/workflows/ - 文件后缀一般是
.yml或.yaml - 一个文件就是一个 Workflow,可以写多个
一份最简单的 Workflow
name: Hello Workflow
on:
push:
branches: [ main ]
jobs:
say-hello:
runs-on: ubuntu-latest
steps:
- name: 打印一句你好
run: echo "Hello GitHub Actions"简单翻译下这段配置:
当代码 push 到
main分支时,在一台 Ubuntu 机器上跑一个叫say-hello的任务,任务里只干一件事:打印Hello GitHub Actions。
配置文件字段说明
顶层常用字段
| 字段 | 作用 |
|---|---|
| name | 工作流名称,会显示在 Actions 页面 |
| on | 触发条件,决定什么时候跑 |
| env | 全局环境变量,对所有 Job 生效 |
| jobs | 定义有哪些 Job |
| defaults | 默认配置,比如默认的 shell、工作目录 |
| concurrency | 并发控制,避免同一类任务同时跑多份 |
on:触发事件
这是 Workflow 的开关,常用的有:
| 事件 | 作用 |
|---|---|
| push | 推送代码时触发 |
| pull_request | 创建或更新 PR 时触发 |
| schedule | 定时触发(cron 表达式) |
| workflow_dispatch | 在网页上手动点一下运行 |
| release | 发布 Release 时触发 |
| workflow_call | 被其他工作流当作可复用流程调用 |
示例:
on:
push:
branches: [ main, develop ]
paths:
- 'src/**'
- 'package.json'
pull_request:
branches: [ main ]
schedule:
- cron: '0 2 * * *' # 每天凌晨 2 点(UTC)
workflow_dispatch: # 允许手动触发简单点说:
branches:限定分支paths:只有改了指定路径才触发,避免无关改动白跑一遍cron:定时任务,注意时间是 UTC,不是北京时间
jobs:任务定义
| 字段 | 作用 |
|---|---|
| runs-on | 指定运行环境,如 ubuntu-latest、windows-latest、macos-latest |
| needs | 依赖其他 Job,实现先后顺序 |
| if | 条件表达式,满足才执行 |
| env | 仅当前 Job 生效的环境变量 |
| strategy | 矩阵构建,一次跑多种版本/系统 |
| timeout-minutes | Job 超时时间 |
| permissions | 控制 GITHUB_TOKEN 的权限 |
| services | 启动附属服务,比如临时起一个 MySQL、Redis |
示例:Job 之间有依赖
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "先构建"
test:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "构建完再测试"
deploy:
needs: [build, test]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- run: echo "只有 main 分支才部署"steps:步骤
每个 Step 通常二选一:
run:直接执行 shell 命令uses:引用一个现成的 Action
| 字段 | 作用 |
|---|---|
| name | 步骤名称,方便在日志里看 |
| run | 执行命令 |
| uses | 使用某个 Action |
| with | 传给 Action 的参数 |
| env | 仅当前 Step 生效的环境变量 |
| if | 条件执行 |
| id | 给步骤起个 id,方便后面取输出结果 |
| working-directory | 指定命令的工作目录 |
示例:
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装 Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: 跑测试
run: npm test
env:
NODE_ENV: test常用官方 Action
| Action | 作用 |
|---|---|
| actions/checkout | 把仓库代码拉到 Runner 上 |
| actions/setup-node | 安装指定版本的 Node.js |
| actions/setup-python | 安装指定版本的 Python |
| actions/setup-go | 安装指定版本的 Go |
| actions/cache | 缓存依赖,加快构建 |
| actions/upload-artifact | 上传构建产物 |
| actions/download-artifact | 下载之前上传的产物 |
| docker/build-push-action | 构建并推送 Docker 镜像 |
注意:
uses后面最好带版本号,比如@v4,不要裸用@main,免得上游一改你流水线也跟着一起改。
环境变量与密钥
环境变量
env:
APP_ENV: production
jobs:
build:
runs-on: ubuntu-latest
env:
BUILD_MODE: release
steps:
- run: echo $APP_ENV
- run: echo $BUILD_MODESecrets(密钥)
密码、Token、私钥这类敏感信息,千万别写死在 YAML 里。
去仓库:Settings → Secrets and variables → Actions 里添加,然后这样用:
steps:
- name: 部署
run: ./deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}简单点说:
secrets.XXX:敏感信息,日志里会自动打码vars.XXX:普通配置变量,不敏感${{ }}:GitHub Actions 的表达式语法,用来取值、写条件
几个常用上下文
| 表达式 | 含义 |
|---|---|
| github.ref | 当前分支/标签引用 |
| github.sha | 当前提交的 commit id |
| github.repository | 仓库名 owner/repo |
| github.event_name | 触发事件名称 |
| runner.os | Runner 的操作系统 |
| secrets.NAME | 读取密钥 |
| needs.job_id.outputs.xxx | 读取上游 Job 的输出 |
矩阵构建(strategy.matrix)
一份配置,同时测多种环境,很香。
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [18, 20]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci && npm test简单翻译下:
在 Ubuntu / Windows 上,分别用 Node 18 和 Node 20 跑测试,一共 2 × 2 = 4 组任务。
一个更完整的示例:Node 项目 CI
name: Node CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install
run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm test
- name: Build
run: npm run build再来一个带部署的简版:
name: Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 构建
run: |
npm ci
npm run build
- name: 发布
if: success()
run: ./scripts/deploy.sh
env:
TOKEN: ${{ secrets.DEPLOY_TOKEN }}本地怎么调试、线上怎么看
在 GitHub 上看结果
仓库页签里点 Actions,左边选对应 Workflow,右边看每次运行日志。某一步红了,点开 Step 就能看到报错。
权限不够时
有时候 push 到别的仓库、创建 Release 会报权限问题,可以在 Job 里显式声明:
permissions:
contents: write
pull-requests: write几个实用小技巧
| 技巧 | 说明 |
|---|---|
continue-on-error: true | 某步失败也不阻断后续(慎用) |
if: always() | 不管前面成败都执行,适合清理 |
if: failure() | 仅失败时执行,适合发告警 |
shell: bash | 指定 shell,Windows 上尤其有用 |
working-directory: ./app | 命令跑在子目录里 |
关于 Workflow 的选用建议
官方/实践建议
能拆就拆:CI(构建测试)和 CD(部署)尽量分两个 Workflow,职责更清晰。
敏感信息一律走 Secrets,别硬编码。
Action 固定版本号,避免上游更新把你干翻。
用paths过滤无关改动,省分钟数也省时间。
先在小仓库把流程跑通,再搬到正式项目。
(完)