AWS S3 이미지 업로드 및 CI/CD 배포하기

Neo-Renaissance·2025년 2월 16일
post-thumbnail

1. 개요

이번 글에서는 Spring Boot 프로젝트에서 AWS S3를 활용한 이미지 업로드 기능을 구현하고, 이를 CI/CD를 통해 자동 배포하는 과정을 정리합니다. 이 글을 읽으면, S3를 활용하여 파일을 저장하는 방법과 CI/CD를 통해 자동 배포하는 방법을 익힐 수 있습니다.

2. AWS S3를 이용한 이미지 업로드 기능 구현

(1) AWS S3 설정

먼저, AWS 콘솔에서 S3 버킷을 생성해야 합니다.

  1. S3 콘솔 접속 (AWS S3 콘솔)

  2. 새 버킷 생성

  • 버킷 이름 설정 (예: my-s3-bucket)

  • 리전 선택 (예: us-east-1)

  • 퍼블릭 액세스 차단 해제 (필요 시 설정)

  • 생성 버튼 클릭

(2) Spring Boot에서 S3 연동 설정

1) AWS S3 의존성 추가

implementation 'com.amazonaws:aws-java-sdk-s3:1.12.530'
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

2) application.yml 설정

cloud:
  aws:
    credentials:
      access-key: ${AWS_ACCESS_KEY}
      secret-key: ${AWS_SECRET_KEY}
    region:
      static: us-east-1
    s3:
      bucket: my-s3-bucket

3) AWS S3 설정 클래스 생성

@Controller
@RequiredArgsConstructor
@RequestMapping("/s3") // S3Controller의 경로를 "/s3"로 한정
public class S3Controller {

    private final S3Uploader s3Uploader;

    @GetMapping("/upload")
    public String uploadPage() {
        return "upload"; // templates/upload.html로 매핑
    }

    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file, Model model) {
        try {
            String url = s3Uploader.uploadFile(file, "images"); // S3에 파일 업로드
            model.addAttribute("url", url); // 업로드된 파일 URL을 모델에 추가
            return "result"; // templates/result.html로 매핑
        } catch (IOException e) {
            model.addAttribute("error", "파일 업로드 실패: " + e.getMessage());
            return "upload"; // 실패 시 다시 업로드 페이지로
        }
    }
}

4) S3 파일 업로드 서비스 구현

@Service
@RequiredArgsConstructor
public class S3Uploader {

    private final AmazonS3 amazonS3;
    @Value("${cloud.aws.s3.bucket}")
    private String bucket;

    public String uploadFile(MultipartFile file, String dirName) throws IOException {
        File convertedFile = convertMultipartFileToFile(file);
        String fileName = dirName + "/" + UUID.randomUUID() + "_" + file.getOriginalFilename();
        String fileUrl = uploadToS3(convertedFile, fileName);
        deleteLocalFile(convertedFile); // 로컬에 임시 파일 삭제
        return fileUrl;
    }

    private String uploadToS3(File file, String fileName) {
        amazonS3.putObject(new PutObjectRequest(bucket, fileName, file)
                .withCannedAcl(CannedAccessControlList.PublicRead)); // 파일을 퍼블릭으로 설정
        return amazonS3.getUrl(bucket, fileName).toString();
    }

    private File convertMultipartFileToFile(MultipartFile file) throws IOException {
        File convertedFile = new File(System.getProperty("java.io.tmpdir") + "/" + file.getOriginalFilename());
        try (FileOutputStream fos = new FileOutputStream(convertedFile)) {
            fos.write(file.getBytes());
        }
        return convertedFile;
    }

    private void deleteLocalFile(File file) {
        if (file.exists()) {
            file.delete();
        }
    }
}

5) 정적 HTML 업로드 폼 (Thymeleaf 기반)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>S3 이미지 업로드</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css">
</head>
<body class="bg-light">
<div class="container mt-5">
    <div class="card shadow-sm">
        <div class="card-header bg-primary text-white">
            <h3>S3 이미지 업로드</h3>
        </div>
        <div class="card-body">
            <form method="post" enctype="multipart/form-data" action="/upload">
                <div class="mb-3">
                    <label for="file" class="form-label">이미지 파일 선택:</label>
                    <input type="file" class="form-control" id="file" name="file" required>
                </div>
                <button type="submit" class="btn btn-success">업로드</button>
            </form>
        </div>
    </div>
</div>
</body>
</html>

3. CI/CD 설정 및 EC2 배포

CI/CD를 이용해 자동 배포하는 GitHub Actions 설정을 추가합니다.

GitHub Actions 설정

name: CI/CD Pipeline  # 파이프라인 이름 설정

on:
  push:
    branches:
      - main  # main 브랜치에 push될 때 실행

jobs:
  build:  # 빌드 작업 정의
    runs-on: ubuntu-latest  # 최신 Ubuntu 환경에서 실행

    steps:
      # 1. 소스 코드 체크아웃
      - name: Checkout code
        uses: actions/checkout@v3

      # 2. JDK 17 설치
      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      # 3. Gradle 실행 권한 부여 (Unix 기반 환경에서 실행 가능하도록 설정)
      - name: Grant execute permission for Gradle
        run: chmod +x ./gradlew

      # 4. 환경 변수 설정 (GitHub Secrets에서 값을 가져와 설정)
      - name: Set environment variables
        run: |
          echo "AWS_ACCESS_KEY=${{ secrets.AWS_ACCESS_KEY }}" >> $GITHUB_ENV
          echo "AWS_SECRET_KEY=${{ secrets.AWS_SECRET_KEY }}" >> $GITHUB_ENV
          echo "AWS_REGION=${{ secrets.AWS_REGION }}" >> $GITHUB_ENV
          echo "AWS_BUCKET=${{ secrets.AWS_BUCKET }}" >> $GITHUB_ENV
          echo "EC2_HOST=${{ secrets.EC2_HOST }}" >> $GITHUB_ENV

      # 5. Gradle을 사용하여 프로젝트 빌드 (테스트 제외)
      - name: Build with Gradle
        run: ./gradlew clean build --exclude-task test

      # 6. DockerHub 로그인 (도커 허브에 접속하여 이미지 업로드 가능하도록 설정)
      - name: Log in to DockerHub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      # 7. Docker 이미지 빌드 및 DockerHub에 푸시
      - name: Build and push Docker image
        run: |
          docker build -t user/s3imageupload:latest .
          docker push user/s3imageupload:latest

  deploy:  # 배포 작업 정의
    runs-on: ubuntu-latest  # 최신 Ubuntu 환경에서 실행
    needs: build  # build 작업이 완료된 후 실행

    steps:
      # 1. EC2 호스트 정보를 출력하여 확인 (디버깅용)
      - name: Debug secrets
        run: echo "EC2_HOST=${{ secrets.EC2_HOST }}"

      # 2. SSH를 이용해 EC2에 접속하여 애플리케이션 배포
      - name: Deploy to EC2
        uses: appleboy/ssh-action@v0.1.7
        with:
          host: ${{ secrets.EC2_HOST }}  # EC2 인스턴스의 퍼블릭 IP 또는 도메인
          username: ubuntu  # EC2의 기본 사용자 이름
          key: ${{ secrets.EC2_KEY }}  # GitHub Secrets에 저장된 SSH 키
          port: 22  # 기본 SSH 포트
          debug: true  # SSH 실행 디버깅 활성화
          script: |
            # Docker 설치 및 최신 패키지 업데이트
            sudo apt update -y
            sudo apt remove -y docker docker-engine docker.io containerd runc || true
            curl -fsSL https://get.docker.com -o get-docker.sh
            sudo sh get-docker.sh
            sudo usermod -aG docker ubuntu  # 현재 사용자를 Docker 그룹에 추가

            # 최신 Docker 이미지 가져오기 및 실행
            docker pull user/s3imageupload:latest  # 최신 이미지 가져오기
            docker stop s3imageupload || true  # 기존 컨테이너 중지 (존재하면)
            docker rm -f s3imageupload || true  # 기존 컨테이너 삭제 (존재하면)
            docker run -d --name s3imageupload -p 8080:8080 \  # 새로운 컨테이너 실행
                -e AWS_ACCESS_KEY=${AWS_ACCESS_KEY} \  # 환경 변수 설정
                -e AWS_SECRET_KEY=${AWS_SECRET_KEY} \
                -e AWS_REGION=${AWS_REGION} \
                -e AWS_BUCKET=${AWS_BUCKET} \
                user/s3imageupload:latest

            # 컨테이너 실행 상태 및 로그 확인
            docker ps -a  # 실행 중인 컨테이너 목록 출력
            docker logs s3imageupload  # 애플리케이션 로그 출력

4. 배포 후 테스트

테스트 성공 예시 (실제 실행 화면)

1) 업로드 페이지

2) 업로드 완료 페이지

3) S3 저장 확인

5. 마무리

이번 글에서는 AWS S3를 활용한 이미지 업로드 기능을 구현하고, GitHub Actions를 이용해 자동 배포하는 과정을 정리했습니다. CI/CD 파이프라인을 구축하면 코드 변경 시 자동으로 배포되어 더욱 편리하게 개발할 수 있습니다.

더 개선하고 싶은 점이나 궁금한 사항이 있다면 댓글로 남겨주세요! 🚀

[출처]
CI/CD GIF

profile
if (실패) { 다시 도전; } else { 성공; }

0개의 댓글