
MinIO로 로컬 S3 구축하기
Docker로 실행하는 S3 호환 오브젝트 스토리지 MinIO 설정 가이드
이 글 목차
개발 중에 ETL 파이프라인을 실행할 때마다 실제 AWS S3를 호출하고 있었어요. 테스트할 때마다 API 비용이 발생하고, 인터넷 접속이 필수이고, 실수로 프로덕션 데이터를 읽거나 쓸 위험도 있었어요. 실제와 동일하게 동작하면서 비용이 들지 않고 오프라인에서도 작동하는 로컬 S3가 필요했어요.
왜 중요한가
S3에 의존하는 코드는 어디에나 있어요 — ETL 작업, 파일 업로드, 백업, 데이터 레이크 읽기. 개발 중에 실제 S3를 대상으로 테스트하면 느리고, 비싸고, 위험해요. 로컬 S3 대체품이 세 가지 문제를 모두 해결해요: 비용 제로, 네트워크 의존성 없음, 프로덕션 데이터를 건드릴 가능성 제로.
겪었던 어려움
- boto3 virtual-hosted 스타일 — boto3가 기본적으로 virtual-hosted 스타일
URL(
bucket.s3.amazonaws.com)을 사용하는데, MinIO는 이를 지원하지 않아요.addressing_style: "path"가 필요하다는 걸 알아내기까지 알 수 없는 connection-refused 에러와 시행착오가 있었어요. - PySpark S3A vs S3 — Spark는
s3://가 아니라s3a://프로토콜(Hadoop 커넥터)을 사용하고, 자체 별도의 설정 키(spark.hadoop.fs.s3a.*)가 필요해요. boto3 설정은 이어지지 않아요. - MinIO 시작 race condition — 버킷을 생성하는
minio-init컨테이너가 MinIO가 완전히 시작되기 전에 실패하는 경우가 있었어요. Docker Compose에서condition: service_healthy를 추가해야 했어요. - SSL 불일치 — MinIO는 로컬에서 일반 HTTP로 실행되지만, S3A는 기본적으로
SSL을 사용해요.
connection.ssl.enabled=false를 빼먹으면 네트워크 문제처럼 보이는 TLS handshake 에러가 나와요.
검토한 옵션
| 옵션 | 장점 | 단점 |
|---|---|---|
| MinIO | 100% S3 API 호환, 가벼운 Docker 이미지, 웹 콘솔 포함 | Path 스타일만 지원, 다른 AWS 서비스 에뮬레이션 없음 |
| LocalStack | 여러 AWS 서비스 에뮬레이션 (S3, SQS, Lambda 등) | 더 무거운 리소스 사용, 무료 티어 제한, 느린 시작 |
| 실제 AWS S3 | 에뮬레이션 갭 없음, 프로덕션과 동일 | 비용 발생, 인터넷 필요, 프로덕션 데이터 접근 위험 |
프로젝트가 S3만 필요하기 때문에(SQS, Lambda 등 불필요) MinIO를 선택했어요. MinIO가 더 가볍고, 시작이 빠르고, 완벽한 S3 API 호환성을 갖추고 있어요. 다른 AWS 서비스가 필요하다면 LocalStack이 더 나은 선택이에요.
셋업
Docker Compose
services:
minio:
image: minio/minio:latest
ports:
- "9000:9000" # S3 API
- "9001:9001" # 웹 콘솔
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
command: server /data --console-address ":9001"
volumes:
- minio-data:/data
# 시작 시 자동으로 버킷 생성
minio-init:
image: minio/mc:latest
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c " mc alias set local http://minio:9000 minioadmin minioadmin;
mc mb local/my-bucket --ignore-existing; "
volumes:
minio-data:
service_healthy 조건이 중요해요. 이게 없으면 minio-init 컨테이너가
MinIO의 시작과 경쟁해서 간헐적으로 실패해요.
boto3 팩토리 함수
import os
import boto3
from botocore.config import Config as BotoConfig
def get_s3_client(region_name: str | None = None):
endpoint_url = os.getenv("AWS_ENDPOINT_URL")
use_path_style = os.getenv("AWS_S3_USE_PATH_STYLE", "false").lower() == "true"
region = region_name or os.getenv("AWS_REGION", "ap-northeast-2")
client_kwargs = {"region_name": region}
if endpoint_url:
client_kwargs["endpoint_url"] = endpoint_url
if use_path_style:
client_kwargs["config"] = BotoConfig(s3={"addressing_style": "path"})
return boto3.client("s3", **client_kwargs)
이 팩토리 함수는 로컬(MinIO)과 프로덕션(실제 AWS) 모두에서 작동해요.
AWS_ENDPOINT_URL이 설정되어 있지 않으면 실제 S3에 연결하고, 설정되어 있으면
path 스타일 주소 지정으로 MinIO를 가리켜요.
PySpark S3A 설정
endpoint_url = os.getenv("AWS_ENDPOINT_URL")
if endpoint_url:
builder = (
builder
.config("spark.hadoop.fs.s3a.endpoint", endpoint_url)
.config("spark.hadoop.fs.s3a.path.style.access", "true")
)
if endpoint_url.startswith("http://"):
builder = builder.config("spark.hadoop.fs.s3a.connection.ssl.enabled", "false")
PySpark는 s3://가 아니라 s3a:// 프로토콜(Hadoop 커넥터)을 사용해요.
boto3 설정이 이어지지 않아서 Spark 전용 설정이 별도로 필요해요. SSL 비활성화가
특히 중요해요: MinIO가 로컬에서 일반 HTTP로 실행되는데, S3A는 기본적으로
SSL을 사용해요. 이 라인 없이는 네트워크 장애처럼 보이는 TLS handshake
에러가 발생해요.
환경 변수
# 로컬 (MinIO)
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
AWS_ENDPOINT_URL=http://localhost:9000
AWS_S3_USE_PATH_STYLE=true
# 프로덕션 (실제 AWS)
# AWS_ENDPOINT_URL을 설정하지 않으면 - 기본으로 실제 S3 사용
이 방식의 장점은 같은 코드가 양쪽 환경에서 작동한다는 거예요. 유일한
차이점은 AWS_ENDPOINT_URL이 설정되어 있는지 여부뿐이에요.
왜 이 방식이 효과적인가
Path Style vs Virtual Hosted
| 스타일 | URL 형식 | 사용처 |
|---|---|---|
| Virtual-hosted | https://bucket.s3.region.amazonaws.com/key | AWS (기본값) |
| Path | http://host:port/bucket/key | MinIO (필수) |
MinIO가 virtual-hosted 스타일을 지원하지 않는 이유는
bucket.s3.region.amazonaws.com 서브도메인의 DNS 해석이 필요하기
때문이에요. Path 스타일은 버킷 이름을 URL 경로에 넣어서 어떤 HTTP
엔드포인트에서든 작동해요.
접근 방법
| 방법 | URL |
|---|---|
| 웹 콘솔 | http://localhost:9001 |
| S3 API | http://localhost:9000 |
| AWS CLI | aws s3 ls --endpoint-url http://localhost:9000 |
포트 9001의 웹 콘솔은 개발 중에 특히 유용해요. 코드를 작성하지 않고도 버킷을 탐색하고, 파일을 업로드하고, 오브젝트를 검사할 수 있어요.
실전 팁
MinIO는 S3 에뮬레이션만 필요할 때 올바른 선택이에요. 다음 경우에 사용하세요:
- S3에 의존하는 코드의 로컬 개발 (ETL, 파일 업로드, 백업)
- AWS 자격 증명 없이 S3가 필요한 CI/CD 파이프라인의 통합 테스트
- 인터넷 접속이 불안정하거나 불가능한 오프라인 개발
- 실제 S3 지연 시간이 피드백 루프를 느리게 하는 빠른 반복 작업
MinIO를 사용하면 안 되는 경우
- 다중 AWS 서비스 에뮬레이션 — S3와 함께 SQS, Lambda, DynamoDB 등이 필요하면 LocalStack을 사용하세요; MinIO는 S3만 에뮬레이트해요
- S3 Select 또는 Glacier — MinIO는 모든 S3 기능을 지원하지 않아요; S3 Select, Glacier 티어, S3 Object Lock 같은 고급 기능은 다르게 동작하거나 없을 수 있어요
- 성능 벤치마킹 — Docker의 로컬 MinIO는 실제 S3와 다른 지연 시간과 처리량 특성을 가져요; 성능 테스트에 사용하지 마세요
- Virtual-hosted 스타일 URL — 코드가 virtual-hosted 버킷 URL에 의존하고 path 스타일로 설정을 변경할 수 없다면, 코드 변경 없이는 MinIO가 작동하지 않아요
기억해야 할 두 가지 주의사항: MinIO에 연결하는 모든 클라이언트에 항상
path.style.access=true를 설정하고, 일반 HTTP 엔드포인트를 사용할 때는
항상 SSL을 비활성화하세요.
댓글 · github discussions
repo brandonwie/brandonwie.dev · category "Blog Comments" · term local-s3-minio