今参画しているプロジェクトでは、ファイルアップロードの機能があり、そのアップロード先がS3
でした。
その機能を実装した人はもうプロジェクトにいないので、ローカル環境でどのように動作確認をしていたのかは謎なのですが、「ローカル環境でもS3
アップロードの動作ができるようにしたいな~」と思い立ったのでLocalStack
を使ってあれこれやってみたらできたので、自分のための備忘録として記載しておきます。
ちなみにAWS
、LocalStack
に関してはズブの素人です。chatGPT
さんのお世話になりながらやったらできました(小並)。
以下の環境で動作確認を行いました。
(WSL)
WSL バージョン: 1.2.5.0
カーネル バージョン: 5.15.90.1
Ubuntu 20.04.6 LTS
(Docker)
Docker version 24.0.2
Docker Compose version v2.18.1
(PHP)
PHP 8.2.8
Laravel Framework 10.16.1
コンテナの用意
Webアプリケーションコンテナを用意する
Webアプリケーションコンテナを用意します。今回は自分が使い慣れているLaravel
が動くコンテナを用意します。
以下のDocker
環境を利用させていただきました
この時点のdocker-compose.yml
は以下です。
docker-compose.yml
web
コンテナのポート80番がぶつかったので、8080
と変更した以外は参照リポジトリのままです。
volumes:
db-store:
psysh-store:
configs:
db-config:
file: ./infra/docker/mysql/my.cnf
services:
app:
build:
context: .
dockerfile: ./infra/docker/php/Dockerfile
target: ${APP_BUILD_TARGET:-development}
volumes:
- type: bind
source: ./src
target: /workspace
- type: volume
source: psysh-store
target: /root/.config/psysh
volume:
nocopy: true
environment:
- APP_DEBUG=${APP_DEBUG:-true}
- APP_ENV=${APP_ENV:-local}
- APP_URL=${APP_URL:-http://localhost}
- LOG_CHANNEL=${LOG_CHANNEL:-stderr}
- LOG_STDERR_FORMATTER=${LOG_STDERR_FORMATTER:-Monolog\Formatter\JsonFormatter}
- DB_CONNECTION=${DB_CONNECTION:-mysql}
- DB_HOST=${DB_HOST:-db}
- DB_PORT=${DB_PORT:-3306}
- DB_DATABASE=${DB_DATABASE:-laravel}
- DB_USERNAME=${DB_USERNAME:-phper}
- DB_PASSWORD=${DB_PASSWORD:-secret}
web:
build:
context: .
dockerfile: ./infra/docker/nginx/Dockerfile
ports:
- target: 8080
published: ${WEB_PUBLISHED_PORT:-8080}
protocol: tcp
mode: host
volumes:
- type: bind
source: ./src
target: /workspace
db:
build:
context: .
dockerfile: ./infra/docker/mysql/Dockerfile
ports:
- target: 3306
published: ${DB_PUBLISHED_PORT:-3306}
protocol: tcp
mode: host
configs:
- source: db-config
target: /etc/my.cnf
volumes:
- type: volume
source: db-store
target: /var/lib/mysql
volume:
nocopy: true
environment:
- MYSQL_DATABASE=${DB_DATABASE:-laravel}
- MYSQL_USER=${DB_USERNAME:-phper}
- MYSQL_PASSWORD=${DB_PASSWORD:-secret}
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD:-secret}
mailpit:
image: axllent/mailpit
ports:
- target: 8025
published: ${MAILPIT_PUBLISHED_PORT:-8025}
protocol: tcp
mode: host
また、ディレクトリ構成はこのようになっています。infra/docker
ディレクトリ以下に各コンテナごとの設定ファイルがあります。
LocalStackのコンテナを追加する
先程のdocker-compose.yml
に公式を参考にしつつ、LocalStack
のコンテナを追加します。
+ aws:
+ build:
+ context: .
+ dockerfile: ./infra/docker/aws/Dockerfile
+ ports:
+ - "127.0.0.1:4566:4566" # LocalStack Gateway
+ - "127.0.0.1:4510-4559:4510-4559" # external services port rang
+ environment:
+ - DEBUG=${DEBUG-}
+ volumes:
+ - "./infra/docker/aws/s3/scripts:/etc/localstack/init/ready.d"
+ - "${LOCALSTACK_VOLUME_DIR:-./infra/docker/aws/volume}:/var/lib/localstack"
なおLambda
を使用する場合は/var/run/docker.sock
をボリュームとしてマウントする必要がある1、とのことですが今回Lambda
は使用しないためマウントしていません。
volumes
の./infra/docker/aws/s3/script
については後述します。
LocalStack用のディレクトリを用意
先程のinfra/docker
ディレクトリ以下に、aws
ディレクトリを用意します。
cd infra/docker
mkdir aws
aws
ディレクトリにはs3/scripts
とvolume
ディレクトリを用意しておきます。また、s3/scripts
にはinit.sh
も用意します。
cd aws
mkdir -p s3/scripts
mkdir volume
touch s3/scripts/init.sh
# init.shが実行権を持っているか確認してください
ls -al s3/scripts/init.sh
# 実行権を持っていなかった場合は実行権が必要です(以下はrootユーザー権限で実行)
chmod u+x s3/scripts/init.sh
今ディレクトリはこのようになっています。赤枠が追加した部分です。
AWSリソースの初期化スクリプトを作成する
以下で紹介されていたやりかたを参考にし、AWSリソース(S3)の初期化スクリプトを作成します。
先程の作成したinit.sh
にこのように追記しました。
今回はバケット直下ではなく、その下にディレクトリ(キー)を作ってアップロードしたいためその設定も追加しています。
#!/bin/bash
readonly BUCKET_NAME="sample-bucket"
# S3にバケットを作成してファイルを初期化
awslocal s3 mb "s3://${BUCKET_NAME}"
awslocal s3 ls "s3://${BUCKET_NAME}"
# ファイルアップロード先作成
awslocal s3api put-object --bucket "${BUCKET_NAME}" --key uploads/
# AWS CLIのプロファイルを設定
aws configure --profile localstack <<EOF
dummy # AWS Access Key ID
dummy # AWS Secret Access Key
us-east-1 # Default region name
json # Default output format
EOF
docker-compose.yml
のapp
コンテナのenvironment
にAWS
関連の設定も追加しておきます。
services:
app:
# 省略
environment:
# 省略
- DB_PASSWORD=${DB_PASSWORD:-secret}
+ - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-dummy}
+ - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-dummy}
+ - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-us-east-1}
+ - AWS_BUCKET=${AWS_BUCKET:-sample-bucket}
+ - AWS_USE_PATH_STYLE_ENDPOINT=${AWS_USE_PATH_STYLE_ENDPOINT:-true}
docker compose
コマンドでbuild
とup
に成功し、localstack
(今回はaws
と指定したのでaws
と付く名前のコンテナ)コンテナが起動していたらひとまず成功です
こちらのREADME.mdを参考に、Laravel
をインストールし、ブラウザからLaravel
のwelcomeページが確認できるようにしておきます。
ファイルをLocalStackコンテナのS3にアップロードできるようにする
以降はLaravel
のwelcomeページが確認できるようになっている前提で進みます。
AWS SDK for PHP のインストール
まずAWS SDK for PHP
をインストールします。
composer require aws/aws-sdk-php
インストールされたSDKのバージョンが2系だった場合
自分が↑このコマンドだけでインストールを試みたところ、SDKのバージョンが2系だったので、composer.json
でバージョンを指定し(guzzlehttp
もバージョンの指定が必要でした)、composer update
したら成功しました
{
"require": {
"php": "^8.1",
+ "aws/aws-sdk-php": "^3.0",
+ "guzzlehttp/guzzle": "^6.0|^7.0",
+ "guzzlehttp/psr7": "^1.0|^2.0",
+ "guzzlehttp/promises": "^1.4.0",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.2",
"laravel/tinker": "^2.8"
},
}
composer update
.env
ファイルの編集
ローカル環境でアップロードができるように、.env
のAWS関連の環境変数を以下のようにします。
AWS_ACCESS_KEY_ID=dummy
AWS_SECRET_ACCESS_KEY=dummy
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=sample-bucket
AWS_USE_PATH_STYLE_ENDPOINT=true
AWS_ENDPOINT=http://aws:4566
AWS_BUCKET
の値は自身が設定したバケット名にし、AWS_ENDPOINT
のaws
の部分は、LocalStack
コンテナを他の名前にしていたら、その名前にしてください(http://localhost:4566
だと上手くいかないです。ちなみにそれになかなか気付けずに私は沼りました)。
またconfig/aws.php
を作成し、これらの環境変数をセットしてconfig
から読み込めるようにしておきます。
config/aws.php
<?php
return [
'access_key_id' => env('AWS_ACCESS_KEY_ID'),
'secret_access_key' => env('AWS_SECRET_ACCESS_KEY'),
'default_region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT'),
'endpoint' => env('AWS_ENDPOINT'),
];
Laravelではconfig:cache
コマンドを使用して設定ファイルをキャッシュします。
一度設定がキャッシュされると.env
ファイルを読み込まなくなるため、env
関数はconfig
の中でのみ使用しなくてはいけません2。
フォームの用意
ファイルをアップロードするだけのview
を用意しておきます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{-- CSS部分は省略 --}}
<title>Document</title>
</head>
<body>
<h1>ファイルをアップロードする</h1>
<form action="{{ route('upload') }}" method="post" enctype="multipart/form-data" name="uploadForm">
@csrf
<input type="file" name="uploadFile">
<button type="submit">送信</button>
</form>
</body>
</html>
アップロード時の処理をするURLも設定しておきます。
Route::post('upload', [FileUploadController::class, 'create'])->name('upload');
アップロード処理を行うController
とりあえずのサンプルなのでバリデーションチェックなし、アーキテクチャとかも気にせずController
で全部やっちゃうスタイルになっています。あくまでアップロードの処理だけ参考に...。
(S3
に慣れていなかったので、最初Key
が何を指すのか分からん!となって困惑しました。)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Aws\S3\Exception\S3Exception;
use Aws\S3\S3Client;
class FileUploadController extends Controller
{
// ファイルアップロード先
private const S3_KEY = 'uploads/';
private S3Client $s3Client;
public function __construct()
{
$this->s3Client = new S3Client([
'version' => 'latest',
'region' => config('aws.default_region'),
'endpoint' => config('aws.endpoint'),
// パス形式のエンドポイントを使用するかどうか(bool)
// ローカル環境ではtrueにしないと上手くいきませんでした
'use_path_style_endpoint' => config('aws.use_path_style_endpoint')
]);
}
/**
* 初期表示
*/
public function index()
{
return view('fileUpload');
}
/**
* ファイルアップロード
*/
public function create(Request $request)
{
if (!$request->hasFile('uploadFile')) {
return;
}
$file = $request->file('uploadFile');
$originalName = $file->getClientOriginalName();
// 今回はアップロード時のファイル名のままでS3にアップロードしています
$uploadFileName = self::S3_KEY . $originalName;
try {
$this->s3Client->putObject([
'Bucket' => config('aws.bucket'),
'Key' => $uploadFileName,
'SourceFile' => $file->getRealPath(),
]);
return redirect()->action([self::class, 'index']);
} catch (S3Exception $e) {
echo $e->getAwsErrorMessage() . "\n";
}
}
}
ファイルを指定して送信ボタンを押すと...
ファイルのアップロードができました!
上のサンプルでは割愛していますが、viewでアップロードファイルの一覧を表示するようにしています。
コウテイペンギンはかわいい。
ローカル環境でS3
にアップロードっぽくできてよかったです。
色々分からない中での手探りだったので、もし「こうした方が良いよ」ということがありましたら教えていただけると幸いです。
-
Mounting the Docker socket
/var/run/docker.sock
as a volume is required for the Lambda service. 「Getting Started
/ Installation / Starting LocalStack with Docker-Compose」より ↩ -
Configuration - Laravel 10.x - The PHP Framework For Web Artisans ↩