CLOVER🍀

That was when it all began.

LocalStack+AWS SAMで簡単にデプロイする(再デプロイできない問題を回避する)

これは、なにをしたくて書いたもの?

AWSを使う代わりに、LocalStackをよく使っているのですが。

特にAWS SAMでデプロイすると2回目以降で困ったことになっていたので、それをなんとか回避したいといろいろ
考えてみました。

1回目はいいのですが、2回目は応答が返らなくなり、スタックを削除しようとしても失敗するのでけっこう困って
いたのですが。そもそも本物のAWSでもないので、これを時間に時間をかけて調べるのもな、と思っていたので。

どのみち短時間しか使わないので、新規にリソースをデプロイしなおすことにしました。

結論

これでいくことにします。

デプロイ。

$ yes | samlocal sync --stack-name $(uuidgen) --region us-east-1

AWS SAM Accelerateを使います。

Amazon API GatewayのREST API IDの取得。

$ REST_API_ID=$(awslocal apigateway get-rest-apis --query 'reverse(sort_by(items[], &createdDate))[0].id' --output text)

デプロイ先にアクセスする時のURL。

http://localhost:4566/restapis/$REST_API_ID/Prod/_user_request_/[path]

環境

確認環境は、こちらです。

$ localstack --version
0.13.2.1


$ python3 -V
Python 3.8.10


$ awslocal --version
aws-cli/2.4.8 Python/3.8.8 Linux/5.4.0-91-generic exe/x86_64.ubuntu.20 prompt/off


$ samlocal --version
SAM CLI, version 1.36.0

起動。

$ LAMBDA_EXECUTOR=docker-reuse localstack start

困っていたこと

とりあえず、なにに困っていたかを書いておきます。

再現のために、プロジェクトを作成。

$ samlocal init --name localstack-deploy-sample --runtime nodejs14.x --app-template hello-world  --package-type Zip

アプリケーション名をlocalstack-deploy-sampleにして、ランタイムはNode.js 14.xにします。

プロジェクト内に移動。

$ cd localstack-deploy-sample

そのままビルドしてデプロイ。

$ samlocal build
$ awslocal s3 mb s3://my-bucket
$ samlocal deploy --stack-name my-stack --region us-east-1 --s3-bucket my-bucket

LocalStackの場合、Amazon S3バケットも先に作っておかないといけないようです。ここも面倒といえば面倒です…。

1回目は、問題なくうまくいきます。

2022-01-05 23:30:03 - Waiting for stack create/update to complete

CloudFormation events from stack operations
---------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                      ResourceType                        LogicalResourceId                   ResourceStatusReason
---------------------------------------------------------------------------------------------------------------------------------------------
CREATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApiDeployment510f4c   -
                                                                        1a20
CREATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApi                   -
CREATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunctionRole              -
CREATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApiProdStage          -
CREATE_COMPLETE                     AWS::CloudFormation::Stack          my-stack                            -
CREATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunctionHelloWorldPermi   -
                                                                        ssionProd
CREATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunction                  -
---------------------------------------------------------------------------------------------------------------------------------------------

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

Key                 HelloWorldFunction
Description         Hello World Lambda Function ARN
Value               arn:aws:lambda:us-east-1:000000000000:function:my-stack-HelloWorldFunction-55d04da1

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

Successfully created/updated stack - my-stack in us-east-1

ここで、もう1度デプロイしようとしてみます。

$ samlocal deploy --stack-name my-stack --region us-east-1 --s3-bucket my-bucket

すると、今度はここで止まってしまいます。

2022-01-05 23:32:18 - Waiting for stack create/update to complete

CloudFormation events from stack operations
---------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                      ResourceType                        LogicalResourceId                   ResourceStatusReason              
---------------------------------------------------------------------------------------------------------------------------------------------
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunction                  -                                 
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunctionHelloWorldPermi   -                                 
                                                                        ssionProd                                                             
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunctionRole              -                                 
CREATE_COMPLETE                     AWS::CloudFormation::Stack          my-stack                            -                                 
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApiProdStage          -                                 
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApiDeployment510f4c   -                                 
                                                                        1a20                                                                  
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApi                   -                                 
---------------------------------------------------------------------------------------------------------------------------------------------

では、スタックを削除したらいいのでは?と思うのですが

$ samlocal delete --stack-name my-stack --region us-east-1                    
        Are you sure you want to delete the stack my-stack in the region us-east-1 ? [y/N]: y

これもうまくいきません。

Traceback (most recent call last):
  File "/path/to/venv/bin/samlocal", line 41, in <module>
    main.cli()
  File "/path/to/venv/lib/python3.8/site-packages/click/core.py", line 1128, in __call__
    return self.main(*args, **kwargs)
  File "/path/to/venv/lib/python3.8/site-packages/click/core.py", line 1053, in main
    rv = self.invoke(ctx)
  File "/path/to/venv/lib/python3.8/site-packages/click/core.py", line 1659, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/path/to/venv/lib/python3.8/site-packages/click/core.py", line 1395, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/path/to/venv/lib/python3.8/site-packages/click/core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "/path/to/venv/lib/python3.8/site-packages/click/decorators.py", line 84, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/path/to/venv/lib/python3.8/site-packages/click/core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "/path/to/venv/lib/python3.8/site-packages/samcli/lib/utils/version_checker.py", line 41, in wrapped
    actual_result = func(*args, **kwargs)
  File "/path/to/venv/lib/python3.8/site-packages/samcli/cli/main.py", line 87, in wrapper
    return func(*args, **kwargs)
  File "/path/to/venv/lib/python3.8/site-packages/samcli/commands/delete/command.py", line 77, in cli
    do_cli(
  File "/path/to/venv/lib/python3.8/site-packages/samcli/commands/delete/command.py", line 101, in do_cli
    delete_context.run()
  File "/path/to/venv/lib/python3.8/site-packages/samcli/commands/delete/delete_context.py", line 345, in run
    is_deployed = self.cf_utils.has_stack(stack_name=self.stack_name)
  File "/path/to/venv/lib/python3.8/site-packages/samcli/lib/delete/cfn_utils.py", line 33, in has_stack
    if stack["EnableTerminationProtection"]:
KeyError: 'EnableTerminationProtection'

いつもどうしていたかというと、LocalStackを再起動していたのですが。

それで、どうしようかなと思っていたのですが。

まず、sam syncというコマンドを見つけまして。これを使うとsam buildとsam deployを一気にできるみたいです。

AWS SAM Accelerateによるサーバーレス開発の加速 | Amazon Web Services ブログ

Getting started with AWS SAM Accelerate - AWS Serverless Application Model

これはAWS SAM Accelerateというらしく、AWS CloudFormationの変更確認をスキップするので高速らしいです。

オプション無しのsam syncコマンドは、sam deployコマンドと同様に全てのインフラストラクチャとコードをデプロイまたは、アップデートします。ただし、sam deployとは異なり、sam syncはAWS CloudFormationの変更セットプロセスをバイパスします。

試してみましょう。

$ samlocal sync --stack-name sync-stack --region us-east-1

Amazon S3バケットはAWS SAM側で作ってくれるみたいです。

初回は、こういう表示が出ます。

        Creating the required resources...                                                                                                           
        Successfully created!  

また、プレビュー機能(かつ開発環境のみで使うこと、だそうで)だからか、実行時に確認を求められます。

                Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-2d2f9b80

                Default capabilities applied: ('CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND')
To override with customized capabilities, use --capabilities flag or set it in samconfig.toml

This feature is currently in beta. Visit the docs page to learn more about the AWS Beta terms https://aws.amazon.com/service-terms/.

The SAM CLI will use the AWS Lambda, Amazon API Gateway, and AWS StepFunctions APIs to upload your code without 
performing a CloudFormation deployment. This will cause drift in your CloudFormation stack. 
**The sync command should only be used against a development stack**.

Confirm that you are synchronizing a development stack and want to turn on beta features.

Enter Y to proceed with the command, or enter N to cancel:
 [y/N]: 

sam buildの動作が見えた後にデプロイが実行され、完了します。

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

2022-01-05 23:45:28 - Waiting for stack create/update to complete

CloudFormation events from stack operations
---------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                      ResourceType                        LogicalResourceId                   ResourceStatusReason
---------------------------------------------------------------------------------------------------------------------------------------------
CREATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunctionRole              -
CREATE_COMPLETE                     AWS::CloudFormation::Stack          AwsSamAutoDependencyLayerNestedSt   -
                                                                        ack
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          AwsSamAutoDependencyLayerNestedSt   -
                                                                        ack
CREATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApiProdStage          -
CREATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApiDeployment510f4c   -
                                                                        1a20
CREATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApi                   -
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunctionRole              -
CREATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunction                  -
CREATE_COMPLETE                     AWS::CloudFormation::Stack          sync-stack                          -
CREATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunctionHelloWorldPermi   -
                                                                        ssionProd
CREATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunction                  -
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApiProdStage          -
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApiDeployment510f4c   -
                                                                        1a20
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          ServerlessRestApi                   -
CREATE_COMPLETE                     AWS::CloudFormation::Stack          HelloWorldFunctionHelloWorldPermi   -
                                                                        ssionProd
---------------------------------------------------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
----------------------------------------------------------------------------------------------------------------------------------------------
Outputs
----------------------------------------------------------------------------------------------------------------------------------------------
Key                 HelloWorldFunction19d43fc4DepLayer
Description
Value

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

Key                 HelloWorldFunction
Description         Hello World Lambda Function ARN
Value               arn:aws:lambda:us-east-1:000000000000:function:sync-stack-HelloWorldFunction-bd920b93

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

Stack creation succeeded. Sync infra completed.

{'StackId': 'arn:aws:cloudformation:us-east-1:000000000000:stack/sync-stack/76e2a488', 'ResponseMetadata': {'HTTPStatusCode': 200, 'HTTPHeaders': {'content-type': 'text/html; charset=utf-8', 'content-length': '254', 'access-control-allow-origin': '*', 'access-control-allow-methods': 'HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH', 'access-control-allow-headers': 'authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request', 'access-control-expose-headers': 'etag,x-amz-version-id', 'connection': 'close', 'date': 'Wed, 05 Jan 2022 14:45:28 GMT', 'server': 'hypercorn-h11'}, 'RetryAttempts': 0}}

なお、もう1度実行するとエラーになってしまいます。既存のリソースがあるとダメみたいですね。

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

2022-01-05 23:46:06 - Waiting for stack create/update to complete

CloudFormation events from stack operations
---------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                      ResourceType                        LogicalResourceId                   ResourceStatusReason              
---------------------------------------------------------------------------------------------------------------------------------------------
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          AwsSamAutoDependencyLayerNestedSt   -                                 
                                                                        ack                                                                   
UPDATE_FAILED                       AWS::CloudFormation::Stack          sync-stack                          -                                 
---------------------------------------------------------------------------------------------------------------------------------------------
Failed to create/update the stack: sync-stack, Waiter StackUpdateComplete failed: Waiter encountered a terminal failure state: For expression "Stacks[].StackStatus" we matched expected path: "UPDATE_FAILED" at least once
Error: Failed to create/update the stack: sync-stack, Waiter StackUpdateComplete failed: Waiter encountered a terminal failure state: For expression "Stacks[].StackStatus" we matched expected path: "UPDATE_FAILED" at least once

なので、もう環境を使い捨てることを前提に、スタック名をランダム(今回はUUID)にしてしまいます。

$ samlocal sync --stack-name $(uuidgen) --region us-east-1

どのみち、遊び終わったら終了しますし、リソースが増えて困るようであれば再起動すればいいので…。

そして、毎回確認されるのも面倒なので、yesコマンドをつなげます(確認を省略する方法はわかりませんでした)。

$ yes | samlocal sync --stack-name $(uuidgen) --region us-east-1

こうすると、Amazon API Gatewayがたくさん作られていくことになりますが、最新のREST APIのIDを抽出します。

$ REST_API_ID=$(awslocal apigateway get-rest-apis --query 'reverse(sort_by(items[], &createdDate))[0].id' --output text)

こちらを使って、デプロイしたAPIにアクセスします。

http://localhost:4566/restapis/$REST_API_ID/Prod/_user_request_/[path]

こんな感じですね。

まとめ

LocalStack+AWS SAMで、いつもデプロイし直す時に困っていたので対処方法を考えてみました。

いや、素直にAWSを使えばいいのではという話もあるのですが、LocalStackとAWS SAMの組み合わせの場合は
これでなんとかしていこうかなと思います。