Deploy to Oracle Cloud VM (Sequential) #58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy to Oracle Cloud VM (Sequential) | |
| on: | |
| workflow_run: | |
| workflows: ["Docker Build and Push to GHCR"] | |
| types: | |
| - completed | |
| jobs: | |
| deploy-sub: | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | |
| outputs: | |
| status: ${{ steps.deployment.outcome }} | |
| image_url: ${{ steps.setup.outputs.image_url }} | |
| steps: | |
| - name: Setup Variables | |
| id: setup | |
| run: | | |
| REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') | |
| IMAGE_URL="ghcr.io/${REPO_LOWER}:latest" | |
| echo "image_url=$IMAGE_URL" >> $GITHUB_OUTPUT | |
| echo "Using image: $IMAGE_URL" | |
| - name: Deploy and Health Check SUB VM | |
| id: deployment | |
| uses: appleboy/ssh-action@v1.0.0 | |
| with: | |
| host: ${{ secrets.SUB_VM_HOST }} | |
| username: ${{ secrets.SUB_VM_USERNAME }} | |
| key: ${{ secrets.SUB_VM_SSH_KEY }} | |
| port: 22 | |
| script: | | |
| set -e | |
| # λ³μ μ€μ | |
| IMAGE_URL="${{ steps.setup.outputs.image_url }}" | |
| CONTAINER_NAME="pray-together-api" | |
| echo "π [SUB VM] Starting deployment with image: $IMAGE_URL" | |
| # GitHub Container Registry λ‘κ·ΈμΈ | |
| echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin | |
| # λ°°ν¬ ν¨μ | |
| deploy_container() { | |
| echo "π§Ή [SUB VM] Cleaning up..." | |
| docker stop $CONTAINER_NAME 2>/dev/null || true | |
| docker rm $CONTAINER_NAME 2>/dev/null || true | |
| docker rmi $IMAGE_URL 2>/dev/null || true | |
| echo "π¦ [SUB VM] Deploying new container..." | |
| docker pull $IMAGE_URL | |
| docker run -d \ | |
| --name $CONTAINER_NAME \ | |
| --restart unless-stopped \ | |
| --memory=700m \ | |
| -p 8080:8080 \ | |
| -v /home/ubuntu/log-pray:/root/log-pray \ | |
| -v /home/ubuntu/heapdump:/app/logs \ | |
| --log-driver json-file \ | |
| --log-opt max-size=50m \ | |
| --log-opt max-file=10 \ | |
| -e ORACLE_DB_DRIVER="${{ secrets.ORACLE_DB_DRIVER }}" \ | |
| -e ORACLE_DB_URL="${{ secrets.ORACLE_DB_URL }}" \ | |
| -e ORACLE_DB_USERNAME="${{ secrets.ORACLE_DB_USERNAME }}" \ | |
| -e ORACLE_DB_PASSWORD="${{ secrets.ORACLE_DB_PASSWORD }}" \ | |
| -e ORACLE_DB_DIALECT="${{ secrets.ORACLE_DB_DIALECT }}" \ | |
| -e MAIL_USERNAME="${{ secrets.MAIL_USERNAME }}" \ | |
| -e MAIL_PASSWORD="${{ secrets.MAIL_PASSWORD }}" \ | |
| -e JWT_SECRET_KEY="${{ secrets.JWT_SECRET_KEY }}" \ | |
| -e ACCESS_EXPIRE_TIME="${{ secrets.ACCESS_EXPIRE_TIME }}" \ | |
| -e REFRESH_EXPIRE_TIME="${{ secrets.REFRESH_EXPIRE_TIME }}" \ | |
| -e APP_VERSION_MINIMUM="${{ secrets.APP_VERSION_MINIMUM }}" \ | |
| -e APP_VERSION_UPDATE_FORCE="${{ secrets.APP_VERSION_UPDATE_FORCE }}" \ | |
| -e APP_MAINTENANCE_MODE="${{ secrets.APP_MAINTENANCE_MODE }}" \ | |
| $IMAGE_URL | |
| echo "β³ [SUB VM] Waiting for container to start..." | |
| sleep 30 | |
| } | |
| # ν¬μ€μ²΄ν¬ ν¨μ | |
| health_check() { | |
| echo "π [SUB VM] Starting health check..." | |
| local max_attempts=6 | |
| local wait_time=20 | |
| for attempt in $(seq 1 $max_attempts); do | |
| echo "β³ [SUB VM] Health check attempt $attempt/$max_attempts..." | |
| # 컨ν μ΄λ μν νμΈ | |
| if ! docker ps --filter "name=$CONTAINER_NAME" --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then | |
| echo "β [SUB VM] Container '$CONTAINER_NAME' is not running" | |
| echo "π [SUB VM] Container logs:" | |
| docker logs --tail=20 $CONTAINER_NAME 2>/dev/null || echo "No logs available" | |
| return 1 | |
| fi | |
| # HTTP ν¬μ€μ²΄ν¬ | |
| local response | |
| local http_code | |
| local body | |
| if response=$(curl -s -w "%{http_code}" --connect-timeout 20 --max-time 20 "http://localhost:8080/health" 2>/dev/null); then | |
| http_code="${response: -3}" | |
| body="${response%???}" | |
| echo "π‘ [SUB VM] HTTP Response: $http_code" | |
| echo "π [SUB VM] Body: $body" | |
| if [[ "$http_code" == "200" ]] && echo "$body" | grep -q "healthy"; then | |
| echo "β [SUB VM] Health check passed!" | |
| return 0 | |
| fi | |
| else | |
| echo "π [SUB VM] Health check request failed" | |
| fi | |
| if [[ $attempt -lt $max_attempts ]]; then | |
| echo "β±οΈ [SUB VM] Waiting ${wait_time}s before retry..." | |
| sleep $wait_time | |
| fi | |
| done | |
| echo "β [SUB VM] Health check failed after $max_attempts attempts" | |
| echo "π [SUB VM] Final container logs:" | |
| docker logs --tail=30 $CONTAINER_NAME 2>/dev/null || echo "No logs available" | |
| return 1 | |
| } | |
| # μ€ν | |
| deploy_container | |
| health_check | |
| echo "π [SUB VM] Deployment completed successfully!" | |
| deploy-main: | |
| runs-on: ubuntu-latest | |
| needs: deploy-sub | |
| if: ${{ needs.deploy-sub.outputs.status == 'success' }} | |
| outputs: | |
| status: ${{ steps.deployment.outcome }} | |
| steps: | |
| - name: Deploy and Health Check MAIN VM | |
| id: deployment | |
| uses: appleboy/ssh-action@v1.0.0 | |
| with: | |
| host: ${{ secrets.MAIN_VM_HOST }} | |
| username: ${{ secrets.MAIN_VM_USERNAME }} | |
| key: ${{ secrets.MAIN_VM_SSH_KEY }} | |
| port: 22 | |
| script: | | |
| set -e | |
| # λ³μ μ€μ | |
| IMAGE_URL="${{ needs.deploy-sub.outputs.image_url }}" | |
| CONTAINER_NAME="pray-together-api" | |
| echo "π [MAIN VM] Starting deployment with image: $IMAGE_URL" | |
| # GitHub Container Registry λ‘κ·ΈμΈ | |
| echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin | |
| # λ°°ν¬ ν¨μ (SUBμ λμΌν λ‘μ§) | |
| deploy_container() { | |
| echo "π§Ή [MAIN VM] Cleaning up..." | |
| docker stop $CONTAINER_NAME 2>/dev/null || true | |
| docker rm $CONTAINER_NAME 2>/dev/null || true | |
| docker rmi $IMAGE_URL 2>/dev/null || true | |
| echo "π¦ [MAIN VM] Deploying new container..." | |
| docker pull $IMAGE_URL | |
| docker run -d \ | |
| --name $CONTAINER_NAME \ | |
| --restart unless-stopped \ | |
| --memory=700m \ | |
| -p 8080:8080 \ | |
| -v /home/ubuntu/log-pray:/root/log-pray \ | |
| -v /home/ubuntu/heapdump:/app/logs \ | |
| --log-driver json-file \ | |
| --log-opt max-size=50m \ | |
| --log-opt max-file=10 \ | |
| -e ORACLE_DB_DRIVER="${{ secrets.ORACLE_DB_DRIVER }}" \ | |
| -e ORACLE_DB_URL="${{ secrets.ORACLE_DB_URL }}" \ | |
| -e ORACLE_DB_USERNAME="${{ secrets.ORACLE_DB_USERNAME }}" \ | |
| -e ORACLE_DB_PASSWORD="${{ secrets.ORACLE_DB_PASSWORD }}" \ | |
| -e ORACLE_DB_DIALECT="${{ secrets.ORACLE_DB_DIALECT }}" \ | |
| -e MAIL_USERNAME="${{ secrets.MAIL_USERNAME }}" \ | |
| -e MAIL_PASSWORD="${{ secrets.MAIL_PASSWORD }}" \ | |
| -e JWT_SECRET_KEY="${{ secrets.JWT_SECRET_KEY }}" \ | |
| -e ACCESS_EXPIRE_TIME="${{ secrets.ACCESS_EXPIRE_TIME }}" \ | |
| -e REFRESH_EXPIRE_TIME="${{ secrets.REFRESH_EXPIRE_TIME }}" \ | |
| -e APP_VERSION_MINIMUM="${{ secrets.APP_VERSION_MINIMUM }}" \ | |
| -e APP_VERSION_UPDATE_FORCE="${{ secrets.APP_VERSION_UPDATE_FORCE }}" \ | |
| -e APP_MAINTENANCE_MODE="${{ secrets.APP_MAINTENANCE_MODE }}" \ | |
| $IMAGE_URL | |
| echo "β³ [MAIN VM] Waiting for container to start..." | |
| sleep 30 | |
| } | |
| # ν¬μ€μ²΄ν¬ ν¨μ | |
| health_check() { | |
| echo "π [MAIN VM] Starting health check..." | |
| local max_attempts=6 | |
| local wait_time=20 | |
| for attempt in $(seq 1 $max_attempts); do | |
| echo "β³ [MAIN VM] Health check attempt $attempt/$max_attempts..." | |
| if ! docker ps --filter "name=$CONTAINER_NAME" --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then | |
| echo "β [MAIN VM] Container '$CONTAINER_NAME' is not running" | |
| echo "π [MAIN VM] Container logs:" | |
| docker logs --tail=20 $CONTAINER_NAME 2>/dev/null || echo "No logs available" | |
| return 1 | |
| fi | |
| local response | |
| local http_code | |
| local body | |
| if response=$(curl -s -w "%{http_code}" --connect-timeout 20 --max-time 20 "http://localhost:8080/health" 2>/dev/null); then | |
| http_code="${response: -3}" | |
| body="${response%???}" | |
| echo "π‘ [MAIN VM] HTTP Response: $http_code" | |
| echo "π [MAIN VM] Body: $body" | |
| if [[ "$http_code" == "200" ]] && echo "$body" | grep -q "healthy"; then | |
| echo "β [MAIN VM] Health check passed!" | |
| return 0 | |
| fi | |
| else | |
| echo "π [MAIN VM] Health check request failed" | |
| fi | |
| if [[ $attempt -lt $max_attempts ]]; then | |
| echo "β±οΈ [MAIN VM] Waiting ${wait_time}s before retry..." | |
| sleep $wait_time | |
| fi | |
| done | |
| echo "β [MAIN VM] Health check failed after $max_attempts attempts" | |
| echo "π [MAIN VM] Final container logs:" | |
| docker logs --tail=30 $CONTAINER_NAME 2>/dev/null || echo "No logs available" | |
| return 1 | |
| } | |
| # μ€ν | |
| deploy_container | |
| health_check | |
| echo "π [MAIN VM] Deployment completed successfully!" | |
| # μλ¦Ό Job - μμ ν 쑰건 μ²λ¦¬ | |
| notify: | |
| runs-on: ubuntu-latest | |
| needs: [deploy-sub, deploy-main] | |
| if: always() | |
| steps: | |
| - name: Determine Status | |
| id: status | |
| run: | | |
| SUB_STATUS="${{ needs.deploy-sub.outputs.status }}" | |
| MAIN_STATUS="${{ needs.deploy-main.outputs.status }}" | |
| echo "SUB_STATUS: $SUB_STATUS" | |
| echo "MAIN_STATUS: $MAIN_STATUS" | |
| # μμ ν 쑰건 μ²λ¦¬ | |
| if [[ "$SUB_STATUS" == "success" && "$MAIN_STATUS" == "success" ]]; then | |
| echo "notification_status=success" >> $GITHUB_OUTPUT | |
| echo "notification_color=0x00ff00" >> $GITHUB_OUTPUT | |
| echo "notification_title=π All Deployments Successful" >> $GITHUB_OUTPUT | |
| echo "sub_icon=π’ β " >> $GITHUB_OUTPUT | |
| echo "main_icon=π’ β " >> $GITHUB_OUTPUT | |
| echo "sub_text=SUCCESS" >> $GITHUB_OUTPUT | |
| echo "main_text=SUCCESS" >> $GITHUB_OUTPUT | |
| echo "footer_message=π Zero-downtime deployment completed successfully!" >> $GITHUB_OUTPUT | |
| elif [[ "$SUB_STATUS" == "success" && "$MAIN_STATUS" == "failure" ]]; then | |
| echo "notification_status=failure" >> $GITHUB_OUTPUT | |
| echo "notification_color=0xff8800" >> $GITHUB_OUTPUT | |
| echo "notification_title=β οΈ Partial Deployment (MAIN Failed)" >> $GITHUB_OUTPUT | |
| echo "sub_icon=π’ β " >> $GITHUB_OUTPUT | |
| echo "main_icon=π΄ β" >> $GITHUB_OUTPUT | |
| echo "sub_text=SUCCESS" >> $GITHUB_OUTPUT | |
| echo "main_text=FAILED" >> $GITHUB_OUTPUT | |
| echo "footer_message=β οΈ Service running on SUB VM only. Manual intervention needed for MAIN VM." >> $GITHUB_OUTPUT | |
| elif [[ "$SUB_STATUS" == "success" && "$MAIN_STATUS" == "" ]]; then | |
| # MAINμ΄ μ€νλμ§ μμ κ²½μ° (skipped) | |
| echo "notification_status=success" >> $GITHUB_OUTPUT | |
| echo "notification_color=0x0099ff" >> $GITHUB_OUTPUT | |
| echo "notification_title=π‘ SUB VM Deployment Only" >> $GITHUB_OUTPUT | |
| echo "sub_icon=π’ β " >> $GITHUB_OUTPUT | |
| echo "main_icon=βΈοΈ βοΈ" >> $GITHUB_OUTPUT | |
| echo "sub_text=SUCCESS" >> $GITHUB_OUTPUT | |
| echo "main_text=SKIPPED" >> $GITHUB_OUTPUT | |
| echo "footer_message=βΉοΈ Only SUB VM was deployed. MAIN VM deployment was skipped." >> $GITHUB_OUTPUT | |
| else | |
| echo "notification_status=failure" >> $GITHUB_OUTPUT | |
| echo "notification_color=0xff0000" >> $GITHUB_OUTPUT | |
| echo "notification_title=π¨ Deployment Failed" >> $GITHUB_OUTPUT | |
| echo "sub_icon=π΄ β" >> $GITHUB_OUTPUT | |
| echo "main_icon=βΈοΈ βοΈ" >> $GITHUB_OUTPUT | |
| echo "sub_text=FAILED" >> $GITHUB_OUTPUT | |
| echo "main_text=SKIPPED" >> $GITHUB_OUTPUT | |
| echo "footer_message=π¨ Critical: SUB VM deployment failed. Service may be unavailable." >> $GITHUB_OUTPUT | |
| fi | |
| - name: Send Discord Notification | |
| uses: sarisia/actions-status-discord@v1 | |
| with: | |
| webhook: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
| status: ${{ steps.status.outputs.notification_status }} | |
| color: ${{ steps.status.outputs.notification_color }} | |
| title: "[${{ github.repository }}] ${{ steps.status.outputs.notification_title }}" | |
| description: | | |
| **Deployment Results:** | |
| ${{ steps.status.outputs.sub_icon }} **SUB VM**: ${{ steps.status.outputs.sub_text }} | |
| ${{ steps.status.outputs.main_icon }} **MAIN VM**: ${{ steps.status.outputs.main_text }} | |
| **Image**: `${{ needs.deploy-sub.outputs.image_url }}` | |
| **Trigger**: ${{ github.event.workflow_run.head_commit.message }} | |
| **Commit**: [`${{ github.event.workflow_run.head_sha }}`](${{ github.event.workflow_run.html_url }}) | |
| ${{ steps.status.outputs.footer_message }} | |
| url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| username: "κΈ°λν¨κ» λμ°λ―Έ" |