离开蓝鲨之后,每当我部署项目的时候,无不怀念蓝鲨的 cicd,果然是由奢入俭难,有了 cicd 就懒得再手动部署了。
关于 cicd
之前在蓝鲨时,我们使用 Gitea 搭建的内网代码仓库,使用 Drone 搭配 Gitea 构建 cicd。如果按这套来折腾应该是比较熟悉的,但是首先我个人没那么多服务器,也没打算搭建一个私人的仓库,其次 Drone 的文档十分简陋,资料少得可怜,所以这套方案被我给 Pass 掉了。因为我个人平常都是用 GitHub 来作为远程仓库,所以我想有没有什么 cicd 工具可以和 GitHub 完美结合呢?官方的 GitHub Actions 可能就是最好的答案了。
Github Actions 是 GitHub 官方提供的一个 cicd 服务,它配置简单,只要在项目中添加 .github/workflows/*.yml 的配置文件,之后的每次 push GitHub 都会自动执行 cicd 服务。而且 GitHub Actions 文档丰富,既有官方文档,网络上还有很多别人的实践,有足够多的石头可以摸着过河。
Github Actions
我使用 GitHub Actions 的工作流程是这样的,某个分支的某个事件(如代码提交)触发了 cicd,cicd 可以通过 ssh 连接到个人的服务器上,将最新的代码拉到服务器本地,构建最新的 Docker 镜像,push 到镜像仓库中,再拉取最新的镜像部署到 Docker,完成。
所以首先要让 cicd 能访问服务器,在服务器生成公钥和私钥:
ssh-keygen -t rsa -C "limyel@outlook.com"
将公钥写入 authorized_keys:
cat id_rsa.pub >> authorized_keys
私钥改如何配置呢?如果直接将私钥写入 yml 配置文件,岂不是很危险?GitHub 早就想到了,一些私密的配置可以写入项目的 secrets,cicd 运行时会将指定位置的值改为 secrets 中配置的值。
上图是我用到的一些配置,从上到下分别是服务器私钥、Docker 密码、Docker 仓库镜像名称、Docker 用户名(这里我使用的是阿里云的 Docker 镜像仓库,所以对应的是阿里云仓库的用户名密码)、服务器公网IP、服务器登录用户名。
接下来在项目的根目录新建一个目录 .github/workflows,在目录中新建一个文件 deploy.yml,写入 cicd 的配置信息:
name: Deploy site files
on:
push:
branches:
- master # 只在master上push触发部署
paths-ignore: # 下列文件的变更不触发部署,可以自行添加
- README.md
- LICENSE
jobs:
deploy:
runs-on: ubuntu-latest # 使用ubuntu系统镜像运行自动化脚本
timeout-minutes: 20
steps: # 自动化步骤
- uses: actions/checkout@v2 # 第一步,下载代码仓库
- name: Deploy to Server # 第二步,rsync推文件
uses: AEnterprise/rsync-deploy@v1.0 # 使用别人包装好的步骤镜像
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} # 引用配置,SSH私钥
ARGS: -avz --delete --exclude='*.pyc' # rsync参数,排除.pyc文件
SERVER_PORT: "22" # SSH端口
FOLDER: ./ # 要推送的文件夹,路径相对于代码仓库的根目录
SERVER_IP: ${{ secrets.SSH_HOST }} # 引用配置,服务器的host名(IP或者域名domain.com)
USERNAME: ${{ secrets.SSH_USERNAME }} # 引用配置,服务器登录名
SERVER_DESTINATION: /home/ubuntu/limyel-blog # 部署到目标文件夹
- name: Restart server # 第三步,重启服务
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }} # 下面三个配置与上一步类似
username: ${{ secrets.SSH_USERNAME }} # 登录服务器的用户名
key: ${{ secrets.DEPLOY_KEY }} # 服务器配置的私钥
# 脚本 推送新构建的镜像到仓库
script: |
cp /home/ubuntu/private/limyel-blog-private.py /home/ubuntu/limyel-blog/blog/config/private.py
cd /home/ubuntu/limyel-blog
docker build -t ${{ secrets.DOCKER_REPOSITORY }}:latest .
docker login --username ${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }} registry.cn-hangzhou.aliyuncs.com
docker push ${{ secrets.DOCKER_REPOSITORY }}:latest
run:
needs: [deploy]
name: Pull And Run Docker
runs-on: ubuntu-latest
steps:
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }} # 下面三个配置与上一步类似
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.DEPLOY_KEY }}
# 脚本 拉取最新的镜像并运行
script: |
docker stop limyel-blog
docker rm limyel-blog
docker image rm ${{ secrets.DOCKER_REPOSITORY }}:latest
docker login --username ${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }} registry.cn-hangzhou.aliyuncs.com
docker pull ${{ secrets.DOCKER_REPOSITORY }}:latest
docker run -d -p 127.0.0.1:7382:7382 --name limyel-blog ${{ secrets.DOCKER_REPOSITORY }}:latest
不同的项目只需根据注释的内容修改配置文件即可,将代码 push 到 GitHub,GitHub Actions 就会自动执行脚本了。
下面是前端的配置:
name: Deploy site files
on:
push:
branches:
- master # 只在master上push触发部署
paths-ignore: # 下列文件的变更不触发部署,可以自行添加
- README.md
- LICENSE
jobs:
deploy:
runs-on: ubuntu-latest # 使用ubuntu系统镜像运行自动化脚本
steps: # 自动化步骤
- uses: actions/checkout@v2 # 第一步,下载代码仓库
- name: Deploy to Server # 第二步,rsync推文件
uses: AEnterprise/rsync-deploy@v1.0 # 使用别人包装好的步骤镜像
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} # 引用配置,SSH私钥
ARGS: -avz --delete --exclude='*.pyc' # rsync参数,排除.pyc文件
SERVER_PORT: "22" # SSH端口
FOLDER: ./ # 要推送的文件夹,路径相对于代码仓库的根目录
SERVER_IP: ${{ secrets.SSH_HOST }} # 引用配置,服务器的host名(IP或者域名domain.com)
USERNAME: ${{ secrets.SSH_USERNAME }} # 引用配置,服务器登录名
SERVER_DESTINATION: /home/ubuntu/blog/frontend # 部署到目标文件夹
- name: Restart server # 第三步,重启服务
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }} # 下面三个配置与上一步类似
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.DEPLOY_KEY }}
# 重启的脚本,根据自身情况做相应改动,一般要做的是migrate数据库以及重启服务器
script: |
cp /home/ubuntu/private/blog-frontend-private.js /home/ubuntu/blog/frontend/src/private.js
cd /home/ubuntu/blog/frontend
docker build -t ${{ secrets.DOCKER_REPOSITORY }} .
docker login --username ${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }} registry.cn-hangzhou.aliyuncs.com
docker push ${{ secrets.DOCKER_REPOSITORY }}:latest
run:
needs: [ deploy ]
name: Pull And Run Docker
runs-on: ubuntu-latest
steps:
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }} # 下面三个配置与上一步类似
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
docker stop blog-frontend
docker container rm blog-frontend
docker image rm ${{ secrets.DOCKER_REPOSITORY }}:latest -f | true
docker login --username ${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }} registry.cn-hangzhou.aliyuncs.com
docker pull ${{ secrets.DOCKER_REPOSITORY }}:latest
docker run -d -p 127.0.0.1:7382:80 --name blog-frontend ${{ secrets.DOCKER_REPOSITORY }}
\