使用 Github actions 部署 SSR 应用
关于 github actions 应该不用多做介绍,不了解的同学可以看看官方文档:https://docs.github.com/cn/actions
这次我将不使用第三方插件,完全从零实现利用 github actions 部署 SSR 应用,大致步骤为:
- clone 项目并切换至对应分支
- 项目打包
- ssh 配置
- 上传静态资源
- 利用 ssh 操作远程服务器
新建一个基本项目
$ mkdir github-actions-deploy-ssr
# 新建 src/index.js 文件,内容为 console.log('app')
$ mkdir src && echo "console.log('app')" >> src/index.js
接下来我们按照文档,在项目目录下 .github/workflows
文件夹下创建一个脚本文件 deploy.yml
$ mkdir .github && mkdir .github/workflows && touch .github/workflows/deploy.yml
写个最简单的脚本
name: Build and deploy
on:
push:
branches:
- master
jobs:
Build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Build
run: echo "build"
- run: echo "deploy"
- name 脚本名称
- on 触发条件,详细介绍可见文档,这里配置的是在 push 到 master 的时候触发
- jobs 任务细节,可定义多个 job
- job.runs-on 执行的虚拟机环境
- job.steps 具体执行步骤,steps 是个数组,可以配置 name / run / uses 等多个配置,并且 name 和 run 必须有一个
- job.step.name 步骤名称
- job.step.run 执行的内容,就是 shell 命令
- job.step.uses 应该是会用到比较多的,他可以直接使用 github 上面的第三方插件。比如
actions/checkout@v2
。这些插件名称规则是${github_owner}/${github_repo}@${branch|tag}
,比如actions/checkout@v2
就是使用 https://github.com/actions/checkout 这个仓库里面 v2 的 tag
所以上面脚本的大概的意思就是:在 push 到 master 的时候,在 ubuntu
的最新环境下执行两个步骤:
- 输出 build
- 输出 deploy
我们提交代码到 github 看下效果
OK,没毛病。可以看到第二个 step 我们没有配置 name,所以他的标题就是 Run ${command}
-> Run echo "deploy"
github actions 脚本
需要打包和部署的话,首先我们需要把我们的项目代码 clone 下来。这里可以使用 actions/checkout 去做,当然,我们也可以自己写
克隆项目
我们先在脚本里面添加一个 step 叫 Clone repository
jobs:
Build-and-deploy:
# ... 省略
steps:
# ... 省略
- name: Clone repository
run: |
echo "clone repository"
我们这部需要做两件事
- clone 项目 (git clone https://github.com/xxx/yyy)
- 将项目目录设置为工作目录
Clone
我们可以直接把命令写死,比如
$ git clone https://github.com/wokeyi/github-actions-deploy-ssr
但是这样太不智能,看下文档可以看到。github actions 为我们提供了很多上下文,比如 github.repository
/ github.ref
等,那么我们就可以把命令改成
# 用 ${{ }} 的方式使用 github actions 变量
$ git clone https://github.com/${{ github.repository }}
将项目目录设置为工作目录
这一步我们拿到项目名称,因为 git clone 下来就是以项目名称命名,比如我们这个 demo 名称就是 github-actions-deploy-ssr
,但 github.repository
这个变量提供的为 wokeyi/github-actions-deploy-ssr
的字符串, 所以我们需要简单写个字符串操作的脚本
# 将 github.repository 用变量 repository_name 保存
$ REPO_NAME="${{ github.repository }}"
# 字符串操作,相当于 const ARRAY = REPO_NAME.split('/')
$ ARRAY=(${REPO_NAME//\// })
# 拿到数组第二项
$ DIR_NAME=${ARRAY[1]}
$ cd $DIR_NAME
这样子 DIR_NAME
就是项目的目录,后续操作都将在这里面操作
但是这里有个坑,我们在这一步 cd $DIR_NAME
进去了 clone 下来的项目目录,下一个 step 又回到了默认的目录,这里有两种解决方案
- 配置
working-directory
属性 (在 env 里面增加一个working_dir
变量,用echo "working_dir=$DIR_NAME" >> $GITHUB_ENV
来更新 这个变量,并把他设置在后续的 step 中) - 直接 git clone 到当前目录
很明显第二种方法比较简单,我们按照这个方法来改,只需一行代码
# 后面多了一个 `.`
$ git clone https://github.com/${{ github.repository }} .
切换到对应分支
因为 clone 下来时默认分支一般是 master
,但是我们可能是在其他分支触发的更新,所以直接在 master
分支上打包明显是不对的。所以这一步我们需要 checkout
到对应分支
增加一个 step 加 Check out branch
jobs:
Build-and-deploy:
# ... 省略
steps:
# ... 省略
- name: Check out branch
run: |
echo "check out branch"
这一步我们需要做这些事情:
- 拿到当前分支名
- 拿到触发这个任务的分支名 ( github context 里面提供了
github.ref
给我们使用) - 分支一样不做操作,不一样则切换到对应分支
直接上代码
# 当前触发的分支: refs/heads/master
GITHUB_REF="${{ github.ref }}"
# 相当于 const ARRAY = GITHUB_REF.split('/')
ARRAY=(${GITHUB_REF//\// })
BRANCH_NAME=${ARRAY[2]}
# 拿到当前的分支名
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
# 判断当前分支和触发分支是否一样
if [ $BRANCH_NAME != $CURRENT_BRANCH ]; then
echo "Start to fetch origin/$BRANCH_NAME"
git fetch origin $BRANCH_NAME
git checkout $BRANCH_NAME
else
echo "Current branch is $CURRENT_BRANCH."
fi
项目打包
上面的流程跑完后我们就拿到了要操作的代码,在打包这一步大部分都是 yarn
+ yarn build
。这个根据特定项目来,不做过多叙述
jobs:
Build-and-deploy:
# ... 省略
steps:
# ... 省略
- name: Clone repository
- name: Check out branch
- name: Build
run: |
# yarn
# yarn build
echo "Build"
ssh 配置
不管是客户端上传静态资源到 nginx 或者服务端应用的 docker-compose build
都需要 ssh,我们这一步来完成 ssh 配置
我们知道想要免密码通过 ssh 登录远程服务器的话,需要把本机的公钥放在服务器的 ~/.ssh/authorized_keys
中。这样子的话就有一个问题,我们不知道 github 触发 actions 的机器的公钥,就没办法配置在远程服务器中了。
这种情况我们有这么一个解决方案:
- 将本机的私钥提供给 GitHub actions,利用私钥动态创建对应的公钥(生成的公钥和本机的公钥是一模一样的)
- 将本机的公钥保存在服务器的
~/.ssh/authorized_keys
中即可
在 secrets 中保存 ssh 配置
进入代码仓库中的 Settings
-> Secrets
中,点击右上角 New repository secret
可以创建一个 secret
- 创建一个 Key 为
SSH_PRIVATE_KEY
,内容是本机私钥的 secret,本机私钥保存在~/.ssh/id_rsa
中 (还没有私钥的话可以通过执行ssh-keygen
创建一个新的的公私钥对) - 创建一个 Key 为
SSH_USERNAME
内容是连接远程服务器的用户名 - 创建一个 Key 为
SSH_HOST
内容是连接远程服务器的公网 IP
这样子我们就完成了 ssh 所需的环境配置,可以通过 \${\{ secrets.SSH_PRIVATE_KEY }}
在 github actions 中使用
动态创建公钥
有了私钥后,我们可以通过 ssh-keygen
来生成对应的公钥
SSHPATH="$HOME/.ssh"
# 写入私钥
echo "${{ secrets.SSH_PRIVATE_KEY }}" >> $SSHPATH/id_rsa
# 更新私钥文件权限
chmod 600 $SSHPATH/id_rsa
# 通过私钥生成公钥
ssh-keygen -y -t rsa -f $SSHPATH/id_rsa
这样子的话我们就创建了登录的公私钥,当然还要一些细节要处理,比如确认 .ssh
这个文件夹在不在。完整代码
jobs:
Build-and-deploy:
# ... 省略
steps:
# ... 省略
- name: Clone repository
- name: Check out branch
- name: Build
- name: Setup ssh
run: |
SSHPATH="$HOME/.ssh"
# 确认 .ssh 文件夹
if [ ! -d "$SSHPATH" ]
then
mkdir $SSHPATH
fi
if [ ! -f "$SSHPATH/known_hosts" ]
then
touch $SSHPATH/known_hosts
fi
# 更新权限
chmod 700 $SSHPATH
chmod 600 $SSHPATH/known_hosts
# 生成公钥
echo "${{ secrets.SSH_PRIVATE_KEY }}" >> $SSHPATH/id_rsa
chmod 600 $SSHPATH/id_rsa
ssh-keygen -y -t rsa -f $SSHPATH/id_rsa
上传静态资源到 nginx
这一步同样比较简单,直接通过 scp 即可,上代码
jobs:
Build-and-deploy:
# ... 省略
steps:
# ... 省略
- name: Clone repository
- name: Check out branch
- name: Build
- name: Setup ssh
- name: Upload static resources
run: |
scp -o StrictHostKeyChecking=no -r ./static ${{ secrets.SSH_USERNAME}}@${{ secrets.SSH_HOST }}:/dest
利用 ssh 操作远程服务器
在 ssr 应用中,由于需要服务端渲染需要跑一个 Node 服务,我们通常通过 docker 等来操作
jobs:
Build-and-deploy:
# ... 省略
steps:
# ... 省略
- name: Clone repository
- name: Check out branch
- name: Build
- name: Setup ssh
- name: Upload static resources
- name: Update server
run: |
scp -o StrictHostKeyChecking=no -r ./dist ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }}:/path/to/working-dir
echo "
cd /path/to/working-dir
# --no-cache 禁止了 docker 构建的缓存,但是会导致构建时间加长
docker-compose build --no-cache project
docker-compose up -d project
" | ssh -o StrictHostKeyChecking=no ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }}
完整代码可查看: https://github.com/wokeyi/github-actions-deploy-ssr/blob/master/.github/workflows/deploy.yml