카테고리 없음

Minio란 무엇일까

minus43 2025. 1. 16. 10:46

객체 저장소라는 것을 많이 들어봤을 것이다.
특히 aws를 쓴다면 s3에 대해서 알 것이다.

S3(Simple Storage Service)는 Amazon Web Services(AWS)에서 제공하는 객체 스토리지 서비스입니다. S3는 대규모 비정형 데이터를 저장하고 관리하기 위한 클라우드 스토리지 솔루션으로, 안정성, 확장성, 그리고 비용 효율성을 제공합니다.
-채찍비티

 

객체 스토리지 서비스. 
객체를 저장하는 서비스 
이게 대체 왜 필요할까.

객체 스토리지 서비스는 현대 데이터 저장 요구를 충족시키는 핵심 기술입니다.
비정형 데이터의 증가, 글로벌 접근성, 비용 효율성, 그리고 클라우드 네이티브 애플리케이션의 확산으로 인해 객체 스토리지 서비스는 기존의 파일 스토리지나 데이터베이스를 보완하거나 대체하며 필수적인 요소로 자리 잡았습니다.
"비정형 데이터를 대규모로 저장하고, 효율적으로 관리할 수 있는 인프라가 필요하다면 객체 스토리지가 답이다"라고 할 수 있습니다.
-채찍비티

 

나는 비정형 데이터를 사용 안하는데요..라고 하면 할 말이 없기는 한데
우리는 주로 비정형 데이터를 이용한다, 사진이라던가, 영상이라던가 등
빠른 검색을 위한 기존의 db에서는 다루기 어려운 고용량의 파일이기 때문이다.

확장자도 얼마나 다양한가.

그리고, 일반적인 프론트엔드의 빌드 파일들을 생각해보면,

이 빌드 파일들은 사실 브라우저가 읽기 때문에, 우리가 보는 거라서
프론트엔드를 위한 다른 서버를 만드는 것은...? 방대한 빌드 파일이 아닌 이상 비효율적이다.
짧게 말하면 텍스트 파일 요청하려고 서버를 하나 두는 거는 비효율적이지 않나는 것이다.
어차피 텍스트 파일을 읽는 것은 내 컴퓨터의 웹-브라우저인데...

그래서 이런 비정형적인 데이터와, 정적 파일들을 저장하는 공간으로서의 객체 스토리지 서비스는 필요하다고 생각된다.

현재 진행하고 있는 프로젝트는 아직 클라우드에 올라가지 않은 상태이다.
근데 사진을 업로드하고 그 링크를 반환받을 서비스가 필요했다.
문득 그런 고민이 있었다. 그런 서비스가 s3만 있는가?
그렇다면 반독점법이 아마존을 쪼개 놨을 거다...
클라우드 안쓰는 곳은 서러워서 살겠나.

클라우드에 너무 의존하는 것은 적절한가..?
클라우드 서비스를 이용한다면 한번쯤은 고민해봐야 할 내용이다.

그래서 훌륭하신 커서에게 물었다.
s3와 비슷한 로컬에서 작동할 수 있는 객체 스토리지 서비스가 뭐가 있냐고

minio, localstack, ceph 등을 소개하더라.
첫 번째 나온 minio를 그래서 써봤다.
https://min.io/docs/minio/container/index.html

 

MinIO Object Storage for Container — MinIO Object Storage for Container

Run the following command to install the latest stable MinIO Client package using Homebrew. brew install minio/stable/mc Run the following commands to install the latest stable MinIO Client package using a binary package for Apple chips. curl -O https://dl

min.io

공식문서를 참고하는 건 언제나 좋은 일이다. 그래야 안전하다.
쿠버네티스 설치 공식문서 안보다가 주먹에서 피를 볼 뻔했다.

mkdir -p ~/minio/data

docker run \
   -p 9000:9000 \
   -p 9001:9001 \
   --name minio \
   -v ~/minio/data:/data \
   -e "MINIO_ROOT_USER=ROOTNAME" \
   -e "MINIO_ROOT_PASSWORD=CHANGEME123" \
   quay.io/minio/minio server /data --console-address ":9001"

mkdir~은 minio가 쓸 공간을 미리 만들어주는 거다. 이건 커스텀을 해주도록 하자.
-p는 돌아갈 포트들이다 2개를 제공하는데 별 의미는 없다 2개 다 접속하면 minio로 연결된다.
--name은 친절한 악어씨 같은 동물 이름 컨테이너 보기 싫으면 정해주도록 하자(개취라면 존중한다.)
-v는 아까 만들었던 저장 공간을 마운트해주는 거다
-e는 환경변수인데 잘 보면 알겠지만 루트 계정 아이디랑 비번이다 비번은 최소 8글자니 유의하도록 하자.
그리고 마지막은 어쨋든 9000이든 9001로 접근하든 9001로 바꿔주겠다는 굳건한 의지를 표명하는 것이다

참고로 나는 podman으로 설치했다.
podman에 대한 설명은 나중에 시간 나면 올리도록 하겠다.

설치하고 이제 localhost:9000으로 접속하면

접속화면

이런게 뜬다. 아름답다.
아까 run. 할때 설정한 비밀번호로 접속하면

첫-화면

난 이미 버킷을 만들어놔서 이게 보인다.
왼쪽 사이드에 버킷을 들어가면

버킷-화면

이렇게 버킷에 대한 정보가 보인다
create bucket을 하면 버킷을 만들 수 있다.
이름만 지정하면 된다. 뭐 선택사항은 옆에 친절하게 설명해주니 그거는 각자 해석해서 하자.

어쨌든 버킷을 만들었으니 버킷에 뭔가를 올려봐야 하겠지.
이것도 s3마냥 유저, 정책을 설정할 수 있다. 정말 비슷하다.
근데 지금은 걍 귀찮으니 지금 로그인한 루트 계정의 액세스 키를 생성하자.

사이드에 Access Keys로 가면 키를 생성할 수 있다.
자연스럽게 Access key와 Secret key가 발급되고 json으로 다운받아서 볼수 있고 다운 받은 이후에는 삭제, 설명 수정만 가능할거다. 그니까 꼭 생성할 때 어딘가에 복사하자.

그래서 이거 스프링에서 어떻게 사용해요?
정말 간단했다.

// https://mvnrepository.com/artifact/io.minio/minio
implementation 'io.minio:minio:8.5.15'

그루비-그래이들 기준이다.(메이븐 레포지토리 참고했으니 안심하라.)
이거 추가하고 코끼리 돌리고(싱크)

이건 컨피그 파일 따위 필요 없다.
난 걍 서비스에서 해결 다했다.

package com.buysellgo.userservice.service;

// 필요한 클래스 임포트
import com.buysellgo.userservice.service.dto.ServiceRes; // 서비스 응답을 위한 DTO 클래스
import io.minio.MinioClient; // Minio 클라이언트 클래스
import io.minio.PutObjectArgs; // Minio에 객체를 업로드하기 위한 인자 클래스
import io.minio.RemoveObjectArgs; // Minio에서 객체를 삭제하기 위한 인자 클래스
import jakarta.annotation.PostConstruct; // 빈 초기화 후 실행되는 메서드를 지정하는 어노테이션
import lombok.extern.slf4j.Slf4j; // 로깅을 위한 Lombok 어노테이션
import org.springframework.beans.factory.annotation.Value; // 프로퍼티 값을 주입받기 위한 어노테이션
import org.springframework.web.multipart.MultipartFile; // 파일 업로드를 위한 Spring 클래스
import lombok.RequiredArgsConstructor; // 필수 생성자를 자동으로 생성해주는 Lombok 어노테이션
import org.springframework.stereotype.Service; // Spring 서비스 빈으로 등록하기 위한 어노테이션
import java.util.Map; // Map 인터페이스
import java.util.HashMap; // HashMap 클래스
import java.io.InputStream; // InputStream 클래스

@Slf4j // 로깅 기능을 추가하는 Lombok 어노테이션
@Service // 이 클래스를 Spring의 서비스 빈으로 등록
@RequiredArgsConstructor // final 필드나 @NonNull 필드에 대한 생성자를 자동으로 생성
public class ImgService {

    // Minio 설정값을 application.yml에서 주입받음
    @Value("${minio.endpoint}")
    private String endpoint; // Minio 서버의 엔드포인트 URL
    @Value("${minio.accessKey}")
    private String accessKey; // Minio 서버의 접근 키
    @Value("${minio.secretKey}")
    private String secretKey; // Minio 서버의 비밀 키
    @Value("${minio.bucketName}")
    private String bucketName; // Minio 서버의 버킷 이름

    private MinioClient minioClient; // Minio 클라이언트 객체

    @PostConstruct // 빈 초기화 후 실행되는 메서드
    public void init() {
        // MinioClient 초기화
        this.minioClient = MinioClient.builder()
            .endpoint(endpoint) // Minio 서버의 엔드포인트 설정
            .credentials(accessKey, secretKey) // 접근 키와 비밀 키 설정
            .build(); // MinioClient 객체 생성
    }

    // 파일 업로드 메서드
    public ServiceRes<Map<String, Object>> upload(MultipartFile file) {
        Map<String, Object> data = new HashMap<>(); // 응답 데이터를 담을 Map 객체 생성
        try {
            InputStream inputStream = file.getInputStream(); // 업로드할 파일의 InputStream 가져오기
            String objectName = file.getOriginalFilename(); // 업로드할 파일의 원본 이름 가져오기

            log.info("objectName: {}", objectName); // 파일 이름을 로그로 출력

            // Minio에 파일 업로드
            minioClient.putObject(
                PutObjectArgs.builder()
                    .bucket(bucketName) // 업로드할 버킷 이름 설정
                    .object(objectName) // 업로드할 객체 이름 설정
                    .stream(inputStream, file.getSize(), -1) // 파일의 InputStream과 크기 설정
                    .contentType(file.getContentType()) // 파일의 콘텐츠 타입 설정
                    .build() // PutObjectArgs 객체 생성
            );

            // 업로드된 파일의 URL 생성
            String url = endpoint + "/" + bucketName + "/" + objectName;

            data.put("url", url); // URL을 응답 데이터에 추가
            return ServiceRes.success("파일 업로드 성공", data); // 성공 응답 반환
        } catch (Exception e) {
            data.put("error", e.getMessage()); // 에러 메시지를 응답 데이터에 추가
            return ServiceRes.fail("파일 업로드 실패", data); // 실패 응답 반환
        }
    }

    // 파일 삭제 메서드
    public ServiceRes<Map<String, Object>> delete(String filePath) {
        Map<String, Object> data = new HashMap<>(); // 응답 데이터를 담을 Map 객체 생성
        try {
            log.info("Received filePath: {}", filePath); // 삭제할 파일 경로를 로그로 출력

            // 파일 경로에서 객체 이름 추출
            String objectName = filePath.replace(endpoint + "/" + bucketName + "/", "");

            log.info("Attempting to delete object: {} from bucket: {}", objectName, bucketName); // 삭제 시도 로그 출력

            // Minio에서 파일 삭제
            minioClient.removeObject(
                RemoveObjectArgs.builder()
                    .bucket(bucketName) // 삭제할 버킷 이름 설정
                    .object(objectName) // 삭제할 객체 이름 설정
                    .build() // RemoveObjectArgs 객체 생성
            );

            return ServiceRes.success("파일 삭제 성공", data); // 성공 응답 반환
        } catch (Exception e) {
            log.error("Error deleting object: {}", e.getMessage(), e); // 에러 로그 출력
            data.put("error", e.getMessage()); // 에러 메시지를 응답 데이터에 추가
            return ServiceRes.fail("파일 삭제 실패", data); // 실패 응답 반환
        }
    }
}

간단한 이미지 업로드-삭제 로직이다. 이게 전부다(?)
MinioClient만 설정해주면 된다.

=> 근데 Value로 불러오면서 하다보니 minioClient가 먼저 초기화하는 문제가 있어서 
@PostConstruct 써줬다. 고마워요 커서!

물론 지금 이건 루트 계정 키라 다 되는 거긴하고 
유저를 정하면 뭐 제약같은거 걸 수 있겠지..?

s3 사용할땐 컨피그 하나 만들고 했던거로 기억하는데
이건 걍 5줄도 안되는 생성만하면 간단하게 로직 구현이 되어서...신기했따.

하여튼 s3도 좋은데 
클라우드-프리한 minio도 많관부