かなり時間かかっちゃった上に
有力な情報がなかなか得られず結構苦しんだのでメモ
local開発でS3が使いたいんだけど!
そもそものスタートはこちら。
AWS上で動かし知ているRoRのシステムで
諸事情によりレールに乗らずに開発をしていたのですが
ローカルの開発環境であるdocker上でS3のモックが欲しくなり調べて実装してみました。
それ「localstack」があるよ
githubはこちら
https://github.com/localstack/localstack
AWSのモックを提供してくれるとても優秀なプロジェクトです。
一つのインスタンスでサービス毎にポートを割り当てて稼働させているようです。
詳細はドキュメントで。
localstackを立ち上げる
今回はdocker-composeで立ち上げるので docker-compose.yml
はこんな感じ
version: '2'
services:
aws:
image: localstack/localstack
ports:
- 8080:8080
- 4567-4578:4567-4578
environment:
- DEFAULT_REGION=ap-northeast-1
- HOSTNAME=localstack.test
- DATA_DIR=/tmp/localstack
privileged: true
ports
でポートを外部に開放してあげる事でブラウザからも参照できます。
環境変数に関しては
DEFAULT_REGION
で東京に指定
HOSTNAME
は内部でドメインに割り当てるのに必要
DATA_DIR
はデータの永続化のために割り当てています。
たぶんこれであっているはず・・・
他にも大量に設定はあるので詳しくはgithubのREADMEでも読んでみてください。
設定が終わったら docker-compose up -d
で立ち上げます
プロジェクトからアップをしてみる
環境設定とか
ローカルと本番で向き先を変えたいので設定ファイルと設定の読み込みを作ります
default: &default
id: <%= ENV.fetch("AWS_S3_ACCESS_KEY_ID") %>
access_key: <%= ENV.fetch("AWS_S3_SECRET_ACCESS_KEY") %>
region: ap-northeast-1
endpoint: https://s3.ap-northeast-1.amazonaws.com
bucket_name: oreno-bucket
development:
<<: *default
endpoint: http://localstack.test:4572
test:
<<: *default
staging:
<<: *default
production:
<<: *default
bucket_name: honban-no-bucket
こんな感じで設定ファイルを作って読み込みのスクリプトを置きます
Rails.application.config.s3 = YAML.load(ERB.new(File.read("#{Rails.root}/config/s3.yml")).result)[Rails.env]
環境情報は Rails.env
に入っているのでここを参照
ymlの中でrubyのscriptを使いたいので ERB
を使って読み込ませています。
アップロードの処理
コードで書くとこんな感じです
require 'aws-sdk-s3'
class S3::Handle
def initialize
@s3 = Aws::S3::Resource.new(
region: Rails.application.config.s3["region"],
endpoint: Rails.application.config.s3["endpoint"],
force_path_style: Rails.env.development?,
credentials: Aws::Credentials.new(
Rails.application.config.s3["id"],
Rails.application.config.s3["access_key"]
)
)
end
def upload_image(bucket_name, key_name, image)
begin
@s3.bucket(bucket_name)
# ここがファイル名とかファイルパスになる
.object(key_name)
# `acl` でアクセス制御を追加しておく
.put(body: image, acl: 'public-read')
return true
rescue => err
return false
end
end
end
あんまりrails的な書き方では無いんだろうなと思いつつ・・・
Aws::S3::Resource
の引数として設定している中で
endpoint
はドメインの向き先の指定を
force_path_style
はbucket_nameがサブドメインに含ませるかどうかの設定になります。
localstackを使う際には少なくともこの2点が本番稼働の時と違う設定になるのでお気をつけください。
ブラウザで参照出来なくて困ったにゃん・・・
適当に放り投げて適当に名前付けて保存してブラウザでimgタグで表示しようとしたら出来なかったので
片っ端から jpeg
に変換するという暴挙に出ました。
ImageMagickのインストール
yum
で入れるなら1行で終わりです。
RUN yum -y install ImageMagick ImageMagick-devel
もしDebian系とかうぶんちゅとかであればググって下さい。
情報はたくさんあります。
RMagickのインストール
これもgemなんで一瞬ですね。
gemファイルに以下の行を追加して gem install
でOKです。
gem "rmagick"
jpeg変換をかけてみる
formからPOSTされてきたファイルをjpegに変換するのでcontroller側はこんな感じです。
class ImagesController
# ~~~~ 中略 ~~~~
# 画像のアップロードの処理
def upload_image
s3 = S3::Handle.new
# file名は固定なので今回はこんな感じ
file_name = 'suteki_na_image.jpg'
# pathの指定とか出来る
path = 'oreno/gazou/' + file_name
# fileの読み込みと変換
blob = s3.convert_image(params[:file].tempfile, file_name, 'JPEG')
if s3.upload_image(path, blob)
render json: {
# ドメインの設定とか読み込むためにメソッドを噛ましている
path: s3.make_image_url(path)
}
else
head 500
end
end
end
jpqgの実際の変換はこんな感じでやっています。
require 'RMagick'
class S3::Handle
# ~~~~ 中略 ~~~~
def convert_image(image, name, extension)
# `from_blob` じゃないとうまく読み込めなかったりとかするみたい
img = Magick::Image::from_blob(image.read).first
# ここで変換
img.format = extension
# ここで書き出し
blob = img.write(name).to_blob
# オブジェクトを破棄してメモリ開放
img.destroy!
return blob
end
end
ブラウザからアクセスしてみる
画像をアップロードしたらブラウザからアクセスしてみます。
pathは
http(s)://{domain}/{bucket_name}/{file_path}
の構成になるので、今回の場合は
http://localstack.test:4572/oreno-bucket/oreno/gazou/suteki_na_image.jpg
にアクセスすれば画像が見られるはずです。
「bucketが無い」ってエラーが出たんだけど!
aws-sdk-s3
の create_bucket
メソッドがうまく動かずに苦労しましたが
通常運用で動的にbucketを作らないといけない事態は想定していなかったので
普通にaws-cliで作りました。
localstackのidとkeyは適当で大丈夫みたいです。
aws-cliについてはまた別の機会に・・・
まとめ
便利な道具があるのはありがたいけどドキュメントが疲れた(語彙力