無償版LocalStackでのRuby SDKを用いたS3操作検証方法

はじめに

AWS上でのシステムを構築する際に簡単な挙動をローカル環境で確認したいという要望はちょくちょく聞きます。そのときによく使われるのがLocalStackです。

www.localstack.cloud

有償版・無償版それぞれあるのですが、無償版でも多くの機能が使えます。詳細は以下を参照。

docs.localstack.cloud

今回はこのLocalStackの無償版を使ってRuby SDKでS3の操作を試してみます。

前提

以下のバージョンで試しました。

  • LocalStack 3.3.0
  • Ruby 3.3.0
  • AWS SDK S3 gem 1.146.1

LocalStackの準備

LocalStackのセットアップはDockerを使うと簡単です。以下のようなcompose.ymlを作成しました。

version: "3.8"

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}"
    image: localstack/localstack:3.3.0
    ports:
      - "127.0.0.1:4566:4566"
    environment:
      - DEBUG=${DEBUG:-0}
    volumes:
      - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"
  app:
    build: .
    tty: true
    volumes:
      - .:/myapp/

アプリの準備

アプリ検証用としてDockerfileも用意しておきます。

FROM ruby:3.3.0-slim

WORKDIR /myapp

COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock

RUN bundle install

COPY lib /myapp/lib

gemの管理用としてGemfileを以下の内容で作成します。

source 'https://rubygems.org'

gem 'aws-sdk-s3', '~> 1.146.1'

Gemfile.lockは空ファイルとして作成します。

アプリはlib/app.rbとして準備しておきます。今回はこんな感じで。

require 'aws-sdk-s3'

region = "us-east-1"
bucket_name = "bucket01"
key_name = "key01"

Aws.config.update(
  endpoint: "http://localstack:4566",
  access_key_id: "test",
  secret_access_key: "test",
  region: region,
  force_path_style: true
)

client = Aws::S3::Client.new

# バケットを作成
client.create_bucket(bucket: bucket_name)
p client.list_buckets

# オブジェクトを作成
client.put_object(bucket: bucket_name, key: key_name, body: "Hello World")

# オブジェクトを読み込み
p client.get_object(bucket: bucket_name, key: key_name).body.read

# オブジェクト情報一覧
p client.list_objects_v2(bucket: bucket_name)

実行

準備が整えば、docker compose upを実行するとアプリが起動します。起動しているコンテナのうち、appコンテナに接続してruby lib/app.rbを実行するとAWS SDK S3 gemを利用したS3の動作が確認できます。

## ↓ client.list_bucketsの実行結果
#<struct Aws::S3::Types::ListBucketsOutput buckets=[#<struct Aws::S3::Types::Bucket name="bucket01", creation_date=2024-04-07 04:47:48 UTC>], owner=#<struct Aws::S3::Types::Owner display_name="webfile", id="xxxxx">>
## ↓ client.get_objectの実行結果
"Hello World"
## ↓ client.list_objects_v2の実行結果
#<struct Aws::S3::Types::ListObjectsV2Output is_truncated=false, contents=[#<struct Aws::S3::Types::Object key="key01", last_modified=2024-04-07 05:18:59 UTC, etag="\"yyyyy\"", checksum_algorithm=[], size=11, storage_class="STANDARD", owner=nil, restore_status=nil>], name="bucket01", prefix="", delimiter=nil, max_keys=1000, common_prefixes=[], encoding_type=nil, key_count=1, continuation_token=nil, next_continuation_token=nil, start_after=nil, request_charged=nil>

考察・注意点

LocalStackはあくまで他ベンダーによるエミュレータですので、挙動がAWSのものと一致しているとは限らないのは注意が必要です。

また、開発用としてAws.config.updateendpointaccess_key_idなどは個別に設定する必要があり、環境変数化などのなんらかの対処は必要になるかなと思います。

最後に、Aws.config.updateで指定しているforce_path_style: trueについてです。S3はPath styleとVirtual hosted styleの2種類のアドレスモデルがあり、Path styleは廃止予定となっています。

aws.amazon.com

force_path_style: trueの記述は、Path styleでアクセスすることを指定するものです。この記述がない場合、以下のエラーメッセージが出力されます。

/usr/local/lib/ruby/3.3.0/net/http.rb:1603:in `initialize': Failed to open TCP connection to bucket01.localstack:4566 (getaddrinfo: Name or service not known) (Seahorse::Client::NetworkingError)

なんらかの回避方法を探しているのですが、この記事を書いているときには見つけることができませんでした。