Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 65 additions & 5 deletions .github/workflows/Backend-CD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ env:
CONTAINER_1_NAME: relife_1 # 슬롯1(고정 이름)
CONTAINER_2_NAME: relife_2 # 슬롯2(고정 이름)
CONTAINER_PORT: 8080 # 컨테이너 내부 포트(스프링부트)
HEALTH_CHECK_PORT: 8090 # 헬스체크용 포트(임시, 추후 필요 시)
EC2_INSTANCE_TAG_NAME: relife-ec2-1 # 배포 대상 EC2 Name 태그
DOCKER_NETWORK: common # 도커 네트워크
BACKEND_DIR: back # Dockerfile 위치
Expand Down Expand Up @@ -87,15 +88,73 @@ jobs:
prerelease: false

# ---------------------------------------------------------
# 2) 도커 이미지 빌드/푸시 (캐시 최대 활용)
# 2) 커밋 해시 값으로부터 PR 당시 Artifact의 run_id 추출
# ---------------------------------------------------------
findCorrespondingCIrun:
runs-on: ubuntu-latest
outputs:
run_id: ${{ steps.find_run.outputs.run_id }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # .git 히스토리 가져오기

- name: 연관있는 PR 및 CI workflow 실행 찾기
id: find_run
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# 1. 현재 push를 유발한 커밋(merge commit) SHA 가져오기
MERGE_COMMIT_SHA=${{ github.sha }}
echo "Merge commit SHA: $MERGE_COMMIT_SHA"

# 2. 커밋 SHA를 이용해 병합된 PR 번호 찾기
PR_NUMBER=$(gh pr list --search "$MERGE_COMMIT_SHA" --state merged --json number --jq '.[0].number')
if [ -z "$PR_NUMBER" ]; then
echo "⚠️ Could not find a merged PR for this commit"
exit 1
fi
echo "✅ Found PR number: $PR_NUMBER"

# 3. PR의 마지막 커밋(head SHA) 알아내기
PR_HEAD_SHA=$(gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid')
echo "PR head SHA: $PR_HEAD_SHA"

# 4. PR의 마지막 커밋과 CI 워크플로우 파일 이름을 이용해 성공한 CI 실행(run)의 ID 찾기
# CI 워크플로우 파일명과 일치
RUN_ID=$(gh run list --workflow="Backend-CI.yml" --commit="$PR_HEAD_SHA" --status=success --json databaseId --jq '.[0].databaseId')
if [ -z "$RUN_ID" ]; then
echo "⚠️ Could not find a successful CI run for this PR."
exit 1
fi
echo "✅ Found CI run ID: $RUN_ID"

# 5. 찾은 RUN_ID를 output으로 내보내기
echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT

# ---------------------------------------------------------
# 3) 도커 이미지 빌드/푸시
# ---------------------------------------------------------
buildImageAndPush:
name: 도커 이미지 빌드와 푸시
needs: createTagAndRelease
needs: [createTagAndRelease, findCorrespondingCIrun]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Create dist directory
working-directory: back
run: mkdir -p dist

- name: CI 상에서 나온 Artifact 다운로드
uses: dawidd6/action-download-artifact@v6
with:
# 앞 단계에서 찾은 run_id 사용
workflow: Backend-CI.yml
run_id: ${{ needs.findCorrespondingCIrun.outputs.run_id }}
name: relife-backend-jar
path: '${{ env.BACKEND_DIR }}/dist' # BACKEND_DIR 기준

- name: Docker Buildx 설치
uses: docker/setup-buildx-action@v3

Expand Down Expand Up @@ -126,7 +185,7 @@ jobs:
ghcr.io/${{ env.OWNER_LC }}/${{ env.IMAGE_REPOSITORY }}:latest

# ---------------------------------------------------------
# 3) Blue/Green 무중단 배포 (EC2 + NPM 스위치)
# 4) Blue/Green 무중단 배포 (EC2 + NPM 스위치)
# ---------------------------------------------------------
deploy:
name: Blue/Green 무중단 배포
Expand Down Expand Up @@ -187,6 +246,7 @@ jobs:
SLOT1="${{ env.CONTAINER_1_NAME }}"
SLOT2="${{ env.CONTAINER_2_NAME }}"
PORT_IN="${{ env.CONTAINER_PORT }}"
HEALTH_PORT="${{ env.HEALTH_CHECK_PORT }}"
NET="${{ env.DOCKER_NETWORK }}"
ENV_FILE="/tmp/relife.env"

Expand Down Expand Up @@ -268,7 +328,7 @@ jobs:
"${IMAGE}"

# ---------------------------------------------------------
# 5) 헬스체크 (/actuator/health 200 OK까지 대기)
# 5) 헬스체크 (/actuator/health/readiness 200 OK까지 대기)
# ---------------------------------------------------------
echo "⏱ health-check: ${GREEN}"
TIMEOUT=120
Expand All @@ -277,7 +337,7 @@ jobs:
sleep 8 # 초기 부팅 여유

while (( ELAPSED < TIMEOUT )); do
CODE=$(docker exec "${GREEN}" curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${PORT_IN}/actuator/health" || echo 000)
CODE=$(docker exec "${GREEN}" curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${HEALTH_PORT}/actuator/health/readiness" || echo 000)
if [[ "${CODE}" == "200" ]]; then
echo "✅ ${GREEN} is healthy"
break
Expand Down
28 changes: 26 additions & 2 deletions .github/workflows/Backend-CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,17 @@ jobs:
with:
distribution: "graalvm"
java-version: "21"
cache: "gradle"

# Gradle 캐시 설정
- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-

# Gradle 실행 권한 부여
- name: Grant execute permission for gradlew
Expand All @@ -87,4 +97,18 @@ jobs:
uses: mikepenz/action-junit-report@v5
if: always()
with:
report_paths: "**/build/test-results/test/TEST-*.xml"
report_paths: "**/build/test-results/test/TEST-*.xml"

# 빌드 및 테스트가 완료된 JAR 파일을 별도 디렉토리에 복사
- name: Copy JAR file to dist directory
run: |
mkdir -p dist
cp $(ls build/libs/*.jar | grep -v plain | head -n 1) dist/app.jar

# JAR 파일을 아티팩트로 업로드 (CD 파이프라인에서 사용)
- name: Upload artifact
uses: actions/upload-artifact@v4
if: success()
with:
name: relife-backend-jar
path: back/dist/app.jar
3 changes: 2 additions & 1 deletion back/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ build/
.gradle/

# Files
.env.example
.env.local
.env.production
.gitignore
.dockerignore

Expand Down
11 changes: 11 additions & 0 deletions back/.env.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
AWS_REGION=SHOULD_BE_SET_IF_YOU_USE_AWS_DEPENDENCIES
AWS_ACCESS_KEY_ID=SHOULD_BE_SET_IF_YOU_USE_AWS_DEPENDENCIES
AWS_SECRET_ACCESS_KEY=SHOULD_BE_SET_IF_YOU_USE_AWS_DEPENDENCIES
AWS_CLOUD_FRONT_DOMAIN=SHOULD_BE_SET_IF_YOU_USE_AWS_DEPENDENCIES
AWS_S3_BUCKET_NAME=SHOULD_BE_SET_IF_YOU_USE_AWS_DEPENDENCIES
PROD_BASE_DOMAIN=localhost
GOOGLE_CLIENT_ID=MUST_BE_SET_AT_LEAST
GOOGLE_CLIENT_SECRET=MUST_BE_SET_AT_LEAST
GITHUB_CLIENT_ID=MUST_BE_SET_AT_LEAST
GITHUB_CLIENT_SECRET=MUST_BE_SET_AT_LEAST
GEMINI_API_KEY=MUST_BE_SET_AT_LEAST
14 changes: 14 additions & 0 deletions back/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
AWS_REGION=
AWS_CLOUD_FRONT_DOMAIN=
AWS_S3_BUCKET_NAME=
AWS_RDS_ENDPOINT=
AWS_RDS_PORT=
AWS_RDS_DB_NAME=
AWS_RDS_USERNAME=
PASSWORD_1=
PROD_BASE_DOMAIN=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GEMINI_API_KEY=
40 changes: 10 additions & 30 deletions back/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,42 +1,22 @@
#####################################################
# 마지막 수정: 250924
##############################################
# 마지막 수정: 250929
# 작성자: gooraeng
#
# CD 과정에서 사용될 Dockerfile 입니다.
# 멀티 스테이지 적용 (빌드 스테이지, 실행 스테이지)
#####################################################

######### 빌드 스테이지 시작 #########
FROM gradle:jdk-21-and-23-graal-jammy AS builder

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

# Gradle build, Setting 파일 복사
COPY build.gradle.kts settings.gradle.kts gradlew ./
COPY gradle gradle

# Gradlew에 실행권한 부여
RUN chmod +x gradlew

# 종속성 설치
RUN ./gradlew dependencies --no-daemon

# 소스 코드 복사
COPY src src

# 애플리케이션 빌드 (CI 통과 이후 테스트 불필요)
RUN ./gradlew build -x test --no-daemon --build-cache
######### 빌드 스테이지 끝 #########

# 싱글 스테이지 적용 (실행 스테이지)
##############################################
######### 실행 스테이지 시작 #########
FROM container-registry.oracle.com/graalvm/jdk:21
FROM eclipse-temurin:21-jre

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

RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*

# 첫 번째(빌드) 스테이지에서 빌드된 JAR 파일 복사
COPY --from=builder /app/build/libs/*.jar app.jar
COPY dist/app.jar app.jar

# JVM 메모리 설정
# - XX:MaxRAMPercentage=N
Expand Down
27 changes: 22 additions & 5 deletions back/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,39 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client") // OAuth2 Client 추가

// Redis
implementation("org.springframework.boot:spring-boot-starter-data-redis")

// Session
implementation("org.springframework.session:spring-session-data-redis")

// Health Check
implementation("org.springframework.boot:spring-boot-starter-actuator")

// Test
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

// Swagger
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9")

// JWT
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")

// Lombok
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")

// Database
runtimeOnly("com.h2database:h2")
runtimeOnly("org.postgresql:postgresql")
annotationProcessor("org.projectlombok:lombok")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

// Migration
implementation("org.flywaydb:flyway-core:11.11.2")
runtimeOnly("org.flywaydb:flyway-database-postgresql:11.11.2")

// QueryDSL
implementation("io.github.openfeign.querydsl:querydsl-jpa:7.0")
Expand All @@ -62,4 +80,3 @@ dependencies {
tasks.withType<Test> {
useJUnitPlatform()
}

2 changes: 2 additions & 0 deletions back/src/main/java/com/back/BackApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
@EnableCaching
public class BackApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/users-auth/**", "/oauth2/**", "/login/oauth2/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/actuator/**").permitAll()
.anyRequest().authenticated()
)
.logout(logout -> logout
Expand Down
Loading