LocalStackを使用してAWSサービスをローカル環境にエミュレートする

# はじめに
本記事はLocalStackを使用してAWSサービスをローカル環境にエミュレートする方法について記載しています。

LocalStackは、AWSサービスをローカル環境にエミュレートすることができるサービスです。
[Announcing LocalStack 2.0 General Availability!](https://localstack.cloud/blog/2023-03-29-announcing-localstack-2.0-general-availability/)よりLocalStack 2.0がリリースされています。

https://localstack.cloud/

AWSの主要なサービスとしてAWS Lambda、S3、Dynamodb、Kinesis、SQS、SNSなどのAPIを擬似的に利用することができます。

従ってクラウド環境を使用することなく、ローカル環境で開発及びテストなどを行うことができます。

## LocalStackとは
ホームページの[About Us](https://localstack.cloud/company/)を踏まえて、2016年にGitHubのオープンソースとして始まりました。その後、2021年に法人化されています。

LocalStackの特徴は、AWSクラウドのAPIをエミュレートすることで、AWS環境でデプロイするアプリケーションのローカル開発及びテストを可能とします。

無料で利用可能なオープンソース版と、サービスが拡充された有料のPro版があります。
それぞの違いは[Features](https://localstack.cloud/features/)から確認できます。

![architecture.png](https://localstack.cloud/features/architecture.png)
（出典元：LocalStack）

## 環境構築
LocalStackを使用するためには、公式ドキュメント[Installation](https://docs.localstack.cloud/getting-started/installation/)のとおりにいくつかの手段が用意されています。

本記事では**LocalStack CLI**を用いた環境構築の方法について記載しています。

前提条件は以下の通りです。
（本記事はPython 3.8.2を使用）

- python (Python 3.7 up to 3.11 supported)
- pip (Python package manager)
- Docker

:::note
デスクトップクライアントとして、[Cockpit](https://docs.localstack.cloud/user-guide/tools/cockpit/)を利用したインスタンスの管理もできます。
:::

### localstack
`pip`から**localstack**をインストールします。

`$ pip3 install localstack`

LocalStackの詳細は、GitHubの[localstack](https://github.com/localstack/localstack)リポジトリから確認できます。

### awscli-local
localstackをインストールしただけでは、後述する`awslocal`コマンドが使用できません。

`awslocal`コマンドを使用するためには、**awscli-local**もインストールします。

`$ pip3 install awscli-local`

以下のコマンドを実行して、`awslocal`コマンドがインストールされたことが確認できます。

`$ which awslocal`

```console
/usr/local/bin/awslocal
```

awscli-localの詳細は、GitHubの[awscli-local](https://github.com/localstack/awscli-local)リポジトリから確認できます。

## 動作確認
LocalStackを起動するためには、以下のコマンドを実行します。

`$ localstack start -d`

```console

     __                     _______ __             __
    / /   ____  _________ _/ / ___// /_____ ______/ /__
   / /   / __ \/ ___/ __ `/ /\__ \/ __/ __ `/ ___/ //_/
  / /___/ /_/ / /__/ /_/ / /___/ / /_/ /_/ / /__/ ,<
 /_____/\____/\___/\__,_/_//____/\__/\__,_/\___/_/|_|

 💻 LocalStack CLI 2.0.2

[17:00:38] starting LocalStack in Docker mode 🐳                                                                                                     localstack.py:142
           preparing environment                                                                                                                      bootstrap.py:615
[17:00:39] configuring container                                                                                                                      bootstrap.py:623
           starting container                                                                                                                         bootstrap.py:630
[17:00:41] detaching  
```

以下のコマンドを実行して、サービスの状態を確認します。

`$ localstack status services`

```console
┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Service                  ┃ Status      ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ acm                      │ ✔ available │
│ apigateway               │ ✔ available │
│ cloudformation           │ ✔ available │
│ cloudwatch               │ ✔ available │
│ config                   │ ✔ available │
│ dynamodb                 │ ✔ available │
│ dynamodbstreams          │ ✔ available │
│ ec2                      │ ✔ available │
│ es                       │ ✔ available │
│ events                   │ ✔ available │
│ firehose                 │ ✔ available │
│ iam                      │ ✔ available │
│ kinesis                  │ ✔ available │
│ kms                      │ ✔ available │
│ lambda                   │ ✔ available │
│ logs                     │ ✔ available │
│ opensearch               │ ✔ available │
│ redshift                 │ ✔ available │
│ resource-groups          │ ✔ available │
│ resourcegroupstaggingapi │ ✔ available │
│ route53                  │ ✔ available │
│ route53resolver          │ ✔ available │
│ s3                       │ ✔ available │
│ s3control                │ ✔ available │
│ secretsmanager           │ ✔ available │
│ ses                      │ ✔ available │
│ sns                      │ ✔ available │
│ sqs                      │ ✔ available │
│ ssm                      │ ✔ available │
│ stepfunctions            │ ✔ available │
│ sts                      │ ✔ available │
│ support                  │ ✔ available │
│ swf                      │ ✔ available │
│ transcribe               │ ✔ available │
└──────────────────────────┴─────────────┘
```

LocalStackはDockerで動いているため、LocalStack起動後コンテナの起動が確認できます。

```console
CONTAINER ID   IMAGE                       COMMAND                  CREATED              STATUS                        PORTS                                                                    NAMES
3ee9cee6a3b8   localstack/localstack       "docker-entrypoint.sh"   About a minute ago   Up About a minute (healthy)   127.0.0.1:4510-4559->4510-4559/tcp, 127.0.0.1:4566->4566/tcp, 5678/tcp   localstack_main
```

`awslocal`コマンドが使用可能な場合、READMEにサンプルとして記載があるSQSも確認できます。

`$ awslocal sqs create-queue --queue-name sample-queue`

```console
{
    "QueueUrl": "http://localhost:4566/000000000000/sample-queue"
}
```

LocalStackのログは、以下のコマンドを実行することで確認できます。

`$ localstack logs`

```console

LocalStack version: 2.0.3.dev
LocalStack Docker container id: 3ee9cee6a3b8
LocalStack build date: 2023-04-19
LocalStack build git hash: 11c094bd

2023-04-20T08:00:42.810  INFO --- [-functhread3] hypercorn.error            : Running on https://0.0.0.0:4566 (CTRL + C to quit)
2023-04-20T08:00:42.810  INFO --- [-functhread3] hypercorn.error            : Running on https://0.0.0.0:4566 (CTRL + C to quit)
Ready.
2023-04-20T08:03:20.596  WARN --- [   asgi_gw_0] localstack.deprecations    : /health is deprecated (since 1.3.0) and will be removed in upcoming releases of LocalStack! Use /_localstack/health instead.
2023-04-20T08:07:19.917  INFO --- [   asgi_gw_0] localstack.request.aws     : AWS sqs.CreateQueue => 200
```

## Local AWS Services
ローカル環境でAWS Servicesを構築します。

本記事では例として、S3とEC2を記載しています。

### Amazon S3
S3のバケットを作成するためには、以下のコマンドを実行します。
`local-test`はバケット名になるため、任意の名前を指定します。

`$ awslocal s3 mb s3://local-test`

```console
make_bucket: local-test
```

以下のコマンドを実行すると、バケットが作成されたことが確認できます。

`$ awslocal s3 ls`

```
2023-04-20 17:08:54 local-test
```

テストファイルを作成して、LocalStackのS3にアップロードを行います。

`$ touch test.txt && echo test > test.txt`
`$ awslocal s3 cp ./test.txt s3://local-test` 

```console
upload: ./test.txt to s3://local-test/test.txt
```

ダウンロードして確認します。

`$ awslocal s3 cp s3://local-test/test.txt ./`

```console
download: s3://local-test/test.txt to ./test.txt
```

ブラウザからも確認できます。

`http://localhost:4566/local-test/`にアクセスすると、バケットが参照できます。

![スクリーンショット 2023-04-20 19.32.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/006e7b97-d896-8b9c-3d31-116312c7bbcc.png)

`http://localhost:4566/local-test/test.txt`にアクセスすると、オブジェクトの中身が確認できます。


![スクリーンショット 2023-04-20 19.32.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/ae9efe0b-8948-bdc6-6ff9-61f22c063025.png)

`localstack logs`コマンドを実行して、APIに関するログが確認できます。

```console
2023-04-20T08:08:54.210  INFO --- [   asgi_gw_0] localstack.request.aws     : AWS s3.CreateBucket => 200
2023-04-20T08:09:02.611  INFO --- [   asgi_gw_0] localstack.request.aws     : AWS s3.ListBuckets => 200
2023-04-20T08:09:43.375  INFO --- [   asgi_gw_0] localstack.request.aws     : AWS s3.PutObject => 200
2023-04-20T08:12:36.670  INFO --- [   asgi_gw_0] localstack.request.aws     : AWS s3.HeadObject => 200
2023-04-20T08:12:36.682  INFO --- [   asgi_gw_0] localstack.request.aws     : AWS s3.GetObject => 200
```

### Amazon EC2
EC2インスタンスを起動します。
イメージはDocker Hubの[amazonlinux](https://hub.docker.com/_/amazonlinux)からAmazon Linux 2023を使用します。

以下のコマンドを実行して、イメージを取得します。

`$ docker pull public.ecr.aws/amazonlinux/amazonlinux:2023`

```console
2023: Pulling from amazonlinux/amazonlinux
48dcbee877aa: Pull complete 
Digest: sha256:1241d87a424c299c6cccc4528504c80bf02923529628edd94ea6fb0ab01030e1
Status: Downloaded newer image for public.ecr.aws/amazonlinux/amazonlinux:2023
public.ecr.aws/amazonlinux/amazonlinux:2023
```

以下のコマンドを実行して、イメージにタグを付けます。

`$ docker tag public.ecr.aws/amazonlinux/amazonlinux:2023 localstack-ec2:ami-local`


以下のコマンドを実行して、EC2を起動します。

`$ awslocal ec2 run-instances --image-id ami-local`

```console
{
    "Groups": [
        {
            "GroupName": "default",
            "GroupId": "sg-245f6a01"
        }
    ],
    "Instances": [
        {
            "AmiLaunchIndex": 0,
            "ImageId": "ami-local",
            "InstanceId": "i-875f2ea3652a2ec66",
            "InstanceType": "m1.small",
            "KernelId": "None",
            "LaunchTime": "2023-04-20T08:46:38+00:00",
            "Monitoring": {
                "State": "enabled"
            },
            "Placement": {
                "AvailabilityZone": "ap-northeast-1a",
                "GroupName": "",
                "Tenancy": "default"
            },
            "PrivateDnsName": "ip-10-97-137-152.ap-northeast-1.compute.internal",
            "PrivateIpAddress": "10.97.137.152",
            "PublicDnsName": "ec2-54-214-47-151.ap-northeast-1.compute.amazonaws.com",
            "PublicIpAddress": "54.214.47.151",
            "State": {
                "Code": 0,
                "Name": "pending"
            },
            "StateTransitionReason": "",
            "SubnetId": "subnet-0eb4ceca",
            "VpcId": "vpc-c78226a1",
            "Architecture": "x86_64",
            "ClientToken": "",
            "EbsOptimized": false,
            "Hypervisor": "xen",
            "NetworkInterfaces": [
                {
                    "Association": {
                        "IpOwnerId": "000000000000",
                        "PublicIp": "54.214.47.151"
                    },
                    "Attachment": {
                        "AttachTime": "2015-01-01T00:00:00+00:00",
                        "AttachmentId": "eni-attach-b317e893",
                        "DeleteOnTermination": true,
                        "DeviceIndex": 0,
                        "Status": "attached"
                    },
                    "Description": "Primary network interface",
                    "Groups": [
                        {
                            "GroupName": "default",
                            "GroupId": "sg-c7923fb1a5174e467"
                        }
                    ],
                    "MacAddress": "1b:2b:3c:4d:5e:6f",
                    "NetworkInterfaceId": "eni-75b3367f",
                    "OwnerId": "000000000000",
                    "PrivateIpAddress": "10.97.137.152",
                    "PrivateIpAddresses": [
                        {
                            "Association": {
                                "IpOwnerId": "000000000000",
                                "PublicIp": "54.214.47.151"
                            },
                            "Primary": true,
                            "PrivateIpAddress": "10.97.137.152"
                        }
                    ],
                    "SourceDestCheck": true,
                    "Status": "in-use",
                    "SubnetId": "subnet-0eb4ceca",
                    "VpcId": "vpc-c78226a1"
                }
            ],
            "SecurityGroups": [],
            "SourceDestCheck": true,
            "Tags": [],
            "VirtualizationType": "paravirtual"
        }
    ],
    "OwnerId": "000000000000",
    "ReservationId": "r-04a780bc"
}
```

同じようにEC2のログも確認できます。

```
2023-04-20T08:46:38.115  INFO --- [   asgi_gw_0] localstack.request.aws     : AWS ec2.RunInstances => 200
2023-04-20T08:52:21.052  INFO --- [   asgi_gw_0] localstack.request.aws     : AWS ec2.DescribeInstances => 200
```

::: note
Amazon Linux 2023については、[5分で理解するAmazon Linux 2023](https://qiita.com/Brutus/items/a2ac164297677edaa6e2)をご参照ください。
:::

# おわりに
本記事では紹介しきれませんが、公式ドキュメント[Terraform](https://docs.localstack.cloud/user-guide/integrations/terraform/)のとおりにIac構成のテストなどにも利用することもできます。

新年度が始まりこれからクラウドを初めて触る人も出てくると思うので、リスク管理としての利用や、コスト削減にも活用できるので、お勧めなツールです。