#動機
AWS S3にファイルをアップロードするPHP Laravelのシステムの開発で、本物のAWSに開発用のS3環境を作るのが色々とアレなのでローカルに擬似的な環境を構築したい。
できれば、Dockerでその他の環境とまとめて管理したい。
そうだ!LocalStackを使おう!
#LocalStackとは
LocalStackは、AWSのモックフレームワーク
AWSの様々なサービスをローカル環境で擬似的に利用することができる。
#環境
macOS Sierra 10.12.6
PHP 7.0
Laravel 5.5
LocalStack
Docker 17.06.2-ce
#前提
- Dockerがインストールされている。
- Laravelが動くWebサーバ用の Docker image がある。
#概要
- HTML画面から画像をアップロードしてAWS S3へ保存する処理のLaravel開発環境を構築する。
- 基本的に全部ローカルPCのDockerを使う。
- S3の環境は、LocalStackを使う。
#環境構築
##docker-compose 構成
(localstack
とlocalhost
と、間違えやすい名前にしてしまったので注意)
# Laravel and LocalStack
version: '2.2'
networks:
aws_net:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.16.238.0/24
gateway: 172.16.238.1
services:
# LocalStack
localstack:
image: localstack/localstack:latest
container_name: localstack
environment:
- SERVICES=s3
- DEFAULT_REGION=us-east-1
networks:
- aws_net
ports:
- "8080:8080"
- "4567-4578:4567-4578"
# awscli
awscli:
image: xueshanf/awscli
environment:
- AWS_DEFAULT_REGION=ap-northeast-1
- AWS_DEFAULT_OUTPUT=json
- AWS_ACCESS_KEY_ID=ando
- AWS_SECRET_ACCESS_KEY=mizue
extra_hosts:
- "localstack:172.16.238.1"
- "ando-test.localstack:172.16.238.1"
depends_on:
- localstack
entrypoint: aws --endpoint-url=http://localstack:4572 s3 mb s3://ando-test/
# web - web
web1:
image: phpweb :latest
container_name: web1
environment:
- AWS_DEFAULT_REGION=ap-northeast-1
- AWS_DEFAULT_OUTPUT=json
- AWS_ACCESS_KEY_ID=ando
- AWS_SECRET_ACCESS_KEY=mizue
volumes:
- "./apache/conf.d/httpd-web.conf:/etc/httpd/conf.d/httpd-web.conf"
- "../repo/localstacktest/:/var/www/project/"
ports:
- "8090:8090"
extra_hosts:
- "localstack:172.16.238.1"
- "ando-test.localstack:172.16.238.1"
depends_on:
- localstack
-
Laravel(web1)からLocalStackへアクセスしようとすると、ホスト名解決で問題が起こるので、
extra_hosts
でhostsを追加している。
172.16.238.1
が、LocalStackに割り当てているネットワークのIP。
S3アップロードの時に、{バケット名}.{aws s3のエンドポイント}
というホストで接続するようなので、それも入れておく。 -
environment
に指定しているAWS関連の値は、特にどこかに登録してあるものではなく、適当な文字列でも大丈夫らしい。 -
サービス名
awscli
は、LocalStackが起動してから、バケットを作成するコマンドを送信するためだけのサービス。imagexueshanf/awscli
に aws cliが入っているのでそれを利用。
LocalStackのコンテナ起動でやろうとしたけど、うまくいかなかったので別サービスにした。 -
imageの
phpweb
は、CentOS7 + Apache + PHP7.0 + aws cli などが入っている独自のイメージ。 -
Laravelプロジェクトは、'web1' の
/var/www/
の下で create project して作成済み。
##動かしてみる
コンテナ立ち上げ
$ docker-compose up -d
Creating network "docker_default" with the default driver
Creating network "docker_aws_net" with driver "bridge"
Creating localstack ...
Creating localstack ... done
Creating docker_awscli_1 ...
Creating web1 ...
Creating docker_awscli_1
Creating docker_awscli_1 ... done
立ち上がったか確認
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3612e83f6d87 xueshanf/awscli "aws --endpoint-ur..." 6 minutes ago Exited (0) 5 minutes ago docker_awscli_1
53c39d930ca8 phpweb:latest "/bin/sh -c '/usr/..." 6 minutes ago Up 6 minutes 0.0.0.0:8090->8090/tcp web1
586886cdb457 localstack/localstack:latest "/usr/bin/supervis..." 6 minutes ago Up 6 minutes 0.0.0.0:4567-4578->4567-4578/tcp, 0.0.0.0:8080->8080/tcp, 4579-4582/tcp localstack
バケットができているはずなので確認してみる。
Webサーバのコンテナで、aws cliで確認。
--endpoint-url
に、今回のLocalStackのS3エンドポイントを指定するだけで、あとは通常のAWSと同じ。
(aws configure を求められたら、適当な文字列を入れておけばいいみたい)
LocalStackのエンドポイントはサービスによって決まっていて、s3は http://LocalStackのホスト:4572
になるので、今回は、http://localstack:4572
$ docker exec -it web1 /bin/bash
[root@1ac2dc002a22 project]# aws --endpoint-url=http://localstack:4572 s3 ls
2006-02-04 01:45:09 ando-test
できてる!
中身は空っぽ!
LocalStackにはブラウザで確認できるダッシュボードがあるので、それも見てみると、バケットが現れた。
#Laravelのコード
##AWS(LocalStack)の設定
エンドポイントは 'endpoint' => env('AWS_S3_ENDPOINT'),
を追加することで変更できる。
's3' => [
'driver' => 's3',
'key' => env('AWS_KEY'),
'secret' => env('AWS_SECRET'),
'region' => env('AWS_REGION'),
'bucket' => env('AWS_BUCKET'),
'endpoint' => env('AWS_S3_ENDPOINT'),
],
環境設定
AWS_BUCKET
と AWS_S3_ENDPOINT
は正確に。
あとは適当で良いみたい。
WS_KEY=ando
AWS_SECRET=mizue
AWS_REGION=tokyo
AWS_BUCKET=ando-test
AWS_S3_ENDPOINT=http://localstack:4572
##Route
Route::get('photo', 'PhotoController@index');
Route::post('photo', 'PhotoController@upload');
##Controller
<?php
namespace App\Http\Controllers;
use Storage;
use Request;
use App\Http\Requests\PhotoRequest;
class PhotoController extends Controller
{
/**
* ファイル一覧表示
*
*/
public function index()
{
// バケットの中のファイル名一覧取得
$files = Storage::disk('s3')->files();
// ファイルリスト生成
$list = [];
foreach ($files as $file) {
$date = Storage::disk('s3')->lastModified($file);
$list[$date] = [
'name' => $file,
'date' => date("Y-m-d H:i:s", $date),
'type' => pathinfo($file, PATHINFO_EXTENSION),
'size' => Storage::disk('s3')->size($file),
];
}
// 日付降順にソート
krsort($list);
// 画面表示
return view('photo.index')->with(['list' => $list]);
}
/**
* アップロードファイルをs3に保存する
*
*/
public function upload(PhotoRequest $request)
{
// アップロードされたファイルを取得
$file = Request::file('photo');
$image_data = file_get_contents($file->getRealPath());
// s3へ保存
Storage::disk('s3')->put($file->getClientOriginalName(), $image_data, 'public');
// 一覧画面へ
return redirect('/photo');
}
}
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class PhotoRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'photo' => ['required', 'image', 'max:1024'], // 必須, 画像のみ, 1024KBまで
];
}
}
##View
<!doctype html>
<html lang="ja">
<head>
<title>LocalStack S3 test</title>
<link rel="stylesheet" href="/bootstrap-3.3.7/css/bootstrap.min.css">
<script src="/js/jquery-3.2.1.min.js"></script>
<script src="/bootstrap-3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<header>
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</header>
<section id="form">
<div class="panel panel-default">
<div class="panel-body">
<img id="preview" class="img-thumbnail" src="" width="150px">
<form method="post" action="/photo" enctype="multipart/form-data">
{{ csrf_field() }}
<input id="target" type="file" name="photo" multiple>
<input class="btn btn-primary btn-lg" type="submit" value="UPLOAD!!">
</form>
</div>
</div>
</section>
<section id="list">
@foreach ($list as $photo)
<hr />
<div class="media">
<a class="media-left" href="#">
{{-- 画像 --}}
<img width="100px" class="media-object img-thumbnail" src="{{env('AWS_S3_BASE_URL')}}/{{env('AWS_BUCKET')}}/{{$photo['name']}}">
</a>
<div class="media-body">
{{-- 画像情報 --}}
<p>date : {{ $photo['date'] }}</p>
<p>name : {{ $photo['name'] }}</p>
<p>size : {{ ($photo['size'] > (1024*1024)) ? round($photo['size']/1024/1024,2).'MB' : round($photo['size']/1024).'KB'}} </p>
</div>
</div>
@endforeach
</section>
<footer>
<hr />
</footer>
<script>
$(function(){
{{-- 選択画像のプレビュー --}}
$("#target").change( function() {
var file = this.files[0];
fileReader = new FileReader();
fileReader.onload = function(event) {
$("#preview").attr('src', event.target.result);
};
fileReader.readAsDataURL(file);
});
});
</script>
</body>
</html>
##テストコード
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
class PhotoTest extends TestCase
{
/**
* 画像アップロード
*/
public function testUpload()
{
Storage::fake('s3');
// 正常
$response = $this->post('/photo', [
'photo' => UploadedFile::fake()->image('pic1.jpg')
]);
Storage::disk('s3')->assertExists('pic1.jpg');
// エラー:サイズ
$response = $this->post('/photo', [
'photo' => UploadedFile::fake()->image('pic2.jpg')->size(1024*2)
]);
Storage::disk('s3')->assertMissing('pic2.jpg');
// エラー:ファイルタイプ
$response = $this->post('/photo', [
'photo' => UploadedFile::fake()->create('pic3.pdf')
]);
Storage::disk('s3')->assertMissing('pic3.pdf');
}
}
##Test
[root@1ac2dc002a22 project]# ./vendor/bin/phpunit
PHPUnit 6.3.0 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 2.13 seconds, Memory: 16.00MB
OK (3 tests, 5 assertions)
ここでは、実際にファイルをアップロードしているわけではないので・・
#実際に画像をアップロードしてみる
アップロード前。
バケットには何もない。
[root@0e0e1473903e project]# aws --endpoint-url=http://localstack:4572 s3 ls s3://ando-test/
アップした画像をブラウザ表示できてる。
aws cliでも確認。
アップしたファイルがある。
[root@0e0e1473903e project]# aws --endpoint-url=http://localstack:4572 s3 ls s3://ando-test/
2017-09-22 19:12:19 903944 mizue.jpg
できた!
これでファイルアップし放題!
##注意
dockerを終了させるとアップしたファイルはバケットごと消えます。
#参照
LocalStack(Docker Hub)
LocalStack(Git Hub)
Laravel 5.5 HTTP Tests