S3 + CloudFront画像配信をMinIOを使ってローカルで再現する


こんにちは! デバイスソフトウエア開発部の米森です。

今回は、開発中に直面しがちな「ローカル環境でのS3画像配信問題」の解決策をご紹介します。クライアントがサーバーから画像のpath(オブジェクトキー)を受け取り、そのパスとCloudFrontを使って画像を表示するというシンプルなシステム構成を例に、MinIOを活用したローカルS3環境の構築方法を解説させていただきます!

前提

本記事では、以下のようなシステム構成を想定します。

 

クライアントがバックエンドに画像のリクエストを送り、バックエンドがS3に格納されている画像のpathを返却する。クライアントがそのpathとCloudFrontで画像を表示する。シンプルな構成です。

フロントエンド(React+TypeScript)の実装イメージ。

バックエンドから来たpathを受け取り、環境変数化されているベースURLと合体させて配信画像のsrcを生成し、表示するコンポーネントです。

バックエンドアプリケーションからは、下記のようなJSONが返却されると仮定します。

上記が組み合わさり、最終的に以下のようなURLが生成されます。

このURLを用いて、

のようなimgタグが生成され、画像が表示されるという流れです。

ローカル環境での問題点

画像を表示するには、CloudFrontのベースURLに紐づくS3バケットに”/aaa/bb_sample.jpg”というオブジェクトが存在している必要があります。

しかしローカル環境だとS3バケット自体がそもそもないという場合が多くあります。DEV環境のバケットを間借りすることもできますが、データの混在は避けたいですし、それぞれの環境は独立させたいです。

MinIOでローカルS3構築

ローカル開発をローカルで完結させるために、MinIOを使って疑似S3を構築しましょう。

以下の内容でdocker-compose.ymlファイルを作成し、コンテナを立ち上げるだけで基本的な準備は完了します。

minio:

MinIOサーバー本体のサービス定義です。

  • image: minio/minio:latest:
    • MinIOサーバーとして使用するDockerイメージを指定しています。 latestタグは、利用可能な最新の安定版MinIOイメージをプルします。
  • environment::
    • MinIOサーバー内で使用される環境変数を設定します。
    • MINIO_ROOT_USER: test-user: MinIOコンソールおよびAPIにアクセスするためのルートユーザー名を test-user に設定します。
    • MINIO_ROOT_PASSWORD: test-password: 上記ユーザーのパスワードを test-password に設定します。
  • ports::
    • ホストマシンとコンテナ間のポートマッピングを定義します。
    • - 9000:9000: ホストの 9000 番ポートをコンテナの 9000 番ポート(MinIOのAPIエンドポイント)にマッピングします。フロントエンドから画像を取得する際のベースURLとして使用されます。
    • - 9001:9001: ホストの 9001 番ポートをコンテナの 9001 番ポート(MinIOのWebコンソール)にマッピングします。ブラウザで http://localhost:9001 にアクセスすると、MinIOの管理画面にログインできます。
  • volumes::
    • ホストマシンとコンテナ間でデータを共有するためのボリュームを定義します。
    • - ./.minio-storage/:/storage: ホストマシン上の現在のディレクトリにある .minio-storage フォルダを、MinIOコンテナ内の /storage パスにマウントします。これにより、MinIOにアップロードされたデータは、コンテナが停止・削除されてもホストマシン上に永続的に保存されます。
  • command: ["server", "/storage", "--console-address", ":9001"]:
    • MinIOコンテナが起動したときに実行されるコマンドです。
    • server /storage: /storage ディレクトリをストレージとしてMinIOサーバーを起動します。
    • --console-address ":9001": MinIOのWebコンソールを 9001 番ポートで利用可能にします。
bucket-setup:

MinIOサーバーを初期設定するためのクライアントサービスの定義です。

  • image: minio/mc:
    • MinIOのコマンドラインツール(mc)を含むDockerイメージを使用します。
  • depends_on::
    • このサービスが依存する他のサービスを指定します。
    • - minio: minio-client サービスは minio サービスが起動するまで待機します。これにより、MinIOサーバーが完全に起動してからクライアントが操作を開始できるようになります。
  • entrypoint: >:
    • コンテナが起動したときに実行されるコマンドのシーケンスです。複数行のシェルスクリプトを実行するために > (YAMLのブロック引用スタイル) を使用しています。
    • /usr/bin/mc config host add myminio http://minio:9000 test-user test-password:
      • mc config host add myminio http://minio:9000 test-user test-password: myminio という名前でMinIOサーバーへの接続情報を登録します。http://minio:9000 は、minio サービス名を使って、Dockerネットワーク内でMinIOサーバーにアクセスできることを示しています。ユーザー名とパスワードは minio サービスで設定したものと同じです。
    • /usr/bin/mc mb --ignore-existing myminio/test-bucket;:
      • mc mb myminio/test-bucket: myminio 接続を使用して test-bucket という名前のバケットを作成します。
      • --ignore-existing: もし test-bucket が既に存在していてもエラーにせず無視します。
    • /usr/bin/mc anonymous set public myminio/test-bucket;:
      • mc anonymous set public myminio/test-bucket: test-bucket のアクセス権限を「パブリック」に設定します。これにより、フロントエンドから認証なしで画像にアクセスできるようになります。
    • exit 0;: スクリプトが正常に終了したことを示します。

コンソールにアクセス

コンテナを立ち上げた状態で、localhost:9001にアクセスするとログイン画面に遷移します。

認証情報はymlに記載した

です。

ログインすると、test-bucketというバケットがあります。

 

このバケットに”/aaa/bb_sample.jpg”オブジェクトを追加してみましょう。

右側の”Create new path”から  “aaa/”というpathを作成し、

Upload Fileでbb_sample.jpgをアップロード。

いけました!

 

ちなみに、このアップロードしたオブジェクトは設定したホストマシンのディレクトリ(今回は “.minio-storage”)に格納されます。

MinIOは飽くまでインターフェースで、データの実態はここにあります。

API経由で画像を取得する

画像を設置したので、次はAPI経由でその画像を取得してみます。

9000番ポートをAPIエンドポイントとして設定したため、

の形式で画像にアクセスできます。

取れましたね!

あとは、フロント側の環境変数でローカル環境の場合は”localhost:9000″を見るようにすれば、疑似的なS3画像配信をローカルで完結できます。

はまったポイント

★データはホストマシン側にあることを忘れずに

上述した通り、データストレージの実態はホストマシンにある特定のディレクトリにあり、それをコンテナにマウントしています。バケットの設定内容などもここにあります。

MinIOコンテナのライフサイクルとは独立しているため、注意が必要です。

 

★MinIOコンソールとAPIは別のポート

MinIOはAPIとコンソールのポートをデフォルトでそれぞれ9000と9001に割り当てます。構築当初、9001でコンソールにアクセスできるようになったので、APIも9001でアクセスしようとしたらうまくいかなくて気づきました。

 

★フロント側のベースURLが若干違う

CloudFrontでS3の画像を配信する際、ディストリビューションドメイン(今回でいう”foofoobarbar.cloudfront.net”)は画像バケットに紐づいている想定です。なので、URLは下記のように「ベースURL + path(オブジェクトキー)」です。バケット名は含まれていません。

 

しかし、MinIOサービスにAPI経由で当たる場合、localhostドメインにはバケットの情報が紐づいていないので、パスに対象バケットを含める必要があります。

今回の実装イメージでは、ベースURLをフロントの環境変数で持っているので、ローカルの場合は対象バケット名もURLに含めるようにしましょう。

 

まとめ

画像をCloudFront経由で配信する、くらいのシンプルな構成ならMinIOで簡単に再現できます。ローカル疑似環境と実際のAWS環境の違いには留意する必要がありますが、便利なのでぜひ使ってみてください!

また、エコモットでは一緒にモノづくりをしていく仲間を随時募集しています。弊社に少しでも興味がある方はぜひ下記の採用ページをご覧ください!