Docker Composeで構築する分散MinIOオブジェクトストレージ:プライベートなS3互換ストレージの自作ガイド

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

現実的な課題:クラウドコストの増大とシステム停止リスク

アプリケーションで大量の画像、動画、バックアップファイルを保存する際、AWS S3は真っ先に候補に挙がる選択肢です。しかし、データ容量が5TB〜10TBを超え始めると、利用料金は一気に跳ね上がります。特にネックとなるのがデータ転送アウト(データ送信量)の費用で、1GBあたり平均約$0.09かかります。このコストは、システムをスケールさせていくプロジェクトにとって大きな負担となりがちです。

そこで、社内サーバー上に単一(スタンドアロン)のMinIOコンテナを立ち上げるチームも多く見られます。この方法は手軽でコストも抑えられますが、単一障害点(Single Point of Failure: SPOF)を抱えることになります。深夜に物理ドライブの障害やプロセスのクラッシュが発生した場合、ストレージサービス全体が即座に停止してしまいます。

この課題を根本から解決するのが、分散MinIO(Distributed MinIO)構成です。Amazon S3完全互換のAPIを維持しつつ、高額なクラウドインフラに依存することなく高可用性(High Availability)を実現できます。

分散MinIOとイレイジャーコーディングの仕組み

分散モードでは、MinIOは複数のコンテナや物理サーバーにまたがる複数のディスクを統合し、単一のストレージプールとして扱います。

イレイジャーコーディングによるデータ保護の仕組み

ストレージ容量を2倍〜3倍消費する従来のレプリケーション(複製)方式とは異なり、MinIOはイレイジャーコーディング(Erasure Coding)アルゴリズム(Reed-Solomon符号ベース)を採用しています。ファイルをアップロードすると、システムは自動的にデータをデータブロック(Data blocks)とパリティブロック(Parity blocks)に分割し、クラスタ内の全ディスクへ均等に分散配置します。

例えば、4本のディスク構成でデフォルト設定(データブロック×2 + パリティブロック×2)を組んだ場合、利用可能な実効容量は50%になります。特筆すべきメリットは、4本中2本のディスクが同時に故障しても、元のファイルを100%欠損なく読み取り・復元できる点です。

クラスタ構築の前提条件

  • 最低4つの独立したドライブ(または個別のボリュームをマウントした4つのコンテナ)が必要です。
  • 全ノードが同一の認証情報(Root User / Password)を持ち、内部ネットワークで正常に通信できる必要があります。
  • 各ノードへのAPIリクエストを適切に振り分けるリバースプロキシ(Nginxなど)が必要です。

実践:Docker Composeで構築する4ノードMinIO + Nginx

初心者によくある典型的な失敗例として、4つのMinIOコンテナすべてにホストマシン上の同じディレクトリをマウントしてしまうケースが挙げられます。これを行うと、プロセス間でファイルロックの競合が発生し、コンテナがクラッシュを繰り返します。必ず各MinIOノードに独立したデータボリュームを割り当ててください。

ステップ1:ディレクトリ構成の準備

ターミナルを開き、プロジェクト用ディレクトリを作成します:

mkdir -p ~/distributed-minio/nginx
cd ~/distributed-minio

ステップ2:負荷分散用Nginxの設定

S3 API(ポート9000)およびWebコンソール(ポート9001)へのトラフィックを分散するため、nginx/nginx.confを作成します:

events {
    worker_connections 1024;
}

http {
    upstream minio_s3_servers {
        least_conn;
        server minio1:9000;
        server minio2:9000;
        server minio3:9000;
        server minio4:9000;
    }

    upstream minio_console_servers {
        ip_hash;
        server minio1:9001;
        server minio2:9001;
        server minio3:9001;
        server minio4:9001;
    }

    server {
        listen 9000;
        server_name localhost;

        client_max_body_size 0;
        proxy_buffering off;
        proxy_request_buffering off;

        location / {
            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;
            proxy_pass http://minio_s3_servers;
        }
    }

    server {
        listen 9001;
        server_name localhost;

        location / {
            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_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_pass http://minio_console_servers;
        }
    }
}

ステップ3:docker-compose.ymlの作成

ルートディレクトリ ~/distributed-minio に docker-compose.yml を作成します:

version: '3.8'

services:
  minio1:
    image: minio/minio:RELEASE.2024-03-30T09-41-56Z
    hostname: minio1
    volumes:
      - data1-1:/data1
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: MySecretPassword123
    command: server http://minio{1...4}/data1 --console-address ":9001"
    networks:
      - minio-net

  minio2:
    image: minio/minio:RELEASE.2024-03-30T09-41-56Z
    hostname: minio2
    volumes:
      - data2-1:/data1
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: MySecretPassword123
    command: server http://minio{1...4}/data1 --console-address ":9001"
    networks:
      - minio-net

  minio3:
    image: minio/minio:RELEASE.2024-03-30T09-41-56Z
    hostname: minio3
    volumes:
      - data3-1:/data1
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: MySecretPassword123
    command: server http://minio{1...4}/data1 --console-address ":9001"
    networks:
      - minio-net

  minio4:
    image: minio/minio:RELEASE.2024-03-30T09-41-56Z
    hostname: minio4
    volumes:
      - data4-1:/data1
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: MySecretPassword123
    command: server http://minio{1...4}/data1 --console-address ":9001"
    networks:
      - minio-net

  nginx:
    image: nginx:alpine
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - minio1
      - minio2
      - minio3
      - minio4
    networks:
      - minio-net

volumes:
  data1-1:
  data2-1:
  data3-1:
  data4-1:

networks:
  minio-net:
    driver: bridge

ステップ4:クラスタの起動と稼働確認

バックグラウンドでクラスタを起動します:

docker compose up -d

コンテナの稼働状況を確認します:

docker compose ps

ブラウザで http://localhost:9001 にアクセスし、ユーザー名 minioadmin、パスワード MySecretPassword123 でログインします。Monitoring > Metrics を開くと、4/4 Online Drives と正常(緑色)ステータスが表示されているはずです。

ステップ5:Python(Boto3)からの接続テスト

MinIOはS3 APIと100%互換性があるため、Boto3クライアント初期化時に endpoint_url を指定するだけで簡単に接続できます:

import boto3
from botocore.client import Config

s3_client = boto3.client(
    's3',
    endpoint_url='http://localhost:9000',
    aws_access_key_id='minioadmin',
    aws_secret_access_key='MySecretPassword123',
    config=Config(signature_version='s3v4'),
    region_name='us-east-1'
)

# 1. 新規バケットの作成
bucket_name = 'app-media-storage'
try:
    s3_client.create_bucket(Bucket=bucket_name)
    print(f"バケットを作成しました: {bucket_name}")
except Exception as e:
    print(f"バケットが既に存在するか、エラーが発生しました: {e}")

# 2. サンプルデータのアップロード
file_content = b"Hello MinIO Distributed Cluster running on Docker Compose!"
s3_client.put_object(Bucket=bucket_name, Key='sample.txt', Body=file_content)
print("sample.txtのアップロードに成功しました!")

# 3. バケットからのデータ読み取り
response = s3_client.get_object(Bucket=bucket_name, Key='sample.txt')
print("MinIOからのファイル内容:", response['Body'].read().decode('utf-8'))

本番運用に向けた推奨事項

単一ホスト上のDocker Compose構成はステージングやテスト環境には最適ですが、本番環境(Production)へ投入する際は以下の3点に留意してください:

  • 物理サーバーの分散:ホストマシンのハードウェア障害によるクラスタ全停止を防ぐため、各MinIOノードを個別の物理サーバーまたは仮想マシン(VM)に配置します。
  • 通信のセキュリティ保護:NginxにSSL/TLS(HTTPS)を設定し、デフォルトのRootユーザー名およびパスワードを必ず変更してください。
  • 書き込みクォーラム(Write Quorum)の考慮:4ドライブ構成の場合、新規書き込み処理には最低でもN/2 + 1(つまり3台以上のドライブが正常稼働)が必要です。システムの要件に応じて、適切な容量設計とノード数を計画してください。
Share: