자체 오브젝트 스토리지가 필요한 이유는 다양합니다. AWS S3 비용 절감, 데이터를 일본 국내(또는 한국 인접)에 둬야 하는 규정, 빠른 LAN 내부 액세스 등. 그럴 때 가장 검증된 선택지가 MinIO입니다. AWS S3 API와 100% 호환되어 코드 수정 없이 그대로 옮길 수 있습니다.


MinIO를 선택해야 할 때

상황 MinIO 적합 비고
S3 비용 절감 데이터 트래픽 비용도 자체 회선이라 0원
데이터를 일본·한국에 둬야 함 도쿄 IDC 사용
사진·영상 호스팅 정적 파일 서빙 가능
글로벌 CDN 필요 앞단에 Cloudflare 등 추가 필요
99.99% 가용성 필요 분산 노드 4대+ 권장

단일 노드 MinIO는 디스크 1대를 그대로 사용하므로 디스크 장애 = 데이터 손실입니다. 중요한 데이터는 백업 또는 분산 모드(4노드+)로 운영해야 합니다.


1단계 — 디스크 준비

MinIO는 빈 디렉토리만 있으면 동작하지만, 별도 디스크/파티션을 권장합니다. 다른 서비스의 디스크 사용량과 영향을 분리하기 위함입니다.

# 새 디스크 마운트 예시 (전용서버에 추가 디스크가 있을 때)
sudo mkfs.xfs /dev/sdb1
sudo mkdir -p /mnt/minio
sudo mount /dev/sdb1 /mnt/minio

# /etc/fstab 등록
echo "/dev/sdb1  /mnt/minio  xfs  defaults,noatime  0  2" | sudo tee -a /etc/fstab

xfs가 큰 파일과 많은 파일 양쪽에서 안정적이라 MinIO 공식 문서도 권장합니다.


2단계 — MinIO 설치

# 바이너리 다운로드
wget https://dl.min.io/server/minio/release/linux-amd64/minio -O /usr/local/bin/minio
chmod +x /usr/local/bin/minio

# 클라이언트(mc)도 함께
wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/local/bin/mc
chmod +x /usr/local/bin/mc

전용 사용자 생성:

sudo useradd -r minio-user -s /sbin/nologin
sudo chown -R minio-user:minio-user /mnt/minio

3단계 — systemd 서비스로 등록

# /etc/systemd/system/minio.service
[Unit]
Description=MinIO
After=network-online.target
Wants=network-online.target

[Service]
User=minio-user
Group=minio-user
EnvironmentFile=/etc/default/minio
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
Restart=always
LimitNOFILE=65536
TasksMax=infinity
TimeoutStopSec=infinity

[Install]
WantedBy=multi-user.target
# /etc/default/minio
MINIO_VOLUMES="/mnt/minio"
MINIO_OPTS="--console-address :9001 --address :9000"
MINIO_ROOT_USER="admin"
MINIO_ROOT_PASSWORD="<강력한 비밀번호 32자 이상>"
sudo systemctl daemon-reload
sudo systemctl enable --now minio
sudo systemctl status minio

4단계 — 방화벽

기본 포트는 9000(API), 9001(Console)입니다. 외부에 노출할 필요가 없다면 차단하고, Nginx 리버스 프록시 뒤에 둡니다.

# 외부 노출 차단, localhost만
sudo ufw deny 9000
sudo ufw deny 9001

5단계 — Nginx로 HTTPS 종단

s3.example.com 도메인을 MinIO 앞에 둡니다.

# /etc/nginx/sites-available/minio
server {
    listen 443 ssl http2;
    server_name s3.example.com;

    ssl_certificate     /etc/letsencrypt/live/s3.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/s3.example.com/privkey.pem;

    # 대용량 업로드 허용
    client_max_body_size 5G;
    proxy_request_buffering off;
    proxy_buffering off;

    location / {
        proxy_pass http://127.0.0.1:9000;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_connect_timeout 300;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        chunked_transfer_encoding off;
    }
}

# Console (관리 UI)
server {
    listen 443 ssl http2;
    server_name console.s3.example.com;

    ssl_certificate     /etc/letsencrypt/live/console.s3.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/console.s3.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:9001;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket (콘솔 실시간 갱신)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        chunked_transfer_encoding off;
    }
}

client_max_body_size는 업로드 가능한 최대 객체 크기입니다. 영상 업로드까지 받으려면 충분히 키워야 합니다.


6단계 — 버킷 생성과 사용자 권한

mc 클라이언트로 관리합니다.

# 별칭 등록
mc alias set local https://s3.example.com admin '<MINIO_ROOT_PASSWORD>'

# 버킷 생성
mc mb local/uploads
mc mb local/backups

# 버킷 목록
mc ls local

# 정책 적용 (uploads만 공개 읽기)
mc anonymous set download local/uploads

애플리케이션용 별도 사용자(IAM 키)를 만듭니다.

mc admin user add local app-user '<32자 비밀번호>'

# 정책 작성: uploads 버킷에만 접근
cat > /tmp/app-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": ["arn:aws:s3:::uploads/*"]
    },
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::uploads"]
    }
  ]
}
EOF

mc admin policy create local app-policy /tmp/app-policy.json
mc admin policy attach local app-policy --user app-user

이제 app-user의 액세스 키와 시크릿 키를 애플리케이션 환경 변수로 사용하면 됩니다.


7단계 — 애플리케이션 코드 (S3 SDK 그대로)

AWS S3 SDK에 endpoint만 바꿔주면 됩니다.

import boto3

s3 = boto3.client(
    's3',
    endpoint_url='https://s3.example.com',
    aws_access_key_id='app-user',
    aws_secret_access_key='<32자 비밀번호>',
    region_name='ap-northeast-1'
)

s3.upload_file('/tmp/photo.jpg', 'uploads', 'photos/2026/04/photo.jpg')
// Node.js (AWS SDK v3)
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const s3 = new S3Client({
  endpoint: "https://s3.example.com",
  region: "ap-northeast-1",
  credentials: {
    accessKeyId: "app-user",
    secretAccessKey: process.env.S3_SECRET,
  },
  forcePathStyle: true,  // MinIO는 path-style 권장
});

8단계 — 백업

단일 노드 MinIO는 디스크 장애에 취약합니다. 다음 중 하나는 반드시 적용하세요.

옵션 A: 별도 서버로 미러링 (Site Replication)

mc admin replicate add local backup --remote-bucket=uploads

옵션 B: 정기적으로 다른 위치로 동기화

# 매일 새벽 백업 서버로 동기화
mc mirror --remove local/uploads backup-server/uploads

옵션 C: 분산 모드 (운영 환경 권장) 서버 4대 이상으로 erasure coding을 활성화. 디스크 절반이 죽어도 데이터가 보존됩니다.


운영 모니터링

MinIO는 Prometheus 메트릭을 기본 제공합니다.

GET https://s3.example.com/minio/v2/metrics/cluster

주요 지표:

  • minio_node_disk_free_bytes — 디스크 여유 공간
  • minio_s3_requests_total — 초당 요청 수
  • minio_node_disk_latency_us — 디스크 지연

Netdata, Grafana 대시보드와 연결하면 실시간 모니터링이 됩니다.


마무리

MinIO는 “S3 호환 + 자체 운영 + 일본 IDC 위치”라는 세 조건을 한 번에 만족시킵니다. 데이터 주권 요구가 있는 한국·일본 기업에 특히 잘 맞습니다.

영상·이미지 같은 대용량 워크로드는 전용서버의 디스크 옵션이 더 적합하며, 디스크 추가 옵션 문의는 @tcp80net으로 부탁드립니다.