Skip to content

Merge pull request #194 from BSSMEOD/main #17

Merge pull request #194 from BSSMEOD/main

Merge pull request #194 from BSSMEOD/main #17

Workflow file for this run

name: CD - Production Deploy
on:
push:
branches: [production]
workflow_dispatch:
inputs:
confirm:
description: '운영 배포를 진행하시겠습니까?'
required: true
type: boolean
default: false
env:
REGISTRY: ghcr.io
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: Java 17 설정
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: 'gradle'
- name: Gradle 실행 권한 부여
run: chmod +x ./gradlew
- name: 애플리케이션 JAR 빌드
run: ./gradlew bootJar --no-daemon -x test
- name: QEMU 설정 (ARM64 크로스 컴파일)
uses: docker/setup-qemu-action@v3
with:
platforms: amd64
- name: 이미지 이름 소문자 변환
id: image_name
run: |
IMAGE_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
echo "name=${IMAGE_NAME}" >> $GITHUB_OUTPUT
echo "소문자 변환된 이미지 이름: ${IMAGE_NAME}"
- name: Docker Buildx 설정
uses: docker/setup-buildx-action@v3
- name: GitHub Container Registry 로그인
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker 메타데이터 추출
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ steps.image_name.outputs.name }}
tags: |
type=raw,value=production
type=sha,prefix=prod-
- name: Docker 이미지 빌드 및 푸시
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=eod-prod-build
cache-to: type=gha,scope=eod-prod-build,mode=max
build-args: |
BUILDKIT_INLINE_CACHE=1
- name: 이미지 다이제스트 출력
run: echo ${{ steps.meta.outputs.digest }}
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: 서버에 SSH 접속 및 Docker Compose 배포
uses: appleboy/[email protected]
with:
host: ${{ secrets.PROD_SSH_HOST }}
username: ${{ secrets.PROD_SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PROD_SSH_PORT || 22 }}
script: |
# 배포 디렉토리 생성 및 이동
mkdir -p ~/eod/prod
cd ~/eod/prod
# GitHub Repository Clone 또는 Pull
if [ -d ".git" ]; then
echo "기존 저장소 업데이트 중..."
git fetch origin production
git reset --hard origin/production
else
echo "저장소 복제 중..."
rm -rf * .* 2>/dev/null || true
git clone -b production https://github.com/${{ github.repository }}.git .
fi
# .env 파일 생성 (GitHub Secrets 사용)
REPO_LOWERCASE=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
cat > .env << EOF
GITHUB_REPOSITORY=${REPO_LOWERCASE}
DOCKER_IMAGE_TAG=production
SPRING_DATASOURCE_URL=${{ secrets.PROD_DB_URL }}
SPRING_DATASOURCE_USERNAME=${{ secrets.PROD_DB_USERNAME }}
SPRING_DATASOURCE_PASSWORD=${{ secrets.PROD_DB_PASSWORD }}
MYSQL_ROOT_PASSWORD=${{ secrets.PROD_MYSQL_ROOT_PASSWORD }}
MYSQL_DATABASE=${{ secrets.PROD_MYSQL_DATABASE }}
MYSQL_USER=${{ secrets.PROD_DB_USERNAME }}
MYSQL_PASSWORD=${{ secrets.PROD_DB_PASSWORD }}
JWT_SECRET=${{ secrets.JWT_SECRET }}
BASE_URL=${{ secrets.PROD_BASE_URL }}
FRONTEND_BASE_URL=${{ secrets.PROD_FRONTEND_BASE_URL }}
LOG_DIR=${{ secrets.PROD_LOG_DIR }}
BSM_CLIENT_ID=${{ secrets.PROD_BSM_CLIENT_ID }}
BSM_CLIENT_SECRET=${{ secrets.PROD_BSM_CLIENT_SECRET }}
BSM_OAUTH_BASE_URL=${{ secrets.BSM_OAUTH_BASE_URL }}
BSM_REDIRECT_URI=${{ secrets.PROD_BSM_REDIRECT_URI }}
FILE_UPLOAD_BASE_URL=${{ secrets.FILE_UPLOAD_BASE_URL }}
EOF
# GitHub Container Registry 로그인
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
# 환경 변수 확인 (디버깅)
echo "GITHUB_REPOSITORY (소문자): ${REPO_LOWERCASE}"
# MySQL 볼륨 존재 여부 확인 및 생성
echo "========================================="
echo "📦 MySQL 볼륨 확인 중..."
if ! docker volume inspect eod_mysql-data-prod >/dev/null 2>&1; then
echo "⚠️ 볼륨이 존재하지 않습니다. 새로 생성합니다..."
docker volume create eod_mysql-data-prod
echo "✅ 볼륨 생성 완료: eod_mysql-data-prod"
else
echo "✅ 볼륨이 이미 존재합니다: eod_mysql-data-prod"
fi
echo "========================================="
# 로그 디렉토리 생성
echo "========================================="
echo "📁 로그 디렉토리 생성 중..."
mkdir -p /eod/prod/logs
mkdir -p /eod/uploads
chmod 777 /eod/uploads
echo "✅ 디렉토리 생성 완료"
echo "========================================="
# Docker Compose로 배포 (production 태그 사용)
echo "========================================="
echo "🚀 PRODUCTION Docker Compose 배포 시작"
echo "========================================="
export DOCKER_IMAGE_TAG=production
for i in 1 2 3; do
echo "Docker 이미지 pull 시도 ${i}/3..."
docker compose -f docker-compose.prod.yml pull && break
echo "Pull 실패, 30초 후 재시도..."
[ $i -lt 3 ] && sleep 30 || { echo "❌ Pull 최종 실패"; exit 1; }
done
docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prod.yml up -d --remove-orphans
# 배포 직후 상태 확인
echo "========================================="
echo "📊 배포 직후 컨테이너 상태:"
docker compose -f docker-compose.prod.yml ps
echo "========================================="
# 컨테이너 시작 대기
echo "컨테이너 시작 대기 중..."
sleep 10
# 사용하지 않는 이미지 정리
docker image prune -af
- name: 배포 상태 확인
uses: appleboy/[email protected]
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT || 22 }}
script: |
cd ~/eod/prod
echo "========================================="
echo "🚀 PRODUCTION 환경 배포 완료"
echo "========================================="
echo "📋 환경 변수 확인 (민감 정보 마스킹):"
cat .env | sed 's/PASSWORD=.*/PASSWORD=***/g' | sed 's/SECRET=.*/SECRET=***/g'
echo "========================================="
echo "🐳 컨테이너 실행 상태:"
docker compose -f docker-compose.prod.yml ps
# 컨테이너 실행 검증 (app 컨테이너가 반드시 떠있어야 함)
if ! docker compose -f docker-compose.prod.yml ps | grep -q "eod-app.*Up"; then
echo "❌ ERROR: app 컨테이너가 실행되지 않았습니다!"
docker compose -f docker-compose.prod.yml logs --tail=100 app
exit 1
fi
echo "✅ app 컨테이너 정상 실행 확인"
echo "========================================="
echo "📊 Docker 이미지 확인:"
docker images | grep eod || true
echo "========================================="
echo "📝 MySQL 로그 (최근 20줄):"
docker compose -f docker-compose.prod.yml logs --tail=20 mysql || true
echo "========================================="
echo "📝 애플리케이션 로그 (최근 50줄):"
docker compose -f docker-compose.prod.yml logs --tail=50 app || true
echo "========================================="
echo "🔍 포트 사용 확인:"
netstat -tuln | grep -E ':(8020|3307)' || true
- name: 배포 완료 알림
if: success()
run: |
IMAGE_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
echo "✅ PRODUCTION Docker Compose 배포 완료!"
echo "서버: ${{ secrets.SSH_HOST }}"
echo "이미지: ${{ env.REGISTRY }}/${IMAGE_NAME}:production"