Skip to content

merge(cd) : 무중단 배포 스크립트 안정성 강화 및 오류 처리 개선 #77

merge(cd) : 무중단 배포 스크립트 안정성 강화 및 오류 처리 개선

merge(cd) : 무중단 배포 스크립트 안정성 강화 및 오류 처리 개선 #77

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}"