merge(cd) : 무중단 배포 스크립트 안정성 강화 및 오류 처리 개선 #77
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: DOCKER-CD-STAGING | |
| on: | |
| push: | |
| branches: [ "staging" ] | |
| jobs: | |
| ci: | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| # 1. 소스 코드 체크아웃 | |
| - name: Checkout | |
| uses: actions/checkout@v3 | |
| # 2. JDK 21 설정 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v3 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '21' | |
| # 3. Gradle 캐싱 (빌드 속도 향상) | |
| - name: Gradle Caching | |
| uses: actions/cache@v3 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-gradle- | |
| # 4. Gradle 빌드 및 테스트 실행 | |
| - name: Build and Test with Gradle | |
| run: | | |
| chmod +x gradlew | |
| ./gradlew build | |
| shell: bash | |
| # 5. Docker Hub 로그인 (Repository Secrets 사용) | |
| - name: Login to Docker Hub | |
| uses: docker/login-action@v2.2.0 | |
| with: | |
| username: ${{ secrets.DOCKER_LOGIN_USERNAME }} | |
| password: ${{ secrets.DOCKER_LOGIN_ACCESSTOKEN }} | |
| # 6. Docker 이미지 빌드 및 푸시 | |
| - name: Build and push Docker image for Staging | |
| run: | | |
| docker build -f Dockerfile-staging --platform linux/amd64 -t terningpoint/terning2025-staging . | |
| docker push terningpoint/terning2025-staging | |
| cd: | |
| needs: ci | |
| runs-on: ubuntu-24.04 | |
| environment: staging | |
| steps: | |
| - name: Deploy to Staging Server | |
| uses: appleboy/ssh-action@master | |
| with: | |
| host: ${{ secrets.STAGING_SERVER_IP }} | |
| username: ${{ secrets.STAGING_SERVER_USER }} | |
| key: ${{ secrets.STAGING_SERVER_KEY }} | |
| script: | | |
| set -e | |
| APP_NAME="terning2025-staging" | |
| IMAGE_NAME="terningpoint/terning2025-staging" | |
| NGINX_CONFIG_PATH="/etc/nginx" | |
| SERVICE_URL_INC_PATH="${NGINX_CONFIG_PATH}/conf.d/service-url-staging.inc" | |
| echo "### 1. 최신 Docker 이미지를 pull합니다." | |
| docker pull ${IMAGE_NAME}:latest | |
| echo "### 2. 현재 실행 중인 포트(Blue)와 새로 실행할 포트(Green)를 결정합니다." | |
| IS_BLUE_RUNNING=$(docker ps -q --filter "name=${APP_NAME}" --filter "publish=8080") | |
| if [ -n "$IS_BLUE_RUNNING" ]; then | |
| RUNNING_PORT=8080 | |
| NEW_PORT=8081 | |
| OLD_CONTAINER_NAME="${APP_NAME}-8080" | |
| else | |
| RUNNING_PORT=8081 | |
| NEW_PORT=8080 | |
| OLD_CONTAINER_NAME="${APP_NAME}-8081" | |
| fi | |
| # 최초 배포인지 확인 | |
| if [ -z "$IS_BLUE_RUNNING" ] && [ -z "$(docker ps -q --filter "name=${APP_NAME}" --filter "publish=8081")" ]; then | |
| echo " > 현재 실행중인 서비스가 없습니다. 최초 배포를 시작합니다." | |
| echo " > 새로 실행할 포트(Green): ${NEW_PORT}" | |
| else | |
| echo " > 현재 서비스 포트(Blue): ${RUNNING_PORT}" | |
| echo " > 새로 실행할 포트(Green): ${NEW_PORT}" | |
| fi | |
| echo "### 3. 새로운 버전의 애플리케이션(Green)을 실행합니다." | |
| NEW_CONTAINER_NAME="${APP_NAME}-${NEW_PORT}" | |
| docker run -d --name ${NEW_CONTAINER_NAME} --restart always \ | |
| -p ${NEW_PORT}:8080 \ | |
| -e SPRING_PROFILES_ACTIVE=staging \ | |
| -e SPRING_DATASOURCE_URL='${{ secrets.DB_URL }}' \ | |
| -e SPRING_DATASOURCE_USERNAME=${{ secrets.DB_USERNAME }} \ | |
| -e SPRING_DATASOURCE_PASSWORD=${{ secrets.DB_PASSWORD }} \ | |
| -e SPRING_JPA_DEFAULT_SCHEMA=${{ secrets.SPRING_JPA_DEFAULT_SCHEMA }} \ | |
| -e JWT_SECRET_KEY='${{ secrets.JWT_SECRET_KEY }}' \ | |
| -e JWT_ACCESS_TOKEN_EXPIRED=${{ secrets.JWT_ACCESS_TOKEN_EXPIRED }} \ | |
| -e JWT_REFRESH_TOKEN_EXPIRED=${{ secrets.JWT_REFRESH_TOKEN_EXPIRED }} \ | |
| -e OPERATION_BASE_URL='${{ secrets.OPERATION_BASE_URL }}' \ | |
| -e DISCORD_WEBHOOK_URL='${{ secrets.DISCORD_WEBHOOK_URL }}' \ | |
| -e FIREBASE_SERVICE_KEY_JSON='${{ secrets.FIREBASE_SERVICE_KEY_JSON }}' \ | |
| -e LOGGING_LOCATION=${{ secrets.LOGGING_LOCATION }} \ | |
| -e TZ=Asia/Seoul \ | |
| -v /home/ubuntu:/home/ubuntu/dev-logs \ | |
| ${IMAGE_NAME}:latest | |
| echo "### 4. 헬스 체크를 시작합니다." | |
| sleep 10 | |
| for retry_count in {1..10}; do | |
| echo " > [${retry_count}/10] 서버 상태 체크 중..." | |
| response=$(curl -s http://localhost:${NEW_PORT}/actuator/health) | |
| up_count=$(echo "$response" | grep -c '"status":"UP"') | |
| if [ $up_count -ge 1 ]; then | |
| echo " > ✅ 서버 실행 성공 (포트: ${NEW_PORT})" | |
| break | |
| fi | |
| if [ $retry_count -eq 10 ]; then | |
| echo " > ❌ 서버 헬스체크 실패. 배포를 중단하고 새 컨테이너를 종료합니다." | |
| docker rm -f ${NEW_CONTAINER_NAME} | |
| exit 1 | |
| fi | |
| sleep 5 | |
| done | |
| echo "### 5. Nginx 설정을 변경하여 트래픽을 새 포트(Green)로 전환합니다." | |
| echo "set \$service_url http://127.0.0.1:${NEW_PORT};" | sudo tee ${SERVICE_URL_INC_PATH} | |
| sudo nginx -s reload | |
| echo "### 6. 이전 버전의 컨테이너(Blue)를 종료 및 삭제합니다." | |
| EXISTING_OLD_CONTAINER_ID=$(docker ps -aq --filter "name=${OLD_CONTAINER_NAME}") | |
| if [ -n "$EXISTING_OLD_CONTAINER_ID" ]; then | |
| echo " > 이전 컨테이너(${OLD_CONTAINER_NAME})를 중지하고 삭제합니다." | |
| docker rm -f ${OLD_CONTAINER_NAME} | |
| else | |
| echo " > 이전에 실행되던 컨테이너가 없어 삭제를 건너뜁니다." | |
| fi | |
| echo "### 7. 사용하지 않는 Docker 이미지를 정리합니다." | |
| docker image prune -af | |
| echo "✅ Staging 배포가 성공적으로 완료되었습니다. 현재 서비스 포트: ${NEW_PORT}" |