開発ブログ

株式会社Nextatのスタッフがお送りする技術コラムメインのブログ。

電話でのお問合わせ 075-744-6842 ([月]-[金] 10:00〜17:00)

  1. top >
  2. 開発ブログ >
  3. PHP >
  4. Laravel >
  5. Laravel + MinIOのローカル環境をAWS環境に近づける設定

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の管理コンソールにアクセスしましょう。

minio-test-console-login.png

compose.yamlに設定したユーザーとパスワードでログインできます。

images バケットが作成されているのが確認できます。

minio-test-console-buckets.png

続いて、https://app.minio-test.local/images にアクセスしてください。

画像アップロード用のフォームが表示されます

minio-test-form.png

適当な画像を選んでアップロードすると、画面上部にアップロードした画像が表示されます。

minio-test-uploaded.png

画像のURLは https://images.minio-test.local/uploaded/{ランダム文字列}.png の形式になっており nginx で設定した画像サーバーのURLから配信されていることが分かります。 管理コンソールからも画像がアップロードされていることが確認できます。

minio-test-console-uploaded.png

まとめ

  • MinIOのコンテナに対して、環境変数のMINIO_DOMAINとDocker Composeのネットワークエイリアスを指定することでLaravelアプリケーションからのS3の操作をバーチャルホストスタイルに対応させることができる。
  • nginxなどWebサーバを手前においてリバースプロキシを設定することで、CloudFrontなどのCDNからの配信をエミュレートでき、ポートも節約できる。

中身がブラックボックスなAWS S3とMinIO自体の違いは残るものの、なるべくステージング環境や本番環境と近い構造とすることで予期しない挙動の違いを減らしていきたいですね。

TOPに戻る