CloudFormationのリソース更新について

なかやまです。 CloudFormationでは同じ名前(同一の一意キ

なかやまです。

CloudFormationでは同じ名前(同一の一意キー)を持つリソースを“同じ更新の中で”削除しつつ新規作成しようとすると失敗します。
これは、CloudFormationの更新順序が 「追加 → 更新 → 削除」 であることが原因です。
CICDパイプラインを構築し、IaCコードのリリースを行う中でちょこちょこ発生した事象だったので備忘として残しておきます。

では、本事象を簡単なシナリオを用いて説明します。

3つのS3バケットを作成する以下のようなテンプレートがあります。🍎🍌🍊

Resources:
  Grape:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'apple-bucket-${AWS::AccountId}'
      AccessControl: Private

  Banana:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'banana-bucket-${AWS::AccountId}'
      AccessControl: Private

  Orange:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'orange-bucket-${AWS::AccountId}'
      AccessControl: Private

これをCloudFormationでプロビジョニングし、3つのS3バケットができました。

「あ、リソース名が「Grape」なのに、「apple-bucket-xxxxxx」つくっちゃてた!」
といって以下のようにテンプレートを修正しました。

Resources:
  # Grape:
  #   Type: AWS::S3::Bucket
  #   Properties:
  #     BucketName: !Sub 'apple-bucket-${AWS::AccountId}'
  #     AccessControl: Private

  Apple:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'apple-bucket-${AWS::AccountId}'
      AccessControl: Private

  Banana:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'banana-bucket-${AWS::AccountId}'
      AccessControl: Private

  Orange:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'orange-bucket-${AWS::AccountId}'
      AccessControl: Private

このテンプレートを用いて、スタックを更新すると

「Apple」リソースの作成に失敗しました。

エラー「apple-bucket-xxxxxx already exists in stack」

「apple-bucket-xxxxxx」というバケットはもう既に存在するので作れませんよ。」というエラーです。
これより、CloudFormationはリソースの追加→削除の順で処理を行うことが分かります。
以下のステップを踏むことで回避可能です。
①「Grape」を削除した状態でデプロイ(Appleはまだ追加しない)
②Appleを追加したテンプレートで再デプロイ

♪.:*:’゜☆.:*:’゜♪.:*:’☆.:*:・’♪.:*:・’゜♪.:*:’゜☆.:*:’゜♪.:*:’☆.:*:・’♪.:*:・’゜

ネストされたスタックの場合はどうなるのか。

以下のようなスタック構成があったとします。
親スタック(root)
┣ 子スタック(fruit)🍎🍌🍊
┃ ┣ Apple
┃ ┣ Banana
┃ ┣ Orange
┃ ┗ Biscuit
┗ 子スタック(snack)🍫🍪🍭
 ┣ Chocolate
 ┣ Cookie
 ┗ Candy

各テンプレートは以下の通り。

★親テンプレート
※DependsOnによって、子スタック(fruit)作成後に子スタック(snack)が作成されるようにしています。

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  fruit:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://バケット名.s3.ap-northeast-1.amazonaws.com/S3BucketTemplate_fruit.yml"

  snack:
    Type: AWS::CloudFormation::Stack
    DependsOn: fruit
    Properties:
      TemplateURL: !Sub "https://バケット名.s3.ap-northeast-1.amazonaws.com/S3BucketTemplate_snack.yml"

★子テンプレート(fruit)

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  Apple:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'apple-bucket-${AWS::AccountId}'
      AccessControl: Private

  Banana:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'banana-bucket-${AWS::AccountId}'
      AccessControl: Private

  Orange:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'orange-bucket-${AWS::AccountId}'
      AccessControl: Private

  Biscuit:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'biscuit-bucket-${AWS::AccountId}'
      AccessControl: Private

★子テンプレート(stack)

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  Chocolate:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'chocolate-bucket-${AWS::AccountId}'
      AccessControl: Private

  Cookie:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'cookie-bucket-${AWS::AccountId}'
      AccessControl: Private

  Candy:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'candy-bucket-${AWS::AccountId}'
      AccessControl: Private

親テンプレートをプロビジョニングすると、7つのS3バケットができました。

プロビジョニングした後に、
「子テンプレート(fruit)に間違って「Biscuit」リソースを記述しちゃってた。」
といって、子テンプレート(fruit)から「Biscuit」リソースを削除し、子テンプレート(stack)に記載しなおしました。

「あ、ついでに「Grape」🍇バケットも追加しよう」
そういって、子テンプレート(fruit)に追記もしました。

構成は以下のようになりました。

親スタック
┣ 子スタック(fruit)🍎🍌🍊🍇
┃ ┣ Apple
┃ ┣ Banana
┃ ┣ Orange
┃ ┣ Biscuit ←削除
┃ ┗ Grape ←新たに追加
┗ 子スタック(snack)🍫🍪🍭
 ┣ Chocolate
 ┣ Cookie
 ┣ Candy
 ┗ Biscuit ←新たに追加

各テンプレートは以下のようになりました。

★子テンプレート(fruit)

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  Apple:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'apple-bucket-${AWS::AccountId}'
      AccessControl: Private

  Banana:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'banana-bucket-${AWS::AccountId}'
      AccessControl: Private

  Orange:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'orange-bucket-${AWS::AccountId}'
      AccessControl: Private

  # Biscuit:
  #   Type: AWS::S3::Bucket
  #   Properties:
  #     BucketName: !Sub 'biscuit-bucket-${AWS::AccountId}'
  #     AccessControl: Private

  Grape:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'grape-bucket-${AWS::AccountId}'
      AccessControl: Private

★子テンプレート(snack)

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  Chocolate:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'chocolate-bucket-${AWS::AccountId}'
      AccessControl: Private

  Cookie:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'cookie-bucket-${AWS::AccountId}'
      AccessControl: Private

  Candy:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'candy-bucket-${AWS::AccountId}'
      AccessControl: Private

  Biscuit:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'biscuit-bucket-${AWS::AccountId}'
      AccessControl: Private

この状態でrootテンプレートを更新すると。。エラーが発生しました。

子スタック(snack)の更新に失敗したようです。
子スタック(snack)に移動し、根本原因を見ると、

「biscuit-bucket-xxxxxxはすでに存在しているので同じ名前のバケットは作れない」という内容です。

親スタックから見える、子スタック(fruit)のステータスは、「UPDATE_COMPLATE」になってるんだから、子スタック(fruit)のBiscuitリソースはその時消えたのだろう、と思いますが実際には、消えていないのです。
CloudFormationは、リソースを「追加 → 更新 → 削除」の順に処理します。
今回は
子スタック(fruit):Grapeリソースの追加
 ※まだ削除処理はせず、次の子スタックの実行へ遷移。

子スタック(snack):Biscuitリソースの追加
★ここで、エラーが発生したということです

以下のステップを踏むことで回避可能です。
①子テンプレート(fruit)から、「Biscuit」を削除・「Grape」を追加した状態でデプロイ(子スタック(snack)に「Biscuit」はまだ追加しない)
②子スタック(snack)に「Biscuit」を追加したテンプレートで再デプロイ

以上です!
備忘用なので読みにくいと思います。
CICDパイプラインでプロビジョニングする際に、気を付けておこう。

ここまで読んでくださいましてありがとうございました😁

余談:最近、長島スパーランドへジェットコースターを乗りに行きました。白鯨とスチールドラゴン、とっても楽しかったです!どちらも浮遊感はそこまでで、マリオカートに乗っているような気分になれました。

コメントを残す

メールアドレスが公開されることはありません。