CloudFormationのDeletion Policyはスタックが削除されるタイミングに限らずリソースが削除されるタイミングでも動作する件

2023.07.30

スタックが削除される時でなければDeletionPolicyのRetainは効かない?

こんにちは、のんピ(@non____97)です。

皆さんはスタックが削除される時でなければDeletionPolicyのRetainは効かないのか気になったことはありますか? 私はあります。

AWS公式ドキュメントにはDeletionPolicyは「スタックが削除された際に」という条件しか記載ありません。

DeletionPolicy 属性を使用すると、スタックが削除された際にリソースをRetainし、場合によってはバックアップすることもできます。制御する各リソースに対して DeletionPolicy 属性を指定します。DeletionPolicy 属性が設定されていない場合、AWS CloudFormation ではデフォルトでリソースが削除されます。

DeletionPolicy 属性 - AWS CloudFormation

リソースの論理IDを変更して更新した場合など、スタックの削除ではなく、スタックの更新によってリソースが削除される場合はDeletionPolicyが効かないのでしょうか。

効くとは思いますが、あまり意識せずに普段触っており、確信が持てなかったので検証してみます。

いきなりまとめ

  • CloudFormationのDeletion Policyはスタックが削除されるタイミングに限らず、リソースが削除されるタイミングでも動作する
  • スタック更新中にロールバックした場合もDeletionPolicyは効く

やってみた

DeletionPolicyをRetainにしたリソースの作成

では、やってみます。

まず、DeletionPolicyをRetainにしたリソースの作成をします。

使用するテンプレートは以下の通りです。

service-linked-role.yml

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とロール名が重複したことによるエラーを防ぐ

service-linked-role.yml

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とエラーになっていました。

Resource handler returned message- Custom suffix is not allowed for globalaccelerator.amazonaws.com

どうやらAWSServiceRoleForGlobalAcceleratorはカスタムサフィックスを指定できないようです。

しょうがないので、テンプレートを以下のようにAWSServiceRoleForCloudHSMを作成するようなものに変更します。

service-linked-role.yml

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

実行後のイベントは以下のようになっていました。

DELETE_SKIPPED

問題なく更新できていますね。論理IDAWSServiceRoleForGlobalAcceleratorDELETE_SKIPPEDとなっていることが分かります。

リソースタブからも論理IDAWSServiceRoleForGlobalAcceleratorは無くなっています。

DELETE_SKIPPEDしたリソースが存在しないこと

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

AWSServiceRoleForGlobalAccelerator

スタック更新中にロールバックした場合もDeletionPolicy Retainは効くのか

スタック更新中にロールバックした場合もDeletionPolicy Retainは効くのか気になったので検証してみます。

以下のようにDeletionPolicyがRetainなリソースAWSServiceRoleForGlobalAccelerator作成後に、必ず作成に失敗するAWSServiceRoleForGlobalAccelerator2を定義してみました。

service-linked-role.yml

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の更新に失敗していますね。

イベントを確認すると、論理IDAWSServiceRoleForGlobalAcceleratorDELETE_SKIPPEDとなっていることが分かります。また、作成に失敗した論理IDAWSServiceRoleForGlobalAccelerator2DELETE_SKIPPEDとなっていました。

ロールバックしてもAWSServiceRoleForGlobalAcceleratorは残っている

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

ロールバックしてもAWSServiceRoleForGlobalAcceleratorは残っているが論理IDとしては見えない

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

AWSServiceRoleForGlobalAccelerator

そのため、CloudFormationでDeletion PolicyをRetainにしているリソースが含まれている場合に、Stackの作成に失敗するとそのリソースが残ることになります。

もし、そのリソースの物理IDを固定している場合はStackの再作成をする際に「同じ名前のリソースが存在する」とエラーになるでしょう。

Deletion Policyでどのリソースが残るのか意識しよう

CloudFormationのDeletion Policyはスタックが削除されるタイミングに限らず、リソースが削除されるタイミングでも動作することを確認しました。

間違ってリソースが削除されることを防ぐ意味でDeletion Policyは便利ですが、意識しなければ不要なリソースが残り続けたり、Stackの再作成をしようとした時に思わぬところでエラーになりそうです。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!