CloudFormationカスタムリソース入門

目次

CloudFormationカスタムリソースの挙動を確認する構成

CloudFormationの機能の1つにカスタムリソースがあります。

カスタムリソースを使用すると、テンプレートにカスタムのプロビジョニングロジックを記述し、ユーザーがスタックを作成、更新(カスタムリソースを変更した場合)、削除するたびに AWS CloudFormation がそれを実行します。

カスタムリソース

今回はLambda-backedカスタムリソースの挙動を確認します。

構築する環境

Diagram of introduction to CloudFormation Custom Resources.

CloudFormationスタックを作成し、その中のLambda関数をカスタムリソースとして設定します。
関数が実行される度に、CloudWatch Logsにログを出力するように設定します。
関数のランタイムはPython3.8とします。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-fa/tree/main/043

テンプレートファイルのポイント解説

カスタムリソース用Lambda関数

カスタムリソースのバックエンドとして動作するLambda関数を確認します。

Resources:
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import cfnresponse
          import datetime

          response_data = {}

          def lambda_handler(event, context):
            try:
              now = datetime.datetime.now()
              message = '{datetime} Request Type: {request_type}, Test Resource Property: {property}'.format(
                datetime=now,
                request_type=event['RequestType'],
                property=event['ResourceProperties']['TestProperty'])

              print(message)
              response_data['message'] = message

              cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      FunctionName: !Sub "${Prefix}-function"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
Code language: YAML (yaml)

基本的にはLambda関数と同様です。
CodeプロパティおよびZipFileプロパティで関数で実行するコードをインラインで定義できます。
Lambda関数の基本については以下のページをご確認ください。

あわせて読みたい
CloudFormationでLambdaを作成する3パータン(S3/インライン/コンテナ) 【CloudFormationでLambdaを作成する】 CloudFormationでLambdaを作成する場合、大別すると以下の3パターンあります。 S3バケットにコードをアップロードする インライ...

Pythonの場合、Lambda-backedカスタムリソースを作成するためのモジュール(cfnresponse)が用意されています。
今回はこちらを使用します。

CloudFormationの操作内容を確認することができます。
操作内容はevent[‘RequestType’]で取得することができます。
この値を参照にすることで、CloudFormationスタックの特定の操作時に限定した処理を実装することができます。
例えば以下のイメージです。

if event['RequestType'] == 'Create':
  pass

elif event['RequestType'] == 'Update':
  pass

elif event['RequestType'] == 'Delete':
  pass
Code language: Python (python)

カスタムリソース作成時に、Lambda関数にある種の引数を渡すことができます。
引数はカスタムリソースのプロパティという形で定義できます。
event[‘ResourceProperties’][<プロパティ名>]の形で取得することができます。
こちらはカスタムリソース側で改めて確認します。

関数は処理の最後に、所定の応答を行う必要があります。

custom resource provider は AWS CloudFormation のリクエストを処理し、SUCCESS または FAILED の応答を署名付き URL に返します。

カスタムリソース

この処理は先述のcfnresponseモジュールのsend関数を使って実装できます。
応答はsend関数の3つ目の引数、今回のコードでは「cfnresponse.SUCCESS」または「cfnresponse.FAILED」という形で渡すことができます。
cfnresponseの詳細については、以下のページをご確認ください。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html

関数が実行した働きの結果を、ある種の戻り値としてCloudFormationスタック側に渡すことができます。
戻り値は先述のsend関数の引数として設定します。
具体的には、4つ目の引数、今回のコードでは「response_data」として渡しています。

以上を踏まえ、この関数で実行する処理の内容をまとめます。

  • CloudFormationの操作内容および引数、そして現在日時を踏まえた文字列(message)を作成する
  • cfnresponse.sendで応答(SUCCESS)を送信する際に、messageを戻り値として設定する
  • 上記の処理に失敗した場合、例外処理という形で、cfnresponse.sendで応答(FAILED)を送信する。

注意点ですが、cfnresponseの使用上、FAILEDを送信する場合も戻り値は必須となります。
ですから空の辞書を渡すように設定しています。

カスタムリソース

カスタムリソースを確認します。

Resources:
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !Ref FunctionArn
      TestProperty: !Ref Prefix
      #TestProperty: !Sub "${Prefix}-Updated"

Outputs:
  CustomResourceReturnValue:
    Value: !GetAtt CustomResource.message
Code language: YAML (yaml)

カスタムリソースのTypeプロパティは、命名規則に従って、ユーザーが定義することができます。

テンプレートのカスタムリソースを定義するには、AWS::CloudFormation::CustomResource または Custom::MyCustomResourceTypeName リソースタイプを使用します。

カスタムリソース

ServiceTokenプロパティはバックエンドで動作するリソースのARNを設定します。
今回は先述のLambda関数が動作しますので、この関数のARNを設定します。

TestPropertyは先述の関数に渡す引数です。
今回は本プロパティに「fa-043」という文字列を設定します。

カスタムリソースの戻り値の値を確認するために、Outputsセクションで戻り値を取得します。
組み込み関数Fn::GetAttを使用して、カスタムリソースを通じて、関数で設定した値(message)を参照します。

環境構築

CloudFormationスタック作成時の挙動

AWS CLIからCloudFormationスタックを作成します。
テンプレートファイルを所定のS3バケットに設置した上で、以下のコマンドを実行します。

$ aws cloudformation create-stack \
--stack-name [stack-name] \
--template-url https://[bacuket-name].s3.[region-name].amazonaws.com/fa-043.yaml
--capabilities CAPABILITY_IAM
Code language: Bash (bash)

スタック作成の詳細については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

以下がLambda関数のログの内、スタック作成時に生成されたものです。

CloudFormation Custom Resources Test 1.

「Request Type: Create」とありますので、スタック作成時に関数が実行されたことがわかります。
また「Test Resource Property: fa-043」とありますので、カスタムリソースのプロパティの値を関数が参照できていることもわかります。

戻り値も確認します。

CloudFormation Custom Resources Test 2.

スタックのOutputsに、先ほど確認したログと同様の文字列が確認できます。
このようにカスタムリソースから戻り値を取得することもできます。

カスタムリソース修正時の挙動

続いてテンプレートファイルのカスタムリソースに関する記述を修正し、変更を実行します。
まずカスタムリソースを以下の通りに修正します。

Resources:
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !Ref FunctionArn
      #TestProperty: !Ref Prefix
      TestProperty: !Sub "${Prefix}-Updated"
Code language: YAML (yaml)

TestPropertyの値を修正しました。

このテンプレートファイルをS3バケットに再アップロード後、以下のコマンドを実行します。

$ aws cloudformation update-stack \
--stack-name [stack-name] \
--template-url https://[bucket-name].s3.[region-name].amazonaws.com/fa-043.yaml \
--capabilities CAPABILITY_IAM
Code language: Bash (bash)

以下のログが、スタック変更時に生成されたものです。

CloudFormation Custom Resources Test 3.

今回は「Request Type: Update」とありますので、スタック変更時に関数が実行されたことがわかります。
また「Test Resource Property: fa-043-Updated」とありますので、変更後のカスタムリソースのプロパティの値を参照できていることもわかります。

CloudFormationスタック削除時の挙動

AWS CLIからCloudFormationスタックを削除します。

$ aws cloudformation delete-stack \
--stack-name fa-043
Code language: Bash (bash)

以下のログが、スタック削除時に生成されたものです。

CloudFormation Custom Resources Test 4.

今回は「Request Type: Delete」とありますので、スタック削除時に関数が実行されたことがわかります。

まとめ

Lambda-backedカスタムリソースの挙動を確認しました。
RequestTypeの値を参照することで、特定の操作(スタック作成、削除など)に合わせた処理を実装できることがわかりました。

目次