Files
wecom-ai-assistant/.github/workflows/deploy.yml
2026-02-05 16:36:32 +08:00

179 lines
6.1 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 生产环境自动部署
# 触发条件push 到 main 分支
# 功能:构建 backend 镜像 → 推送到 GHCR → SSH 部署到云服务器 → 健康检查
name: Deploy to Production
on:
push:
branches: [main]
workflow_dispatch:
inputs:
image_tag:
description: '镜像标签(默认: latest'
required: false
default: 'latest'
env:
REGISTRY: ghcr.io
IMAGE_NAME_BACKEND: wecom-ai-backend
jobs:
# 构建并推送 Backend 镜像
build-backend:
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.meta.outputs.tags }}
image_digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME_BACKEND }}
tags: |
type=raw,value=latest
type=sha,prefix={{branch}}-
type=sha,format=short
- name: Build and push backend image
id: build
uses: docker/build-push-action@v6
with:
context: .
file: ./deploy/docker/backend.Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# 部署到生产服务器
deploy:
runs-on: ubuntu-latest
needs: build-backend
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set deployment variables
id: vars
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "image_tag=${{ github.event.inputs.image_tag }}" >> $GITHUB_OUTPUT
else
echo "image_tag=latest" >> $GITHUB_OUTPUT
fi
echo "domain=${{ secrets.PROD_DOMAIN }}" >> $GITHUB_OUTPUT
- name: Prepare deployment token
id: prepare-token
run: |
if [ -n "${{ secrets.GHCR_TOKEN }}" ]; then
echo "token=${{ secrets.GHCR_TOKEN }}" >> $GITHUB_OUTPUT
else
echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT
fi
- name: Deploy to production server
uses: appleboy/ssh-action@v1.0.0
env:
DEPLOY_TOKEN: ${{ steps.prepare-token.outputs.token }}
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
port: ${{ secrets.PROD_SSH_PORT || 22 }}
script: |
set -e
# 设置变量
IMAGE_TAG="${{ steps.vars.outputs.image_tag }}"
GITHUB_REPOSITORY_OWNER="${{ github.repository_owner }}"
REGISTRY="${{ env.REGISTRY }}"
IMAGE_NAME_BACKEND="${{ env.IMAGE_NAME_BACKEND }}"
APP_PATH="${{ secrets.PROD_APP_PATH || '/opt/wecom-ai-assistant' }}"
# 进入项目目录
cd "${APP_PATH}" || { echo "错误: 无法进入目录 ${APP_PATH}"; exit 1; }
# 登录到容器镜像仓库
echo "${DEPLOY_TOKEN}" | docker login "${REGISTRY}" -u "${{ github.actor }}" --password-stdin || {
echo "警告: Docker 登录失败,尝试继续部署(可能使用本地镜像)"
}
# 拉取最新镜像
IMAGE_FULL="${REGISTRY}/${GITHUB_REPOSITORY_OWNER}/${IMAGE_NAME_BACKEND}:${IMAGE_TAG}"
echo "拉取镜像: ${IMAGE_FULL}"
docker pull "${IMAGE_FULL}" || {
echo "警告: 拉取镜像失败,尝试使用 latest 标签"
docker pull "${REGISTRY}/${GITHUB_REPOSITORY_OWNER}/${IMAGE_NAME_BACKEND}:latest"
IMAGE_TAG="latest"
}
# 设置环境变量供 docker-compose 使用
export IMAGE_TAG="${IMAGE_TAG}"
export GITHUB_REPOSITORY_OWNER="${GITHUB_REPOSITORY_OWNER}"
# 更新服务
echo "更新服务..."
docker compose -f docker-compose.prod.yml --env-file .env.prod pull backend 2>/dev/null || true
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --force-recreate backend
# 等待服务启动
echo "等待服务启动..."
sleep 15
# 检查服务状态
echo "服务状态:"
docker compose -f docker-compose.prod.yml ps
# 检查后端健康状态
echo "检查后端健康状态..."
for i in {1..10}; do
if docker compose -f docker-compose.prod.yml exec -T backend python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health')" 2>/dev/null; then
echo "✓ 后端服务健康"
break
fi
if [ $i -eq 10 ]; then
echo "⚠ 后端服务可能未就绪,请检查日志"
else
sleep 2
fi
done
- name: Health check
run: |
DOMAIN=${{ secrets.PROD_DOMAIN }}
MAX_RETRIES=30
RETRY_COUNT=0
echo "健康检查: https://${DOMAIN}/api/health"
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
if curl -f -s "https://${DOMAIN}/api/health" > /dev/null 2>&1; then
echo "✓ 健康检查通过"
exit 0
fi
RETRY_COUNT=$((RETRY_COUNT + 1))
echo "等待服务就绪... ($RETRY_COUNT/$MAX_RETRIES)"
sleep 2
done
echo "✗ 健康检查失败"
exit 1