Skip to content

[조재구] sprint8 #221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 37 commits into
base: 조재구
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8f50c6b
refactor: 리팩토링, 코드 리뷰 반영
NINE-J Apr 15, 2025
352ffe0
refactor: Spring Boot Admin 제거
NINE-J Jul 1, 2025
1315d84
add: 애플리케이션 컨테이너화
NINE-J Jul 1, 2025
d73a66c
feat: BinaryContentStorage 고도화 1
NINE-J Jul 1, 2025
f4ce1b4
feat: BinaryContentStorage 고도화 1
NINE-J Jul 1, 2025
63240c0
chore: Object 타입 지양
NINE-J Jul 1, 2025
eb60bc7
feat: 이미지 최적화
NINE-J Jul 2, 2025
af739bd
Merge pull request #1 from NINE-J/조재구-sprint1
NINE-J Jul 5, 2025
85ce716
Merge branch 'main' into 조재구-sprint2
NINE-J Jul 5, 2025
cdb56bd
Merge pull request #2 from NINE-J/조재구-sprint2
NINE-J Jul 5, 2025
4427f5b
Merge branch 'main' into 조재구-sprint3
NINE-J Jul 5, 2025
384adac
Merge pull request #3 from NINE-J/조재구-sprint3
NINE-J Jul 5, 2025
d2cbb99
Merge pull request #4 from NINE-J/조재구-sprint4
NINE-J Jul 5, 2025
878cc6d
Merge pull request #5 from NINE-J/조재구-sprint5
NINE-J Jul 5, 2025
4433341
Merge pull request #6 from NINE-J/조재구-sprint6
NINE-J Jul 5, 2025
448d280
Merge pull request #7 from NINE-J/조재구-sprint7
NINE-J Jul 5, 2025
b0d0762
feat: ECR, ECS 구성 후 배포 테스트
NINE-J Jul 5, 2025
a1a0a70
feat: CI(지속적 통합)을 위한 워크플로우 설정
NINE-J Jul 6, 2025
03c9031
Merge pull request #8 from NINE-J/조재구-sprint8
NINE-J Jul 6, 2025
ac55f00
feat: CD(지속적 배포)를 위한 워크플로우 설정
NINE-J Jul 6, 2025
1ebed45
chore: workflows 폴더 생성
NINE-J Jul 6, 2025
9cf404c
chore: PR 병합 시 누락된 삭제 적용
NINE-J Jul 6, 2025
aab4462
Merge pull request #9 from NINE-J/조재구-sprint8
NINE-J Jul 6, 2025
ff7ddaa
docs: README.md
NINE-J Jul 6, 2025
2e10663
chore: CI 테스트
NINE-J Jul 6, 2025
d11ad20
chore: CodeCov token
NINE-J Jul 6, 2025
266f2e9
Merge pull request #10 from NINE-J/조재구-sprint8
NINE-J Jul 6, 2025
da03920
Update README.md
NINE-J Jul 6, 2025
372d86c
이거 왜 안 되지..?
NINE-J Jul 6, 2025
39bf7d5
Merge branch 'main' into 조재구-sprint8
NINE-J Jul 6, 2025
15e01c2
정신 머리 진짜..
NINE-J Jul 6, 2025
22f2930
chore: jobs 구분에 따른 outputs 정의
NINE-J Jul 6, 2025
45da86b
chore: 돼라.
NINE-J Jul 6, 2025
8b07b16
뭐야.. 지금까지 뭘 한 거야 ㅋㅋ
NINE-J Jul 6, 2025
042aa36
chore: task-definition.json
NINE-J Jul 6, 2025
12df22f
Merge remote-tracking branch 'origin/release' into 조재구-sprint8
NINE-J Jul 6, 2025
6a9d2c7
feat: 프리티어 절약
NINE-J Jul 7, 2025
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
45 changes: 45 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Gradle build directories (기존 빌드 결과는 제외, Docker에서 새로 빌드)
build/
.gradle/
gradle-app.setting

# gradle/wrapper는 Docker 빌드에 필요하므로 포함

# IDE files
.idea/
.vscode/
*.iml
*.ipr
*.iws

# Git (Git 히스토리는 Docker 이미지에 불필요)
.git/
.gitignore

# Docker files (Docker 빌드 시 자기 자신은 불필요)
Dockerfile
.dockerignore
docker-compose.yml

# Environment files (.env는 런타임에 환경변수로 전달)
.env
.env.*

# Documentation
README.md
*.md

# Logs
*.log

# OS generated files
.DS_Store
Thumbs.db

# Test resources
src/test/resources/images/

# Temporary files
*.tmp
*.swp
*.bak
97 changes: 97 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: CD - Deploy to ECS

on:
# release 브랜치에 push될 때 워크플로우 실행
push:
branches: [release]

env:
# AWS 및 ECS 관련 환경 변수 (GitHub Variables에서 관리)
AWS_REGION: ${{ vars.AWS_REGION }}
ECR_REPOSITORY_URI: ${{ vars.ECR_REPOSITORY_URI }}
ECS_CLUSTER: ${{ vars.ECS_CLUSTER }}
ECS_SERVICE: ${{ vars.ECS_SERVICE }}
ECS_TASK_DEFINITION: ${{ vars.ECS_TASK_DEFINITION }}
ECS_CONTAINER_NAME: ${{ vars.ECS_CONTAINER_NAME }}

jobs:
build-and-push:
runs-on: ubuntu-latest
outputs:
image: ${{ steps.build-image.outputs.image }}
steps:
# 코드 체크아웃
- name: Checkout code
uses: actions/checkout@v4

# JDK 17 환경 세팅
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: '17'

# AWS 인증 (Public ECR용, 리전: us-east-1)
- name: Login to AWS (Public ECR)
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
aws-region: us-east-1

# Public ECR에 Docker 로그인
- name: Login to Amazon ECR (Public)
run: |
aws ecr-public get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin $ECR_REPOSITORY_URI
# Docker 이미지 빌드 및 푸시, 이미지 태그는 latest와 커밋 해시
- name: Build and push Docker image
id: build-image
env:
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REPOSITORY_URI:latest -t $ECR_REPOSITORY_URI:$IMAGE_TAG .
docker push $ECR_REPOSITORY_URI:latest
docker push $ECR_REPOSITORY_URI:$IMAGE_TAG
echo "image=$ECR_REPOSITORY_URI:$IMAGE_TAG" >> $GITHUB_OUTPUT
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
# 코드 체크아웃
- name: Checkout code
uses: actions/checkout@v4

# AWS 인증 (ECS 작업용, 리전: AWS_REGION)
- name: Configure AWS credentials (ECS)
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
aws-region: ${{ env.AWS_REGION }}

# 태스크 정의 파일에서 컨테이너 이미지 정보를 새 이미지로 업데이트
- name: Render new ECS task definition
id: render-task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: ${{ env.ECS_CONTAINER_NAME }}
image: ${{ needs.build-and-push.outputs.image }}

# 프리티어 절약을 위해 서비스 중단
- name: Stop running ECS tasks (프리티어 절약)
run: |
aws ecs update-service --cluster $ECS_CLUSTER --service $ECS_SERVICE --desired-count 0
sleep 30
# 새 태스크 정의로 서비스 재시작
# - name: Deploy Amazon ECS task definition
# uses: aws-actions/amazon-ecs-deploy-task-definition@v1
# with:
# task-definition: ${{ steps.render-task-def.outputs.task-definition }}
# service: ${{ env.ECS_SERVICE }}
# cluster: ${{ env.ECS_CLUSTER }}
# wait-for-service-stability: true
44 changes: 44 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: CI - Test new source

on:
# main 브랜치로 PR이 생성될 때 워크플로우 실행
pull_request:
branches: [main]

env:
AWS_S3_ACCESS_KEY: ${{ secrets.AWS_S3_ACCESS_KEY }}
AWS_S3_SECRET_KEY: ${{ secrets.AWS_S3_SECRET_KEY }}
AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
AWS_S3_REGION: ap-northeast-2

jobs:
test:
runs-on: ubuntu-latest

steps:
# PR 브랜치의 코드를 체크아웃
- name: Checkout code
uses: actions/checkout@v4

# JDK 17 환경 세팅
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: '17'

# gradlew 실행 권한 부여 (리눅스 환경)
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew

# 테스트 및 커버리지 리포트 생성
- name: Run tests with coverage
run: ./gradlew test jacocoTestReport

# Codecov에 커버리지 업로드
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: build/reports/jacoco/test/jacocoTestReport.xml
fail_ci_if_error: true
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,8 @@ data

### logs ###
logs
*.log
*.log

### Docker ###
.env*
*.env
48 changes: 48 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -------------------------------------------------------------------
# 1단계: 빌드 스테이지 (빌드 도구 포함)
# Amazon Corretto 17 베이스 이미지 사용
FROM amazoncorretto:17 AS build

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

# 프로젝트 정보를 환경 변수로 설정
ENV PROJECT_NAME=discodeit
ENV PROJECT_VERSION=1.2-M8

# Gradle Wrapper와 설정 파일들을 먼저 복사 (Docker 레이어 캐싱 최적화)
COPY gradlew gradlew.bat build.gradle settings.gradle ./
COPY gradle/ gradle/

# 의존성만 먼저 다운로드 (캐시 활용)
RUN chmod +x ./gradlew && ./gradlew dependencies --no-daemon || return 0

# 소스 코드 복사
COPY src/ src/

# 애플리케이션 빌드 (테스트 제외)
RUN ./gradlew build -x test --no-daemon

# -------------------------------------------------------------------
# 2단계: 런타임 스테이지 (경량 이미지)
FROM amazoncorretto:17-alpine AS runtime

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

# curl 설치
RUN apk add --no-cache curl

# 프로젝트 정보를 환경 변수로 설정
ENV PROJECT_NAME=discodeit
ENV PROJECT_VERSION=1.2-M8

# 빌드 결과물만 복사 (최종 JAR 파일만 포함)
COPY --from=build /app/build/libs/${PROJECT_NAME}-${PROJECT_VERSION}.jar ./app.jar

# 기본 포트 노출 (실제 포트는 SERVER_PORT 환경변수로)
EXPOSE 80

# 애플리케이션 실행을 위한 ENTRYPOINT와 CMD 조합
ENTRYPOINT ["sh", "-c"]
CMD ["java ${JVM_OPTS} -jar app.jar"]
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Sprint-M8

- [CodeCov](https://app.codecov.io/)를 통해 테스트 커버리지 뱃지를 README에 추가.

[![codecov](https://codecov.io/gh/nine-j/3-sprint-mission/branch/main/graph/badge.svg)](https://codecov.io/gh/nine-j/3-sprint-mission)
34 changes: 21 additions & 13 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

group = 'com.sprint.mission'
version = '0.0.1-SNAPSHOT'
version = '1.2-M8'

java {
toolchain {
Expand All @@ -29,43 +29,51 @@ repositories {
}

dependencies {
// Spring Boot
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'de.codecentric:spring-boot-admin-starter-client:3.4.5'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.6'
// Hibernate
implementation 'org.hibernate.validator:hibernate-validator:9.0.1.Final'

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// Logging
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
implementation 'org.apache.logging.log4j:log4j-layout-template-json:2.20.0'
implementation 'org.apache.logging.log4j:log4j-jul' // Tomcat log

// DB
implementation 'org.postgresql:postgresql:42.7.5'
runtimeOnly 'com.h2database:h2'

// Log4j2
runtimeOnly 'org.springframework.boot:spring-boot-starter-log4j2'
implementation 'org.apache.logging.log4j:log4j-layout-template-json:2.20.0'
// External API
implementation 'software.amazon.awssdk:s3:2.31.7'

// Tomcat log
implementation 'org.apache.logging.log4j:log4j-jul'
// Docs
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.6'

// p6spy
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.10.0'

// Lombok과 MapStruct를 동시에 사용하는 경우 Lombok이 제대로 동작하지 않는 문제. MapStruct 위에 배치한다.
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

// MapStruct
implementation 'org.mapstruct:mapstruct:1.6.3'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3'

// 테스트
// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'com.h2database:h2'

testImplementation 'io.github.cdimascio:dotenv-java:3.0.2' // .env 파일 로드용
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.withType(JavaCompile).configureEach {
tasks.withType(JavaCompile) {
options.compilerArgs += ["-parameters"]
options.compilerArgs += ['--add-modules', 'java.logging']
}

Expand Down
67 changes: 67 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
version: "3.8"

services:
# PostgreSQL 데이터베이스 서비스
postgres:
image: postgres:15-alpine
container_name: discodeit-postgres
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/schema.sql
networks:
- discodeit-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5

# 애플리케이션 서비스
app:
build:
context: .
dockerfile: Dockerfile
image: discodeit:local-slim # 빌드 후 local-slim 태그로 생성
container_name: discodeit-app
environment:
SPRING_PROFILES_ACTIVE: dev # 개발 환경용
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB}
SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER}
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
SERVER_PORT: ${APP_INTERNAL_PORT:-80}
JVM_OPTS: ${JVM_OPTS:-}
# Storage Configuration
STORAGE_TYPE: ${STORAGE_TYPE:-local}
STORAGE_LOCAL_ROOT_PATH: ${STORAGE_LOCAL_ROOT_PATH:-storage}
# AWS S3 Configuration
AWS_S3_ACCESS_KEY: ${AWS_S3_ACCESS_KEY}
AWS_S3_SECRET_KEY: ${AWS_S3_SECRET_KEY}
AWS_S3_REGION: ${AWS_S3_REGION}
AWS_S3_BUCKET: ${AWS_S3_BUCKET}
AWS_S3_PRESIGNED_URL_EXPIRATION: ${AWS_S3_PRESIGNED_URL_EXPIRATION:-600}
ports:
- "${APP_EXTERNAL_PORT:-8081}:${APP_INTERNAL_PORT:-80}"
volumes:
- .discodeit/storage:/app/storage
depends_on:
postgres:
condition: service_healthy
networks:
- discodeit-network
restart: unless-stopped

# 볼륨 정의
volumes:
postgres_data:
driver: local

# 네트워크 정의
networks:
discodeit-network:
driver: bridge
Loading