[アップデート] CloudFormation の DeletionPolicy にて、リソース作成時のみ Delete でそれ以外は Retain として動作する RetainExceptOnCreate が設定できるようなりました

2023.08.01

いわさです。

CloudFormation ではスタック削除時のリソース動作を示すポリシーとして DeletionPolicy 属性というものが用意されています。
デフォルトはスタック削除時にリソースも削除される Delete として動作しますが、Retain を設定することでスタックが削除されても AWS リソースとしては削除せずに保持しておくことが出来ます。
あるいは特定のリソースタイプにおいてはリソースは削除するけどその前にスナップショットを取得してくれる Snapshot という設定も存在します。(主に EBS や RDS、Redshift などのストレージ系)

私はテンプレートの開発中などはトライアンドエラーを繰り返すことがよくあるのですが、その段階で Retain を設定するとテンプレートに誤りがあってスタックがロールバック動作で削除された場合でも、途中まで成功したリソースが保持されてしまい、それによって次にスタックのデプロイを行ったときにユニーク値の重複が原因で失敗してしまうことがよく経験していました。
そのため最近はテンプレートの作り込みがある程度終わってから DeletionPolicy をひととおり設定するような作り方を行っていました。

しかし、本日のアップデートで DeletionPolicy に新しく RetainExceptOnCreate という値が設定できるようになりました。

要はリソースを初回作成するタイミングでのスタック削除時は Delete と同じ動作をし、それ以外は Retain と同じ動作をする設定です。
これによって Retain を設定しつつも、初期段階のトライアンドエラーを試みやすくなりそうです。

既存の挙動をおさらい

スタックと一緒にリソースを削除する Delete

まず Delete の動作を簡単におさらいしておきます。

AWSTemplateFormatVersion: 2010-09-09
Description: ---
Resources: 
  sqsQueue1:
    Type: AWS::SQS::Queue
    DeletionPolicy: Delete
    Properties:
      QueueName: hoge0801queue1

  sqsQueue2:
    Type: AWS::SQS::Queue
    DeletionPolicy: Delete
    Properties:
      QueueName: hoge0801queue2

上記テンプレートをデプロイすると SQS のキューが 2 つ作成されます。
それぞれのキューには DeletionPolicy に Delete を設定しています。

ここで作成済みのスタックを削除してみましょう。

スタックと一緒にリソース(キュー)も削除されましたね。
こちらが Delete の動作です。

スタックを削除してもリソースは保持する Retain

続いて Retain の動作を確認します。
DeletionPolicy だけ変更しました。

AWSTemplateFormatVersion: 2010-09-09
Description: ---
Resources: 
  sqsQueue1:
    Type: AWS::SQS::Queue
    DeletionPolicy: Retain
    Properties:
      QueueName: hoge0801queue1

  sqsQueue2:
    Type: AWS::SQS::Queue
    DeletionPolicy: Retain
    Properties:
      QueueName: hoge0801queue2

上記で作成されたスタックを削除してみると次のようにリソース削除がスキップされていることがわかります。

実際に SQS コンソールを見てみるとキューが残っています。
意図的に残している状態であれば問題ないのですが、例えば先程のテンプレートをまたデプロイしようとするとキュー名の重複でスタックデプロイに失敗してしまいます。

そういった場合の対処としては手動削除が必要です。

また、次のようにリソース作成に失敗する状態のテンプレートをデプロイしてみましょう。
DelaySeconds が許容される最大値 900 を超えているので、キュー 1 (hoge0801queue1) の作成に成功した後にキュー 2 (hoge0801queue2) の作成に失敗するはずです。

AWSTemplateFormatVersion: 2010-09-09
Description: ---
Resources: 
  sqsQueue1:
    Type: AWS::SQS::Queue
    DeletionPolicy: Retain
    Properties:
      QueueName: hoge0801queue1

  sqsQueue2:
    Type: AWS::SQS::Queue
    DependsOn: sqsQueue1
    DeletionPolicy: Retain
    Properties:
      QueueName: hoge0801queue2
      DelaySeconds: 1000

デプロイしてみるとスタック作成に失敗しました。
SQS のコンソールを確認してみると、期待どおり キュー 1 が保持されている状態です。

このあとキュー 2 が正常にデプロイできるようにテンプレートを修正したとしても、キュー 1 が存在するためスタック作成に失敗してしまいます。

% rain deploy template.yaml hoge0801cfn
CloudFormation will make the following changes:
Stack hoge0801cfn:
  + AWS::SQS::Queue sqsQueue1
  + AWS::SQS::Queue sqsQueue2
Do you wish to continue? (Y/n) 
Deploying template 'template.yaml' as stack 'hoge0801cfn' in ap-northeast-1.
Stack hoge0801cfn: ROLLBACK_IN_PROGRESS - 1 resource pending
Messages:
  - sqsQueue1: Resource handler returned message: "Resource of type 'AWS::SQS::Queue' with identifier 'hoge0801queue1' already exists." (RequestToken: 9fe117f1-1dca-55d2-1efb-6db58c3b34f5, HandlerErrorCode: AlreadyExists)

RetainExceptOnCreate で回避

続いて、今回登場した RetainExceptOnCreate を使ってみましょう。
ただし、テンプレートはリソース作成に失敗する内容 (DelaySeconds) となっています。

AWSTemplateFormatVersion: 2010-09-09
Description: ---
Resources: 
  sqsQueue1:
    Type: AWS::SQS::Queue
    DeletionPolicy: RetainExceptOnCreate
    Properties:
      QueueName: hoge0801queue1

  sqsQueue2:
    Type: AWS::SQS::Queue
    DependsOn: sqsQueue1
    DeletionPolicy: RetainExceptOnCreate
    Properties:
      QueueName: hoge0801queue2
      DelaySeconds: 1000

スタック作成時のロールバックで成功したリソースも削除される

スタック作成を行うと、キュー 2 の作成に失敗し、ロールバックされます。
先程はキュー 1 が残ったままでしたが...

キュー 1 も削除されていますね。
このようにリソース作成タイミングの削除動作に限っては Retain ではなく Delete として動作します。

スタック作成成功後にスタックを削除してもリソースは保持される

ただしスタック作成に成功した後に、スタックを削除してみると、次のように保持されていることがわかります。

なるほど。

スタック更新でリソース追加した場合

スタック更新時にリソース追加した場合の挙動も確認してみました。
次のように 1 つのキューをデプロイ済みの状態とします。

AWSTemplateFormatVersion: 2010-09-09
Description: ---
Resources: 
  sqsQueue1:
    Type: AWS::SQS::Queue
    DeletionPolicy: RetainExceptOnCreate
    Properties:
      QueueName: hoge0801queue1

このスタックを更新してみます。

Retain の場合は残る

次のように 2 つのキューを追加します。
ひとつはリソース作成に失敗する内容となっています。

AWSTemplateFormatVersion: 2010-09-09
Description: ---
Resources: 
  sqsQueue1:
    Type: AWS::SQS::Queue
    DeletionPolicy: RetainExceptOnCreate
    Properties:
      QueueName: hoge0801queue1

  sqsQueue2:
    Type: AWS::SQS::Queue
    DependsOn: sqsQueue1
    DeletionPolicy: Retain
    Properties:
      QueueName: hoge0801queue2

  sqsQueue3:
    Type: AWS::SQS::Queue
    DependsOn: sqsQueue2
    DeletionPolicy: Retain
    Properties:
      QueueName: hoge0801queue2
      DelaySeconds: 1000

この場合は次のようにスタック更新処理がロールバックされるのですが、作成リソースの削除はスキップされます。

そのため、今回のテンプレート状態であれば、追加したリソースのうち成功したキュー 2 のみ保持されてしまいます。

そして、このあとキュー 3 の内容を修正してデプロイしても重複によって失敗してしまうので、事前にキュー 2 の手動削除が必要です。

RetainExceptOnCreate の場合はスタック更新でもリソース自体が新規作成であれば削除される

先程と同じテンプレート・条件で DeletionPolicy だけ RetainExceptOnCreate に変更してます。

AWSTemplateFormatVersion: 2010-09-09
Description: ---
Resources: 
  sqsQueue1:
    Type: AWS::SQS::Queue
    DeletionPolicy: RetainExceptOnCreate
    Properties:
      QueueName: hoge0801queue1

  sqsQueue2:
    Type: AWS::SQS::Queue
    DependsOn: sqsQueue1
    DeletionPolicy: RetainExceptOnCreate
    Properties:
      QueueName: hoge0801queue2

  sqsQueue3:
    Type: AWS::SQS::Queue
    DependsOn: sqsQueue2
    DeletionPolicy: RetainExceptOnCreate
    Properties:
      QueueName: hoge0801queue2
      DelaySeconds: 1000

同じようにロールバックは発生するのですが、リソースの削除処理が実行されていますね。

スタック更新前から残っていたキュー 1 はもちろん保持され、中途半端に成功してしまったキュー 2 がちゃんと削除されました。良いですね。

UpdateReplacePolicy には使えない

ちなみにですが、RetainExceptOnCreate は DletionPolicy でのみ使用が出来ます。
UpdateReplacePolicy では使用出来ません。

% rain deploy template.yaml hoge0801cfn
error creating changeset: Template format error: Unrecognized UpdateReplacePolicy RetainExceptOnCreate for resource sqsQueue2

RetainExceptOnCreate の目的から UpdateReplacePolicy で使える意味はないですけど、一応確認してみました。

さいごに

本日は CloudFormation の DeletionPolicy にて RetainExceptOnCreate が設定できるようなったので試してみました。

Retain は基本的に RetainExceptOnCreate に置き換えても良さそうだなって感じがしますね。

ただ、私はトライアンドエラーのタイミングでも S3 バケットだけはよくロールバックに失敗するので Retain をはじめに設定しちゃうことがあります。
そこは Retain のままにしたいかもしれないです。混在しちゃうと微妙ですかね、うーん。

完成したテンプレートをもし別環境に配布する場合を考えると、配布先の環境の状態によってはスタック作成に失敗することがありえます。(既存リソースやクォータの関係など)
アップデートアナウンスでは開発サイクルの〜という表現がされていましたが、今回の RetainExceptOnCreate を設定しておくと、失敗時に手動削除しなくてよくなるのでその観点で考えるとかなり有効なのではないでしょうか。