
이번 글에서는 Spring Boot 프로젝트에서 AWS S3를 활용한 이미지 업로드 기능을 구현하고, 이를 CI/CD를 통해 자동 배포하는 과정을 정리합니다. 이 글을 읽으면, S3를 활용하여 파일을 저장하는 방법과 CI/CD를 통해 자동 배포하는 방법을 익힐 수 있습니다.
먼저, AWS 콘솔에서 S3 버킷을 생성해야 합니다.
S3 콘솔 접속 (AWS S3 콘솔)
새 버킷 생성
버킷 이름 설정 (예: my-s3-bucket)
리전 선택 (예: us-east-1)
퍼블릭 액세스 차단 해제 (필요 시 설정)
생성 버튼 클릭
(2) Spring Boot에서 S3 연동 설정
implementation 'com.amazonaws:aws-java-sdk-s3:1.12.530'
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
cloud:
aws:
credentials:
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
region:
static: us-east-1
s3:
bucket: my-s3-bucket
@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"; // 실패 시 다시 업로드 페이지로
}
}
}
@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();
}
}
}
<!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>
CI/CD를 이용해 자동 배포하는 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 # 애플리케이션 로그 출력
이번 글에서는 AWS S3를 활용한 이미지 업로드 기능을 구현하고, GitHub Actions를 이용해 자동 배포하는 과정을 정리했습니다. CI/CD 파이프라인을 구축하면 코드 변경 시 자동으로 배포되어 더욱 편리하게 개발할 수 있습니다.
더 개선하고 싶은 점이나 궁금한 사항이 있다면 댓글로 남겨주세요! 🚀
[출처]
CI/CD GIF