🤫

CloudFormationのテンプレートは Mappingを使った方が良いかも

6 min read

この記事の要点

  1. Mappingでyml内に定数を持てる
  2. ソースを深追いしなくてよくなる
  3. コマンドでさらに便利に

intro

『インフラのコード化』としてお馴染みのCloudFormationですが
リソースが増えるに連れてコードの行数が増え
例えばインスタンスタイプなどを変更する際に、コードを追うのが面倒になってきます。
そこで、CloudFormationの書式 "Mapping" を使ってちょっと楽してみましょう。

Mappingの書式

ユーザーガイド[1]より

定義する時
Mappings: 
    Map01: 
        Key01: 
            Name01: Value01
呼び出す時
!FindInMap [Map01, Key01, Name01]

※この場合は "Value01" という値が返ります。

使用・不使用での比較

EC2インスタンスを2つ作成するテンプレートを例に、比較してみましょう。

Mappingを使わない場合(クリックで開く)
Mapping不使用
AWSTemplateFormatVersion: "2010-09-09"
Resources:
    Server01:
        Type: "AWS::EC2::Instance"
        Properties:
            ImageId: "ami-1a2b3c4d5f6g7h8i9"
            InstanceType: "r5.large"
            KeyName: "myKey-xxxxx"
            AvailabilityZone: "ap-northeast-1a"
            DisableApiTermination: true
            Tenancy: "default"
            SubnetId: "subnet-12ab34cd56ef78gh9"
            EbsOptimized: true
            SecurityGroupIds: 
              - "sg-123abc456def789gh"
            SourceDestCheck: true
            IamInstanceProfile: "my-role"
            Tags: 
              - Key: "Name"
                Value: "Server01"
            HibernationOptions: 
                Configured: false
    Server02:
        Type: "AWS::EC2::Instance"
        Properties:
            ImageId: "ami-9i8h7g6f5d4c3b2a1"
            InstanceType: "r5d.2xlarge"
            KeyName: "myKey-xxxxx"
            AvailabilityZone: "ap-northeast-1c"
            DisableApiTermination: true
            Tenancy: "default"
            SubnetId: "subnet-9hg87fe65dc43ba21"
            EbsOptimized: true
            SecurityGroupIds: 
              - "sg-123abc456def789gh"
            SourceDestCheck: true
            IamInstanceProfile: "my-role"
            Tags: 
              - Key: "Name"
                Value: "Server02"
            HibernationOptions: 
                Configured: false
Mappingを使う場合(クリックで開く)
Mapping使用
AWSTemplateFormatVersion: "2010-09-09"
Mappings:
    Constant:
        SubnetId:
            PrivateA: "subnet-12ab34cd56ef78gh9"
            PrivateC: "subnet-9hg87fe65dc43ba21"
            PublicA: "subnet-123abc456def789gh"
    Variable:
        KeyName:
            Common: "myKey-xxxxx"
        ImageId:
            Server01: "ami-1a2b3c4d5f6g7h8i9"
            Server02: "ami-9i8h7g6f5d4c3b2a1"
        InstanceType:
            Server01: "r5.large"
            Server02: "r5d.2xlarge"
        Subnet:
            Server01: "PrivateA"
            Server02: "PublicA"
Resources:
    Server01:
        Type: "AWS::EC2::Instance"
        Properties:
            ImageId: !FindInMap [ Variable, ImageId, Server01 ]
            InstanceType: !FindInMap [ Variable, InstanceType, Server01 ]
            KeyName: !FindInMap [ Variable, KeyName, Common ]
            AvailabilityZone: "ap-northeast-1a"
            DisableApiTermination: true
            Tenancy: "default"
            SubnetId: !FindInMap [ Constant, SubnetId, !FindInMap [Variable, Subnet, Server01] ]
            EbsOptimized: true
            SecurityGroupIds:
              - "sg-123abc456def789gh"
            SourceDestCheck: true
            IamInstanceProfile: "my-role"
            Tags:
              - Key: "Name"
                Value: "Server01"
            HibernationOptions:
                Configured: false
    Server02:
        Type: "AWS::EC2::Instance"
        Properties:
            ImageId: !FindInMap [ Variable, ImageId, Server02 ]
            InstanceType: !FindInMap [ Variable, InstanceType, Server02 ]
            KeyName: !FindInMap [ Variable, KeyName, Common ]
            AvailabilityZone: "ap-northeast-1a"
            DisableApiTermination: true
            Tenancy: "default"
            SubnetId: !FindInMap [ Constant, SubnetId, !FindInMap [Variable, Subnet, Server02] ]
            EbsOptimized: true
            SecurityGroupIds:
              - "sg-123abc456def789gh"
            SourceDestCheck: true
            IamInstanceProfile: "my-role"
            Tags:
              - Key: "Name"
                Value: "Server02"
            HibernationOptions:
                Configured: false

いかがでしょうか。
仮に、テンプレート内に30個リソースがあったら
Mappingを修正するだけで完結します。
コードをスクロールして該当部分まで行く必要が無いわけです。

コマンドで作ろう

Mapを用いた記述を一つずつ作るのは面倒・コピペミスが怖い という場合は
ベースになるymlを作成し、コマンドで置換すると楽です。

今回のインスタンスを10個作るとしたら

base-model.yml
Resources:
    @:
        Type: "AWS::EC2::Instance"
        Properties:
            ImageId: !FindInMap [ Variable, ImageId, @ ]
            InstanceType: !FindInMap [ Variable, InstanceType, @ ]
            KeyName: !FindInMap [ Variable, KeyName, Common ]
            AvailabilityZone: "ap-northeast-1a"
            DisableApiTermination: true
            Tenancy: "default"
            SubnetId: !FindInMap [ Constant, SubnetId, !FindInMap [Variable, Subnet, @] ]
            EbsOptimized: true
            SecurityGroupIds:
              - "sg-041bb99af7da0f6d8"
            SourceDestCheck: true
            IamInstanceProfile: "my-role"
            Tags:
              - Key: "Name"
                Value: "@"
            HibernationOptions:
                Configured: false

上記ymlをテンプレート元に、コマンドで増やしてあげます。

for name in Server{01..10}
do
	cat base-model.yml | sed -e "s/@/${name}/g"
done

といった具合でしょうか。(多少の調整は必要)

outro

パラメータ入力をCLIで回すというのも考えたのですが、
Git上でのレビューが楽かなと思ったのと、ベタ書きより可読性が上がったらいいな
という話でした。

特定の接尾次・接頭辞をNameタグなどに付与したいときも
Mappingでもたせて、!joinでくっつけたら便利そうですね。

脚注
  1. CloudFormationユーザーガイド ↩︎