解决部署工作流程合并冲突:采用更健壮的Docker构建和推送流程
Some checks failed
部署后端服务 / 🚀 构建并部署 (push) Failing after 18s

This commit is contained in:
iriver
2025-07-15 20:03:05 +08:00
3 changed files with 201 additions and 174 deletions

View File

@ -48,12 +48,33 @@ jobs:
# 验证配置 # 验证配置
docker info | grep -A 5 "Registry Mirrors" || true docker info | grep -A 5 "Registry Mirrors" || true
- name: 🔑 登录到镜像仓库 - name: 🔑 登录到镜像仓库(使用阿里云访问令牌)
uses: docker/login-action@v3 run: |
with: echo "🔑 使用阿里云访问令牌登录..."
registry: ${{ env.REGISTRY }} # 创建 Docker 配置文件
username: ${{ secrets.DOCKER_USERNAME }} mkdir -p ~/.docker
password: ${{ secrets.DOCKER_PASSWORD }} cat > ~/.docker/config.json << EOF
{
"auths": {
"${{ env.REGISTRY }}": {
"auth": "$(echo -n '${{ secrets.DOCKER_USERNAME }}:${{ secrets.DOCKER_PASSWORD }}' | base64 -w 0)"
}
}
}
EOF
# 验证登录状态
echo "🔍 验证登录状态..."
if docker pull ${{ env.REGISTRY }}/library/hello-world:latest 2>/dev/null; then
echo "✅ 镜像仓库认证成功"
else
echo "❌ 镜像仓库认证失败,尝试基础认证..."
# 使用基础认证
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login ${{ env.REGISTRY }} --username "${{ secrets.DOCKER_USERNAME }}" --password-stdin || {
echo "❌ 所有认证方式都失败"
exit 1
}
fi
- name: 📝 提取元数据 - name: 📝 提取元数据
id: meta id: meta
@ -66,28 +87,74 @@ jobs:
type=sha,prefix={{branch}}- type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}} type=raw,value=latest,enable={{is_default_branch}}
- name: 🔐 验证 Docker 认证 - name: 🏗️ 构建镜像(使用正确格式)
run: | run: |
echo "验证 Docker 认证状态..." echo "🔨 开始构建 Docker 镜像..."
docker info | grep -A 10 "Registry:" || true cd ./backend
echo "测试镜像仓库连接..."
docker pull ${{ env.REGISTRY }}/hello-world:latest || echo "拉取测试镜像失败(正常)"
- name: 🏗️ 构建并推送镜像 # 设置正确的镜像标签格式
uses: docker/build-push-action@v5 IMAGE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
with: IMAGE_TAG_LATEST="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
context: ./backend
file: ./backend/Dockerfile echo "🏷️ 镜像标签:"
platforms: linux/amd64 echo " - $IMAGE_TAG"
push: true echo " - $IMAGE_TAG_LATEST"
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} # 构建镜像
cache-from: type=gha docker build -t $IMAGE_TAG -t $IMAGE_TAG_LATEST .
cache-to: type=gha,mode=max
provenance: false # 验证镜像
sbom: false echo "🔍 验证镜像构建..."
env: docker images | grep photography-backend
BUILDKIT_PROGRESS: plain
echo "✅ 镜像构建完成"
- name: 📤 推送镜像(使用正确路径)
run: |
IMAGE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
IMAGE_TAG_LATEST="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
echo "📤 开始推送镜像..."
echo "📋 目标仓库: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
# 重试机制
max_attempts=3
attempt=1
while [ $attempt -le $max_attempts ]; do
echo "📤 推送尝试 $attempt/$max_attempts..."
echo "🚀 推送命令: docker push $IMAGE_TAG"
if docker push $IMAGE_TAG; then
echo "✅ 版本标签推送成功"
echo "🚀 推送 latest 标签: docker push $IMAGE_TAG_LATEST"
if docker push $IMAGE_TAG_LATEST; then
echo "✅ latest 标签推送成功"
break
else
echo "⚠️ latest 标签推送失败,但版本标签已成功"
break
fi
else
echo "❌ 推送失败,等待重试..."
if [ $attempt -lt $max_attempts ]; then
echo "⏳ 等待15秒后重试..."
sleep 15
fi
fi
attempt=$((attempt + 1))
done
if [ $attempt -gt $max_attempts ]; then
echo "❌ 所有推送尝试都失败"
echo "📋 请检查以下配置:"
echo " - 仓库地址: ${{ env.REGISTRY }}"
echo " - 镜像名称: ${{ env.IMAGE_NAME }}"
echo " - 认证信息: ${{ secrets.DOCKER_USERNAME }}"
exit 1
fi
- name: 📦 同步配置文件 - name: 📦 同步配置文件
run: | run: |
@ -121,6 +188,28 @@ jobs:
# 切换到后端项目目录 # 切换到后端项目目录
cd /data/docker/photography/backend cd /data/docker/photography/backend
# 检查 Docker 服务状态
echo "🐳 检查 Docker 服务状态..."
if ! docker info >/dev/null 2>&1; then
echo "❌ Docker 服务未运行或权限不足,尝试使用 sudo..."
echo '${{ secrets.TYY_PWD }}' | sudo -S systemctl start docker
sleep 5
fi
# 验证 Docker 权限
echo "🔍 验证 Docker 权限..."
docker --version || {
echo "❌ Docker 命令不可用"
exit 1
}
# 登录阿里云镜像仓库
echo "🔑 登录阿里云镜像仓库..."
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login ${{ env.REGISTRY }} --username "${{ secrets.DOCKER_USERNAME }}" --password-stdin || {
echo "❌ Docker 登录失败,检查认证信息..."
exit 1
}
# 备份当前运行的容器 (如果存在) # 备份当前运行的容器 (如果存在)
if docker ps -q -f name=photography-api; then if docker ps -q -f name=photography-api; then
echo "📦 备份当前后端容器..." echo "📦 备份当前后端容器..."
@ -129,19 +218,26 @@ jobs:
# 停止现有服务 # 停止现有服务
echo "🛑 停止现有服务..." echo "🛑 停止现有服务..."
docker-compose down api || true docker compose down api || {
echo "⚠️ 停止服务时遇到问题,继续执行..."
docker stop photography-api || true
docker rm photography-api || true
}
# 拉取最新镜像 # 拉取最新镜像
echo "📥 拉取最新镜像..." echo "📥 拉取最新镜像..."
docker-compose pull api docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} || {
echo "❌ 镜像拉取失败,检查网络连接..."
exit 1
}
# 数据库迁移需要手动执行 # 数据库迁移需要手动执行
echo "⚠️ 数据库迁移需要手动执行,请在部署后运行:" echo "⚠️ 数据库迁移需要手动执行,请在部署后运行:"
echo " docker-compose exec api ./main migrate" echo " docker compose exec api ./main migrate"
# 启动后端服务 # 启动后端服务
echo "🚀 启动后端服务..." echo "🚀 启动后端服务..."
docker-compose up -d api docker compose up -d api
# 等待服务启动 # 等待服务启动
echo "⏳ 等待服务启动..." echo "⏳ 等待服务启动..."
@ -160,7 +256,7 @@ jobs:
# 检查服务状态 # 检查服务状态
echo "📊 检查服务状态..." echo "📊 检查服务状态..."
docker-compose ps docker compose ps
# 清理旧镜像 (保留最近3个) # 清理旧镜像 (保留最近3个)
echo "🧹 清理旧镜像..." echo "🧹 清理旧镜像..."
@ -171,7 +267,7 @@ jobs:
echo "🎉 后端部署完成!" echo "🎉 后端部署完成!"
echo "📋 请记住手动运行数据库迁移:" echo "📋 请记住手动运行数据库迁移:"
echo " docker-compose exec api ./main migrate" echo " docker compose exec api ./main migrate"
- name: 📧 发送部署通知 - name: 📧 发送部署通知
if: always() if: always()
@ -189,50 +285,50 @@ jobs:
${{ job.status == 'success' && '✅ 部署成功' || '❌ 部署失败' }} ${{ job.status == 'success' && '✅ 部署成功' || '❌ 部署失败' }}
${{ job.status == 'success' && '⚠️ 请记住手动运行数据库迁移: docker-compose exec api ./main migrate' || '' }} ${{ job.status == 'success' && '⚠️ 请记住手动运行数据库迁移: docker compose exec api ./main migrate' || '' }}
🌐 API: https://api.photography.iriver.top/health 🌐 API: https://api.photography.iriver.top/health
📊 监控: https://admin.photography.iriver.top 📊 监控: https://admin.photography.iriver.top
rollback: # rollback:
name: 🔄 回滚部署 # name: 🔄 回滚部署
runs-on: ubuntu-latest # runs-on: ubuntu-latest
if: failure() && github.ref == 'refs/heads/main' # if: failure() && github.ref == 'refs/heads/main'
needs: build-and-deploy # needs: build-and-deploy
steps: # steps:
- name: 🔄 执行回滚 # - name: 🔄 执行回滚
uses: appleboy/ssh-action@v1.0.0 # uses: appleboy/ssh-action@v1.0.0
with: # with:
host: ${{ secrets.HOST }} # host: ${{ secrets.HOST }}
username: ${{ secrets.TYY_USER }} # username: ${{ secrets.TYY_USER }}
password: ${{ secrets.TYY_PWD }} # password: ${{ secrets.TYY_PWD }}
port: ${{ secrets.PORT }} # port: ${{ secrets.PORT }}
script: | # script: |
cd /data/docker/photography/backend # cd /data/docker/photography/backend
echo "🔄 开始回滚后端服务..." # echo "🔄 开始回滚后端服务..."
# 查找最新的备份容器 # # 查找最新的备份容器
BACKUP_IMAGE=$(docker images photography_backend_backup_* --format "table {{.Repository}}:{{.Tag}}\t{{.CreatedAt}}" | tail -n +2 | sort -k2 -r | head -n 1 | awk '{print $1}') # BACKUP_IMAGE=$(docker images photography_backend_backup_* --format "table {{.Repository}}:{{.Tag}}\t{{.CreatedAt}}" | tail -n +2 | sort -k2 -r | head -n 1 | awk '{print $1}')
if [ -n "$BACKUP_IMAGE" ]; then # if [ -n "$BACKUP_IMAGE" ]; then
echo "📦 找到备份镜像: $BACKUP_IMAGE" # echo "📦 找到备份镜像: $BACKUP_IMAGE"
# 停止当前服务 # # 停止当前服务
docker-compose down backend # docker compose down api
# 标记备份镜像为最新 # # 标记备份镜像为最新
docker tag $BACKUP_IMAGE photography_backend:rollback # docker tag $BACKUP_IMAGE photography_backend:rollback
# 修改 docker-compose 使用回滚镜像 # # 修改 docker compose 使用回滚镜像
sed -i 's|build: .*|image: photography_backend:rollback|g' docker-compose.yml # sed -i 's|build: .*|image: photography_backend:rollback|g' docker-compose.yml
# 启动回滚版本 # # 启动回滚版本
docker-compose up -d backend # docker compose up -d api
echo "✅ 回滚完成" # echo "✅ 回滚完成"
else # else
echo "❌ 未找到备份镜像,无法回滚" # echo "❌ 未找到备份镜像,无法回滚"
exit 1 # exit 1
fi # fi

View File

@ -1,14 +1,13 @@
# Photography Portfolio Backend - Production Docker Compose # Photography Portfolio Backend - Production Docker Compose
# 生产环境配置 - 使用现有 PostgreSQL 和 Redis 服务 # 生产环境配置 - 使用现有 PostgreSQL 和 Redis 服务
version: '3.8' # version 字段已废弃,不再需要使用
# version: '3.8'
services: services:
# 后端API服务 (仅API服务无数据库) # 后端API服务 (仅API服务无数据库)
api: api:
build: image: crpi-b4fqtfbvv583enk2.cn-shanghai.personal.cr.aliyuncs.com/photography-backend/photography:latest
context: .
dockerfile: Dockerfile
container_name: photography-api container_name: photography-api
environment: environment:
# 数据库配置 (连接现有服务) # 数据库配置 (连接现有服务)
@ -59,9 +58,7 @@ services:
# 数据库迁移服务 (一次性运行) # 数据库迁移服务 (一次性运行)
migrate: migrate:
build: image: crpi-b4fqtfbvv583enk2.cn-shanghai.personal.cr.aliyuncs.com/photography-backend/photography:latest
context: .
dockerfile: Dockerfile
container_name: photography-migrate container_name: photography-migrate
environment: environment:
DB_HOST: ${DB_HOST:-localhost} DB_HOST: ${DB_HOST:-localhost}

View File

@ -1,103 +1,53 @@
# Photography Portfolio Backend - Docker Compose # Photography Portfolio Backend - Production Docker Compose
# 本地开发和测试环境配置 # 生产环境配置 - 使用现有 PostgreSQL 和 Redis 服务
version: '3.8' # version 字段已废弃,不再需要使用
# version: '3.8'
services: services:
# PostgreSQL 数据库 # 后端API服务 (仅API服务数据库)
db:
image: postgres:16-alpine
container_name: photography-db
environment:
POSTGRES_DB: photography
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres123
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./configs/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
networks:
- photography-network
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d photography"]
interval: 10s
timeout: 5s
retries: 5
# Redis 缓存
redis:
image: redis:7-alpine
container_name: photography-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
- ./configs/redis.conf:/usr/local/etc/redis/redis.conf:ro
command: redis-server /usr/local/etc/redis/redis.conf
networks:
- photography-network
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
# 后端API服务
api: api:
build: image: crpi-b4fqtfbvv583enk2.cn-shanghai.personal.cr.aliyuncs.com/photography-backend/photography:latest
context: .
dockerfile: Dockerfile
container_name: photography-api container_name: photography-api
environment: environment:
# 数据库配置 # 数据库配置 (连接现有服务)
DB_HOST: db DB_HOST: ${DB_HOST:-redis_cache}
DB_PORT: 5432 DB_PORT: ${DB_PORT:-5432}
DB_NAME: photography DB_NAME: ${DB_NAME:-photography}
DB_USER: postgres DB_USER: ${DB_USER:-postgres}
DB_PASSWORD: postgres123 DB_PASSWORD: ${DB_PASSWORD}
DB_SSL_MODE: disable DB_SSL_MODE: ${DB_SSL_MODE:-disable}
# Redis配置 # Redis配置 (连接现有服务)
REDIS_HOST: redis REDIS_HOST: ${REDIS_HOST:-localhost}
REDIS_PORT: 6379 REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_PASSWORD: "" REDIS_PASSWORD: ${REDIS_PASSWORD:-}
REDIS_DB: 0 REDIS_DB: ${REDIS_DB:-0}
# JWT配置 # JWT配置
JWT_SECRET: your-super-secret-jwt-key-change-in-production JWT_SECRET: ${JWT_SECRET}
JWT_EXPIRE: 24h JWT_EXPIRE: ${JWT_EXPIRE:-24h}
# 服务配置 # 服务配置
APP_ENV: development APP_ENV: ${APP_ENV:-production}
APP_PORT: 8080 APP_PORT: ${APP_PORT:-8080}
APP_HOST: 0.0.0.0 APP_HOST: ${APP_HOST:-0.0.0.0}
# CORS配置 # CORS配置
CORS_ORIGINS: "http://localhost:3000,http://localhost:3001,http://localhost:5173" CORS_ORIGINS: ${CORS_ORIGINS:-https://photography.iriver.top}
# 文件上传配置 # 文件上传配置
UPLOAD_PATH: /app/uploads UPLOAD_PATH: ${UPLOAD_PATH:-/app/uploads}
UPLOAD_MAX_SIZE: 10485760 # 10MB UPLOAD_MAX_SIZE: ${UPLOAD_MAX_SIZE:-10485760}
# 日志配置 # 日志配置
LOG_LEVEL: info LOG_LEVEL: ${LOG_LEVEL:-info}
LOG_FORMAT: json LOG_FORMAT: ${LOG_FORMAT:-json}
ports: ports:
- "8080:8080" - "8080:8080"
volumes: volumes:
- uploads_data:/app/uploads - uploads_data:/app/uploads
- logs_data:/app/logs - logs_data:/app/logs
networks:
- photography-network
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "/photography-api", "--health-check"] test: ["CMD", "/photography-api", "--health-check"]
@ -113,33 +63,17 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: photography-migrate container_name: photography-migrate
environment: environment:
DB_HOST: db DB_HOST: ${DB_HOST:-localhost}
DB_PORT: 5432 DB_PORT: ${DB_PORT:-5432}
DB_NAME: photography DB_NAME: ${DB_NAME:-photography}
DB_USER: postgres DB_USER: ${DB_USER:-postgres}
DB_PASSWORD: postgres123 DB_PASSWORD: ${DB_PASSWORD}
DB_SSL_MODE: disable DB_SSL_MODE: ${DB_SSL_MODE:-disable}
networks:
- photography-network
depends_on:
db:
condition: service_healthy
entrypoint: ["/migrate", "up"] entrypoint: ["/migrate", "up"]
restart: "no" restart: "no"
volumes: volumes:
postgres_data:
driver: local
redis_data:
driver: local
uploads_data: uploads_data:
driver: local driver: local
logs_data: logs_data:
driver: local driver: local
networks:
photography-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16