
CloudFormationのDeletion Policyはスタックが削除されるタイミングに限らずリソースが削除されるタイミングでも動作する件
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
スタックが削除される時でなければDeletionPolicyのRetainは効かない?
こんにちは、のんピ(@non____97)です。
皆さんはスタックが削除される時でなければDeletionPolicyのRetainは効かないのか気になったことはありますか? 私はあります。
AWS公式ドキュメントにはDeletionPolicyは「スタックが削除された際に」という条件しか記載ありません。
DeletionPolicy 属性を使用すると、スタックが削除された際にリソースをRetainし、場合によってはバックアップすることもできます。制御する各リソースに対して DeletionPolicy 属性を指定します。DeletionPolicy 属性が設定されていない場合、AWS CloudFormation ではデフォルトでリソースが削除されます。
リソースの論理IDを変更して更新した場合など、スタックの削除ではなく、スタックの更新によってリソースが削除される場合はDeletionPolicyが効かないのでしょうか。
効くとは思いますが、あまり意識せずに普段触っており、確信が持てなかったので検証してみます。
いきなりまとめ
- CloudFormationのDeletion Policyはスタックが削除されるタイミングに限らず、リソースが削除されるタイミングでも動作する
- スタック更新中にロールバックした場合もDeletionPolicyは効く
やってみた
DeletionPolicyをRetainにしたリソースの作成
では、やってみます。
まず、DeletionPolicyをRetainにしたリソースの作成をします。
使用するテンプレートは以下の通りです。
AWSTemplateFormatVersion: "2010-09-09"
Conditions:
  IsControlPlaneRegion: !Equals [!Ref AWS::Region, us-east-1]
Resources:
  AWSServiceRoleForGlobalAccelerator:
    Condition: IsControlPlaneRegion
    DeletionPolicy: Retain
    Type: "AWS::IAM::ServiceLinkedRole"
    Properties:
      AWSServiceName: globalaccelerator.amazonaws.com
AWS CLIでStackを作成します。
$ aws cloudformation deploy \
    --stack-name service-linked-role-globalaccelerator \
    --template-file service-linked-role.yml \
    --capabilities CAPABILITY_NAMED_IAM \
    --region us-east-1
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - service-linked-role-globalaccelerator
作成後、Stackのイベントを確認します。特にエラーなく完了していますね。

リソースも問題なく作成完了しています。

DeletionPolicyをRetainにしたリソースの削除
それでは、DeletionPolicyをRetainにしたリソースの削除をしようとしてみます。
テンプレートを以下のように変更しました。
- 論理IDをAWSServiceRoleForGlobalAcceleratorからAWSServiceRoleForGlobalAccelerator2に変更
- CustomSuffixを付与- 既存のAWSServiceRoleForGlobalAcceleratorとロール名が重複したことによるエラーを防ぐ
 
AWSTemplateFormatVersion: "2010-09-09"
Conditions:
  IsControlPlaneRegion: !Equals [!Ref AWS::Region, us-east-1]
Resources:
  AWSServiceRoleForGlobalAccelerator2:
    Condition: IsControlPlaneRegion
    DeletionPolicy: Retain
    Type: "AWS::IAM::ServiceLinkedRole"
    Properties:
      AWSServiceName: globalaccelerator.amazonaws.com
      CustomSuffix: TestSuffix
それでは変更セットを作成します。
$ aws cloudformation deploy \
    --stack-name service-linked-role-globalaccelerator \
    --template-file service-linked-role.yml \
    --capabilities CAPABILITY_NAMED_IAM \
    --region us-east-1 \
    --no-execute-changeset
Waiting for changeset to be created..
Changeset created successfully. Run the following command to review changes:
aws cloudformation describe-change-set --change-set-name arn:aws:cloudformation:us-east-1:<AWSアカウントID>:changeSet/awscli-cloudformation-package-deploy-1690698779/bcd7c57d-a329-4c76-ac29-9ab724ae6067
作成した変更セットを確認します。
aws cloudformation describe-change-set \
    --change-set-name arn:aws:cloudformation:us-east-1:<AWSアカウントID>:changeSet/awscli-cloudformation-package-deploy-1690698779/bcd7c57d-a329-4c76-ac29-9ab724ae6067
{
    "Changes": [
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Add",
                "LogicalResourceId": "AWSServiceRoleForGlobalAccelerator2",
                "ResourceType": "AWS::IAM::ServiceLinkedRole",
                "Scope": [],
                "Details": []
            }
        },
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Remove",
                "LogicalResourceId": "AWSServiceRoleForGlobalAccelerator",
                "PhysicalResourceId": "AWSServiceRoleForGlobalAccelerator",
                "ResourceType": "AWS::IAM::ServiceLinkedRole",
                "Scope": [],
                "Details": []
            }
        }
    ],
    "ChangeSetName": "awscli-cloudformation-package-deploy-1690698779",
    "ChangeSetId": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:changeSet/awscli-cloudformation-package-deploy-1690698779/bcd7c57d-a329-4c76-ac29-9ab724ae6067",
    "StackId": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/service-linked-role-globalaccelerator/e95ecaa0-2ea1-11ee-88eb-12a6b76090b7",
    "StackName": "service-linked-role-globalaccelerator",
    "Description": "Created by AWS CLI at 2023-07-30T06:32:59.614445 UTC",
    "Parameters": null,
    "CreationTime": "2023-07-30T06:33:00.949000+00:00",
    "ExecutionStatus": "AVAILABLE",
    "Status": "CREATE_COMPLETE",
    "StatusReason": null,
    "NotificationARNs": [],
    "RollbackConfiguration": {},
    "Capabilities": [
        "CAPABILITY_NAMED_IAM"
    ],
    "Tags": null,
    "ParentChangeSetId": null,
    "IncludeNestedStacks": false,
    "RootChangeSetId": null
}
論理IDAWSServiceRoleForGlobalAccelerator2が追加されて、論理IDAWSServiceRoleForGlobalAcceleratorが削除されそうですね。
それでは、こちらの変更セットを実行します。
$ aws cloudformation execute-change-set \
    --change-set-name arn:aws:cloudformation:us-east-1:<AWSアカウントID>:changeSet/awscli-cloudformation-package-deploy-1690698779/bcd7c57d-a329-4c76-ac29-9ab724ae6067
実行後、Stackのイベントを確認するとResource handler returned message: "Custom suffix is not allowed for globalaccelerator.amazonaws.comとエラーになっていました。

どうやらAWSServiceRoleForGlobalAcceleratorはカスタムサフィックスを指定できないようです。
しょうがないので、テンプレートを以下のようにAWSServiceRoleForCloudHSMを作成するようなものに変更します。
AWSTemplateFormatVersion: "2010-09-09"
Conditions:
  IsControlPlaneRegion: !Equals [!Ref AWS::Region, us-east-1]
Resources:
  AWSServiceRoleForCloudHSM:
    Condition: IsControlPlaneRegion
    DeletionPolicy: Retain
    Type: "AWS::IAM::ServiceLinkedRole"
    Properties:
      AWSServiceName: cloudhsm.amazonaws.com
それでは変更セットを作成して、作成された変更セットを確認します。
$ aws cloudformation deploy \
    --stack-name service-linked-role-globalaccelerator \
    --template-file service-linked-role.yml \
    --capabilities CAPABILITY_NAMED_IAM \
    --region us-east-1 \
    --no-execute-changeset
Waiting for changeset to be created..
Changeset created successfully. Run the following command to review changes:
aws cloudformation describe-change-set --change-set-name arn:aws:cloudformation:us-east-1:<AWSアカウントID>:changeSet/awscli-cloudformation-package-deploy-1690699228/abda1f6d-dbb2-4b45-82e0-91d429fb9287
$ aws cloudformation describe-change-set \
    --change-set-name arn:aws:cloudformation:us-east-1:<AWSアカウントID>:changeSet/awscli-cloudformation-package-deploy-1690699228/abda1f6d-dbb2-4b45-82e0-91d429fb9287
{
    "Changes": [
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Add",
                "LogicalResourceId": "AWSServiceRoleForCloudHSM",
                "ResourceType": "AWS::IAM::ServiceLinkedRole",
                "Scope": [],
                "Details": []
            }
        },
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Remove",
                "LogicalResourceId": "AWSServiceRoleForGlobalAccelerator",
                "PhysicalResourceId": "AWSServiceRoleForGlobalAccelerator",
                "ResourceType": "AWS::IAM::ServiceLinkedRole",
                "Scope": [],
                "Details": []
            }
        }
    ],
    "ChangeSetName": "awscli-cloudformation-package-deploy-1690699228",
    "ChangeSetId": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:changeSet/awscli-cloudformation-package-deploy-1690699228/abda1f6d-dbb2-4b45-82e0-91d429fb9287",
    "StackId": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/service-linked-role-globalaccelerator/e95ecaa0-2ea1-11ee-88eb-12a6b76090b7",
    "StackName": "service-linked-role-globalaccelerator",
    "Description": "Created by AWS CLI at 2023-07-30T06:40:28.682688 UTC",
    "Parameters": null,
    "CreationTime": "2023-07-30T06:40:30.020000+00:00",
    "ExecutionStatus": "AVAILABLE",
    "Status": "CREATE_COMPLETE",
    "StatusReason": null,
    "NotificationARNs": [],
    "RollbackConfiguration": {},
    "Capabilities": [
        "CAPABILITY_NAMED_IAM"
    ],
    "Tags": null,
    "ParentChangeSetId": null,
    "IncludeNestedStacks": false,
    "RootChangeSetId": null
}
論理IDAWSServiceRoleForCloudHSMが追加されて、論理IDAWSServiceRoleForGlobalAcceleratorが削除されそうですね。
こちらの変更セットを実行します。
$ aws cloudformation execute-change-set \
    --change-set-name arn:aws:cloudformation:us-east-1:<AWSアカウントID>:changeSet/awscli-cloudformation-package-deploy-1690699228/abda1f6d-dbb2-4b45-82e0-91d429fb9287
実行後のイベントは以下のようになっていました。

問題なく更新できていますね。論理IDAWSServiceRoleForGlobalAcceleratorはDELETE_SKIPPEDとなっていることが分かります。
リソースタブからも論理IDAWSServiceRoleForGlobalAcceleratorは無くなっています。

IAMのコンソールからAWSServiceRoleForGlobalAcceleratorが残っているか確認すると、確かに削除されずに存在していました。

スタック更新中にロールバックした場合もDeletionPolicy Retainは効くのか
スタック更新中にロールバックした場合もDeletionPolicy Retainは効くのか気になったので検証してみます。
以下のようにDeletionPolicyがRetainなリソースAWSServiceRoleForGlobalAccelerator作成後に、必ず作成に失敗するAWSServiceRoleForGlobalAccelerator2を定義してみました。
AWSTemplateFormatVersion: "2010-09-09"
Conditions:
  IsControlPlaneRegion: !Equals [!Ref AWS::Region, us-east-1]
Resources:
  AWSServiceRoleForGlobalAccelerator:
    Condition: IsControlPlaneRegion
    DeletionPolicy: Retain
    Type: "AWS::IAM::ServiceLinkedRole"
    Properties:
      AWSServiceName: globalaccelerator.amazonaws.com
  AWSServiceRoleForGlobalAccelerator2:
    Condition: IsControlPlaneRegion
    DeletionPolicy: Retain
    DependsOn: AWSServiceRoleForGlobalAccelerator
    Type: "AWS::IAM::ServiceLinkedRole"
    Properties:
      AWSServiceName: globalaccelerator.amazonaws.com
      CustomSuffix: TestSuffix
IAMのコンソールから先の検証で作成したAWSServiceRoleForGlobalAcceleratorを削除しておきます。
この状態でStackを更新します。
$ aws cloudformation deploy \
    --stack-name service-linked-role-globalaccelerator \
    --template-file service-linked-role.yml \
    --capabilities CAPABILITY_NAMED_IAM \
    --region us-east-1
Waiting for changeset to be created..
Waiting for stack create/update to complete
Failed to create/update the stack. Run the following command
to fetch the list of events leading up to the failure
aws cloudformation describe-stack-events --stack-name service-linked-role-globalaccelerator
Stackの更新に失敗していますね。
イベントを確認すると、論理IDAWSServiceRoleForGlobalAcceleratorはDELETE_SKIPPEDとなっていることが分かります。また、作成に失敗した論理IDAWSServiceRoleForGlobalAccelerator2もDELETE_SKIPPEDとなっていました。

リソースタブから論理IDAWSServiceRoleForGlobalAcceleratorとAWSServiceRoleForGlobalAccelerator2のリソースが存在しないことを確認します。

IAMのコンソールからAWSServiceRoleForGlobalAcceleratorが残っているか確認すると、確かに削除されずに存在していました。

そのため、CloudFormationでDeletion PolicyをRetainにしているリソースが含まれている場合に、Stackの作成に失敗するとそのリソースが残ることになります。
もし、そのリソースの物理IDを固定している場合はStackの再作成をする際に「同じ名前のリソースが存在する」とエラーになるでしょう。
Deletion Policyでどのリソースが残るのか意識しよう
CloudFormationのDeletion Policyはスタックが削除されるタイミングに限らず、リソースが削除されるタイミングでも動作することを確認しました。
間違ってリソースが削除されることを防ぐ意味でDeletion Policyは便利ですが、意識しなければ不要なリソースが残り続けたり、Stackの再作成をしようとした時に思わぬところでエラーになりそうです。
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!











