LocalStackとSAMを使ってDockerコンテナにサーバーレス環境を構築してみた

# はじめに
バックエンドにAWS Lambdaを用いる場合、どうやってローカル環境でLambda関数を動作検証しながら開発するか？が課題となると思います。

SAM（AWS Serverless Application Model）を使う場合、sam local start-apiコマンドを用いてローカル環境上でAPI Gateway, Lambda関数の動作検証をすることが可能です。

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/what-is-sam.html

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-start-api.html

# Lambda関数は他のAWSサービスに対して何か処理するケース多いよね？
しかし、API Gateway、Lambda関数だけでシステムが構成されているケースは少なく、例えばDynamoDBやS3などの他のAWSリソースに対して何かの処理をするケースが多いと思います。

例えばLambda関数の処理でS3に画像をアップロードしたい場合、
**ローカル（dockerコンテナ）にS3のようなストレージを構築するケース** や、**AWS環境上にS3だけ構築するケース** が考えられますが、どちらの方法も満足のいく結果が得られないと感じております。

## ローカルにS3（ストレージ）を構築するケース
任意のdockerイメージを用いて、dockerコンテナ上でS3のようなストレージを構築するという手が考えられますが、本番環境では使わない専用のコンテナを作るのは面倒です。S3だけならまだしも、DynamoDBなどの他のリソースもコンテナ上で構築する事になるとさらに面倒な気持ちになります。

## AWS環境上にS3だけ構築するケース
ローカルでS3を作るのが面倒なら、AWS環境で作ってしまうのも一つの手かと思います。

しかしsam local start-apiでは、Lambda関数に権限をつける事ができないため、AWS環境上にあるS3へ画像アップロードする、みたいな処理は権限エラーとなってしまいます。

# LocalStackよさそう
解決策としてLocalStackは使えそうな印象です。
LocalStack とは、ローカルマシン上に AWS 環境をエミュレートする機能をもつクラウドサービスエミュレータです。

- dockerでlocalstackコンテナを起動し、そのコンテナ上にAWS環境を構築できる
- awslocalというaws cliをwrapしたコマンドにより、AWSリソースを作成したり参照したりすることができる
- samlocalというsam cliをwrapしたコマンドにより、既存のtemplate.yamlを利用し、SAMで構成されたサーバーレス環境を構築することができる
- cdklocalなど、他の統合についても同様

https://www.localstack.cloud/

https://github.com/localstack/localstack

AWSでリソースを構築しないので料金はかかりませんし、既存の AWS 環境に不要な影響を与えることもありません。
基本的な API 群を利用できる「Community 版（無料）」や、全ての API群 を利用できる「Pro 版（有料）」があります。

https://www.localstack.cloud/pricing

## 無料版で利用できるAPI群一覧
![スクリーンショット 2024-12-19 16.46.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730383/05a6d826-7d2d-63c5-608d-6206b2126fca.png)

:::note warn
無料で使えるAPIでも、一部機能は有料というパターンがあるので注意です。
- API GatewayのREST APIは無料で利用できるがHTTP APIは有料
- Lambdaのレイヤーを利用したい場合は有料
:::

:::note warn
無料版は、コンテナを「停止」すると作成したリソースが全て消えますので注意です。その場合、再度deployして環境を再構築する必要があります。
:::




## 有料版で利用できるAPI群一覧
![スクリーンショット 2024-12-19 16.47.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730383/8447ed93-3eca-2d79-72b2-5e880a6150f0.png)


正直、有料版は手始めに導入するには費用が高いと感じます。
では無料版はどうか？という話ですが、実際に検証してみたところ、**無料版でも十分有用** だと感じたので、今回ローカル開発環境構築に向けて行ったことを記事に残したいと思います。

# 今回前提となる環境

バックエンド環境は、SAM（AWS Serverless Application Model） を利用して構築しています。AWSのリソースは以下です。
- API Gateway（REST API）
- Lambda
- DynamoDB
- Secrets Maneger

また、ローカル開発環境にはdockerを用いています。

# やってみる

以下の流れとなります。

1. インストール
2. dockerコンテナ起動
3. buildする
4. deployする

既存のSAMのtemplate.yamlを **そのまま**利用するので、びっくりするくらい簡単です。
それではやっていきましょう。

## 1. インストール

samlocalコマンドを使うため、ローカルマシンに [aws-sam-cli-local](https://github.com/localstack/aws-sam-cli-local)をインストールします。

[LocalStack 管理画面](https://app.localstack.cloud/dashboard)へのアクセスできるようにしておいた方が便利なので、サインアップ（無料）をお願いします。


## 2. dockerコンテナ起動

ドキュメントに従い、compose.yamlを修正します。

https://docs.localstack.cloud/getting-started/installation/#docker-compose

少しアレンジしていますが、ほぼドキュメント通りです。
```compose.yaml
services:
  localstack:
    container_name: localstack
    image: localstack/localstack:latest
    ports:
      - "4566:4566" # LocalStack Gateway
      - "4510-4559:4510-4559" # external services port range
    environment:
      - DEFAULT_REGION=ap-northeast-1
      - DEBUG=1 # トラブルシューティングに役立つため、DEBUGログをonに設定
    volumes:
      - localstack:/var/lib/localstack/
      - /var/run/docker.sock:/var/run/docker.sock
      - ./localstack_script:/etc/localstack/init/ready.d # localstackのDocker起動時に実行される.shスクリプトの置き場
    networks:
      app_net:
        ipv4_address: "170.28.0.3"

networks:
  app_net:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 170.28.0.0/24

volumes:
  localstack:
```

コンテナを起動しましょう。

```
$ docker compose up
```

すると、localstackコンテナが4566ポートで起動します。

## 3. buildする

samlocalコマンドを用いてbuildします。

```
$ samlocal build
```

※sam buildでもdeploy後に問題なく動きましたので、sam buildでもsamlocal buildでも違いはないかもしれません。

## 4. deployする

samlocalコマンドを用いてdeployします。

```
$ samlocal deploy --stack-name hogehoge-stack --config-env dev --no-confirm-changeset
```
- samcomfig.yamlで環境毎に読み込む環境変数の設定をしています
- --config-envでdev環境用の設定を読むように指定しています
- --no-confirm-changesetでyesのyを入力しないようにしています


これにより、4566ポートで起動しているlocalstackコンテナに対して、deployが実行されます。deployまでの作業は以上です。

めっちゃくちゃ簡単ですね！
以下の点で良さを感じています。

- 既存のtemplate.yamlを使って環境を作ることができた
- 作られたリソースに関しては、本番環境と差分がない
- LocalStack用に修正したコードはcompose.yamlだけ


何より構築が楽なのでgoodです。


# 作られたリソースの確認

localstackコンテナに対して、用意されたコマンドを叩くとリソースの確認ができます。

さらに、localstackには専用のダッシュボードがあるため、ブラウザやデスクトップアプリからでも作られたリソースの確認ができます。

https://docs.localstack.cloud/user-guide/web-application/resource-browser/

https://docs.localstack.cloud/user-guide/tools/localstack-desktop/

後述するのですが、私の場合API Gatewayのエンドポイントをソースコード側に設定するために、API Gateway idを確認する必要があり、その時にダッシュボードを見に行っています。
他にもLambda関数のファイルサイズの確認や、API Gatewayの各種設定が意図した通りに設定されているかなどの確認用として、ダッシュボードを利用したりします。

# 作られたAWSリソースにアクセスする

例えば、localstackコンテナ内に作成されたAPI Gatewayのエンドポイントは以下の形で生成されます。

```
http://<apiId>.execute-api.localhost.localstack.cloud:4566/<stageName>/<path>
```

https://docs.localstack.cloud/user-guide/aws/apigateway/#accessing-http-apis-via-local-domain-name

API Gatewayのidが `abcdefg`、 stageNameが `api`、 pathが `upload` だとすると、以下のURLとなります。

```
http://abcdefg.execute-api.localhost.localstack.cloud:4566/api/upload
```

S3内のオブジェクトへのアクセスパスなどもドキュメントに記載があります。

https://docs.localstack.cloud/user-guide/aws/s3/#path-style-and-virtual-hosted-style-requests

S3内のオブジェクトに対してpre-signed URLの発行も可能なので、とても良いです。


# さいごに
無料版でできる範囲は限られると思いますが、ちょっとしたバックエンド環境が必要なサービスだったら役に立つな〜と感じているので、検討の余地があると思います。
コスト削減もそうですが、開発の速度がこれまで以上に上がると良いな〜。