GitHub Actions 自动部署问题总结

GitHub Actions 自动部署问题总结

GitHub Actions 自动部署问题总结

本文档记录了在配置 GitHub Actions 自动部署到生产服务器时遇到的所有问题及解决方案。


📋 目录

  1. 部署架构
  2. 问题一:Secrets 配置警告
  3. 问题二:文件权限拒绝
  4. 问题三:服务器网络限制
  5. 问题四:docker-compose 配置冲突
  6. 问题五:环境变量缺失
  7. 最终解决方案
  8. 经验教训

部署架构

目标

使用 GitHub Actions 实现代码推送后自动构建 Docker 镜像并部署到生产服务器。

技术栈

  • 应用:Next.js 16 + Prisma
  • 容器化:Docker + Docker Compose
  • CI/CD:GitHub Actions
  • 部署方式:SCP 上传镜像 + SSH 远程执行部署命令

问题一:Secrets 配置警告

❌ 错误信息

Context access might be invalid: SERVER_HOST @[deploy.yml:L27]

🔍 原因分析

GitHub Actions 在静态分析时无法验证 secrets.SERVER_HOST 是否存在,因为 repository secrets 尚未配置。

✅ 解决方案

在 GitHub 仓库设置中添加以下 Secrets:

  1. 进入 SettingsSecrets and variablesActions
  2. 添加以下 secrets:
    • SERVER_HOST:服务器 IP 地址(如 8.166.136.59
    • SERVER_USER:SSH 用户名(如 root
    • SERVER_SSH_KEY:SSH 私钥完整内容
    • SERVER_PORT:SSH 端口(可选,默认 22)

问题二:文件权限拒绝

❌ 错误信息

tar: can't open 'cbiu-website.tar': Permission denied
exit status 1

🔍 原因分析

docker save 命令生成的 tar 文件默认权限可能不允许后续的 SCP action 读取。

✅ 解决方案

在保存 Docker 镜像后添加 chmod 命令修改文件权限:

- name: Build Docker image
  run: |
    docker build -t cbiu-website:latest .
    docker save cbiu-website:latest -o cbiu-website.tar
    chmod 644 cbiu-website.tar  # 添加此行

问题三:服务器网络限制

❌ 错误信息

ERROR: failed to resolve reference "docker.io/library/mysql:8.0": 
failed to do request: Head "https://registry-1.docker.io/v2/library/mysql/manifests/8.0": 
dial tcp 168.143.162.58:443: i/o timeout

🔍 原因分析

生产服务器存在严重的网络限制问题:

  1. 无法访问 Docker Hub 官方镜像仓库
  2. DNS 解析失败(无法解析 mirror.ccs.tencentyun.com 等镜像加速器域名)
  3. 配置了 Docker 镜像加速器后仍然超时
  4. 尝试多个镜像源(腾讯云、阿里云、网易、中科大)均失败

核心问题:服务器网络环境特殊,几乎无法从外部拉取任何 Docker 镜像。

✅ 解决方案

将镜像拉取工作转移到 GitHub Actions 服务器(网络畅通),然后上传到生产服务器:

1. 修改构建步骤,同时拉取并保存 MySQL 镜像

- name: Build Docker images
  run: |
    # Build application image
    docker build -t cbiu-website:latest .
    docker save cbiu-website:latest -o cbiu-website.tar
    chmod 644 cbiu-website.tar
    
    # Pull and save MySQL image
    docker pull mysql:8.0
    docker save mysql:8.0 -o mysql.tar
    chmod 644 mysql.tar

2. 上传两个镜像文件

- name: Upload images to server
  uses: appleboy/scp-action@v0.1.7
  with:
    source: "cbiu-website.tar,mysql.tar"  # 上传多个文件
    target: "/root/projects/cbiu-website/"

3. 在服务器上加载镜像

script: |
  cd /root/projects/cbiu-website
  docker load -i mysql.tar
  docker load -i cbiu-website.tar

问题四:docker-compose 配置冲突

❌ 错误信息

Building app
ERROR: failed to solve: node:20.9.0-alpine3.18: 
failed to resolve source metadata for docker.io/library/node:20.9.0-alpine3.18

🔍 原因分析

docker-compose.ymlapp 服务配置了 build 字段:

app:
  build:
    context: .
    dockerfile: Dockerfile

执行 docker-compose up -d 时,Docker Compose 会尝试重新构建镜像,而不是使用已有的 cbiu-website:latest 镜像。这导致服务器再次尝试从网络拉取 Node.js 基础镜像,最终失败。

✅ 解决方案(两步)

方案 1:修改本地 docker-compose.yml

build 配置改为 image 配置:

app:
  container_name: cbiu-website
  image: cbiu-website:latest  # 使用预构建镜像
  restart: always

优点:一劳永逸
缺点:影响本地开发环境的构建流程

方案 2:部署时自动修改(最终采用)

在 GitHub Actions 部署脚本中自动修改服务器上的 docker-compose.yml

script: |
  cd /root/projects/cbiu-website
  
  # Load images
  docker load -i mysql.tar
  docker load -i cbiu-website.tar
  
  # Auto-fix docker-compose.yml
  if grep -q "build:" docker-compose.yml; then
    sed -i '/app:/,/restart:/ {
      /build:/,/dockerfile:/d
      /container_name:/a\    image: cbiu-website:latest
    }' docker-compose.yml
  fi
  
  # Start services
  docker-compose up -d

优点:不影响本地开发,每次部署自动修正
缺点:需要额外的脚本逻辑


问题五:环境变量缺失

❌ 现象

容器启动后立即退出,或应用无法连接数据库。

🔍 原因分析

GitHub Actions 只上传了镜像文件,但没有上传 .env 文件(因为 .env.gitignore 中)。

应用启动时缺少必要的环境变量:

  • DATABASE_URL
  • AUTH_SECRET
  • AUTH_URL
  • MYSQL_ROOT_PASSWORD
  • MYSQL_DATABASE

✅ 解决方案

在服务器上手动创建 .env 文件:

cd /root/projects/cbiu-website
nano .env

填入以下内容:

# 数据库配置
DATABASE_URL="mysql://root:123456@db:3306/cbiu_webside"

# MySQL 初始化配置
MYSQL_ROOT_PASSWORD="123456"
MYSQL_DATABASE="cbiu_webside"

# NextAuth 配置
AUTH_SECRET="生成的随机密钥"  # 使用 openssl rand -base64 32 生成
AUTH_URL="http://8.166.136.59:3000"

注意AUTH_URL 必须包含 http:// 协议前缀,否则会导致认证失败。


最终解决方案

完整的 GitHub Actions 工作流

name: Deploy to Production

on:
  push:
    branches: [ main, master ]
  workflow_dispatch:

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      # 构建应用镜像 + 拉取 MySQL 镜像
      - name: Build Docker images
        run: |
          docker build -t cbiu-website:latest .
          docker save cbiu-website:latest -o cbiu-website.tar
          chmod 644 cbiu-website.tar
          
          docker pull mysql:8.0
          docker save mysql:8.0 -o mysql.tar
          chmod 644 mysql.tar
      
      # 同时上传两个镜像
      - name: Upload images to server
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          port: ${{ secrets.SERVER_PORT || 22 }}
          source: "cbiu-website.tar,mysql.tar"
          target: "/root/projects/cbiu-website/"
      
      # 部署并自动修正配置
      - name: Deploy on server
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          port: ${{ secrets.SERVER_PORT || 22 }}
          script: |
            cd /root/projects/cbiu-website
            
            # 加载镜像
            docker load -i mysql.tar
            docker load -i cbiu-website.tar
            
            # 自动修正 docker-compose.yml
            if grep -q "build:" docker-compose.yml; then
              sed -i '/app:/,/restart:/ {
                /build:/,/dockerfile:/d
                /container_name:/a\    image: cbiu-website:latest
              }' docker-compose.yml
            fi
            
            # 启动服务
            docker-compose up -d
            
            # 清理
            docker image prune -f
            rm cbiu-website.tar mysql.tar
            
            echo "✅ Deployment completed successfully!"

经验教训

1. 🌐 网络环境评估至关重要

在配置 CI/CD 之前,必须先评估生产服务器的网络环境:

  • 能否访问 Docker Hub?
  • 能否使用镜像加速器?
  • DNS 解析是否正常?

如果网络受限,应该将镜像构建工作完全转移到 CI 环境。

2. 📦 Docker Compose 的两种模式

  • 开发模式:使用 build 配置,本地构建镜像
  • 生产模式:使用 image 配置,使用预构建镜像

两种模式不能混用,否则会导致部署时尝试重新构建。

3. 🔐 环境变量管理

  • .env 文件不应提交到 Git
  • 生产环境需要手动或通过配置管理工具创建 .env
  • 考虑使用 GitHub Secrets + 部署脚本自动生成 .env

4. 🛠️ 自动化脚本的健壮性

部署脚本应该:

  • 检查文件是否存在
  • 处理配置文件差异
  • 提供清晰的错误信息
  • 支持幂等操作(多次执行结果一致)

5. 📝 权限问题预防

生成的文件(如 tar 包)应该显式设置权限,避免后续步骤无法访问。

6. 🔄 分步验证

遇到问题时,逐步验证:

  1. 镜像是否已构建?
  2. 镜像是否已上传?
  3. 镜像是否已加载?
  4. 容器是否已创建?
  5. 容器是否正在运行?
  6. 应用是否正常启动?

后续优化建议

  1. 使用私有镜像仓库

    • 在阿里云 ACR 或腾讯云 TCR 创建私有仓库
    • GitHub Actions 推送镜像到私有仓库
    • 服务器从私有仓库拉取(避免传输大文件)
  2. 环境变量自动化

    - name: Create .env file
      script: |
        cat > .env << EOF
        DATABASE_URL=${{ secrets.DATABASE_URL }}
        AUTH_SECRET=${{ secrets.AUTH_SECRET }}
        AUTH_URL=${{ secrets.AUTH_URL }}
        EOF
    
  3. 健康检查 添加部署后的健康检查:

    - name: Health check
      run: |
        sleep 10
        curl -f http://${{ secrets.SERVER_HOST }}:3000/ || exit 1
    
  4. 回滚机制 保留上一个版本的镜像,部署失败时自动回滚。

  5. 通知集成 部署成功/失败时发送通知(Slack、邮件、企业微信等)。


总结

这次部署经历了从简单的配置错误到复杂的网络环境问题,最终通过将镜像构建转移到 CI 环境自动化配置修正解决了所有问题。

关键要点

  • ✅ CI/CD 设计应适应生产环境限制
  • ✅ 网络受限环境需要完整的镜像传输方案
  • ✅ 自动化脚本要能处理配置差异
  • ✅ 环境变量管理需要明确的流程

GitHub Actions 部署已成功运行,现在每次代码推送都会自动构建并部署到生产环境!🎉