TL;DR
-
CustomResource作ったら、Stack更新時にDeleteアクション実行してしまう
-
これが、起きるのはCustomResourceのlambda処理を更新したとき
-
原因はStackのCleanUp処理とPhysicalResourceIdの仕組み
-
CustomResourceの更新時にPhysicalResourceIdが変わってしまうと、CustomResourceにDelete Requestを送ってしまう
-
cfnresponse.sendで送るPhysicalResourceIdを固定することで、変更がなくなり、結果としてDeleteアクションを防げる
- こんな感じで固定。Create時はeventにPhysicalResourceIdを持たないので、そこはうまくハンドル
-
pycfnresponse.send(event, context, cfnresponse.SUCCESS, response_data, event.get('PhysicalResourceId'))
# 環境
* cfnresponseを使用したCustomResource
* CustomResourceのlambdaはpythonで作成
* SESのidentityにnotificationを設定するCustomResource
* `"RequestType": "Create", "RequestType": "Update", "RequestType": "Delete"` それぞれ処理をハンドルしている
# CustomResourceに送られるリクエストって?
* こんな感じのが変数`event`の中に入って送られてます
* Create
```json
{
"RequestType": "Create",
"ServiceToken": "arn:aws:lambda:us-west-2:******:function:set_identity_notifications",
"ResponseURL": "https://cloudformation-custom-resource-response-uswest2.s3-us-west-2.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-west-2%3A535996888546%3Astack/ses-suppression-register/fa440490-497e-11ea-b69e-06fe8be0ff5e%7CProvideSetIdentityNotifications*******",
"StackId": "arn:aws:cloudformation:us-west-2:535996888546:stack/ses-suppression-register/fa440490-497e-11ea-b69e-06fe8be0ff5e",
"RequestId": "fe0f6642-9115-46d2-856d-98047f5c214b",
"LogicalResourceId": "ProvideSetIdentityNotifications",
"ResourceType": "AWS::CloudFormation::CustomResource",
"ResourceProperties": {
"ServiceToken": "arn:aws:lambda:us-west-2:******:function:set_identity_notifications",
"HeadersInBounceNotificationsEnabled": "False",
"ComplaintTopic": "",
"HeadersInComplaintNotificationsEnabled": "False",
"BounceTopic": "arn:aws:sns:us-west-2:*******:ses-suppression-list-topic",
"Identities": "willco21.test.com"
}
}
- Update
{
"RequestType": "Update",
"ServiceToken": "arn:aws:lambda:us-west-2:*******:function:set_identity_notifications",
"ResponseURL": "https://cloudformation-custom-resource-response-uswest2.s3-us-west-2.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-west-2%3A*******%3Astack/ses-suppression-register/6ba8e740-4989-11ea-9aac-06be751c1bd6%7CProvideSetId******",
"StackId": "arn:aws:cloudformation:us-west-2:*******:stack/ses-suppression-register/6ba8e740-4989-11ea-9aac-06be751c1bd6",
"RequestId": "c3a874a5-a181-4a5c-88fc-d2b2e1504aa0",
"LogicalResourceId": "ProvideSetIdentityNotifications",
"PhysicalResourceId": "2020/02/07/[$LATEST]0b367e84e59e46798738c00bece897be",
"ResourceType": "AWS::CloudFormation::CustomResource",
"ResourceProperties": {
"ServiceToken": "arn:aws:lambda:us-west-2:*******:function:set_identity_notifications",
"HeadersInBounceNotificationsEnabled": "False",
"ComplaintTopic": "",
"HeadersInComplaintNotificationsEnabled": "False",
"BounceTopic": "arn:aws:sns:us-west-2:*******:ses-suppression-list-topic",
"Identities": ["willco21.test.com", "example.co.jp"]
},
"OldResourceProperties": {
"ServiceToken": "arn:aws:lambda:us-west-2:*******:function:set_identity_notifications",
"HeadersInBounceNotificationsEnabled": "False",
"ComplaintTopic": "",
"HeadersInComplaintNotificationsEnabled": "False",
"BounceTopic": "arn:aws:sns:us-west-2:*******:ses-suppression-list-topic",
"Identities": ["willco21.test.com"]
}
}
- Delete
{
"RequestType": "Delete",
"ServiceToken": "arn:aws:lambda:us-west-2:*******:function:set_identity_notifications",
"ResponseURL": "https://cloudformation-custom-resource-response-uswest2.s3-us-west-2.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-west-2%3A*******%3Astack/ses-suppression-register/6ba8e740-4989-11ea-9aac-06be751c1bd6%7C*******************",
"StackId": "arn:aws:cloudformation:us-west-2:*******:stack/ses-suppression-register/6ba8e740-4989-11ea-9aac-06be751c1bd6",
"RequestId": "33c8a15c-d8c1-477e-a88d-38c43fa46c67",
"LogicalResourceId": "ProvideSetIdentityNotifications",
"PhysicalResourceId": "2020/02/07/[$LATEST]0b367e84e59e46798738c00bece897be",
"ResourceType": "AWS::CloudFormation::CustomResource",
"ResourceProperties": {
"ServiceToken": "arn:aws:lambda:us-west-2:*******:function:set_identity_notifications",
"HeadersInBounceNotificationsEnabled": "False",
"ComplaintTopic": "",
"HeadersInComplaintNotificationsEnabled": "False",
"BounceTopic": "arn:aws:sns:us-west-2:*******:ses-suppression-list-topic",
"Identities": ["willco21.test.com"]
}
}
PhysicalResourceIdについて
- CFNで作成したリソースを判別するもの。実際に作成されたリソースを指すのが基本。PhysicalResourceId
- LogicalResourceIdとは違う。
- LogicalResourceIdはテンプレートのResourceで定義するリソース名
- CustomResourceのlambdaには作成されたlambdaように作成された、CloudWatchLogsのLog streamが入る
なぜUpdateでDeleteアクション実行されるの??
- cfnresponse.sendは5つ目の引数にPhysicalResourceIdが渡せる。
- PhysicalResourceIdを渡さない場合は、新しいPhysicalResourceIdが勝手に送られる
- 順番はこんな感じ
- CustomResourceのlambdaが更新
- 更新されたものでCustomResource実行
- 新しいLog stream作成
- cfnresponse.sendを実行するとそのLog streamでPhysicalResourceIdが更新される
- PhysicalResourceIdが更新されたので、StackのCleanUp処理で古いPhysicalResourceIdに関連するリソースはDelete リクエストが送られる
- CustomResourceがDeteleリクエストで実行される
回避策
-
cfnresponse.sendで送るPhysicalResourceIdを固定することで、変更がなくなり、結果としてDeleteアクションを防げる
- こんな感じで固定。Create時はeventにPhysicalResourceIdを持たないので、そこはうまくハンドル
-
pycfnresponse.send(event, context, cfnresponse.SUCCESS, response_data, event.get('PhysicalResourceId'))
# 参考
https://stackoverflow.com/questions/50599602/updating-custom-resources-causes-them-to-be-deleted