DockerだけでAWS SAMをLocalStackにデプロイしてみた

# はじめに

AWS SAMを試すため、[LocalStack](https://github.com/localstack/localstack)へデプロイしようとしたら、通常の[sam](https://github.com/aws/aws-sam-cli)コマンドでは、おそらくデプロイ先のエンドポイントURLを変更できないため、LocalStackへデプロイできませんでした。
そこで、LocalStackが提供する[samlocal](https://github.com/localstack/aws-sam-cli-local)コマンドを使用するとデプロイできました。

また、ローカル環境をあまり汚したくなかったため、Docker (+ Docker Compose) のみで試しております。

この記事は以上のことについて、備忘録としてまとめたものです。

# 前提条件
DockerとDocker Composeがインストールされていること。

# やってみた

## LocalStackを起動させておく

```bash
$ git clone https://github.com/localstack/localstack.git
Cloning into 'localstack'...
remote: Enumerating objects: 23149, done.
remote: Counting objects: 100% (1772/1772), done.
remote: Compressing objects: 100% (619/619), done.
remote: Total 23149 (delta 1241), reused 1611 (delta 1134), pack-reused 21377
Receiving objects: 100% (23149/23149), 9.99 MiB | 16.35 MiB/s, done.
Resolving deltas: 100% (16797/16797), done.

$ cd localstack

$ docker-compose up -d
Pulling localstack (localstack/localstack:)...
latest: Pulling from localstack/localstack
c91cc7261d92: Pull complete
Digest: sha256:74323cb3ef6709f9732802e74c80b3b997359d25e603955b708efae61633b1df
Status: Downloaded newer image for localstack/localstack:latest
Creating localstack_main ... done
```

## 開発環境としてPythonコンテナを起動し、中に入る

```bash
# Pythonコンテナ内からLocalStackコンテナを名前解決可能にするため、「--link localstack_main:localstack」を設定している
$ docker run --rm -it --link localstack_main:localstack python:3.8 bash
Unable to find image 'python:3.8' locally
3.8: Pulling from library/python
bb7d5a84853b: Pull complete 
f02b617c6a8c: Pull complete 
d32e17419b7e: Pull complete 
c9d2d81226a4: Pull complete 
3c24ae8b6604: Pull complete 
8a4322d1621d: Pull complete 
a03ef301ddd7: Pull complete 
a4c591fc96f3: Pull complete 
799e70ce6240: Pull complete 
Digest: sha256:b39ab988ac2fa749273cf6fdeb89fb3635850824419dc612c8a21ff4c7961b8b
Status: Downloaded newer image for python:3.8
```

## Pythonコンテナで事前設定を行う

```bash
# ホームディレクトリへ移動する (任意)
root@151ed9cb109a:/# cd

root@151ed9cb109a:~# pip install awscli aws-sam-cli aws-sam-cli-local
# ・・・(省略)
Successfully installed Flask-1.1.4 Jinja2-2.11.3 MarkupSafe-2.0.1 PyYAML-5.4.1 Werkzeug-1.0.1 arrow-1.2.1 attrs-21.2.0 aws-lambda-builders-1.8.1 aws-sam-cli-1.33.0 aws-sam-cli-local-1.1.0.1 aws-sam-translator-1.39.0 awscli-1.21.2 backports.zoneinfo-0.2.1 binaryornot-0.4.4 boto3-1.19.2 botocore-1.22.2 certifi-2021.10.8 chardet-4.0.0 chevron-0.14.0 click-7.1.2 colorama-0.4.3 cookiecutter-1.7.3 dateparser-1.1.0 docker-4.2.2 docutils-0.15.2 idna-2.10 itsdangerous-1.1.0 jinja2-time-0.2.0 jmespath-0.10.0 jsonschema-3.2.0 poyo-0.5.0 pyasn1-0.4.8 pyrsistent-0.18.0 python-dateutil-2.8.2 python-slugify-5.0.2 pytz-2021.3 pytz-deprecation-shim-0.1.0.post0 regex-2021.10.23 requests-2.25.1 rsa-4.7.2 s3transfer-0.5.0 serverlessrepo-0.1.10 six-1.16.0 text-unidecode-1.3 tomlkit-0.7.2 tzdata-2021.4 tzlocal-4.0.1 urllib3-1.26.7 watchdog-2.1.2 websocket-client-1.2.1
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
WARNING: You are using pip version 21.2.4; however, version 21.3.1 is available.
You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.

# LocalStack用プロファイルを設定する
root@151ed9cb109a:~# aws configure --profile=local
AWS Access Key ID [None]: dummy
AWS Secret Access Key [None]: dummy
Default region name [None]: ap-northeast-1
Default output format [None]: 

# samlocal用に設定する
root@151ed9cb109a:~# export LOCALSTACK_HOSTNAME=localstack

# samlocal用に設定する
root@151ed9cb109a:~# export EDGE_PORT=4566
```

## SAMサンプルアプリケーションを作成する

```bash
root@151ed9cb109a:~# sam init --runtime python3.8

        SAM CLI now collects telemetry to better understand customer needs.

        You can OPT OUT and disable telemetry collection by setting the
        environment variable SAM_CLI_TELEMETRY=0 in your shell.
        Thanks for your help!

        Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html

Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1
What package type would you like to use?
        1 - Zip (artifact is a zip uploaded to S3)
        2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 1

Project name [sam-app]:  

Cloning from https://github.com/aws/aws-sam-cli-app-templates

AWS quick start application templates:
        1 - Hello World Example
        2 - EventBridge Hello World
        3 - EventBridge App from scratch (100+ Event Schemas)
        4 - Step Functions Sample App (Stock Trader)
        5 - Elastic File System Sample App
Template selection: 1

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: python3.8
    Architectures: x86_64
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .
    
    Next steps can be found in the README file at ./sam-app/README.md
```

## SAMサンプルアプリケーションをLocalStackへデプロイする

```bash
root@151ed9cb109a:~# cd sam-app

root@151ed9cb109a:~/sam-app# SAM_S3_BUCKET_NAME=sam-bucket

# SAMのソースコードアップロード用S3バケットを作成する
root@151ed9cb109a:~/sam-app# aws s3 mb s3://${SAM_S3_BUCKET_NAME} \
  --endpoint-url http://${LOCALSTACK_HOSTNAME}:${EDGE_PORT} \
  --profile local
make_bucket: sam-bucket

root@151ed9cb109a:~/sam-app# samlocal deploy \
  --stack-name sam-stack \
  --s3-bucket ${SAM_S3_BUCKET_NAME} \
  --profile local
Uploading to c6ce8fa8b5a97dd022ecd006536eb5a4  847 / 847  (100.00%)

        Deploying with following values
        ===============================
        Stack name                   : sam-stack
        Region                       : ap-northeast-1
        Confirm changeset            : False
        Deployment s3 bucket         : sam-bucket
        Capabilities                 : null
        Parameter overrides          : {}
        Signing Profiles             : {}

Initiating deployment
=====================
Uploading to d3c4a0926d7588147cfdc9906b4e375a.template  1068 / 1068  (100.00%)

Waiting for changeset to be created..

CloudFormation stack changeset
-------------------------------------------------------------------------------------------------------------
Operation                   LogicalResourceId           ResourceType                Replacement               
-------------------------------------------------------------------------------------------------------------
+ Add                       HelloWorldFunction          AWS::Lambda::Function       False                     
+ Add                       HelloWorldFunctionRole      AWS::IAM::Role              False                     
+ Add                       HelloWorldFunctionHelloWo   AWS::Lambda::Permission     False                     
                            rldPermissionProd                                                                 
+ Add                       ServerlessRestApi           AWS::ApiGateway::RestApi    False                     
+ Add                       ServerlessRestApiDeployme   AWS::ApiGateway::Deployme   False                     
                            nt510f4c1a20                nt                                                    
+ Add                       ServerlessRestApiProdStag   AWS::ApiGateway::Stage      False                     
                            e                                                                                 
-------------------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:000000000000:changeSet/samcli-deploy1635154640/fa450628


2021-10-25 09:37:20 - Waiting for stack create/update to complete

CloudFormation events from changeset
-------------------------------------------------------------------------------------------------------------
ResourceStatus              ResourceType                LogicalResourceId           ResourceStatusReason      
-------------------------------------------------------------------------------------------------------------
CREATE_COMPLETE             AWS::CloudFormation::Stac   HelloWorldFunctionRole      -                         
                            k                                                                                 
CREATE_COMPLETE             AWS::CloudFormation::Stac   ServerlessRestApiProdStag   -                         
                            k                           e                                                     
CREATE_COMPLETE             AWS::CloudFormation::Stac   ServerlessRestApiDeployme   -                         
                            k                           nt510f4c1a20                                          
CREATE_COMPLETE             AWS::CloudFormation::Stac   ServerlessRestApi           -                         
                            k                                                                                 
CREATE_COMPLETE             AWS::CloudFormation::Stac   sam-stack                   -                         
                            k                                                                                 
CREATE_COMPLETE             AWS::CloudFormation::Stac   HelloWorldFunctionHelloWo   -                         
                            k                           rldPermissionProd                                     
CREATE_COMPLETE             AWS::CloudFormation::Stac   HelloWorldFunction          -                         
                            k                                                                                 
-------------------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
--------------------------------------------------------------------------------------------------------------
Outputs                                                                                                      
--------------------------------------------------------------------------------------------------------------
Key                 HelloWorldApi                                                                            
Description         API Gateway endpoint URL for Prod stage for Hello World function                         
Value               https://x3ovhxtd7l.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/                  

Key                 HelloWorldFunction                                                                       
Description         Hello World Lambda Function ARN                                                          
Value               arn:aws:lambda:ap-northeast-1:000000000000:function:sam-stack-HelloWorldFunction-        
fd0867a1                                                                                                     

Key                 HelloWorldFunctionIamRole                                                                
Description         Implicit IAM Role created for Hello World function                                       
Value               arn:aws:iam::000000000000:role/sam-stack-HelloWorldFunctionRole-4bd323a3                 
--------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - sam-stack in ap-northeast-1
```

## 実行確認として、LocalStackにデプロイされたAPI Gatewayへリクエストする

```bash
# デプロイされたREST API IDを取得する
root@151ed9cb109a:~/sam-app# REST_API_ID=$(
  aws apigateway get-rest-apis \
    --query items[0].id \
    --output text \
    --endpoint-url http://${LOCALSTACK_HOSTNAME}:${EDGE_PORT} \
    --profile local
)

root@151ed9cb109a:~/sam-app# curl http://${LOCALSTACK_HOSTNAME}:${EDGE_PORT}/restapis/${REST_API_ID}/Prod/_user_request_/hello
{"message": "hello world"}
```