AWS CloudFormationのLambda-Backedカスタムリソースでリソース作成を待ち受けできるようにする

[cloudpack あら便利カレンダー 2019](https://benri2019.cloudpack.jp/) の記事となります。[誕生秘話](https://qiita.com/shinichi-takahashi/items/cea235795f904ccea207#%E3%81%82%E3%82%89%E4%BE%BF%E5%88%A9%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC%E8%AA%95%E7%94%9F%E7%A7%98%E8%A9%B1) はこちら。


AWS CloudFormation(CFn)のLambda-Backedカスタムリソースを利用するとCFnが対応していないリソースでもAWS Lambdaを利用して管理できます。

AWS Lambda-backed カスタムリソース - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html

その際に、リソース作成するとレスポンスはすぐに返ってくるけど、リソースが利用できるまでに時間がかかるものを取り扱えるようにしてみました。


## テンプレート

CFnでリソース作成を待ってから関連するリソース作成などができるようにするテンプレートです。

```yaml
Resources:
  CreateResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt CreateResourceFunction.Arn

  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt CustomResourceFunction.Arn
      ResourceId: !GetAtt CreateResource.Id
    DependsOn: CreateResource

  CreateResourceFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt FunctionExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          import cfnresponse
          def handler(event, context):
            response = {}
            if event['RequestType'] == 'Create':
              try:
                # ほんとはここでリソース作成
                response = {
                  "Id": "hoge"
                }
              except Exception as e:
                response = {'error': str(e)}
                cfnresponse.send(event, context, cfnresponse.FAILED, response)
                return

            cfnresponse.send(event, context, cfnresponse.SUCCESS, response)
      Runtime: python3.7

  CustomResourceFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt FunctionExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          import cfnresponse
          import time
          def handler(event, context):
            Id = event['ResourceProperties']['ResourceId']

            # リソース作成完了を待ち受ける
            response = {}
            while True:
              # ホントはここでリソース取得
              response = {
                "Id": Id,
                "Status": "AVAILABLE"
              }

              # 特定のステータスになったら抜ける
              if response['Status'] == 'AVAILABLE':
                break
              print('create wait...')
              time.sleep(60)

            cfnresponse.send(event, context, cfnresponse.SUCCESS, response)
      Runtime: python3.7
      Timeout: 900

  FunctionExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: root
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource: "arn:aws:logs:*:*:*"
```

## ポイント

### カスタムリソースを2つ用意する

リソースを作成する`CreateResource` とリソース情報を取得する`CustomResource` のカスタムリソースを定義することで、リソース作成を待ち受けできるようにしています。


### Lambda関数のタイムタウト x 3回まで待ち受けできる

AWS Lambdaの関数は最大15分のタイムタウト設定ができます。またLambda-Backedカスタムリソースで関数実行に失敗すると3回までリトライしてくれるのでそれを利用して最大45分間待ち受けできるようになります。

AWS Lambda の制限 - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/limits.html


※Lambda-Backedカスタムリソースでのリトライ回数について必ず3回実行されるのかはドキュメントが見当たらず不明確となります。

AWS Lambda 再試行動作 - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/retries-on-errors.html


### `DependsOn` 属性を利用してリソース作成順を制御する

CFnの`DependsOn` 属性を利用することで、リソース作成`CreateResource` 後、リソース情報取得`CustomResource` が実行されるようにします。
後続するリソースは`CustomResource` を`DependsOn` 属性に指定することでリソース作成完了してから実行されるようにできます。

DependsOn 属性 - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html


### カスタムリソースの更新・削除と絡めて利用する

下記記事の内容と組み合わせることで、カスタムリソースの更新・削除が実現できます。

AWS CloudFormationのLambda-backedカスタムリソースでリソースの更新・削除をする方法 - Qiita
https://qiita.com/kai_kou/items/7be2eb9a36611bb5da12


## 参考

AWS Lambda-backed カスタムリソース - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html

AWS Lambda の制限 - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/limits.html

AWS Lambda 再試行動作 - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/retries-on-errors.html

DependsOn 属性 - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html

AWS CloudFormationのLambda-backedカスタムリソースでリソースの更新・削除をする方法 - Qiita
https://qiita.com/kai_kou/items/7be2eb9a36611bb5da12