merge: Log 파일 관련 설정 및 CI/CD 파이프라인 개선점 적용 #28
This file contains 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 Production Server | |
on: | |
push: | |
branches: | |
- main | |
jobs: | |
set: | |
environment: Deploy-Prod | |
runs-on: ubuntu-latest | |
outputs: | |
TIMESTAMP: ${{ steps.set_timestamp.outputs.TIMESTAMP }} | |
steps: | |
- name: 🕒 Set Timestamp | |
id: set_timestamp | |
run: echo "TIMESTAMP=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT | |
build: | |
environment: Deploy-Prod | |
runs-on: ubuntu-latest | |
steps: | |
- name: 📂 Checkout Repository | |
uses: actions/checkout@v3 | |
- name: 🏗️ Setup JDK 21 | |
uses: actions/setup-java@v3 | |
with: | |
java-version: '21' | |
distribution: 'temurin' | |
- name: 🎖️ Grant Execute Permission | |
run: chmod +x gradlew | |
- name: ⚙️ Create application-prod.yml | |
run: | | |
touch ./src/main/resources/application-prod.yml | |
echo "${{ secrets.APPLICATION_PROD_YML }}" > ./src/main/resources/application-prod.yml | |
shell: bash | |
- name: 🔨 Build Gradle | |
run: ./gradlew build --no-daemon | |
- name: 🪂 Upload Artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
path: build/libs/groom-0.0.1-SNAPSHOT.jar | |
retention-days: 1 | |
push: | |
needs: | |
- build | |
- set | |
runs-on: ubuntu-latest | |
environment: Deploy-Prod | |
steps: | |
- name: 📥 Load Outputs | |
run: echo "TIMESTAMP=${{ needs.set.outputs.TIMESTAMP }}" >> $GITHUB_ENV | |
- name: 🏷️ Set Docker Image Name | |
run: echo "IMAGE_NAME=${{ secrets.DOCKER_USERNAME }}/groom-server" >> $GITHUB_ENV | |
- name: 📂 Checkout Repository | |
uses: actions/checkout@v3 | |
- name: 📦 Download Artifact | |
uses: actions/download-artifact@v4 | |
with: | |
path: build/libs | |
- name: 🐋 Docker Login | |
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin | |
- name: 📦 Build Docker Image | |
run: docker build -t ${IMAGE_NAME}:latest -t ${IMAGE_NAME}:${TIMESTAMP} . | |
- name: 🚀 Push Docker Image | |
run: | | |
docker push ${IMAGE_NAME}:latest | |
docker push ${IMAGE_NAME}:${TIMESTAMP} | |
deploy: | |
needs: push | |
runs-on: ubuntu-latest | |
environment: Deploy-Prod | |
steps: | |
- name: 🏷️ Set Docker Image Name | |
run: echo "IMAGE_NAME=${{ secrets.DOCKER_USERNAME }}/groom-server" >> $GITHUB_ENV | |
- name: 🎁 Set Docker Container Name | |
run: echo "CONTAINER_NAME=groom-server" >> $GITHUB_ENV | |
- name: 🔑 Setup SSH | |
run: | | |
mkdir -p ~/.ssh | |
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa | |
chmod 600 ~/.ssh/id_rsa | |
ssh-agent bash -c "ssh-add ~/.ssh/id_rsa" | |
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts | |
- name: 🚀 Deploy to Production | |
run: | | |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << EOF | |
set -e | |
echo "🛑 Stop and Remove Existing Container" | |
docker stop $CONTAINER_NAME || true | |
docker rm $CONTAINER_NAME || true | |
echo "📥 Pull Latest Image" | |
docker pull ${IMAGE_NAME}:latest | |
echo "🚀 Run New Container" | |
docker run -d --name $CONTAINER_NAME -p 8080:8080 ${IMAGE_NAME}:latest | |
echo "🧹 Cleaning up old images" | |
docker system prune -af | |
exit 0 | |
EOF | |
- name: ⌛ Wait for Application to Start | |
run: sleep 30 | |
test: | |
needs: | |
- deploy | |
- set | |
runs-on: ubuntu-latest | |
environment: Deploy-Prod | |
steps: | |
- name: 🏷️ Set Docker Image Name | |
run: echo "IMAGE_NAME=${{ secrets.DOCKER_USERNAME }}/groom-server" >> $GITHUB_ENV | |
- name: 🎁 Set Docker Container Name | |
run: echo "CONTAINER_NAME=groom-server" >> $GITHUB_ENV | |
- name: 📥 Load Outputs | |
run: echo "TIMESTAMP=${{ needs.set.outputs.TIMESTAMP }}" >> $GITHUB_ENV | |
- name: 🔐 Setup SSH | |
run: | | |
mkdir -p ~/.ssh | |
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa | |
chmod 600 ~/.ssh/id_rsa | |
ssh-agent bash -c "ssh-add ~/.ssh/id_rsa" | |
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts | |
- name: 🫀 Check Application Health & Rollback if needed | |
id: health_check | |
run: | | |
RESULT=$(ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << EOF | |
set -e | |
echo "🩺 Checking Application Health..." | |
RESPONSE=\$(curl -s "https://${{ secrets.PROD_SERVER_URL }}/") | |
if [ "\$RESPONSE" = "OK" ]; then | |
echo "DEPLOYMENT_STATUS=successful" | |
exit 0 | |
else | |
echo "❌ Deployment Failed! Rolling back..." | |
docker stop $CONTAINER_NAME || true | |
docker rm $CONTAINER_NAME || true | |
PREVIOUS_IMAGE=\$(docker images --format "{{.Repository}}:{{.Tag}}" | grep "$IMAGE_NAME" | grep -v "latest" | sort -r | sed -n '2p') | |
if [ -z "\$PREVIOUS_IMAGE" ]; then | |
echo "⚠️ No previous image found! Manual rollback required." | |
echo "DEPLOYMENT_STATUS=rollback_failed_no_previous" | |
exit 1 | |
fi | |
echo "🔄 Rolling back to previous image: \$PREVIOUS_IMAGE" | |
docker run -d --name $CONTAINER_NAME -p 8080:8080 \$PREVIOUS_IMAGE | |
if [ \$? -eq 0 ]; then | |
echo "✅ Rollback completed successfully!" | |
echo "DEPLOYMENT_STATUS=rollback_success" | |
exit 2 | |
else | |
echo "❌ Rollback execution failed!" | |
echo "DEPLOYMENT_STATUS=rollback_failure" | |
exit 3 | |
fi | |
fi | |
EOF | |
) | |
echo "$RESULT" | |
ROLLBACK_STATUS=$(echo "$RESULT" | grep "DEPLOYMENT_STATUS=" | cut -d '=' -f2) | |
echo "rollback_status=$ROLLBACK_STATUS" >> $GITHUB_OUTPUT | |
continue-on-error: true | |
- name: 🎉 Discord Notification - Successful Deployment | |
if: steps.health_check.outputs.rollback_status == 'successful' | |
uses: Ilshidur/action-discord@0.3.2 | |
env: | |
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
DISCORD_USERNAME: Deploy Result | |
DISCORD_AVATAR: https://avatars.githubusercontent.com/u/44036562 | |
DISCORD_EMBEDS: | | |
[ | |
{ | |
"title": "🚀 성공적으로 배포 되었습니다!\n", | |
"description": "🎉 프로덕션 서버에 성공적으로 배포되었습니다.", | |
"color": 3066993, | |
"fields": [ | |
{ | |
"name": "🔗 배포 서버 Health Check", | |
"value": "[바로가기](https://${{ secrets.PROD_SERVER_URL }})" | |
} | |
] | |
} | |
] | |
with: | |
args: '' | |
- name: 📢 Discord Notification - Rollback Executed | |
if: steps.health_check.outputs.rollback_status == 'rollback_success' | |
uses: Ilshidur/action-discord@0.3.2 | |
env: | |
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
DISCORD_USERNAME: Deploy Result | |
DISCORD_AVATAR: https://avatars.githubusercontent.com/u/44036562 | |
DISCORD_EMBEDS: | | |
[ | |
{ | |
"title": "🔄 롤백이 성공적으로 실행되었습니다!\n", | |
"description": "❌ 배포가 실패하여 이전 이미지로 롤백되었습니다.", | |
"color": 15158332, | |
"fields": [ | |
{ | |
"name": "🔗 복구된 서버 Health Check", | |
"value": "[바로가기](https://${{ secrets.PROD_SERVER_URL }})" | |
} | |
] | |
with: | |
args: '' | |
- name: 📢 Discord Notification - Rollback Failed | |
if: steps.health_check.outputs.rollback_status == 'rollback_failure' || steps.health_check.outputs.rollback_status == 'rollback_failed_no_previous' | |
uses: Ilshidur/action-discord@0.3.2 | |
env: | |
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
DISCORD_USERNAME: Deploy Result | |
DISCORD_AVATAR: https://avatars.githubusercontent.com/u/44036562 | |
DISCORD_EMBEDS: | | |
[ | |
{ | |
"title": "❌ 롤백이 실패하였습니다!\n", | |
"description": "롤백이 실패하여 이전 이미지로 롤백되지 않았습니다.직접 서버를 확인해주세요.", | |
"color": 15158332 | |
} | |
] | |
with: | |
args: '' |