kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Lambda の運用面でのベストプラクティスを学べる「AWS Lambda Operator Guide」を読んだ

AWS の公式ドキュメント「AWS Lambda Operator Guide」を読んだ❗️AWS Lambda を軸にサーバーレスアプリケーションを構築するときに意識しておくべき "運用面のポイント・ベストプラクティス" がまとまっていて,とても良いドキュメントだった👏 内容的には AWS Well-Architected Framework: Serverless Applications Lens と重複するところもあるけど,サーバーレスアプリケーションを開発・運用しているなら1度は読んでおくと良いのではないでしょうか❗️

\( 'ω')/ 多くの人に読みやすくなるように日本語翻訳もあるとイイなぁ〜

docs.aws.amazon.com

構成

ドキュメントとしては全6章で構成されている.どれも重要で,理解を深めるために読むのはもちろん,開発中もしくは運用中のアプリケーションに対して「これは検討できてるかな?こっちはどう?」という感じで自問自答しながらセルフレビューに活用できたりもする💡

  • 1. Event-driven architectures(イベント駆動型アーキテクチャ)
  • 2. Application design(アプリケーション設計)
  • 3. Security(セキュリティ)
  • 4. Debugging(デバッグ)
  • 5. Monitoring and observability(モニタリングとオブザーバビリティ)
  • 6. Performance optimization(パフォーマンス最適化)

⚡️1. Event-driven architectures(イベント駆動型アーキテクチャ)

トピック一覧

  • How Lambda fits into the event-driven paradigm(Lambda がイベント駆動型パラダイムにどのように適合するか)
  • The benefits of event-driven architectures(イベント駆動型アーキテクチャのメリット)
  • Trade-offs of event-driven architectures(イベント駆動型アーキテクチャのトレードオフ)
  • Design principles(設計原則)
  • Anti-patterns in Lambda-based applications(Lambda を使ったアプリケーションのアンチパターン)
  • Frequently asked questions(よくある質問)

メモ

AWS Lambda の活用例としてよく出てくる「イベント駆動型」に関するトピックを学べる.前半ではイベント駆動型のメリットやポーリング型との違いなどがまとまっている.後半ではトレードオフ・設計原則・アンチパターンまでまとまっていて,イベント駆動型アーキテクチャを実践するときに理解しておくべきポイントが凝縮されている.

Design principlesDeveloping for retries and failures では「冪等性」の実現例として,Amazon DynamoDB に TTL (Time To Live) 付きの項目を登録して,トランザクションが以前に処理されたかどうかを判断するテクニックが載っていた💡これはとても実践的で,実は最近仕事で似たような実装を検討していて,関連情報として使えるな〜なんて思ったりもした.

他には Anti-patterns in Lambda-based applicationsThe Lambda monolith(モノリスラムダ)という印象的な名前のアンチパターンも紹介されていたりもする❗️

⚡️2. Application design(アプリケーション設計)

トピック一覧

  • Understanding quotas(クォータを理解する)
  • Scaling and concurrency in Lambda(Lambda でのスケーリングと同時実行)
  • Choosing and managing runtimes in Lambda functions(Lambda 関数でのランタイムの選択と管理)
  • Networking and VPC configurations(ネットワーク構成と VPC 構成)
  • Comparing Lambda invocation modes(Lambda 呼び出しモードの比較)
  • Controlling traffic flow for server-based resources(サーバーを使ったリソースのトラフィックフローのコントロール)
  • Frequently asked questions(よくある質問)

メモ

サーバーレスアプリケーションをスケールしようとするときに重要になる AWS Lambda や Amazon API Gateway 関連のクォータ・設定項目がまとまっているのは良かった❗️同時実行数・バースト同時実行数・予約された同時実行数など.以下のドキュメントにも詳しく載っている.

docs.aws.amazon.com

Choosing and managing runtimes in Lambda functionsRuntimes and performance では AWS Lambda 関数のランタイムをどう選ぶかというトピックも載っていた.例えば Python / Node.js は初期化が高速で,Go は起動と実行どちらもパフォーマンスが良いなど.さらに Managing AWS SDKs in Lambda functions では,Python など一部のランタイムには AWS SDK(例えば boto3)がバンドルされているけど,Lambda レイヤーを使って AWS SDK のバージョンを固定するべきというベストプラクティスも載っていた❗️

docs.aws.amazon.com

AWS SDK のバージョンを固定するという話は The Twelve-Factor App の "依存関係" で宣言されている システム全体にインストールされるパッケージが暗黙的に存在することに決して依存しない を意識することにも繋がって非常に重要なプラクティスと言える👌

12factor.net

⚡️3. Security(セキュリティ)

トピック一覧

  • Understanding the Lambda execution environment(Lambda 実行環境の理解)
  • Applying the principles of least privilege(最小権限の法則を適用)
  • Securing workloads with public endpoints(パブリックエンドポイントでのワークロードの保護)
  • Encrypting data in Lambda-based applications(Lambda を使ったアプリケーションでのデータの暗号化)
  • Governance controls with AWS CloudTrail(AWS CloudTrail によるガバナンスコントロール)
  • Frequently asked questions(よくある質問)

メモ

Understanding the Lambda execution environment に載っている Lambda 実行環境の構成図は AWS Lambda 関数がどうやって立ち上がるのかを理解するのに良いと思う💡

また Avoiding granting wildcard permissions in IAM policies では AWS Lambda 関数に設定する IAM Role のポリシーでワイルドカードを回避して最小権限にするべきという話も載っている.理解はしつつもワイルドカードのまま設定されている AWS Lambda 関数はよく見るし,改めて意識しないとな〜と思えた.

⚡️4. Debugging(デバッグ)

  • Standardizing a debugging approach(デバッグアプローチの標準化)
  • Troubleshooting payloads(ペイロードのトラブルシューティング)
  • Troubleshooting integration errors(統合エラーのトラブルシューティング)
  • Troubleshooting Lambda configurations(Lambda 構成のトラブルシューティング)
  • Troubleshooting queue processing by Lambda functions(Lambda 関数によるキュー処理のトラブルシューティング)
  • Best practices for your debugging environment(デバッグ環境のベストプラクティス)

メモ

サーバーレスアプリケーションでは複数のコンポーネントを組み合わせることになるため,デバッグするポイントも増える.多種多様な観点で,どのようなトラブルが起きる可能性があるのか?という視野を広げられる内容になっている💡

例えば Troubleshooting payloads では,予期しないペイロードを受け取る可能性もあるので例外処理をしようというプラクティスが載っていて,すぐに使えそうだった.また Troubleshooting queue processing by Lambda functions では AWS Lambda と Amazon SQS を組み合わせる場合の注意点も詳しく載っているのも参考になる❗️

⚡️5. Monitoring and observability(モニタリングとオブザーバビリティ)

トピック一覧

  • Monitoring concepts in Lambda-based applications(Lambda を使ったアプリケーションのモニタリング概念)
  • Logging and metrics with Amazon CloudWatch(Amazon CloudWatch によるログとメトリクス)
  • Searching across logs with CloudWatch Logs Insights(CloudWatch Logs Insights を使ったログ全体の検索)
  • Tracing requests with AWS X-Ray(AWS X-Ray を使ったリクエストのトレース)
  • Troubleshooting walkthrough: isolating and resolving issues(トラブルシューティングウォークスルー: 問題の切り分けと解決)
  • A general approach to debugging Lambda performance issues and errors(Lambda のパフォーマンス問題とエラーをデバッグするための一般的なアプローチ)
  • Monitoring Lambda code storage(Lambda コールドストレージのモニタリング)

メモ

オブザーバビリティの定義としてよくある「メトリクス・ログ・トレース」それぞれをどのように実現するか?というトピックがまとまっている💡Logging and metrics with Amazon CloudWatchImportant metrics for CloudWatch では AWS Lambda をモニタリングするときに特に重要なメトリクス一覧が載っていて,Searching across logs with CloudWatch Logs InsightsUseful Insights queries では AWS Lambda 関数のログ検索でよく使う CloudWatch Logs Insights のクエリテンプレートも載っていて参考になる❗️

他にも Troubleshooting walkthrough: isolating and resolving issues は実際のアプリケーションでパフォーマンス問題を解決するというトラブルシューティングウォークスルーになっていて良かった💡

⚡️6. Performance optimization(パフォーマンス最適化)

トピック一覧

  • Lambda execution environments(Lambda 実行環境)
  • Memory and computing power(メモリと計算能力)
  • Optimizing static initialization(初期化の最適化)
  • Architecture and Best Practices(アーキテクチャとベストプラクティス)

メモ

Lambda 入門者が理解しておくと良い "コールドスタート" と "ウォームスタート" の違いが紹介されている.またドキュメントでは functions warmers という用語で紹介されていたけど,Amazon EventBridge を使って定期的に AWS Lambda 関数を呼び出し続けるという,前からよく知られたテクニックは,コールドスタートを削減することを保証するわけではないけど低優先度のワークロードであれば適しているかもしれないと紹介されているのも良かった💡

他には Profiling functions with AWS Lambda Power Tuning ではAWS Lambda 関数に割り当てる最適なメモリサイズを模索するために AWS Lambda Power TuningAWS Compute Optimizer を使うという話なども載っている.

github.com

まとめ

AWS の公式ドキュメント「AWS Lambda Operator Guide」を読んだ❗️サーバーレス(特に AWS Lambda)に対する理解度をグッと高められる良いドキュメントだった👏

サーバーレスアプリケーションを開発・運用しているなら1度は読んでおくと良いのではないでしょうか❗️

docs.aws.amazon.com

rain: CloudFormation を便利に操作しちゃおう

AWS CloudFormation のテンプレートとスタックを便利に操作できる AWS 公式ツール「rain」を紹介する.僕自身も AWS CloudFormation スタックを試行錯誤しながら実装するときによく rain を使っている.rain には機能が多くあるけど,今回は個人的に便利だと思う「10点」にしぼって紹介する❗️

github.com

aws-cloudformation.github.io

セットアップ

Homebrew で簡単にセットアップできる.サブコマンドは大きく「スタック用」「テンプレート用」でたくさんある❗️

$ brew install rain

$ rain --version
Rain v1.3.3 darwin/arm64

$ rain -h
Rain is a command line tool for working with AWS CloudFormation templates and stacks

Usage:
  rain [command]

Stack commands:
  cat         Get the CloudFormation template from a running stack
  deploy      Deploy a CloudFormation stack from a local template
  logs        Show the event log for the named stack
  ls          List running CloudFormation stacks
  rm          Delete a running CloudFormation stack
  stackset    This command manipulates stack sets.
  watch       Display an updating view of a CloudFormation stack

Template commands:
  build       Create CloudFormation templates
  diff        Compare CloudFormation templates
  fmt         Format CloudFormation templates
  merge       Merge two or more CloudFormation templates
  pkg         Package local artifacts into a template
  tree        Find dependencies of Resources and Outputs in a local template

Other Commands:
  completion  Generate the autocompletion script for the specified shell
  console     Login to the AWS console
  help        Help about any command
  info        Show your current configuration

便利な操作を紹介する

1. rain build でテンプレートの雛形を作ろう

AWS CloudFormation テンプレートを作るときに個人的によくやるのは AWS CloudFormation のドキュメントを検索して,YAML の雛形をコピーペーストして,あとは必須項目を埋めていく流れだけど,rain build コマンドを使うと AWS CloudFormation テンプレートの雛形をサクッと作れる👌

$ rain build AWS::S3::Bucket
AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      AccelerateConfiguration: # Optional
        AccelerationStatus: CHANGEME
      AccessControl: CHANGEME # Optional

(中略)

AWS::S3::Bucket は設定が多く200行以上も出力される.CHANGEME に値を設定したり,Optional は必要なければ消したりしながら組み立てる.また最初はシンプルに作りたいことも多いと思う.--bare オプションを設定すると必要最低限のテンプレートの雛形を作れる.AWS::S3::Bucket には必須項目はなく,以下のように驚くほどシンプルになる❗️

$ rain build --bare AWS::S3::Bucket
AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Resources:
  MyBucket:
    Type: AWS::S3::Bucket

rain build コマンドには複数のリソースを指定できる.以下の例は AWS::EC2::VPCAWS::EC2::Subnet を指定している.VpcId !Ref MyVPC のように紐付きまでは自動的に解決されないところは注意点かなぁー.

$ rain build --bare AWS::EC2::VPC AWS::EC2::Subnet
AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Resources:
  MySubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: CHANGEME

  MyVPC:
    Type: AWS::EC2::VPC

2. rain build --list でリソース一覧を検索しよう

rain build --list コマンドを使うと AWS CloudFormation でサポートしているリソース一覧を確認できる.現時点で1000種類以上もあるため,僕は peco と組み合わせて検索できるようにしている(おすすめ!).適当にエイリアスを設定しておくと便利で,よく使っている👾

$ rain build --list | head -n 5
AWS::ACMPCA::Certificate
AWS::ACMPCA::CertificateAuthority
AWS::ACMPCA::CertificateAuthorityActivation
AWS::ACMPCA::Permission
AWS::APS::RuleGroupsNamespace

$ rain build --list | wc -l
    1032

$ rain build --list | peco | pbcopy

$ alias cr='rain build --list | peco | pbcopy'

3. rain deploy でデプロイしよう

次に AWS::S3::Bucket をデプロイする.デプロイするときは rain deploy コマンドを使う.aws cloudformation deploy コマンドとあまり差がなさそうに思うけど,影響のあるリソース一覧が表示されたり,実行するかどうか (Y/n) のプロンプトが表示されたり,より価値のある情報が確認できるようになっている.

$ rain build --bare AWS::S3::Bucket > s3-bare.yaml

$ rain deploy s3-bare.yaml
CloudFormation will make the following changes:
Stack s3-bare:
  + AWS::S3::Bucket MyBucket
Do you wish to continue? (Y/n) Y
Deploying template 's3-bare.yaml' as stack 's3-bare' in ap-northeast-1.
Stack s3-bare: CREATE_COMPLETE
Successfully deployed s3-bare

rain の特徴の一つに「必要なログのみを表示する」という思想があって(Rain also filters out uninteresting log messages by default so you just see the errors that require attention.),rain コマンドで出力されるログや rain logs コマンドで取得できるログなどはデフォルトで非常に工夫されている.

$ rain logs s3-bare
No interesting log messages to display. To see everything, use the --all flag

$ rain logs s3-bare --all
Apr 16 00:00:00 s3-bare/s3-bare (AWS::CloudFormation::Stack) CREATE_COMPLETE
Apr 16 00:00:00 s3-bare/MyBucket (AWS::S3::Bucket) CREATE_COMPLETE
Apr 16 00:00:00 s3-bare/MyBucket (AWS::S3::Bucket) CREATE_IN_PROGRESS "Resource creation Initiated"
Apr 16 00:00:00 s3-bare/MyBucket (AWS::S3::Bucket) CREATE_IN_PROGRESS
Apr 16 00:00:00 s3-bare/s3-bare (AWS::CloudFormation::Stack) CREATE_IN_PROGRESS "User Initiated"
Apr 16 00:00:00 s3-bare/s3-bare (AWS::CloudFormation::Stack) REVIEW_IN_PROGRESS "User Initiated"

4. rain ls でスタック一覧を確認しよう

rain ls コマンドを使うとスタック一覧とステータスを確認できる.aws cloudformation list-stacks コマンドでも同じ情報は取得できるけど,無駄な情報がなくシンプル👍

$ rain ls
CloudFormation stacks in ap-northeast-1:
  s3-bare: CREATE_COMPLETE

5. rain rm でスタックを削除しよう

rain rm コマンドでスタックを削除できる.

$ rain rm s3-bare
Stack s3-bare: CREATE_COMPLETE
Are you sure you want to delete this stack? (y/N) y
Successfully deleted stack 's3-bare'

6. rain deploy でパラメータを設定しよう

以下のように Amazon S3 バケット名をパラメータから取得するように変更した.このテンプレートを aws cloudformation deploy コマンドでデプロイするときはパラメータを指定しないと ValidationError になる.

AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Parameters:
  BucketName:
    Type: String

Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref BucketName

rain deploy コマンドを使うと未指定の場合はインタラクティブに指定できる❗️これも便利〜

$ rain deploy s3-with-parameters.yaml
Enter a value for parameter 'BucketName': kakakakakku-s3-with-parameters
CloudFormation will make the following changes:
Stack s3-with-parameters:
  + AWS::S3::Bucket MyBucket
Do you wish to continue? (Y/n) Y
Deploying template 's3-with-parameters.yaml' as stack 's3-with-parameters' in ap-northeast-1.
Stack s3-with-parameters: CREATE_COMPLETE
Successfully deployed s3-with-parameters

スタックを一度消しておく.

$ rain rm s3-with-parameters

7. rain deploy で失敗してもそのままやり直そう

今度は Amazon S3 バケット名として許容されていない _ を含めてデプロイするとエラーになり,そのままもう一度 rain deploy コマンドを実行すると Deleted existing, empty stack. というメッセージが出力されて自動的に ROLLBACK_COMPLETE で失敗したスタックを削除してくれる.

$ rain deploy s3-with-parameters.yaml
Enter a value for parameter 'BucketName': kakakakakku_s3_with_parameters
CloudFormation will make the following changes:
Stack s3-with-parameters:
  + AWS::S3::Bucket MyBucket
Do you wish to continue? (Y/n) Y
Deploying template 's3-with-parameters.yaml' as stack 's3-with-parameters' in ap-northeast-1.
Stack s3-with-parameters: ROLLBACK_COMPLETE
Messages:
  - MyBucket: Bucket name should not contain '_'
failed deploying stack 's3-with-parameters'

$ rain deploy s3-with-parameters.yaml
Deleted existing, empty stack.
Enter a value for parameter 'BucketName' (existing value: kakakakakku_s3_with_parameters): kakakakakku-s3-with-parameters
CloudFormation will make the following changes:
Stack s3-with-parameters:
  + AWS::S3::Bucket MyBucket
Do you wish to continue? (Y/n) Y
Deploying template 's3-with-parameters.yaml' as stack 's3-with-parameters' in ap-northeast-1.
Stack s3-with-parameters: CREATE_COMPLETE
Successfully deployed s3-with-parameters

AWS CloudFormation スタックを作ったことがある人は経験していると思うけど,スタックを作るのに失敗して ROLLBACK_COMPLETE になると,1度スタックを削除する必要があって,地味にこの作業が手間に感じる.rain deploy コマンドではその手間を削減してくれて最高❗️

8. rain fmt でテンプレートを整形しよう

rain fmt コマンドを使うと AWS CloudFormation テンプレートを整形できる.整形できることを確認するために AWS::S3::Bucket のテンプレートを以下のように一部修正した.

AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Parameters:
  BucketName:
    Type: String
    Default: "kakakakakku-s3-bucket"

Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref BucketName
      Tags:
        - Key: 'Key1'
          Value: 'Value1'

Outputs:
  MyBucketArn:
    Value:
      Fn::GetAtt:
        - MyBucket
        - Arn

このテンプレートに rain fmt コマンドを実行すると整形できる.

  • 文字列の "' が削除された
  • Fn::GetAtt 組み込み関数が短縮形の !GetAtt になって1行にまとまった
rain fmt s3-before-format.yaml
AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Parameters:
  BucketName:
    Type: String
    Default: kakakakakku-s3-bucket

Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref BucketName
      Tags:
        - Key: Key1
          Value: Value1

Outputs:
  MyBucketArn:
    Value: !GetAtt MyBucket.Arn

他にも YAML / JSON を相補変換したりもできる(これは cfn-flip コマンドでもできるけど!).

github.com

9. rain tree で Resources と Outputs の依存関係を図にしよう

rain tree コマンドを使うと AWS CloudFormation テンプレートから Resources と Outputs の依存関係を図にできる.dot フォーマットで出力されるため,dot コマンドを使えば png ファイルなどに変換できる.

$ rain tree -d s3-with-parameters.yaml
digraph {
    rankdir=LR;
    concentrate=true;
    subgraph cluster_Parameters {
        label="Parameters";
        "Parameters: BucketName" [label="BucketName" shape=diamond];
    }

    subgraph cluster_Resources {
        label="Resources";
        "Resources: MyBucket" [label="MyBucket" shape=Mrecord];
    }

    subgraph cluster_Outputs {
        label="Outputs";
    }

    "Parameters: BucketName" -> "Resources: MyBucket";
}

$ rain tree -d s3-with-parameters.yaml > s3-with-parameters.dot

$ dot -T png s3-with-parameters.dot -o s3-with-parameters.png

10. rain cat で AWS SAM リソースの変換後テンプレートを確認しよう

rain cat コマンドを使うと AWS CloudFormation スタックのテンプレートを確認できる.さらに --transformed オプションを付けると AWS SAM などマクロを使った AWS CloudFormation テンプレートの変換後を確認できる.今回は AWS SAM のサンプルテンプレートを用意してスタックを作る.

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64

次に rain cat template --transformed コマンドを実行すると,以下のようにリソース名が AWS::Lambda::Function になっている.マクロによってどのように変換されたのかを確認したいときに使える.

$ rain cat template --transformed
AWSTemplateFormatVersion: "2010-09-09"

Resources:
  HelloWorldFunction:
    Type: AWS::Lambda::Function
    Properties: {Code: {S3Bucket: rain-artifacts-000000000000-ap-northeast-1, S3Key: b769a6f86744588f72b9c40013aec639a9dd8f8fdcbca559029a388836f8fe12}, Handler: app.lambda_handler, Role: !GetAtt HelloWorldFunctionRole.Arn, Runtime: python3.9, Tags: [{Key: 'lambda:createdBy', Value: SAM}], Architectures: [x86_64]}

  HelloWorldFunctionRole:
    Type: AWS::IAM::Role
    Properties: {AssumeRolePolicyDocument: {Version: "2012-10-17", Statement: [{Action: ['sts:AssumeRole'], Effect: Allow, Principal: {Service: [lambda.amazonaws.com]}}]}, ManagedPolicyArns: ['arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'], Tags: [{Key: 'lambda:createdBy', Value: SAM}]}

まとめ

AWS CloudFormation のテンプレートとスタックを便利に操作できる AWS 公式ツール「rain」を紹介した.aws cloudformation コマンドを使うよりも便利なので,AWS CloudFormation を使っていたら試してみると良いかと❗️おすすめです〜

github.com

LocalStack と samlocal コマンドで SQS x Lambda 構成をローカル環境で実行する

開発中に AWS Lambda 関数をローカル環境で実行するなら AWS SAM CLI の sam local invoke -e event.json コマンドを使えば良く,また AWS Lambda 関数のベストプラクティスに載っている Lambda ハンドラーをコアロジックから分離します。 を意識して実装すれば,単体テストを軸に動作確認を進めることもできる.

とは言え,実際に AWS Lambda 関数のイベントソースマッピング(トリガー設定)を確認したいこともあり,今回は AWS SAM を使って構築する Amazon SQS キューと AWS Lambda 関数の組み合わせを samlocal コマンドを使って LocalStack にデプロイして動作確認をしてみた.結果的に期待した通りに動いた❗️

\( 'ω')/ LocalStack 便利すぎ〜

🚀 アーキテクチャ図

🚀 検証環境

template.yaml

検証に使った AWS SAM テンプレート(AWS CloudFormation テンプレート)を以下に載せておく.特別なことはなく AWS::SQS::QueueAWS::Serverless::Function を構築して,イベントソースマッピングを設定してある.ランタイムは何でも良く,今回は Python 3.9 にしてある.

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
  Queue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: kakakakakku-queue
      ReceiveMessageWaitTimeSeconds: 20
  Function:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: kakakakakku-function
      CodeUri: app/
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      Events:
        SqsEvent:
          Type: SQS
          Properties:
            Queue: !GetAtt Queue.Arn

app/app.py

AWS Lambda 関数で動かす Python コードはあくまで必要最低限にして,Amazon SQS キューのメッセージをログ出力(AWS CloudWatch Logs)している🐍

def lambda_handler(event, context):
    for record in event['Records']:
        print(record['body'])

GitHub にも置いておいた💡

github.com

🚀 samlocal コマンドを準備する

今回は AWS SAM (AWS CloudFormation) と LocalStack を組み合わせるため,LocalStack AWS CLI (awslocal コマンド) に加えて AWS SAM CLI for LocalStack (samlocal コマンド) をセットアップしておく.samlocal --version コマンドを実行して,通常の sam コマンドにバイパスされていれば OK❗️

$ pip install aws-sam-cli-local

$ samlocal --version
SAM CLI, version 1.81.0

github.com

🚀 LocalStack にデプロイする

次はさっそく LocalStack にデプロイする.AWS SAM CLI を使ったことがあれば「今まで通り」と言えば伝わると思う.sam コマンドではなく samlocal コマンドに置き換えて,あとは同じく builddeploy を実行する❗️すると samlocal コマンドによって,LocalStack にデプロイできる.

samlocal deploy コマンドの実行結果をできる限りそのまま載せておく.見慣れたログではあるけど,最後の arn:aws:cloudformation:ap-northeast-1:000000000000:changeSet/samcli-deploy1682430872/b79df315 を見て「ああ!LocalStack にデプロイしてたんだ!」と気付くレベル✌️

  • samconfig.toml を作る
  • AWS SAM 専用の Amazon S3 バケットを作る
    • 今回だと aws-sam-cli-managed-default-samclisourcebucket-055e11c4
  • AWS リソースを作る
    • AWS::SQS::Queue
    • AWS::Lambda::Function
    • AWS::IAM::Role
    • AWS::Lambda::EventSourceMapping
$ export DEFAULT_REGION=ap-northeast-1

$ samlocal build

$ samlocal deploy --guided

Configuring SAM deploy
======================

    Looking for config file [samconfig.toml] :  Not found

    Setting default arguments for 'sam deploy'
    =========================================
    Stack Name [sam-app]: sandbox-app
    AWS Region [ap-northeast-1]:
    #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
    Confirm changes before deploy [y/N]:
    #SAM needs permission to be able to create roles to connect to the resources in your template
    Allow SAM CLI IAM role creation [Y/n]:
    #Preserves the state of previously provisioned resources when an operation fails
    Disable rollback [y/N]:
    Save arguments to configuration file [Y/n]:
    SAM configuration file [samconfig.toml]:
    SAM configuration environment [default]:

    Looking for resources needed for deployment:
    Creating the required resources...
    Successfully created!

    Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-055e11c4
    A different default S3 bucket can be set in samconfig.toml and auto resolution of buckets turned off by setting resolve_s3=False

    Saved arguments to config file
    Running 'sam deploy' for future deployments will use the parameters saved above.
    The above parameters can be changed by modifying samconfig.toml
    Learn more about samconfig.toml syntax at
    https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html

    Uploading to sandbox-app/62f50535d41ccb37bda86914d0d680f3  405 / 405  (100.00%)

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

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

    Uploading to sandbox-app/12ff669083061914f8a1286e9c315653.template  781 / 781  (100.00%)


Waiting for changeset to be created..

CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                                                           LogicalResourceId                                                   ResourceType                                                        Replacement
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add                                                               Queue                                                               AWS::SQS::Queue                                                     N/A
+ Add                                                               Function                                                            AWS::Lambda::Function                                               N/A
+ Add                                                               FunctionRole                                                        AWS::IAM::Role                                                      N/A
+ Add                                                               FunctionSqsEvent                                                    AWS::Lambda::EventSourceMapping                                     N/A
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


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


2023-04-25 00:00:00 - Waiting for stack create/update to complete

CloudFormation events from stack operations (refresh every 5.0 seconds)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                                                      ResourceType                                                        LogicalResourceId                                                   ResourceStatusReason
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_COMPLETE                                                     AWS::SQS::Queue                                                     Queue                                                               -
CREATE_COMPLETE                                                     AWS::IAM::Role                                                      FunctionRole                                                        -
CREATE_COMPLETE                                                     AWS::Lambda::Function                                               Function                                                            -
CREATE_COMPLETE                                                     AWS::Lambda::EventSourceMapping                                     FunctionSqsEvent                                                    -
CREATE_COMPLETE                                                     AWS::CloudFormation::Stack                                          sandbox-app                                                         -
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Successfully created/updated stack - sandbox-app in ap-northeast-1

🚀 デプロイ結果を確認する

awslocal コマンドを使って LocalStack にデプロイされた AWS リソースを確認する(日付は適当にマスキングしてある).

Amazon SQS キュー

$ awslocal sqs list-queues
{
    "QueueUrls": [
        "http://localhost:4566/000000000000/kakakakakku-queue"
    ]
}

AWS Lambda 関数

$ awslocal lambda list-functions
{
    "Functions": [
        {
            "FunctionName": "kakakakakku-function",
            "FunctionArn": "arn:aws:lambda:ap-northeast-1:000000000000:function:kakakakakku-function",
            "Runtime": "python3.9",
            "Role": "arn:aws:iam::000000000000:role/sandbox-app-FunctionRole-c5268ca9",
            "Handler": "app.lambda_handler",
            "CodeSize": 405,
            "Description": "",
            "Timeout": 3,
            "MemorySize": 128,
            "LastModified": "2023-04-25T00:00:00.000000+09:00",
            "CodeSha256": "fpUHNomors6Sbn3Ii0zpVUYGBpzxbmxSXB950QZmisg=",
            "Version": "$LATEST",
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "ee14fbd2-1412-431b-b615-7c87676f4330",
            "PackageType": "Zip",
            "Architectures": [
                "x86_64"
            ],
            "EphemeralStorage": {
                "Size": 512
            },
            "SnapStart": {
                "ApplyOn": "None",
                "OptimizationStatus": "Off"
            }
        }
    ]
}

AWS Lambda 関数(イベントソースマッピング)

$ awslocal lambda list-event-source-mappings
{
    "EventSourceMappings": [
        {
            "UUID": "98e14881-4802-4e3d-be48-ffdf6766a455",
            "BatchSize": 10,
            "MaximumBatchingWindowInSeconds": 0,
            "EventSourceArn": "arn:aws:sqs:ap-northeast-1:000000000000:kakakakakku-queue",
            "FunctionArn": "arn:aws:lambda:ap-northeast-1:000000000000:function:kakakakakku-function",
            "LastModified": "2023-04-25T00:00:00.000000+09:00",
            "State": "Enabled",
            "StateTransitionReason": "USER_INITIATED",
            "FunctionResponseTypes": []
        }
    ]
}

Amazon S3 バケット

$ awslocal s3api list-buckets
{
    "Buckets": [
        {
            "Name": "aws-sam-cli-managed-default-samclisourcebucket-055e11c4",
            "CreationDate": "2023-04-25T00:00:00+00:00"
        },
        {
            "Name": "awslambda-ap-northeast-1-tasks",
            "CreationDate": "2023-04-25T00:00:00+00:00"
        }
    ],
    "Owner": {
        "DisplayName": "webfile",
        "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
    }
}

Amazon CloudWatch Logs ロググループ

Amazon CloudWatch Logs ロググループは AWS SAM (AWS CloudFormation) で構築していないので1個もなくて OK👌この後 AWS Lambda 関数を実行してログを確認するため,実行前の状況を確認しておきたかった.

$ awslocal logs describe-log-groups
{
    "logGroups": []
}

🚀 動作確認をする

次は awslocal コマンドを使って Amazon SQS キューにメッセージを送信して,AWS Lambda 関数のイベントソースマッピングの動作確認をする.

$ MESSAGE='{ "my-key": "my-value" }'
$ awslocal sqs send-message --queue-url http://localhost:4566/000000000000/kakakakakku-queue --message-body ${MESSAGE}
{
    "MD5OfMessageBody": "3234172c8554dd70c6b2dbaac5acf11e",
    "MessageId": "678a5cd4-8849-4969-bbd7-0b9df93703d3"
}

すると Amazon CloudWatch Logs ロググループに "{ \"my-key\": \"my-value\" }" というログを確認できて,期待通り AWS Lambda 関数が実行されたことがわかる❗️

$ awslocal logs describe-log-groups
{
    "logGroups": [
        {
            "logGroupName": "/aws/lambda/kakakakakku-function",
            "creationTime": 1682431237731,
            "metricFilterCount": 0,
            "arn": "arn:aws:logs:ap-northeast-1:000000000000:log-group:/aws/lambda/kakakakakku-function:*",
            "storedBytes": 286
        }
    ]
}

$ awslocal logs describe-log-streams --log-group-name /aws/lambda/kakakakakku-function
{
    "logStreams": [
        {
            "logStreamName": "2023/04/25/[$LATEST]fe281c36f2654eb40cce1a30fbcfd8eb",
            "creationTime": 1682431237736,
            "firstEventTimestamp": 1682431237714,
            "lastEventTimestamp": 1682431237728,
            "lastIngestionTime": 1682431237742,
            "uploadSequenceToken": "1",
            "arn": "arn:aws:logs:ap-northeast-1:000000000000:log-group:/aws/lambda/kakakakakku-function:log-stream:2023/04/25/[$LATEST]fe281c36f2654eb40cce1a30fbcfd8eb",
            "storedBytes": 286
        }
    ]
}

$ awslocal logs get-log-events --log-group-name /aws/lambda/kakakakakku-function --log-stream-name '2023/04/25/[$LATEST]fe281c36f2654eb40cce1a30fbcfd8eb'
{
    "events": [
        {
            "timestamp": 1682431237714,
            "message": "START RequestId: 93a8f37b-5d8b-4c96-81fc-4e7bc3d3239e Version: $LATEST",
            "ingestionTime": 1682431237742
        },
        {
            "timestamp": 1682431237718,
            "message": "{ \"my-key\": \"my-value\" }",
            "ingestionTime": 1682431237742
        },
        {
            "timestamp": 1682431237723,
            "message": "END RequestId: 93a8f37b-5d8b-4c96-81fc-4e7bc3d3239e",
            "ingestionTime": 1682431237742
        },
        {
            "timestamp": 1682431237728,
            "message": "REPORT RequestId: 93a8f37b-5d8b-4c96-81fc-4e7bc3d3239e\tDuration: 13.08 ms\tBilled Duration: 14 ms\tMemory Size: 128 MB\tMax Memory Used: 128 MB\t",
            "ingestionTime": 1682431237742
        }
    ],
    "nextForwardToken": "f/00000000000000000000000000000000000000000000000000000003",
    "nextBackwardToken": "b/00000000000000000000000000000000000000000000000000000000"
}

もちろん AWS CloudFormation スタック sandbox-app もできあがっている✌️

$ awslocal cloudformation list-stacks
{
    "StackSummaries": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:000000000000:stack/aws-sam-cli-managed-default/5f4cfa71",
            "StackName": "aws-sam-cli-managed-default",
            "CreationTime": "2023-04-25T00:00:00.000000+00:00",
            "LastUpdatedTime": "2023-04-25T00:00:00.000000+00:00",
            "StackStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        },
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:000000000000:stack/sandbox-app/d0a1ffad",
            "StackName": "sandbox-app",
            "CreationTime": "2023-04-25T00:00:00.000000+00:00",
            "LastUpdatedTime": "2023-04-25T00:00:00.000000+00:00",
            "StackStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

🚀 お掃除

使わなくなったら awslocal cloudformation delete-stack コマンドで AWS CloudFormation スタックを削除できる.ちゃんと削除後は StackStatus が DELETE_COMPLETE になっている.

$ awslocal cloudformation delete-stack --stack-name sandbox-app

$ awslocal cloudformation list-stacks
{
    "StackSummaries": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:000000000000:stack/aws-sam-cli-managed-default/5f4cfa71",
            "StackName": "aws-sam-cli-managed-default",
            "CreationTime": "2023-04-25T00:00:00.000000+00:00",
            "LastUpdatedTime": "2023-04-25T00:00:00.000000+00:00",
            "StackStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        },
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:000000000000:stack/sandbox-app/d0a1ffad",
            "StackName": "sandbox-app",
            "CreationTime": "2023-04-25T00:00:00.000000+00:00",
            "LastUpdatedTime": "2023-04-25T00:00:00.000000+00:00",
            "DeletionTime": "2023-04-25T00:00:00.000000+00:00",
            "StackStatus": "DELETE_COMPLETE",
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

$ awslocal lambda list-functions
{
    "Functions": []
}

🚀 まとめ

Amazon SQS キューと AWS Lambda 関数の組み合わせなど「イベントソースマッピング(トリガー設定)」の動作確認をローカル環境で行うために samlocal コマンドを使って LocalStack にデプロイしてみた.最終的には LocalStack でサポートされている API 次第ではあるけど,今回の簡単なシナリオでは期待通りに動いた❗️

ちなみに LocalStack を使うときは,基本 awslocal コマンドを使うけど,LocalStack Web Application の Resource Browser を使うと,一部の AWS リソースであれば画面から確認できて便利なときもある❗️最初はウェブサイトからどうやってローカル環境の LocalStack にアクセスしてるんだろ?と疑問だったけど,ちょっと調べてみたら https://localhost.localstack.cloud:4566/ に対して API を呼び出す仕組みになっていた💨

docs.localstack.cloud

\( 'ω')/ あ〜 LocalStack 便利〜

LocalStack を使って SSM Parameter Store をローカル環境で操作する

AWS Systems Manager Parameter Store を使ったアプリケーションを開発するときに,ローカル環境ではできる限り AWS アカウントに依存しないようにしたく,LocalStack を使う機会があったので調査ログをまとめておく.結果的に AWS Systems Manager Parameter Store のほとんどの API がサポートされていて良かった❗️CI/CD パイプラインで自動テストするときにも使える〜

github.com

LocalStack のセットアップ方法は複数あるからドキュメント参照で📝最近だと Docker Desktop の Extensions にも対応していて,1番簡単かも👌今回は Extensions を使った.

docs.localstack.cloud

AWS Systems Manager Parameter Store API

LocalStack では AWS Systems Manager Parameter Store 関連の API がほとんどサポートされている❗️

  • DeleteParameter
  • DeleteParameters
  • DescribeParameters
  • GetParameter
  • GetParameterHistory
  • GetParameters
  • GetParametersByPath
  • LabelParameterVersion
  • PutParameter

(現時点だと)唯一 UnlabelParameterVersion だけはサポートされてなく,使う場合は要注意〜

  • UnlabelParameterVersion

docs.localstack.cloud

PutParameter

AWS CLI を実行するときに --endpoint-url http://localhost:4566 オプションを付けると LocalStack を操作できる.AWS Systems Manager Parameter Store の PutParameter を AWS CLI で実行する場合は以下のようになる.もちろん SecureString も使える👌(暗号化自体はされないけど)

$ aws --endpoint-url http://localhost:4566 ssm put-parameter --name '/parameter/string' --type 'String' --value 'value/String'
{
    "Version": 1
}

$ aws --endpoint-url http://localhost:4566 ssm put-parameter --name '/parameter/secure-string' --type 'SecureString' --value 'value/SecureString'
{
    "Version": 1
}

さらに LocalStack AWS CLI (awslocal コマンド) を使うと --endpoint-url オプションを付けなくても実行できて,もっとシンプルになる.本記事では awslocal コマンドを使った例を載せていく.

$ export DEFAULT_REGION=ap-northeast-1
$ awslocal ssm put-parameter --name '/parameter/awslocal' --type 'String' --value 'value/awslocal'
{
    "Version": 1
}

github.com

GetParameter

$ awslocal ssm get-parameter --name '/parameter/string'
{
    "Parameter": {
        "Name": "/parameter/string",
        "Type": "String",
        "Value": "value/String",
        "Version": 1,
        "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
        "ARN": "arn:aws:ssm:ap-northeast-1:000000000000:parameter/parameter/string",
        "DataType": "text"
    }
}

DeleteParameter

$ awslocal ssm delete-parameter --name '/parameter/string'
$ awslocal ssm delete-parameter --name '/parameter/secure-string'

DescribeParameters

今度はパラメータを3個追加してから DescribeParameters を実行する.

$ awslocal ssm put-parameter --name '/service1/parameter1' --type 'String' --value 'value1'
$ awslocal ssm put-parameter --name '/service1/parameter2' --type 'String' --value 'value2'
$ awslocal ssm put-parameter --name '/service1/parameter3' --type 'String' --value 'value3'

$ awslocal ssm describe-parameters
{
    "Parameters": [
        {
            "Name": "/service1/parameter1",
            "Type": "String",
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "LastModifiedUser": "N/A",
            "Version": 1,
            "DataType": "text"
        },
        {
            "Name": "/service1/parameter2",
            "Type": "String",
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "LastModifiedUser": "N/A",
            "Version": 1,
            "DataType": "text"
        },
        {
            "Name": "/service1/parameter3",
            "Type": "String",
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "LastModifiedUser": "N/A",
            "Version": 1,
            "DataType": "text"
        }
    ]
}

GetParameters

$ awslocal ssm get-parameters --names /service1/parameter1 /service1/parameter2
{
    "Parameters": [
        {
            "Name": "/service1/parameter1",
            "Type": "String",
            "Value": "value1",
            "Version": 1,
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "ARN": "arn:aws:ssm:ap-northeast-1:000000000000:parameter/service1/parameter1",
            "DataType": "text"
        },
        {
            "Name": "/service1/parameter2",
            "Type": "String",
            "Value": "value2",
            "Version": 1,
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "ARN": "arn:aws:ssm:ap-northeast-1:000000000000:parameter/service1/parameter2",
            "DataType": "text"
        }
    ],
    "InvalidParameters": []
}

GetParametersByPath

$ awslocal ssm get-parameters-by-path --path /service1/
{
    "Parameters": [
        {
            "Name": "/service1/parameter1",
            "Type": "String",
            "Value": "value1",
            "Version": 1,
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "ARN": "arn:aws:ssm:ap-northeast-1:000000000000:parameter/service1/parameter1",
            "DataType": "text"
        },
        {
            "Name": "/service1/parameter2",
            "Type": "String",
            "Value": "value2",
            "Version": 1,
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "ARN": "arn:aws:ssm:ap-northeast-1:000000000000:parameter/service1/parameter2",
            "DataType": "text"
        },
        {
            "Name": "/service1/parameter3",
            "Type": "String",
            "Value": "value3",
            "Version": 1,
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "ARN": "arn:aws:ssm:ap-northeast-1:000000000000:parameter/service1/parameter3",
            "DataType": "text"
        }
    ]
}

DeleteParameters

$ awslocal ssm delete-parameters --names /service1/parameter1 /service1/parameter2 /service1/parameter3
{
    "DeletedParameters": [
        "/service1/parameter1",
        "/service1/parameter2",
        "/service1/parameter3"
    ],
    "InvalidParameters": []
}

GetParameterHistory

ssm put-parameter --overwrite コマンドを使ってパラメータを更新してから GetParameterHistory を実行する.

$ awslocal ssm put-parameter --name 'parameter1' --type 'String' --value 'version1'
{
    "Version": 1
}

$ awslocal ssm put-parameter --name 'parameter1' --type 'String' --value 'version2' --overwrite
{
    "Version": 2
}

$ awslocal ssm put-parameter --name 'parameter1' --type 'String' --value 'version3' --overwrite
{
    "Version": 3
}

$ awslocal ssm get-parameter-history --name 'parameter1'
{
    "Parameters": [
        {
            "Name": "parameter1",
            "Type": "String",
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "LastModifiedUser": "N/A",
            "Value": "version1",
            "Version": 1,
            "Labels": [],
            "DataType": "text"
        },
        {
            "Name": "parameter1",
            "Type": "String",
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "LastModifiedUser": "N/A",
            "Value": "version2",
            "Version": 2,
            "Labels": [],
            "DataType": "text"
        },
        {
            "Name": "parameter1",
            "Type": "String",
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "LastModifiedUser": "N/A",
            "Value": "version3",
            "Version": 3,
            "Labels": [],
            "DataType": "text"
        }
    ]
}

LabelParameterVersion

最後は LabelParameterVersion を実行してパラメータに verified ラベルを追加する例を載せる.パラメータ parameter1 の最新バージョンにのみ verified ラベルが追加されている.

$ awslocal ssm label-parameter-version --name parameter1 --labels verified
{
    "InvalidLabels": [],
    "ParameterVersion": 3
}

$ awslocal ssm get-parameter-history --name 'parameter1'
{
    "Parameters": [
        {
            "Name": "parameter1",
            "Type": "String",
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "LastModifiedUser": "N/A",
            "Value": "version1",
            "Version": 1,
            "Labels": [],
            "DataType": "text"
        },
        {
            "Name": "parameter1",
            "Type": "String",
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "LastModifiedUser": "N/A",
            "Value": "version2",
            "Version": 2,
            "Labels": [],
            "DataType": "text"
        },
        {
            "Name": "parameter1",
            "Type": "String",
            "LastModifiedDate": "2023-04-24T00:00:00.000000+09:00",
            "LastModifiedUser": "N/A",
            "Value": "version3",
            "Version": 3,
            "Labels": [
                "verified"
            ],
            "DataType": "text"
        }
    ]
}

まとめ

LocalStack でサポートされている AWS Systems Manager Parameter Store 関連の API を調査した.結果的にほとんどの API がサポートされていて良かった❗️LocalStack をどんどん使っていくぞ〜🚀

AWS SAM で Amazon API Gateway の Usage Plan を作るときに API Stage not found と出たら

AWS SAM で Amazon API Gateway (REST) の API Key と Usage Plan を構築するとき,実行時に以下のように API Stage not found というエラーが出ることがある.これはよくハマる問題で,AWS SAM 関連の GitHub を見ると issue も出ている.今回は解決策と注意点を紹介しようと思う👌

Resource handler returned message: "API Stage not found: 22efb50tt2:v1 (Service: ApiGateway, Status Code: 404, Request ID: 2fe0c9bf-6d15-4438-b379-52c40bb66a80)" (RequestToken: d4727ca1-4d1c-6c68-0cda-478e6d499bd6, HandlerErrorCode: NotFound)

TL;DR

最初に解決策を書いておく❗️AWS SAM テンプレートの AWS::ApiGateway::UsagePlan に Amazon API Gateway Stage に対する DependsOn を追加して,Amazon API Gateway Stage の構築完了を明示的に待つ必要がある.

論理名は2種類ある!?

以下の例では DependsOn に指定している論理名は ApiGatewayv1Stage になっているけど,これは <api‑LogicalId><stage‑name>Stage という命名規則に従っている📝なお,正確には以下のように Stage 名の指定によって論理名も変わってしまうため注意する🌀

  • <api‑LogicalId><stage‑name>Stage(Stage 名をベタ書きする場合)
  • <api‑LogicalId>Stage(Stage 名を Parameters から取得する場合)
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      Name: sandbox-api-gateway
      StageName: v1
      DefinitionUri: ./openapi/openapi.yaml

  ApiGatewayKey:
    Type: AWS::ApiGateway::ApiKey

  ApiGatewayUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    Properties:
      ApiStages:
        - ApiId: !Ref ApiGateway
          Stage: v1
    DependsOn:
      - ApiGatewayv1Stage

  ApiGatewayUsagePlanKey:
    Type: AWS::ApiGateway::UsagePlanKey
    Properties:
      KeyId: !Ref ApiGatewayKey
      KeyType: API_KEY
      UsagePlanId: !Ref ApiGatewayUsagePlan

検証(Stage 名をベタ書きする場合)

以下のように AWS SAM で Amazon API Gateway (REST) 関連リソースを構築する.今回 Stage 名 v1 は2箇所にベタ書きしてある.このままデプロイすると API Stage not found というエラーが出ることがある.

  • AWS::Serverless::Api
  • AWS::ApiGateway::ApiKey
  • AWS::ApiGateway::UsagePlan
  • AWS::ApiGateway::UsagePlanKey
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      Name: sandbox-api-gateway
      StageName: v1
      DefinitionUri: ./openapi/openapi.yaml

  ApiGatewayKey:
    Type: AWS::ApiGateway::ApiKey

  ApiGatewayUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    Properties:
      ApiStages:
        - ApiId: !Ref ApiGateway
          Stage: v1

  ApiGatewayUsagePlanKey:
    Type: AWS::ApiGateway::UsagePlanKey
    Properties:
      KeyId: !Ref ApiGatewayKey
      KeyType: API_KEY
      UsagePlanId: !Ref ApiGatewayUsagePlan

実は AWS SAM で AWS::Serverless::Api を使って Amazon API Gateway (REST) を構築しようとすると,内部的に AWS::ApiGateway::StageAWS::ApiGateway::Deployment も構築される.ブラックボックスな仕様ではなく,ちゃんと以下のドキュメントに載っている.

docs.aws.amazon.com

そして,AWS::ApiGateway::UsagePlan を構築するときに,依存する AWS::ApiGateway::Stage の構築が間に合わず API Stage not found というエラーが出てしまう.よって,内部的に構築される AWS::ApiGateway::Stage の論理名の命名規則に従って <api‑LogicalId><stage‑name>Stage に対する DependsOn を追加する必要がある.以下の AWS SAM テンプレートは期待した通りに構築できる❗️

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      Name: sandbox-api-gateway
      StageName: v1
      DefinitionUri: ./openapi/openapi.yaml

  ApiGatewayKey:
    Type: AWS::ApiGateway::ApiKey

  ApiGatewayUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    Properties:
      ApiStages:
        - ApiId: !Ref ApiGateway
          Stage: v1
    DependsOn:
      - ApiGatewayv1Stage

  ApiGatewayUsagePlanKey:
    Type: AWS::ApiGateway::UsagePlanKey
    Properties:
      KeyId: !Ref ApiGatewayKey
      KeyType: API_KEY
      UsagePlanId: !Ref ApiGatewayUsagePlan

実際に aws cloudformation describe-stack-resources コマンドで Amazon API Gateway Stage を確認すると,確かに論理名 ApiGatewayv1Stage で構築されている🙂

$ aws cloudformation describe-stack-resources --stack-name kakakakakku-blog-apigw-apikey | jq '.StackResources[] | select(.ResourceType == "AWS::ApiGateway::Stage")'
{
  "StackName": "kakakakakku-blog-apigw-apikey",
  "StackId": "arn:aws:cloudformation:ap-northeast-1:000000000000:stack/kakakakakku-blog-apigw-apikey/082450d0-de49-11ed-a0dc-0acc4753a47f",
  "LogicalResourceId": "ApiGatewayv1Stage",
  "PhysicalResourceId": "v1",
  "ResourceType": "AWS::ApiGateway::Stage",
  "Timestamp": "2023-04-19T00:00:00.000000+00:00",
  "ResourceStatus": "CREATE_COMPLETE",
  "DriftInformation": {
    "StackResourceDriftStatus": "NOT_CHECKED"
  }
}

検証(Stage 名を Parameters から取得する場合)

しかし Stage 名 v1 をベタ書きするのではなく,AWS CloudFormation の Parameters から取得しようとすると,先ほど紹介した AWS::ApiGateway::Stage の論理名の命名規則が <api‑LogicalId>Stage に変わってしまうという注意点がある(ドキュメントにも書かれていないように思う...💧).よって,以下のように論理名 ApiGatewayStage に対する DependsOn を追加すると AWS SAM テンプレートは期待した通りに構築できる❗️

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Parameters:
  StageName:
    Type: String
    Default: v1

Resources:
  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      Name: sandbox-api-gateway
      StageName: !Ref StageName
      DefinitionUri: ./openapi/openapi.yaml

  ApiGatewayKey:
    Type: AWS::ApiGateway::ApiKey

  ApiGatewayUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    Properties:
      ApiStages:
        - ApiId: !Ref ApiGateway
          Stage: !Ref StageName
    DependsOn:
      - ApiGatewayStage

  ApiGatewayUsagePlanKey:
    Type: AWS::ApiGateway::UsagePlanKey
    Properties:
      KeyId: !Ref ApiGatewayKey
      KeyType: API_KEY
      UsagePlanId: !Ref ApiGatewayUsagePlan

もう一度 aws cloudformation describe-stack-resources コマンドで Amazon API Gateway Stage を確認すると,確かに論理名は ApiGatewayStage で構築されている🙂

$ aws cloudformation describe-stack-resources --stack-name kakakakakku-blog-apigw-apikey | jq '.StackResources[] | select(.ResourceType == "AWS::ApiGateway::Stage")'
{
  "StackName": "kakakakakku-blog-apigw-apikey",
  "StackId": "arn:aws:cloudformation:ap-northeast-1:000000000000:stack/kakakakakku-blog-apigw-apikey/26030900-de4b-11ed-ab51-0e3a83b37d3b",
  "LogicalResourceId": "ApiGatewayStage",
  "PhysicalResourceId": "v1",
  "ResourceType": "AWS::ApiGateway::Stage",
  "Timestamp": "2023-04-19T00:00:00.000000+00:00",
  "ResourceStatus": "CREATE_COMPLETE",
  "DriftInformation": {
    "StackResourceDriftStatus": "NOT_CHECKED"
  }
}

まとめ

AWS SAM で Amazon API Gateway (REST) の API Key と Usage Plan を構築するときに出る可能性のある API Stage not found というエラーに関してまとめてみた.論理名の命名規則が2種類あるところは注意❗️

参考になれば💡

github.com

おまけ

せっかく AWS SAM で AWS::Serverless::Api を使ってるなら Auth プロパティを使って API Key と Usage Plan を構築すればもっと簡単じゃん❓という話もあると思うけど,AWS::Serverless::ApiDefinitionUri で OpenAPI ファイルを指定している場合には Auth プロパティが使えないという仕様になっている💧

$ sam validate -t template.yaml
Error: [InvalidResourceException('ApiGateway', "Auth works only with inline Swagger specified in 'DefinitionBody' property.")] ('ApiGateway', "Auth works only with inline Swagger specified in 'DefinitionBody' property.")

関連 issue

github.com

github.com