Skip to content

Develop -> Main merge : 2025.10.14 - 11:14 (#180) #57

Develop -> Main merge : 2025.10.14 - 11:14 (#180)

Develop -> Main merge : 2025.10.14 - 11:14 (#180) #57

Workflow file for this run

# 워크플로우 이름
name: Spring CD (Production)
# main 브랜치 PR에서만 실행 (이미 빌드된 Docker 이미지 사용)
on:
push:
branches:
- main
paths:
- 'src/**'
- 'build.gradle*'
- 'settings.gradle*'
- 'gradle/**'
- 'Dockerfile'
- '.github/workflows/**'
jobs:
# ==================================
# CD: Deploy to Production Environment
# ==================================
cd-prod:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy to Test Environment
uses: appleboy/[email protected]
with:
host: ${{ secrets.PROD_SERVER_HOST }}
username: ec2-user
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
# GHCR 로그인 (EC2)
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{github.repository_owner}}" --password-stdin
# 최신 이미지 pull
docker pull ghcr.io/${{ github.repository }}/zoopzoop:latest
# NPM API 설정
NPM_HOST="localhost:81"
NPM_EMAIL="${{secrets.NPM_ADMIN_EMAIL}}"
NPM_PASSWORD="${{secrets.NPM_ADMIN_PASSWORD}}"
PROXY_HOST_ID="${{secrets.NPM_PROXY_HOST_ID}}"
# NPM API 토큰 가져오기
echo "Getting NPM API token..."
TOKEN=$(curl -s -X POST "http://${NPM_HOST}/api/tokens" \
-H "Content-Type: application/json" \
-d "{\"identity\":\"${NPM_EMAIL}\",\"secret\":\"${NPM_PASSWORD}\"}" | \
jq -r '.token')
if [ "$TOKEN" == "null" ] || [ -z "$TOKEN" ]; then
echo "❌ Failed to get NPM API token"
exit 1
fi
# 현재 NPM Proxy Host 설정 확인
echo "📋 Checking current NPM configuration... 📋"
CURRENT_CONFIG=$(curl -s -H "Authorization: Bearer $TOKEN" \
"http://${NPM_HOST}/api/nginx/proxy-hosts/${PROXY_HOST_ID}")
CURRENT_TARGET=$(echo $CURRENT_CONFIG | jq -r '.[0].forward_host // .forward_host')
CURRENT_PORT=$(echo $CURRENT_CONFIG | jq -r '.[0].forward_port // .forward_port')
echo "Current NPM target: $CURRENT_TARGET:$CURRENT_PORT"
# Blue-Green 배포
if [ "$(docker ps -q -f name=zoopzoop-blue)" ]; then
NEW_CONTAINER=zoopzoop-green
OLD_CONTAINER=zoopzoop-blue
NEW_PORT=8082
else
NEW_CONTAINER=zoopzoop-blue
OLD_CONTAINER=zoopzoop-green
NEW_PORT=8081
fi
echo "Starting new container: $NEW_CONTAINER on port $NEW_PORT"
docker run -d --restart unless-stopped \
-p $NEW_PORT:8080 \
--name $NEW_CONTAINER \
--network common \
-e SPRING_PROFILES_ACTIVE=server \
-e SPRING_DATASOURCE_URL="${{secrets.PROD_DB_URL}}" \
-e SPRING_DATASOURCE_USERNAME="${{secrets.PROD_DB_USERNAME}}" \
-e SPRING_DATASOURCE_PASSWORD="${{secrets.PROD_DB_PASSWORD}}" \
-e AWS_S3_BUCKET_NAME="${{secrets.AWS_S3_BUCKET_NAME}}" \
-e AWS_S3_PREFIX="${{secrets.PROD_AWS_S3_PREFIX}}" \
-e SPRING_RABBITMQ_HOST="${{secrets.PROD_RABBITMQ_HOST}}" \
-e SPRING_RABBITMQ_PORT="${{secrets.PROD_RABBITMQ_PORT}}" \
-e SPRING_RABBITMQ_USERNAME="${{secrets.PROD_RABBITMQ_USERNAME}}" \
-e SPRING_RABBITMQ_PASSWORD="${{secrets.PROD_RABBITMQ_PASSWORD}}" \
-e REDIS_HOST="${{secrets.PROD_REDIS_HOST}}" \
-e REDIS_PASSWORD="${{secrets.PROD_REDIS_PASSWORD}}" \
-e KAKAO_CLIENT_ID="${{secrets.OAUTH_KAKAO_CLIENT_ID}}" \
-e GOOGLE_CLIENT_ID="${{secrets.OAUTH_GOOGLE_CLIENT_ID}}" \
-e GOOGLE_CLIENT_SECRET="${{secrets.OAUTH_GOOGLE_CLIENT_SECRET}}" \
-e KAKAO_REDIRECT_URI="${{secrets.PROD_OAUTH_KAKAO_REDIRECT_URI}}" \
-e GOOGLE_REDIRECT_URI="${{secrets.PROD_OAUTH_GOOGLE_REDIRECT_URI}}" \
-e SENTRY_DSN="${{secrets.SENTRY_DSN}}" \
-e OPENAI_API_KEY="${{secrets.PROD_OPENAI_API_KEY}}" \
-e LIVEBLOCKS_SECRET_KEY="${{secrets.LIVEBLOCKS_SECRET_KEY}}" \
-e NAVER_CLIENT_ID="${{secrets.NAVER_CLIENT_ID}}" \
-e NAVER_CLIENT_SECRET="${{secrets.NAVER_CLIENT_SECRET}}" \
-e JWT_SECRET_KEY="${{secrets.JWT_SECRET_KEY}}" \
-e JWT_ACCESS_TOKEN_VALIDITY="${{secrets.JWT_ACCESS_TOKEN_VALIDITY}}" \
-e JWT_REFRESH_TOKEN_VALIDITY="${{secrets.JWT_REFRESH_TOKEN_VALIDITY}}" \
-e FRONT_REDIRECT_DOMAIN="${{secrets.FRONT_REDIRECT_DOMAIN}}" \
-e FRONT_MAIN_DOMAIN="${{secrets.MAIN_DOMAIN}}" \
-e ELASTIC_HOST="${{secrets.PROD_ELASTIC_HOST}}" \
ghcr.io/${{ github.repository }}/zoopzoop:latest
# 헬스체크 (Spring Boot Actuator)
for i in {1..30}; do
if curl -s http://localhost:$NEW_PORT/actuator/health | grep -q '"status":"UP"'; then
echo "✅New container is healthy!"
break
else
echo "Waiting for new container to be healthy..."
sleep 5
fi
if [ $i -eq 30 ]; then
echo "❌ Health check failed. Rolling back..."
docker stop $NEW_CONTAINER || true
docker rm $NEW_CONTAINER || true
exit 1
fi
done
# NPM에서 트래픽 스위칭
echo "🔄 Switching traffic in Nginx Proxy Manager..."
DOMAIN_NAME=$(echo $CURRENT_CONFIG | jq -r '.domain_names[0]')
CERT_ID=$(echo "$CURRENT_CONFIG" | jq -r '.certificate_id')
SWITCH_RESPONSE=$(curl -s -w "%{http_code}" -X PUT "http://${NPM_HOST}/api/nginx/proxy-hosts/${PROXY_HOST_ID}" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"domain_names\": [\"$DOMAIN_NAME\"],
\"forward_scheme\": \"http\",
\"forward_host\": \"$NEW_CONTAINER\",
\"forward_port\": 8080,
\"caching_enabled\": false,
\"block_exploits\": true,
\"advanced_config\": \"\",
\"locations\": [],
\"certificate_id\": $CERT_ID,
\"ssl_forced\": 1,
\"hsts_enabled\": 1,
\"hsts_subdomains\": 1
}")
HTTP_CODE=${SWITCH_RESPONSE: -3}
if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 201 ]; then
echo "✅ Traffic switching completed successfully!"
echo "🎯 NPM now points to: $NEW_CONTAINER:8080"
# 최종 확인
sleep 5
echo "🔍 Final verification..."
VERIFY_CONFIG=$(curl -s -H "Authorization: Bearer $TOKEN" \
"http://${NPM_HOST}/api/nginx/proxy-hosts/${PROXY_HOST_ID}")
VERIFY_TARGET=$(echo $VERIFY_CONFIG | jq -r '.forward_host')
echo "✅ Verified NPM target: $VERIFY_TARGET"
else
echo "❌ Traffic switching failed! HTTP Code: $HTTP_CODE"
echo "Response: ${SWITCH_RESPONSE%???}"
echo "🔄 Rolling back new container..."
docker stop $NEW_CONTAINER || true
docker rm $NEW_CONTAINER || true
exit 1
fi
# 이전 컨테이너 종료 및 제거
echo "Stopping old container: $OLD_CONTAINER"
docker stop $OLD_CONTAINER || true
docker rm $OLD_CONTAINER || true