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
106 changes: 106 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Deploy To EC2

on:
push:
branches:
- "feat/#49-cicd-setup"

jobs:
Deploy:
runs-on: ubuntu-latest
steps:
- name: Github Repository 파일 불러오기
uses: actions/checkout@v4

- name: JDK 21버전 설치
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21

- name: application.yml (통합본) 파일 생성
run: |
mkdir -p ./src/main/resources
# 통합된 yml 하나만 생성. local.yml은 삭제했습니다.
echo '${{ secrets.APPLICATION_YML }}' > ./src/main/resources/application.yml

- name: Create Firebase Secret File
run: |
# 폴더가 없으면 생성
mkdir -p ./src/main/resources/firebase
# Secret에 담긴 텍스트를 파일로 기록
echo '${{ secrets.FIREBASE_JSON }}' > ./src/main/resources/firebase/serviceAccountKey.json

- name: 빌드하기 (테스트 제외)
# RDS 보안 그룹 설정 등에 따라 빌드 중 DB 접속 테스트가 실패할 수 있으므로
# 안정적인 배포를 위해 -x test를 권장.
run: |
chmod +x ./gradlew
./gradlew clean build -x test

- name: AWS credentials 인증 설정
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ap-northeast-2
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

- name: ECR에 로그인
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2

- name: Docker 이미지 생성 및 Push
run: |
docker build -t remu-server .
docker tag remu-server ${{ steps.login-ecr.outputs.registry }}/remu-server:latest
docker push ${{ steps.login-ecr.outputs.registry }}/remu-server:latest

- name: SSH로 EC2에 접속 및 배포 실행
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
script_stop: true
# SSH 환경으로 GitHub Secrets 변수 전달
envs: ECR_REGISTRY,DB_URL,DB_USER,DB_PW,GEMINI_API_KEY,GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,KAKAO_CLIENT_ID,KAKAO_CLIENT_SECRET,JWT_SECRET,BASE_URL,GOOGLE_API_KEY,S3_BUCKET_NAME
script: |
# EC2 내 도커가 ECR에서 이미지를 당겨올 수 있도록 로그인
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REGISTRY

# 기존 컨테이너 정리
docker stop remu-server || true
docker rm remu-server || true

# 새 이미지 pull
docker pull $ECR_REGISTRY/remu-server:latest

# [핵심] RDS 및 환경변수 주입하며 실행
docker run -d --name remu-server -p 8080:8080 \
-e DB_URL=$DB_URL \
-e DB_USER=$DB_USER \
-e DB_PW=$DB_PW \
-e GEMINI_API_KEY=$GEMINI_API_KEY \
-e GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID \
-e GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET \
-e KAKAO_CLIENT_ID=$KAKAO_CLIENT_ID \
-e KAKAO_CLIENT_SECRET=$KAKAO_CLIENT_SECRET \
-e JWT_SECRET=$JWT_SECRET \
-e BASE_URL=$BASE_URL \
-e GOOGLE_API_KEY=$GOOGLE_API_KEY \
-e S3_BUCKET_NAME=$S3_BUCKET_NAME \
$ECR_REGISTRY/remu-server:latest
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
DB_URL: ${{ secrets.DB_URL }}
DB_USER: ${{ secrets.DB_USER }}
DB_PW: ${{ secrets.DB_PW }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
KAKAO_CLIENT_ID: ${{ secrets.KAKAO_CLIENT_ID }}
KAKAO_CLIENT_SECRET: ${{ secrets.KAKAO_CLIENT_SECRET }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
BASE_URL: ${{ secrets.BASE_URL }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ out/
### Custom Configuration ###
# 운영 환경 설정 파일 (보안상 절대 금지)
/src/main/resources/application-prod.yml
/src/main/resources/application-local.yml

# 자체 테스트 코드용 data
/src/main/resources/data.sql
Expand Down
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Java 21 및 Alpine 리눅스 기반 (가볍고 빠름)
FROM eclipse-temurin:21-jdk-alpine

# 빌드 결과물인 jar 파일을 컨테이너 내부로 복사
# build.gradle의 version = '0.0.1-SNAPSHOT' 설정을 반영.
COPY ./build/libs/*-0.0.1-SNAPSHOT.jar project.jar

# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "project.jar"]
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
implementation 'org.springframework.boot:spring-boot-configuration-processor'

// aws S3
implementation 'software.amazon.awssdk:s3:2.20.0'
}

tasks.named('test') {
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/remu/global/common/HealthCheckController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.remu.global.common;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HealthCheckController {

// ELB 상태 검사용 (/health) API
@GetMapping("/health")
public ResponseEntity<String> healthCheck() {
// 상태 코드 200과 함께 메시지 반환
return ResponseEntity.ok("Success Health Check!");
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/remu/global/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.remu.global.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;

@Configuration
public class S3Config {

@Value("${cloud.aws.region.static}")
private String region;

@Bean
public S3Client s3Client() {
return S3Client.builder()
.region(Region.of(region))
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
}

// 임시 링크 생성 전담
@Bean
public S3Presigner s3Presigner() {
return S3Presigner.builder()
.region(Region.of(region))
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
}

}
64 changes: 64 additions & 0 deletions src/main/java/com/remu/global/s3/S3Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.remu.global.s3;

import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;


/**
* 테스트용 컨트롤러라 추후 삭제 예정
*/

@RestController
@RequestMapping("/api/test/s3")
@RequiredArgsConstructor
public class S3Controller {

private final S3Service s3Service;

/**
* 1. 단일 파일 업로드 테스트
* Postman: POST -> Body -> form-data -> Key: "file" (File 타입) / Key: "folder" (Text 타입)
*/
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public S3Service.S3TotalResponse uploadFile(
@RequestPart("file") MultipartFile file,
@RequestParam(value = "folder", defaultValue = "test") S3Directory s3Directory,
@RequestParam Long userId) {

return s3Service.uploadFile(file, s3Directory, userId);
}

/**
* 2. 다중 파일 업로드 테스트
* Postman: POST -> Body -> form-data -> Key: "files" (File 타입으로 여러 개 추가)
*/
@PostMapping(value = "/upload-multiple", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public List<S3Service.S3TotalResponse> uploadFiles(
@RequestPart("files") List<MultipartFile> files,
@RequestParam(value = "folder", defaultValue = "test") S3Directory s3Directory,
@RequestParam Long userId) {

return s3Service.uploadFiles(files, s3Directory, userId);
}

/**
* 3. 파일 조회 테스트 (기존 DB에 저장된 fileName으로 URL 생성)
* Postman: GET -> /api/test/s3/view?fileName=test/uuid_filename.png
*/
@GetMapping("/view")
public S3Service.S3TotalResponse getFileUrl(@RequestParam("fileName") String fileName) {
return s3Service.getPresignedViewUrl(fileName);
}

/**
* 4. 파일 삭제 테스트
*/
@DeleteMapping("/delete")
public S3Service.S3TotalResponse deleteFile(@RequestParam("fileName") String fileName) {
return s3Service.deleteFile(fileName);
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/remu/global/s3/S3Directory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.remu.global.s3;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum S3Directory {
PROFILE("profiles"),
STAR("stars"),
TEST("test"); // 테스트용 폴더

private final String directory;
}
Loading