MinIO 완벽 가이드: 설치부터 Node.js 연동까지

MinIO란 무엇인가?

MinIO는 오픈소스 기반의 고성능 객체 스토리지 서버입니다. AWS S3와 호환되는 API를 제공하여 클라우드 네이티브 애플리케이션을 위한 완벽한 스토리지 솔루션으로 자리잡고 있습니다.

왜 MinIO를 사용해야 할까?

1. AWS S3 호환성

MinIO는 S3 API와 완벽하게 호환됩니다. 즉, 기존 S3 클라이언트 라이브러리를 그대로 사용할 수 있어 학습 곡선이 낮고 마이그레이션이 쉽습니다.

2. 비용 절감

클라우드 스토리지 비용이 부담되시나요? MinIO는 온프레미스나 프라이빗 클라우드에서 직접 운영할 수 있어 장기적으로 비용을 크게 절감할 수 있습니다.

3. 개발 환경 구축의 용이성

로컬 개발 환경에서 S3와 동일한 API를 사용하며 테스트할 수 있습니다. 실제 AWS 계정 없이도 개발과 테스트가 가능합니다.

4. 높은 성능

Go 언어로 작성되어 가볍고 빠르며, 멀티 테넌시와 고가용성을 지원합니다.

5. 쿠버네티스 친화적

클라우드 네이티브 아키텍처에 최적화되어 있어 컨테이너 환경에서 쉽게 배포하고 관리할 수 있습니다.

Docker로 MinIO 설치하기

Docker를 이용하면 MinIO를 몇 분 안에 실행할 수 있습니다.

기본 설치

docker run -d \
  -p 9000:9000 \
  -p 9001:9001 \
  --name minio \
  -e MINIO_ROOT_USER=minioadmin \
  -e MINIO_ROOT_PASSWORD=minioadmin \
  -v ~/minio/data:/data \
  minio/minio server /data --console-address ":9001"

포트 설명

  • 9000: MinIO API 서버 포트 (S3 호환 API)
  • 9001: MinIO Console UI 포트 (웹 관리 콘솔)

MinIO Console 접속

브라우저에서 http://localhost:9001로 접속합니다.

로그인 정보:

  • Username: minioadmin
  • Password: minioadmin

Console에서 버킷 생성하기

  1. 왼쪽 메뉴에서 Buckets 클릭
  2. Create Bucket 버튼 클릭
  3. 버킷 이름 입력 (예: test-bucket)
  4. Create 버튼 클릭

이제 MinIO가 정상적으로 작동하는 것을 확인했습니다!

Node.js에서 MinIO 사용하기

실제 애플리케이션에서 MinIO를 어떻게 사용하는지 알아보겠습니다.

1. 패키지 설치

npm install minio

2. MinIO 클라이언트 초기화

const Minio = require('minio');

const minioClient = new Minio.Client({
  endPoint: 'localhost',
  port: 9000,
  useSSL: false,
  accessKey: 'minioadmin',
  secretKey: 'minioadmin'
});

3. 버킷 생성

async function createBucket(bucketName) {
  try {
    const exists = await minioClient.bucketExists(bucketName);
    
    if (exists) {
      console.log(`버킷 '${bucketName}'이 이미 존재합니다.`);
    } else {
      await minioClient.makeBucket(bucketName, 'us-east-1');
      console.log(`버킷 '${bucketName}'이 생성되었습니다.`);
    }
  } catch (err) {
    console.error('버킷 생성 중 오류:', err);
  }
}

createBucket('my-bucket');

4. 파일 업로드

async function uploadFile(bucketName, objectName, filePath) {
  try {
    const metaData = {
      'Content-Type': 'application/octet-stream'
    };
    
    await minioClient.fPutObject(bucketName, objectName, filePath, metaData);
    console.log(`파일 '${objectName}'이 업로드되었습니다.`);
  } catch (err) {
    console.error('파일 업로드 중 오류:', err);
  }
}

uploadFile('my-bucket', 'test.txt', './test.txt');

5. 파일 다운로드

async function downloadFile(bucketName, objectName, downloadPath) {
  try {
    await minioClient.fGetObject(bucketName, objectName, downloadPath);
    console.log(`파일 '${objectName}'이 다운로드되었습니다.`);
  } catch (err) {
    console.error('파일 다운로드 중 오류:', err);
  }
}

downloadFile('my-bucket', 'test.txt', './downloaded-test.txt');

6. 파일 목록 조회

async function listObjects(bucketName) {
  try {
    const stream = minioClient.listObjects(bucketName, '', true);
    
    stream.on('data', (obj) => {
      console.log('파일:', obj.name, '크기:', obj.size);
    });
    
    stream.on('error', (err) => {
      console.error('목록 조회 중 오류:', err);
    });
  } catch (err) {
    console.error('오류:', err);
  }
}

listObjects('my-bucket');

7. 파일 삭제

async function deleteFile(bucketName, objectName) {
  try {
    await minioClient.removeObject(bucketName, objectName);
    console.log(`파일 '${objectName}'이 삭제되었습니다.`);
  } catch (err) {
    console.error('파일 삭제 중 오류:', err);
  }
}

deleteFile('my-bucket', 'test.txt');

8. Presigned URL 생성 (임시 접근 링크)

async function getPresignedUrl(bucketName, objectName, expirySeconds = 3600) {
  try {
    const url = await minioClient.presignedGetObject(
      bucketName, 
      objectName, 
      expirySeconds
    );
    console.log('다운로드 URL:', url);
    return url;
  } catch (err) {
    console.error('URL 생성 중 오류:', err);
  }
}

getPresignedUrl('my-bucket', 'test.txt', 7200); // 2시간 유효

실전 활용 예제: Express.js 파일 업로드

const express = require('express');
const multer = require('multer');
const Minio = require('minio');
const fs = require('fs');

const app = express();
const upload = multer({ dest: 'uploads/' });

const minioClient = new Minio.Client({
  endPoint: 'localhost',
  port: 9000,
  useSSL: false,
  accessKey: 'minioadmin',
  secretKey: 'minioadmin'
});

const BUCKET_NAME = 'uploads';

// 버킷 초기화
(async () => {
  const exists = await minioClient.bucketExists(BUCKET_NAME);
  if (!exists) {
    await minioClient.makeBucket(BUCKET_NAME, 'us-east-1');
  }
})();

// 파일 업로드 엔드포인트
app.post('/upload', upload.single('file'), async (req, res) => {
  try {
    const file = req.file;
    const objectName = `${Date.now()}-${file.originalname}`;
    
    await minioClient.fPutObject(
      BUCKET_NAME,
      objectName,
      file.path,
      { 'Content-Type': file.mimetype }
    );
    
    // 임시 파일 삭제
    fs.unlinkSync(file.path);
    
    // Presigned URL 생성
    const url = await minioClient.presignedGetObject(BUCKET_NAME, objectName);
    
    res.json({
      success: true,
      message: '파일이 업로드되었습니다.',
      filename: objectName,
      url: url
    });
  } catch (err) {
    res.status(500).json({ success: false, error: err.message });
  }
});

app.listen(3000, () => {
  console.log('서버가 3000번 포트에서 실행 중입니다.');
});

프로덕션 환경 고려사항

1. 보안 설정

기본 계정 정보를 반드시 변경하세요.

docker run -d \
  -p 9000:9000 \
  -p 9001:9001 \
  --name minio \
  -e MINIO_ROOT_USER=your_secure_username \
  -e MINIO_ROOT_PASSWORD=your_secure_password_min_8_chars \
  -v ~/minio/data:/data \
  minio/minio server /data --console-address ":9001"

2. SSL/TLS 설정

프로덕션에서는 반드시 HTTPS를 사용하세요.

const minioClient = new Minio.Client({
  endPoint: 'minio.yourdomain.com',
  port: 443,
  useSSL: true,  // HTTPS 사용
  accessKey: process.env.MINIO_ACCESS_KEY,
  secretKey: process.env.MINIO_SECRET_KEY
});

3. 환경변수 사용

민감한 정보는 환경변수로 관리하세요.

require('dotenv').config();

const minioClient = new Minio.Client({
  endPoint: process.env.MINIO_ENDPOINT,
  port: parseInt(process.env.MINIO_PORT),
  useSSL: process.env.MINIO_USE_SSL === 'true',
  accessKey: process.env.MINIO_ACCESS_KEY,
  secretKey: process.env.MINIO_SECRET_KEY
});

마치며

MinIO는 S3 호환 객체 스토리지가 필요한 모든 프로젝트에 훌륭한 선택입니다. 특히 다음과 같은 경우에 적극 추천합니다:

  • 로컬 개발 환경에서 S3를 시뮬레이션하고 싶을 때
  • 클라우드 비용을 절감하고 싶을 때
  • 데이터 주권이나 컴플라이언스 요구사항이 있을 때
  • 멀티클라우드 전략을 구사하고 싶을 때