LocalStackでAWSの開発環境を再現しよう

# はじめに
本記事では、LocalStackとは何か、そして開発環境でどのように活用できるかについて解説します。

※ LocalStackをセットアップする前提となるDockerのインストールや環境構築については触れませんのでご了承ください🙇🏻‍♀️

## 環境
私の環境は以下の通りです。
- 使用OS: MacOS（バージョン: 15.3.1 ）
- 使用IDE: VSCode (バージョン: 1.97.2)
- LocalStack バージョン: 4.1.1
- Dockerバージョン: 24.0.2

# LocalStackについて
LocalStackはAWSの主要なサービスをローカル環境で再現できるツールです。実際のAWSインフラを利用せずに開発やテストを行うことができるため、コスト削減と効率的な開発を実現できます。

## サポートしているAWSサービス
LocalStackは、多くのAWSサービスをサポートしています。あなたが「主要AWSリソースは？」と聞かれた時にパッと思い浮かぶものは大体サポートされていると思ってもらっていいくらいです。
2025年1月時点では QuickSight などのBIツールや一部の機械学習サービスは未サポートまたは限定的なサポートにとどまっています。

最新の対応状況は、[公式ドキュメント](https://docs.localstack.cloud/user-guide/aws/feature-coverage/)をご確認ください。

## ユースケースについて
LocalStackはエンジニアにとって、AWS環境をローカルでシミュレーションできる便利なツールとして活用されています。特に開発コードのテストや CI/CDパイプラインでの自動テストに利用されることが一般的です。

### 個人的に感心したユーザの成功話
CartonCloudというクラウドベースの輸送管理システム（TMS）と倉庫管理システム（WMS）を提供している会社の実話です。

こちらの会社では、各開発者が個別のAWS環境を立ち上げる必要があり、コストは1人あたり月500ドルに達することもあったようです。
LocalStackを使用してからは、新しいチームメンバーは、初日から完全なローカル環境をセットアップできるようになり、オンボーディング時間がなんと10倍も短縮。開発ワークフローが効率化され、コスト効率は15倍向上しました。

これによって、個別のAWS開発環境を必要としなくなり、安定した開発環境が確保されるようになったようです。

AWSのサンドボックスアカウントの料金が常に課題の部署は多いと思います。私の会社もそうです。（LocalStackを社内勉強会などで強く推しています。）

この実話はLocalStackの[blog](https://blog.localstack.cloud/2024-03-28-localstack-cartoncloud/)で紹介されています。

### インテグレーション
AWSにて開発構築する上で、使用する様々なツールもサポートしています。
いくつかピックアップしています。

（本稿では詳しい説明は割愛させていただきます。次回以降にハンズオンしたアウトプットとして寄稿しようと考えています。）

- IaC
    - Terraform
- CI
    - CircleCI
    - GitHub Actions
    - Travis
    - Jenkins

# 使用方法

ここからは、実際に自分がハンズオンした時のコードを元に、どのように使用するかをお話しします。
今回は、LocalStackに触れることが目的のため、ハンズオンは比較的簡単そうなAWSの公式ハンズオンを参考にしました。

[こちら(SNS / SQS / Lambda の連携)](https://dcj71ciaiav4i.cloudfront.net/49D0D220-8D0F-11EB-8F39-FD9A62BABEEC/)の内容になります。


## セットアップ方法

LocalStackでのセットアップは極めて簡単です。
LocalStackはDockerを使用してローカル環境で起動させます。Dockerに慣れている方は、Dockerでセットアップするのが一番簡単だと思います。

Dockerに抵抗がある方は、LocalStack CLIの使用を検討してみてください。LocalStack CLIは、LocalStackが独自に提供していて、このCLIがDocker周りを全て対応してくれます。

自分はDocker composeを使用するのが一番やりやすかったため、ここからはDocker composeを使用しての使用方法を紹介します。

### Docker compose

使用したDocker composeは以下の通りです。

```yml
services:
  localstack:
    build: ./localstack
    container_name: localstack
    ports:
      - "4566:4566" # LocalStack end point
      - "4510-4559:4510-4559" # Lambda ports
    environment:
      SERVICES: sqs,sns,lambda,iam,logs,s3
      LAMBDA_EXECUTOR: docker
      DEBUG: 1
      DATA_DIR: /tmp/localstack/data
      AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION}
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
      LOCALSTACK_URL: ${LOCALSTACK_URL}
    healthcheck:
      test: nc -z 127.0.0.1 4566
      interval: "5s"
      timeout: "5s"
      start_period: "10s"
      retries: 3
    volumes:
      - localstack_data:/var/lib/localstack
      - "/var/run/docker.sock:/var/run/docker.sock" # Required for Lambda Docker execution
  
  setup-localstack:
    build: ./setup-localstack
    container_name: setup-localstack
    environment:
      LOCALSTACK_URL: ${LOCALSTACK_URL}
      AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION}
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
    depends_on:
      localstack:
        condition: service_healthy
    volumes:
      - localstack_data:/var/lib/localstack

volumes:
  localstack_data:
```

#### 重要ポイント
このdocker-compose.ymlについて、重要ポイントだけ説明します。

**コンテナ**
まず、コンテナを2つ用意しました。
`localstack`コンテナは必須です。LocalStackを常時稼働させるためのコンテナです。
`setup-localstack`コンテナは、一部のAWSリソースの構築を自動で先にやっておきたかったため、設定しました。

**LocalStackコンテナ**
LocalStackを使用する上で必須のパラメータがあります。

```yml
- ports:
    - "4566:4566" 
    - "4510-4559:4510-4559" 
- environment:
    - SERVICES: sqs,sns,lambda,iam,logs
    - AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
    - AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
```
portsにて、`4566:4566`は必ず設定してください。
`4510-4559:4510-4559`はLambdaを使用するときのみに指定します。

`environment`にて、`SERVICES`に使用したいAWSリソース名を入力します。ここでのリソース名はLocalStackで指定された書き方をしてください。詳しくは公式ドキュメントを参照してください。

ここでは、前述したAWS公式ハンズオンをやるので、
SQS、SNS、Lambda それからLambdaにIAMロールをつけるためのIAM、実行ログを確認するためのLogsを記載しています。
また、`AWS_ACCESS_KEY_ID`と`AWS_SECRET_ACCESS_KEY`には、LocalStack用にダミーの値を投入します。

自分は普段からこのように環境変数にKeyの値を入れているので、名残で上記のように書いていますが、ダミーなのでハードコードでも全く問題ないです。
`${AWS_ACCESS_KEY_ID}`や`${AWS_SECRET_ACCESS_KEY}`も`test`のような値で問題ありません。

### setup-localstack コンテナ

`setup-localstack`コンテナには以下を用意します。

- Dockerfile
- setup.sh
- cloudwatch_policy.json
- trust_policy.json

ディレクトリ構成は以下のようにしました。
```
├── Dockerfile
└── src
    ├── cloudwatch_policy.json
    ├── setup.sh
    └── trust_policy.json
```

各ファイルについて説明します。

#### Dockerfile
まず、Dockerfileです。

```dockerfile
FROM amazonlinux:2023

# Install AWS CLI and other necessary packages
RUN dnf update -y \
    && dnf install -y --allowerasing \
    awscli python3 python3-pip 
RUN pip3 install --no-cache-dir boto3 awscli-local awscli \ 
    && rm -rf /var/cache/dnf/* \ 
    && dnf clean all

# Copy initialization scripts and Lambda files into the container
COPY ./src/ /usr/local/bin/

RUN chmod +x /usr/local/bin/setup.sh
# Make sure scripts are executable
ENTRYPOINT /usr/local/bin/setup.sh
```

上記、書いてある通りですが、コンテナ内で諸々事前セットアップをさせるためにAWS CLIなど必要なパッケージをインストールさせます。また、必要なスクリプトファイル等もコンテナにコピーしています。

#### setup.sh

`setup.sh`を使用してLocalStack上に以下を事前設定します。
※ エラーハンドリングには対応していないです！

- SQS Standard Queue
- SNS Standard Topic
- IAM Roles 
- SNSにSQSをサブスクライブさせる

```sh
#!/bin/bash
echo -e "Setting up LocalStack resources...\nCreating the /var/lib/localstack directory..."

mkdir -p /var/lib/localstack

echo "Creating resources in LocalStack..."

# Create SQS Standard queue
aws sqs create-queue \
  --queue-name localstack-handson \
  --endpoint-url "$LOCALSTACK_URL" \
  --region "$AWS_DEFAULT_REGION"

QUEUE_URL=$(aws sqs get-queue-url \
  --queue-name localstack-handson \
  --endpoint-url "$LOCALSTACK_URL" \
  --region "$AWS_DEFAULT_REGION" \
  --query QueueUrl \
  --output text)

echo "QUEUE_URL: $QUEUE_URL"

QUEUE_ARN=$(aws sqs get-queue-attributes \
  --queue-url "$QUEUE_URL" \
  --attribute-names QueueArn \
  --endpoint-url "$LOCALSTACK_URL" \
  --region "$AWS_DEFAULT_REGION" \
  --query Attributes.QueueArn \
  --output text)
echo "QUEUE_ARN: $QUEUE_ARN"

# Create SNS topic
SNS_TOPIC_ARN=$(aws sns create-topic \
  --name localstack-handson \
  --endpoint-url "$LOCALSTACK_URL" \
  --region "$AWS_DEFAULT_REGION" \
  --query TopicArn \
  --output text)

# Subscribe SQS to SNS
aws sns subscribe \
  --topic-arn "$SNS_TOPIC_ARN" \
  --protocol sqs \
  --notification-endpoint "$QUEUE_ARN" \
  --endpoint-url "$LOCALSTACK_URL" \
  --region "$AWS_DEFAULT_REGION"

# Create IAM role for Lambda
aws iam create-role \
  --role-name AWSLambdaSQSQueueExecutionRole \
  --assume-role-policy-document file:///usr/local/bin/trust_policy.json \
  --endpoint-url "$LOCALSTACK_URL"

aws iam attach-role-policy \
  --role-name AWSLambdaSQSQueueExecutionRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole \
  --endpoint-url "$LOCALSTACK_URL"

echo "Resources setup complete!"
```

#### Policy 系

Shell script内でIAM Roleの設定をする際に使用しているポリシーは以下のJSONファイルから参照するようにしました。

- cloudwatch_policy.json
- trust_policy.json

**cloudwatch_policy.json**

```json
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ],
        "Resource": "*"
      }
    ]
  }

```


**trust_policy.json**

```json
{
    "Version": "2012-10-17",
    "Statement": {
      "Effect": "Allow",
      "Principal": { "Service": "lambda.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  }
  
```

これで、事前準備用（`setup-localstack`）の設定は完了しました。

### localstackコンテナ

それでは、次にLocalStackコンテナの中身を紹介していきます。

#### ディレクトリ構成
ディレクトリ構成は以下のようにしています。

```
├── Dockerfile
└── src
    ├── function.zip
    ├── lambda_function.py
    └── setup_lambda.sh
```

#### Dockerfile

Dockerfileでは、[LocalStackの公式Dockerイメージ](https://hub.docker.com/r/localstack/localstack)を使用します。


```dockerfile
FROM localstack/localstack:latest

RUN apt-get update -y \
&& apt-get upgrade -y \
&& apt-get install -y \
netcat-traditional \
&& apt-get clean -y

COPY ./src /usr/local/bin/
RUN rm -rf /var/lib/apt/lists/*
```

#### srcディレクトリ

srcディレクトリにはLambda関数LambdaのセットアップをするShell scriptを格納しています。

まず、setup_lambda.shからご紹介します。
※ エラーハンドリングはしていないです！

このShell script内では、以下の対応をしています。

- Lambda関数の作成
- Event Source Mappingの作成

```sh
#!/bin/bash

echo "Create Lambda function..."

# Create Lambda function
aws lambda create-function \
  --function-name process-sqs-messages \
  --runtime python3.12 \
  --role arn:aws:iam::000000000000:role/AWSLambdaSQSQueueExecutionRole \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --endpoint-url http://localhost:4566 \
  --profile localstack

echo "Fetching the SQS queue ARN..."

QUEUE_ARN=$(aws sqs get-queue-attributes \
  --queue-url http://sqs.ap-northeast-1.localhost.localstack.cloud:4566/000000000000/localstack-handson \
  --attribute-names QueueArn \
  --endpoint-url http://localhost:4566 \
  --query Attributes.QueueArn \
  --output text \
  --profile localstack )
echo "QUEUE_ARN: $QUEUE_ARN"

echo "Create event source mapping..."

# Add SQS as a Lambda trigger
aws lambda create-event-source-mapping \
  --function-name process-sqs-messages \
  --event-source-arn "$QUEUE_ARN" \
  --endpoint-url http://localhost:4566 \
  --profile localstack
```

私は、AWS CLIに複数のCredential設定をしているため、オプション`--profile`には、LocalStack特定の設定をしたプロファイル`localstack`を指定しています。

事前準備でエラーになる場合はここで躓いている可能性があります。
profileの指定をしない場合、defaultプロファイルが使用されるため、個人アカウントにAWS CLIの矛先が向いている可能性があります。


Lambda関数は、以下を使用しています。
出力させるメッセージはAWS公式ハンズオンと同じですが、`try`,`except`を追加しました。

```py
import json

def lambda_handler(event, context):
    try:
        print("Received event: " + json.dumps(event, indent=2))

        for record in event['Records']:
            print("Message Body: " + record['body'])
        return {
            'statusCode': 200,
            'body': json.dumps('Hello from Lambda!')
        }
    except Exception as e:
        print("Error processing event: " + str(e))
        raise e
```

ここまでで、各種スクリプトの準備は完了です。

それでは実際にLocalStackを使って、AWSリソースができているか動作確認をしていきます。

## 動作確認

1.Lambda関数はZipしておきます。

```sh
zip localstack/src/function.zip localstack/src/lambda_function.py
```

2.先ほど作成したdocker-compose.ymlを使用し、コンテナを起動させます。


```sh
docker compose build

docker compose up
```
以下は`docker compose up`が完了した後のTerminalをスクショしたものです。

![Screenshot 2025-03-10 at 22.44.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2882476/ba2e4355-4a72-4fa4-986c-1afd97098854.png)


3.SNSが無事に設定できたというログが出ている（以下スクショ）ので、試してみます。

![Screenshot 2025-03-10 at 22.48.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2882476/883c284e-e0be-4c45-a226-a0bcd8a90037.png)


```sh
aws sns publish \
  --topic-arn arn:aws:sns:ap-northeast-1:000000000000:localstack-handson \
  --message "Hello from SNS #1" \
  --profile localstack \
  --endpoint-url http://localhost:4566
```

![Screenshot 2025-03-10 at 22.50.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2882476/bc51136c-b321-414b-9652-1c97976b9a4a.png)


4.次にLambda関数を設定をします。
`setup_lambda.sh`に設定方法は記載しているので、これを実行させるだけです。
別タブでTerminalを開き、以下のコマンドを叩きます。

```sh
cd localstack/src/

./setup_lambda.sh

```

Lambda関数が無事に作成されたので、先ほどと同様にSNSにメッセージを送信してみます。

```sh
aws sns publish \
  --topic-arn arn:aws:sns:ap-northeast-1:000000000000:localstack-handson \
  --message "Hello from SNS #1" \
  --profile localstack \
  --endpoint-url http://localhost:4566
```

![Screenshot 2025-03-10 at 23.13.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2882476/2ecb28b5-b58f-4b40-ad97-9f38b77a00ca.png)

かなり見にくいですが、上図の最後の行のようにSQSのメッセージが削除されていることが確認できれば処理は成功しています。

動作確認が終わったら、dockerはストップしてください。

```sh
docker compose stop

docker compose down 

```

Terminal上での動作確認ができました。いかがでしたでしょうか？LocalStackはかなり可能性があると思っていただけましたでしょうか？

## おまけ

本記事は、会社の社内勉強会でも話したLocalStackのハンズオンの内容ですが、勉強会後に以下のようなご意見をいただきました。
- 「AWS Management Consoleに慣れてしまっていてTerminalは抵抗がある」
- 「LocalStackはAWS CLIをバンバン使う人向けだ」

などなどのご意見がありました。

同じように思われた方がいましたら、少し肩の力を抜いていただけるかもと思いLocalStackのダッシュボードも紹介します。

LocalStackでは起動させたAWS模擬リソースを確認できるダッシュボードを提供しています。
以下スクショは`setup_lambda.sh`を実行させる前のダッシュボードです。

![Screenshot 2025-03-10 at 22.56.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2882476/b3f8f462-58f7-418b-b57a-4efca4559f9e.png)

Lambda関数の中を覗いてみましょう。
設定した通りの情報が載っています。

![Screenshot 2025-03-10 at 23.26.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2882476/3b496d23-b712-4255-99ce-772b2f8572b9.png)

CloudWatchLogsでLambda関数のログ確認もできちゃいます！
![Screenshot 2025-03-10 at 23.26.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2882476/727b3393-482e-47c9-b467-f32fcb597c2d.png)

ダッシュボードからLambda関数を作成したり、IAMロールを設定したりすることも可能です。

少しでもLocalStackの面白さ、便利さが伝われば幸いです。自分も積極的に使用していきたいです。