LocalStackを使ってローカル環境でS3を立ち上げ、Lambdaでファイルをアップロードしてみた



# この記事について

AWSのサービスをローカルで実行できるLocalStackについて紹介したいと思います。今回、AWS CLIの練習も兼ねてLocalStackを使用し、ローカルでAWS LambdaとS3を立ち上げ、Lambda関数を使用し、S3に日時.txtファイルをアップロードしてみました。

# 記事の対象者

- LocalStackに興味のある方
- AWS・AWS CLIに触れてみたい方





#  LocalStackとは

開発環境において無料で**AWSのアカウント登録なし**でAWSのサービスを擬似的に使用できるモックフレームワークでpipやdockerを用いて簡単に環境構築が可能。
ローカルにAWS環境を作ってくれて、料金などを気にすることなくテストや動作確認が可能である。

https://localstack.cloud/





LocalStackは無料版と有料版がある。[^abe] 有料版は無料版に加え、高度なサービスをサポート。以下にLocalStackのサービスを示す。[^abc]（2021年10月時点）

|  無料  |  有料  |
| ---- | ---- |
|<top> ACM <br> API Gateway <br> CloudFormation <br> CloudWatch <br>  CloudWatchLogs <br>  DynamoDB <br> DynamoDB Streams <br> EC2 <br> Elasticsearch Service <br> EventBridge <br>  Firehose <br> IAM  <br>  Kinesis <br> KMS <br> Lambda <br> Redshift <br> Route53 <br> S3 <br> SecretsManager <br> SES <br> StepFunctions  <br> STS <br><br><br><br><br><br>|  Amplify <br> AppConfig <br> Application AutoScaling  <br>  AppSync <br> Athena <br> Backup <br> Batch <br> CloudFront<br> CloudTrail <br> CodeCommit <br> Cognito Identity <br> ECR <br> ECS <br> EKS <br> ElastiCache <br> EMR <br> Glue <br> IoT (including IoT Analytics, IoT Data)  <br> Managed Streaming for Kafka (MSK) <br> Neptune DB <br> QLDB <br> RDS / Aurora Serverless <br> SageMaker <br> Timestream <br> Transfer <br> XRay <br><br>

[^abe]: https://localstack.cloud/pricing/ より引用
[^abc]: https://localstack.cloud/features/ より引用











## 構築環境

Windows10上でWSL2をインストールし、WSL2上でUbuntuを動かした。


- `Ubuntu 20.04 LTS`
- `docker 20.10.8`
- `localstack 0.11.0`
- `aws cli  1.18.69`


### 構築環境のイメージ

![環境構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2004450/123a8a8d-f68e-643f-e168-df1d5aa3b208.png)


以下を参考にWSL2、Ubuntu、Dockerの環境を構築した。

https://qiita.com/amenoyoya/items/ca9210593395dbfc8531

### 構成図

LocalStack上でS3、Lambdaを立ち上げ、S3に接続し、日時.txtファイルを作成する。

![Localstack構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2004450/1f33a95c-4060-4fff-c8b2-db5a760b1a19.png)


# 事前準備


##  AWS CLIの設定


### AWS CLIとは

AWS Command Line Interface（AWS CLI）は、AWSをコマンド入力で操作できるようにするインターフェースである。
AWS CLIのメリットは、GUIで行っていた操作を自動化できる点である。自動化することで、業務効率化ができヒューマンエラーを減らすことが可能。
LocalStackでも様々な用途でAWS-CLIを利用可能。

https://aws.amazon.com/jp/cli/

### AWS CLIのインストール

```
sudo apt  install awscli
```

### AWS　CLIがインストールできたか確認

```
$ aws --version
aws-cli/1.18.69 Python/3.8.10 Linux/5.10.16.3-microsoft-standard-WSL2 botocore/1.16.19
```


### LocalStack用のプロファイルを作成

ローカルで使うだけなのでダミーの設定

```
$ aws configure --profile localstack
AWS Access Key ID [None]: dummy
AWS Secret Access Key [None]: dummy
Default region name [None]: us-east-1
Default output format [None]: json

# id,keyの確認
$ cat ~/.aws/credentials
[localstack]
aws_access_key_id = dummy
aws_secret_access_key = dummy

# region,outputの確認確認
$ cat ~/.aws/config
[profile localstack]
region = us-east-1
output = json

```

## LocalStackの設定

### githubからLocalStackをクローン

GitHubからLocalStack環境一式をダウンロード。

```
git clone https://github.com/localstack/localstack.git
cd localstack
```

### docker-compose.ymlを編集

クローンしてきた`docker-compose.yml`に`LAMBDA_DOCKER_NETWORK=host`と`LAMBDA_EXECUTOR=docker-reuse`を追記

```docker-compose.yml
version: "3"
services:
  localstack:
    image: localstack/localstack:latest
    ports:
      - "4566:4566"
    environment:
      - SERVICES=${SERVICES- }
      - DEBUG=${DEBUG- }
      - DATA_DIR=${DATA_DIR- }
      - PORT_WEB_UI=${PORT_WEB_UI- }
      - KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- }
      - DOCKER_HOST=unix:///var/run/docker.sock
      - LAMBDA_DOCKER_NETWORK=host
      - LAMBDA_EXECUTOR=docker-reuse
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
```


- `ports`
    - サービスごとにポートが指定されていたが、LocalStackのバージョン`v0.11.00`以降は`4566`番ポートのみを介して提供（有料版のWebUIのみ`8080`番が利用可能）
    - ポート番号は`EDGE_PORT`で指定することで変更可能
    - サービスごとには`<SERVICE名>_PORT_EXTERNAL`でポートを指定することも可能
-  `SERVICES`
    - LocalStackで起動するサービスはSERVICESで指定
- `DATA＿DIR`
    - 永続化データを保存するディレクトリを指定
    - 本記事の執筆時点では、下記のサービスが永続化対象
        - Kinesis
        - DynamoDB
        - Elasticssearch
        - S3
        - Secretmanager
        - SSM
        - SQS
        - SNS
- `KINESIS_ERROR_PROBABILITY`:
    - `ProvisionedThroughputExceededException`エラーをKinesis APIの応答にランダムに挿入する
- `LAMBDA_EXECUTOR`
    - デフォルトは`docker`で、Dockerが利用できない場合は`local`
    - `local`：ローカルマシン上でLambda関数を実行する
    - `docker`：各Lambda関数呼び出しを別のdockerコンテナで呼び出し実行する
    - `docker-reuse`：Lambda関数ごとに１つのdockerコンテナを作成し、再利用し実行する。

    LocalStack上では、Lambda関数ごとにdockerコンテナが作成され、同じLambda関数を実行するたびにコンテナが作成されるのを防ぐために、`docker-reuse`を指定

- `LAMBDA_DOCKER_NETWORK`
    - Lambda関数を実行するコンテナのネットワークを指定
    - LocalStack上のコンテナで実行するようにするために`LAMBDA_DOCKER_NETWORK=host`とした。
    - これの設定をしないとLambda関数を実行した際に、後述のエラーが出た。



# LocalStackの起動

```
$ docker-compose up -d
Creating network "localstack_default" with the default driver
Creating localstack_localstack_1 ... done
```


`http://localhost:4566/health`にアクセスするとサービスが立ち上がったかどうか確認可能（runningであれば起動済み）
![health.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2004450/9cc47392-8f4b-4617-5c20-fcbaa29b976b.png)






# S3の設定

## S3とは

S3とはAWSのサービスのひとつで、「Amazon Simple Storage Service」の略称。
オブジェクトのファイル単位での出し入れが可能なので、その場に応じて自由な使い道が想定され、より柔軟なデータ保存が実行できるのが特徴となっている。
以下のようなS3のメリットが挙げられる。

- 柔軟に対応可能なストレージ機能
- 耐久性と可用性
    - AWSのS3ではすべてのオブジェクトに対して99.999999999% (9 x 11)の耐久性を実現
- 低コストによる運用が可能
    


## S3バケットの作成

以下のコマンドでLocalstack内にバケットを作成
オプションで`--endpoint-url=http://localhost:4566`とすることでLocalStack上で作成できる。

```
$ aws --endpoint-url=http://localhost:4566 --profile localstack s3api create-bucket --bucket test-bucket
{
    "Location": "/test-bucket"
}
```

## S3バケットの確認

以下のコマンドでバケット一覧を確認できる。先程作成した`test-bucket`が表示されればOK

```
$ aws s3 ls --endpoint-url=http://localhost:4566 --profile localstack
2021-09-17 11:02:26 test-bucket
```


# Lambda関数の設定

## Lambdaとは

Lambdaはサーバレスコンピューティングサービスである。
サーバレスという言葉となっているが、稼働するサーバが無いというわけではなく、
AWS側で実行するサーバが管理されていることから、ユーザが意識して管理するサーバが無いという意味である。
OSなどのインフラストラクチャの管理が不要で、利用者はプログラムコードを準備し、Lambdaにアップロードするだけで実行可能。
現時点でLambdaは次の言語をサポートしている。

- Node.js
- Python
- Ruby
- GO
- java
- PowerShell
- C#

## Lambda関数作成

```lambda.py
import boto3
from boto3.session import Session
from datetime import datetime


session = Session(aws_access_key_id='dummy',
    aws_secret_access_key='dummy',
    region_name='us-east-1'
)

s3 = session.resource(
    service_name='s3', 
    endpoint_url='http://localhost:4566'
)

def lambda_handler(event, context):

    bucket = 'test-bucket'    # バケット名を指定
    key = datetime.now().strftime('%Y-%m-%d-%H-%M-%S') + '.txt'
    file_contents = 'Lambda Save File'

    s3.Bucket(bucket).put_object(Key=key, Body=file_contents)
    
    return 'create file'+ key

```

## lambda.pyをzipファイルに圧縮

```
$ zip lambda.zip lambda.py
  adding: lambda.py (deflated 42%)
```

## LocalStack上でLambda関数作成

```
aws --endpoint-url=http://localhost:4566 lambda create-function --function-name test  --runtime python3.8 --handler lambda.lambda_handler --role r1  --zip-file fileb://lambda.zip --region us-east-1 --profile localstack
{
    "FunctionName": "test",
    "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:f1",
    "Runtime": "python3.8",
    "Role": "r1",
    "Handler": "lambda.lambda_handler",
    "CodeSize": 285,
    "Description": "",
    "Timeout": 3,
    "LastModified": "2021-09-17T05:26:23.710+0000",
    "CodeSha256": "XfGpeloNrfmdJSseF9lbxrIgTI68G1hSmEpotNGZi0g=",
    "Version": "$LATEST",
    "VpcConfig": {},
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "90c9bd95-e63e-4bec-adfa-c8aed74c3499",
    "State": "Active",
    "LastUpdateStatus": "Successful"
}
```

今回は以下のように設定

- `function-name`：`test` 関数名
- `runtime`：`Python3.8`  使用するプログラミング言語
- `handler`：`lambda.lambda_handler` 関数のハンドラー名<モジュール名>.<エントリー関数>
- `zip-file`：`lambda.zip` zip圧縮したプログラムファイルのパス
- `role`：`r1`  任意に設定可能で、ロールの作成は必須ではない
- `profile`：`localstack`　事前準備で作成したプロファイル名

## Lambda関数の実行


```
$ aws lambda --endpoint-url=http://localhost:4566 invoke --function-name test  --profile localstack  result.log
{
    "StatusCode": 200,
    "LogResult": "",
    "ExecutedVersion": "$LATEST"
}

```
Lambda関数の実行自体が成功した場合、ステータスコードが200となる。
関数がエラーを返した場合もステータスコードは200を返すため、`result.log`の確認が必要



Lambda関数を実行するたびにコンテナが起動し作成されていることがわかる。

Ubuntu上での確認

```
$ docker ps
CONTAINER ID   IMAGE                          COMMAND                  CREATED         STATUS         PORTS                                                           NAMES
1eb4639961f4   lambci/lambda:python3.8        "/bin/bash"              7 seconds ago   Up 6 seconds                                                                   localstack_lambda_arn_aws_lambda_us-east-1_000000000000_function_test
ecb128220fd6   localstack/localstack:latest   "docker-entrypoint.sh"   3 days ago      Up 2 hours     4571/tcp, 0.0.0.0:4566->4566/tcp, :::4566->4566/tcp, 5678/tcp   localstack_localstack_1
```

## result.logファイルの確認


成功した場合の出力例

```result.log
$ cat result.log
"create file 2021-09-21-05-14-50.txt"
```

エラーが出た場合の出力例

```result.log
{"errorMessage":"Lambda process returned with error. Result: {\"errorType\":\"EndpointConnectionError\",\"errorMessage\":\"Could not connect to the endpoint URL: ...(省略）
```

解決策
`docker-compose.yml`に `LAMBDA_DOCKER_NETWORK=host`を追記

### S3にアップロードできているか確認


```
$ aws s3 ls --endpoint-url=http://localhost:4566  test-bucket --profile localstack
2021-09-21 14:02:11         16 2021-09-21-14-02-11.txt
```

S3に`2021-09-21-14-02-11.txt`がアップロードされていることがわかる。

# おわりに


今回は、一部となりますが、LocalStackの簡単な使い方を紹介しました。
実際に触ってみて、AWSサービスをAWSのアカウント登録なしで、触ることができるので、テストや動作確認の際に便利だと感じました。
これからAWSを触りたい方やAWS CLIの勉強をしたい方の練習環境としても手軽に利用することができます。
個人的には、AWSの資格試験があるので、勉強も兼ねて色々触ってみようと思いました！
LocalStackには、今回紹介したS３やLambda以外にもAWSのサービスが利用できるので
ぜひLocalStackで構築してみてください！



# 参考

https://bsblog.casareal.co.jp/archives/5571
https://aimstogeek.hatenablog.com/entry/2020/08/03/080000
https://arailly.hatenablog.com/entry/2020/04/29/153822
https://tech.unifa-e.com/entry/2020/12/16/080000