你好ニーハウ!(こんにちは!)クラウドインテグレーション部の盧(ろ)です。
本記事はCloudFormationスタックのネスト構成について紹介していきます。
CloudFormationについてまだご存じでない方は、井川さんが執筆していたこちらの記事をご参照いただければと思います。
ネストスタックは、CloudFormationテンプレートの中で、別のCloudFormationテンプレートを呼び出して利用する仕組みです。
つまり、1つの大きなテンプレートを小さなテンプレートに分けて、親テンプレートからまとめて使う方法なんです。
例えば、ネットワーク設定、アプリケーション設定、データベース設定などを、それぞれ別々のテンプレートに分けて、必要な部分だけを親テンプレートで呼び出すことができます。
テンプレートの分割で保守性アップ
CloudFormationテンプレートは機能が増えるにつれて巨大化しがちです。
ネスト化すれば「VPC」「EC2」「RDS」などを個別ファイルに分割でき、テンプレートの見通しが良くなり、修正も部分的に行いやすくなります。
再利用性の向上
ネストスタックはモジュール化されたパーツのように使えます。
たとえば「共通のVPC定義」や「標準構成のセキュリティグループ」など、複数環境で使いまわしたい場合に非常に便利です。
チーム開発に強い
テンプレートを分割できることで、担当チームごとに管理を分担できます。
たとえば、以下のようにできます。
変更影響範囲の限定
親スタックで子スタックを参照している場合、一部の子スタックだけを更新できます。
これにより、不要な再デプロイ(=ダウンタイムやリスク)を減らすことができます。
子スタックの更新方法は以下の2通りがありますが、基本的には「親スタックからの更新」が推奨されます。
構造の明確化と理解しやすさ
ネスト化により、テンプレートに論理的な階層構造が生まれます。
例:
root.yaml
├── vpc.yaml
├── ec2.yaml
└── rds.yaml
これにより、初見の人にも「どこで何をしているか」がすぐに把握できるようになります。
では、実例を見ていきましょう!
作成する構成は以下とします。
テンプレートはサービス単位で分割して管理します。
ここでは、親スタック用のテンプレートをご紹介します。
子スタック用のテンプレート(VPC、EC2、RDS)については、記事の最後にある付録セクションでまとめて掲載していますので、ぜひそちらもチェックしてみてください。
AWSTemplateFormatVersion: "2010-09-09"
Description: 'Main template that creates VPC, EC2, and RDS resources using nested stacks'
Parameters:
  VPCTemplateURL:
    Description: template url for vpc
    Type: String
    Default: https://xxxxx/vpc.yml #使用するVPCテンプレートのS3 URLに置き換えてください
  EC2TemplateURL:
    Description: template url for ec2
    Type: String
    Default: https://xxxxx/ec2.yml #使用するEC2テンプレートのS3 URLに置き換えてください
  RDSTemplateURL:
    Description: template url for rds
    Type: String
    Default: https://xxxxx/rds.yml #使用するRDSテンプレートのS3 URLに置き換えてください
Resources:
  # VPC用の子スタックを呼び出し
  VPCStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref VPCTemplateURL
  # EC2用の子スタックを呼び出し
  EC2Stack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref EC2TemplateURL
      Parameters:
        VpcId: !GetAtt VPCStack.Outputs.VpcId # VPCスタックで出力されたVPC IDを参照
        PublicSubnetId: !GetAtt VPCStack.Outputs.PublicSubnetId # VPCスタックで出力されたPublic Subnet IDを参照
    DependsOn: VPCStack # VPCの構築完了後にEC2スタックの作成を開始
  # RDS用の子スタックを呼び出し
  RDSStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref RDSTemplateURL
      Parameters:
        VpcId: !GetAtt VPCStack.Outputs.VpcId
        PrivateSubnet1Id: !GetAtt VPCStack.Outputs.PrivateSubnet1Id # VPCスタックで出力されたPrivate Subnet IDを参照
        PrivateSubnet2Id: !GetAtt VPCStack.Outputs.PrivateSubnet2Id # VPCスタックで出力されたPrivate Subnet IDを参照
        EC2SecurityGroupId: !GetAtt EC2Stack.Outputs.EC2SecurityGroupId # EC2スタックの出力からセキュリティグループを取得
    DependsOn: VPCStack # VPCの構築完了後にRDSスタックの作成を開始
Outputs:
  # 親スタックのOutputsとして、子スタックの重要な情報を集約して出力
  EC2InstanceId:
    Description: ID of the EC2 instance
    Value: !GetAtt EC2Stack.Outputs.EC2InstanceId
  EC2PublicIP:
    Description: Public IP of the EC2 instance
    Value: !GetAtt EC2Stack.Outputs.EC2PublicIP
  RDSEndpoint:
    Description: Endpoint of the RDS instance
    Value: !GetAtt RDSStack.Outputs.RDSEndpoint
CloudFormationスタックのネスト構成について紹介しましたが、いかがでしたでしょうか。
複雑なシステム構成を作成する際には、ネスト構成をぜひ活用してみてください。
本記事が少しでもご参考になれば幸いです。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'VPC with public and private subnets'
Resources:
  # VPC本体の作成
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: nested-test-vpc
  
  # インターネットゲートウェイ(IGW)とVPCへのアタッチ
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: nested-test-igw
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  # パブリックサブネット(NAT Gateway配置用)
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: nested-test-public-subnet
  # プライベートサブネット1
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: nested-test-private-subnet1
  # プライベートサブネット2
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: 10.0.3.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: nested-test-private-subnet2
  # パブリックサブネット用のルートテーブルとルート設定(IGW経由のインターネット通信)
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: nested-test-public-rtb
  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PublicSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable
  # NAT GatewayとそのEIP(プライベートサブネット用)
  NatGatewayEIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP.AllocationId
      SubnetId: !Ref PublicSubnet
      Tags:
        - Key: Name
          Value: nested-test-ngw
  # プライベートサブネット用のルートテーブル(NAT経由でインターネットアクセス)
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: nested-test-private-rtb
  PrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway
  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable
  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable
Outputs:
  VpcId:
    Description: VPC ID
    Value: !Ref VPC
  PublicSubnetId:
    Description: Public Subnet ID
    Value: !Ref PublicSubnet
  PrivateSubnet1Id:
    Description: Private Subnet 1 ID
    Value: !Ref PrivateSubnet1
  PrivateSubnet2Id:
    Description: Private Subnet 2 ID
    Value: !Ref PrivateSubnet2
AWSTemplateFormatVersion: '2010-09-09'
Description: 'EC2 instance in a public subnet'
Parameters:
  VpcId:
    Type: String
    Description: VPC ID
  PublicSubnetId:
    Type: String
    Description: Public Subnet ID
Resources:
  # EC2用のセキュリティグループ(SSH許可)
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for EC2 instance
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: nested-test-ec2-sg
   # パブリックサブネット内のEC2インスタンス
   EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: ami-03598bf9d15814511 # 東京リージョンのAmazon Linux 2023
      SubnetId: !Ref PublicSubnetId
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      Tags:
        - Key: Name
          Value: nested-test-ec2
Outputs:
  EC2InstanceId:
    Description: ID of the EC2 instance
    Value: !Ref EC2Instance
  EC2PublicIP:
    Description: Public IP of the EC2 instance
    Value: !GetAtt EC2Instance.PublicIp
  EC2SecurityGroupId:
    Description: ID of the EC2 security group
    Value: !Ref EC2SecurityGroup
AWSTemplateFormatVersion: '2010-09-09'
Description: 'RDS instance in a private subnet'
Parameters:
  VpcId:
    Type: String
    Description: VPC ID
  PrivateSubnet1Id:
    Type: String
    Description: Private Subnet 1 ID
  PrivateSubnet2Id:
    Type: String
    Description: Private Subnet 2 ID
  EC2SecurityGroupId:
    Type: String
    Description: EC2 Security Group ID
Resources:
  # RDS用セキュリティグループ(EC2からのMySQL接続を許可)
  RDSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for RDS instance
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref EC2SecurityGroupId
      Tags:
        - Key: Name
          Value: nested-test-rds-sg
  # RDS用サブネットグループ(Multi-AZ用で2つのAZにまたがる)
  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Subnet group for RDS instance
      SubnetIds:
        - !Ref PrivateSubnet1Id
        - !Ref PrivateSubnet2Id
      Tags:
        - Key: Name
          Value: nested-test-rds-subnetgroup
  # RDSインスタンスの作成(MySQL)
  RDSInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: my-rds-instance
      AllocatedStorage: 20
      DBInstanceClass: db.t3.micro
      BackupRetentionPeriod: 7
      Engine: mysql
      MasterUsername: masteruser
      MasterUserPassword: masterpassword
      DBSubnetGroupName: !Ref DBSubnetGroup
      VPCSecurityGroups:
        - !Ref RDSSecurityGroup
      MultiAZ: false
      Tags:
        - Key: Name
          Value: nested-test-rds
Outputs:
  RDSEndpoint:
    Description: Endpoint of the RDS instance
    Value: !GetAtt RDSInstance.Endpoint.Address