🐡

AWS CloudFormation変更セットの見方を整理する

2023/08/26に公開

はじめに

AWS CloudFormationの変更セットは、変更予定のリソースや変更内容を出力できるのですが、ときどき表示された内容の見方が分からなくなるので、簡単に整理しました。

変更セットとは

変更セットを作成すると、新規・既存のCloudFormationスタックに対する変更予定の内容を明示します。内容に問題がなければ変更セットを実行し、スタックに対する変更を適用します。

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

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets.html

describe-change-setの見方

変更セットの作成後にスタックへの変更予定内容を確認するには DescribeChangeSet APIにリクエストを送ります。AWS CLIの例から、どのようなレスポンスが返ってくるかを見てみます。

https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudformation/describe-change-set.html

{
    "Changes": [
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Modify",
                "LogicalResourceId": "function",
                "PhysicalResourceId": "my-function-SEZV4XMPL4S5",
                "ResourceType": "AWS::Lambda::Function",
                "Replacement": "False",
                "Scope": [
                    "Properties"
                ],
                "Details": [
                    {
                        "Target": {
                            "Attribute": "Properties",
                            "Name": "Timeout",
                            "RequiresRecreation": "Never"
                        },
                        "Evaluation": "Static",
                        "ChangeSource": "DirectModification"
                    }
                ]
            }
        }
    ],
    "ChangeSetName": "my-change-set",
    "ChangeSetId": "arn:aws:cloudformation:us-west-2:123456789012:changeSet/my-change-set/4eca1a01-e285-xmpl-8026-9a1967bfb4b0",
    "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/my-stack/d0a825a0-e4cd-xmpl-b9fb-061c69e99204",
    "StackName": "my-stack",
    "Description": null,
    "Parameters": null,
    "CreationTime": "2019-10-02T05:20:56.651Z",
    "ExecutionStatus": "AVAILABLE",
    "Status": "CREATE_COMPLETE",
    "StatusReason": null,
    "NotificationARNs": [],
    "RollbackConfiguration": {},
    "Capabilities": [
        "CAPABILITY_IAM"
    ],
    "Tags": null
}

AWS CLIドキュメントより抜粋

ここでは変更予定の内容に着目するので 'Changes[].ResourceChange' の部分のみ言及します。

  • Action: CloudFormationがリソースに対して行うアクションを表示します。アクションの種類は Add Modify Remove Import Dynamic になります。
  • ResourceType: AWSリソースのタイプ ( AWS::EC2::Instance など)
  • Replacement: ActionModify の場合に表示されます。CloudFormationへの変更適用時に新しいリソースを作成して既存リソースと置き換えるか否かを表示します。値は True False Conditionally です。
  • Scope: ActionModify の場合に表示されます。この更新のトリガーとなる変更がどの属性かを表示します。値は Metadata Properties Tags です。
  • Details: ActionModify の場合に表示されます。リソースに対しどのような変更が行われるかを表示します。
    • Target: リソースの変更される箇所と、リソースの再作成がされるかを表示します。
      • Attribute: この更新のトリガーとなる変更がどの属性かを表示します。値は Metadata Properties Tags です。
      • Name: AttributeProperties の場合、変更するPropertiesの名前を表示します。
      • RequiresRecreation: AttributeProperties の場合、このリソースが再作成されるかどうか表示します。値は Never Always Conditionally です。
    • Evaluation: CloudFormationが変更する値を決定できるかどうか、ターゲットの値が変更されるかどうかを表示します。値は Static Dynamic です。Static の場合は変更前に値を決定できますが、Dynamic の場合は決定できません。
    • ChangeSource: テンプレートに対してどのような方法で変更が加えられたかを表示します (例: DirectModification はテンプレートを直接修正した場合)。値は ResourceReference ParameterReference ResourceAttribute DirectModification Automatic です。

ここからいくつかの点を抜き出します。

  • ReplacementFalse 以外の場合: True の場合はリソースの置き換えが発生するのでもちろん注意が必要ですが、 Conditionally の場合は、リソースの置き換えが発生するか否か、利用者側で判断する必要があります。
  • EvaluationDynamic の場合: Dynamic で値が決定できないのはどういう場合かというと、CloudFormationテンプレートで RefFn::GetAtt などを利用しており、値が組込み関数の結果に依存する時です。
  • ChangeSourceDirectModification 以外の場合DirectModification 以外の場合、Ref Fn::GetAtt などの組み込み関数の結果が変わったとき、あるいは AWS::CloudFormation::Stack を利用する場合です。特に AWS::CloudFormation::Stack の場合、子スタックの変更の有無にかかわらず Automatic とするため、実際に変更があるかは確認が必要です。

試してみる

ここではVPC/Subnetを使っています。事前に以下のファイルを使ってVPCを作成します。

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  EnvName:
    Type: String
    Default: cfn-changeset-test

  VPCCIDR:
    Type: String
    Default: "10.0.0.0/16"

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      Tags:
        - Key: Name
          Value: !Sub ${EnvName}-VPC

作成後、まずはファイルを修正してSubnetを追加します。

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  EnvName:
    Type: String
    Default: cfn-changeset-test

  VPCCIDR:
    Type: String
    Default: "10.0.0.0/16"
  # 追加
  PrivateSubnetCIDR:
    Type: String
    Default: "10.0.0.0/24"

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      Tags:
        - Key: Name
          Value: !Sub ${EnvName}-VPC

  # 追加
  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: !Ref PrivateSubnetCIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvName}-PrivateSubnet

変更セットを作成します。変更セットの名称はここでは add-subnet としました。

$ aws cloudformation create-change-set --stack-name cfn-changeset-test --change-set-name add-subnet --template-body file://cfn-changeset-test.yaml
{
    "Id": "arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/add-subnet/0be22236-4617-434d-a1d1-82ac135acf3a",
    "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cfn-changeset-test/165e80c0-401f-11ee-b005-0eb9feabba13"
}

この時の変更セットの内容は以下の通りです。想定通り PrivateSubnet という名称のサブネットの追加 ( Add ) を認識しています。

$ aws cloudformation describe-change-set --stack-name cfn-changeset-test --change-set-name add-subnet
{
    "Changes": [
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Add",
                "LogicalResourceId": "PrivateSubnet",
                "ResourceType": "AWS::EC2::Subnet",
                "Scope": [],
                "Details": []
            }
        }
    ],
    "ChangeSetName": "add-subnet",
    "ChangeSetId": "arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/add-subnet/0be22236-4617-434d-a1d1-82ac135acf3a",
    "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cfn-changeset-test/165e80c0-401f-11ee-b005-0eb9feabba13",
    "StackName": "cfn-changeset-test",
    "Description": null,
    "Parameters": [
        {
            "ParameterKey": "PrivateSubnetCIDR",
            "ParameterValue": "10.0.0.0/24"
        },
        {
            "ParameterKey": "VPCCIDR",
            "ParameterValue": "10.0.0.0/16"
        },
        {
            "ParameterKey": "EnvName",
            "ParameterValue": "cfn-changeset-test"
        }
    ],
    "CreationTime": "2023-08-21T12:41:23.976000+00:00",
    "ExecutionStatus": "AVAILABLE",
    "Status": "CREATE_COMPLETE",
    "StatusReason": null,
    "NotificationARNs": [],
    "RollbackConfiguration": {
        "RollbackTriggers": []
    },
    "Capabilities": [],
    "Tags": null,
    "ParentChangeSetId": null,
    "IncludeNestedStacks": false,
    "RootChangeSetId": null
}

なおAWSマネジメントコンソールでも同様の情報を確認できます。

上記変更セットを適用して PrivateSubnet サブネットを作成します。

$ aws cloudformation execute-change-set --stack-name cfn-changeset-test --change-set-name add-subnet

$ aws cloudformation describe-stack-resources --stack-name cfn-changeset-test
{
    "StackResources": [
        {
            "StackName": "cfn-changeset-test",
            "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cfn-changeset-test/165e80c0-401f-11ee-b005-0eb9feabba13",
            "LogicalResourceId": "PrivateSubnet",
            "PhysicalResourceId": "subnet-06e42c89f18e0ef41",
            "ResourceType": "AWS::EC2::Subnet",
            "Timestamp": "2023-08-21T12:49:34.928000+00:00",
            "ResourceStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackResourceDriftStatus": "NOT_CHECKED"
            }
        },
        {
            "StackName": "cfn-changeset-test",
            "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cfn-changeset-test/165e80c0-401f-11ee-b005-0eb9feabba13",
            "LogicalResourceId": "VPC",
            "PhysicalResourceId": "vpc-0623ffb826dd2e9c1",
            "ResourceType": "AWS::EC2::VPC",
            "Timestamp": "2023-08-21T12:34:51.812000+00:00",
            "ResourceStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackResourceDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

続いて、再びファイルを修正します。ここでは Subnetの削除VPCのDNSホスト名の有効化 という2つの変更をします。

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  EnvName:
    Type: String
    Default: cfn-changeset-test

  VPCCIDR:
    Type: String
    Default: "10.0.0.0/16"

  #PrivateSubnetCIDR:
  #  Type: String
  #  Default: "10.0.0.0/24"

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true    # 追加
      EnableDnsHostnames: true  # 追加
      Tags:
        - Key: Name
          Value: !Sub ${EnvName}-VPC

  #PrivateSubnet:
  #  Type: AWS::EC2::Subnet
  #  Properties:
  #    AvailabilityZone: !Select [0, !GetAZs ""]
  #    CidrBlock: !Ref PrivateSubnetCIDR
  #    VpcId: !Ref VPC
  #    Tags:
  #      - Key: Name
  #        Value: !Sub ${EnvName}-PrivateSubnet

上記ファイルを使って変更セットを作成します。ここでの変更セット名は delete-subnet-enable-dns-hostname です。

$ aws cloudformation create-change-set --stack-name cfn-changeset-test --change-set-name delete-subnet-enable-dns-hostname --template-body file://cfn-changeset-test.yaml
{
    "Id": "arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/delete-subnet-enable-dns-hostname/36d1a0e8-7705-4146-a485-7b6c3bb48dec",
    "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cfn-changeset-test/165e80c0-401f-11ee-b005-0eb9feabba13"
}

作成した変更セットの内容を確認します。ここでも想定通りサブネットの削除 (Remove) とVPCの変更 (Modify) の2つの操作が認識されています。また Modify のほうは変更内容も記載されています。

今回の変更のうち EnableDnsSupportのみを取り上げると、 Detail 以降の内容は以下のように読み取れます。

  • 変更対象を Target で確認すると、 Properties 中の EnableDnsSupport であることがわかります。また RequiresRecreation を見ると、この変更によるリソースの再作成は発生しないことがわかります (これはCloudFormationのドキュメントを見ても一致する挙動です)。
  • Evaluation を見ると Static であるため、CloudFormationが決定できる値になることがわかります。
  • ChangeSource を見ると DirectModification であるため、今回の変更はテンプレートを直接修正して発生したことがわかります。
$ aws cloudformation describe-change-set --stack-name cfn-changeset-test --change-set-name delete-subnet-enable-dns-hostname
{
    "Changes": [
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Remove",
                "LogicalResourceId": "PrivateSubnet",
                "PhysicalResourceId": "subnet-06e42c89f18e0ef41",
                "ResourceType": "AWS::EC2::Subnet",
                "Scope": [],
                "Details": []
            }
        },
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Modify",
                "LogicalResourceId": "VPC",
                "PhysicalResourceId": "vpc-0623ffb826dd2e9c1",
                "ResourceType": "AWS::EC2::VPC",
                "Replacement": "False",
                "Scope": [
                    "Properties"
                ],
                "Details": [
		    # "EnableDnsSupport" に関する記述はこのあたり
                    {
                        "Target": {
                            "Attribute": "Properties",
                            "Name": "EnableDnsSupport",
                            "RequiresRecreation": "Never"
                        },
                        "Evaluation": "Static",
                        "ChangeSource": "DirectModification"
                    },
                    {
                        "Target": {
                            "Attribute": "Properties",
                            "Name": "EnableDnsHostnames",
                            "RequiresRecreation": "Never"
                        },
                        "Evaluation": "Static",
                        "ChangeSource": "DirectModification"
                    }
                ]
            }
        }
    ],
    "ChangeSetName": "delete-subnet-enable-dns-hostname",
    "ChangeSetId": "arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/delete-subnet-enable-dns-hostname/36d1a0e8-7705-4146-a485-7b6c3bb48dec",
    "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cfn-changeset-test/165e80c0-401f-11ee-b005-0eb9feabba13",
    "StackName": "cfn-changeset-test",
    "Description": null,
    "Parameters": [
        {
            "ParameterKey": "VPCCIDR",
            "ParameterValue": "10.0.0.0/16"
        },
        {
            "ParameterKey": "EnvName",
            "ParameterValue": "cfn-changeset-test"
        }
    ],
    "CreationTime": "2023-08-21T12:53:09.492000+00:00",
    "ExecutionStatus": "AVAILABLE",
    "Status": "CREATE_COMPLETE",
    "StatusReason": null,
    "NotificationARNs": [],
    "RollbackConfiguration": {
        "RollbackTriggers": []
    },
    "Capabilities": [],
    "Tags": null,
    "ParentChangeSetId": null,
    "IncludeNestedStacks": false,
    "RootChangeSetId": null
}

AWSコンソールはこちら。

変更セットの作成・確認までを行うスクリプト

変更セットの作成と変更内容の確認は、AWS CLIコマンドを使えば済む話ですが、コマンドを毎回打つのがちょっと面倒でもあるので、簡単に試せそうなスクリプトを作成してみました。1点だけ補足すると、CloudFormationスタックがない状態から変更セットを作成するには、 --change-set-type オプションに CREATE を指定する必要があります。

#!/bin/bash

set -o errexit

STACK_NAME=$1
CHANGE_SET_NAME=$2
TEMPLATE_FILE=$3
NEW_OPTION=$4

CHANGESET_TYPE="UPDATE"

if [ $# = 4 ] && [ $4 = "CREATE" ]; then
    CHANGESET_TYPE="CREATE"
fi

CREATE_STATUS=`aws cloudformation create-change-set \
    --stack-name ${STACK_NAME} \
    --change-set-name ${CHANGE_SET_NAME} \
    --template-body file://${TEMPLATE_FILE} \
    --change-set-type ${CHANGESET_TYPE}`

echo "Creating changeset is successful. Please wait a while..."

aws cloudformation wait change-set-create-complete \
    --stack-name ${STACK_NAME} \
    --change-set-name ${CHANGE_SET_NAME}

aws cloudformation describe-change-set \
    --stack-name ${STACK_NAME} \
    --change-set-name ${CHANGE_SET_NAME} \
    --query 'Changes[].ResourceChange'
# 実行した例
$ ./test.sh cfn-changeset-test test cfn-changeset-test.yaml CREATE
Creating changeset is successful. Please wait a while...
[
    {
        "Action": "Add",
        "LogicalResourceId": "VPC",
        "ResourceType": "AWS::EC2::VPC",
        "Scope": [],
        "Details": []
    }
]

参考リンク

Discussion