ちょっと話題の記事

AWS再入門2018 AWS CloudFormation編

2018.02.04

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

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

はじめに

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

もくじ

概要

  • 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の内容を簡潔に変更、無駄な改行を減らしたりリソース名を変更したものが以下となります。

{
  "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としました。

$ 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 に変換する 素晴らしい世界ですね。
早速実行します。

$ 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ファイルで作成したスタックを削除してから実行します。

2018/02/06追記
はてブのコメントにて教えていただきました。気づいていませんでした。ありがとうございます。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/working-with-templates-cfn-designer-json-editor.html


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

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

まだ物足りない

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

複数AZにSubnetを配置させる

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

---
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

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

$ 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認定試験に挑戦しようと考えている方にとって僅かでもお役に立てば幸いです。