LocalStackでAWSローカル環境を構築

GMOメイクショップコアグループでエンジニアをしている池田です。
先日指定された期間に発行された領収書(PDF)をEFSから読み、zipファイルにまとめてS3にアップロードしてダウンロードできるようにする機能を開発しました。 簡単に図で表すとこんな感じです。

開発にあたってローカルで動作確認は必須ですが、上記のAWS環境をローカルで再現するのは難しいです。
今回はLocalStackというツールを使いAWS環境をローカルで簡単に構築する方法をご紹介したいと思います。

LocalStackとは

LocalStackはAWSクラウド環境をローカルマシン上で再現できるツールです。 ローカルにインストールしたり、Dockerを使って利用することも可能です。 今回はDockerを利用してセットアップし、上記の図のAWS部分を構築した方法を紹介していきたいと思います。

無料版と有料版

無料版(Community Edition)と有料版(Pro/Starter,Teams,Enterprise Edition)があります。無料版の場合は以下の2つが大きく異なります。 当プロジェクトでは無料版を利用しました。EFSが無料版では利用できないため、代替手段で対応しました。 ここで扱う内容も無料版を想定しています。

  • データが永続化されない
    • 例えばDockerを利用してLocalStackを使用している場合、コンテナを停止すると作成したAWSリソースは消失してしまいます。
    • 後述するライフサイクルフックを利用して対策することも可能です。
  • 利用できないサービスがある
    • こちらで利用可能なサービスが確認できます。
    • localstack status services を実行して確認することもできます。

LocalStack導入でどう変わったか

導入前

導入前はElasticMQと自前実装でLambdaがSQSからメッセージをポーリングする挙動を再現していました。

  • SQS: ElasticMQ
  • Lambda: SQSとLambdaをイベントマッピングした時の動きを再現するため、SQSからポーリングを行うためのコンテナを別途用意
  • S3: Minio

ElasticMQ → SQSのエミュレーター、Minio → S3のエミュレーターです。

ElasticMQを使ってローカルでAWS SQSをモックする

S3 互換のシンプルなオブジェクトストレージ Minio を使う #minio - Qiita

導入後

LocalStack上にSQSとLambda関数をデプロイし、イベントマッピングも作成しました。

  • SQS: LocalStack
  • Lambda: SAMで作成した関数をLocalStack上にデプロイ、SQSとのイベントマッピングも作成
  • S3: 特に問題が無かったため、引き続きMinioを使用

導入によって変わった部分としては、SQSからポーリング処理を行うというAWS上の動きを自前で再現するローカル用の仕組みが不要になったことです。 また、AWS上の動きをローカルでテストできた点も大きなメリットでした。 例えばデッドレターキューを入れた時のLambdaのリトライの動きが想定通りかローカルでテストできました。

セットアップ

下記のようなcompose.yamlを用意し、docker compose up -dでコンテナを起動するだけで使用開始できます。

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}"
    image: localstack/localstack
    ports:
      - "127.0.0.1:4566:4566"                       # LocalStack Gateway
      - "127.0.0.1:4510-4559:4510-4559"  # external services port range
    environment:
      # LocalStack configuration: https://docs.localstack.cloud/references/configuration/
      - DEBUG=${DEBUG:-1}
      - AWS_DEFAULT_REGION=ap-northeast-1
      - LOCALSTACK_HOST=localstack
    volumes:
      - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

コンテナが起動したら、CLIでリソースの作成・操作が可能になります。

例: S3にバケットを作成

# awscliでエンドポイント指定し、LocalStackに対して実行
aws --endpoint-url=http://localhost:4566 s3 mb s3://my-bucket

このエンドポイントの指定が毎回煩わしいです。その場合はawscliではなく、awscli-localを使うとエンドポイントの指定が不要になります。

# インストール
brew install awscli-local
# awscli-localは向き先がLocalStackになっているので、エンドポイント指定不要
awslocal s3 mb s3://my-bucket

ライフサイクルフックを利用して初期化

無料版では永続化されないというのは前述した通りですが、ライフサイクルフックを利用して初期化スクリプトを準備すれば、コンテナ起動時に毎回リソースの作成を行うことができます。 下記のように各ライフサイクルごとにディレクトリが用意されているので、そこに実行したいスクリプト(.shまたは.py)を配置します。

/etc
└── localstack
    └── init
        ├── boot.d           <-- executed in the container before localstack starts
        ├── ready.d          <-- executed when localstack becomes ready
        ├── shutdown.d       <-- executed when localstack shuts down
        └── start.d          <-- executed when localstack starts up
echo "setup SQS"
awslocal sqs create-queue --queue-name sample-queue.fifo --attributes FifoQueue=true

用意したスクリプトをホストマシンからコンテナ内の初期化用ディレクトリにマウントします。

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}"
    image: localstack/localstack
    ports:
      - "127.0.0.1:4566:4566"                       # LocalStack Gateway
      - "127.0.0.1:4510-4559:4510-4559"  # external services port range
    environment:
      # LocalStack configuration: https://docs.localstack.cloud/references/configuration/
      - DEBUG=${DEBUG:-1}
      - AWS_DEFAULT_REGION=ap-northeast-1
      - LOCALSTACK_HOST=localstack
    volumes:
+     - "./init-aws.sh:/etc/localstack/init/ready.d/init-aws.sh"
      - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

SAMと組み合わせて使う

今回の機能ではLambdaをSAMで作成したので、SAM CLIを使ってLocalStackへLambda関数をデプロイできるようにしました。 以下のどちらかの方法でデプロイできます。

方法1: aws-sam-cli-localを使用する

aws-sam-cliのラッパーであるaws-sam-cli-localを使用することで、特別な設定なしでLocalStackにLambda関数をデプロイできます。 homebrewなどには対応していないようなので、pipでインストールします。

pip install aws-sam-cli-local

その後はsamlocal deployでLocalStackにLambda関数をデプロイすることができます。

方法2: AWS_ENDPOINT_URLにLocalStackのエンドポイントを指定する

aws-sam-cli-localをインストールせずにデプロイすることができます。

AWS_ENDPOINT_URL="http://localhost:4566" sam deploy --guided

デプロイが完了すると、SAMで管理しているリソースは全てLocalStack上に構築されます。 当プロジェクトでは、Lambda関数とSQSのイベントマッピングのみSAMで管理しています。

これで必要なリソース全てローカルに構築できました。 SQSにメッセージを送信すると、Lambda用のコンテナが別で起動しLambdaの処理が実行されます。

EFSはどうしたのか。。。

EFSは残念ながら無料版では使用できません。そのため、以下のような方法でLambdaにEFSをマウントした状態を擬似的に再現しました。

  1. 初期化スクリプトでLocalStack上にS3のバケットを作成しPDFを保存
  2. Lambdaにローカル用のセットアップ処理を作成
  3. 1.で作成したバケットからPDFを読み取り
  4. Lambdaコンテナの/tmpディレクトリに保存

SQSとLambdaの部分は改善しましたが、ローカル環境固有の処理を完全に無くせなかった点は心残りです。

まとめ

無料版でも十分に役に立つツールなのでぜひ使ってみてください。 AWSサービスのローカル用モックとしてはもちろんですが、事前にローカルで挙動を調査する、AWS資格の勉強で実際に手を動かしてみるというケースでも活用できそうです。 Lambda, S3, API Gateway, DynamoDBなどのサーバーレス構成でよく利用されるサービスは無料版でも使用可能なため、ローカル環境をLocalStackだけで構築できるところに可能性を感じました。

参考

docs.localstack.cloud