LocalStack で Rails による S3 の投稿をモックする


LocalStack は、AWS のサービスを開発環境において擬似的に使用できるモックフレームワークです。
定番の S3 から Lambda まで多くのサービスを開発環境で体験できます。

本記事では、Railsチュートリアルの SampleApp を使用し、コンテナ環境で実装します。
コンテナの設定は、Railsチュートリアルの開発環境を Docker でもっと便利にしなイカ!? - Qiitaの続きとなります。

今回の実装コードは右の通りです=>(実装コード --github)



□ LocalStack の設定

■ コンテナの追加

  • version 0.11.0 からhttp://localhost:4566で全ての endpoint を受け付けている。
  • SERVICESにより、モックするサービスを定義する。
  • DATA_DIR: /tmp/localstack/dataにより、投稿画像を永続化する。
  • volumesで設定したディレクトリにスクリプトを配置すると、コンテナ生成時に実行してくれる( 詳細は後述 )。
  • なお、access key 等を設定する必要はない。
./docker-compose.yml
  ...
  smtp:
    ...
+ localstack:
+   image: localstack/localstack
+   ports:
+     - 8080:8080 # dashboard
+     - 4566:4566 # edge port
+   environment:
+     SERVICES: s3
+     AWS_DEFAULT_REGION: ap-northeast-1
+     DATA_DIR: /tmp/localstack/data
+   volumes:
+     - ./docker/localstack/:/docker-entrypoint-initaws.d
  volumes:
    ...

■ 初期設定スクリプト

S3 保存用のバケットを生成する必要があるため、次のファイルを作成すること。

./docker/localstack/setup_s3.sh
awslocal s3 mb s3://microposts

□ 既存ファイルの修正

■ 保存場所を S3 に固定

app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
  ...
- if Rails.env.production?
-   storage :fog
- else
-   storage :file
- end
+ storage :fog
  ...
end

■ carrier_wave の初期設定を変更

config/initializers/carrier_wave.rb
CarrierWave.configure do |config|
  config.fog_credentials = {
    :provider              => 'AWS',
    :region                => ENV['S3_REGION'],
    :aws_access_key_id     => ENV['S3_ACCESS_KEY'],
    :aws_secret_access_key => ENV['S3_SECRET_KEY'],
  }
  config.fog_directory     =  ENV['S3_BUCKET']

  unless Rails.env.production?
    config.fog_credentials.merge!(
      {
        # [app -> localstack] コンテナ間の通信用に設定 ( http://localstack:4566 )
        :endpoint          => ENV['S3_ENDPOINT'],

        # デフォルトだと S3_BUCKET がサブドメインとなり接続できないため true に設定
        :path_style        => true,
      }
    )

    # endpoint がコンテナ間の通信用であるため、ホスト側から画像にアクセスするための URL ( http://localhost:4566 )
    config.asset_host = "#{ENV['S3_ASSET_HOST']}/#{ENV['S3_BUCKET']}"
  end
end

■ 環境変数の設定

./docker-compose.yml
  ...
  app:
    ...
    environment:
      APP_DATABASE_HOST: db
      APP_DATABASE_USERNAME: root
      APP_DATABASE_PASSWORD: pass
+     S3_REGION: ap-northeast-1
+     S3_ACCESS_KEY: dummy
+     S3_SECRET_KEY: dummy
+     S3_BUCKET: microposts
+     S3_ENDPOINT: http://localstack:4566
+     S3_ASSET_HOST: http://localhost:4566
    ...

□ テスト投稿

投稿前
投稿成功!

□ 余談: 投稿画像の永続化

前述の通りDATA_DIRを追加すると、投稿画像の永続化が可能となるが、コンテナ停止再起動でのみ有効となる。
もし、コンテナ削除時でもデータを保持したい場合、次の通り設定すること。

また、この設定の場合、意識的に volume を削除しないと保存容量が膨れ上がるため、注意する必要あり。

./docker-compose.yml
services:
  datastore:
    image: busybox
    volumes:
      - bundle_install:/usr/local/bundle
      - db_data:/var/lib/postgresql/data
+     - localstack_data:/tmp/localstack
...
  localstack:
    image: localstack/localstack
    ports:
      - 8080:8080 # dashboard
      - 4572:4572 # s3
    environment:
      SERVICES: s3
      AWS_DEFAULT_REGION: ap-northeast-1
      DATA_DIR: /tmp/localstack/data
    volumes:
      - ./docker/localstack/:/docker-entrypoint-initaws.d
+     - localstack_data:/tmp/localstack
volumes:
  bundle_install:
  db_data:
+ localstack_data:

□ メモ

コンテナから S3 保存を確認

docker-compose exec localstack ash

aws --endpoint-url=http://localstack:4572 s3 ls s3://${バケット名}

S3 に保存したファイルをコンテナ内にダウンロード

docker-compose exec localstack ash

aws --endpoint-url=http://localstack:4572 s3 cp #{key} ~/
cat ~/#{key}

ブラウザからファイルを確認

open http://localhost:4572/#{バケット名}

# TODO: ファイルの中身は 403 によりアクセス不可,何かしら設定が必要なはず
open http://localhost:4572/#{key}