쏭의 개발 블로그

AWS S3/Wasabi 환경 구축 및 파일 저장 구현(Spring) 본문

Back-end

AWS S3/Wasabi 환경 구축 및 파일 저장 구현(Spring)

songu1 2025. 6. 15. 23:08

 

프로젝트에서 이미지, 비디오, 오디오 등 파일을 저장하기 위해 어떤 클라우드 스토리지를 사용할지 고민했다. 프로젝트의 요구사항과 개발 방식, 비용을 고려하여 효율적이고 저렴한 클라우드 스토리지를 선택하고자 했다.

 

이번 포스트에서는 프로젝트에서 사용했던 AWS S3와 Wasabi를 비교하고, 실제로 두 환경을 모두 구성하고 파일 저장 코드를 구현한 내용을 공유하고자 한다. AWS S3 환경 구축과 구현에 대한 블로그들은 많지만, Wasabi를 다루고 AWS S3와 Wasabi 전환을 고려한 자료는 찾기가 쉽지 않아 이참에 포스트를 작성해보았다.

 

[1] AWS S3 vs Wasabi 비교

내가 프로젝트에서 고민한 클라우드 스토리지는 AWS S3와 Wasabi이다.

AWS S3

먼저, AWS S3는 클라우드 기반의 객체 스토리지 서비스로 데이터를 안전하게 저장하고 검색할 수 있는 확장 가능한 솔루션을 제공한다. 파일, 데이터 및 다양한 유형의 미디어를 저장하고 관리할 수 있다.

 

AWS S3에서 크게 2가지 주요 개념이 있다.

  • 버킷 : S3에서 데이터를 저장하는 컨테이너이다. 고유한 이름을 가져야하며, 여러 개의 객체를 포함할 수 있다.
  • 객체 : S3에서 저장되는 데이터 단위이다. 객체는 파일과 메타데이터로 구성되며, 고유한 키로 식별된다.

즉, 버킷에 저장되는 데이터는 모두 객체라고 부르며, S3는 데이터를 인터넷 형태를 통해 객체로 저장하는 서비스라고 보면 된다.

 

AWS S3는 강력한 보안, 확장성, 통합 서비스를 제공한다. 하지만 요금의 경우 저쟝용량과 요청수, 전송량 모두 과금이 된다.

Wasabi

Wasabi는 객체 스토리지만 제공하는 서비스로 AWS S3의 API와 호환되는 객체 스토리지 서비스이다. AWS S3와 동일한 방식으로 데이터를 저장하고 접근할 수 있도록 API 호환성을 제공하는 스토리지 솔루션이라고 할 수 있다.

AWS, GCP처럼 복잡한 계산과 네트워크 기능이 없으며, 데이터 다운로드가 무료이다. S3 API와 호환되어 쉽게 개발이 가능하다.

AWS 대비 훨씬 저렴한 저장 비용을 가진다.

 

AWS S3와 Wasabi 비교

AWS S3와 Wasabi를 비교하면 아래와 같다.

  AWS S3  Wasabi
요금 구조 저장 + 요청 + 전송 모두 과금 저장량 기반 요금 (데이터 송신 무료)
프리티어 있음 (12개월 무료 평가판)
매달 5GB의 스토리지 + 2만건의 GET 요청 + 2000건의 PUT, COPY, POST,LIST 요청 + 100GB의 데이터 송신
없음 (단, 첫 30일 무료 체험 있음)
S3 호환성 AWS S3 자체 API AWS S3와 호환 (S3 API 동일)
저장 비용 과금 과금
데이터 송신 비용 전송량당 과금 무료
요청 요금 과금 무료
지연시간/속도 빠름 (AWS 글로벌 인프라 사용) AWS보다는 다소 느릴 수 있음
정책/권한 설정 IAM 정책, 버킷 정책 등 유연 IAM 또는 버킷 정책 설정
적합한 용도 전체 인프라를 AWS에 둘 경우 적합 파일 저장소로만 사용 시 최고 효율

 

AWS S3는 프리티어를 제공한다는 점과 AWS 인프라를 활용할수 있고, 유연하게 설정할 수 있다는 장점을 가지고 있지만 저장, 요청, 전송 모두 과금이 되고 요금 예측이 어렵다는 단점이 있다. 비용 또한 사용량에 따라 어마어마하게 과금될 수 있다는 것이다.

 

Wasabi의 경우 프리티어를 제공하지는 않지만, 저장 요금만 과금되고, 데이터 송신, 요청은 무료라는 점과 단순한 가격 정책으로 가격 예측이 쉽다는 장점을 가지고 있다. AWS S3와 호환된다. 하지만 파일 저장소로만 사용할 수 있다.

 

 

우리 프로젝트에서는 초기에는 다음과 같이 선택했다.

  • 개발 단계에서는 저장, 요청, 전송량이 많지 않을 것임을 예상하여 AWS 프리티어로 S3를 사용
  • 개발 완료 후 운영 단계에서는 저장, 요청, 전송량이 매우 많을 것이므로 Wasabi를 사용
  • 이에 따라 AWS S3로 파일 저장을 구현한 후 Wasabi로 전환

이에 따라 AWS S3와 Wasabi 모두 호환되는 AWS SDK 방식을 사용하여 파일 저장을 구현했다.

 

하지만 이후 테스트를 많이 수행함에 따라, AWS S3 프리티어를 사용 시 데이터 송신 및 요청에서 과금이 발생할 가능성이 크다는 점이 문제로 떠올랐다. 이러한 이유로 개발 단계에서부터 Wasabi를 사용하게 되었다.

 

[2] AWS S3 환경 구축

AWS 프리티어 계정을 가지고 초기 개발환경에서 사용할 AWS S3 환경을 구축했다.

AWS S3 버킷 생성

먼저, S3 환경을 사용하기 위해 S3 버킷을 생성해야한다.

 

(1) AWS S3에 접속한 후 "버킷 만들기"를 클릭한다.

(2)  버킷 만들기에서 일반 구성을 입력한다.

버킷이름은 전세계에서 유일하게 작성하고, 소문자와 하이픈만 사용하는 것이 권장된다. AWS 리전은 원하는 지역을 선택하면 되는데, 난 서울(ap-northeast-2)를 선택했다.

(3) 다음은 객체 소유권을 "ACL 비활성화됨(권장)”으로 선택했다. 프리티어의 경우 추가적인 비용문제가 발생할 수 있기 때문에 비활성화를 선택하는 것이 좋다.

(4) 퍼블릭 엑세스의 경우, "모든 퍼블릭 억세스 차단"을 선택 해제했다. 파일 업로드 및 조회 서비스를 구현하거나 정적 컨텐츠를 서빙한다면 설정을 해제해야한다.

(5) 버킷 버전 관리 : 개발 환경에서만 사용할 생각이었어서 비활성화를 했다.

(5) 기본 암호화

버킷에 저장되는 모든 새 객체를 암호화하여 저장할지를 선택하는 것이다. 기본 설정을 유지했다.

 

IAM 사용자 생성 및 엑세스 키 발급

다음은, 엑세스 키를 발급하기 위해 IAM 사용자를 생성한다.

 

(1) IAM - 사용자 - 사용자로 이동해서 사용자 생성을 클릭한다.

(2) 사용자 세부 정보 지정에서는 사용자 이름을 작성하면 된다.

 

(3) 권한 설정을 한다.

권한 옵션을 "직접 정책 연결"을 선택하고, 권한 정책은 AmazonS3FullAccess를 검색하여 체크했다. 나는 개발 단계에서만 사용할 것이었기때문에 FullAccess 허용으로선택했다. 

(4) 사용자 생성을 클릭하면 사용자가 생성된다.

(5) 사용자 목록에서 생성한 사용자 이름 s3-dev-user를 클릭한다.

(6) "보안 자격 증명" 항목에서 "엑세스 키 만들기"를 클릭한다. Access Key와 Secret Key가 발급되는데, 이 key들을 절대로 유출되면 안된다. 그리고 secret key의 경우 한번만 조회가능하므로 키를 발급받았을 때 파일로 저장하거나 따로 메모해둬야한다.

 

[3] Wasabi 환경 구축

개발 환경에서 사용할 Wasabi 환경 구축을 해보자.

Wasabi 환경 구축은 AWS S3과 비슷하지만 조금 더 간단하다.

 

Wasabi Bucket 생성

먼저, Wasabi도 S3와 마찬가지로 Bucket을 생성해야한다.

 

(1) Bucket 메뉴에서 "Create Bucket"을 클릭한다.

(2) Bucket Name을 입력한다.

여기서도 Bucket Name은 전세계에서 유일해야한다. Region의 경우 Wasabi에서는 한국 지역이 없기 때문에, 가장 가까운 Tokyo로 설정했다.

(3) 현재는 개발환경에서의 Bucket 설정이므로 나머지 SetProperties, Logging, Replication, Tags 설정의 경우, 모두 비활성화를 했다.

(4) Create Bucket 버튼을 눌럴 버킷을 생성한다.

 

IAM User 생성 및 엑세스 키 발급

다음은 엑세스 키 발급을 위한 IAM User 생성이다. IAM User 생성없이 Root User를 가지고 엑세스 키를 발급받을 수도 있지만, 이 경우 키 노출 위험을 가지고 있다. Root key가 노출되면 전체 계정이 뚫릴 수 있기 때문에 Root User의 Access Key는 발급받지 않는 것을 추천한다. IAM User의 경우 나중에 권한 회수와 삭제가 가능하기 때문에 관리가 쉽다. 따라서 IAM User로 엑세스 키를 발급받아보겠다.

 

(1) User 메뉴에서 "Create User"를 클릭한다.

(2) Create User에서 Username과 Type of Access를 선택한다.

Type of Access에서 API 접근이 필요하므로 Programmatic Access는 반드시 체크한다. Console Access 의 경우, 필요하다면 선택하면 된다.

(3) groups의 경우, 여러 사용자를 묶는 단위로, 공통 권한을 여러 사용자에게 한번에 부여할 때 사용할 수 있다.

사용자마다 읽기/쓰기 권한, 읽기전용 권한, admin 권한 등 권한을 그룹별로 부과하면 된다. 나는 추후 필요 시 선택을 하고자 이 부분은 넘겼다.

 

(4) Policies는 어떤 리소스에 대해 어떤 액션을 허용/거부 할지 정의하는 것이다. 개발 환경이므로 나는 Wasabi에서 기본 제공하는 전체 권한인 WasabiFullAccess 를 선택했다.

(5) "Create User"를 클릭하면 Access Key와 Secret Key가 생성된다. 이 Key들도 S3와 같이 따로 저장해둬야한다.

 

 

현재는 개발 환경에서의 Wasabi Bucket만 생성했기 때문에 여러 설정들을 생략했는데, 추후 운영 환경으로 Bucket을 생성하여 환경을 구축할 예정이다.

 

 

[4] S3 파일 저장

나는 초기에는 AWS S3로 환경을 구축했고, 이후 Wasabi로 전환했다. 이를 고려해서 AWS S3와 Wasabi 전환이 용이하도록 AWS SDK 방법을 적용했다.

 

의존성 추가

S3 파일 저장을 구현하기 위해 AWS SDK v2를 사용했다. AWS SDK는 AWS 공식 SDK를 사용한다. Wasabi는 S3 API 호환이므로 AWS SDK로 개발하면 Wasabi로 전환 시 endpoint만 교체하면 된다. 엔드 포인트 , 버킷, 키 등을 분리해서 사용하므로, 환경변수 또는 설정 파일로 쉽게 교체가 가능하다.

implementation 'software.amazon.awssdk:s3:2.31.61'

 

 

software.amazon.awssdk는 AWS SDK v2이며, AWS S3와 Wasabi 공식 문서에서도 찾아볼 수 있다.

 

application.yml 설정

application.yml

S3Config.java과 sS3FileService에서 필요한 S3/Wasabi 설정 값을 application.yml에 지정해준다.

aws:
    s3:
      endpoint: ${S3_ENDPOINT}
      region: ${S3_REGION}
      access-key: ${S3_ACCESS_KEY}
      secret-key: ${S3_SECRET_KEY}
      bucket: ${S3_BUCKET}

 

application-secrets.yml (gitignore)

AWS S3와 Wasabi 설정은 각각 아래와 같이 지정하면 된다. access_key와 secret_key는 노출되면 안되므로 gitignore한 yml파일에 넣어줘야한다.

# AWS S3
S3_ENDPOINT: https://s3.ap-northeast-2.amazonaws.com
S3_REGION: ap-northeast-2
S3_ACCESS_KEY: {ACCESS_KEY}
S3_SECRET_KEY: {SECRET_KEY}
S3_BUCKET: {BUCKET_NAME}

# Wasabi
S3_ENDPOINT: https://s3.ap-northeast-1.wasabisys.com
S3_REGION: ap-northeast-1
S3_ACCESS_KEY: {ACCESS_KEY}
S3_SECRET_KEY: {SECRET_KEY}
S3_BUCKET: {BUCKET_NAME}

 

 

S3Config

S3Config에서 S3Client를 설정해준다.

@Configuration
public class S3Config {
    @Value("${aws.s3.endpoint}")
    private String endpoint;

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

    @Value("${aws.s3.access-key}")
    private String accessKey;

    @Value("${aws.s3.secret-key}")
    private String secretKey;

    @Bean
    public S3Client s3Client() {
        AwsBasicCredentials basicCredentials = AwsBasicCredentials.create(accessKey,secretKey);
        return S3Client.builder()
                .endpointOverride(URI.create(endpoint))			// Wasabi 전환 고려
                .region(Region.of(region))
                .credentialsProvider(StaticCredentialsProvider.create(basicCredentials))
                .serviceConfiguration(S3Configuration.builder()		// Wasabi 전환 고려
                        .pathStyleAccessEnabled(true)
                        .build())
                .build();
    }
}

코드를 하나하나 설명하면 아래와 같다.

  • AwsBasicCredentials : AWS SDK에서 제공하는 인증 자격 클래스로, AWS S3나 S3 호환 API에 접근하기 위해 필요한 Access Key와 Secret Key를 담는다.
  • endpointOverride(URI.create(endpoint)) : Wasabi 전환을 염두한 설정이다. 이 메서드를 사용하지 않으면 기본적으로 AWS의 공식 엔드포인트로 연결하지만, 엔드포인트를 오버라이드하여 S3 호환 스토리지의 엔드포인트를 설정할 수 있다.
  • region(Region.of(region)) : 사용할 리전을 지정
  • credentialsProvider(StaticCredentialsProvider.create(basicCredentials)) : AWS SDK v2에서는 자격증명 제공자( Credentials Provider)를 사용한다. StaticCredentialsProvider는 고정된 자격증명을 제공하는 Provider로, 내부적으로 AWS 요청마다 인증 헤더를 생성한다.
  • pathStyleAccessEnabled(true) : Path-Style URL을 사용하도록 설정한다. 기본 S3 URL 방식은 Virtual Hosted-Style URL이지만, 일부 S3 호환 스토리지는 virtual-hosted-style을 지원하지 않거나 제한적으로 지원하므로 path-style로 지정해준다.

 

 

S3FileService

@Service
@RequiredArgsConstructor
public class S3FileService {
    private final S3Client s3Client;

    @Value("${aws.s3.bucket}")
    private String bucket;

  // fileKey 형식 : {POST-TYPE}/{RAMDOM-POST-ULID}/{FILE-TYPE}/{FILENAME}
    public void uploadFile(MultipartFile file, String fileKey) throws IOException {
        PutObjectRequest request = PutObjectRequest.builder()
                .bucket(bucket)
                .key(fileKey)
                .contentType(file.getContentType())
                .contentLength(file.getSize())
                .build();

        s3Client.putObject(request, RequestBody.fromBytes(file.getBytes()));
    }

    public byte[] downloadFile(String fileKey) throws IOException {
        GetObjectRequest request = GetObjectRequest.builder()
                .bucket(bucket)
                .key(fileKey)
                .build();

        return s3Client.getObject(request).readAllBytes();
    }

    public void deleteFiles(String fileKey) {
        DeleteObjectRequest request = DeleteObjectRequest.builder()
                .bucket(bucket)
                .key(fileKey)
                .build();

        s3Client.deleteObject(request);
    }
}

S3Client를 사용해 파일 업로드, 다운로드, 삭제 기능을 구현했다.

 

  • PutObjectRequest : 파일 업로드 요청
  • GetObjectRequest : 파일 다운로드 요청
  • DeleteObjectRequest : 파일 삭제 요청

각 요청 객체에는 버킷 이름과 함께 파일을 구분하기 위한 key값을 지정해줘야한다. 여기서 key는 실제 파일 시스템의 경로 개념이라기보다는 S3 내부에서 오브젝트를 식별하기 위한 고유 문자열이다. 경로처럼 보이더라도 하나의 key 문자열로 인식한다.

업로드할 파일의 콘텐츠 타입과 크기를 함께 명시하고, 실제 데이터는 RequestBody.fromBytes()로 전달하면 된다. 다운로드나 삭제 역시 동일한 방식으로 key를 지정해 요청을 보내면 된다.

 

 

이 방법으로 구현하면 AWS S3와 S3 호환 스토리지 모두 잘 동작한다.

 

 

 


참고자료

https://sddev.tistory.com/315

https://bigco-growth-diary.tistory.com/43

https://xeunnie.tistory.com/entry/AWS-S3-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EB%B2%95

https://tao-tech.tistory.com/27