📝

CloudFormationの変更セットってなんだろ?

2021/06/02に公開

変更セットとは?

スタックを更新する必要がある場合は、変更の実装前に実行中のリソースに与える影響を理解することで、安心してスタックを更新できます。変更セットを使用すると、スタックの変更案が実行中のリソースに与える可能性がある影響 (たとえば、変更によって重要なリソースが削除されたり置き換えられたりしないか) を確認できます。変更セットの実行を確定したときのみ AWS CloudFormation によってスタックが変更されるため、変更案のまま続行するか別の変更セットを作成して他の変更を検討するかを決定できます。
変更セットを使用したスタックの更新 - AWS CloudFormationより

一言で言うと、安心してスタックを更新できる機能です。
3行で言うと
スタックの更新前に
リソースの更新や削除を
把握することができる
という機能です。

使うとどうなるの?

変更セットを使用すると、スタックの変更案が実行中のリソースに与える可能性がある影響 (たとえば、変更によって重要なリソースが削除されたり置き換えられたりしないか) を確認できます。

これが大きなメリットです。
変更セットを作ることで、実際の更新はしないけど、更新後にどうなるかを前もって知ることができます。これにより、意図した通りに更新を実施したり、問題がある場合にはドリフトの検出やテンプレートの修正を行うことができるのです。ある種のテストといったかんじですね。

そんなの使わなくても更新できるけど?

はい、更新できます。でも更新をポチった後に「DELETE_COMPLETE」って表示されてからじゃ遅いんです。消えたリソースは帰ってきません・・・。まあ半分冗談ですが半分本気です。
変更セットを使わない更新を直接更新と言います。

更新をスタックにすばやくデプロイするには、直接更新を実行します。直接更新では、テンプレートを送信するか、スタック内のリソースに対して更新を指定する入力パラメータを送信すると、AWS CloudFormation によりすぐにデプロイされます。
スタックの直接更新 - AWS CloudFormationより

直接更新はすばやくできますが、更新後にリソースがどうなるかは更新が終わらないとわかりません。なので、軽い気持ちで更新したけど終わってみたら
「意図した内容と違う」
「リソースが消えた」
ということが起こる可能性があります。

こういったトラブルを防ぐために、変更セットを利用することがベストプラクティスとなっています。
スタックを更新する前に変更セットを作成する

どうやって作るの?

作成方法は3種類です。

  1. AWS CloudFormation コンソール
  2. AWS CLI
  3. AWS CloudFormation API

特に難しいことはなく、直接更新と同じようなステップで進めることができます。

やってみよう!

百聞は一見に如かずなので、とりあえずコンソールから試してみましょう!
まずはVPCを作ります。
使用したテンプレートは以下の通りです。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "SampleVpc": {
      "Type": "AWS::EC2::VPC",
      "Properties": {
        "CidrBlock": "10.0.0.0/16",
        "EnableDnsSupport": true,
        "EnableDnsHostnames": false,
        "InstanceTenancy": "default",
        "Tags": [
          {
            "Key": "Name",
            "Value": "Sample-vpc"
          }
        ]
      }
    }
  }
}

マネジメントコンソールからCloudFormationのコンソールに移動しましょう。

スタックの作成から新しいリソースを使用を選択します。

テンプレートファイルをアップロードして、次へをクリックします。

スタックの名前を入力して、次へをクリックします。

以降はデフォルトのまま進めますが、最後だけ注意してください!
スタックの作成ではなく、変更セットの作成をクリックします。これにより、まだVPCは作られず変更セットだけが作られます。

変更セット名と説明はデフォルトのままで変更セットを作成します。

作成直後は何も表示されません。

右上の更新ボタンをクリックすると、画面下部に変更内容が表示されます。
今回はVPCがAdd、つまり追加されるという内容になっており、意図通りです。
ちなみにこの状態で実行せずに保持しておくこともできるので、変更セットの作成と実行のタイミングは分けることができます。

それでは変更セットを実行してみましょう!

しばらくすると作成が完了します。

VPCのコンソールでも見てみましょう!
うん、ちゃんとSample_vpcができてる。
変更セットからでも簡単に実行できることがわかりました!

サブネットを追加してみよう!

先ほど作成したVPCにサブネットを2つ追加してみます。
使用したテンプレートは以下の通りです。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "SampleVpc": {
      "Type": "AWS::EC2::VPC",
      "Properties": {
        "CidrBlock": "10.0.0.0/16",
        "EnableDnsSupport": true,
        "EnableDnsHostnames": false,
        "InstanceTenancy": "default",
        "Tags": [
          {
            "Key": "Name",
            "Value": "Sample-vpc"
          }
        ]
      }
    },
    "SampleSubnet1": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "AvailabilityZone": {
          "Fn::Sub": "${AWS::Region}a"
        },
        "CidrBlock": "10.0.10.0/24",
        "VpcId": {
          "Ref": "SampleVpc"
        },
        "MapPublicIpOnLaunch": false
      }
    },
    "SampleSubnet2": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "AvailabilityZone": {
          "Fn::Sub": "${AWS::Region}c"
        },
        "CidrBlock": "10.0.20.0/24",
        "VpcId": {
          "Ref": "SampleVpc"
        },
        "MapPublicIpOnLaunch": false
      }
    }
  }
}

CloudFormationのコンソールで先ほど作成したスタックを選択して、スタックアクションから既存スタックの変更セットを作成をクリックします。

既存テンプレートを置き換えるを選択して新しいテンプレートファイルをアップロードします。

パラメータは特にないので次へをクリックします。

以降はデフォルトのままとして、最後に変更セットの作成をクリックします。

変更セット名と説明もデフォルトのまま作成します。

更新ボタンをクリックすると、サブネットが2つ追加されるという変更が表示されます。意図通りの内容ですね。

それではこれを実行しましょう!

作成が完了したらVPCコンソールでサブネットを見てみましょう!
名前を付け忘れましたが、10.0.10.0/2410.0.20.0/24のサブネットができています。

サブネットに名前をつけてみよう!

計画通りのように言いましたが、名前の付け忘れはわざとじゃなく本当に忘れてました・・・。でもせっかくなので更新だとどのように表示されるのかも知りたいのでやってみましょう!ただでは起きませんw
使用したテンプレートは以下の通りです。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "SampleVpc": {
      "Type": "AWS::EC2::VPC",
      "Properties": {
        "CidrBlock": "10.0.0.0/16",
        "EnableDnsSupport": true,
        "EnableDnsHostnames": false,
        "InstanceTenancy": "default",
        "Tags": [
          {
            "Key": "Name",
            "Value": "Sample-vpc"
          }
        ]
      }
    },
    "SampleSubnet1": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "AvailabilityZone": {
          "Fn::Sub": "${AWS::Region}a"
        },
        "CidrBlock": "10.0.10.0/24",
        "VpcId": {
          "Ref": "SampleVpc"
        },
        "MapPublicIpOnLaunch": false,
        "Tags": [
          {
            "Key": "Name",
            "Value": "Sample-Subnet-1a"
          }
        ]
      }
    },
    "SampleSubnet2": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "AvailabilityZone": {
          "Fn::Sub": "${AWS::Region}c"
        },
        "CidrBlock": "10.0.20.0/24",
        "VpcId": {
          "Ref": "SampleVpc"
        },
        "MapPublicIpOnLaunch": false,
        "Tags": [
          {
            "Key": "Name",
            "Value": "Sample-Subnet-1c"
          }
        ]
      }
    }
  }
}

基本的には先ほど同じ流れなので細かい部分はカットします。
変更セットが表示されました。先ほどまではAddでしたが今度はModifyになっていますね。

JSONの変更タブをクリックすると、詳しい変更内容がわかります。

resourceChangeというくくりが1つのリソースの変更単位です。
いろいろ書いてありますが、以下の部分に注目してください。

"details": [
    {
      "target": {
        "name": null,
        "requiresRecreation": "Never",
        "attribute": "Tags"
      },
      "causingEntity": null,
      "evaluation": "Static",
      "changeSource": "DirectModification"
    }
  ],

このdetailsの中身が詳しい変更内容です。
以下のようなかんじです。
"requiresRecreation": "Never":リソースの作り直しはNeverだからしないよ
"attribute": "Tags":変更される部分はTagsだよ
"causingEntity": null:別のリソースの変更が原因じゃないよ(nullなので)
"changeSource": "DirectModification":テンプレートに直接変更があったよ

より詳しい内容はResourceChangeDetail-AWS CloudFormationをご覧ください。
今回の場合、サブネットに新しくタグで名前を付けるけど、サブネットの作り直しはしないよ。という内容になっています。

作り直しがないので安心して実行しましょう!
更新完了後にサブネットを見ると、IDは同じだけどちゃんと名前が付与されていることがわかります。
変更セットの予告通りですね。

サブネットを1つ削除してみよう!

先ほど作成したサブネットのうち、1つを削除してみましょう!おっと、コンソールからじゃなくてCloudFormationでやりますよ。
使用したテンプレートは以下の通りです。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "SampleVpc": {
      "Type": "AWS::EC2::VPC",
      "Properties": {
        "CidrBlock": "10.0.0.0/16",
        "EnableDnsSupport": true,
        "EnableDnsHostnames": false,
        "InstanceTenancy": "default",
        "Tags": [
          {
            "Key": "Name",
            "Value": "Sample-vpc"
          }
        ]
      }
    },
    "SampleSubnet1": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "AvailabilityZone": {
          "Fn::Sub": "${AWS::Region}a"
        },
        "CidrBlock": "10.0.10.0/24",
        "VpcId": {
          "Ref": "SampleVpc"
        },
        "MapPublicIpOnLaunch": false,
        "Tags": [
          {
            "Key": "Name",
            "Value": "Sample-Subnet-1a"
          }
        ]
      }
    }
  }
}

変更セット作成の流れは同じですので、変更セットの表示までカット!
むむ!Removeが出てきた!!
もちろん今回は想定通りですが、いつも通りの更新をしようと思ってるのにRemoveが出てきたら要注意!
「その更新ちょっと待った!!」です。

冒頭で述べましたが、

スタックの更新前に
リソースの更新や削除を
把握することができる

というのがここで役に立ちます。変更セットを作成した段階で予期せぬRemoveが出たら、テンプレートの修正やドリフトの検出で予期せぬリソース削除を防ぎましょう!
もちろん今回は意図しての削除なので実行します。さよならサブネット2さん・・・。

意図通り1つだけサブネットが消えました。

まとめ

結構なボリュームになってしまいましたね。ひとまずお疲れさまでした!
安心して意図通りにCloudFormationを実行するには不可欠な機能が変更セットです。知らぬ間にテンプレートや既存リソースの設定が変更され、その変更に気づかず直接更新を実行してリソースが消えた・・・なんてことにならないように、変更セットをうまく使っていきましょう!
ドリフトの検出と併用することでより安心して更新をすることができますので、こちらもぜひご覧ください!

変更セット + ドリフトの検出でよりよいCloudFormationライフを!

Discussion