VPC を云々する CloudFormation なテンプレ

ざくっと作ってみました。基本的には

  • NetworkACL は使わない
  • サブネットは四つ
    • public 一つと private 三つ
    • 二つの private は multi AZ な実装で RDS を配置できるように
  • ELB も投入
  • SecurityGroup でアクセス制限
    • public なセグメントに踏台と nat なサーバがある形
    • outbound は基本スルー
    • memcached な 11211 ポートを云々
  • SecurityGroup については作成したのみ

みたいな形。あ、あと SecurityGroup 書いてる時に

  • 複数の項目なアクセス制限を表現するために配列を使っている
  • icmp もポート指定必須

なあたりで微妙にハマりました。あと、現時点で理解できてる範囲で必要な記述 (?) を以下に列挙してみます。

  • VPC の定義 (AWS::EC2::VPC)
  • InternetGateway の定義と VPC との関連付け (AWS::EC2::VPCGatewayAttachment)
  • サブネットの定義 (AWS::EC2::Subnet)
  • ルーティングテーブル関連
    • AWS::EC2::Routetable を作って VPC と関連付け
    • AWS::EC2::Route を作って RouteTable と関連付け
    • AWS::EC2::SubnetRouteTableAssociation で Routetable と Subnet を関連付け
  • SecurityGroup 関連
    • VPC と関連付け
    • SecurityGroupIngress (inbound 通信設定)、SecurityGroupEgress (outbound 通信設定) を作る
      • 配列が関連付けられます
      • 要素の属性としては IpProtocol (必須)、FromPort、ToPort、CidrIp などがあります

なんとなく今時点で雛形っぽくなってるのは以下なソレ。

{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "AWS CloudFormation Template vpc_multiple_subnets.",

  "Resources" : {

    "VPC" : {
      "Type" : "AWS::EC2::VPC",
      "Properties" : {
        "CidrBlock" : "10.0.0.0/16",
        "Tags" : [
          {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} },
          {"Key" : "Network", "Value" : "Public" }
        ]
      }
    },

    "PublicSubnet" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
	"AvailabilityZone" : "ap-northeast-1a",
        "CidrBlock" : "10.0.0.0/24",
        "Tags" : [
          {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} },
          {"Key" : "Network", "Value" : "Public" }
        ]
      }
    },

    "InternetGateway" : {
      "Type" : "AWS::EC2::InternetGateway",
      "Properties" : {
        "Tags" : [
          {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} },
          {"Key" : "Network", "Value" : "Public" }
        ]
      }
    },

    "AttachGateway" : {
       "Type" : "AWS::EC2::VPCGatewayAttachment",
       "Properties" : {
         "VpcId" : { "Ref" : "VPC" },
         "InternetGatewayId" : { "Ref" : "InternetGateway" }
       }
    },

    "PublicRouteTable" : {
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId" : {"Ref" : "VPC"},
        "Tags" : [
          {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} },
          {"Key" : "Network", "Value" : "Public" }
        ]
      }
    },

    "PublicRoute" : {
      "Type" : "AWS::EC2::Route",
      "Properties" : {
        "RouteTableId" : { "Ref" : "PublicRouteTable" },
        "DestinationCidrBlock" : "0.0.0.0/0",
        "GatewayId" : { "Ref" : "InternetGateway" }
      }
    },

    "PublicSubnetRouteTableAssociation" : {
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet" },
        "RouteTableId" : { "Ref" : "PublicRouteTable" }
      }
    },

    "PrivateSubnet" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
	"AvailabilityZone" : "ap-northeast-1a",
        "CidrBlock" : "10.0.1.0/24",
        "Tags" : [
          {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} },
          {"Key" : "Network", "Value" : "Private" }
        ]
      }
    },

    "PrivateRouteTable" : {
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId" : {"Ref" : "VPC"},
        "Tags" : [
          {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} },
          {"Key" : "Network", "Value" : "Private" }
        ]
      }
    },

    "PrivateSubnetRouteTableAssociation" : {
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PrivateSubnet" },
        "RouteTableId" : { "Ref" : "PrivateRouteTable" }
      }
    },

    "DatabaseSubnet1" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
	"AvailabilityZone" : "ap-northeast-1a",
        "CidrBlock" : "10.0.2.0/24",
        "Tags" : [
          {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} },
          {"Key" : "Network", "Value" : "Database1" }
        ]
      }
    },

    "DatabaseRouteTable" : {
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId" : {"Ref" : "VPC"},
        "Tags" : [
          {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} },
          {"Key" : "Network", "Value" : "Database1" }
        ]
      }
    },

    "DatabaseSubnet1RouteTableAssociation" : {
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "DatabaseSubnet1" },
        "RouteTableId" : { "Ref" : "DatabaseRouteTable" }
      }
    },

    "DatabaseSubnet2" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
	"AvailabilityZone" : "ap-northeast-1b",
        "CidrBlock" : "10.0.3.0/24",
        "Tags" : [
          {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} },
          {"Key" : "Network", "Value" : "Database2" }
        ]
      }
    },

    "DatabaseSubnet2RouteTableAssociation" : {
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "DatabaseSubnet2" },
        "RouteTableId" : { "Ref" : "DatabaseRouteTable" }
      }
    },

    "ElasticLoadBalancer" : {
      "Type" : "AWS::ElasticLoadBalancing::LoadBalancer",
      "Properties" : {
        "SecurityGroups" : [ { "Ref" : "LoadBalancerSecurityGroup" } ],
        "Subnets" : [ { "Ref" : "PublicSubnet" } ],
        "Listeners" : [ { "LoadBalancerPort" : "80", "InstancePort" : "80", "Protocol" : "HTTP" } ],
        "HealthCheck" : {
          "Target" : "HTTP:80/",
          "HealthyThreshold" : "3",
          "UnhealthyThreshold" : "5",
          "Interval" : "90",
          "Timeout" : "60"
        }
      }
    },

    "SSHSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "FUMIDAI",
        "VpcId" : { "Ref" : "VPC" },
        "SecurityGroupIngress" : [ { "IpProtocol" : "-1", "CidrIp" : "10.0.0.0/24" },
	{ "IpProtocol" : "-1", "CidrIp" : "10.0.1.0/24" },
	{ "IpProtocol" : "-1", "CidrIp" : "10.0.2.0/24" },
	{ "IpProtocol" : "-1", "CidrIp" : "10.0.3.0/24" },
	{ "IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "x.x.x.x/32" },
	{ "IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "y.y.y.y/32" } ],
        "SecurityGroupEgress" : [ { "IpProtocol" : "-1", "CidrIp" : "0.0.0.0/0" } ]
      }
    },

    "APSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "AP",
        "VpcId" : { "Ref" : "VPC" },
        "SecurityGroupIngress" : [ { "IpProtocol" : "icmp", "FromPort" : "-1", "ToPort" : "-1", "CidrIp" : "0.0.0.0/0" },
	{ "IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "10.0.0.0/24" },
	{ "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0" },
	{ "IpProtocol" : "tcp", "FromPort" : "443", "ToPort" : "443", "CidrIp" : "0.0.0.0/0" },
	{ "IpProtocol" : "tcp", "FromPort" : "11211", "ToPort" : "11211", "CidrIp" : "10.0.1.0/24" } ],
        "SecurityGroupEgress" : [ { "IpProtocol" : "-1", "CidrIp" : "0.0.0.0/0" } ]
      }
    },

    "DBSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "RDS",
        "VpcId" : { "Ref" : "VPC" },
        "SecurityGroupIngress" : [ { "IpProtocol" : "tcp", "FromPort" : "3306", "ToPort" : "3306", "CidrIp" : "10.0.0.0/16" },
	{ "IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "10.0.0.0/16" } ],
        "SecurityGroupEgress" : [ { "IpProtocol" : "-1", "CidrIp" : "0.0.0.0/0" } ]
      }
    },

    "LoadBalancerSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "Enable HTTP access on port 80",
        "VpcId" : { "Ref" : "VPC" },
        "SecurityGroupIngress" : [ { "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0" } ],
        "SecurityGroupEgress" : [ { "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0" } ]
      }
    }
  },

  "Outputs" : {
    "URL" : {
      "Description" : "URL of the website",
      "Value" :  { "Fn::Join" : [ "", [ "http://", { "Fn::GetAtt" : [ "ElasticLoadBalancer", "DNSName" ]}]]}
    }
  }
}

Github 方面にも放り込んでおくかどうするか。

あ、あと validate なナニを以下に控え。

require 'yaml'
require 'json'
require 'aws-sdk'

config_file = File.join(File.dirname(__FILE__), "../config.yml")
config = YAML.load(File.read(config_file))
AWS.config(config)

cfm = AWS::CloudFormation.new
template_file = File.join(File.dirname(__FILE__), "vpc_multiple_subnet.template")
template_json = JSON.parse(File.read(template_file))

cfm.validate_template(template_json)

ファイルの名前とか位置は適当に読みかえて頂ければと。