大家好啊,我是独立开发豆小匠。
先说一下背景~
我的小程序:豆流便签,目前使用云托管部署后端服务,使用轻量级服务器部署数据库和一些中间件。
因此服务器成本:云托管 + 云服务器
云托管每周花费5元,一个月就是50,一年就是500啊,所以这期准备把云托管优化掉!
1. 需求分析
使用云托管的好处是很明显的,可以推送代码后自动化部署。如果转移到云服务器,怎么延续自动化部署的开发体验咧,主要的需求如下:
- 自动化部署test分支
- 自动化部署master分支
- 部署期间服务可用
其中第2、3点都是云托管有的功能,第1点云托管也可以做到。但是,得加钱!也就是多开一个服务。
2. 实现思路
实现主要依赖于GitHub提供的Action workflow工作流和Nginx的自动分发、故障转移。
3. 具体实现
3.1. GitHub Action
简单介绍下Github Action,它允许通过配置文件来自动构建 、测试 和部署项目等。
我们本次编写deploy.yml
文件定义一个自动化部署的工作流,实现的效果:推送master/test分支到GitHub后,连接云服务器,执行部署脚本。
首先在项目根目录创建文件夹.github/workflows
,然后新建文件deploy.yml
,文件内容如下
bash
name: Build and Deploy
on:
push:
branches: [master] # 触发任务的分支
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Build and run Go program
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST_MILK }} # 服务器地址 @1
username: ${{ secrets.HOST_ROLE }} # 登陆用户名 @2
key: ${{ secrets.HOST_KEY }} # 服务器私钥 @3
port: 22
script: |
cd /var/you_code_dir && # 更换成你服务器放代码的地方 @4
git checkout . && git pull &&
if [ $? -ne 0 ]; then
echo "拉取失败,脚本终止"
exit 1
fi &&
echo "pull success"
chmod +x ./deploy.sh && # 给部署脚本加权限
./deploy.sh
echo "deploy success"
其中,Github Action完成的事情:
- 连接云服务器,拉取最新代码
- 触发部署脚本
deploy.sh
其中上面@1、2、3、4都是需要根据我们实际情况修改的
@1:服务器地址,eg:12.12.12.12
@2:用户名,eg:root
@3 服务器私钥,参考下面的配置步骤
bash
# 生成密钥
mkdir -p ~/.ssh && cd ~/.ssh
ssh-keygen
# 公钥复制到authorized_keys文件
cat id_rsa.pub >> authorized_keys
# 加权限
chmod -R 700 ~/.ssh
chmod -R 640 authorized_keys
# 复制私钥
cat id_rsa
# 配置到GitHub
@4:需要先把你的代码拉到服务器的目录(如/var/beanflow-test/),然后切换到对应分支(如master/test) ,这样workflow上直接git pull
就能更新到最新的代码。
3.2. deploy脚本
Github Action只是解决了推送代码,怎么通知开始部署的问题。真正的版本发布,需要在shell脚本里执行!
我们把部署脚本文件放在项目根目录,文件名叫deploy.sh
,这里的示例脚本是支持使用Dockerfile打包项目,然后启动容器的。
你可以根据你的项目情况,编写对应的Dockerfile文件,或者使用其他方式启动项目,如PM2、supervisorctl 等。
在项目根目录编写deploy.sh
文件,内容如下:
bash
#!/bin/bash
# 定义要部署的端口
ports=(81 82)
# 定义镜像名称
image_name="beanflow"
# 构建 Docker 镜像
docker build -t $image_name .
# 循环处理每个要部署的端口
for port in "${ports[@]}"
do
# 检查是否存在对应的容器,存在则删除
container_id=$(docker ps -aq --filter "name=beanflow-$port")
if [ -n "$container_id" ]; then
echo "删除端口 $port 对应的容器:beanflow-$port"
docker stop $container_id
docker rm $container_id
fi
# 使用docker命令运行容器,每个端口对应一个容器实例
echo "部署端口 $port"
docker run -d -p $port:80 --name beanflow-$port $image_name --restart=always
done
这个脚本会在服务器上的代码文件目录打包一个beanflow
(豆流)的镜像,然后串行重建81、82端口的容器。
- 部署两个容器的目的:因为是串行部署,部署期间,至少一个容器可以提供服务,利用nginx,可以对外提供可用的服务。
- 思考,新服务部署完成,可以只留一个容器服务吗?
3.3. Nginx配置
现在还需要解决的问题:
- 流量分发问题,前端需要一个固定的请求地址/端口。
- 需要区分不可用的服务,自动转发到可用的服务。
在服务器安装Nginx后,在conf.d
目录编写流量转发规则和负载均衡配置,配置文件内容如下:
ini
upstream test {
# 失败一次后,服务会被标记为不健康,流量转发到其他服务器
server 127.0.0.1:91 max_fails=1 fail_timeout=10s;
server 127.0.0.1:92 max_fails=1 fail_timeout=10s;
}
upstream live {
server 127.0.0.1:81 max_fails=1 fail_timeout=10s;
server 127.0.0.1:82 max_fails=1 fail_timeout=10s;
}
server {
# test服务使用90端口
listen 90;
server_name beanflow.top;
location / {
proxy_pass http://test;
}
}
server {
# live服务使用80端口
listen 80;
server_name beanflow.top;
location / {
proxy_pass http://live;
}
}
配置后,需要测试和重启nginx
nginx -t
systemctl reload nginx
至此,只要前端请求服务器的80端口,流量就会打到部署了master分支代码的服务;请求90端口,流量就会打到部署了test分支的服务!
这期就喵到这!收!
哎,还留了个尾巴,nginx可以使用第三方扩展功能进行主动的健康检查。这样的话,新服务部署后,其实可以只保留一个容器就行!收!