1
3

EC2を柔軟に設定するためのCloudFormation Templateの作成

Posted at

概要

本記事ではEC2をCloudFormationで作成する際に、様々なオプションに柔軟に対応する方法をご紹介します。

GUIでEC2を作成する場合、アタッチするEBSの数は簡単に増減出来ますよね?他にもVPCやSecurityGroupをリストから選択出来たり、EC2の作成ウィザードは非常に多くの項目を簡単に設定することが出来ます。
一方で組織のポリシーとしてEC2にプリセットしたい設定がある場合(例:特定タグの付与やリソース名称の指定etc)などは、あらかじめ値を指定したCloudFormation Templateを用意・利用して作成することで実現することが出来ます。
マルチアカウント環境においてもAWS Service Catalogを使えば、メンバーアカウントに対してCloudFormation Templateを配布することが出来ますので、EC2の作成ウィザードではなくService Catalogのメニューから配布されたCloudFormation Templateを使ってEC2を作成することで組織のポリシーを満たしたリソースの作成が可能です。

image.png

では、EC2の作成ウィザードのような設定の柔軟性を持つCloudFormation Templateを作るにはどうすればよいでしょうか。
以下ではその一例として、設定の種類毎のパラメータの書き方や条件に応じてEC2に複数のEBSをアタッチする方法をご説明します。

ユースケース

  • 設定の種類(Boolean型,複数項目から単一選択,複数項目から複数選択)に合わせたCloudFormation Templateでの記載方法を確認したい
  • 複数の条件分岐を持たせたEC2作成用のCloudFormation Templateを用意したい

設定内容

全体

CloudFormation Templateの全文を記載します。
*かなり行数があるためいくつか設定値をカットしています。
*パラメータの整理のため、パラメータグループの利用をオススメします。

AWSTemplateFormatVersion: "2010-09-09"
Description: ""       
Parameters:
  Ec2ImageId:
    Type: AWS::EC2::Image::Id
  Ec2InstanceType:
    Description: Select EC2 InstanceType
    Type: String
    AllowedValues:  ["t3.micro", "t3.medium", "t3.large"]
  Ec2KeyName:
    Type: AWS::EC2::KeyPair::KeyName
  SubnetID:
    Type: AWS::EC2::Subnet::Id
  SecurityGroups:
    Type: "List<AWS::EC2::SecurityGroup::Id>"
  EbsVolume00Size:
    Type: Number
    Default: 10
  EbsVolume00Type:
    Type: String
    Default: gp2
    AllowedValues: [gp2, gp3, io1, io2, standard]
  EbsVolume01Size:
    Type: Number
    Default: 0
  EbsVolume01Type:
    Type: String
    Default: gp2
    AllowedValues: [gp2, gp3, io1, io2, standard]
  EbsVolume02Size:
    Type: Number
    Default: 0
  EbsVolume02Type:
    Type: String
    Default: gp2
    AllowedValues: [gp2, gp3, io1, io2, standard]
  InstanceProfile:
    Type: String
  DisableApiTermination: 
    Type: String
    Default: false
    AllowedValues: [true, false]
  Tenancy:
    Type: String
    Default: default
    AllowedValues: [default, dedicated, host]
  EBSEncryption:
    Type: String
    Default: true
    AllowedValues: [true, false]
  EBSOptimized:
    Type: String
    Default: false
    AllowedValues: [true, false]
  EBSDeleteOnTermination:
    Type: String
    Default: true
    AllowedValues: [true, false]

Conditions:
  AttachVolume01: !Not 
    - !Equals [!Ref EbsVolume01Size, "0"]
  AttachVolume02: !Not 
    - !Equals [!Ref EbsVolume02Size, "0"]

Resources:
    CreateEC2InstanceAmazonLinux2:
        Type: "AWS::EC2::Instance"
        Properties:
            ImageId: !Ref Ec2ImageId
            InstanceType: !Ref Ec2InstanceType
            KeyName: !Ref Ec2KeyName
            Tenancy: !Ref Tenancy
            EbsOptimized: !Ref EBSOptimized
            DisableApiTermination: !Ref DisableApiTermination         
            BlockDeviceMappings: 
              - DeviceName: "/dev/xvda"
                Ebs: 
                    Encrypted: !Ref EBSEncryption
                    VolumeSize: !Ref EbsVolume00Size
                    VolumeType: !Ref EbsVolume00Type
                    DeleteOnTermination: !Ref EBSDeleteOnTermination
              - DeviceName: "/dev/xvdb"
                NoDevice: !If [AttachVolume01,!Ref "AWS::NoValue", {}]
                Ebs:
                  !If
                    - AttachVolume01
                    - Encrypted: !Ref EBSEncryption
                      VolumeSize: !Ref EbsVolume01Size
                      VolumeType: !Ref EbsVolume01Type
                      DeleteOnTermination: !Ref EBSDeleteOnTermination
                    - !Ref "AWS::NoValue"
              - DeviceName: "/dev/xvdc"
                NoDevice: !If [AttachVolume02,!Ref "AWS::NoValue", {}]
                Ebs:
                  !If
                    - AttachVolume02
                    - Encrypted: !Ref EBSEncryption
                      VolumeSize: !Ref EbsVolume02Size
                      VolumeType: !Ref EbsVolume02Type
                      DeleteOnTermination: !Ref EBSDeleteOnTermination
                    - !Ref "AWS::NoValue"
            NetworkInterfaces:
              - AssociatePublicIpAddress: "false"
                DeviceIndex: "0"
                SubnetId: !Ref SubnetID
                GroupSet: 
                  - {"Fn::Join" : [ ",", { "Ref" : "SecurityGroups" }] }                
            IamInstanceProfile: !Ref InstanceProfile

AWS固有のパラメータタイプ

一部のリソースの値はAWS固有のパラメータとして、アカウント内の既存の設定値を参照することが出ます。
このテンプレートの場合、KeyPair・Subnet・SecurityGroupはこのパラメータを利用しています。

  Ec2KeyName:
    Type: AWS::EC2::KeyPair::KeyName
  SubnetID:
    Type: AWS::EC2::Subnet::Id
  SecurityGroups:
    Type: "List<AWS::EC2::SecurityGroup::Id>"

CloudFormationのスタック作成画面から見ると以下のようになります。これはEC2の作成ウィザードにかなり近いですね。
スクリーンショット 2024-05-08 075653.png

この値が参照されるのは以下の箇所のSubnetIdの部分、SubnetIdを調べるのは面倒なのでリストから選択出来るのはありがたいです。

            NetworkInterfaces:
              - AssociatePublicIpAddress: "false"
                DeviceIndex: "0"
                SubnetId: !Ref SubnetID

サポートされるAWSの固有パラメータタイプの一覧については以下のドキュメントをご確認ください。

Boolean型の設定

EC2作成に関して言えば、このタイプのオプションが一番多いと思われます。
パラメータとしてはtrue/falseの二択として、どちらかを選択する形とします。

  DisableApiTermination: 
    Type: String
    Default: false
    AllowedValues: [true, false]

GUI上の表示
image.png

リソース作成の箇所では!Refで参照します。

            DisableApiTermination: !Ref DisableApiTermination 

複数項目から単一選択を行う設定

上記の派生でtrue/falseではなく、許可された複数の項目から一つを選択する場合のオプションです。
例えば、上記で紹介したSubnetIdやInstanceTypeなどがこれに当たります。
SubnetIdは固有パラメータタイプを利用していましたが、AllowedValuesを利用することでリストから選択するように設定出来ます。

  Ec2InstanceType:
    Description: Select EC2 InstanceType
    Type: String
    AllowedValues:  ["t3.micro", "t3.medium", "t3.large"]

image.png

リソース作成の箇所では!Refで参照します。

            InstanceType: !Ref Ec2InstanceType

複数項目から複数選択を行う設定

今回ではSecurityGroupが該当します。SecurityGroupは固有パラメータタイプを利用していますが、固有パラメータタイプ以外を複数選択する場合はTypeにList<Number>またはCommaDelimitedListを利用します。

  SecurityGroups:
    Type: "List<AWS::EC2::SecurityGroup::Id>"

CloudFormationのコンソールだと以下のように表示され、チェックボックスにチェックする形式になっているのが分かります。
スクリーンショット 2024-05-08 075546.png

リソース作成の箇所での指定は以下の通りです。複数の値を選択出来る場合はカンマ区切りが必要となりますので、","区切りになるようJoin関数を活用しましょう。

                GroupSet: 
                  - {"Fn::Join" : [ ",", { "Ref" : "SecurityGroups" }] } 

任意の文字列・数字を入力させる設定

ここではEBSのボリュームサイズやインスタンスプロファイルの指定に利用しています。TypeにNumberもしくはStringを利用します。

  EbsVolume00Size:
    Type: Number
    Default: 10
...
  InstanceProfile:
    Type: String

複数EBSのアタッチ

Conditionの設定

Conditionは設定した条件に当てはまっていればtrue、当てはまらなければfalseを返す設定で、これを利用することで特定の条件の場合のみリソースを作成するよう設定出来ます。
ここでは、VolumeSizeとして0以外が入力されたときはtrue0が入力されたときはfalseとなるようなAttachVolume01(02)というConditionを用意します。

Conditions:
  AttachVolume01: !Not 
    - !Equals [!Ref EbsVolume01Size, "0"]
  AttachVolume02: !Not 
    - !Equals [!Ref EbsVolume02Size, "0"]

Conditionの詳細な説明については下記のドキュメントをご確認ください。

EBSリソース作成の条件分岐

ここが非常にややこしいです。

全体(EBS)

              - DeviceName: "/dev/xvdb"
                NoDevice: !If [AttachVolume01,!Ref "AWS::NoValue", {}]
                Ebs:
                  !If
                    - AttachVolume01
                    - Encrypted: !Ref EBSEncryption
                      VolumeSize: !Ref EbsVolume01Size
                      VolumeType: !Ref EbsVolume01Type
                      DeleteOnTermination: !Ref EBSDeleteOnTermination
                    - !Ref "AWS::NoValue"

NoDevice設定

まずEC2インスタンス作成時に合わせてEBSを作成するにはAWS::EC2::Instance BlockDeviceMappingを使用します。
このマッピング設定でNoDevice: {}を使用することでEBSが作成されない状態になります。

NoDeviceとIfの組み合わせ

次にCloudFormationのIf関数を利用します。If関数は"Fn::If": [condition_name, value_if_true, value_if_false]の形で利用されます。

今回はNoDevice: !If [AttachVolume01,!Ref "AWS::NoValue", {}]としていますので、
・AttachVolume01がtrueの場合
NoDevice: !Ref "AWS::NoValue"
・AttachVolume01がfalseの場合
NoDevice: {}
となります。

ここで登場する!Ref "AWS::NoValueは疑似パラメータ参照と呼ばれるものの一つで、!Ifの組み合わせることで対応するリソースプロパティを削除します。つまり今回の場合はNoDevice自体を削除するよう設定出来ます。

AWS::NoValue
Fn::If 組み込み関数の戻り値として指定すると、対応するリソースプロパティを削除します。

EBSリソースの作成

EBSの作成についてもIf関数を利用します。
・AttachVolume01がtrueの場合
指定した設定値でリソースを作成します。

                Ebs:
                    Encrypted: !Ref EBSEncryption
                    VolumeSize: !Ref EbsVolume01Size
                    VolumeType: !Ref EbsVolume01Type
                    DeleteOnTermination: !Ref EBSDeleteOnTermination

NoDevice: !Ref "AWS::NoValue"
・AttachVolume01がfalseの場合
AWS::NoValueを利用してリソースプロパティ自体を削除します。

EBS: !Ref "AWS::NoValue"

まとめ

本記事ではEC2を作成するCloudFormation Templateを作成する場合を例として、設定の種類ごとのCloudFormationのパラメータの記載方法をご紹介しました。記事内で紹介している複数EBSのアタッチように関数等を組み合わせることで、CloudFormationでも柔軟な設定を実現することが出来ます。
一方、過度な条件分岐やパラメータ化を行うとテンプレート自体の可読性が下がってしまう可能性がありますので、複雑すぎる設定作業については無理にCloudFormationのみで実現しようとせず、適切なレベル感にとどめておくことをオススメします。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3