0 Comments

GitHub Actions CI/CD 流水线从零搭建:让每次推送都自动跑起来

你肯定遇到过这种情况:代码在本地跑得好好的,推到仓库之后同事拉下来却报错;或者上线前一天才发现某个环境变量没配。CI/CD 要解决的,正是这类”只在某台机器上能用”的玄学问题。本文用一个真实可跑的流水线,讲清楚 GitHub Actions 的核心概念和常见坑。

为什么需要 CI/CD

先说结论:CI/CD 不是大厂专属的”高级货”,哪怕你维护一个几百 star 的个人开源项目,甚至一个只有自己在用的玩具仓库,它都能帮你省下大量重复劳动。

所谓 CI(持续集成)和 CD(持续交付/部署),拆开理解其实很朴素:

  • CI:每次提交代码,自动跑一遍测试、lint、类型检查、构建,保证主干永远是健康的。
  • CD:CI 通过之后,自动把产物部署到服务器或云平台。

没有 CI 的时候,这些步骤全靠人肉:手动 npm install、手动 npm test、手动打包上传服务器。一旦项目变大、协作者变多,漏掉一步就是一场事故。

GitHub Actions 是 GitHub 内建的 CI/CD 方案,对公共仓库免费,私有仓库每月也有免费额度。它最吸引人的地方在于:配置就是仓库里的一个 YAML 文件,不用自己搭 Jenkins 服务器,也不用维护 Runner。

一个小而完整的流水线

先看一个 Node.js 项目最基础的配置,感受一下结构。在仓库根目录创建 .github/workflows/ci.yml

name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

这个文件做了四件事:拉代码、装 Node、装依赖、跑测试。第一次看到的人可能一脸懵,我们逐行拆开。

on 决定什么时候触发。 上面的意思是:向 main 分支 push,或者有人向 main 发起 pull request 时触发。你也可以改成 on: push(任何分支都触发),或用 schedule 做定时任务。

jobs 是执行单元。 一个 workflow 可以有多个 job,每个 job 跑在一台全新的虚拟机上,互不干扰。这里的 test 是 job 的名字,可以随便起。

runs-on 指定运行环境。 ubuntu-latest 是最常用的 Linux 环境,另外还有 windows-latestmacos-latest。注意这里的环境是全新的,没有任何你本地装过的东西。

steps 是一系列有序动作。 每个 step 要么用 run 直接执行 shell 命令,要么用 uses 复用别人写好的 Action。

uses 到底用了什么

actions/checkout@v4actions/setup-node@v4 这种写法,是引用 GitHub 官方维护的 Action。checkout 负责把代码拉到虚拟机上,setup-node 负责安装 Node.js 并设置版本。

这里的 @v4 是版本号,强烈建议固定主版本,避免上游偷偷发布破坏性更新导致你的流水线突然挂掉。

with 用来给 Action 传参。node-version: 20 指定 Node 版本,cache: 'npm' 让 setup-node 自动缓存依赖,下次跑能省下不少时间。

如果你需要把自己的脚本也复用成一个 Action,本质上就是把一段 run 命令封装成独立的仓库,这里不展开,先会用官方的就行。

一个会出错的真实例子

光会跑测试还不够,我们加一个 lint 步骤,看看错误信息长什么样:

permissions:
  contents: read

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - name: Lint
        run: npm run lint
      - name: Test
        run: npm test

permissions: contents: read 这一行容易被忽略,它给 workflow 显式声明了只读权限,是 GitHub 官方推荐的最小权限实践,能降低 token 泄露后的风险。

当 lint 失败时,pipeline 会在对应 step 处停下来,并把错误日志完整展示在 Actions 页面里。这里有个常踩的坑:本地 npm install 生成的 lockfile 和线上不一致npm ci 会严格按照 package-lock.json 安装,如果锁文件没提交,或者提交了但有人手动改过 node_modules,流水线就会报错。解决方式很简单——永远提交 lockfile,且不要手动改动依赖。

缓存机制:别让流水线每次都从头来

流水线每次都是全新环境,如果每次都重新下载依赖、重新构建,跑一次可能要几分钟甚至更久。GitHub Actions 提供了 actions/cache 来复用中间产物。

Node 项目用 setup-nodecache: 'npm' 已经自动处理了依赖缓存,但如果你用的是 Rust、Go 之类的语言,或者有自定义构建产物,可以手动配置:

- name: Cache build artifacts
  uses: actions/cache@v4
  with:
    path: |
      ~/.cargo/registry
      ~/.cargo/git
      target
    key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
    restore-keys: |
      ${{ runner.os }}-cargo-

key 是缓存的唯一标识,hashFiles 会根据 Cargo.lock 内容生成 hash,锁文件一改,缓存 key 就变,自动失效旧的缓存。restore-keys 是回退匹配,key 完全匹配不上时,用前缀匹配最近的缓存。

这里有个容易踩的坑:缓存不是全能的,restore-keys 匹配到的旧缓存可能是脏的。所以缓存 key 最好设计成”内容相关”而非”时间相关”,避免拿到过期的构建产物。

部署:CI 通过后自动上线

测试和 lint 都绿了,就可以考虑自动部署。以部署到 VPS 为例,用一个单独的 job 依赖测试 job:

jobs:
  test:
    runs-on: ubuntu-latest
    outputs:
      should-deploy: ${{ steps.check.outputs.result }}
    steps:
      # ... 测试步骤省略

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /var/www/myapp
            git pull origin main
            npm ci --omit=dev
            pm2 restart myapp

几个关键点:

  • needs: testdeploytest 成功后才运行。
  • if: github.ref == 'refs/heads/main' 保证只有主干触发部署,feature 分支不会误上线。
  • secrets.SERVER_HOST 这些是存放在仓库 Settings → Secrets 里的敏感信息,绝不能硬编码在 YAML 里,否则一旦仓库公开,服务器 IP、私钥全泄漏。

SSH 私钥、服务器账号这些,直接写在配置里是新手最常犯、后果也最严重的错误。哪怕仓库是私有的,也养成用 Secrets 的习惯。

几个高频踩坑小结

  1. YAML 缩进:Actions 配置文件对缩进极其敏感,- 列表项和 key-value 缩进错了,GitHub 会直接报语法错误。本地可以用 yamllint 或直接在线校验。

  2. npm ci vs npm install:CI 环境务必用 npm ci,它是”干净安装”,不会像 npm install 那样尝试更新锁文件,行为可预期。

  3. 环境变量传递:不同 step 之间的环境变量不会自动共享,想要跨 step 传值,用 GITHUB_ENV 这种文件或 outputs

  4. 免费额度:私有仓库有分钟数限制,公共仓库基本不限制。写大流水线前先评估一下,别因为一个 while 死循环把额度烧光。

  5. 敏感日志console.log 千万别输出 token、密码,Actions 的日志默认对贡献者(甚至公开仓库的所有人)可见。

写在最后

GitHub Actions 的上手成本其实就是学会写一个 YAML 文件,剩下的都是实践细节。建议的节奏是:先在个人项目里配一条最基础的”push 即测试”流水线,把它跑绿;然后逐步加 lint、构建缓存;最后再接上自动部署。不要一上来就追求”一键从提交到生产”的完整流水线,那样一旦失败,排查起来反而更费劲。

工具存在的意义,是让你把精力留在写代码上,而不是反复做那些可以交给机器的事。打开你手头的仓库,给 .github/workflows/ 里加上第一条配置吧。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注