1+ name : Deploy to EC2
2+
3+ # =========================
4+ # 전역 환경변수
5+ # =========================
6+ env :
7+ IMAGE_REPOSITORY : fivelogic # GHCR 이미지 리포지토리명
8+ CONTAINER_NAME : spring-boot # 컨테이너 이름
9+ EC2_INSTANCE_TAG_NAME : devcos-team10-main # 배포 대상 EC2 Name 태그
10+ DOCKER_NETWORK : app-network # 도커 네트워크
11+ BACKEND_DIR : back # Dockerfile 위치 (프로젝트 루트)
12+
13+ on :
14+ push :
15+ paths :
16+ - " .github/workflows/**"
17+ - " src/**"
18+ - " build.gradle"
19+ - " settings.gradle"
20+ - " Dockerfile"
21+ branches :
22+ - main
23+ - chore/107
24+ pull_request :
25+ branches :
26+ - main
27+ - develop
28+
29+ # 권한 최소화/명시화
30+ permissions :
31+ contents : write # 태그/릴리즈
32+ packages : write # GHCR 푸시
33+
34+ # 기본 셸
35+ defaults :
36+ run :
37+ shell : bash
38+
39+ jobs :
40+ # ---------------------------------------------------------
41+ # 1) 테스트
42+ # ---------------------------------------------------------
43+ test :
44+ runs-on : ubuntu-latest
45+
46+ steps :
47+ - name : 코드 체크아웃
48+ uses : actions/checkout@v4
49+
50+ - name : Java 21 설정
51+ uses : actions/setup-java@v4
52+ with :
53+ distribution : ' temurin'
54+ java-version : ' 21'
55+ cache : ' gradle'
56+
57+ - name : Gradle 권한 설정
58+ run : chmod +x ./gradlew
59+
60+ - name : 테스트 실행
61+ run : ./gradlew test
62+
63+ - name : 테스트 결과 업로드
64+ if : always()
65+ uses : actions/upload-artifact@v4
66+ with :
67+ name : test-results
68+ path : build/reports/tests/test/
69+
70+ # ---------------------------------------------------------
71+ # 2) 태그/릴리즈 생성 (main 브랜치만)
72+ # ---------------------------------------------------------
73+ makeTagAndRelease :
74+ needs : test
75+ if : github.ref == 'refs/heads/main' && github.event_name == 'push'
76+ runs-on : ubuntu-latest
77+ outputs :
78+ tag_name : ${{ steps.create_tag.outputs.new_tag }}
79+
80+ steps :
81+ - name : 코드 체크아웃
82+ uses : actions/checkout@v4
83+
84+ # 버전 태그 자동 생성 (vX.Y.Z)
85+ - name : Create Tag
86+ id : create_tag
87+ uses :
mathieudutour/[email protected] 88+ with :
89+ github_token : ${{ secrets.GITHUB_TOKEN }}
90+
91+ # 릴리즈 생성
92+ - name : Create Release
93+ uses : actions/create-release@v1
94+ env :
95+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
96+ with :
97+ tag_name : ${{ steps.create_tag.outputs.new_tag }}
98+ release_name : Release ${{ steps.create_tag.outputs.new_tag }}
99+ body : ${{ steps.create_tag.outputs.changelog }}
100+ draft : false
101+ prerelease : false
102+
103+ # ---------------------------------------------------------
104+ # 3) 도커 이미지 빌드/푸시
105+ # ---------------------------------------------------------
106+ buildImageAndPush :
107+ name : 도커 이미지 빌드와 푸시
108+ needs : makeTagAndRelease
109+ runs-on : ubuntu-latest
110+
111+ steps :
112+ - name : 코드 체크아웃
113+ uses : actions/checkout@v4
114+
115+
116+ - name : .env 파일 생성
117+ env :
118+ DOT_ENV : ${{ secrets.DOT_ENV }}
119+ run : |
120+ mkdir -p "${{ env.BACKEND_DIR }}"
121+ printf "%s" "${DOT_ENV}" > "${{ env.BACKEND_DIR }}/.env"
122+
123+ - name : Docker Buildx 설치
124+ uses : docker/setup-buildx-action@v3
125+
126+ # GHCR 로그인
127+ - name : 레지스트리 로그인
128+ uses : docker/login-action@v3
129+ with :
130+ registry : ghcr.io
131+ username : ${{ github.actor }}
132+ password : ${{ secrets.GITHUB_TOKEN }}
133+
134+ # 저장소 소유자명을 소문자로 (GHCR 경로 표준화)
135+ - name : set lower case owner name
136+ run : |
137+ echo "OWNER_LC=${OWNER,,}" >> "${GITHUB_ENV}"
138+ env :
139+ OWNER : " ${{ github.repository_owner }}"
140+
141+ # 캐시를 최대한 활용하여 빌드 → 버전태그 및 latest 동시 푸시
142+ - name : 빌드 앤 푸시
143+ uses : docker/build-push-action@v6
144+ with :
145+ context : ${{ env.BACKEND_DIR }}
146+ push : true
147+ cache-from : type=gha
148+ cache-to : type=gha,mode=max
149+ tags : |
150+ ghcr.io/${{ env.OWNER_LC }}/${{ env.IMAGE_REPOSITORY }}:${{ needs.makeTagAndRelease.outputs.tag_name }}
151+ ghcr.io/${{ env.OWNER_LC }}/${{ env.IMAGE_REPOSITORY }}:latest
152+
153+ # ---------------------------------------------------------
154+ # 4) 배포
155+ # ---------------------------------------------------------
156+ deploy :
157+ name : 배포
158+ needs : [makeTagAndRelease, buildImageAndPush]
159+ runs-on : ubuntu-latest
160+
161+ steps :
162+ # AWS 자격 구성
163+ - name : AWS 자격 구성
164+ uses : aws-actions/configure-aws-credentials@v4
165+ with :
166+ aws-region : ${{ secrets.AWS_REGION }}
167+ aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }}
168+ aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }}
169+
170+ # Name 태그로 EC2 인스턴스 조회
171+ - name : 인스턴스 ID 가져오기
172+ id : get_instance_id
173+ run : |
174+ INSTANCE_ID=$(aws ec2 describe-instances \
175+ --filters "Name=tag:Name,Values=${{ env.EC2_INSTANCE_TAG_NAME }}" "Name=instance-state-name,Values=running" \
176+ --query "Reservations[].Instances[].InstanceId" --output text)
177+
178+ if [[ -z "${INSTANCE_ID}" || "${INSTANCE_ID}" == "None" ]]; then
179+ echo "❌ 실행 중인 EC2 인스턴스를 찾을 수 없습니다."
180+ exit 1
181+ fi
182+
183+ echo "✅ EC2 인스턴스 발견: ${INSTANCE_ID}"
184+ echo "INSTANCE_ID=${INSTANCE_ID}" >> "${GITHUB_ENV}"
185+
186+ # SSM으로 배포 수행
187+ - name : AWS SSM Send-Command로 배포
188+ uses : peterkimzz/aws-ssm-send-command@master
189+ with :
190+ aws-region : ${{ secrets.AWS_REGION }}
191+ aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }}
192+ aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }}
193+ instance-ids : ${{ env.INSTANCE_ID }}
194+ working-directory : /home/ssm-user
195+ comment : Deploy Spring Boot Application
196+ command : |
197+ set -Eeuo pipefail
198+
199+ # 로그 파일 설정
200+ LOG="/tmp/deploy-$(date +%Y%m%d_%H%M%S).log"
201+ exec > >(awk '{ fflush(); print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }' | tee -a "$LOG")
202+ exec 2> >(awk '{ fflush(); print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }' | tee -a "$LOG" >&2)
203+
204+ echo "🚀 배포 시작..."
205+
206+ # 환경변수 로드
207+ source /etc/environment || true
208+
209+ # 이미지 정보 설정
210+ OWNER_LC="${{ github.repository_owner }}"
211+ OWNER_LC="${OWNER_LC,,}"
212+ IMAGE_TAG='${{ needs.makeTagAndRelease.outputs.tag_name }}'
213+ IMAGE_REPOSITORY='${{ env.IMAGE_REPOSITORY }}'
214+ IMAGE="ghcr.io/${OWNER_LC}/${IMAGE_REPOSITORY}:${IMAGE_TAG}"
215+ CONTAINER_NAME="${{ env.CONTAINER_NAME }}"
216+ NET="${{ env.DOCKER_NETWORK }}"
217+
218+ echo "📦 이미지: ${IMAGE}"
219+ echo "📦 컨테이너: ${CONTAINER_NAME}"
220+
221+ # 프로젝트 디렉토리로 이동
222+ cd ~/WEB6_8_FiveLogic_BE || exit 1
223+
224+
225+ # 최신 이미지 pull
226+ echo "📥 Docker 이미지 다운로드 중..."
227+ docker pull $IMAGE
228+
229+ # 기존 컨테이너 중지 및 삭제
230+ echo "🛑 기존 컨테이너 중지 중..."
231+ docker-compose stop $CONTAINER_NAME || true
232+ docker-compose rm -f $CONTAINER_NAME || true
233+
234+ # docker-compose.yml 이미지 업데이트
235+ sed -i "s|image:.*${IMAGE_REPOSITORY}.*|image: ${IMAGE}|g" docker-compose.yml
236+
237+ # 새 컨테이너 시작
238+ echo "🚀 새 컨테이너 시작 중..."
239+ docker-compose up -d $CONTAINER_NAME
240+
241+ # 헬스체크
242+ echo "🏥 헬스체크 중..."
243+ for i in {1..30}; do
244+ if docker exec $CONTAINER_NAME curl -f http://localhost:8080/health > /dev/null 2>&1; then
245+ echo "✅ 서버 정상 구동!"
246+ break
247+ fi
248+ echo "대기 중... ($i/30)"
249+ sleep 2
250+ done
251+
252+ # 컨테이너 상태 확인
253+ echo "📊 컨테이너 상태:"
254+ docker-compose ps $CONTAINER_NAME
255+
256+ # 최근 로그 출력
257+ echo "📋 최근 로그:"
258+ docker-compose logs --tail=50 $CONTAINER_NAME
259+
260+ # 오래된 이미지 정리
261+ echo "🧹 오래된 이미지 정리 중..."
262+ {
263+ docker images --format '{{.Repository}}:{{.Tag}}' \
264+ | grep -F "ghcr.io/${OWNER_LC}/${IMAGE_REPOSITORY}:" \
265+ | grep -v -F ":${IMAGE_TAG}" \
266+ | grep -v -F ":latest" \
267+ | xargs -r docker rmi
268+ } || true
269+
270+ echo "✅ 배포 완료!"
0 commit comments