Github Action 部署应用实战 工作流配置与四种上线方式
每次改完文档站,我都得本地跑一遍 npm run build,再把 dist 目录手动传到服务器,传完还要进控制台刷一次缓存。一次两次还好,一天来五六回就烦了,而且总有那么一回会忘了先构建,直接把上一版的文件传了上去。GitHub Actions 能把这段重复劳动整个接走,你只管 push,拉代码、装依赖、打包、上传、刷缓存都在云端跑完。这篇把我这阵子用 Actions 做部署的几种配置整理成一篇,从 workflow 文件的字段读法开始,一直写到四个不同目标环境的可复制配置,最后留一份上线前的自查清单。
在本篇文章中,我们将从浅入深,和大家一起学习以下知识:
- GitHub Actions 的执行模型,workflow、job、step、action 四个概念分别对应什么
- workflow 文件的核心字段,
name、on、needs、runs-on、steps怎么读怎么写 - 发布到阿里云 OSS,顺带把静态资源的长缓存也设上
- 跨仓库发布到 GitHub Pages,公钥私钥分别放在哪个仓库
- 用 rsync 推到自己的云服务器,SSH 密钥对怎么配才不会连不上
- 发布到腾讯云静态网站托管
- 上线前的 checklist,以及这些老版本号今天该怎么处理
# 一、先把 GitHub Actions 的执行模型讲清楚
持续集成本身不是什么新东西,它就是一串固定动作:抓取代码、装依赖、跑测试、登录远程服务器、把产物发到第三方服务。GitHub 把这一串里的每一个动作单拎出来,称为 actions。
真正让它跟传统 CI 拉开差距的是复用方式。你需要某个动作,不用自己写脚本,直接引用别人写好的 action 就行,整个持续集成过程就变成了一堆 actions 的组合。
这个设计是真的舒服。
GitHub 为此做了一个官方市场,能搜到别人提交的 actions。另外还有一个 awesome actions 仓库,也能捞到不少现成的。
既然每个 action 就是一个独立脚本,那它当然可以做成一个代码仓库。引用的时候用 userName/repoName 语法,比如 actions/setup-node 指的就是 github.com/actions/setup-node 这个仓库,它的作用是安装 Node.js。GitHub 官方维护的 actions 都放在 github.com/actions 下面。
是代码仓库就有版本概念,所以你可以精确引用某一个版本,用的是 Git 的指针语义:
actions/setup-node@74bc508 # 指向一个 commit
actions/setup-node@v1.0 # 指向一个标签
actions/setup-node@master # 指向一个分支
这里有个坑要注意,指向分支(比如 @master)意味着对方一改你就跟着变,出问题的时候你连是哪次变更引起的都查不到。生产用的 workflow 建议锁大版本标签,安全要求高的直接锁 commit sha。
# 1.1 四个术语的层级关系
GitHub Actions 有一套自己的说法,四个词是层层嵌套的:
| 术语 | 含义 | 关系 |
|---|---|---|
| workflow(工作流程) | 持续集成一次运行的完整过程 | 最外层,一个 .yml 文件就是一个 workflow |
| job(任务) | 一次运行里可以完成的多个任务之一 | 一个 workflow 由一个或多个 job 构成 |
| step(步骤) | 任务内部的一步 | 一个 job 由多个 step 串行构成 |
| action(动作) | 每个 step 执行的具体命令 | 一个 step 可以依次执行一个或多个 action |
要记的关键点只有一条:同一个 workflow 里的多个 job 默认是并行跑的,而同一个 job 里的多个 step 是严格串行的。所以「装依赖、打包、上传」这种有先后依赖的动作必须写在同一个 job 里,写成三个 job 会同时启动,第二个 job 根本看不到第一个 job 装好的 node_modules。
# 1.2 整条链路长什么样
把上面这些串起来,一次自动部署的完整链路是这样:
本地 git push
│
▼
GitHub 仓库(master 分支)
│ 触发 push 事件
▼
.github/workflows/*.yml ← GitHub 自动发现目录下的 yml 并执行
│
▼
┌──────────────── workflow ────────────────┐
│ runs-on: ubuntu-latest(一台干净的虚拟机)│
│ │
│ job: build │
│ ├─ step1 actions/checkout 拉代码 │
│ ├─ step2 npm install 装依赖 │
│ ├─ step3 npm run build 打包 │
│ └─ step4 第三方 action 上传/部署 │
└──────────────────────────────────────────┘
│
▼
目标环境:阿里云 OSS / GitHub Pages / 自有服务器 / 腾讯云静态托管
有一点特别值得先说清楚,每次运行拿到的都是一台全新的虚拟机,跑完就销毁。所以本地能跑不代表 CI 能跑,凡是你在本机全局装过的东西(ossutil、某个 CLI、某个字体),CI 上都得在 step 里重新装一遍。我第一次配的时候就是栽在这个上,本地好好的,CI 上一片红。
# 二、workflow 文件的字段拆解
GitHub Actions 的配置文件叫 workflow 文件,固定放在仓库的 .github/workflows 目录下。
文件采用 YAML 格式,文件名随便取,后缀统一为 .yml,比如 foo.yml。一个仓库可以有多个 workflow 文件,GitHub 只要发现 .github/workflows 目录里有 .yml 就会自动运行。字段非常多,详见官方文档,下面挑常用的几个说。
# 2.1 name
name 字段是 workflow 的名称,显示在仓库 Actions 页签的列表里。省略的话默认取当前 workflow 的文件名。
name: GitHub Actions Demo
小建议,如果你一个仓库配了三四个 workflow,这个 name 一定要写,而且写得能一眼区分。全靠文件名认的话,Actions 列表里全是 ci.yml,排查起来很难受。
# 2.2 on
on 字段指定触发 workflow 的条件,通常是某些事件。
on: push
上面这行的意思是 push 事件触发 workflow。它也可以写成事件数组:
on: [push, pull_request]
这样 push 或 pull_request 任意一个都能触发。完整的事件列表见官方文档,除了代码库事件,GitHub Actions 还支持外部事件触发和定时运行。
# 2.3 限定分支和标签
只写 on: push 的问题是任何分支的推送都会触发一次构建,你在 feature 分支上推十次,就白跑十次部署。指定事件的同时限定分支就能解决:
on:
push:
branches:
- master
这样只有 master 分支发生 push 事件时才会触发 workflow。部署类的 workflow 我基本都会加这一段。
# 2.4 jobs 与 job 之间的依赖
workflow 文件的主体是 jobs 字段,表示要执行的一项或多项任务。jobs 里面需要写出每一项任务的 job_id,具体名称自定义,job_id 里面的 name 字段是这项任务的说明文字。
jobs:
my_first_job:
name: My first job
my_second_job:
name: My second job
上面的 jobs 字段包含两项任务,job_id 分别是 my_first_job 和 my_second_job。
前面提过 job 默认并行,那要串起来怎么办?用 needs 声明依赖关系:
jobs:
job1:
job2:
needs: job1
job3:
needs: [job1, job2]
上面这段里 job1 必须先于 job2 完成,而 job3 要等 job1 和 job2 都完成才能运行,所以整个 workflow 的运行顺序是 job1、job2、job3。
「构建镜像」和「登录服务器部署」这种典型的两段式流程,就是靠 needs 把部署 job 挂在构建 job 后面的。少了这一句,部署 job 会在镜像还没推上去的时候就开始拉,然后失败。
# 2.5 runs-on
runs-on 指定运行所需要的虚拟机环境,是必填字段。当时可用的虚拟机是这些:
ubuntu-latest,ubuntu-18.04或ubuntu-16.04
windows-latest,windows-2019或windows-2016
macOS-latest或macOS-10.14
这几个带具体版本号的镜像后来陆续退役过,我不在这里写死替换成哪一版,因为写死了过阵子又会过期。实际配的时候直接用 ubuntu-latest / windows-latest / macos-latest 这类滚动标签最省事,GitHub 会自动指向当前维护中的镜像。只有当你的构建对系统库版本敏感(比如依赖某个特定的 glibc 或者 Xcode),才需要去官方 runner 镜像文档里查当前支持的固定版本再钉住。
# 2.6 steps
steps 字段指定每个 job 的运行步骤,可以包含一个或多个步骤。每个步骤可以指定这三个字段:
jobs.<job_id>.steps.name:步骤名称jobs.<job_id>.steps.run:该步骤运行的命令或者 actionjobs.<job_id>.steps.env:该步骤所需的环境变量
下面是一个完整的 workflow 文件范例:
name: Greeting from Mona
on: push
jobs:
my-job:
name: My Job
runs-on: ubuntu-latest
steps:
- name: Print a greeting
env:
MY_VAR: Hi there! My name is
FIRST_NAME: Mona
MIDDLE_NAME: The
LAST_NAME: Octocat
run: |
echo $MY_VAR $FIRST_NAME $MIDDLE_NAME $LAST_NAME.
steps 字段只包括一个步骤,这个步骤先注入四个环境变量,然后执行一条 Bash 命令。run 后面跟 | 表示这是一段多行脚本,多行命令都写在这个块里。
# 2.7 关于文中的版本号
下面几节的配置里会出现 actions/checkout@v2、peaceiris/actions-gh-pages@v2.5.1 这类版本号,那是当时能用的写法,我原样保留。这些常用 action 后来都发过更新的大版本,主体语法基本兼容,但底层 Node 运行时和一部分默认行为(比如 checkout 拉取的深度、缓存策略)有过调整。
所以你照抄之前,先去对应 action 仓库的 releases 页面看一眼当前推荐的大版本,把 @v2 换成它标注的那个。我这里不写具体数字,写了也是很快就旧。
# 三、发布到阿里云 OSS
第一种场景最简单,产物是一堆静态文件,直接扔进对象存储。