🦍

AWS SAMとLocalStackを使って、簡単にクラウドとローカルにデプロイする

2023/04/10に公開

はじめに

AWS SAMを使えば、サーバレスの開発がサクッとできて便利です。
ですが、少し凝ったことをするときはローカルでテストを行いたいことが多々あります。

そういった場合には、Localstackを利用する方が多いと思うのですが、SAMをどうやってLocalstackにデプロイするのか一瞬考えると思います。

lambdaであれば、zipで固めたものをLocakstackにデプロイしてもいいですが、他にも利用しているサービスがあった場合は面倒です。

そこで、aws-sam-cli-localを利用してみたら結構便利だったので、備忘録として残します。

やりたいこと

sam initで作成したプロジェクトを、sam deployと同じようなコマンドで、Localstackにデプロイしたい。

環境

本記事での環境に利用した環境は以下の通りです。

  • mac book pro 16(2019)
  • mac OS Monterey 12.5.1
  • Docker Desktop 4.16.2
  • SAM CLI, version 1.79.0
  • Local stack community 1.4.0

完成品

完成したコードは、こちらのリポジトリで公開しています。
https://github.com/toefuVox/tutorial_aws_sam_and_localstack

本題

では、実際にaws-sam-cli-localを利用して、Localstackにデプロイする環境を構築していきましょう。
今回は、サンプルプログラム Hello World をデプロイしてみようと思います。

環境構築

sam でサンプルプロジェクト Hello World を構築

まず、samとlocalstackのDockerfileを保存するためのディレクトリを作成します。

# sam と localstackの設定を保存するディレクトリを作成
$ mkdir sam-project

$ cd sam-project

次に、サンプルプロジェクトを作成していきます。

# samでプロジェクトを作成
$ sam init
You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

今回は、テンプレートを利用します。

Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

Hello World Example を選択します。

Choose an AWS Quick Start application template
        1 - Hello World Example
        2 - Multi-step workflow
        3 - Serverless API
        4 - Scheduled task
        5 - Standalone function
        6 - Data processing
        7 - Hello World Example With Powertools
        8 - Infrastructure event management
        9 - Serverless Connector Hello World Example
        10 - Multi-step workflow with Connectors
        11 - Lambda EFS example
        12 - DynamoDB Example
        13 - Machine Learning
Template: 1

今回は、個人的な趣味でnodejs18.xを利用します。

Use the most popular runtime and package type? (Python and zip) [y/N]:

Which runtime would you like to use?
        1 - aot.dotnet7 (provided.al2)
        2 - dotnet6
        3 - dotnet5.0
        4 - dotnetcore3.1
        5 - go1.x
        6 - go (provided.al2)
        7 - graalvm.java11 (provided.al2)
        8 - graalvm.java17 (provided.al2)
        9 - java11
        10 - java8.al2
        11 - java8
        12 - nodejs18.x
        13 - nodejs16.x
        14 - nodejs14.x
        15 - nodejs12.x
        16 - python3.9
        17 - python3.8
        18 - python3.7
        19 - python3.10
        20 - ruby2.7
        21 - rust (provided.al2)
Runtime: 12

packageのタイプは、Zipを選択して下さい。
Imageを選択してしまうと、aws-sam-local-cliでのデプロイ時に失敗します。

What package type would you like to use?
        1 - Zip
        2 - Image
Package type: 1

今回はJavascriptを選択しました。(TypeScriptでも問題ないと思います)

Based on your selections, the only dependency manager available is npm.
We will proceed copying the template using npm.

Select your starter template
        1 - Hello World Example
        2 - Hello World Example TypeScript
Template: 1

X-Rayは不要なのでインストールは行いません。

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]:

Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]:

後は、プロジェクトネームを入力して終了です。

Project name [sam-app]: hello-world

Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)

    -----------------------
    Generating application:
    -----------------------
    Name: hello-world
    Runtime: nodejs18.x
    Architectures: x86_64
    Dependency Manager: npm
    Application Template: hello-world
    Output Directory: .
    Configuration file: hello-world/samconfig.toml

    Next steps can be found in the README file at hello-world/README.md


Commands you can use next
=========================
[*] Create pipeline: cd hello-world && sam pipeline init --bootstrap
[*] Validate SAM template: cd hello-world && sam validate
[*] Test Function in the Cloud: cd hello-world && sam sync --stack-name {stack-name} --watch

aws-sam-cli-local をインストール

samlocal deployを実行するだけで、localstackへデプロイを行いたいので、aws-sam-cli-local をインストールします。
このパッケージを動かすには、pythonが必要なので、pipenvで環境を構築してインストールします。

$ pipenv --python 3.9

$ pipenv install aws-sam-cli-local

localstackの環境を構築

環境を汚したく無いので、localstackはdocker-composeを利用して動かします。

profileの準備

localstackで利用するプロファイルを準備しましょう。(この記事では特に必要ないですが、あった方が便利です)

まず、設定ファイルを格納するディレクトリを準備します。

$ mkdir -p docker/settings/profile

# profileに必要なファイルをファイルを作成
$ touch docker/settings/profile/config
$ touch docker/setting/profile/credentials

後は、以下の記述をそれぞれのファイルに記述して下さい。

config
[profile localstack]
region = us-east-1
output = json
credentials
[localstack]
aws_access_key_id = dummy
aws_secret_access_key = dummy

compose.yamlの準備

次に、compose.yamlを作成します。

$ touch compose.yaml

以下の設定をcompose.yamlに記載します。
ここで重要なのでは、localstack1.4.0を利用することです。
2.0以上のバージョンを利用すると、cloudformationを利用するには有料版(Pro)を利用してね!って出てきます(cloudformationも有料版限定になったんですかね?ドキュメント読む限りでは変更されてないはずなんですが・・・)

compose.yaml
services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME:-localstack_main}"
    image: localstack/localstack:1.4.0
    configs:
      - source: aws_profile
        target: /root/.aws
    ports:
      - "127.0.0.1:4566:4566" # LocalStack Gateway
      - "127.0.0.1:4510-4559:4510-4559" # external services port range
    environment:
      - DEBUG=${DEBUG:-0}
      - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR:-docker-reuse}
      - PROVIDER_OVERRIDE_LAMBDA=asf # enable runtime node.js 18.x
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

configs:
  aws_profile:
    file: ./docker/settings/profile

AWSへサンプルプロジェクト Hello Worldのデプロイ

せっかくなので、作成した Hello World プロジェクトをデプロイしてみましょう。

Hello World のビルド

まず、ビルドしましょう。

$ sam build

ビルドに成功すると、以下のように表示されます。

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided

Hello World のデプロイ

それでは、AWSにデプロイしましょう。
以下のコマンドを実行します。
※デフォルトの設定だとAPI GatewayはPublic(認証が設定されていない)なので注意して下さい。

$ sam deploy

デプロイの前準備が完了すると、以下のように聞かれるので、y を入力して下さい。

Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y

デプロイが正常に完了すると、以下のように結果が出力されます。

CloudFormation outputs from deployed stack
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 HelloWorldFunctionIamRole
Description         Implicit IAM Role created for Hello World function
Value               arn:aws:iam::*************:role/hello-world-HelloWorldFunctionRole-6424RVTUI0FK

Key                 HelloWorldApi
Description         API Gateway endpoint URL for Prod stage for Hello World function
Value               https://ijo53fvme5.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/

Key                 HelloWorldFunction
Description         Hello World Lambda Function ARN
Value               arn:aws:lambda:ap-northeast-1:*************:function:hello-world-HelloWorldFunction-AwwoJ7wiNUIz
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Successfully created/updated stack - hello-world in ap-northeast-1

APIへのアクセス

早速、デプロイしたAPIを叩いてみましょう。
以下のようにコマンドを実行すると、無事にレスポンスが返ってくることが確認できます。

$ curl https://ijo53fvme5.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message":"hello world"}

Hello Worldの削除

PublicでAPIを解放しておくのは怖いので、リソースを全て削除しておきます。
リソースを削除してもよいかと聞いてくるので、両方ともyを入力して削除します。

$ sam delete
        Are you sure you want to delete the stack hello-world in the region ap-northeast-1 ? [y/N]: y
        Do you want to delete the template file 611597fa35f8a2c9dd32e39f1b1b5a88.template in S3? [y/N]: y
        - Deleting S3 object with key 5e3a92ff23df6d2dc4d4f08846f18326
        - Deleting S3 object with key 611597fa35f8a2c9dd32e39f1b1b5a88.template
        - Deleting Cloudformation stack hello-world

Deleted successfully

上記のコマンドを実行しても、aws-sam-cli-managed-default というスタックだけは消えません。
これが不要な場合は、CloudFormationのコンソールから手動で削除して下さい。

localstackへのデプロイ

それでは、本命のlocalstackにデプロイします。
pipenvのshellに入って、samlocal deployを実行するだけです。

# samlocalコマンドを利用するので、pipenvのshellに入る
$ pipenv shell

# localstackにデプロイ
$ samlocal deploy

                Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-d00eacc7
                A different default S3 bucket can be set in samconfig.toml
                Or by specifying --s3-bucket explicitly.
        Uploading to 5e3a92ff23df6d2dc4d4f08846f18326  565189 / 565189  (100.00%)

        Deploying with following values
        ===============================
        Stack name                   : hello-world
        Region                       : ap-northeast-1
        Confirm changeset            : True
        Disable rollback             : False
        Deployment s3 bucket         : aws-sam-cli-managed-default-samclisourcebucket-d00eacc7
        Capabilities                 : ["CAPABILITY_IAM"]
        Parameter overrides          : {}
        Signing Profiles             : {}

Initiating deployment
=====================

        Uploading to c3b9a99c53e505d7a2990d04e3cfe9d3.template  1175 / 1175  (100.00%)


Waiting for changeset to be created..

CloudFormation stack changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                                           LogicalResourceId                                   ResourceType                                        Replacement
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add                                               HelloWorldFunctionHelloWorldPermissionProd          AWS::Lambda::Permission                             N/A
+ Add                                               HelloWorldFunctionRole                              AWS::IAM::Role                                      N/A
+ Add                                               ServerlessRestApiProdStage                          AWS::ApiGateway::Stage                              N/A
+ Add                                               ServerlessRestApiDeployment47fc2d5f9d               AWS::ApiGateway::Deployment                         N/A
+ Add                                               ServerlessRestApi                                   AWS::ApiGateway::RestApi                            N/A
+ Add                                               HelloWorldFunction                                  AWS::Lambda::Function                               N/A
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:000000000000:changeSet/samcli-deploy1680994057/5fec67f8



AWSへデプロイする時と同様に、デプロイしてよいか?と聞かれるので y を入力して下さい。


Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y



デプロイが完了すると、以下のようにAPIのエンドポイントを確認することができます。

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

Key                 HelloWorldFunction
Description         Hello World Lambda Function ARN
Value               arn:aws:lambda:ap-northeast-1:000000000000:function:hello-world-HelloWorldFunction-f6ad045a

Key                 HelloWorldFunctionIamRole
Description         Implicit IAM Role created for Hello World function
Value               arn:aws:iam::000000000000:role/hello-world-HelloWorldFunctionRole-5bbbddd8
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Successfully created/updated stack - hello-world in ap-northeast-1



ここまできたら、後はhttps://yut38v0htj.execute-api.amazonaws.com:4566/Prod/hello/を叩くだけです!
と言いたいのですが、このエンドポイントは嘘です。信じないでください。

localstackでAPI Gatewayのエンドポイントを叩く場合は、以下のように読み替える必要があります。

- https://yut38v0htj.execute-api.amazonaws.com:4566/Prod/hello/
+ http://yut38v0htj.execute-api.localhost.localstack.cloud:4566/Prod/hello/



読み替えた状態で、以下のようにエンドポイントを叩けば、無事にレスポンスが返ってきます。

$ curl http://yut38v0htj.execute-api.localhost.localstack.cloud:4566/Prod/hello/
{"message":"hello world"}

まとめ

ということで、AWS SAM + Localstack + aws-sam-cli-local を使って、Hello Worldのデプロイをやってみました。

以前までは、localstackを起動して、S3バケットを作って、zipで固めて、Lambdaをデプロイして、API Gate Wayを作成して・・・と原始人スタイルで開発していたので結構大変でしたが、aws-sam-cli-localを使うことで簡単にデプロイできることが確認できました。

この程度であれば、sam local start-apiを実行するだけでよいですが、S3などを利用したい場合は一気に楽になると思います。

もっとリッチな構成でテストを行う場合は、LocalStack Proを利用する必要性がありますが、構成次第ではCommunity でも十分だと思います。

Discussion