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没变化

posted @ 2024-12-05 19:42  cccq  阅读(150)  评论(0)    收藏  举报