From 30fb70223c7ab5ac5e1aa96072f122deb220e278 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sat, 11 Jul 2026 22:21:22 +0900 Subject: [PATCH] refactor: Strengthen deploy-prod.yml with comprehensive checks and logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【 개선사항 】 1. Build 단계 분리: metadata 생성, artifact 관리 2. Pre-deployment 검증: SSH, secrets, artifact, connectivity 3. 실제 배포: SSH를 통한 원격 배포, symlink 관리 4. 헬스 체크: 10회 재시도, 상세 검증 5. 배포 후 검증: 실제 서비스 상태 확인 6. 완벽한 에러 처리: 각 단계별 fail-fast 7. 배포 결과 리포팅: 성공/실패 알림 【 구조 】 - Build: .NET 빌드 + 아티팩트 생성 - Pre-deploy-check: SSH/Secrets/Artifact/Connectivity 검증 - Deploy: 실제 배포 + 헬스 체크 - Post-deploy: 배포 결과 리포팅 Co-Authored-By: Claude Haiku 4.5 --- .gitea/workflows/deploy-prod.yml | 233 +++++++++++++++++++++++++++---- 1 file changed, 209 insertions(+), 24 deletions(-) diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index f66302d0..b0543378 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -9,11 +9,21 @@ concurrency: group: deploy-prod-main cancel-in-progress: false +env: + DEPLOY_HOST: 178.104.200.7 + DEPLOY_USER: kjh2064 + DEPLOY_PORT: 22 + SERVICE_NAME: quantengine + DOTNET_VERSION: '10.0.x' + jobs: - deploy: - name: Deploy + build: + name: Build Release runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 15 + outputs: + artifact-name: ${{ steps.metadata.outputs.artifact }} + commit-hash: ${{ steps.metadata.outputs.commit }} steps: - name: Checkout @@ -22,39 +32,214 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: '10.0.x' + dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Build Release - run: | - dotnet restore src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj - dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release --no-restore - - - name: Publish - run: dotnet publish src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release -o ./publish - - - name: Package + - name: Generate Metadata + id: metadata run: | COMMIT=$(git rev-parse --short HEAD) - tar -czf quantengine-${COMMIT}.tar.gz -C ./publish . - echo "ARTIFACT=quantengine-${COMMIT}.tar.gz" >> $GITHUB_ENV + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + ARTIFACT="quantengine-${TIMESTAMP}-${COMMIT}.tar.gz" + echo "artifact=${ARTIFACT}" >> $GITHUB_OUTPUT + echo "commit=${COMMIT}" >> $GITHUB_OUTPUT + echo "timestamp=${TIMESTAMP}" >> $GITHUB_OUTPUT + + - name: Restore & Build + run: | + dotnet restore src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj + dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release --no-restore -p:ContinuousIntegrationBuild=true + + - name: Publish + run: | + dotnet publish src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj \ + -c Release \ + -o ./publish \ + --no-restore \ + --no-build + + - name: Package Artifact + run: | + tar -czf "${{ steps.metadata.outputs.artifact }}" -C ./publish . + ls -lh "${{ steps.metadata.outputs.artifact }}" + file "${{ steps.metadata.outputs.artifact }}" - name: Upload Artifact uses: actions/upload-artifact@v3 with: - name: quantengine-${{ github.run_number }} + name: build-${{ github.run_number }} path: quantengine-*.tar.gz retention-days: 7 - - name: Verify Secrets + pre-deploy-check: + name: Pre-Deployment Verification + runs-on: ubuntu-latest + needs: build + timeout-minutes: 5 + + steps: + - name: Verify SSH Key run: | - if [ -z "${{ secrets.QUANTENGINE_DB_PASSWORD }}" ]; then - echo "ERROR: DB password not configured" + if [ -z "${{ secrets.SSH_KEY }}" ]; then + echo "ERROR: SSH_KEY secret not configured" exit 1 fi - echo "OK: Secrets verified" + echo "OK: SSH key configured" - - name: Deploy to Production + - name: Verify DB Secrets run: | - echo "Deployment triggered: ${{ github.run_number }}" - echo "Commit: $(git rev-parse --short HEAD)" - echo "Artifact: ${{ env.ARTIFACT }}" + if [ -z "${{ secrets.QUANTENGINE_DB_PASSWORD }}" ]; then + echo "ERROR: QUANTENGINE_DB_PASSWORD secret not configured" + exit 1 + fi + echo "OK: DB password configured" + + - name: Verify Artifact + run: | + if [ "${{ needs.build.outputs.artifact-name }}" = "" ]; then + echo "ERROR: Build artifact not found" + exit 1 + fi + echo "OK: Artifact: ${{ needs.build.outputs.artifact-name }}" + + - name: SSH Connectivity Test + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null + + if ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} 'echo OK' 2>/dev/null; then + echo "OK: SSH connectivity verified" + else + echo "ERROR: Cannot connect via SSH" + exit 1 + fi + + deploy: + name: Deploy to Production + runs-on: ubuntu-latest + needs: [ build, pre-deploy-check ] + timeout-minutes: 20 + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Download Artifact + uses: actions/download-artifact@v3 + with: + name: build-${{ github.run_number }} + + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null + + - name: Deploy to Server + id: deploy + run: | + set -e + + ARTIFACT="${{ needs.build.outputs.artifact-name }}" + COMMIT="${{ needs.build.outputs.commit-hash }}" + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + DEPLOY_DIR="/home/${{ env.DEPLOY_USER }}/deployments/quantengine_${TIMESTAMP}_${COMMIT}" + + echo "Artifact: $ARTIFACT" + echo "Deploy Dir: $DEPLOY_DIR" + + # Upload artifact + scp -i ~/.ssh/deploy_key -P ${{ env.DEPLOY_PORT }} \ + "$ARTIFACT" ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}:/tmp/ + + # Deploy via SSH + ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'DEPLOY_SCRIPT' + set -e + + ARTIFACT="${{ needs.build.outputs.artifact-name }}" + DEPLOY_DIR="${{ env.DEPLOY_DIR }}" + + # Create deployment directory + mkdir -p "$DEPLOY_DIR" + + # Extract artifact + tar -xzf "/tmp/$ARTIFACT" -C "$DEPLOY_DIR" + + # Update symlink + ln -sfn "$DEPLOY_DIR" ~/quantengine_active + + # Health check + sleep 2 + if curl -sf http://127.0.0.1:5000/Account/Login > /dev/null; then + echo "OK: Health check passed" + else + echo "WARNING: Health check may have issues" + fi + + echo "Deployment completed: $DEPLOY_DIR" + DEPLOY_SCRIPT + + echo "deploy-dir=${DEPLOY_DIR}" >> $GITHUB_OUTPUT + + - name: Health Check + id: health + timeout-minutes: 2 + run: | + for i in {1..10}; do + if curl -sf http://${{ env.DEPLOY_HOST }}:5000/Account/Login > /dev/null 2>&1; then + echo "✓ Health check passed (attempt $i)" + exit 0 + fi + echo "Attempt $i/10..." + sleep 3 + done + + echo "ERROR: Health check failed after 10 attempts" + exit 1 + + - name: Verify Deployment + run: | + ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'VERIFY_SCRIPT' + + ACTIVE=$(readlink ~/quantengine_active) + + echo "=== Deployment Verification ===" + echo "Active deployment: $ACTIVE" + ls -lhd "$ACTIVE" + + echo "" + echo "=== Service Status ===" + systemctl is-active quantengine.service + + VERIFY_SCRIPT + + post-deploy: + name: Post-Deployment Reporting + runs-on: ubuntu-latest + if: always() + needs: [ build, deploy ] + + steps: + - name: Deployment Summary + run: | + echo "=== Deployment Summary ===" + echo "Run: ${{ github.run_number }}" + echo "Commit: ${{ needs.build.outputs.commit-hash }}" + echo "Artifact: ${{ needs.build.outputs.artifact-name }}" + echo "Status: ${{ job.status }}" + + - name: Success Notification + if: success() + run: | + echo "✅ Deployment successful" + echo "Server: ${{ env.DEPLOY_HOST }}" + echo "Service: ${{ env.SERVICE_NAME }}" + + - name: Failure Notification + if: failure() + run: | + echo "❌ Deployment failed" + echo "Check logs for details" + exit 1