CloudFormation設計を一段レベルアップ!ネスト構成で快適インフラ運用

CloudFormation設計を一段レベルアップ!ネスト構成で快適インフラ運用

目次

    はじめに

    你好ニーハウ!(こんにちは!)クラウドインテグレーション部の盧(ろ)です。
    本記事はCloudFormationスタックのネスト構成について紹介していきます。
    CloudFormationについてまだご存じでない方は、井川さんが執筆していたこちらの記事をご参照いただければと思います。

    ネスト構成について

    ネストスタックは、CloudFormationテンプレートの中で、別のCloudFormationテンプレートを呼び出して利用する仕組みです。
    つまり、1つの大きなテンプレートを小さなテンプレートに分けて、親テンプレートからまとめて使う方法なんです。
    例えば、ネットワーク設定、アプリケーション設定、データベース設定などを、それぞれ別々のテンプレートに分けて、必要な部分だけを親テンプレートで呼び出すことができます。

    ネスト化するメリットについて

    1. テンプレートの分割で保守性アップ
      CloudFormationテンプレートは機能が増えるにつれて巨大化しがちです。
      ネスト化すれば「VPC」「EC2」「RDS」などを個別ファイルに分割でき、テンプレートの見通しが良くなり、修正も部分的に行いやすくなります。

    2. 再利用性の向上
      ネストスタックはモジュール化されたパーツのように使えます。
      たとえば「共通のVPC定義」や「標準構成のセキュリティグループ」など、複数環境で使いまわしたい場合に非常に便利です。

    3. チーム開発に強い
      テンプレートを分割できることで、担当チームごとに管理を分担できます。
      たとえば、以下のようにできます。

      • ネットワーク担当 → vpc.yaml
      • インフラ担当 → ec2.yaml
      • DB担当 → rds.yaml
    4. 変更影響範囲の限定
      親スタックで子スタックを参照している場合、一部の子スタックだけを更新できます。
      これにより、不要な再デプロイ(=ダウンタイムやリスク)を減らすことができます。
      子スタックの更新方法は以下の2通りがありますが、基本的には「親スタックからの更新」が推奨されます。

      • 親スタックから更新する(推奨)
        子スタックのテンプレートを新しいものに差し替え、親スタックを更新します。
        操作上は親スタックの更新になりますが、実際に変更されるのは子スタックだけです。
      • 子スタック単体で更新する
        子スタックを選択して直接テンプレートを更新する方法です。
        可能ではありますが、依存関係や整合性の観点から通常はあまり使いません。
        このように、子スタックだけを更新する場合でも、操作は親スタック経由になることが多い点にご注意ください。
    5. 構造の明確化と理解しやすさ
      ネスト化により、テンプレートに論理的な階層構造が生まれます。
      例:

      root.yaml
      ├── vpc.yaml
      ├── ec2.yaml
      └── rds.yaml

    これにより、初見の人にも「どこで何をしているか」がすぐに把握できるようになります。

    具体例

    では、実例を見ていきましょう!

    構成図

    作成する構成は以下とします。
    202508_cloudformation_01

    テンプレート

    テンプレートはサービス単位で分割して管理します。
    ここでは、親スタック用のテンプレートをご紹介します。
    子スタック用のテンプレート(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スタックのネスト構成について紹介しましたが、いかがでしたでしょうか。
    複雑なシステム構成を作成する際には、ネスト構成をぜひ活用してみてください。
    本記事が少しでもご参考になれば幸いです。

    付録:子スタック用のテンプレート

    VPC テンプレート(vpc.yml)

    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

    EC2 テンプレート(ec2.yml)

    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

    RDS テンプレート(rds.yml)

    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

    アジアクエスト株式会社では一緒に働いていただける方を募集しています。
    興味のある方は以下のURLを御覧ください。