Skip to content

Commit cd988fd

Browse files
authored
Merge pull request #138 from prgrms-web-devcourse-final-project/develop
5차 메인 머지(10/2) - 백엔드 배포
2 parents 7e7ab91 + de335ef commit cd988fd

55 files changed

Lines changed: 3159 additions & 124 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/deploy.yml

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

.gitignore

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,11 @@ cookies.txt
4747
.DS_Store
4848

4949
### QueryDSL Q클래스 ###
50-
**/generated/
50+
**/generated/
51+
52+
.terraform
53+
.terraform.lock.hcl
54+
terraform.tfstate
55+
terraform.tfstate.backup
56+
secrets.tf
57+
gradle.properties

back/.env.default

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
CUSTOM__JWT__SECRET_KEY=NEED_TO_SET
2+
DB_USERNAME=NEED_TO_SET
3+
DB_PASSWORD=NEED_TO_SET

back/Dockerfile

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# ==========================================
2+
# 첫 번째 스테이지: 빌드 스테이지
3+
# ==========================================
4+
FROM gradle:8.5-jdk21 AS builder
5+
6+
WORKDIR /app
7+
8+
# Gradle 설정 파일 먼저 복사 (캐시 최적화)
9+
COPY build.gradle.kts .
10+
COPY settings.gradle.kts .
11+
12+
# Gradle wrapper 복사
13+
COPY gradle gradle
14+
COPY gradlew .
15+
16+
RUN chmod +x gradlew
17+
18+
# 의존성 다운로드 (캐시 활용)
19+
RUN ./gradlew dependencies --no-daemon
20+
21+
# 소스 코드 및 .env 복사
22+
COPY .env .
23+
COPY src src
24+
25+
# 애플리케이션 빌드
26+
RUN ./gradlew build --no-daemon -x test
27+
28+
# ==========================================
29+
# 두 번째 스테이지: 실행 스테이지
30+
# ==========================================
31+
FROM eclipse-temurin:21-jre-alpine
32+
33+
WORKDIR /app
34+
35+
# 첫 번째 스테이지에서 빌드된 JAR 파일 복사
36+
COPY --from=builder /app/build/libs/*.jar app.jar
37+
38+
# 첫 번째 스테이지에서 .env 파일 복사
39+
COPY --from=builder /app/.env .env
40+
41+
# 환경변수 기본값
42+
ENV SPRING_PROFILES_ACTIVE=prod
43+
44+
# 헬스체크용 curl 설치
45+
RUN apk add --no-cache curl
46+
47+
# 포트 노출
48+
EXPOSE 8080
49+
50+
# 실행
51+
ENTRYPOINT ["java", "-jar", "app.jar"]

back/build.gradle.kts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import org.gradle.kotlin.dsl.implementation
2-
31
plugins {
42
java
53
id("org.springframework.boot") version "3.5.5"
@@ -11,9 +9,8 @@ version = "0.0.1-SNAPSHOT"
119
description = "back"
1210

1311
java {
14-
toolchain {
15-
languageVersion = JavaLanguageVersion.of(21)
16-
}
12+
sourceCompatibility = JavaVersion.VERSION_21
13+
targetCompatibility = JavaVersion.VERSION_21
1714
}
1815

1916
configurations {
@@ -68,6 +65,8 @@ dependencies {
6865
implementation ("software.amazon.awssdk:s3:2.25.0")
6966

7067
implementation ("org.springframework.kafka:spring-kafka")
68+
69+
runtimeOnly("com.mysql:mysql-connector-j")
7170
}
7271

7372
tasks.withType<Test> {

back/src/main/java/com/back/domain/member/member/verification/InMemoryVerificationCodeStore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import java.util.concurrent.ConcurrentHashMap;
1313

1414
@Component
15-
@Profile({"test", "dev"})
15+
@Profile({"test", "dev", "prod"})
1616
@Slf4j
1717
public class InMemoryVerificationCodeStore implements VerificationCodeStore {
1818
private final Map<String, CodeData> store = new ConcurrentHashMap<>();

back/src/main/java/com/back/domain/member/mentor/entity/Mentor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ public void updateCareerYears(Integer careerYears) {
4040
this.careerYears = careerYears;
4141
}
4242

43+
public void updateRating(Double averageRating) {
44+
this.rate = averageRating;
45+
}
46+
4347
public void delete() {
4448
this.isDeleted = true;
4549
}

0 commit comments

Comments
 (0)