이 연재글은 AWS CloudFormation의 2번째 글입니다.

이전 시간에는 VPC의 기본적인 개념에 대해 살펴보았고 이번 시간에는 CloudFormation을 이용하여 직접 VPC를 생성해보겠습니다.

CloudFormation

AWS CloudFormation은 Amazon Web Services 리소스를 모델링하고 설정하여 리소스 관리 시간을 줄이고 AWS에서 실행되는 애플리케이션에 더 많은 시간을 사용하도록 해 주는 서비스입니다. 필요한 모든 AWS 리소스(예: Amazon EC2 인스턴스 또는 Amazon RDS DB 인스턴스)를 설명하는 템플릿(yaml 또는 json)을 생성하면 AWS CloudFormation이 해당 리소스의 프로비저닝과 구성을 담당합니다. AWS 리소스를 개별적으로 생성하고 구성할 필요가 없으며 어떤 것이 무엇에 의존하는지 파악할 필요도 없습니다. AWS CloudFormation에서 모든 것을 처리합니다.

CloudFormation 사용으로 얻는 이점

  • 인프라 관리를 간소화할 수 있습니다.
  • 템플릿을 이용하므로 신속한 인프라 복제가 가능합니다.
  • 템플릿 문서로 저장되므로 형상관리(Git)가 가능하여 인프라 변경 사항을 쉽게 제어 및 추적할 수 있습니다.

VPC 생성 참고용 템플릿

아래 템플릿을 기반으로 하여 Cloudformation 템플릿을 작성합니다.

https://docs.aws.amazon.com/ko_kr/codebuild/latest/userguide/cloudformation-vpc-template.html

CloudFormation으로 VPC 생성을 위한 준비

  • VPC CIDR은 10.10.0.0/16 (65534개의 ip)을 사용합니다.
  • 서울 Region에 VPC를 생성하며 총 4개의 az(availability zone)에 Subnet을 설치합니다.
  • Subnet은 public / private 각각 4개의 Subnet을 생성합니다.
  • 한 개의 Subnet이 8190개의 IP를 가질 수 있도록 다음과 같이 IP 대역을 설정합니다

public subnet 4개

  • public-subnet-a – 10.10.0.0/19 (10.10.0.1 – 10.10.31.254)
  • public-subnet-b – 10.10.32.0/19 (10.10.32.1 – 10.10.63.254)
  • public-subnet-c – 10.10.64.0/19 (10.10.64.1 – 10.10.95.254)
  • public-subnet-d – 10.10.96.0/19 (10.10.96.1 – 10.10.127.254)

private subnet 4개

  • private-subnet-a – 10.10.128.0/19 (10.10.128.1 – 10.10.159.254)
  • private-subnet-b – 10.10.160.0/19 (10.10.160.1 – 10.10.191.254)
  • private-subnet-c – 10.10.192.0/19 (10.10.192.1 – 10.10.223.254)
  • private-subnet-d – 10.10.224.0/19 (10.10.224.1 – 10.10.255.254)
  • public 서브넷에 위치한 인스턴스가 인터넷 통신이 가능하도록 Internet Gateway를 생성하여 연결합니다.
  • private 서브넷에 위치한 인스턴스가 인터넷 또는 기타 AWS 서비스에 연결할 수 있도록 NatGateway와 EIP을 생성하여 연결합니다.

CloudFormation 작성

VPC CIDR, Subnet CIDR 작성

범용으로 사용할 수 있도록 CIDR정보를 입력받을 수 있게 작성합니다. Parameters 구문을 작성하면 CloudFormation 등록 시 파라미터 값을 입력받을 수 있습니다. 바로 위에서 작성한 VPC cidr, public subnet 4개, private subnet 4개에 대한 정보를 입력하거나 기본 값을 사용하도록 다음과 같이 작성합니다. Condition 구문을 작성하면 입력 값이 유효한지 조건을 체크할 수 있습니다. Fn으로 시작하는 구문은 내장 함수를 뜻하며 아래 문서에서 자세한 내용을 살펴볼 수 있습니다.

https://docs.aws.amazon.com/ko_kr/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "Builds a VPC",

  "Parameters" : {

    "VpcCidr": {
      "Description": "Enter the whole VPC CIDR Block.",
      "Type": "String",
      "ConstraintDescription": "Supports subnet sizes of /16 to /22 only. Input must be a correct CIDR, such as: 10.0.0.0/16",
      "AllowedPattern": "^((\\d)+.){3}(0)\\/(16|17|18|19|20|21|22)",
      "Default": "10.10.0.0/16"
    },

    "PublicSubnetCIDR": {
      "Description": "Public Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.0.0/19"
    },

    "PublicSubnetCIDR2": {
      "Description": "2nd Public Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.32.0/19"
    },

    "PublicSubnetCIDR3": {
      "Description": "3nd Public Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.64.0/19"
    },

    "PublicSubnetCIDR4": {
      "Description": "4nd Public Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.96.0/19"
    },

    "PrivateSubnetCIDR": {
      "Description": "Private Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.128.0/19"
    },

    "PrivateSubnetCIDR2": {
      "Description": "2nd Private Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.160.0/19"
    },

    "PrivateSubnetCIDR3": {
      "Description": "3nd Private Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.192.0/19"
    },

    "PrivateSubnetCIDR4": {
      "Description": "4nd Private Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.224.0/19"
    }
  },

  "Conditions": {
    "CreateSubnetA": { "Fn::And": [
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PublicSubnetCIDR"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PublicSubnetCIDR2"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PublicSubnetCIDR3"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PublicSubnetCIDR4"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PrivateSubnetCIDR"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PrivateSubnetCIDR2"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PrivateSubnetCIDR3"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PrivateSubnetCIDR4"}, "" ] } ] }
    ]}
  },
// 이어서 설명할 것이므로 아래 내용 생략

AWS 리소스 생성

Resource 구문안에 생성할 리소스 정보를 나열하면 Cloudformation에 의해 해당 리소스가 생성됩니다.

"Resources" : {

}

VPC 생성

위에서 입력받은 값을 바탕으로 VPC를 생성합니다. “Type” : “AWS::EC2::VPC”을 명시하고 관련 항목을 작성하면 VPC가 생성됩니다. “Ref” 구문을 사용하면 위에서 선언한 변수를 가져다 사용할 수 있습니다. AWS에서 제공하는 변수를 사용하려면 {“Ref” : “AWS:환경변수명”}을 이용하여 값을 불러올 수 있습니다. 이러한 변수를 Pseudo parameter라고 하며 참고할 수 있는 파라미터는 아래 문서에서 확인할 수 있습니다.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html

"Resources" : {

  "VPC" : {
    "Type" : "AWS::EC2::VPC",
    "Properties" : {
      "CidrBlock" : { "Ref" : "VpcCidr" },
      "EnableDnsHostnames" : true,
      "EnableDnsSupport" : true,
      "Tags" : [
        {"Key" : "Name", "Value" : { "Ref" : "AWS::StackName"} },
      ]
    }
  },
// 아래 내용 생략

Subnet 생성

“Type” : “AWS::EC2::Subnet” 구문을 사용하여 Subnet을 생성하는 템플릿을 작성합니다. Public, Private Subnet을 작성하는 것이라 내용은 길지만 동일한 구문의 반복이므로 그렇게 복잡하지 않습니다. 필요한 변수 값은 Ref 구문을 이용하여 위에서 입력한 값을 가져다 쓰도록 되어 있습니다.

    "PublicSubnet" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PublicSubnetCIDR" },
        "AvailabilityZone" : { "Fn::Select": [ "0", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PublicSubnet1"]]} },
        ]
      }
    },

    "PublicSubnet2" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PublicSubnetCIDR2" },
        "AvailabilityZone" : { "Fn::Select": [ "1", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PublicSubnet2"]]} },
        ]
      }
    },

    "PublicSubnet3" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PublicSubnetCIDR3" },
        "AvailabilityZone" : { "Fn::Select": [ "2", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PublicSubnet3"]]} },
        ]
      }
    },

    "PublicSubnet4" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PublicSubnetCIDR4" },
        "AvailabilityZone" : { "Fn::Select": [ "2", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PublicSubnet4"]]} },
        ]
      }
    },

    "PrivateSubnet" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PrivateSubnetCIDR" },
        "AvailabilityZone" : { "Fn::Select": [ "0", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PrivateSubnet1"]]} },
        ]
      }
    },

    "PrivateSubnet2" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PrivateSubnetCIDR2" },
        "AvailabilityZone" : { "Fn::Select": [ "1", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PrivateSubnet2"]]} },
        ]
      }
    },

    "PrivateSubnet3" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PrivateSubnetCIDR3" },
        "AvailabilityZone" : { "Fn::Select": [ "2", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" :{"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PrivateSubnet3"]]} },
          {"Key" : "Network", "Value" : "Private" },
          {"Key" : "Environment", "Value" : "Qa" }
        ]
      }
    },

    "PrivateSubnet4" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PrivateSubnetCIDR4" },
        "AvailabilityZone" : { "Fn::Select": [ "2", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" :{"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PrivateSubnet4"]]} },
          {"Key" : "Network", "Value" : "Private" },
          {"Key" : "Environment", "Value" : "Qa" }
        ]
      }
    },
// 아래 내용 생략

Internet Gateway, Public Route Table

AWS::EC2::InternetGateway 구문을 작성하여 인터넷 게이트웨이를 생성합니다. 생성한 인터넷 게이트웨이는 AWS::EC2::VPCGatewayAttachment 구문을 이용하여 VPC에 인터넷 게이트웨이를 연결합니다. AWS::EC2::RouteTable 구문을 통해 Public route table을 생성하고 “AWS::EC2::Route 구문으로 외부로 요청하는 트래픽은 InternetGateway를 사용하도록 설정합니다.

    
   "InternetGateway" : {
      "Type" : "AWS::EC2::InternetGateway",
      "Properties" : {
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "InternetGateway"]]} },
          {"Key" : "Network", "Value" : "Public" },
          {"Key" : "Environment", "Value" : "Qa" }
        ]
      }
    },

    "VPCGatewayAttach" : {
      "DependsOn" : ["VPC", "InternetGateway"],
      "Type" : "AWS::EC2::VPCGatewayAttachment",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "InternetGatewayId" : { "Ref" : "InternetGateway" }
      }
    },

    "PublicRouteTable" : {
      "DependsOn" : ["VPC"],
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId" : {"Ref" : "VPC"},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PublicRouteTable"]]} },
          {"Key" : "Network", "Value" : "Public" },
          {"Key" : "Environment", "Value" : "Qa" }
        ]
      }
    },

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

“Type” : “AWS::EC2::SubnetRouteTableAssociation” 구문으로 Public Subnet의 인스턴스들이 Public RouteTable을 사용하도록 설정합니다.

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

    "PublicSubnetRouteTableAssociation2" : {
      "DependsOn" : ["PublicSubnet2", "PublicRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet2" },
        "RouteTableId" : { "Ref" : "PublicRouteTable" }
      }
    },

    "PublicSubnetRouteTableAssociation3" : {
      "DependsOn" : ["PublicSubnet3", "PublicRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet3" },
        "RouteTableId" : { "Ref" : "PublicRouteTable" }
      }
    },

    "PublicSubnetRouteTableAssociation4" : {
      "DependsOn" : ["PublicSubnet4", "PublicRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet4" },
        "RouteTableId" : { "Ref" : "PublicRouteTable" }
      }
    },

Private RouteTable

“AWS::EC2::RouteTable” 구문으로 Private Subnet을 위한 RouteTable을 생성하고, “AWS::EC2::SubnetRouteTableAssociation” 구문으로 Private Subnet의 인스턴스들이 Private RouteTable을 사용하도록 설정합니다.

    "PrivateRouteTable" : {
      "DependsOn" : ["VPC"],
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "Tags" : [
          { "Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PrivateRouteTable"]]} },
        ]
      }
    },

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

    "PrivateSubnetRouteTableAssociation2" : {
      "DependsOn" : ["PrivateSubnet2", "PrivateRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PrivateSubnet2" },
        "RouteTableId" : { "Ref" : "PrivateRouteTable" }
      }
    },

    "PrivateSubnetRouteTableAssociation3" : {
      "DependsOn" : ["PrivateSubnet3", "PrivateRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PrivateSubnet3" },
        "RouteTableId" : { "Ref" : "PrivateRouteTable" }
      }
    },

    "PrivateSubnetRouteTableAssociation4" : {
      "DependsOn" : ["PrivateSubnet4", "PrivateRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PrivateSubnet4" },
        "RouteTableId" : { "Ref" : "PrivateRouteTable" }
      }
    },

NAT Gateway, EIP(Elastic IP), Route Table

“AWS::EC2::NatGateway” 구문으로 NAT Gateway를 생성합니다. 이때 주의 깊게 살펴봐야 할 부분은 NatGateway에 EIP(Elastic IP)를 설정하고 PublicSubnet에 생성되도록 설정하는 부분입니다. 마지막으로 PrivateRouteTable의 Route 정보를 수정하여 외부로 요청해야 하는 트래픽은 NatGateway를 향하도록 설정합니다.

    "NATGateway" : {
      "Type" : "AWS::EC2::NatGateway",
      "DependsOn": "VPCGatewayAttach",
      "Properties" : {
        "AllocationId" : { "Fn::GetAtt" : ["NATGatewayEIP", "AllocationId"]},
        "SubnetId" : { "Ref" : "PublicSubnet"},
        "Tags" : [
          { "Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "NATGateway"]]} },
        ]
      }
    },

    "NATGatewayEIP" : {
      "Type" : "AWS::EC2::EIP",
      "Properties" : {
        "Domain" : "vpc",
        "Tags" : [
          { "Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "NATGatewayEIP"]]} },
        ]
      }
    },

    "NATRoute" : {
      "Type" : "AWS::EC2::Route",
      "Properties" : {
        "RouteTableId" : { "Ref" : "PrivateRouteTable" },
        "DestinationCidrBlock" : "0.0.0.0/0",
        "NatGatewayId" : { "Ref" : "NATGateway" }
      }
    }
  },

여기까지 작성하면 VPC 템플릿 작성이 완료됩니다. 위에서 설명한 내용을 모두 합치면 아래와 같은 템플릿이 완성되며 .template 확장자로 파일을 저장합니다. 실습에서는 vpc.template으로 저장합니다.

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "Builds a VPC",

  "Parameters" : {

    "VpcCidr": {
      "Description": "Enter the whole VPC CIDR Block.",
      "Type": "String",
      "ConstraintDescription": "Supports subnet sizes of /16 to /22 only. Input must be a correct CIDR, such as: 10.0.0.0/16",
      "AllowedPattern": "^((\\d)+.){3}(0)\\/(16|17|18|19|20|21|22)",
      "Default": "10.10.0.0/16"
    },

    "PublicSubnetCIDR": {
      "Description": "Public Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.0.0/19"
    },

    "PublicSubnetCIDR2": {
      "Description": "2nd Public Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.32.0/19"
    },

    "PublicSubnetCIDR3": {
      "Description": "3nd Public Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.64.0/19"
    },

    "PublicSubnetCIDR4": {
      "Description": "4nd Public Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.96.0/19"
    },

    "PrivateSubnetCIDR": {
      "Description": "Private Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.128.0/19"
    },

    "PrivateSubnetCIDR2": {
      "Description": "2nd Private Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.160.0/19"
    },

    "PrivateSubnetCIDR3": {
      "Description": "3nd Private Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.192.0/19"
    },

    "PrivateSubnetCIDR4": {
      "Description": "4nd Private Subnet CIDR.",
      "Type": "String",
      "AllowedPattern": "^(((\\d)+.){3}(0)\\/(18|19|20|21|22|23|24|25)|)",
      "Default": "10.10.224.0/19"
    }
  },

  "Conditions": {
    "CreateSubnetA": { "Fn::And": [
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PublicSubnetCIDR"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PublicSubnetCIDR2"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PublicSubnetCIDR3"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PublicSubnetCIDR4"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PrivateSubnetCIDR"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PrivateSubnetCIDR2"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PrivateSubnetCIDR3"}, "" ] } ] },
      { "Fn::Not" : [ { "Fn::Equals" : [ {"Ref" : "PrivateSubnetCIDR4"}, "" ] } ] }
    ]}
  },

  "Resources" : {

    "VPC" : {
      "Type" : "AWS::EC2::VPC",
      "Properties" : {
        "CidrBlock" : { "Ref" : "VpcCidr" },
        "EnableDnsHostnames" : true,
        "EnableDnsSupport" : true,
        "Tags" : [
          {"Key" : "Name", "Value" : { "Ref" : "AWS::StackName"} },
        ]
      }
    },

    "PublicSubnet" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PublicSubnetCIDR" },
        "AvailabilityZone" : { "Fn::Select": [ "0", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PublicSubnet1"]]} },
          {"Key" : "Network", "Value" : "Public" }
        ]
      }
    },

    "PublicSubnet2" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PublicSubnetCIDR2" },
        "AvailabilityZone" : { "Fn::Select": [ "1", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PublicSubnet2"]]} },
          {"Key" : "Network", "Value" : "Public" }
        ]
      }
    },

    "PublicSubnet3" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PublicSubnetCIDR3" },
        "AvailabilityZone" : { "Fn::Select": [ "2", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PublicSubnet3"]]} },
          {"Key" : "Network", "Value" : "Public" }
        ]
      }
    },

    "PublicSubnet4" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PublicSubnetCIDR4" },
        "AvailabilityZone" : { "Fn::Select": [ "2", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PublicSubnet4"]]} },
          {"Key" : "Network", "Value" : "Public" }
        ]
      }
    },

    "PrivateSubnet" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PrivateSubnetCIDR" },
        "AvailabilityZone" : { "Fn::Select": [ "0", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PrivateSubnet1"]]} },
          {"Key" : "Network", "Value" : "Private" }
        ]
      }
    },

    "PrivateSubnet2" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PrivateSubnetCIDR2" },
        "AvailabilityZone" : { "Fn::Select": [ "1", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PrivateSubnet2"]]} },
          {"Key" : "Network", "Value" : "Private" }
        ]
      }
    },

    "PrivateSubnet3" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PrivateSubnetCIDR3" },
        "AvailabilityZone" : { "Fn::Select": [ "2", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" :{"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PrivateSubnet3"]]} },
          {"Key" : "Network", "Value" : "Private" }
        ]
      }
    },

    "PrivateSubnet4" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : { "Ref" : "PrivateSubnetCIDR4" },
        "AvailabilityZone" : { "Fn::Select": [ "2", {"Fn::GetAZs": {"Ref": "AWS::Region"}} ]},
        "Tags" : [
          {"Key" : "Name", "Value" :{"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PrivateSubnet4"]]} },
          {"Key" : "Network", "Value" : "Private" }
        ]
      }
    },

    "InternetGateway" : {
      "Type" : "AWS::EC2::InternetGateway",
      "Properties" : {
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "InternetGateway"]]} },
          {"Key" : "Network", "Value" : "Public" }
        ]
      }
    },

    "VPCGatewayAttach" : {
      "DependsOn" : ["VPC", "InternetGateway"],
      "Type" : "AWS::EC2::VPCGatewayAttachment",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "InternetGatewayId" : { "Ref" : "InternetGateway" }
      }
    },

    "PublicRouteTable" : {
      "DependsOn" : ["VPC"],
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId" : {"Ref" : "VPC"},
        "Tags" : [
          {"Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PublicRouteTable"]]} },
          {"Key" : "Network", "Value" : "Public" }
        ]
      }
    },

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

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

    "PublicSubnetRouteTableAssociation2" : {
      "DependsOn" : ["PublicSubnet2", "PublicRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet2" },
        "RouteTableId" : { "Ref" : "PublicRouteTable" }
      }
    },

    "PublicSubnetRouteTableAssociation3" : {
      "DependsOn" : ["PublicSubnet3", "PublicRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet3" },
        "RouteTableId" : { "Ref" : "PublicRouteTable" }
      }
    },

    "PublicSubnetRouteTableAssociation4" : {
      "DependsOn" : ["PublicSubnet4", "PublicRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet4" },
        "RouteTableId" : { "Ref" : "PublicRouteTable" }
      }
    },

    "PrivateRouteTable" : {
      "DependsOn" : ["VPC"],
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "Tags" : [
          { "Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "PrivateRouteTable"]]} },
          { "Key" : "Network", "Value" : "Private" }
        ]
      }
    },

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

    "PrivateSubnetRouteTableAssociation2" : {
      "DependsOn" : ["PrivateSubnet2", "PrivateRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PrivateSubnet2" },
        "RouteTableId" : { "Ref" : "PrivateRouteTable" }
      }
    },

    "PrivateSubnetRouteTableAssociation3" : {
      "DependsOn" : ["PrivateSubnet3", "PrivateRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PrivateSubnet3" },
        "RouteTableId" : { "Ref" : "PrivateRouteTable" }
      }
    },

    "PrivateSubnetRouteTableAssociation4" : {
      "DependsOn" : ["PrivateSubnet4", "PrivateRouteTable"],
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PrivateSubnet4" },
        "RouteTableId" : { "Ref" : "PrivateRouteTable" }
      }
    },

    "NATGateway" : {
      "Type" : "AWS::EC2::NatGateway",
      "DependsOn": "VPCGatewayAttach",
      "Properties" : {
        "AllocationId" : { "Fn::GetAtt" : ["NATGatewayEIP", "AllocationId"]},
        "SubnetId" : { "Ref" : "PublicSubnet"},
        "Tags" : [
          { "Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "NATGateway"]]} }
        ]
      }
    },

    "NATGatewayEIP" : {
      "Type" : "AWS::EC2::EIP",
      "Properties" : {
        "Domain" : "vpc",
        "Tags" : [
          { "Key" : "Name", "Value" : {"Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName"}, "NATGatewayEIP"]]} }
        ]
      }
    },

    "NATRoute" : {
      "Type" : "AWS::EC2::Route",
      "Properties" : {
        "RouteTableId" : { "Ref" : "PrivateRouteTable" },
        "DestinationCidrBlock" : "0.0.0.0/0",
        "NatGatewayId" : { "Ref" : "NATGateway" }
      }
    }
  }
}

AWS Console에서 CloudFormation 스택 생성

Cloudformation에 vpc.template을 등록하여 vpc를 생성합니다. aws cloudformation console로 이동하여 create stack을 클릭합니다. template is ready -> upload a template file을 선택하고 로컬에 작성한 template 파일을 선택하고 Next를 클릭합니다.

View in Designer를 클릭하면 작성한 template이 어떤 구조를 가지고 있는지 Diagram으로 확인할 수 있습니다.

Specify stack detail – 스택의 설정 내용 확인

스택의 이름을 입력합니다. 그리고 template에 설정한 내용이 표시되는데 맞는지 확인 합니다. 입력 내용에 문제가 없으면 Next를 클릭합니다.

Configure stack options – 옵션 설정

추가로 설정할 옵션을 입력합니다. 실습에서는 없으므로 Next를 클릭합니다.

Review sample-vpc

최종 설정한 내용을 확인합니다. 문제가 없으면 Create stack을 눌러 Cloudformation을 실행합니다. Status가 CREATE_IN_PROGRESS로 변경됩니다. 각각의 단계마다 Event 내역이 표시되며 Stack 생성 중 실패하면 빨간색으로 표시되며 작업이 중단됩니다. 최종 완료되면 CREATE_COMPLETE로 변경됩니다.

VPC 확인

vpc console로 이동하면 새로 생성된 sample-vpc를 확인할 수 있습니다.

Subnet 확인

vpc- subnets에서 public/private subnet이 정상적으로 생성된 것을 확인할 수 있습니다.

Route Table 확인

vpc – route tables에서 public/private routetable을 확인할 수 있으며 subnet도 정상적으로 association된것을 확인할 수 있습니다. public route table에는 internet-gateway와 public subnet 4개가 연결되었습니다

private route table에는 nat-gateway와 private subnet 4개가 연결되었습니다.

Internet Gateway 확인

public route table에서 확인한 Internet gateway ID를 확인할 수 있습니다.

Nat Gateway 확인

private route table에서 확인한 Nat gateway ID를 확인할 수 있습니다.

details에서 EIP(Elastic IP address)가 잘 연결되어 있는것도 확인이 가능합니다.

VPC 구성요소에 대한 모든 확인이 끝났습니다. CloudFormation 템플릿을 사용하면 이렇게 복잡한 구조의 VPC도 쉽게 생성하고 관리할 수 있습니다. 한번 template을 작성해 놓으면 다음번 vpc를 만들땐 보다 더 쉽고 빠르게 구성이 가능할 것이므로 유용합니다. 따라서 AWS 인프라 구성은 Cloudformation template으로 작성해 놓는것이 향후 관리적 측면에서 유리하므로 적극적인 사용이 필요하다고 생각합니다..

연재글 이동[이전글] CloudFormation으로 하는 AWS 인프라관리 – Virtual Private Cloud(VPC) 알아보기
[다음글] CloudFormation으로 하는 AWS 인프라관리 – Database(RDS) 생성하기