返回首页

流水线自动部署

布莱克2026-08-17 22:29已编辑
Tip:文章封面与内容无关,作者旅游时拍摄,因为没什么值得把四季都错过!

懒惰是第一生产力,本篇文章关于项目自动化部署,告别手动打包,上传至终端等等操作


作为一名前端开发者,你是否也经历过这样的场景:

  1. 本地开发完成,npm run build 打包
  2. 打开终端,scp -r ./.output root@xxx:/www
  3. 登录服务器,pm2 restart my-project
  4. 刷新页面,确认更新

每次上线都要重复这"四大步",虽然熟练了,但总归是机械劳动。如果一天上线好几次,时间就被这些重复操作吃掉了。


因为代码存储在Github,所以使用Github Actions实现自动部署

工具用途
GitHub ActionsCI/CD 自动化平台
Nuxt 3前端框架
阿里云 ECS服务器运行环境
PM2Node.js 进程管理
SSH/SCP远程连接与文件传输


什么是 GitHub Actions?

简单说,GitHub Actions 是 GitHub 内置的 CI/CD 工具。当你的代码仓库发生特定事件(如 push、PR 合并)时,它会在云端的虚拟机上自动执行你预设的任务。

你可以把它理解为一个"机器人助手":

  • 你告诉它:"每当 main 分支有新代码,就帮我做这几件事"
  • 它就会:拉代码 → 安装依赖 → 构建 → 上传服务器 → 重启服务

整个过程完全自动化,无需人工干预。


关于服务器的某些配置在此就不再赘述,安装pm2,创建项目目录等等


配置 GitHub Secrets

为了避免将服务器密码等敏感信息写在代码中,将它们存储在 GitHub Secrets 中。

操作步骤

  1. 进入 GitHub 仓库 → Settings → Secrets and variables → Actions
  2. 点击 New repository secret,依次添加:
Secret Name示例值说明
HOST1.2.3.4服务器公网 IP
USERNAMErootSSH 登录用户名
PASSWORD********SSH 登录密码


编写 GitHub Actions 工作流

在项目根目录下创建 .github/workflows/deploy.yml:

该配置以nuxt技术栈项目为例,不同技术栈会有些许不同

# ============================================
# GitHub Actions 工作流配置文件
# 作用:当代码推送到 main 分支时,自动构建并部署 Nuxt 项目到阿里云 ECS
# ============================================

# 工作流名称(显示在 GitHub Actions 页面)
name: Deploy Nuxt to Aliyun ECS

# ============================================
# 触发条件配置
# ============================================
on:
  push:
    branches: [ main ]  # 只有推送到 main 分支时才触发

# ============================================
# 工作任务定义
# ============================================
jobs:
  # 任务名称:构建并部署
  build-and-deploy:
    # 运行环境:使用 GitHub 提供的 Ubuntu 虚拟机
    runs-on: ubuntu-latest
    
    # 指定环境(用于读取该环境下的 Secrets)
    environment: prod
    
    # ============================================
    # 执行步骤(按顺序从上到下执行)
    # ============================================
    steps:
      # ---------- 步骤 1:拉取代码 ----------
      - name: Checkout code
        uses: actions/checkout@v4
        # 作用:将 GitHub 仓库的代码下载到虚拟机上
        # @v4 是版本号,表示使用第 4 个大版本

      # ---------- 步骤 2:安装 Node.js ----------
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'  # 指定 Node.js 版本(改成你项目实际使用的版本)
          cache: 'npm'        # 缓存 node_modules,加速后续构建
        # 作用:在虚拟机上安装 Node.js 环境
        # 为什么需要:虚拟机是干净的,没有 Node.js

      # ---------- 步骤 3:安装项目依赖 ----------
      - name: Install dependencies
        run: npm ci
        # 作用:安装 package.json 中声明的所有依赖
        # npm ci 特点:
        #   - 比 npm install 更快
        #   - 严格按照 package-lock.json 安装,保证版本一致性
        #   - 专门为 CI/CD 环境设计
        # 注意:这只是在 GitHub 虚拟机上安装依赖用于构建!
        #       服务器上不需要再安装,因为 .output 已自包含

      # ---------- 步骤 4:构建 Nuxt 项目 ----------
      - name: Build application
        run: npm run build
        # 作用:执行 package.json 中的 build 命令
        # 对于 Nuxt 3:生成 .output 目录(自包含的构建产物)
        # 对于 Nuxt 2:生成 .nuxt 目录
        # 
        # ⚠️ 重要:Nuxt 3 的 .output 目录特点:
        #   - 包含了所有编译后的代码
        #   - 包含了运行时需要的所有依赖
        #   - 是"自包含"的,不需要在服务器上再执行 npm install
        #   - 直接可以用 node .output/server/index.mjs 启动

      # ---------- 步骤 5:打包构建产物 ----------
      - name: Package build artifacts
        run: tar -czf release.tgz .output
        # 作用:将 .output 文件夹压缩成 .tgz 文件
        # 为什么压缩:
        #   - 单个文件传输比成千上万个小文件快得多
        #   - 减少网络传输时间
        # 参数说明:
        #   - c:创建压缩包 (create)
        #   - z:使用 gzip 压缩
        #   - f:指定文件名 (file)
        # 
        # ⚠️ 注意:只打包 .output,不打包 node_modules
        # 因为 .output 已经自包含,不需要服务器端安装依赖

      # ---------- 步骤 6:部署到阿里云 ECS ----------
      - name: Deploy to Aliyun ECS
        # 从 GitHub Secrets 中读取服务器连接信息
        env:
          HOST: ${{ secrets.HOST }}          # 服务器公网 IP
          USERNAME: ${{ secrets.USERNAME }}  # SSH 登录用户名
          PASSWORD: ${{ secrets.PASSWORD }}  # SSH 登录密码
        run: |
          # --------------------------------------------
          # 6.1 安装 sshpass 工具
          # --------------------------------------------
          sudo apt-get update
          sudo apt-get install -y sshpass
          # sshpass 作用:在命令行中自动输入 SSH 密码
          # 为什么需要:scp 和 ssh 命令本身不支持 -p 密码参数
          # 没有 sshpass 的话,每次都需要手动输入密码
          # 在 CI/CD 中无法手动输入,所以需要自动认证

          # --------------------------------------------
          # 6.2 上传压缩包到服务器
          # --------------------------------------------
          echo "=== 1. 上传压缩包到服务器 ==="
          sshpass -p "$PASSWORD" scp \
            -o StrictHostKeyChecking=no \
            release.tgz $USERNAME@$HOST:/nuxt/project/
          # 命令拆解:
          #   sshpass -p "$PASSWORD"   :用密码自动认证
          #   scp                       :Secure Copy,基于 SSH 的文件传输
          #   -o StrictHostKeyChecking=no:跳过"是否信任此主机"的询问
          #   release.tgz               :要上传的本地文件
          #   root@1.2.3.4:/nuxt/project/:目标位置
          # 
          # ⚠️ 路径说明:
          #   /nuxt/project/ 是你的项目部署目录
          #   如果路径不同,修改成你服务器上的实际路径

          # --------------------------------------------
          # 6.3 在服务器上执行部署命令
          # --------------------------------------------
          echo "=== 2. 执行部署命令 ==="
          sshpass -p "$PASSWORD" ssh \
            -o StrictHostKeyChecking=no \
            $USERNAME@$HOST << 'EOF'
            # 上面的命令通过 SSH 连接到服务器
            # << 'EOF' 表示之后的内容都在服务器上执行,直到遇到 EOF

            # ---------- 错误处理 ----------
            set -e
            # 作用:任何命令失败(返回非 0 值),脚本立即退出
            # 防止"部分失败"导致部署不完整

            # ---------- 进入部署目录 ----------
            cd /nuxt/project

            # 切换到项目部署目录
            # ⚠️ 如果路径不同,请修改

            # ---------- 显示当前状态 ----------
            echo "📂 当前目录: $(pwd)"
            echo "📦 检查压缩包:"
            ls -lh release.tgz
            # 确认压缩包已成功上传

            # ---------- 备份旧版本 ----------
            echo "📦 备份旧版本..."
            if [ -d ".output" ]; then
              mv .output .output.backup.$(date +%Y%m%d_%H%M%S)
            fi
            # 作用:如果存在旧版本,先备份
            # 为什么备份:如果新版本有问题,可以快速回滚
            # 备份命名:.output.backup.20260817_120000(带时间戳)

            # ---------- 解压新版本 ----------
            echo "📦 解压新版本..."
            tar -xzf release.tgz
            # 解压压缩包,生成 .output 目录
            # 
            # ⚠️ Nuxt 3 特别说明:
            #   .output 目录是"自包含"的
            #   不需要执行 npm install
            #   不需要执行 npm run build
            #   直接就可以运行

            # ---------- 验证解压结果 ----------
            echo "✅ 验证解压结果:"
            ls -la .output/server/index.mjs
            # 检查关键入口文件是否存在
            # 如果文件不存在,ls 会报错,set -e 会停止执行

            # ---------- 清理压缩包 ----------
            echo "🧹 清理压缩包..."
            rm -f release.tgz
            # 删除压缩包,释放磁盘空间

            # ---------- 重启 PM2 服务 ----------
            echo "🔄 重启 PM2..."
            if pm2 list | grep -q "nuxt-blog"; then
              # 如果 PM2 中已有该应用,则重启
              pm2 restart nuxt-blog
            else
              # 如果是首次部署,PM2 中还没有该应用,则启动
              pm2 start .output/server/index.mjs --name nuxt-blog
            fi
            # pm2 list          :列出所有 PM2 应用
            # grep -q "nuxt-blog":静默搜索(不输出结果,只返回成功/失败)
            # 
            # 为什么需要判断:
            #   - 首次部署时,PM2 中没有该应用
            #   - restart 会报错 "Process not found"
            #   - 所以先用 start,后续用 restart

            # ---------- 保存 PM2 配置 ----------
            pm2 save
            # 作用:保存当前 PM2 进程列表
            # 效果:服务器重启后,PM2 会自动恢复这些进程
            # 相当于"开机自启"配置

            # ---------- 显示最终状态 ----------
            pm2 status
            # 显示所有 PM2 应用的运行状态
            # 确认 nuxt-blog 是 online 状态

          EOF
          # EOF 结束标记:以上都是在服务器上执行的命令

          # --------------------------------------------
          # 6.4 部署完成提示
          # --------------------------------------------
          echo "✅ 部署完成!"


触发部署

当你将代码推送到main分支时,一切自动发生

上述为大致总结,个人项目通过此操作可实现自动化部署~


assistant