Cloudformationのカスタムリソースについて簡単解説

## はじめに
直近、Cloudformationのカスタムリソースに触れる機会があり調べていたのですが、
あまり使用されていないのか情報がそれほど多くはありませんでした。
なので、誰かの一助になればと思い残します。
カスタムリソースは何なのか？と実装例を書きます。

## 超ざっくり、カスタムリソースとは
Cloudformationでサポートされていないものや、
スタックのイベントに合わせて動作させたい機能をカスタムリソースと定義して、
LambdaかSNSを呼び出して呼び出し先で何とかする機能です。
なので、”カスタムリソース”で何かできるというわけではなく、
実態は呼び出すLambdaで何とかするロジックを作ります。
SNSを呼び出すユースケースは見たことがありません。
（教えていただけると助かります！）
![カスタムリソース図解.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380272/972aac27-52c4-13aa-4839-bedc742c87fd.png)
※通信用のS3が隠れていますが、割愛しています。

## もう少し詳しく、カスタムリソースとは
>カスタムリソースを使用すると、テンプレートにカスタムのプロビジョニングロジックを記述し、ユーザーがスタックを作成、更新(カスタムリソースを変更した場合)、削除するたびに AWS CloudFormation がそれを実行します。
たとえば、AWS CloudFormation のリソースタイプとして使用できないリソースを含める必要があるとします。
それらのリソースは、カスタム リソースを使用して含めることができます。この方法により、すべての関連リソースを 1 つのスタックで管理できます。

[カスタムリソース（AWS公式ドキュメント）](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-custom-resources.html)

AWSドキュメント特有の難解な文章かと思いますが、
これを分かりやすく説明すると以下の通りです。

* カスタムリソースは、Cloudformationでサポートされていないリソースをテンプレートに定義・実装出来る
* カスタムリソースは、スタックの作成・更新・削除をトリガーに実行される

##### カスタムリソースは、Cloudformationでサポートされていないリソースをテンプレートに定義・実装出来る

現在、CloudformationではAWSにおける主要なリソースを構築できるようになっていますが、
その一方で未だサポートされていないリソースが存在しています。
また、リソース自体は構築できるがプロパティの設定が出来ないもの、
特定のリソース同士を一つのテンプレートで構築しようとした場合に、依存関係の問題から構築できないものなどがあります。
上記のようなサポートされていないものをCloudformationで実現しようとした時、
カスタムリソースを使用することで実現できるようになります。

![cfnサポートあり.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380272/ca158a3a-0985-9e9d-5a4f-d409d9218ad0.png)
![cfnなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380272/d0840845-a8b2-be7a-44ec-140d11c0cde9.png)


##### カスタムリソースは、スタックの作成・更新・削除をトリガーに実行される
ユーザーが作成のみ・削除のみのように、特定のトリガーイベントのみ実行するよう制限することは出来ません。
よって、カスタムリソースの作成・更新・削除のいずれかを行った場合、必ずイベントがカスタムリソースへ飛び実行されます。
その為、カスタムリソース・スタックの作成前にどう運用するかの想定や、カスタムリソース実行後に各イベントでの処理を考慮しておく必要があります。

## カスタムリソースの定義(Cloudformationテンプレート)
カスタムリソースの定義に必要なものは下記の通り。

| 定義内容 | 説明 |
|:-----------|:------------|
| リソース名  | カスタムリソース名。分かりやすい名前を付ければ良い。|
| リソースタイプ | カスタムリソースタイプ。決められた型は存在しないので、自分が分かりやすい名前を付ければ良い。|
| サービストークン | 呼び出すLambdaもしくはSNSのArn。|
| サービストークンより後ろのパラメータ| 呼び出すLambdaもしくはSNSに渡すパラメータ。パラメータ名は好きなように命名できる。|

定義例
```template.yml
SampleCustomResource: 
  Type: 'Custom::SampleCustomResource'
  Properties:
    ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:sample-lambda'
    param: 'sample'
```

## カスタムリソースの実装(Lambda)
Lambdaでは、大きく分けて2つの処理を実装します。
* 各イベントに対応する処理
* Cloudformationにレスポンスを返却する処理

各イベントに対応する処理については、変数event['RequestType']の中に
作成・更新・削除（Create/Update/Delete）のいずれかの値が格納されるので、
その値からイベントを判断し対応する処理を記述します。
また、その他のCloudformationからのパラメータや
リクエストデータも変数eventに格納されて渡されるので、
必要に応じて参照します。

実装例
```lambda_function.py
from cfnResponse import send

def main(event, context):
    try:
        if event['RequestType'] == 'Delete':
            # 削除イベントに対応する処理を書く

        elif event['RequestType'] == 'Create':
            # 作成イベントに対応する処理を書く

        elif event['RequestType'] == 'Update':
            # 更新イベントに対応する処理を書く
            
        send(event, context, 'SUCCESS', {})
    except Exception as e:
        send(event, context, 'FAILED', {})
```

レスポンスの返却処理については、自分で実装するか公式のモジュール(cfn-response)を使うかの2択です。
公式のモジュールを使えば楽じゃん！と思いますが、公式のモジュールはCloudformationテンプレートにインラインコードした場合のみ使えるため、通常のLambdaでは使用できません。
その為、インラインコードしない場合は自分で実装します。
実装内容は、公式ドキュメントにコードが載っているのでそれを使用します。
（下記リンクのPython 2 および 3 関数のレスポンスモジュールのソースコード）
[cfn-response モジュール（AWS公式ドキュメント）](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html)

## カスタムリソースの使用例
基本Lambdaなので何でもできると思いますが、
私が使用したのは以下の2つです。

* スタック削除時にS3バケット内のオブジェクトを全て削除するカスタムリソース
* Cloudformationテンプレートに記述されたS3イベントトリガーを反映するカスタムリソース

人手を介する必要があったところを、カスタムリソースで自動化させました。

## おわりに
正直、色々出来る反面ちゃんと運用方針を決めないと危険なものであると認識しました。
スタック削除時のS3のオブジェクト削除を例に挙げると、
手動でオブジェクトを削除せずにスタックを丸ごと削除できるので便利になります。
しかし、その分誤って削除されるリスクもあるので運用をきちんと整備しなくてはならないと感じました。

また、結構適当なところ（通常のLambdaでcfn-response使えないとか）もあり、もう少し良くならないかなと思っています。