AWS CloudFormation ネストされたスタックの変更セットの実行方法

2020.08.07

はじめに

こんにちは、あるいはこんばんは なかはらです。

CloudFormation使ってますか?AWSリソースをコード化し、便利にAWS環境を構築できるのでよく利用しています。設定変更や管理のしやすさを考えてテンプレートをサービスや機能単位で分割し、ネストさせていますが、設定変更する前の変更セットを使ったチェックを実行するとネストされたスタックの変更対象が表示されません。
今回は、ネストされたスタックの変更セットで変更対象が確認できる方法を書き留めておきます。

ルートスタックの変更セットの場合

まずはルートスタックから変更セットを実行するとどうなるのか、確認していきます。VPC、セキュリティグループ(SG)に分割したテンプレートを用意し、ネストされたスタックを作成しました。

test-stack.yml

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  SystemName:
    Type: String
    MinLength: 1
    MaxLength: 10
    Default: test
  EnvironmentName:
    Description: environment name
    Type: String
    AllowedValues: 
      - dev
      - stg
      - prd
    Default: dev

Resources: 
  VPC: 
    Type: AWS::CloudFormation::Stack
    Properties: 
      TemplateURL: "https://S3バケットFQDN/dev/vpc.yml"
      Parameters: 
        SystemName: !Sub ${SystemName}
        EnvironmentName: !Sub ${EnvironmentName}
  SG: 
    Type: AWS::CloudFormation::Stack
    DependsOn: VPC
    Properties: 
      TemplateURL: "https://S3バケットFQDN/dev/sg.yml"
      Parameters: 
        SystemName: !Sub ${SystemName}
        EnvironmentName: !Sub ${EnvironmentName}

vpc.yml

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  SystemName:
    Type: String
    MinLength: 1
    MaxLength: 10
    Default: test
  EnvironmentName:
    Description: environment name
    Type: String
    AllowedValues: 
      - dev
      - stg
      - prd
    Default: dev

Resources:
  VPC:
    Type: AWS::EC2::VPC    
    Properties:
      CidrBlock: 10.250.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - 
          Key: Name
          Value: !Sub ${SystemName}-${EnvironmentName}-vpc
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    
    Properties:
      Tags:
        - 
          Key: Name
          Value: !Sub ${SystemName}-${EnvironmentName}-igw
  DHCPOptions: 
    Type: AWS::EC2::DHCPOptions    
    Properties: 
        DomainName: ap-northeast-1.compute.internal
        DomainNameServers: 
          - AmazonProvidedDNS
        Tags: 
          - 
            Key: Name
            Value: !Sub ${SystemName}-${EnvironmentName}-dopt
  DHCPOptionsAssociation:
    Type: AWS::EC2::VPCDHCPOptionsAssociation    
    Properties:
      VpcId: !Ref VPC
      DhcpOptionsId: !Ref DHCPOptions
  RouteTable1:
    Type: AWS::EC2::RouteTable    
    Properties:
      VpcId: !Ref VPC
      Tags:
        - 
          Key: Name
          Value: !Sub ${SystemName}-${EnvironmentName}-front-rtb
  Route:
    Type: AWS::EC2::Route    
    Properties:
      RouteTableId: !Ref RouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  AttachInternetGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
  NetworkAcl1:
    Type: AWS::EC2::NetworkAcl
    Properties: 
      VpcId: !Ref VPC
      Tags:
        - 
          Key: Name
          Value: !Sub ${SystemName}-${EnvironmentName}-nacl
  NetworkAclEntry1:
      Type: AWS::EC2::NetworkAclEntry
      Properties:
         NetworkAclId: !Ref NetworkAcl1
         RuleNumber: 100
         Protocol: -1
         RuleAction: allow
         Egress: true
         CidrBlock: 0.0.0.0/0
         Icmp:
            Code: -1
            Type: -1
         PortRange:
            From: -1
            To: -1
  NetworkAclEntry2:
      Type: AWS::EC2::NetworkAclEntry
      Properties:
         NetworkAclId: !Ref NetworkAcl1
         RuleNumber: 100
         Protocol: -1
         RuleAction: allow
         Egress: false
         CidrBlock: 0.0.0.0/0
         Icmp:
            Code: -1
            Type: -1
         PortRange:
            From: -1
            To: -1
  NetworkAclAssociation1:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref Subnet1
      NetworkAclId: !Ref NetworkAcl1
  Subnet1:
    Type: AWS::EC2::Subnet
    Properties: 
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 10.11.0.0/24
      VpcId: !Ref VPC
      MapPublicIpOnLaunch: false
      Tags:
        - 
          Key: Name
          Value: !Sub ${SystemName}-${EnvironmentName}-front-a-subnet
  Subnet1RouteTableAssciation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref Subnet1
      RouteTableId: !Ref RouteTable1

Outputs:
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub ${SystemName}-${EnvironmentName}-vpc
  Subnet1:
    Value: !Ref Subnet1
    Export:
      Name: !Sub ${SystemName}-${EnvironmentName}-subnet1

sg.yml

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  SystemName:
    Type: String
    MinLength: 1
    MaxLength: 10
    Default: test
  EnvironmentName:
    Description: environment name
    Type: String
    AllowedValues: 
      - dev
      - stg
      - prd
    Default: dev

Resources:
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      VpcId: {"Fn::ImportValue": !Sub "${SystemName}-${EnvironmentName}-vpc"}
      GroupDescription: EC2SG
      Tags:
        - 
          Key: Name
          Value: !Sub ${SystemName}-${EnvironmentName}-ec2-sg
  EC2SGIngress1:
    Type: AWS::EC2::SecurityGroupIngress
    Properties: 
      GroupId: !Ref EC2SG
      IpProtocol: tcp
      FromPort: 22
      ToPort: 22
      SourceSecurityGroupId: !Ref EC2SG

Outputs:
  EC2SG:
    Value: !Ref EC2SG
    Export:
      Name: !Sub ${SystemName}-${EnvironmentName}-ec2-sg

S3バケットにVPC、SGのテンプレートを格納し、CloudFormationでルートスタックを実行するとNested Stackの完成です。

ここからセキュリティグループのインバウンドルールを追加(EC2SGIngress2を追加)しました。

sg.yml

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  SystemName:
    Type: String
    MinLength: 1
    MaxLength: 10
    Default: test
  EnvironmentName:
    Description: environment name
    Type: String
    AllowedValues: 
      - dev
      - stg
      - prd
    Default: dev

Resources:
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      VpcId: {"Fn::ImportValue": !Sub "${SystemName}-${EnvironmentName}-vpc"}
      GroupDescription: EC2SG
      Tags:
        - 
          Key: Name
          Value: !Sub ${SystemName}-${EnvironmentName}-ec2-sg
  EC2SGIngress1:
    Type: AWS::EC2::SecurityGroupIngress
    Properties: 
      GroupId: !Ref EC2SG
      IpProtocol: tcp
      FromPort: 22
      ToPort: 22
      SourceSecurityGroupId: !Ref EC2SG
  EC2SGIngress2:
    Type: AWS::EC2::SecurityGroupIngress
    Properties: 
      GroupId: !Ref EC2SG
      IpProtocol: tcp
      FromPort: 1024
      ToPort: 65525
      SourceSecurityGroupId: !Ref EC2SG

Outputs:
  EC2SG:
    Value: !Ref EC2SG
    Export:
      Name: !Sub ${SystemName}-${EnvironmentName}-ec2-sg

S3バケットに格納してルートスタックから変更セットを実施してみます。

VPC、SGどちらのスタックもModifyとなってしまいました。ルートスタックからだとネストされたスタックのどのリソースが変更されたか確認することができません。また、ルートスタックのテンプレートを変更していないのにModifyになってしまう為、VPCスタックもなにか更新されてしまうのか?と勘違いしてしまいます。ネストされたスタック管理の悩ましいところです。

ネストされたスタックの変更セット

ルートスタックではなく、ネストされたスタックに対して変更セットを実施すればいいんじゃないかと考えてやってみました。

ネストされたSGスタックから「既存スタックの変更セットの作成」をクリックします。

ネストされたスタックから操作すると以下のポップアップが必ず表示されます。ルートスタックから更新されないと正しく更新できなくなる可能性があります。例えばルートスタックから渡されたパラメータを使ったResourcesがネストされたスタックにある場合、エラーとなってしまいます。今回は、ネストされたスタックのテンプレートにも同じParametersを用意している且つ、変更セットで変更対象を確認する操作なので既存のスタックに影響はない(はず)です。

「ネストされたスタックの変更セットを作成する」をチェックし、「変更セットの作成」をクリックします。

更新したテンプレートの変更を確認したいので、「既存テンプレートを置き換える」をチェックしてください。Amazon S3 URLまたはテンプレートファイルのアップロードを選択し、テンプレートを指定してください。

操作を進めて変更セットを作成すると変更対象を確認することができます。
注意事項としては、この画面では「実行」せず、変更セットを「削除」してください。さきほどのポップアップでも記載されている通り、更新はルートスタックから実行するのが推奨です。

終わりに

ルートスタックから確認できるのが望ましいのですが、現時点では確認することができません。ネストされたスタックだけで変更セットが実行できるようにParametersやOutputsなどを活用し、ネストされたスタックだけで実行できるようにテンプレートを作成すれば変更対象を確認することができます。