CI/CD 与 GitHub Actions 入门

27 阅读9分钟

前言

JD 里常见"熟悉 CI/CD"、"了解 GitHub Actions"。CI/CD 说的是一套工程做法:代码提交后,用脚本自动做检查、测试、部署,减少漏步骤等问题。

GitHub Actions 是 GitHub 提供的实现工具, 在仓库里放一份 YAML 配置,由 GitHub 在云端执行环境上按配置跑命令。


一、CI/CD 在解决什么问题

1.1 一次正常发布要经过哪些环节

以前端项目为例,从改代码到用户能打开新版本,通常要经过:

  1. 本地跑测试、lint,确认改动没有明显问题
  2. git push,把 commit 推到 GitHub 远程仓库
  3. 与主分支合并(多人协作时通过 Pull Request)
  4. 构建,例如 npm run build 生成 dist/
  5. 把产物部署到服务器或 Vercel、GitHub Pages 等平台

这些步骤每次发布都要做一遍。CI/CD 要做的就是:把能写成脚本的步骤固定下来,在指定时机(例如每次 push、每次 PR)自动执行

1.2 CI:Continuous Integration(持续集成)

先拆两个词。

集成(Integration)
在软件工程里指:把你的改动和仓库里已有代码合并成一个整体,并确认这个整体能构建、能跑测试。多人协作时,你改的是模块 A,同事改的是模块 B,集成就是验证 A 和 B 合在一起仍然正常。

持续(Continuous)
每次向主开发分支合入改动(push 或 merge 到 main),都跑一遍集成验证,而不是积累很多天、很多 PR 再一次合并。单次合并的变更越小,冲突越少,出问题也更容易定位是哪一次 commit 引入的。CI 选的是高频集成,用自动化换更低的合并风险。

CI 在自动化环境里通常做什么
下面这些是常见步骤,都在云端执行机(Runner)上跑:

  • 安装依赖(npm ci
  • 静态检查(npm run lint、类型检查)
  • 测试(npm test
  • 构建(npm run build,确认能编过)

1.3 CD:Continuous Delivery / Deployment(持续交付 / 持续部署)

CI 通过之后,代码在逻辑上已经可发布。CD 负责把构建产物放到用户能访问的环境(测试服、预发、生产)。

两个英文词经常混用,区别如下:

  • Continuous Delivery(持续交付): 构建和发布准备自动化,但上生产可能还要人工点确认
  • Continuous Deployment(持续部署): CI 通过后自动部署,没有人工审批环节。

很多团队对生产环境保留人工卡点,测试环境则全自动。下文里的 CD 泛指CI 之后的部署环节,不严格区分这两个词。

CD 要回答的问题:已经验证过的版本,如何稳定、重复地到达目标环境。

1.4 和工具的关系

CI/CD 是目标GitHub Actions、GitLab CI、Jenkins 等是实现手段。阶段划分在各工具里大同小异:装依赖 → 检查 → 测试 → 构建 → 部署,差别主要在配置文件写法。

仓库在 GitHub 上时,用 GitHub Actions 最省事:配置放在仓库里,和代码一起版本管理。本文后面都用 Actions 举例。


二、代码提交后,谁在什么地方执行这些命令

理解这一节,后面所有配置才看得懂。很多人第一次学 CI/CD 会卡在这里:「拉代码」到底是谁在拉、拉的是什么。

2.1 Runner

Runner(执行机):GitHub Actions 为每次 Job 分配的执行环境;托管 Runner 通常是临时虚拟机;CI 里的 npm test 等命令在这里执行

GitHub 收到 push,读仓库里的 workflow 配置,在 Runner 上把命令跑一遍。

可以把它理解成:用 GitHub 托管 Runner 时,相当于借你一台用完就还的 Linux 环境,专门用来验证这次提交。

2.2 workflow 里的 "拉代码" 到底是什么

配置文件里几乎总有一步:

- uses: actions/checkout@v6

这不是你在本地 git pull,而是 Runner 从 GitHub 远程仓库克隆代码

一次 push 触发 CI 时,过程大致是:

  1. GitHub 给这个 Job 分配一台空的 Runner
  2. actions/checkout 从 GitHub 下载本次触发所对应的那一个 commit 的文件,写到 Runner 的磁盘上
  3. 后面的 npm cinpm test 都在 Runner 上、针对这份代码执行

2.3 Runner 用完就销毁

Job 结束后 Runner 被回收,上面的代码、node_modules、构建产物都会清掉。因此:

  • 不能指望上一次 CI 装好的依赖留给下一次(除非用缓存,见第六节)
  • 不能把 Runner 当生产服务器;用户访问的网站要部署到 Vercel、自己的云主机等长期在线的环境

三、GitHub Actions 怎么知道要跑、跑什么

3.1 配置文件放哪

GitHub 只认仓库里的固定路径:

项目根目录/
└── .github/
    └── workflows/
        └── ci.yml

文件必须push 到 GitHub才生效。

3.2 从 push 到跑命令的完整链路

你在本机 git push
    → GitHub 更新远程分支,并产生「push 事件」
    → Actions 读取该 commit 里的 .github/workflows/*.yml
    → 看 on: 是否匹配(例如:是否是 push 到 main)
    → 匹配则创建一次 Workflow Run,为每个 Job 分配 Runner
    → 每个 Job 内按 steps 顺序:checkout → 装 Node → npm ci → npm test …
    → 成功或失败写回 GitHub(Actions 页、PR 上的检查项)
    → Runner 释放

有些外部 CI(例如自建 Jenkins)需要你在 GitHub 的 Webhooks 里填一个 URL:push 时 GitHub 再通知那台外部服务器去干活。GitHub Actions 是 GitHub 内置功能:你 push 时,GitHub 在接收 push、更新仓库的过程中就会触发 Actions 去读 workflow。只要 push 成功,且仓库里已有 .github/workflows/ 下的 yml,就会按 on: 规则跑起来。

3.3 配置文件的三层结构

一个 YAML 文件是一个 Workflow。里面有一个或多个 Job;每个 Job 在一台 Runner 上跑,包含多个 Step

Workflow(整个 ci.yml)
  └── Job(例如 test、deploy)
        └── Step(例如 checkout、npm test
关键字管什么
on:什么情况下触发(push、PR、定时等)
jobs:有哪些任务块
steps:每个任务里具体执行什么

多个 Job 默认并行(各占一台 Runner)。如果要求 B 在 A 成功之后才跑,写 needs: A。A 失败时,依赖它的 B 默认会被跳过(skipped)。


四、usesrun:Step 里两种写法

run: — 在 Runner 的 shell 里执行你写的命令,和在本机终端里敲一样:

- run: npm ci
- run: npm test

uses: — 调用写好的 Action,省得每个项目自己写 clone、装 Node 的脚本:

- uses: actions/checkout@v6
- uses: actions/setup-node@v6
  with:
    node-version: '20'

项目自己的测试、构建用 run;拉代码、装运行时这类通用操作用 uses


五、示例:Vue 项目的 CI 配置

假设 Vue 3 + Vite 项目,有 package.jsonpackage-lock.json,主分支叫 main

5.1 一份可以直接用的配置

name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Set up Node
        uses: actions/setup-node@v6
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Test
        run: npm test

      - name: Build
        run: npm run build

5.2 配置解释

on: push / pull_request + branches: [main]
main push,或向 main 开/更新 PR 时,都会触发。

runs-on: ubuntu-latest
使用 GitHub 托管的 Ubuntu Runner(临时虚拟机)。

actions/checkout@v6
Runner 从 GitHub 检出本次触发对应的代码。@v6 是官方 Action 的主版本标签,可按官方文档升级。

actions/setup-node@v6 + node-version: '20'
在 Runner 上安装 Node.js 20。本地和 CI 的 Node 主版本不一致,是常见的「本地过、CI 挂」原因之一,所以建议在配置里写明版本。

npm ci
package-lock.json(或 npm-shrinkwrap.json)安装依赖。CI 里通常用 npm ci 而不是 npm install,结果更可复现。没有 lock 文件时 npm ci 会失败,需先生成 lock,或改用 npm install

npm run lint / npm test / npm run build
对应 package.jsonscripts 下的命令;项目里必须事先定义好这些脚本,否则 CI 会失败。

5.3 跑起来之后去哪看

push 后打开仓库的 Actions 标签页,每次运行一条记录,点进去能看到每个 Step 的完整终端输出。commit 和 PR 页面也会显示检查项是否通过。


六、常见增强:缓存、拆分 Job、部署条件

基础 CI 能跑通之后,通常会做几件事。

6.1 依赖缓存

每次 Job 用新的 Runner,默认要重新 npm ci。若 package-lock.json 没变,重复下载浪费时间。可以:

- uses: actions/setup-node@v6
  with:
    node-version: '20'
    cache: 'npm'

GitHub 会把 npm 缓存存到 Actions Cache。lock 文件不变时,后续 Run 的 Install 步骤会快很多。日志里出现 Cache hit 表示命中。

6.2 拆分 Job,用 needs 控制顺序

例如先跑 lint,通过后再跑 test:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci && npm run lint

  test:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci && npm test

needs: lint 表示 lint 成功之后 test 才开始。lint 失败时,test 默认会被标记为 skipped。

每个 Job 使用不同的 Runner,所以每个 Job 都要各自 checkout、各自安装依赖。若再增加一个不依赖 test、只依赖 lint 的 build Job,并同样写 needs: lint,则 test 与 build 可以并行。

6.3 CD:加一个 deploy Job

CI 只验证;deploy 负责上线。典型写法:

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci && npm run build
      # 在此接入 GitHub Pages、Vercel、SSH 等

needs: test — 名为 test 的 Job 必须成功,deploy 才运行。若你把测试 Job 改成了别的名字,这里也要改成对应名字。

if: — 两个条件同时满足才执行:

  • github.event_name == 'push': push 事件
  • github.ref == 'refs/heads/main':当前引用是 main 分支。

Runner 用完会销毁,构建产物要推到托管平台、镜像仓库或目标服务器。

七、Secrets 与分支保护

7.1 密钥

API Token、SSH 私钥写在仓库 Secrets 里,workflow 中引用:

env:
  VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}

${{ secrets.XXX }} 在运行时注入;匹配到的 Secret 值在日志里会被打码。

配置入口:

仓库顶栏 Settings → Secrets and variables → Actions → New repository secret

7.2 分支保护

在仓库 Settings → Branchesmain 加规则(需要 Admin),例如:合并前必须通过 PR,且必须通过指定的 CI 检查。这样 CI 失败时无法合并,workflow 才真正起到门禁作用。


参考