CI/CD
个人理解的 CI/CD 就是持续集成和持续部署, 持续集成就是代码提交后自动构建, 持续部署就是自动部署到测试环境, 测试环境通过后自动部署到生产环境.
为了简化流程, 此项目是直接将构建后的二进制文件部署到服务器, 并执行脚本运行
新建工作流
首先选择要部署的仓库, Actions -> New workflow
新建一个工作流
选择一个合适的模板文件, 这里我们选择 go
他会为这个工作流生成一个 .github/workflows/go.yml
文件, 我们填写里面的配置文件, 在右侧的 Marketplace 我们可以搜索合适的插件
最后我的配置如下:
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
# 当有 push 或者 pull request 到 main 分支时会触发任务
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# 选择 go 的版本
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.20"
# 构建
- name: Build
run: |
go mod tidy
mkdir tmp
go build -o ./cmd/user-api ./app/user/cmd/api/user.go
go build -o ./cmd/user-rpc ./app/user/cmd/rpc/user.go
tar -zcvf ./cmd.tar.gz ./cmd
# 部署到服务器
# 这里使用插件 ssh-scp-ssh-pipelines
# 详细说明见: https://github.com/marketplace/actions/ssh-scp-ssh-pipelines
- name: Deploy To Server
uses: cross-the-world/ssh-scp-ssh-pipelines@v1.1.4
with:
host: ${{ secrets.HOST }}
port: ${{ secrets.PORT }}
user: ${{ secrets.USERNAME }}
pass: ${{ secrets.PASSWORD }}
first_ssh: |
cd /home/guochenxu/gtodolist/go
if [ -f "./cmd.tar.gz" ]; then rm ./cmd.tar.gz; fi
scp: |
'./cmd.tar.gz' => /home/guochenxu/gtodolist/go/
last_ssh: |
cd /home/guochenxu/gtodolist/go
./start.sh
上面配置文件中的 ${{ secrets.HOST }}
是仓库的环境变量, start.sh
是服务器上的启动脚本
配置仓库的环境变量
在该仓库里面选择 Settings -> Secret and variables -> Actions -> New repository secret
进行配置, 第一步的配置文件中的名字要和这里的环境变量的名字相同
编写服务器上的启动脚本
这个可以根据自己的需求自己编写
# 杀死进程
function kill_process(){
for i do
pid=`ps -ef | grep $i | grep -v grep | awk '{print $2}'`
if [ -n "$pid" ]; then
kill -9 $pid;
fi
done
}
kill_process user-rpc user-api
# 解压文件
if [ -f "./cmd.tar.gz" ]; then
if [ -d "./cmd" ]; then
rm -r ./cmd
fi
tar -zxvf ./cmd.tar.gz
#chmod -R 755 ./cmd
rm ./cmd.tar.gz
fi
# 启动服务
function start_service(){
cmd=$1
etc=$2
logs=$3
for file in $(ls $cmd)
do
bin=$cmd/$file
config=$etc/${file}.yaml
log=$logs/${file}.log
nohup $bin -f $config > $log &
done
}
start_service ./cmd ./etc ./logs
# 退出
exit 0
查看效果
到此就部署完毕, 现在只要提交代码就会自动构建, 然后将二进制文件部署到服务器上, 并运行启动脚本
服务部署成功并且正常运行
更新
因为编译后的大文件在往服务器传输的时候实在是太慢了 (平均花费半个多小时, 并且期间很容易出现网络问题), 所以改成直接上传源代码, 将编译的工作放到服务器上, 效率提高了将近 90%
事实上, 应当是把项目打包成镜像, 然后在服务器上拉去镜像运行的,
但是菜鸡不会 docker 😭
工作流更新:
换了两个新的插件, 一个传输文件 (scp 或者 rsync), 一个 ssh 登录执行命令
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
build-and-deploy:
# 设置容器环境
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# # 设置 GO 版本
# - name: Set up Go
# uses: actions/setup-go@v4
# with:
# go-version: "1.20"
# # 编译
# - name: Build
# run: |
# go mod tidy
# mkdir tmp
# go build -o ./cmd/user-api ./app/user/cmd/api/user.go
# go build -o ./cmd/user-rpc ./app/user/cmd/rpc/user.go
# go build -o ./cmd/task-api ./app/task/cmd/api/task.go
# go build -o ./cmd/task-rpc ./app/task/cmd/rpc/task.go
# tar -zcvf ./cmd.tar.gz ./cmd
# 两种传输方式, 我的 scp 不知道为什么一直出问题, 所以就用了 rsync
# # 使用 scp 把文件传输到服务器上
# - name: Transfer to server
# uses: appleboy/scp-action@v0.1.4
# with:
# host: ${{ secrets.HOST }}
# username: ${{ secrets.USERNAME }}
# password: ${{ secrets.PASSWORD }}
# port: ${{ secrets.PORT }}
# source: "./cmd.tar.gz"
# target: /home/guochenxu/gtodolist/go/
# 使用 rsync 把文件传输到服务器上
- name: Transfer to server
uses: up9cloud/action-rsync@master
env:
HOST: ${{ secrets.HOST }}
USER: ${{ secrets.USERNAME }}
PORT: ${{ secrets.PORT }}
PASSWORD: ${{ secrets.PASSWORD }}
TARGET: /home/guochenxu/gtodolist/go/gtodolist
SOURCE: ./
ARGS: -avuz --progress --delete --exclude=./.git/
PRE_SCRIPT: |
echo start at:
date -u
POST_SCRIPT: "echo done at: && date -u"
# 远程执行命令
- name: Run Service On Server
uses: fifsky/ssh-action@master
with:
host: ${{ secrets.HOST }}
port: ${{ secrets.PORT }}
user: ${{ secrets.USERNAME }}
pass: ${{ secrets.PASSWORD }}
command: |
cd /home/guochenxu/gtodolist/go
./start.sh
脚本更新:
之前的脚本挺多有问题的地方的, 这里做了两个更新:
- 把 rpc 服务和 api 服务分开放, 并且前后间隔一段时间启动, 防止出现找不到服务的问题;
- 把
nohup
的错误输出重定向到标准输出的引用, 即2>&1
(也可以选择直接丢弃输出,2>/dev/null
, 将输出重定向到黑洞文件), 防止远程 ssh 无法正常关闭.
# 杀死进程
function kill_process(){
for i do
pid=`ps -ef | grep $i | grep -v grep | awk '{print $2}'`
if [ -n "$pid" ]; then
kill -9 $pid;
fi
done
}
kill_process user-api user-rpc task-api task-rpc
# 解压文件
#if [ -f "./cmd.tar.gz" ]; then
# if [ -d "./cmd" ]; then
# rm -r ./cmd
# fi
# tar -zxvf ./cmd.tar.gz
# #chmod -R 755 ./cmd
# rm ./cmd.tar.gz
#fi
# 编译
if [ -d "./cmd" ];then
rm -rf ./cmd
fi
mkdir ./cmd
mkdir ./cmd/api
mkdir ./cmd/rpc
if [ -d "./gtodolist" ];then
cd ./gtodolist
go mod tidy
go build -o ../cmd/api/user-api ./app/user/cmd/api/user.go
go build -o ../cmd/rpc/user-rpc ./app/user/cmd/rpc/user.go
go build -o ../cmd/api/task-api ./app/task/cmd/api/task.go
go build -o ../cmd/rpc/task-rpc ./app/task/cmd/rpc/task.go
cd ..
rm -rf ./gtodolist
else
echo "编译失败, 没有找到代码目录"
exit 1
fi
echo "===== 编译完成 ====="
# 启动服务
function start_service(){
rpc=$1
api=$2
etc=$3
logs=$4
# 先启动 rpc 服务
for file in $(ls $rpc)
do
bin=$rpc/$file
config=$etc/${file}.yaml
log=$logs/${file}.log
nohup $bin -f $config > $log 2>&1 &
done
# 睡眠 7s, 要不然 api 服务启动但是 rpc 没有启动就会报错
sleep 7s
# 后启动 api
for file in $(ls $api)
do
bin=$api/$file
config=$etc/${file}.yaml
log=$logs/${file}.log
nohup $bin -f $config > $log 2>&1 &
done
}
start_service ./cmd/rpc ./cmd/api ./etc ./logs
echo "===== 启动完成 ====="
# 退出
echo "===== 部署成功 ! ====="
exit 0
更新后的效果
nginx 配置
最后补充一下 nginx 怎么配置后端端口转发的配置怎么写。
为了方便文件管理,我在/etc/nginx/conf.d
目录下创建了todolist.conf
文件,该文件就是 gtodolist 项目的 nginx 配置文件,文件内容如下:
# todolist
server {
listen 80;
listen [::]:80;
server_name todolist.chenxutalk.top;
#把http的域名请求转成https
return 301 https://$server_name$request_uri;
# rewrite ^(.*) https://$server_name$1 permanent; #自动从http跳转到https
root /home/guochenxu/gtodolist/web;
include /etc/nginx/default.d/*.conf;
# 前端配置
location / {
root /home/guochenxu/gtodolist/web;
try_files $uri $uri/ /index.html; #解决页面刷新404问题
index index.html index.htm;
}
# 后端匹配路径前端分别进行端口转发
location /api/v1/user {
proxy_pass http://localhost:22301;
}
location /api/v1/task {
proxy_pass http://localhost:22302;
}
error_page 404 /404.html;
location = /40x.html {
root /home/guochenxu/gtodolist/web;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
server {
listen 443 ssl;
server_name todolist.chenxutalk.top;
root /home/guochenxu/gtodolist/web;
ssl_certificate /etc/nginx/cert.pem;
ssl_certificate_key /etc/nginx/key.pem;
ssl_session_timeout 5m;
#请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
error_page 404 /404.html;
location / {
root /home/guochenxu/gtodolist/web;
try_files $uri $uri/ /index.html; #解决页面刷新404问题
index index.html index.htm;
}
location /api/v1/user {
proxy_pass http://localhost:22301;
}
location /api/v1/task {
proxy_pass http://localhost:22302;
}
include /etc/nginx/default.d/*.conf;
}