DeletionPolicyの変更が反映されないときの対処法 | CDK/CloudFormation

CDKやCloudFormationにて、変更したDeletionPolicy (RemovalPolicy)がStackに反映されない時の対処法を残します。

はじめに

おはよう。@bioerrorlogです。

先日、CDKで作成したリソースのDeleteionPolicyを変更してdeployした際に、その変更が実環境のStackに反映されないことがありました。

その原因と対処法をメモします。

DeletionPolicyの変更が反映されないときの対処法

起こったこと

まず、起こったことをおさらいします。

例えば以下のように、S3を定義したシンプルなCDKコードがあるとします(Pythonを例とします)。

from aws_cdk import (
    aws_s3 as s3,
    core
)


class SampleS3Stack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        s3_bucket = s3.Bucket(self, "TestBucket", bucket_name="deletion-policy-test-bucket")

app = core.App()
SampleS3Stack(app, "deletion-policy-test-stack")

app.synth()

上記のようにS3 BucketをCDK Constructのデフォルトで作成した場合、DeletionPolicyおよびUpdateReplacePolicyにはRetainが設定されます。

以下、生成されるCloudFormationテンプレート抜粋です。

Resources:
  TestBucket560B80BC:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: deletion-policy-test-bucket
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain

ここで、DeletionPolicy / UpdateReplacePolicyをDeleteに変更するには、以下のようにapply_removal_policy()を利用します。

from aws_cdk import (
    aws_s3 as s3,
    core
)


class SampleS3Stack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        s3_bucket = s3.Bucket(self, "TestBucket", bucket_name="deletion-policy-test-bucket")
        
        # DeletionPolicy / UpdateReplacePolicyを指定
        s3_bucket.apply_removal_policy(core.RemovalPolicy.DESTROY) 

app = core.App()
SampleS3Stack(app, "deletion-policy-test-stack")

app.synth()

上記のようにapply_removal_policy()を適用することで、生成されるCloudFormationテンプレートやcdk diffの結果には変更が反映されます。

# cdk synth
Resources:
  TestBucket560B80BC:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: deletion-policy-test-bucket
    UpdateReplacePolicy: Delete
    DeletionPolicy: Delete
# cdk diff
Stack deletion-policy-test-stack
Resources
[~] AWS::S3::Bucket TestBucket TestBucket560B80BC 
 ├─ [~] DeletionPolicy
 │   ├─ [-] Retain
 │   └─ [+] Delete
 └─ [~] UpdateReplacePolicy
     ├─ [-] Retain
     └─ [+] Delete

しかし、上記変更後のCDKコードをデプロイしても、変更がStackに反映されませんでした。

以下のようにno changesと表示され、変更はデプロイされなかったのです。

# cdk deploy
deletion-policy-test-stack: deploying...
deletion-policy-test-stack: creating CloudFormation changeset...

 ✅  deletion-policy-test-stack (no changes)

原因

少し調べた結果、上記の振る舞いはCloudFormationの仕様であることが分かりました。

以下ドキュメント抜粋です。

You can't update the CreationPolicy, DeletionPolicy. or UpdatePolicy attribute by itself. You can update them only when you include changes that add, modify, or delete resources.

CreationPolicy/DeletionPolicy/UpdatePolicyのみでは変更を更新できないため、他のリソース情報と一緒に更新する必要がある、とのことです。

仕様としてイケてないとは思いますが、DeletionPolicyが更新できないこと自体は仕様として想定通りということですね。

対処法

では、どのようにしてDeletionPolicyを更新すればよいのか。

それ単体では更新できないことが仕様である以上、他のリソース情報と一緒に更新する他なさそうです。

例えば、metadataやTagの変更を同時に行えば、リソースそのものにはあまり影響を与えずにDeletionPolicyを更新することが出来ます。

ドキュメントにも以下のように、metadataの更新を一緒に行うことが言及されています。

For example, you can add or modify a metadata attribute of a resource.


例えば、以下のようにmetadataを変更して再デプロイすればDeletionPolicyの変更が反映されます。

from aws_cdk import (
    aws_s3 as s3,
    core
)


class SampleS3Stack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        s3_bucket = s3.Bucket(self, "TestBucket", bucket_name="deletion-policy-test-bucket")
        
        # DeletionPolicy / UpdateReplacePolicyを指定
        s3_bucket.apply_removal_policy(core.RemovalPolicy.DESTROY)
        
        # metadataを更新
        cfn_bucket = s3_bucket.node.default_child
        cfn_bucket.cfn_options.metadata = {
            "DeletionPolicyUpdated": "DESTROY"
        }

app = core.App()
SampleS3Stack(app, "deletion-policy-test-stack")

app.synth()

※Metadataの更新方法はドキュメントGitHub issueを参考にしています。 Metadataの上書き操作になるので注意が必要です(もともとあったmetadata aws:cdk:pathが上書きされる)。


また以下のようにTagを追加して再デプロイしても、DeletionPolicyの変更が反映されます。

from aws_cdk import (
    aws_s3 as s3,
    core
)


class SampleS3Stack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        s3_bucket = s3.Bucket(self, "TestBucket", bucket_name="deletion-policy-test-bucket")
        
        # DeletionPolicy / UpdateReplacePolicyを指定
        s3_bucket.apply_removal_policy(core.RemovalPolicy.DESTROY)
        
        # Tagを追加
        core.Tags.of(s3_bucket).add("DeletionPolicyUpdated", "DESTROY")

app = core.App()
SampleS3Stack(app, "deletion-policy-test-stack")

app.synth()

おわりに

今回は、CloudFormation/CDKにてDeletionPolicyの変更が反映されないときの対処法を書きました。

そもそもDeletionPolicy単体では更新できないのがCloudFormationの仕様、というのにはなかなか驚きました。

なるべくリソースそのものに影響のないTagやmetadataの更新を一緒に行うことを対処法として書きましたが、他にもっと適切なやり方があれば教えて頂けると嬉しいです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

参考

[aws-events] `cdk diff` shows difference, but `cdk deploy` tells no changes · Issue #10219 · aws/aws-cdk · GitHub

Modifying a stack template - AWS CloudFormation

RemovalPolicy — AWS Cloud Development Kit 1.94.1 documentation

Escape hatches - AWS Cloud Development Kit (AWS CDK)

Identifiers - AWS Cloud Development Kit (AWS CDK)

Python: cfn_options.metadata cannot be mutated · Issue #6379 · aws/aws-cdk · GitHub

Add Support for adding Metadata to deal with 3rd party tools · Issue #8336 · aws/aws-cdk · GitHub