Github Actions自动部署golang项目到服务器
非docker部署项目
在项目目录 /.github/.workflows/go.yml 配置yml自动部署,当push到github时,自动构建项目,并将构建的可运行程序通过ssh上传到自己的服务器。
name: Go
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
# 设置 Go 环境
- name: 设置 Go
uses: actions/setup-go@v4
with:
go-version: '1.23.3'
# 构建项目,启用静态编译以确保在 CentOS 上运行
- name: 构建
run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o youappname -ldflags="-s -w" .
# 停止守护程序和主程序
- name: 停止程序
uses: appleboy/ssh-action@master
with:
host: {服务器ip}
username: {服务器账号}
password: {服务器密码}
script: |
sh /www/wwwroot/yourappname/stop.sh || true
# 上传文件到服务器
- name: 上传构建文件到服务器
uses: appleboy/scp-action@master
with:
host: {服务器ip}
username: {服务器账号}
password: {服务器密码}
source: yourappname
overwrite: true
target: /www/wwwroot/yourappname
# 远程运行脚本,启动守护程序,必须进入到项目目录下否则.env文件找不到
- name: 启动守护程序
uses: appleboy/ssh-action@master
with:
host: {服务器ip}
username: {服务器账号}
password: {服务器密码}
script: |
cd /www/wwwroot/yourappname
nohup sh start.sh > /dev/null 2>&1 &
踩坑点:
1、CGO_ENABLED=0 没有配置静态编译可能导致服务器中程序启动失败。
2、target: /www/wwwroot/yourappname服务器目标路径,希望将文件放到哪个路径下。
3、一开始使用的是sh /www/wwwroot/yourappname/start.sh
程序一直启动失败但在服务器中运行可以成功运行。
因为执行sh脚本时程序找不到.env配置文件
执行脚本时是根据当前所在目录找.env文件,举例:
目前在/etc目录下执行/www/wwwroot/yourappname/start.sh,那么获取/etc/.env,但实际.env文件在/www/wwwroot/yourappname目录中,所以导致程序获取不到.env文件启动失败
4、当程序未停止时,无法将文件传输到服务器,导致Github Actions中一直在加载,解决方法是先执行stop.sh再传输文件最后start.sh启动项目
5、守护进程导致Github Actions流程无法结束,将start.sh挂入后台运行不会阻塞actions
sh start.sh
=>
nohup sh start.sh > /dev/null 2>&1 &
stop.sh
将运行守护程序和主程序关闭,否则文件无法上传。
pkill -f "start.sh"
pkill -f "/www/wwwroot/yourappname/yourappname"
start.sh
运行守护程序,每120秒检查程序是否在运行,没有则启动。
# 设置程序路径
PROGRAM="/www/wwwroot/yourappname/yourappname"
LOG_FILE="/www/wwwroot/yourappname/yourappname.log"
# 确保程序可执行
chmod +x "$PROGRAM"
# 启动程序函数
start_program() {
echo "$(date +'%Y-%m-%d %H:%M:%S') - 启动程序..." >> "$LOG_FILE"
nohup "$PROGRAM" >> "$LOG_FILE" 2>&1 &
}
# 检查并启动程序函数
check_and_start() {
if ! pgrep -f "$PROGRAM" > /dev/null; then
echo "$(date +'%Y-%m-%d %H:%M:%S') - 程序未运行,启动中..." >> "$LOG_FILE"
start_program
else
echo "$(date +'%Y-%m-%d %H:%M:%S') - 程序正在运行..." >> "$LOG_FILE"
fi
}
# 运行守护
while true; do
sleep 120
check_and_start
done
可改进的点:目前停止服务和启动服务中间间隔了一个上传,这会导致中间有较长时间的服务处于不可用状态。
-----------2024.12.04更新 完成可改进的点----------
将构建和部署拆分为两个步骤,平滑更新版本。
构建的文件名带当前时间戳防止与服务器中的文件名重复。
将隐私信息存入github中加密。
name: Go
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
# 检出代码
- uses: actions/checkout@v4
# 设置 Go 环境
- name: 设置 Go
uses: actions/setup-go@v4
with:
go-version: '1.23.3'
# 构建项目
- name: 构建项目
run: |
DEPLOY_TIME=$(date +"%Y%m%d_%H%M%S")
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o yourappname_${DEPLOY_TIME} -ldflags="-s -w" .
# 上传文件到服务器
- name: 上传文件
uses: appleboy/scp-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
password: ${{ secrets.PASSWORD }}
source: yourappname_*
target: /www/wwwroot/yourappname/server/
overwrite: true
deploy:
needs: build
runs-on: ubuntu-latest
steps:
# 停止服务
- name: 停止服务
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
password: ${{ secrets.PASSWORD }}
script: |
sh /www/wwwroot/yourappname/server/stop.sh || true
# 启动服务
- name: 启动服务
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
password: ${{ secrets.PASSWORD }}
script: |
cd /www/wwwroot/yourappname/server
nohup sh start.sh > /dev/null 2>&1 &
踩坑点:停止服务和启动服务写在同一个步骤中会失败,不清楚原因。
deploy:
needs: build
runs-on: ubuntu-latest
steps:
# 停止并启动服务
- name: 停止并启动服务
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
password: ${{ secrets.PASSWORD }}
script: |
cd /www/wwwroot/yourappname/server
sh stop.sh || true
nohup sh start.sh > /dev/null 2>&1 &
start.sh
获取最新符合规则的文件作为程序启动,删除第二个(旧版本)
如果第一个和第二个文件名一样表示不存在实际的第二个文件,则不删除否则会将唯一的文件删除。
也可以再次改进保留一次旧版本的文件作为备份。
# 设置程序路径和日志路径
PROGRAM_DIR="/www/wwwroot/yourappname/server/"
LOG_FILE="/www/wwwroot/yourappname/server/logs/yourappname.log"
# 获取最新的程序文件
get_latest_program() {
# 查找匹配的文件并获取最新的文件
first_file=$(ls -t "$PROGRAM_DIR"yourappname_* 2>/dev/null | head -n 1)
second_file=$(ls -t "$PROGRAM_DIR"yourappname_* 2>/dev/null | head -n 2 | tail -n 1)
if [[ -z "$first_file" ]]; then
echo "$(date +'%Y-%m-%d %H:%M:%S') - 未找到符合规则的文件。" >> "$LOG_FILE"
exit 1
fi
# 如果第二个文件存在,删除它
if [[ -n "$second_file" ]] && [[ "$first_file" != "$second_file" ]]; then
rm -f "$second_file"
echo "$(date +'%Y-%m-%d %H:%M:%S') - 删除了第二个文件: $second_file" >> "$LOG_FILE"
fi
echo "$first_file"
}
# 启动程序函数
start_program() {
local program="$1"
echo "$(date +'%Y-%m-%d %H:%M:%S') - 启动程序: $program ..." >> "$LOG_FILE"
nohup "$program" >> "$LOG_FILE" 2>&1 &
}
# 检查并启动程序函数
check_and_start() {
local program="$1"
if ! pgrep -f "$program" > /dev/null; then
echo "$(date +'%Y-%m-%d %H:%M:%S') - 程序未运行,启动中: $program ..." >> "$LOG_FILE"
start_program "$program"
else
echo "$(date +'%Y-%m-%d %H:%M:%S') - 程序正在运行: $program ..." >> "$LOG_FILE"
fi
}
# 主逻辑
first_file=$(get_latest_program) # 获取最新文件
# 确保程序可执行
chmod +x "$first_file"
start_program "$first_file" # 初次启动程序
while true; do
sleep 120
check_and_start "$first_file"
done
stop.sh没变化

浙公网安备 33010602011771号