Open11

Cloud Formation - AWS SAM

ピン留めされたアイテム
mocknmockn

パラメータ管理方法

背景

  • 関わっている案件で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パラメータストアでの参照
https://blog.serverworks.co.jp/cloudformation-ssm-ami-nest-stack

動的参照
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/dynamic-references.html
https://dev.classmethod.jp/articles/cfn-dynamic-references-support-latest/
https://www.tech-nn.com/cfn-ssmparameter-stack-ref/

Outputs - ImportValue
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html

CloudFormation
https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2023_CloudFormation-1_0731_v1.pdf

ピン留めされたアイテム
mocknmockn

AWS SAM +Eventbridge で Lambdaを定期実行する方法

背景

  • EventBridgeを使ってLambdaを定期実行するときに、CloudFormationテンプレートでは主に2つの書き方がある。
  • それぞれのメリット・デメリットと記述方法を比較した。

比較

  1. 組み合わせテンプレートの使用
    Lambdaの定義中にEventBridgeRuleの定義を含める
  • メリット

    • コードの記述量が少なく、管理するテンプレートも減らせる。
    • YAML では、イベントパターンを定義するだけでよく、AWS SAMが必要なアクセス許可を持つ IAM ロールを自動的に作成してくれる。
  • デメリット

    • LambdaとEventBridgeのコードが密結合になっているため、別々に管理できずリソース(特にEventBridgeRule)を認識しずらい。
    • IAMロールなど細かい制御ができない。
    • 一つのルールでは一つのLambdaしか指定できない。(ベストプラクティス)
template.yaml
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
  1. 分離テンプレート
    Lambdaの定義とEventBridgeRuleの定義を分離する
  • メリット

    • LambdaとEventBridgeのコードが疎結合になっているため、リソース(EventBridgeRule)の数を把握しやすく、EventBridgeのスタックに統合したりと管理しやすい。
    • 一つのルールから複数のLambdaを指定できる。
      -AWS SAM テンプレートの外部のリソースに接続している場合でも対応できる。
  • デメリット

    • コードの記述量と管理するテンプレートが増える。
    • 必要なアクセス許可を持つ IAM ロールの定義を自分で行う必要がある。
template.yaml
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"に想定するタイムゾーンを記載する必要がある。

参考

https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-use-sam.html
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-property-function-schedulev2.html
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-property-function-eventbridgerule.html
https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-cron-expressions.html
https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-rules-best-practices.html
https://zenn.dev/robon/articles/81f4cf35b3a32c
https://qiita.com/baku2san/items/2514ce485731ff7068c5

https://zenn.dev/nakam_aws/articles/cc37dcd8ab8792

mocknmockn

AWS SAMの組み合わせテンプレートを利用して意気揚々とデプロイしたところ、EventBridgeのルール画面で探しても見つからない!となった。
CodeBuildのビルドログには正常に作成されていることが確認できていたため混乱していた。

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

ここで、EventBridgeのスケジュールトリガーの設定は2つあることを知る。

  1. EventBridge Rule
  2. EventBridge Scheduler

SchedulerのほうはRuleで設定するよりも詳細な設定ができるよう。
タイムゾーンやリトライ、DLQ、暗号化など。

AWS SAMの"ScheduleV2"で作成されたリソースはSchedulerの方になるらしい。

したがって、先述していた比較には誤りがあって、組み込みテンプレートの方はScheduler、分離テンプレートの方はRuleを例にしていたため、後者の例を修正する必要がある。

また、Ruleの方には"ScheduleExpressionTimezon"は定義されていないため、タイムゾーン設定はできない。こちらもコード例の修正が必要。

正しくは、以下の通り。

Scheduler

リソースタイプは"AWS::Scheduler::Schedule"を使用する。

template.yaml
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を指定

参考
https://dev.classmethod.jp/articles/eventbridge-scheduler-and-event-bridge-rule-difference/

EventBridge Rule
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-events-rule.html

EventBridge Scheduler
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-scheduler-schedule.html

mocknmockn

ParameterGroup

  • 主にパラメータをグルーピングして、グループ内の順序を制御するための機能。
  • CFnテンプレートのトップレベル(Parameters等のsiblings)の名前 Metadata に対して、AWS::CloudFormation::Interface を定義することにより実現する。
  • ParameterLabelsでは、パラメータ名ではない自由なラベルをパラメータに対して定義できる。
  • 入力時の可視性が上がり、毎回同じ順番で入力することになるので入力ミスも減ることにも繋がる。
  • テンプレート自体の可読性の向上も期待できる。
template.yaml
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups: # パラメータグループ群
      - # パラメータグループ1つ目
        Label: # グループ名
          default: VPC Configuration
        Parameters: # グルーピングしたいパラメータ群
          - VPCCIDR 
          - PrivateSubnetCIDR
      -  # パラメータグループ2つ目
        Label:
          default: EC2 Configuration
        Parameters:
          - Ec2ImageId
          - Ec2InstanceType
          - KeyPair
...
参考
mocknmockn

スタック障害オプション

背景

CloudFormationでCodePipeline, CodeBuildを含むスタックを作成した後に、CodePipelineを実行するとビルドが成功しているにも関わらずCodePipelineとCodeBuildが削除されてしまった。
Cfnテンプレート上でCodePipelineとCodeBuildを削除するような記載はしていなかったため、原因がわからず詰まってしまった。

原因

CloudFormationのスタック障害オプションによるもの。

スタック障害オプション

プロビジョニングに失敗した場合、正常にプロビジョニングされたリソースを自動でロールバックせずに処理を一時停止することができる。
ロールバックを行う場合は、2つの方法があり、運用に合わせて選択できる。
停止した状態でトラブルシューティングを行い、変更内容を反映したうえで失敗時点からプロビジョニングを再開することができる。
スタックのデプロイと変更セットに対して障害オプションをプロビジョニングできる。
障害オプションが適用される対象のスタックステータスは"CREATE_FAILED" または" UPDATE_FAILED"。

ロールバック構成

ロールバック実行時に以下2つのどちらかのオプションを選択することができる。

  • すべてのスタックリソースをロールバックする
    • 正常・異常に関わらず、作成されたリソースをすべてロールバックする。
  • 正常にプロビジョニングされたリソースの保持(デフォルト)
    • 正常作成されたリソースは保持しつつ、失敗したリソースのみロールバックする。

"正常にプロビジョニングされたリソースの保持"の挙動

スタック作成

プロビジョニング失敗時は、正常に処理されたリソースはその状態を保持するが、失敗したリソースは次の更新オペレーションが行われるまで失敗した状態が維持される。

スタック更新・変更セット作成

変更セットオペレーション失敗時は、失敗したリソースを最後の安定した状態にロールバックしながら、正常にプロビジョニングされたリソースの状態を保持する。
失敗したリソースは UPDATE_FAILED 状態になる。
最後の安定状態が確認されていないリソースは、次のスタックオペレーション時に削除される。

まとめ

たくさんのリソースの作成が必要な時や、復旧を急いでいるときは、”正常にプロビジョニングされたリソースの保持”を選択すると素早く再試行を完了することができる。
依存関係が多かったり、既存リソースがある場合にプロビジョニングに失敗してしまうようなサービスが多い時には、無難に”すべてのスタックリソースをロールバックする”を選択するのが良さそう。

参考

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stack-failure-options.html

mocknmockn

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/'

参考

https://gist.github.com/eoinsha/e1c3dcf748f2792d621d297493859df2