Skip to content

Commit 7e9f45e

Browse files
committed
feat:gichub actions 테스트
1 parent f0d7af1 commit 7e9f45e

File tree

3 files changed

+330
-13
lines changed

3 files changed

+330
-13
lines changed

.github/workflows/deploy.yml

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
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 "✅ 배포 완료!"

back/Dockerfile

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,49 @@
1+
# ==========================================
2+
# 첫 번째 스테이지: 빌드 스테이지
3+
# ==========================================
4+
FROM gradle:8.5-jdk21 AS builder
5+
6+
WORKDIR /app
7+
8+
# Gradle 설정 파일 먼저 복사 (캐시 최적화)
9+
COPY build.gradle .
10+
COPY settings.gradle .
11+
12+
# Gradle wrapper 복사
13+
COPY gradle gradle
14+
COPY gradlew .
15+
16+
# 의존성 다운로드 (캐시 활용)
17+
RUN ./gradlew dependencies --no-daemon
18+
19+
# 소스 코드 및 .env 복사
20+
COPY .env .
21+
COPY src src
22+
23+
# 애플리케이션 빌드
24+
RUN ./gradlew build --no-daemon -x test
25+
26+
# ==========================================
27+
# 두 번째 스테이지: 실행 스테이지
28+
# ==========================================
129
FROM eclipse-temurin:21-jre-alpine
230

331
WORKDIR /app
432

5-
COPY build/libs/*.jar app.jar
33+
# 첫 번째 스테이지에서 빌드된 JAR 파일 복사
34+
COPY --from=builder /app/build/libs/*.jar app.jar
635

7-
EXPOSE 8080
36+
# 첫 번째 스테이지에서 .env 파일 복사
37+
COPY --from=builder /app/.env .env
838

39+
# 환경변수 기본값
940
ENV SPRING_PROFILES_ACTIVE=prod
1041

42+
# 헬스체크용 curl 설치
43+
RUN apk add --no-cache curl
44+
45+
# 포트 노출
46+
EXPOSE 8080
47+
48+
# 실행
1149
ENTRYPOINT ["java", "-jar", "app.jar"]

0 commit comments

Comments
 (0)