クラウド エンジニアブログ

[ゼロから始めるInfrastructure as Code] 第4回 テンプレートの分割

2023年2月22日掲載

始めに

クラウドインテグレーション課 クラウドエンジニアの濱田です。 前回はParametersセクションや組み込み関数を駆使して、動的にパラメータを設定する方法を学びました。 「ゼロから始めるInfrastructure as Code」、第4回目は、テンプレートの分割というテクニックについて勉強していきます。

前回のテンプレートは、VPCからEC2インスタンスまで一気通貫で作成するものになっています。 皆さんに考えて頂きたいのですが、運用の中で、EC2を増やしたくなったらどうしますか? 恐らく、以下の2択のどちらかだと思います。

  • テンプレートに追記する
  • 新しくEC2用のテンプレートを書く

「テンプレートに追記する」場合、だんだん1つのテンプレートが長くなって可読性が下がっていきます。 また、大体の場合、NWに比べてEC2の方が作成・更新・削除の頻度が激しいです。 EC2の修正の為に、毎回NWの部分にも目を通すのは大変です。

「新しくEC2用のテンプレートを書く」について、こちらは良いですね。 前回のテンプレートからEC2の部分を取り出して、NWはNW、EC2はEC2のテンプレートに分けた方が管理が楽になります。 では、どんな観点で分割していけばいいのか?

いきなりですが、まずは課題に取り組んで頂きましょう!

課題

  • 以下の要件を満たすテンプレートを記述して下さい。また、テンプレートはコンポーネント毎に分割して下さい。
    • 192.168.0.0/16のCidrを持つVPCを作成する
    • 作成したVPC内に、以下セグメントのサブネットを作成する
      • 192.168.0.0/24
    • Internet Gatewayを作成し、VPCにアタッチする
    • ルートテーブルを作成し、Subnetに関連付ける
    • ルートテーブルに0.0.0.0/0 -> IGWのルートを書く
    • EC2インスタンス用セキュリティグループを作成する
    • 作成したサブネット内に、EC2インスタンスを1台起動する

解説

ここから解説編です。 テンプレートの分割について、 ・どういった観点で行うべきなのか ・どんなテクニックがあるのか
という観点から解説していきます。

ライフサイクルによって整理する

まずは、テンプレートの分割はどういった観点から行うべきなのか、です。

ずばり! CloudFormationのテンプレートは、ライフサイクル毎に分けましょう。 ライフサイクルとは、作成・削除のタイミングが同じであることを指します。 今回の場合、 ・VPC、サブネット、インターネットゲートウェイなどのNW系 ・セキュリティグループ、EC2
に分けるのが良いでしょう。

では、テンプレートを分割してみましょう。

  • network.yaml
    AWSTemplateFormatVersion: "2010-09-09"
    Parameters:
      VpcCidr:
        Type: String
      
      SubnetCidr:
        Type: String
    
    Resources:
      Vpc:
        Type: AWS::EC2::VPC
        Properties:
          CidrBlock: !Ref VpcCidr
    
      InternetGW:
        Type: AWS::EC2::InternetGateway
    
      AttachGateway:
        Type: AWS::EC2::VPCGatewayAttachment
        Properties:
          VpcId: !Ref Vpc
          InternetGatewayId: !Ref InternetGW
    
      Subnet:
        Type: AWS::EC2::Subnet
        Properties:
          AvailabilityZone: !Select
            - 0
            - Fn::GetAZs: !Ref 'AWS::Region'
          VpcId: !Ref Vpc
          CidrBlock: !Ref SubnetCidr
    
      RouteTable:
        Type: AWS::EC2::RouteTable
        Properties:
          VpcId: !Ref Vpc
    
      Route:
        DependsOn: AttachGateway
        Type: AWS::EC2::Route
        Properties:
          RouteTableId: !Ref RouteTable
          DestinationCidrBlock: 0.0.0.0/0
          GatewayId: !Ref InternetGW
    
      AssociateRouteTable:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
          SubnetId: !Ref Subnet
          RouteTableId: !Ref RouteTable
    
  • ec2.yaml
    AWSTemplateFormatVersion: "2010-09-09"
    Parameters:
      VpcId:
        Type: AWS::EC2::VPC::Id
    
      SubnetId:
        Type: AWS::EC2::Subnet::Id
    
      KeyName:
        Type: AWS::EC2::KeyPair::KeyName
    
      InstanceName:
        Type: String
    
      ImageId:
        Type: AWS::SSM::Parameter::Value
        Default: "/aws/service/ami-windows-latest/Windows_Server-2022-Japanese-Full-Base"
    
      InstanceType:
        Type: String
    
      VolumeSize:
        Type: Number
        MinValue: 60
    
    Resources:
      SecurityGroup:
        Type: AWS::EC2::SecurityGroup
        Properties:
          VpcId: !Ref Vpc
          GroupName: !Sub '${InstanceName}_SecurityGroup'
          GroupDescription: Enable Private access
          SecurityGroupIngress:
            - CidrIp: 10.0.0.0/8
              IpProtocol: -1
            - CidrIp: 172.16.0.0/12
              IpProtocol: -1
            - CidrIp: 192.168.0.0/16
              IpProtocol: -1
          SecurityGroupEgress:
            - CidrIp: 0.0.0.0/0
              IpProtocol: -1
    
      EC2:
        Type: AWS::EC2::Instance
        Properties:
          ImageId: !Ref ImageId
          InstanceType: !Ref InstanceType
          KeyName: !Ref KeyName
          BlockDeviceMappings: 
            - DeviceName: '/dev/sda1'
              Ebs:
                VolumeType: gp3
                VolumeSize: !Ref VolumeSize
                DeleteOnTermination: true
          NetworkInterfaces:
            - AssociatePublicIpAddress: false
              DeviceIndex: '0'
              SubnetId: !Ref SubnetId
              GroupSet: 
                - !Ref SecurityGroup
          Tags:
            -
              Key: Name
              Value: !Ref InstanceName
    

ec2.yamlのパラメータで「AWS::EC2::VPC::Id」と「AWS::EC2::Subnet::Id」を使ってあげれば、プルダウンでそれぞれのIDを選べるようになります。 これでnetworkとec2を分割出来ました。

パラメータを参照する方法を知る

ところで、VpcIdはシステム全体で1つのものを使う事が多いですよね。 これを都度入力するのはちょっと面倒です。 実は、CloudFormationにはスタック間でパラメータを参照する方法が用意されています。

    • クロススタック参照
    • ダイナミック参照
  • クロススタック参照 CloudFormationのOutputsセクションを上手く使う事で、スタック間でパラメータ参照できます。 使い方はまず、参照元のテンプレートにOutputsセクションを記述します。
    参照元: vpc.yaml

    AWSTemplateFormatVersion: "2010-09-09"
    Parameters:
      VpcCidr:
        Type: String
    
    Resources:
      Vpc:
        Type: AWS::EC2::VPC
        Properties:
          CidrBlock: !Ref VpcCidr
    
    Outputs:
      VpcId:
        Value: !Ref Vpc
        Export:
          Name: network-VpcId
    

    次に、参照先で、Fn::ImportValueを使って、Export -> Nameに設定した値を引数に取ります。
    参照先: ec2.yaml

    AWSTemplateFormatVersion: "2010-09-09"
    Parameters:
      VpcId:
        Type: AWS::EC2::VPC::Id
    
    Resources:
      SecurityGroup:
        Type: AWS::EC2::SecurityGroup
        Properties:
          VpcId: !ImportValue network-VpcId  # <--ココです
          GroupName: !Sub '${InstanceName}_SecurityGroup'
          GroupDescription: Enable Private access
          SecurityGroupIngress:
            - CidrIp: 10.0.0.0/8
              IpProtocol: -1
            - CidrIp: 172.16.0.0/12
              IpProtocol: -1
            - CidrIp: 192.168.0.0/16
              IpProtocol: -1
          SecurityGroupEgress:
            - CidrIp: 0.0.0.0/0
              IpProtocol: -1
    

    このように、VPCのような共有リソースのIDに対してOutputsセクションを定義、Exportで命名しておく事で、他のスタックから容易に参照できるようになります。 しかし便利な反面、注意事項もあります。 Exportした値が他のスタックから参照されている場合、元のリソースの置き換えが出来なくなります。 例えばCloudFormationでS3を作成し、他のスタックからバケット名を参照している場合、S3用スタックを削除できません。 場合によっては運用が止まる危険性もあるので、よく計画した上で利用しましょう。

  • ダイナミック参照 スタック間のパラメータ参照方法その2は、SSMパラメータストアを経由して参照する方法です。 使い方は、まず参照元のテンプレートでリソースを作成後、パラメータストアへ格納します。
    参照元: vpc.yaml

    AWSTemplateFormatVersion: "2010-09-09"
    Parameters:
      VpcCidr:
        Type: String
    
    Resources:
      Vpc:
        Type: AWS::EC2::VPC
        Properties:
          CidrBlock: !Ref VpcCidr
    
      VpcId:
        Type: AWS::SSM::Parameter
        Properties: 
          Description: 'VpcId'
          Name: 'VpcId'
          Type: String
          Value: !Ref Vpc
    

    そして、参照先でパラメータ名を指定するだけです。 ※ 書き方: ‘{{resolve:ssm:パラメータ名}}’
    参照先: ec2.yaml

    AWSTemplateFormatVersion: "2010-09-09"
    Parameters:
      VpcId:
        Type: AWS::EC2::VPC::Id
    
    Resources:
      SecurityGroup:
        Type: AWS::EC2::SecurityGroup
        Properties:
          VpcId: '{{resolve:ssm:VpcId}}'  # <--ココです
          GroupName: !Sub '${InstanceName}_SecurityGroup'
          GroupDescription: Enable Private access
          SecurityGroupIngress:
            - CidrIp: 10.0.0.0/8
              IpProtocol: -1
            - CidrIp: 172.16.0.0/12
              IpProtocol: -1
            - CidrIp: 192.168.0.0/16
              IpProtocol: -1
          SecurityGroupEgress:
            - CidrIp: 0.0.0.0/0
              IpProtocol: -1
    

    こちらはSSMパラメータストアが間に挟まる分、スタックの更新に影響を与えません。 個人的には、ExportするよりSSMを使う方が好きだったりします。

以上がスタックを跨いだパラメータの参照方法です。

テンプレート分割のメリット

テンプレートを分割すると何がいいのか? 以下の2つが挙げられます。

    • 可読性を上げられる
    • 再利用が可能になる
  • 可読性を上げられる こちらは最初に軽くお話した通り、テンプレートは長くなるとどんどん読みづらくなります。 ライフサイクル毎に分割することで、ある程度長さを抑えられるようになります。
  • 再利用が可能になる こちらが一番大きなメリットです。 スタックを作成する際、再利用可能なテンプレートをモジュールとして用意しておくことで、整合性の担保や、コード量の抑制を実現できます。

例えば、以下のように構成することで、root.yamlを修正するだけで、EC2やRDSを増やせるようになります。

これを実際に運用するには、AWS CLIと併用するなどひと手間必要なのですが、それはまた別の機会にご説明します。

テンプレートの分割については以上です。 ここまででCloudFormationの基本的な書き方の勉強は終了です。

「ゼロから始めるInfrastructure as Code」は次回で最後です。 最終回ではCloudFormationのベストプラクティスについて総括します。 最後までお楽しみに! 以上、エンジニアの濱田でした!

関連コラム

このコラムに関連する製品

このコラムに関連する
導入事例

このコラムに関連する
セミナーアーカイブ動画

AWS Ambassador Presents トレンド技術を語り尽くせ! 第1回 「Infrastructure as Code」

SHARE
シェアシェア ポストポスト
AWS Ambassador Presents トレンド技術を語り尽くせ! 第1回 「Infrastructure as Code」
SHARE
ポスト シェア