refactor: Strengthen deploy-prod.yml with comprehensive checks and logging
Deploy to Production / Build Release (push) Failing after 26s
Deploy to Production / Pre-Deployment Verification (push) Has been skipped
Deploy to Production / Deploy to Production (push) Has been skipped
Deploy to Production / Post-Deployment Reporting (push) Successful in 1s
Deploy to Production / Build Release (push) Failing after 26s
Deploy to Production / Pre-Deployment Verification (push) Has been skipped
Deploy to Production / Deploy to Production (push) Has been skipped
Deploy to Production / Post-Deployment Reporting (push) Successful in 1s
【 개선사항 】 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 <noreply@anthropic.com>
This commit is contained in:
@@ -9,11 +9,21 @@ concurrency:
|
|||||||
group: deploy-prod-main
|
group: deploy-prod-main
|
||||||
cancel-in-progress: false
|
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:
|
jobs:
|
||||||
deploy:
|
build:
|
||||||
name: Deploy
|
name: Build Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 15
|
||||||
|
outputs:
|
||||||
|
artifact-name: ${{ steps.metadata.outputs.artifact }}
|
||||||
|
commit-hash: ${{ steps.metadata.outputs.commit }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -22,39 +32,214 @@ jobs:
|
|||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '10.0.x'
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
||||||
|
|
||||||
- name: Build Release
|
- name: Generate Metadata
|
||||||
run: |
|
id: metadata
|
||||||
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
|
|
||||||
run: |
|
run: |
|
||||||
COMMIT=$(git rev-parse --short HEAD)
|
COMMIT=$(git rev-parse --short HEAD)
|
||||||
tar -czf quantengine-${COMMIT}.tar.gz -C ./publish .
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
echo "ARTIFACT=quantengine-${COMMIT}.tar.gz" >> $GITHUB_ENV
|
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
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: quantengine-${{ github.run_number }}
|
name: build-${{ github.run_number }}
|
||||||
path: quantengine-*.tar.gz
|
path: quantengine-*.tar.gz
|
||||||
retention-days: 7
|
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: |
|
run: |
|
||||||
if [ -z "${{ secrets.QUANTENGINE_DB_PASSWORD }}" ]; then
|
if [ -z "${{ secrets.SSH_KEY }}" ]; then
|
||||||
echo "ERROR: DB password not configured"
|
echo "ERROR: SSH_KEY secret not configured"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "OK: Secrets verified"
|
echo "OK: SSH key configured"
|
||||||
|
|
||||||
- name: Deploy to Production
|
- name: Verify DB Secrets
|
||||||
run: |
|
run: |
|
||||||
echo "Deployment triggered: ${{ github.run_number }}"
|
if [ -z "${{ secrets.QUANTENGINE_DB_PASSWORD }}" ]; then
|
||||||
echo "Commit: $(git rev-parse --short HEAD)"
|
echo "ERROR: QUANTENGINE_DB_PASSWORD secret not configured"
|
||||||
echo "Artifact: ${{ env.ARTIFACT }}"
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user