-
Notifications
You must be signed in to change notification settings - Fork 8
refactor: prod/stage 환경 nginx 블루/그린 배포 방식 도입 #753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,17 +57,17 @@ jobs: | |
| id: image_meta | ||
| run: | | ||
| OWNER_LOWERCASE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') | ||
|
|
||
| # Trigger가 Release인 경우: Release의 Tag Name (예: v1.0.0) 사용 | ||
| if [ "${{ github.event_name }}" == "release" ]; then | ||
| IMAGE_TAG="${{ github.ref_name }}" | ||
| # Trigger가 수동(workflow_dispatch)인 경우: 입력받은 tag_name 사용 | ||
| else | ||
| IMAGE_TAG="${{ inputs.tag_name }}" | ||
| fi | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| echo "Docker Image Tag: $IMAGE_TAG" | ||
|
|
||
| echo "image_name=ghcr.io/${OWNER_LOWERCASE}/solid-connection-server" >> $GITHUB_OUTPUT | ||
| echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT | ||
|
|
||
|
|
@@ -103,7 +103,6 @@ jobs: | |
| packages: read | ||
|
|
||
| steps: | ||
| # 설정 파일 전송을 위해 코드 체크아웃 (서브모듈 불필요) | ||
| - name: Checkout config files | ||
| uses: actions/checkout@v4 | ||
| with: | ||
|
|
@@ -112,44 +111,80 @@ jobs: | |
| docs/infra-config | ||
| sparse-checkout-cone-mode: false | ||
|
|
||
| # --- 설정 파일 전송 --- | ||
| - name: Copy config files to remote | ||
| run: | | ||
| echo "${{ secrets.PRIVATE_KEY }}" > deploy_key.pem | ||
| chmod 600 deploy_key.pem | ||
|
|
||
| scp -i deploy_key.pem \ | ||
| -o StrictHostKeyChecking=no \ | ||
| ./docker-compose.prod.yml \ | ||
| ${{ secrets.USERNAME }}@${{ secrets.HOST }}:/home/${{ secrets.USERNAME }}/solid-connection-prod/ | ||
|
|
||
| # --- 서버에서 Docker Pull 및 재시작 --- | ||
| - name: Run docker compose and apply nginx config | ||
| - name: Blue/Green deploy | ||
| run: | | ||
| ssh -i deploy_key.pem \ | ||
| -o StrictHostKeyChecking=no \ | ||
| ${{ secrets.USERNAME }}@${{ secrets.HOST }} \ | ||
| ' | ||
| set -e | ||
|
|
||
| # 1. 변수 설정 (이전 Job의 Output 사용) | ||
| export OWNER_LOWERCASE=$(echo "${{ github.repository_owner }}" | tr "[:upper:]" "[:lower:]") | ||
| export IMAGE_TAG_ONLY="${{ needs.build-and-push.outputs.image_tag }}" | ||
| export FULL_IMAGE_NAME="ghcr.io/${OWNER_LOWERCASE}/solid-connection-server:${IMAGE_TAG_ONLY}" | ||
|
|
||
| # 2. GHCR 로그인 & Pull | ||
| # App Token 대신 현재 워크플로우의 임시 토큰을 사용합니다. | ||
| echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin | ||
| echo "Pulling new image: $FULL_IMAGE_NAME" | ||
| docker pull $FULL_IMAGE_NAME | ||
|
|
||
| # 3. Spring Boot 앱 재시작 | ||
| echo "Restarting Docker Compose with tag: $IMAGE_TAG_ONLY" | ||
| cd /home/${{ secrets.USERNAME }}/solid-connection-prod | ||
| docker compose -f docker-compose.prod.yml down || true | ||
| OWNER_LOWERCASE=$OWNER_LOWERCASE IMAGE_TAG=$IMAGE_TAG_ONLY docker compose -f docker-compose.prod.yml up -d | ||
|
|
||
| # 6. 정리 | ||
| docker image prune -f | ||
| echo "Deployment finished successfully." | ||
| ' | ||
| -o StrictHostKeyChecking=no \ | ||
| ${{ secrets.USERNAME }}@${{ secrets.HOST }} \ | ||
| ' | ||
| set -e | ||
| OWNER_LOWERCASE=$(echo "${{ github.repository_owner }}" | tr "[:upper:]" "[:lower:]") | ||
| IMAGE_TAG_ONLY="${{ needs.build-and-push.outputs.image_tag }}" | ||
| FULL_IMAGE_NAME="ghcr.io/${OWNER_LOWERCASE}/solid-connection-server:${IMAGE_TAG_ONLY}" | ||
| IMAGE_NAME_BASE="ghcr.io/${OWNER_LOWERCASE}/solid-connection-server" | ||
| WORK_DIR="/home/${{ secrets.USERNAME }}/solid-connection-prod" | ||
| CONTAINER_BASE="solid-connection-server" | ||
|
|
||
| # 1. Active 슬롯 확인 (upstream.conf 기준) | ||
| UPSTREAM_PORT=$(grep -oE "server 127\.0\.0\.1:[0-9]+" /etc/nginx/conf.d/upstream.conf 2>/dev/null | grep -oE "[0-9]+$" || echo "8081") | ||
| if [ "$UPSTREAM_PORT" = "8080" ]; then | ||
| ACTIVE_SLOT="blue"; ACTIVE_PORT=8080; NEW_SLOT="green"; NEW_PORT=8081 | ||
| else | ||
| ACTIVE_SLOT="green"; ACTIVE_PORT=8081; NEW_SLOT="blue"; NEW_PORT=8080 | ||
| fi | ||
| echo "Active: ${ACTIVE_SLOT}(${ACTIVE_PORT}) → Deploy: ${NEW_SLOT}(${NEW_PORT})" | ||
|
|
||
| # 2. Pull 전 디스크 정리 (태그 이미지 최근 2개 유지) | ||
| docker images "${IMAGE_NAME_BASE}" --format "{{.Tag}}" | \ | ||
| grep -v buildcache | sort -r | tail -n +3 | \ | ||
| xargs -I {} docker rmi "${IMAGE_NAME_BASE}:{}" 2>/dev/null || true | ||
| docker image prune -f | ||
|
|
||
| # 3. GHCR 로그인 & Pull | ||
| echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin | ||
| docker pull "${FULL_IMAGE_NAME}" | ||
|
|
||
| # 4. 새 슬롯 잔여 컨테이너 정리 | ||
| docker stop "${CONTAINER_BASE}-${NEW_SLOT}" 2>/dev/null || true | ||
| docker rm "${CONTAINER_BASE}-${NEW_SLOT}" 2>/dev/null || true | ||
|
|
||
| # 5. 새 컨테이너 시작 | ||
| cd "${WORK_DIR}" | ||
| SLOT="${NEW_SLOT}" APP_PORT="${NEW_PORT}" OWNER_LOWERCASE="${OWNER_LOWERCASE}" IMAGE_TAG="${IMAGE_TAG_ONLY}" \ | ||
| docker compose -p "${CONTAINER_BASE}-${NEW_SLOT}" -f docker-compose.prod.yml up -d | ||
|
Hexeong marked this conversation as resolved.
Outdated
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dev는 스크립트 동작을 통일시킬 필요가 있어 보입니다 !
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 반영 완료했습니다! 액츄에이터 노출까지 반영했습니다! |
||
|
|
||
| # 6. 헬스 체크 (앱 기동 대기, 최대 150초) | ||
| # /actuator/health 미노출로 포트 응답 여부로 확인 (HTTP 000 = 연결 불가) | ||
| echo "Waiting for app on port ${NEW_PORT}..." | ||
| for i in $(seq 1 30); do | ||
| HTTP=$(curl -s --connect-timeout 2 -o /dev/null -w "%{http_code}" "http://localhost:${NEW_PORT}/" || true) | ||
| [ "$HTTP" != "000" ] && { echo "App responding (HTTP ${HTTP}, attempt ${i})"; break; } | ||
| [ "$i" = "30" ] && { | ||
| echo "Health check timed out after 150s" >&2 | ||
| docker stop "${CONTAINER_BASE}-${NEW_SLOT}" 2>/dev/null || true | ||
| docker rm "${CONTAINER_BASE}-${NEW_SLOT}" 2>/dev/null || true | ||
| exit 1 | ||
| } | ||
| sleep 5 | ||
| done | ||
|
|
||
| # 7. Nginx upstream 전환 (무중단) | ||
| sudo sed -i "s|server 127.0.0.1:[0-9]*;|server 127.0.0.1:${NEW_PORT};|" /etc/nginx/conf.d/upstream.conf | ||
| sudo nginx -s reload | ||
| echo "Traffic switched → ${NEW_SLOT}(${NEW_PORT})" | ||
|
|
||
| # 8. 구 컨테이너 종료 | ||
| docker compose -p "${CONTAINER_BASE}-${ACTIVE_SLOT}" -f docker-compose.prod.yml down 2>/dev/null || true | ||
|
|
||
| echo "Deployment complete. Active: ${NEW_SLOT}(${NEW_PORT})" | ||
| ' | ||
Uh oh!
There was an error while loading. Please reload this page.