AWS再入門2018 AWS CloudFormation編

クラウドでコスト削減はこちら

こんにちは。池田です。ついにEcho Dotが届きました。注文から中1日で届いたのは感動です。でもEcho Plusが欲しいのです(Echo Dotはホワイトモデルを購入しました)。
開封の儀は家族全員揃って行いました。

早速[日本語Alexa] 発話の揺れをカスタムスロットで吸収する〜より自然に会話できるスキル作成のために〜を参考にスキルを作ったり、音楽を再生してもらったり、ピカチュウを呼び出したりしてみました。楽しかったです。
Echo Plusを購入できたら照明のOFF/ONとかもやってみたいです。

はじめに

AWS再入門2018シリーズ第九弾です。今回はAWS CloudFormationについて、実際にテンプレートを作成しながらその基本操作やお作法について整理していくことにします。

もくじ

  • 概要
  • やってみた
  • 物足りない...
  • 複数AZにSubnetを配置させる
  • まとめ
  • 概要

    • AWSより提供されているアプリケーション管理サービスの1つ
    • ほぼ全てのAWSリソースの構築、設定が行える
    • 対応した言語で記述されたコードでインフラの構築が可能
      • JSONとYAMLに対応
    • GUIによる設計ツールとしてAWS CloudFormation Designerが提供されている
    • ローカルまたはS3に作成したコードを保存しておけば繰り返し利用が可能
      • 複数プロジェクト/部門間で同一のインフラ基盤が容易に構築できる
    • 利用可能なサンプルテンプレートも多く公開されている

    やってみた

    取り敢えずやってみよう。ということで、まずは以下の構成を作成できるテンプレートを用意してみようと思います。

    • VPCをひとつ
    • 単一AZにPublic SubnetとPrivate Subnetをひとつずつ
    • VPCにInternet Gatewayをアタッチ

    いきなりゼロからテンプレートを書くのではなく、公開されているサンプルテンプレートに近い構成となったものが無いか探した結果、「Amazon VPC における単一の Amazon EC2」テンプレートを利用することにしました。

    不要な部分の削除と整形など

    まず、今回の目的ではEC2の構築は不要ですので、該当する記述(サンプルの6行目から187行目まで)をバッサリと削除しました。
    次に、残りのコードからEC2向けの記述(残ったコードの75行目から151行目と161行目から286行目)も削除しました。
    このままだと、Private Subnetが作られないので、Subnetのセクションを複製して追加しました。
    仕上げに}の数を確認、Descriptionの内容を簡潔に変更、無駄な改行を減らしたりリソース名を変更したものが以下となります。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    {
      "AWSTemplateFormatVersion" : "2010-09-09",
     
      "Description" : "TEST template.",
     
      "Resources" : {
     
        "VPC" : {
          "Type" : "AWS::EC2::VPC",
          "Properties" : {
            "CidrBlock" : "10.0.0.0/16",
            "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
          }
        },
     
        "PublicSubnet" : {
          "Type" : "AWS::EC2::Subnet",
          "Properties" : {
            "VpcId" : { "Ref" : "VPC" },
            "CidrBlock" : "10.0.0.0/24",
            "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
          }
        },
     
        "PrivateSubnet" : {
          "Type" : "AWS::EC2::Subnet",
          "Properties" : {
            "VpcId" : { "Ref" : "VPC" },
            "CidrBlock" : "10.0.1.0/24",
            "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
          }
        },
     
        "InternetGateway" : {
          "Type" : "AWS::EC2::InternetGateway",
          "Properties" : {
            "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
          }
        },
     
        "AttachGateway" : {
           "Type" : "AWS::EC2::VPCGatewayAttachment",
           "Properties" : {
             "VpcId" : { "Ref" : "VPC" },
             "InternetGatewayId" : { "Ref" : "InternetGateway" }
           }
        },
     
        "RouteTable" : {
          "Type" : "AWS::EC2::RouteTable",
          "Properties" : {
            "VpcId" : {"Ref" : "VPC"},
            "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
          }
        },
     
        "Route" : {
          "Type" : "AWS::EC2::Route",
          "DependsOn" : "AttachGateway",
          "Properties" : {
            "RouteTableId" : { "Ref" : "RouteTable" },
            "DestinationCidrBlock" : "0.0.0.0/0",
            "GatewayId" : { "Ref" : "InternetGateway" }
          }
        },
     
        "SubnetRouteTableAssociation" : {
          "Type" : "AWS::EC2::SubnetRouteTableAssociation",
          "Properties" : {
            "SubnetId" : { "Ref" : "PublicSubnet" },
            "RouteTableId" : { "Ref" : "RouteTable" }
          }
        },
     
        "NetworkAcl" : {
          "Type" : "AWS::EC2::NetworkAcl",
          "Properties" : {
            "VpcId" : {"Ref" : "VPC"},
            "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
          }
        },
     
        "SubnetNetworkAclAssociation" : {
          "Type" : "AWS::EC2::SubnetNetworkAclAssociation",
          "Properties" : {
            "SubnetId" : { "Ref" : "PublicSubnet" },
            "NetworkAclId" : { "Ref" : "NetworkAcl" }
          }
        }
      }
    }

    構文のチェック

    これで想定しているリソースが自動で作成されるようになりましたので、AWS CLIでテンプレートの検証をしてみます。ファイル名はCFn-test.jsonとしました。

    1
    2
    3
    4
    5
    $ aws cloudformation validate-template --template-body file://CFn-test.json
    {
        "Description": "TEST template.",
        "Parameters": []
    }

    AWS CLIでのチェックでは問題がない事がわかりましたが、せっかくなのでAWS CloudFormation Designerも利用してみたいと思います。
    AWS CloudFormation Designerを開き、画面下部のテンプレートタブに先ほどのJSONコードを貼り付けます。


    画面右上のエリアにリフレッシュを促すメッセージが表示されますので、リフレッシュボタンをクリックします。

    VPCの中にPublic SubnetとPrivate Subnetが、それぞれひとつずつ配置されるということが視覚的にわかりやすく表示されました。

    構築してみる

    さて、ここまでで作成したテンプレートは、構文にミスもなく想定したリソースが配置されそうだ。ということが確認できましたので、実際に作成してみることにします。
    CloudFormationの管理コンソールに移動して「新しいスタックの作成」を選択します。
    ローカルに保存しておいたCFn-test.jsonファイルを使用するので「テンプレートをAmazon S3にアップロード」を選択します。

    詳細の指定画面に変遷しますので、作成するスタックの名前を入力して進めます。今回は「TEST01」としました。
    次のオプション選択画面では特に何もせずデフォルト値のままとしました。
    作成内容の確認画面で間違いがなければ「作成」ボタンをクリックします。

    上記画面が表示されたら少し待ちます。
    「状況」が「CREATE_COMPLETE」になれば作成完了です。

    作成されたものを確認する

    スタックの作成は成功しましたので、確認のためVPCダッシュボードへ移動します。

    VPCとサブネットが作成されています。

    物足りない...

    さすがにこれで終わりにするのは物足りなさすぎるので、まずはテンプレートをJSONからYAMLに書き換えてみます。

    JSON形式からYAML形式へ

    そもそも、今回のテンプレートをJSON形式で作成したのは拝借したサンプルテンプレートがJSON形式だったからです。そのため、編集作業の中で{}の数を確かめたりする必要がありました。記述ミスを減らすためにも、より可読性の高いYAML形式の方が、筆者のようにコーディングに不慣れな場合はありがたいです。
    とはいえ、ゼロからYAML形式に編集していくのも大変なので「俺が思いつくものは既に誰かが作っている」の理論で探してみることにしました。

    はい、ありました。[Ruby][小ネタ] ワンライナーで JSON を YAML に変換する 素晴らしい世界ですね。
    早速実行します。

    1
    2
    3
    4
    5
    6
    $ ruby -ryaml -rjson -e 'puts YAML.dump(JSON.parse(STDIN.read))' < CFn-test.json > test.yaml
    $ aws cloudformation validate-template --template-body file://test.yaml
    {
        "Description": "TEST template.",
        "Parameters": []
    }

    JSONファイルで行った時と同様に、AWS CloudFormation Designerで確かめてみます。

    当たり前ではありますが、同じ構成図になりました。
    では、実際に作成します。先にJSONファイルで作成したスタックを削除してから実行します。

    削除処理が終わったら、YAML形式に変換したテンプレートを選択してスタックを作成し、JSON形式のテンプレートで作成した時と同じ結果になるかを確かめます。

    スタック名は「YAML-TEST」としました。

    まだ物足りない

    単にJSON形式とYAML形式で同じ構成のスタックを作成しただけで、満足してしまう訳にもいきません。せっかくYAML形式でテンプレートが用意できたのですから、もう少しYAMLファイルと触れ合うことにします。
    東京リージョンにAZが追加されたのですから、複数AZにSubnetを配置させることにします。

    複数AZにSubnetを配置させる

    さて、複数のAZにSubnetを分散配置するにはどうしたら良いのでしょうか。迷ったら検索ですね。
    ありました。こちらの【アップデート】CloudFormationに事前にリソースがどう変更されるのかを確認できる「Change Sets」が追加されました!で使われているFn::SelectFn::GetAZsです。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    ---
    AWSTemplateFormatVersion: '2010-09-09'
    Description: TEST template.
    Resources:
      VPC:
        Type: AWS::EC2::VPC
        Properties:
          CidrBlock: 10.0.0.0/16
          Tags:
          - Key: Application
            Value:
              Ref: AWS::StackId
      PublicSubnet1a:
        Type: AWS::EC2::Subnet
        Properties:
          VpcId:
            Ref: VPC
          CidrBlock: 10.0.0.0/24
          AvailabilityZone:
            Fn::Select:
            - '0'
            - Fn::GetAZs:
                Ref: AWS::Region
          Tags:
          - Key: Application
            Value:
              Ref: AWS::StackId
      PrivateSubnet1a:
        Type: AWS::EC2::Subnet
        Properties:
          VpcId:
            Ref: VPC
          CidrBlock: 10.0.10.0/24
          AvailabilityZone:
            Fn::Select:
            - '0'
            - Fn::GetAZs:
                Ref: AWS::Region
          Tags:
          - Key: Application
            Value:
              Ref: AWS::StackId
      PublicSubnet1c:
        Type: AWS::EC2::Subnet
        Properties:
          VpcId:
            Ref: VPC
          CidrBlock: 10.0.1.0/24
          AvailabilityZone:
            Fn::Select:
            - '1'
            - Fn::GetAZs:
                Ref: AWS::Region
          Tags:
          - Key: Application
            Value:
              Ref: AWS::StackId
      PrivateSubnet1c:
        Type: AWS::EC2::Subnet
        Properties:
          VpcId:
            Ref: VPC
          CidrBlock: 10.0.20.0/24
          AvailabilityZone:
            Fn::Select:
            - '1'
            - Fn::GetAZs:
                Ref: AWS::Region
          Tags:
          - Key: Application
            Value:
              Ref: AWS::StackId
      PublicSubnet1d:
        Type: AWS::EC2::Subnet
        Properties:
          VpcId:
            Ref: VPC
          CidrBlock: 10.0.3.0/24
          AvailabilityZone:
            Fn::Select:
            - '2'
            - Fn::GetAZs:
                Ref: AWS::Region
          Tags:
          - Key: Application
            Value:
              Ref: AWS::StackId
      PrivateSubnet1d:
        Type: AWS::EC2::Subnet
        Properties:
          VpcId:
            Ref: VPC
          CidrBlock: 10.0.30.0/24
          AvailabilityZone:
            Fn::Select:
            - '2'
            - Fn::GetAZs:
                Ref: AWS::Region
          Tags:
          - Key: Application
            Value:
              Ref: AWS::StackId
      InternetGateway:
        Type: AWS::EC2::InternetGateway
        Properties:
          Tags:
          - Key: Application
            Value:
              Ref: AWS::StackId
      AttachGateway:
        Type: AWS::EC2::VPCGatewayAttachment
        Properties:
          VpcId:
            Ref: VPC
          InternetGatewayId:
            Ref: InternetGateway
      RouteTable:
        Type: AWS::EC2::RouteTable
        Properties:
          VpcId:
            Ref: VPC
          Tags:
          - Key: Application
            Value:
              Ref: AWS::StackId
      Route:
        Type: AWS::EC2::Route
        DependsOn: AttachGateway
        Properties:
          RouteTableId:
            Ref: RouteTable
          DestinationCidrBlock: 0.0.0.0/0
          GatewayId:
            Ref: InternetGateway
      SubnetRouteTableAssociation1a:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
          SubnetId:
            Ref: PublicSubnet1a
          RouteTableId:
            Ref: RouteTable
      SubnetRouteTableAssociation1c:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
          SubnetId:
            Ref: PublicSubnet1c
          RouteTableId:
            Ref: RouteTable
      SubnetRouteTableAssociation1d:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
          SubnetId:
            Ref: PublicSubnet1d
          RouteTableId:
            Ref: RouteTable
      NetworkAcl:
        Type: AWS::EC2::NetworkAcl
        Properties:
          VpcId:
            Ref: VPC
          Tags:
          - Key: Application
            Value:
              Ref: AWS::StackId
      SubnetNetworkAclAssociation1a:
        Type: AWS::EC2::SubnetNetworkAclAssociation
        Properties:
          SubnetId:
            Ref: PublicSubnet1a
          NetworkAclId:
            Ref: NetworkAcl
      SubnetNetworkAclAssociation1c:
        Type: AWS::EC2::SubnetNetworkAclAssociation
        Properties:
          SubnetId:
            Ref: PublicSubnet1c
          NetworkAclId:
            Ref: NetworkAcl
      SubnetNetworkAclAssociation1d:
        Type: AWS::EC2::SubnetNetworkAclAssociation
        Properties:
          SubnetId:
            Ref: PublicSubnet1d
          NetworkAclId:
            Ref: NetworkAcl

    テンプレートを作成したら構文チェックをします。

    1
    2
    3
    4
    5
    $ aws cloudformation validate-template --template-body file://test2.yaml
    {
        "Description": "TEST template.",
        "Parameters": []
    }

    では、既存のスタックを削除してtest2.yamlでスタックの作成を行います。
    TEST-3AZというスタック名にしました。

    期待通り3つのAZにそれぞれPublic SubnetとPrivate Subnetがひとつずつ配置されました。

    まとめ

    AWS CloudFormationの基本となるテンプレートの記述と使用方法から、複数AZにSubnetを作成するところまでを実践してみました。
    今回作成したテンプレートをベースとしてEC2やRDSの配置や、S3など他のAWSリソースと連携させる方法についてはまた別の機会にしようと思います。この記事がこれからAWSを触れてみようとしている方や、AWS認定試験に挑戦しようと考えている方にとって僅かでもお役に立てば幸いです。

    Invent2017japan portal