Cloud Formation - AWS SAM
パラメータ管理方法
背景
- 関わっている案件でCloudFormation(Cfn)テンプレートを使ってAWSリソースの管理を行っている。
- 最近管理対象のリソース(スタック)が増え、手入力で行っていたパラメータの設定を、スタック間で受け渡しを行えるようにしたくなってきた。
- スタック間でのパラメータの受け渡し方法は複数あるようなので、それぞれの利用方法とメリット・デメリットを調べ比較してみた。
比較
1. Systems Manager Parameter Store(SSM) / Secrets Manager(SM) を利用しパラメーターとして利用する
- 利用方法
- Systems Manager Parameter Store / Secrets Managerに手動でパラメータやシークレット情報を登録する。
- CfnテンプレートのParametersセクションでパラメータもしくはシークレットをデフォルト値として参照し設定する。
- CfnテンプレートのResourcesセクションにParameterリソースを記載し、任意のリソースの設定値をSSMやSMに登録し、他テンプレートから参照できるようにする。
- メリット
- パラメータの値をセキュアかつテンプレート間で疎結合に管理ができる。
- リソースの削除やパラメータやシークレットの設定変更がしやすい。
- テンプレート中の可読性が高い。
- デメリット
- テンプレートの記述量が増える。
- Systems Manager Parameter Store / Secrets Managerを利用する必要があり、コストがかかる場合がある。
- コード例
入力
Parameters
TableName: # SSMパラメータストアの型でパラメータとして設定
Type: AWS::SSM::Parameter::Value<String>
Default: '/dev/hoge/dynamodb/user/table-name'
Resources:
Function:
....
Environment:
Variables:
TABLE_NAME: !Ref TableName # 使いたいところでパラメータを参照
出力
UserTable: # 対象リソースを定義
Type: AWS::DynamoDB::Table
Properties:
TableName: dev-hoge-user
TableNameParameter: # 対象リソースの属性をSSMパラメータストアに設定
Type: AWS::SSM::Parameter
Properties:
Name: '/dev/hoge/dynamodb/user/table-name'
Type: String
Value: !Ref UserTable
2. Systems Manager Parameter Store / Secrets Manager を利用し動的参照を行う
- メリット
- ほぼ1と同じ。Parametersの記載が比較的シンプル書ける。
- デメリット
- ほぼ1と同じ。
- コード例
入力
Parameters
TableName: # パラメータとして設定
Type: String
Default: '{{resolve:ssm:/dev/hoge/dynamodb/user/table-name}}' # 動的参照を利用
Resources:
Function:
....
Environment:
Variables:
TABLE_NAME: !Ref TableName # 使いたいところでパラメータを参照
出力
UserTable: # 対象リソースを定義
Type: AWS::DynamoDB::Table
Properties:
TableName: dev-hoge-user
TableNameParameter: # 対象リソースの属性をSSMパラメータストアに設定
Type: AWS::SSM::Parameter
Properties:
Name: '/dev/hoge/dynamodb/user/table-name'
Type: String
Value: !Ref UserTable
3. クロススタック参照を行う
- メリット
- コードの記述がシンプルで記述量が少なくて済む。
- デメリット
- リソース同士が密結合になってしまう。
- 参照しているスタックがあると参照元のスタックで変更や削除ができなくなる。
- アカウント内、リージョン内で重複しないようにパラメータ名を考え管理する必要がある。
- 機密性の高い情報を格納できない。一部のシークレット等は別途SSMやSMで管理する必要がある。
- リソース同士が密結合になってしまう。
- コード例
入力
Resources:
Function:
....
Environment:
Variables:
TABLE_NAME: !ImportValue UserTableName # 使いたいところでパラメータを参照
出力
UserTable: # 対象リソースを定義
Type: AWS::DynamoDB::Table
Properties:
TableName: dev-hoge-user
Outputs:
UserTableName:
Value: !Ref UserTable
Export:
Name: UserTableName # スタック全体で一意な値を設定
結論
- 基本的に2. 動的参照を使う。
- 業務利用されるシステムや中〜大規模のプロジェクトでスタック数が多い場合は、2. 動的参照を使うのが良い。
- パラメータやシークレットをセキュアに管理できて、かつスタック間も疎結合にできるため管理がしやすいため。
- 1.か2.かという問題だが、AWS公式やネットの情報を見る限り、最近は動的参照の利用が推奨されていそう。
- 個人開発レベルで管理対象のスタックが少なく、機密情報も特にない場合は 3.クロススタック参照を使うと管理が楽で良さそう。
参考
SSMパラメータストアでの参照
動的参照
Outputs - ImportValue
CloudFormation
AWS SAM +Eventbridge で Lambdaを定期実行する方法
背景
- EventBridgeを使ってLambdaを定期実行するときに、CloudFormationテンプレートでは主に2つの書き方がある。
- それぞれのメリット・デメリットと記述方法を比較した。
比較
- 組み合わせテンプレートの使用
Lambdaの定義中にEventBridgeRuleの定義を含める
-
メリット
- コードの記述量が少なく、管理するテンプレートも減らせる。
- YAML では、イベントパターンを定義するだけでよく、AWS SAMが必要なアクセス許可を持つ IAM ロールを自動的に作成してくれる。
-
デメリット
- LambdaとEventBridgeのコードが密結合になっているため、別々に管理できずリソース(特にEventBridgeRule)を認識しずらい。
- IAMロールなど細かい制御ができない。
- 一つのルールでは一つのLambdaしか指定できない。(ベストプラクティス)
Resources:
HelloWorldFunction: # Lambdaの定義
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: hello-world
Runtime: python3.10
Architectures:
- x86_64
Events: # Lambdaの定義内でEventBridgeRuleを定義
ScheduledFunction:
Type: ScheduleV2
Properties:
ScheduleExpression: cron(0/5 * * * ? *) # 5分毎にトリガー
ScheduleExpressionTimezone: "Asia/Tokyo" # 日本のタイムゾーンを考慮
State: ENABLED
Name: dev-hoge-hello-schedule-lambda
Description: hello schedule
- 分離テンプレート
Lambdaの定義とEventBridgeRuleの定義を分離する
-
メリット
- LambdaとEventBridgeのコードが疎結合になっているため、リソース(EventBridgeRule)の数を把握しやすく、EventBridgeのスタックに統合したりと管理しやすい。
- 一つのルールから複数のLambdaを指定できる。
-AWS SAM テンプレートの外部のリソースに接続している場合でも対応できる。
-
デメリット
- コードの記述量と管理するテンプレートが増える。
- 必要なアクセス許可を持つ IAM ロールの定義を自分で行う必要がある。
Resources:
HelloWorldFunction: # Lambdaの定義
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: hello-world
Runtime: python3.10
Architectures:
- x86_64
EventBridgeRule: # Lambdaの定義とは別にEventBridgeRuleを定義
Type: AWS::Events::Rule
Properties:
EventBusName: default
Name: dev-hoge-hello-schedule-lambda
Description: hello schedule
ScheduleExpression: cron(0/5 * * * ? *) # 5分毎にトリガー
ScheduleExpressionTimezone: "Asia/Tokyo" # 日本のタイムゾーンを考慮
State: ENABLED
Targets:
- Arn: !GetAtt LambdaFunction.Arn # トリガー対象のLambdaのArnを指定
Id: LambdaFunction # トリガー対象のLambdaのidを指定
PermissionForEventsToInvokeLambda: # EventBridgeRuleからLambdaをトリガーするためのPermissionを定義
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref HelloWorldFunction
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt 'EventBridgeRule.Arn'
結論
- 1 つのルールによって 1 つの Lambda 関数が呼び出される単純な統合では、組み合わせテンプレートがお勧め。EventBridgeRuleを一か所でまとめて管理したいという要望がなければこの方法がシンプルで良さそう。
- 複雑なルーティングロジックがある場合、または AWS SAM テンプレートの外部のリソースに接続している場合には、分離テンプレートの利用が推奨される。
補足
- 通常は"Events/Type"には"Schedule"を設定するが、タイムゾーンを考慮する場合は"Type: ScheduleV2"を設定し、"ScheduleExpression"にスケジュール設定、"ScheduleExpressionTimezone"に想定するタイムゾーンを記載する必要がある。
参考
AWS SAMの組み合わせテンプレートを利用して意気揚々とデプロイしたところ、EventBridgeのルール画面で探しても見つからない!となった。
CodeBuildのビルドログには正常に作成されていることが確認できていたため混乱していた。

EventBridgeの画面をよく見ると、サイドメニューに「スケジュール」という項目があり、クリックすると探していた設定が、、!

ここで、EventBridgeのスケジュールトリガーの設定は2つあることを知る。
- EventBridge Rule
- EventBridge Scheduler
SchedulerのほうはRuleで設定するよりも詳細な設定ができるよう。
タイムゾーンやリトライ、DLQ、暗号化など。

AWS SAMの"ScheduleV2"で作成されたリソースはSchedulerの方になるらしい。
したがって、先述していた比較には誤りがあって、組み込みテンプレートの方はScheduler、分離テンプレートの方はRuleを例にしていたため、後者の例を修正する必要がある。
また、Ruleの方には"ScheduleExpressionTimezon"は定義されていないため、タイムゾーン設定はできない。こちらもコード例の修正が必要。
正しくは、以下の通り。
Scheduler
リソースタイプは"AWS::Scheduler::Schedule"を使用する。
Type: AWS::Scheduler::Schedule
Properties:
Name: String
Description: String
ScheduleExpression: String
ScheduleExpressionTimezone: String
FlexibleTimeWindow:
MaximumWindowInMinutes: 60
Mode: "FLEXIBLE"
State: String
Target:
Arn:
!GetAtt LambdaFunction.Arn
RoleArn:
Fn::GetAtt:
- EventScheduleRole
- Arn GroupName: String
EventScheduleRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- scheduler.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: CallStepFunctions
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource:
- "*"
Rule
タイムゾーン設定を考えるとあまり使わないかもしれないが、一応修正版。
EventBridgeRule: # Lambdaの定義とは別にEventBridgeRuleを定義
Type: AWS::Events::Rule
Properties:
EventBusName: default
Name: dev-hoge-hello-schedule-lambda
Description: hello schedule
ScheduleExpression: cron(0/5 * * * ? *) # 5分毎にトリガー
State: ENABLED
Targets:
- Arn: !GetAtt LambdaFunction.Arn # トリガー対象のLambdaのArnを指定
Id: LambdaFunction # トリガー対象のLambdaのidを指定
参考
EventBridge Rule
EventBridge Scheduler
AWS SAMの使い方
組み込み関数
Cognito
APIGateway
Cognito Authorizer
CloudWatch
ParameterGroup
- 主にパラメータをグルーピングして、グループ内の順序を制御するための機能。
- CFnテンプレートのトップレベル(Parameters等のsiblings)の名前 Metadata に対して、AWS::CloudFormation::Interface を定義することにより実現する。
- ParameterLabelsでは、パラメータ名ではない自由なラベルをパラメータに対して定義できる。
- 入力時の可視性が上がり、毎回同じ順番で入力することになるので入力ミスも減ることにも繋がる。
- テンプレート自体の可読性の向上も期待できる。
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups: # パラメータグループ群
- # パラメータグループ1つ目
Label: # グループ名
default: VPC Configuration
Parameters: # グルーピングしたいパラメータ群
- VPCCIDR
- PrivateSubnetCIDR
- # パラメータグループ2つ目
Label:
default: EC2 Configuration
Parameters:
- Ec2ImageId
- Ec2InstanceType
- KeyPair
...
参考
スタック障害オプション
背景
CloudFormationでCodePipeline, CodeBuildを含むスタックを作成した後に、CodePipelineを実行するとビルドが成功しているにも関わらずCodePipelineとCodeBuildが削除されてしまった。
Cfnテンプレート上でCodePipelineとCodeBuildを削除するような記載はしていなかったため、原因がわからず詰まってしまった。
原因
CloudFormationのスタック障害オプションによるもの。
スタック障害オプション
プロビジョニングに失敗した場合、正常にプロビジョニングされたリソースを自動でロールバックせずに処理を一時停止することができる。
ロールバックを行う場合は、2つの方法があり、運用に合わせて選択できる。
停止した状態でトラブルシューティングを行い、変更内容を反映したうえで失敗時点からプロビジョニングを再開することができる。
スタックのデプロイと変更セットに対して障害オプションをプロビジョニングできる。
障害オプションが適用される対象のスタックステータスは"CREATE_FAILED" または" UPDATE_FAILED"。
ロールバック構成
ロールバック実行時に以下2つのどちらかのオプションを選択することができる。
- すべてのスタックリソースをロールバックする
- 正常・異常に関わらず、作成されたリソースをすべてロールバックする。
- 正常にプロビジョニングされたリソースの保持(デフォルト)
- 正常作成されたリソースは保持しつつ、失敗したリソースのみロールバックする。
"正常にプロビジョニングされたリソースの保持"の挙動
スタック作成
プロビジョニング失敗時は、正常に処理されたリソースはその状態を保持するが、失敗したリソースは次の更新オペレーションが行われるまで失敗した状態が維持される。
スタック更新・変更セット作成
変更セットオペレーション失敗時は、失敗したリソースを最後の安定した状態にロールバックしながら、正常にプロビジョニングされたリソースの状態を保持する。
失敗したリソースは UPDATE_FAILED 状態になる。
最後の安定状態が確認されていないリソースは、次のスタックオペレーション時に削除される。
まとめ
たくさんのリソースの作成が必要な時や、復旧を急いでいるときは、”正常にプロビジョニングされたリソースの保持”を選択すると素早く再試行を完了することができる。
依存関係が多かったり、既存リソースがある場合にプロビジョニングに失敗してしまうようなサービスが多い時には、無難に”すべてのスタックリソースをロールバックする”を選択するのが良さそう。
参考
Lambdaのトリガー設定(EventBridge Rule)
Cfnテンプレート・SAM
EventBridgeFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: handlers/
Handler: event-bridge.handleEvent
Runtime: nodejs12.x
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:GetObject
Resource:
- arn:aws:s3:::s3-trigger-compare/uploads/*
Events:
EventBridgeEvent:
Type: EventBridgeRule
Properties:
RuleName: example-rule
Pattern:
source:
- aws.s3
detail-type:
- AWS API Call via CloudTrail
detail:
eventSource:
- s3.amazonaws.com
eventName:
- CopyObject
- PutObject
- CompleteMultipartUpload
- RestoreObject
requestParameters:
bucketName:
- !Ref TriggerBucket
key:
- prefix: 'uploads/'
参考