Laravel + MinIOのローカル環境をAWS環境に近づける設定
こんにちは、ナカエです。 そろそろ梅雨も明けるらしいですが、蒸し暑い日が続いていますね。
AWS S3互換のオブジェクトストレージとして、 ローカル開発環境で MinIO を利用されている方も多いかと思います。 弊社でも以前はLocalStackを使うことも多かったですが、最近はMinIOの割合が高いです。
さて、MinIOは非常に手軽に設定できますが、素朴に使うとAWS環境との差異がいくつか残ります。 本記事の主旨は次のような問題を解決し、ローカルで利用するMinIOとステージング・本番環境の構成を近づけようというものです。
- ステージング・本番環境のAWS S3ではパススタイルのリクエストを利用していないのに、ローカル環境でのみ使っているのが気にかかる
- 本番ではCDNを使うので、画像にもlocalhostではないホスト+HTTPS通信でアクセスしたい
- MinIOが使うホスト側のポートを減らしたい
目指す構成
- バーチャルホストスタイルのエンドポイントを利用する。つまり、Laravelアプリケーションからも {MinIO
のホスト名}/{バケット名}
ではなく{バケット名}
.{MinIOのホスト名}
でアクセスする - CloudFrontなどのCDNを介して画像を配信する構造に近づけるため、Webサーバ(今回はnginx)にてリバースプロキシとTLS終端を設定し、
https://images.minio-test.local
でアクセスできるようにする - MinIOの管理コンソールにもHTTPSでアクセスできるようにする
URLの構成は下記になります。
- Laravelアプリケーション: https://app.minio-test.local
- 画像サーバー: https://images.minio-test.local
- MinIOの管理コンソール: https://console.minio-test.local
環境
- macOS 14.5(Sonoma)
- CPU: Apple M1 Mac
- PHP 8.3.9
- Laravel 11.15.0
前提
MinIO用の設定の説明に集中するため一部の設定は省略しますが、下記を前提としています。
/etc/hosts
/etc/hosts が下記のように設定されているものとします。
127.0.0.1 app.minio-test.local images.minio-test.local console.minio-test.local
:: app.minio-test.local images.minio-test.local console.minio-test.local
nginx/PHP-FPMの設定
一般的なnginx + PHP-FPMの構成でLaravelアプリケーションが表示できるようになっているものとします。 nginxで利用するTLS証明書として、自己署名証明書が下記ドメインに対して用意されています。
- app.minio-test.local
- images.minio-test.local
- console.minio-test.local
Laravelアプリケーションの設定
構築済みのLaravelアプリケーションに対し、league/flysystem-aws-s3-v3 がインストール済みであり、LaravelのFilesystemがS3の操作に対応した状態とします。
作業用ディレクトリの構成
作業ディレクトリのルートは下記のような構成になっています。 ローカル環境用のコンテナの設定を containers に、Laravelアプリケーションをlaravelに配置しているものとします。
containers
nginx
certs/ // nginx用の証明書
server.crt
server.csr
server.key
conf.d
laravel.conf // nginxのLaravelアプリケーション用の設定
nginx.conf // nginxの設定
php
php.ini
Dockerfile
laravel // Laravelアプリケーション
compose.yaml
本題:MinIO関連の設定
本題であるMinIOのための設定を行います。
nginxの画像サーバー設定
containers/conf.d/image-server.conf
を追加します。
imagesバケットの中身を配信する画像サーバーと、MinIOの管理コンソール画面を表示するサーバーの2つを設定します。 双方とも、リバースプロキシを設定しています。
MinIOはバーチャルホストスタイルでのAPI操作には対応しているものの、同じスタイルでのファイルのHTTP配信には対応していないようだったので、 画像サーバーの proxy_pass は パススタイルによる指定です。
設定項目は、MinIO公式ドキュメントの Configure NGINX Proxy for MinIO Server を参考にしています。
server {
listen 443 ssl;
listen [::]:443;
server_name images.minio-test.local;
ssl_certificate certs/server.crt;
ssl_certificate_key certs/server.key;
ignore_invalid_headers off;
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:9000/images/;
}
}
server {
listen 443;
listen [::]:443;
server_name console.minio-test.local;
ssl_certificate certs/server.crt;
ssl_certificate_key certs/server.key;
ignore_invalid_headers off;
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_set_header X-NginX-Proxy true;
real_ip_header X-Real-IP;
proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
chunked_transfer_encoding off;
proxy_pass http://minio:9090/;
}
}
compose.yamlの設定
Docker Composeの設定を行います。 compose.yamlにて、nginxとPHP-FPMに加え、MinIO と mc(MinIOのクライアント)をサービスとして用意します。 今回の内容ではDBサーバーは利用しないため省略しています。
services:
nginx:
image: nginx:1.27.0-bookworm
ports:
- "443:443"
volumes:
- ./containers/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./containers/nginx/conf.d:/etc/nginx/conf.d
- ./containers/nginx/certs:/etc/nginx/certs
- ./laravel/public:/work/public
php:
build: ./containers/php
working_dir: /work
volumes:
- ./laravel:/work:cached
minio:
image: minio/minio:RELEASE.2024-07-13T01-46-15Z
environment:
MINIO_DOMAIN: minio
MINIO_ROOT_USER: user
MINIO_ROOT_PASSWORD: P@ssw0rd
command: server --console-address ":9090" /data
volumes:
- minio-data:/data
networks:
default:
aliases:
- images.minio
mc:
image: minio/mc:RELEASE.2024-07-11T18-01-28Z
depends_on:
- minio
working_dir: /root
volumes:
- ./containers/mc/config.json:/root/.mc/config.json
- ./containers/mc/entrypoint.sh:/root/entrypoint.sh
entrypoint: [ "/root/entrypoint.sh" ]
volumes:
minio-data:
設定のポイントはそれぞれ下記のとおりです。
■MinIO
MinIOのポイントは環境変数とネットワークです。
MINIO_DOMAIN を設定することでバーチャルホストスタイルのリクエストが有効になります。
参考: Core Settings — MinIO Object Storage for Linux
加えて、コンテナ間のネットワークにおいて images.minio
のホストでも minio サービスへのアクセスを可能にするため、networksでエイリアスを指定します。
なお、ホストマシンからはすべてnginx経由のアクセスとなるため、9000番ポートと9090番ポートはホストには公開していません。
■mc(MinIO Client)
理想的には標準のAWS CLIを使いたいところですが、専用クライアントのmcのほうが何かと便利なのでこちらを使います。
config.jsonは次のようになります。
{
"version": "10",
"aliases": {
"minio": {
"url": "http://minio:9000",
"accessKey": "user",
"secretKey": "P@ssw0rd",
"api": "s3v4",
"path": "auto"
}
}
}
エントリポイントに指定した entrypoint.sh では 画像用の images バケット作成とpublicアクセスの許可を行っています。
#!/usr/bin/env bash
mc mb --ignore-existing minio/images;
mc anonymous set public minio/images;
ローカル環境起動時にバケットの作成処理が動けば良いだけなので、tty: trueなどは設定しておらず、作成処理の後コンテナは即停止する形になります。 作成処理の際にMinIOが起動している必要があるので、depends_onをお忘れなきよう。
Laravelアプリケーションの環境変数
.envの設定を行います。 AWS_USE_PATH_STYLE_ENDPOINT は デフォルトのまま false とし、アクセスキー・エンドポイント・バケットの設定をMinIOの設定と合わせます。
AWS_ACCESS_KEY_ID=user
AWS_SECRET_ACCESS_KEY=P@ssw0rd
AWS_DEFAULT_REGION=ap-northeast-1
AWS_BUCKET=images
AWS_USE_PATH_STYLE_ENDPOINT=false
AWS_ENDPOINT=http://minio:9000
動作確認用の画面とエンドポイントの作成
簡単なフォーム画面と画像アップロード用のエンドポイントを作成します。
laravel/app/Http/Requests/StoreImageRequest.php
バリデーションルールのみ設定します。
<?php
declare(strict_types=1);
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreImageRequest extends FormRequest
{
public function rules(): array
{
return [
'file' => [
'required',
'image'
],
];
}
}
laravel/app/Http/Controllers/ImagesController.php
アップロード用のフォームを表示するアクションindex() と アップロード用のアクション store()の2つを用意します。
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Http\Requests\StoreImageRequest;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class ImagesController
{
private const UPLOAD_DIR = 'uploaded';
public function index(): View
{
$imageServerRootUrl = 'https://images.minio-test.local';
$filePaths = Storage::disk('images')->allFiles(self::UPLOAD_DIR);
$imageUrls = array_map(
fn (string $path) => "$imageServerRootUrl/$path",
$filePaths
);
return view(
'images.index',
[
'imageUrls' => $imageUrls
]
);
}
public function store(StoreImageRequest $request): RedirectResponse
{
$file = $request->file('file');
assert($file instanceof UploadedFile);
// 簡単のためDBへの保存などは省略
$fileName = Str::random(20) . '.' . $file->guessExtension();
Storage::disk('images')->putFileAs(self::UPLOAD_DIR, $file, $fileName);
return redirect()->route('images.index');
}
}
laravel/resources/views/images/index.blade.php
画像アップロード用のフォームです。 アップロードされた画像も表示できるようにしておきます。
@foreach($imageUrls as $url)
<ul style="list-style: none;">
<li>
<img src="{{ $url }}" alt="画像" width="50" height="50">
</li>
</ul>
@endforeach
<form action="{{ route('images.store') }}" method="post" enctype="multipart/form-data">
@csrf
<input type="file" name="file" accept="image/jpeg,image/png">
<button type="submit">アップロード</button>
</form>
laravel/routes/web.php
コントローラーで作成した2つのアクションのルートを指定します。 特に意味はないですが、Routeファサードを使わないバージョンです。
<?php
declare(strict_types=1);
use Illuminate\Routing\Router;
use App\Http\Controllers;
/** @var Router $router */
$router->name('images.')
->prefix('images')
->group(
function (Router $router) {
$router->get('/', [Controllers\ImagesController::class, 'index'])->name('index');
$router->post('/', [Controllers\ImagesController::class, 'store'])->name('store');
}
);
動作確認
docker compose up
でDocker Compose環境を起動します。
https://console.minio-test.local にてMinIOの管理コンソールにアクセスしましょう。
compose.yamlに設定したユーザーとパスワードでログインできます。
続いて、https://app.minio-test.local/images にアクセスしてください。
画像アップロード用のフォームが表示されます
適当な画像を選んでアップロードすると、画面上部にアップロードした画像が表示されます。
画像のURLは https://images.minio-test.local/uploaded/{ランダム文字列}.png の形式になっており nginx で設定した画像サーバーのURLから配信されていることが分かります。 管理コンソールからも画像がアップロードされていることが確認できます。
まとめ
- MinIOのコンテナに対して、環境変数のMINIO_DOMAINとDocker Composeのネットワークエイリアスを指定することでLaravelアプリケーションからのS3の操作をバーチャルホストスタイルに対応させることができる。
- nginxなどWebサーバを手前においてリバースプロキシを設定することで、CloudFrontなどのCDNからの配信をエミュレートでき、ポートも節約できる。
中身がブラックボックスなAWS S3とMinIO自体の違いは残るものの、なるべくステージング環境や本番環境と近い構造とすることで予期しない挙動の違いを減らしていきたいですね。