使用 Github Actions 实现一个简单的 ci/cd
本文首发于itsuki.cn个人博客
前言
在自己的个人博客项目开发中, 每次完成一个新功能, 前后端都需要重新部署进行更新, 发现每次手动打包部署比较麻烦(我就是一个懒狗), 所以想想能不能使用 Github Actions 来实现一个每次 push 上去就自动打包构建部署, 这样子我就可以少做很多事情. 经过我的不断捣鼓, 终于实现了, 所以写一篇文章来记录一下, 一个 ci/cd 可以帮你节约很多时间.
前置条件
- 服务器 (花 💰).
- 服务器安装
node
(自行百度). - 服务器安装
pm2
(npm i -g pm2
). - 如果没有的话, 先看看有个印象.
Github Actions
我觉得官方文档就写得挺好的, 所以这一节的介绍, 我就直接翻译过来了.
引用官方文档来解释是个啥:
使用 GitHub Actions 自动化, 自定义和执行软件开发工作流程. 可以创建和共享操作以执行您想要的任何工作, 包括 CI/CD, 并将操作结合在完全自定义的工作流程中.
Workflows
Workflows: Github Actions 有多个 Workflows, 工作流是一个可配置的自动化流程, 将运行一个或多个 Job. 工作流由签入到存储库的 yaml 文件定义, 并将在由存储库中的事件触发时运行, 也可以手动触发, 或按定义的时间表触发.
Event
Event: Git 仓库中的特定活动, 用来触发工作流. 例如, 某人 push 到 main 分支, push 到 main 分支就是一个 Event, 然后会执行工作流, 当然还有其他的操作也可以触发,比如说 pull request、tag 等操作.
Runner
Runner: Runner 是触发工作流程的服务器. 每个 Runner 一次都可以运行一个作业. GitHub 提供Ubuntu Linux
、Microsoft Windows
和MacOS
来运行工作流程; 每个工作流程运行均以新的, 新的虚拟机执行.
Jobs
Jobs: 工作流中在同一运行器上执行的一组步骤. 每个步骤要么是将要执行的 shell 脚本, 要么是将要运行的操作. 步骤按顺序执行, 并且相互依赖. 由于每个步骤都在同一个运行程序上执行, 因此您可以在一个步骤之间共享数据. 例如, 您可以有一个构建应用程序的步骤, 然后是一个测试所构建应用程序的步骤.
Actions
Actions: 执行复杂且经常重复任务的 GitHub Action 平台的自定义应用程序. 使用操作来帮助减少您在工作流文件中编写的重复代码的量. Actions 可以从 GitHub 中提取 Git 存储库, 为您的构建环境设置正确的工具链, 或为云提供商设置身份验证.
准备活动
创建一个项目
- 我平时使用
next.js
多一点, 就使用npx create-next-app silver-robot
来创建一个next.js
项目. - 包管理使用的
yarn
.
选择什么创建项目就是萝卜青菜各有所爱.
创建完成之后的目录:
创建 Github Actions
首先我们在 Github 仓库中点击 Actions 这个 tab, 如果是 node 项目的话, 点击红框里面的 node.js.
然后就会在当前项目新建一个文件夹.github/workflows/node.js.yml
.
# .github/workflows/node.js.yml
# 当前整个文件就是一个Workflows
name: Node.js CI # 工作流的名称
on: # 执行该工作流的事件, 上图的Events
push:
branches: [main] # 当main分支push之后就执行该工作流
pull_request:
branches: [main] # 当main分支pull_request就执行该工作流
jobs: # 执行的一组任务, 上图中的Jobs
build:
runs-on: ubuntu-latest # 在什么机器上执行, 上图中的Runner
strategy: # 矩阵策略
matrix:
node-version: [12.x, 14.x, 16.x] # 等同于使用12.x、14.x、16.x三个不同node版本的job跑同一个工作流
steps: # 要执行的步骤 上图中的Actions
- uses: actions/checkout@v3 # 相当于git clone到当前运行的机器上
- name: Use Node.js ${{ matrix.node-version }} # 显示在step上的别名
uses: actions/setup-node@v3 # 设置node环境(可以使用npm、yarn, 选择指定的node版本)
with: # 传入参数
node-version: ${{ matrix.node-version }} # 指定什么版本 ${{}} 引用变量
cache: 'npm' #
- run: npm ci # 运行npm ci
- run: npm run build --if-present # 运行npm run build 命令
- run: npm test # 运行 npm test
所以node.js.yml
文件的意思就是:
- 定义了一个 Node.js CI 的工作流.
- 当 main 分支有
push
、pull_request
操作的时候就在ubuntu-latest
机器上执行工作流. - 使用三个不同的 node 版本, 传入不同的参数依次运行
npm ci
、npm run build
、npm test
命令. - 结束.
等同于
// node 12.x
// 1. git clone xxxx
// 2. npm ci
// 3. npm run build --if-present
// 4. npm test
// node 14.x
// 1. git clone xxxx
// 2. npm ci
// 3. npm run build --if-present
// 4. npm test
// node 16.x
// 1. git clone xxxx
// 2. npm ci
// 3. npm run build --if-present
// 4. npm test
所以看看这个工作流的效果:
报错了, 原因也很简单, 因为npm ci
它依赖于package-lock.json
, 因为包管理使用的 yarn, 只有yarn.lock
, 如果使用的 npm 就不会有这个问题啦.
实现一个简单的工作流
我们来实现一个全局安装@vue/cli
, 查看全局安装之后的 vue 版本, 在刚刚的node.js.yml
进行精简与修改.
name: vue/cli # 显示名字
on:
push:
branches: [main] # main分支push就执行该工作流
jobs:
build:
runs-on: ubuntu-latest # 在最新的ubuntu机器上
steps:
- uses: actions/checkout@v3 # git 克隆到当前机器上
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 16.x # 使用16的node版本
cache: 'yarn'
- run: yarn global add @vue/cli # 全局安装@vue/cli
- run: vue -V # 查看版本
效果如下图所示:
实现一个简单的 ci/cd
有了上面这个例子的基础, 我们就可以一点点的实现简单的 ci/cd, 我感觉就是用 Github Actions 来模仿我们手动构建的步骤.
- 打包.
- 将打包后的文件上传到服务器.
- 在服务器初始化然后运行.
我将从这三个步开始拆解说明.
打包
第一步就是要进行打包, 我们想一下在本地是怎么进行打包的呢?
- 通过
git clone
拉一个项目下来. yarn
/npm install
安装依赖.- 最后再进行
yarn build
/npm run build
.
name: Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3 # git 克隆到当前机器上
- name: Use Node.js
uses: actions/setup-node@v3 # 设置node环境
with:
node-version: 16.x # 指定版本
cache: 'yarn'
- run: yarn # 运行 yarn
- run: yarn build # 运行 yarn build
- run: ls -a # 查看打包后的目录文件
可以使用name
来设置在jobs
、steps
的名称显示, 更加清晰易懂, 所以再来修改一下.
# 省略...
steps:
# 省略...
- name: Install dependencies
run: yarn # 运行 yarn
- name: Run build
run: yarn build # 运行 yarn build
- name: view directory files
run: ls -a # 查看打包后的目录文件
ok, 我们来看看运行的效果.
可以看到ls -a
完美执行, 同时可以看到next.js
打包后的文件: .next
, 这第一步打包算是完成了.
上传服务器
第二步就是将打包好的文件上传到服务器, 我自己在本地部署的时候, 就是利用ssh
连接远程服务器上传.
所以在使用 Github Actions 时, 可以使用scp-action这个库, 我们先来看看代码.
# 省略...
steps:
# 省略...
- name: Rename build folder
run: mv .next build # 上传之前先重命名.next成build, 防止上传之后覆盖了.next
- uses: appleboy/scp-action@master
with:
host: ${{ secrets.HOST }} # 服务器host
username: ${{ secrets.USER }} # 服务器用户名
password: ${{ secrets.PASSWORD }} # 服务器密码
source: 'build,package.json,public' # 需要上传的文件, 多文件使用逗号隔开
target: '~/demo' # 上传到服务器的什么位置
这里我们用到了${{}}
以及secrets.HOST
, 前一个好理解, 就是引用一个变量, 后一个是啥呢?
我们可以使用env
进行环境变量的声明. 也可以使用 secrets
进行加密版环境变量的声明.
env 环境变量
我们先看 Github Actions 中的一个环境变量的例子.
name: Greeting on variable day
on: workflow_dispatch # 手动触发工作流
env:
DAY_OF_WEEK: Monday # 定义整个工作流中的变量
jobs:
greeting_job:
runs-on: ubuntu-latest
env:
Greeting: Hello # 定义在jobs中的变量
steps:
- name: "Say Hello Mona it's Monday"
run: echo "$Greeting $First_Name. Today is $DAY_OF_WEEK!"
env:
First_Name: Mona # 定义在steps中的变量
打印出来的结果是:
Run echo "$Greeting $First_Name. Today is $DAY_OF_WEEK!"
Hello Mona. Today is Monday!
secrets
比如说有一些登陆、数据库的账号和密码、或者说一些第三方提供的密钥要使用的话, 使用环境变量无疑全部都暴露出来了, 所以我们不妨使用secrets
来定义这些秘密变量.
首先在这里找到 secrets 的定义:
点击右上角的 New repository secret.
添加对应的 key、value 即可添加 sercet 了.
是不是很简单, 这样子你就可以使用${{secrets.xxxx}}
引用秘密变量了, 所以上面的代码中用到了HOST
、USER
、PASSWORD
三个密码变量需要在 sercets 中定义.
在服务器运行命令
如果是@vue/cli
、create-react-app
打包出来的静态文件, 只需要上传到服务器的指定文件夹就可以了, 就不需要进行这一步.
但是如果还需要在服务器把打包后的项目(比如说 ssr 项目)再次运行起来的话, 就需要进行当前这一步了.
在本地的时候就是上传到服务器之后, 然后在服务器进行一些操作就可以部署成功, 所以在使用 Github Actions 时, 可以使用ssh-action这个库, 我们先来看看代码.
# 省略...
steps:
- name: Run Deploy
uses: appleboy/ssh-action@master
with:
command_timeout: 4m
host: ${{ secrets.HOST }}
username: ${{ secrets.USER }}
password: ${{ secrets.PASSWORD }}
script: | # 运行多行命令
echo "[deploy] start deployment..."
# 进到当前文件夹
cd ~/demo
# 停止服务
pm2 stop demo
pm2 delete demo
# 删除之前的文件
rm -rf .next
rm -rf node_modules
# 将上传的文件的文件进行重命名 build -> .next
mv build .next
# 安装依赖
yarn --prod
# 启动服务
yarn pm2
echo "[deploy] end deployment..."
echo "[deploy] success"
所以我们还需要在package.json
中写一个pm2
的运行命令.
{
"scripts": {
"start": "next start",
"pm2": "pm2 start yarn --name 'demo' --interpreter bash -- start "
},
"dependencies": {
// ...
}
}
最终代码
.github/workflows/node.js.yml
name: Node.js CI
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3 # git 克隆到当前机器上
- name: Use Node.js
uses: actions/setup-node@v3 # 设置node环境
with:
node-version: 16.x # 指定版本
cache: 'yarn'
- name: Install dependencies
run: yarn # 运行 yarn
- name: Run build
run: yarn build # 运行 yarn build
- name: View directory files
run: ls -a # 查看打包后的目录文件
- name: Rename build folder
run: mv .next build # 重命名.next成build
- uses: appleboy/scp-action@master # 使用scp-action进行文件上传
with:
host: ${{ secrets.HOST }} # 服务器host
username: ${{ secrets.USER }} # 服务器用户名
password: ${{ secrets.PASSWORD }} # 服务器密码
source: 'build,package.json,public' # 需要上传的文件
target: '~/demo' # 上传到服务器的什么位置
- name: Run Deploy
uses: appleboy/ssh-action@master
with:
command_timeout: 4m
host: ${{ secrets.HOST }} # 服务器host
username: ${{ secrets.USER }} # 服务器用户名
password: ${{ secrets.PASSWORD }} # 服务器密码
script: | # 运行多行命令
echo "[deploy] start deployment..."
# 进到当前文件夹
cd ~/demo
# 停止服务
pm2 stop demo
pm2 delete demo
# 删除之前的文件
rm -rf .next
rm -rf node_modules
# 将上传的文件的文件进行重命名 build -> .next
mv build .next
# 安装依赖
yarn --prod
# 启动服务
yarn pm2
echo "[deploy] end deployment..."
echo "[deploy] success"
package.json
{
"scripts": {
"start": "next start",
"pm2": "pm2 start yarn --name 'demo' --interpreter bash -- start "
},
"dependencies": {
// ...
}
}
小结
以上就完成一个简单的 ci/cd, 每当 main 分支 push、pull_request 的时候就会运行该工作流, 经过我的使用感觉挺不错的, 当然这只是比较简单的.
后面自己捣鼓的话, 可以尝试加上actions-cache
加快 Github Actions 的流程、使用几个job
进行解耦合, 在不同的job
进行分享数据、进行测试/ci 等操作, 这个完全取决于你自己怎么玩.
如果本文对你有所帮助的话, 点个赞就是对作者最大的肯定!!!
转载自:https://juejin.cn/post/7104177351537197070