Skip to content

Commit ad1c983

Browse files
authored
[FEAT]: 프로덕션 프로파일 추가 등 (#53)
* Chore: CI CD 수정 - CI에서 Artifact를 업로드 - PR 머지 시, 성공한 PR의 해시값으로부터 워크플로우 id를 추출 - 이를 기반으로 artifact 탐색 및 다운로드 후 빌드 시도 * Chore: 도커 이미지 경량화 * fix: actuator 관련 엔드포인트 시큐리티 수정 * chore: 의존성 추가 및 정리 - 추가 (1) 세션 관리 등 * org.springframework.boot:spring-boot-starter-data-redis * org.springframework.session:spring-session-data-redis (2) 헬스 체크 * org.springframework.boot:spring-boot-starter-actuator (3) 데이터베이스 마이그레이션 * org.flywaydb:flyway-core:11.11.2 * org.flywaydb:flyway-database-postgresql:11.11.2 * fix: application.yml 수정 (1) 추가 - redis, session 자동 구성 제외 - .env 파일 상의 값을 참조할 수 있도록 수정 - flyway 관련 설정 추가 (prod 에서 활성화) - open-in-view 옵션 추가 - cookie 설정 시 domain 명시적으로 지정하도록 수정 - forward-headers-strategy 명시 - custom 링크 추가 * feat: @EnableCaching 어노테이션 추가 * feat: application-prod.yml 초안 추가 * fix: 누락된 client 옵션 추가 * feat: .env 예시 추가 - 개발 환경 (.env.local에서 시작) - 운영 환경 (.env.production) - .dockerignore 수정 * Chore: Flyway 관련 README 추가 * Chore: .env.local 수정 * fix: client 옵션 수정 * Chore: .env.local 수정
1 parent ff275e2 commit ad1c983

File tree

12 files changed

+325
-48
lines changed

12 files changed

+325
-48
lines changed

.github/workflows/Backend-CD.yml

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ env:
2121
CONTAINER_1_NAME: relife_1 # 슬롯1(고정 이름)
2222
CONTAINER_2_NAME: relife_2 # 슬롯2(고정 이름)
2323
CONTAINER_PORT: 8080 # 컨테이너 내부 포트(스프링부트)
24+
HEALTH_CHECK_PORT: 8090 # 헬스체크용 포트(임시, 추후 필요 시)
2425
EC2_INSTANCE_TAG_NAME: relife-ec2-1 # 배포 대상 EC2 Name 태그
2526
DOCKER_NETWORK: common # 도커 네트워크
2627
BACKEND_DIR: back # Dockerfile 위치
@@ -87,15 +88,73 @@ jobs:
8788
prerelease: false
8889

8990
# ---------------------------------------------------------
90-
# 2) 도커 이미지 빌드/푸시 (캐시 최대 활용)
91+
# 2) 커밋 해시 값으로부터 PR 당시 Artifact의 run_id 추출
92+
# ---------------------------------------------------------
93+
findCorrespondingCIrun:
94+
runs-on: ubuntu-latest
95+
outputs:
96+
run_id: ${{ steps.find_run.outputs.run_id }}
97+
steps:
98+
- uses: actions/checkout@v4
99+
with:
100+
fetch-depth: 0 # .git 히스토리 가져오기
101+
102+
- name: 연관있는 PR 및 CI workflow 실행 찾기
103+
id: find_run
104+
env:
105+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
106+
run: |
107+
# 1. 현재 push를 유발한 커밋(merge commit) SHA 가져오기
108+
MERGE_COMMIT_SHA=${{ github.sha }}
109+
echo "Merge commit SHA: $MERGE_COMMIT_SHA"
110+
111+
# 2. 커밋 SHA를 이용해 병합된 PR 번호 찾기
112+
PR_NUMBER=$(gh pr list --search "$MERGE_COMMIT_SHA" --state merged --json number --jq '.[0].number')
113+
if [ -z "$PR_NUMBER" ]; then
114+
echo "⚠️ Could not find a merged PR for this commit"
115+
exit 1
116+
fi
117+
echo "✅ Found PR number: $PR_NUMBER"
118+
119+
# 3. PR의 마지막 커밋(head SHA) 알아내기
120+
PR_HEAD_SHA=$(gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid')
121+
echo "PR head SHA: $PR_HEAD_SHA"
122+
123+
# 4. PR의 마지막 커밋과 CI 워크플로우 파일 이름을 이용해 성공한 CI 실행(run)의 ID 찾기
124+
# CI 워크플로우 파일명과 일치
125+
RUN_ID=$(gh run list --workflow="Backend-CI.yml" --commit="$PR_HEAD_SHA" --status=success --json databaseId --jq '.[0].databaseId')
126+
if [ -z "$RUN_ID" ]; then
127+
echo "⚠️ Could not find a successful CI run for this PR."
128+
exit 1
129+
fi
130+
echo "✅ Found CI run ID: $RUN_ID"
131+
132+
# 5. 찾은 RUN_ID를 output으로 내보내기
133+
echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT
134+
135+
# ---------------------------------------------------------
136+
# 3) 도커 이미지 빌드/푸시
91137
# ---------------------------------------------------------
92138
buildImageAndPush:
93139
name: 도커 이미지 빌드와 푸시
94-
needs: createTagAndRelease
140+
needs: [createTagAndRelease, findCorrespondingCIrun]
95141
runs-on: ubuntu-latest
96142
steps:
97143
- uses: actions/checkout@v4
98144

145+
- name: Create dist directory
146+
working-directory: back
147+
run: mkdir -p dist
148+
149+
- name: CI 상에서 나온 Artifact 다운로드
150+
uses: dawidd6/action-download-artifact@v6
151+
with:
152+
# 앞 단계에서 찾은 run_id 사용
153+
workflow: Backend-CI.yml
154+
run_id: ${{ needs.findCorrespondingCIrun.outputs.run_id }}
155+
name: relife-backend-jar
156+
path: '${{ env.BACKEND_DIR }}/dist' # BACKEND_DIR 기준
157+
99158
- name: Docker Buildx 설치
100159
uses: docker/setup-buildx-action@v3
101160

@@ -126,7 +185,7 @@ jobs:
126185
ghcr.io/${{ env.OWNER_LC }}/${{ env.IMAGE_REPOSITORY }}:latest
127186
128187
# ---------------------------------------------------------
129-
# 3) Blue/Green 무중단 배포 (EC2 + NPM 스위치)
188+
# 4) Blue/Green 무중단 배포 (EC2 + NPM 스위치)
130189
# ---------------------------------------------------------
131190
deploy:
132191
name: Blue/Green 무중단 배포
@@ -187,6 +246,7 @@ jobs:
187246
SLOT1="${{ env.CONTAINER_1_NAME }}"
188247
SLOT2="${{ env.CONTAINER_2_NAME }}"
189248
PORT_IN="${{ env.CONTAINER_PORT }}"
249+
HEALTH_PORT="${{ env.HEALTH_CHECK_PORT }}"
190250
NET="${{ env.DOCKER_NETWORK }}"
191251
ENV_FILE="/tmp/relife.env"
192252
@@ -268,7 +328,7 @@ jobs:
268328
"${IMAGE}"
269329
270330
# ---------------------------------------------------------
271-
# 5) 헬스체크 (/actuator/health 200 OK까지 대기)
331+
# 5) 헬스체크 (/actuator/health/readiness 200 OK까지 대기)
272332
# ---------------------------------------------------------
273333
echo "⏱ health-check: ${GREEN}"
274334
TIMEOUT=120
@@ -277,7 +337,7 @@ jobs:
277337
sleep 8 # 초기 부팅 여유
278338
279339
while (( ELAPSED < TIMEOUT )); do
280-
CODE=$(docker exec "${GREEN}" curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${PORT_IN}/actuator/health" || echo 000)
340+
CODE=$(docker exec "${GREEN}" curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${HEALTH_PORT}/actuator/health/readiness" || echo 000)
281341
if [[ "${CODE}" == "200" ]]; then
282342
echo "✅ ${GREEN} is healthy"
283343
break

.github/workflows/Backend-CI.yml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,17 @@ jobs:
6363
with:
6464
distribution: "graalvm"
6565
java-version: "21"
66-
cache: "gradle"
66+
67+
# Gradle 캐시 설정
68+
- name: Cache Gradle dependencies
69+
uses: actions/cache@v4
70+
with:
71+
path: |
72+
~/.gradle/caches
73+
~/.gradle/wrapper
74+
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
75+
restore-keys: |
76+
gradle-${{ runner.os }}-
6777
6878
# Gradle 실행 권한 부여
6979
- name: Grant execute permission for gradlew
@@ -87,4 +97,18 @@ jobs:
8797
uses: mikepenz/action-junit-report@v5
8898
if: always()
8999
with:
90-
report_paths: "**/build/test-results/test/TEST-*.xml"
100+
report_paths: "**/build/test-results/test/TEST-*.xml"
101+
102+
# 빌드 및 테스트가 완료된 JAR 파일을 별도 디렉토리에 복사
103+
- name: Copy JAR file to dist directory
104+
run: |
105+
mkdir -p dist
106+
cp $(ls build/libs/*.jar | grep -v plain | head -n 1) dist/app.jar
107+
108+
# JAR 파일을 아티팩트로 업로드 (CD 파이프라인에서 사용)
109+
- name: Upload artifact
110+
uses: actions/upload-artifact@v4
111+
if: success()
112+
with:
113+
name: relife-backend-jar
114+
path: back/dist/app.jar

back/.dockerignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ build/
99
.gradle/
1010

1111
# Files
12-
.env.example
12+
.env.local
13+
.env.production
1314
.gitignore
1415
.dockerignore
1516

back/.env.local

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
AWS_REGION=SHOULD_BE_SET_IF_YOU_USE_AWS_DEPENDENCIES
2+
AWS_ACCESS_KEY_ID=SHOULD_BE_SET_IF_YOU_USE_AWS_DEPENDENCIES
3+
AWS_SECRET_ACCESS_KEY=SHOULD_BE_SET_IF_YOU_USE_AWS_DEPENDENCIES
4+
AWS_CLOUD_FRONT_DOMAIN=SHOULD_BE_SET_IF_YOU_USE_AWS_DEPENDENCIES
5+
AWS_S3_BUCKET_NAME=SHOULD_BE_SET_IF_YOU_USE_AWS_DEPENDENCIES
6+
PROD_BASE_DOMAIN=localhost
7+
GOOGLE_CLIENT_ID=MUST_BE_SET_AT_LEAST
8+
GOOGLE_CLIENT_SECRET=MUST_BE_SET_AT_LEAST
9+
GITHUB_CLIENT_ID=MUST_BE_SET_AT_LEAST
10+
GITHUB_CLIENT_SECRET=MUST_BE_SET_AT_LEAST
11+
GEMINI_API_KEY=MUST_BE_SET_AT_LEAST

back/.env.production

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
AWS_REGION=
2+
AWS_CLOUD_FRONT_DOMAIN=
3+
AWS_S3_BUCKET_NAME=
4+
AWS_RDS_ENDPOINT=
5+
AWS_RDS_PORT=
6+
AWS_RDS_DB_NAME=
7+
AWS_RDS_USERNAME=
8+
PASSWORD_1=
9+
PROD_BASE_DOMAIN=
10+
GOOGLE_CLIENT_ID=
11+
GOOGLE_CLIENT_SECRET=
12+
GITHUB_CLIENT_ID=
13+
GITHUB_CLIENT_SECRET=
14+
GEMINI_API_KEY=

back/Dockerfile

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,22 @@
1-
#####################################################
2-
# 마지막 수정: 250924
1+
##############################################
2+
# 마지막 수정: 250929
33
# 작성자: gooraeng
44
#
55
# CD 과정에서 사용될 Dockerfile 입니다.
6-
# 멀티 스테이지 적용 (빌드 스테이지, 실행 스테이지)
7-
#####################################################
8-
9-
######### 빌드 스테이지 시작 #########
10-
FROM gradle:jdk-21-and-23-graal-jammy AS builder
11-
12-
# 작업 디렉토리 설정
13-
WORKDIR /app
14-
15-
# Gradle build, Setting 파일 복사
16-
COPY build.gradle.kts settings.gradle.kts gradlew ./
17-
COPY gradle gradle
18-
19-
# Gradlew에 실행권한 부여
20-
RUN chmod +x gradlew
21-
22-
# 종속성 설치
23-
RUN ./gradlew dependencies --no-daemon
24-
25-
# 소스 코드 복사
26-
COPY src src
27-
28-
# 애플리케이션 빌드 (CI 통과 이후 테스트 불필요)
29-
RUN ./gradlew build -x test --no-daemon --build-cache
30-
######### 빌드 스테이지 끝 #########
31-
6+
# 싱글 스테이지 적용 (실행 스테이지)
7+
##############################################
328
######### 실행 스테이지 시작 #########
33-
FROM container-registry.oracle.com/graalvm/jdk:21
9+
FROM eclipse-temurin:21-jre
3410

3511
# 작업 디렉토리 설정
3612
WORKDIR /app
3713

14+
RUN apt-get update && \
15+
apt-get install -y curl && \
16+
rm -rf /var/lib/apt/lists/*
17+
3818
# 첫 번째(빌드) 스테이지에서 빌드된 JAR 파일 복사
39-
COPY --from=builder /app/build/libs/*.jar app.jar
19+
COPY dist/app.jar app.jar
4020

4121
# JVM 메모리 설정
4222
# - XX:MaxRAMPercentage=N

back/build.gradle.kts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,39 @@ dependencies {
3131
implementation("org.springframework.boot:spring-boot-starter-web")
3232
implementation("org.springframework.boot:spring-boot-starter-oauth2-client") // OAuth2 Client 추가
3333

34+
// Redis
35+
implementation("org.springframework.boot:spring-boot-starter-data-redis")
36+
37+
// Session
38+
implementation("org.springframework.session:spring-session-data-redis")
39+
40+
// Health Check
41+
implementation("org.springframework.boot:spring-boot-starter-actuator")
42+
43+
// Test
44+
testImplementation("org.springframework.boot:spring-boot-starter-test")
45+
testImplementation("org.springframework.security:spring-security-test")
46+
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
47+
3448
// Swagger
3549
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9")
3650

3751
// JWT
3852
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
3953
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
4054
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
55+
56+
// Lombok
4157
compileOnly("org.projectlombok:lombok")
58+
annotationProcessor("org.projectlombok:lombok")
59+
60+
// Database
4261
runtimeOnly("com.h2database:h2")
4362
runtimeOnly("org.postgresql:postgresql")
44-
annotationProcessor("org.projectlombok:lombok")
45-
testImplementation("org.springframework.boot:spring-boot-starter-test")
46-
testImplementation("org.springframework.security:spring-security-test")
47-
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
4863

64+
// Migration
65+
implementation("org.flywaydb:flyway-core:11.11.2")
66+
runtimeOnly("org.flywaydb:flyway-database-postgresql:11.11.2")
4967

5068
// QueryDSL
5169
implementation("io.github.openfeign.querydsl:querydsl-jpa:7.0")
@@ -62,4 +80,3 @@ dependencies {
6280
tasks.withType<Test> {
6381
useJUnitPlatform()
6482
}
65-

back/src/main/java/com/back/BackApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.cache.annotation.EnableCaching;
56
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
67

78
@EnableJpaAuditing
89
@SpringBootApplication
10+
@EnableCaching
911
public class BackApplication {
1012

1113
public static void main(String[] args) {

back/src/main/java/com/back/global/security/SecurityConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
5252
.authorizeHttpRequests(auth -> auth
5353
.requestMatchers("/api/v1/users-auth/**", "/oauth2/**", "/login/oauth2/**").permitAll()
5454
.requestMatchers("/admin/**").hasRole("ADMIN")
55+
.requestMatchers("/actuator/**").permitAll()
5556
.anyRequest().authenticated()
5657
)
5758
.logout(logout -> logout

0 commit comments

Comments
 (0)