Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

소개

terrafom tutorial문서에서 아주 간단하게 사용방법을 알아봤다. 이제 좀 복잡한 인프라를 구성하면서 테라폼의 다양한 기능들을 살펴보려 한다. 이 문서는 terrafom tutorial를 기반으로 개선을 하는게 내용이므로 링크의 문서를 참고해야 한다.

테라폼으로 구성할 인프라는 아래와 같다.

 VPC 인프라

  • 2개의 가용영역을 사용한다. : 현재 서울리전은 3개의 가용영역을 가지고 있지만, 지금은 2개의 가용영역만 사용한다. 배열로 관리 할 거라서 3개로 확장은 간단하다.
  • 퍼블릭 서브넷과 프라이빗 서브넷을 구성한다. 2개의 가용영역에 배치되므로 총 4개의 서브넷이 배치된다.
  • 퍼블릭 서브넷으로의 인터넷에서의 접근을 위해서 인터넷 게이트웨이를 만든다.
  • 프라이빗 서브넷에서 인터넷으로 접근하기 위한 Nat gateway를 만든다.
여기에서 사용한 예제의 원본은 여기에서 확인 할 수 있다. 원본의 내용을 개선하는 형태로 이루어진다.

버전

Terraform v0.11.13으로 테스트했다. (2019년 7월 2일)현재 최신버전은 v.12다. v.12 부터 for, if 문을 지원하는 등 변화가 크다.

가용영역 설정

ap-northeast-2a, ap-northeast-2b 두 개의 가용영역을 선택하기로 했다. 변수를 아래와 같이 정의했다.
variable "availability_zone" {
  description = "Seoul region availability zone"
  type = "list"
  default = ["ap-northeast-2a", "ap-northeast-2b"]
}
가용 영역에 배열(list)를 사용했다.

Public subnet 정의

아래와 같이 퍼블릭 서브넷을 적용해보자.
resource "aws_subnet" "public" {
  count = "${length(var.availability_zone)}"
  availability_zone = "${element(var.availability_zone, count.index)}"
  vpc_id = "${aws_vpc.default.id}"
  cidr_block = "${cidrsubnet(var.vpc_cidr, 8, count.index)}"
  tags  { 
    Name = "Public Subnet - ${element(var.availability_zone, count.index)}"
  }
}
거의 모든 terraform 리소스(resource)는 메타데이터 매개변수를 가지고 있다. count는 terrafrom의 count를 증가하면서 구문 블럭을 추가실행 하도록 해주는 매개변수다. HCL(HashCorp Configuration Language)는 선언적언어이기 때문에 루프나 if/else 문을 제공하지 않는다. 이는 인프라를 코드화하는 작업을 어렵게 만들 수 있다. count를 이용해서 루프를 실행 할 수 있다. 아래의 예제를 보자.
resource "aws_instance" "example" {
  count = 3
  ami = "ami-2d39803a"
  instance_type = "t2.micro"
}
위 블럭을 일반언어로 바꾸면 대략 아래와 같이 표현할 수 있다.
for i := 0; i < 3; i++ {
  resource "aws_instance" "example" {
    ami = "ami-2d39803a"
    instance_type = "t2.micro"
  }
}

HCL은 내장 함수들을 제공한다. length는 HCL의 내장 함수 중 하나로 배열의 크기를 반환한다. 위 코드에서는 2가 반환될 것이다. cidrsubnet는 cidr 서브넷을 계산하는 내장 함수다.
cidrsubnet(prefix, newbits, netnum)
  • prefix : 10.100.0.0/16 이 될 것이다.
  • newbits : 24 비트 서브넷을 사용하기로 했으니 8이 된다.
  • netnum : 네트워크 인덱스로 0부터 시작한다.
terraform console명령으로 함수를 테스트해볼 수 있다.
# terraform console
> cidrsubnet("10.100.0.0/16", 8, 0)
10.100.0.0/24
> cidrsubnet("10.100.0.0/16", 8, 1)
10.100.1.0/24
> cidrsubnet("10.100.0.0/16", 8, 2)
10.100.2.0/24
> cidrsubnet("10.100.0.0/16", 8, 3)
10.100.3.0/24
element는 배열에서 아이템을 꺼낸다. 해석을 해보자.
  1. count : 가용영역(availability_zone)의 크기는 2이므로 이 리소스 블럭은 두 반복된다. for 루프문의 다른 구현이라고 볼 수 있겠다. 첫번째 실행에서는 ap-northeast-2a, 다은 번째 실행에서는 ap-northeast-2b가 될 것이다. 결론적으로 2개의 서브넷은 서로 다른 가용영역에 위치하게 될 것이다.
  2. cidr_block : count.index는 0, 1이 될 것이다. 결국 10.100.0.0/24, 10.100.1.0/24 두 개의 서브넷이 만들어진다.
  3. tags : Name 태그를 사용했다. 태그는 필터링, 분류, 과금, 통계 등을 위한 중요한 데이터다. 나중에 활용 할 수 있도록 자원에 대한 정보를 포함해야 한다. element를 이용해서 availability_zone을 가져오기로 했다.
Private subnet도 동일한 방법으로 구성했다.
resource "aws_subnet" "private" {
  count = "${length(var.availability_zone)}"
  availability_zone = "${element(var.availability_zone, count.index)}"
  vpc_id = "${aws_vpc.default.id}"
  cidr_block = "${cidrsubnet(var.vpc_cidr, 8, count.index+2)}"
  tags  {
    Name = "Private Subnet - ${element(var.availability_zone, count.index)}"
  }
} 

NAT Gateway

Private subnet에 있는 인스턴스는 NAT Gateway를 통해서 인터넷과 통신 할 수 있다. 이를 위해서 필요한 요소는 아래와 같다.
  • Publc subnet에 위치해야 한다. 그래야 internet gateway를 통해서 패킷을 인터넷으로 보낼 수 있다.
  • 고정된 EIP가 필요하다.
EIP를 만든다.
resource "aws_eip" "nat_eip" {
  vpc = true
  depends_on = ["aws_internet_gateway.gw"]
}
Internet gateway가 만들어지고 난 다음에, EIP를 붙이도록 했다.

NAT gateway를 만든다.
resource "aws_nat_gateway" "nat" {
  allocation_id = "${aws_eip.nat_eip.id}"
  subnet_id = "${element(aws_subnet.public.*.id, 0)}"
  depends_on = ["aws_internet_gateway.gw"]
}
  • allocation_id : EIP를 NAT gateway에 할당했다.
  • subnet_id : NAT gateway는 0번 퍼블릭 서브넷에만 배치하기로 했다. 실제 환경에서 가용성이 중요하다면 두 개 서브넷에 배치할 것이다.
  • depends_on : Internet gateway가 만들어진 후 NAT gateway를 전개한다.
Private subnet 라우팅 테이블에 nat gateway로 향하는 룰(0.0.0.0/0을 nat gateway로)을 추가했다.
resource "aws_route_table" "private" {
  vpc_id = "${aws_vpc.default.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_nat_gateway.nat.id}"
  }

  tags {
    Name = "Private Subnet Route Table"
  }
}

이제 라우팅 테이블에 각 subnet을 associate 하면 된다. 마지막으로 라우팅 테이블(aws_route_table.private)에 private subnet 을 associate 하면 된다.
resource "aws_route_table_association" "public" {
  count = "${length(var.availability_zone)}"
  subnet_id = "${element(aws_subnet.public.*.id, count.index)}"
  route_table_id = "${aws_route_table.public.id}"
}

resource "aws_route_table_association" "private" {
  count = "${length(var.availability_zone)}"
  subnet_id = "${element(aws_subnet.private.*.id, count.index)}"
  route_table_id = "${aws_route_table.private.id}"
}
  • 2개의 가용영역에있는 서브넷들이 배치된다. 따라서 count에 가용영역의 크기가 설정되게 했다.
  • element 함수를 이용해서 2개의 서브넷들이 배치되게했다.
terraform plan, terraform apply 로 우리가 정의한 형상을 전개하자.

검증

제대로 만들어졌는지 검증해보자. vpc를 확인했다.
# aws ec2 describe-vpcs
{
    "Vpcs": [
        {
            "CidrBlock": "10.100.0.0/16",
            "DhcpOptionsId": "dopt-535a9438",
            "State": "available",
            "VpcId": "vpc-0b22c42f51fdb0dcc",
            "OwnerId": "522373083963",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-0d094cfcf3aadec7e",
                    "CidrBlock": "10.100.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                }
            ],
            "IsDefault": false,
            "Tags": [
                {
                    "Key": "Name",
                    "Value": "test-vpc"
                }
            ]
        }

}
vpc-cidr-assoc-0d094cfcf3aadec7e의 subnet 정보다.
# aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-0b22c42f51fdb0dcc"
{
    "Subnets": [
        {
            "AvailabilityZone": "ap-northeast-2a",
            "AvailabilityZoneId": "apne2-az1",
            "AvailableIpAddressCount": 250,
            "CidrBlock": "10.100.0.0/24",
            "DefaultForAz": false,
            "MapPublicIpOnLaunch": false,
            "State": "available",
            "SubnetId": "subnet-04a9e32c2d120e3db",
            "VpcId": "vpc-0b22c42f51fdb0dcc",
            "OwnerId": "522373083963",
            "AssignIpv6AddressOnCreation": false,
            "Ipv6CidrBlockAssociationSet": [],
            "Tags": [
                {
                    "Key": "Name",
                    "Value": "Public Subnet - ap-northeast-2a"
                }
            ],
            "SubnetArn": "arn:aws:ec2:ap-northeast-2:522373083963:subnet/subnet-04a9e32c2d120e3db"
        },
        {
            "AvailabilityZone": "ap-northeast-2a",
            "AvailabilityZoneId": "apne2-az1",
            "AvailableIpAddressCount": 251,
            "CidrBlock": "10.100.2.0/24",
            "DefaultForAz": false,
            "MapPublicIpOnLaunch": false,
            "State": "available",
            "SubnetId": "subnet-085a9890bad2c315f",
            "VpcId": "vpc-0b22c42f51fdb0dcc",
            "OwnerId": "522373083963",
            "AssignIpv6AddressOnCreation": false,
            "Ipv6CidrBlockAssociationSet": [],
            "Tags": [
                {
                    "Key": "Name",
                    "Value": "Private Subnet - ap-northeast-2a"
                }
            ],
            "SubnetArn": "arn:aws:ec2:ap-northeast-2:522373083963:subnet/subnet-085a9890bad2c315f"
        },
        {
            "AvailabilityZone": "ap-northeast-2b",
            "AvailabilityZoneId": "apne2-az2",
            "AvailableIpAddressCount": 251,
            "CidrBlock": "10.100.1.0/24",
            "DefaultForAz": false,
            "MapPublicIpOnLaunch": false,
            "State": "available",
            "SubnetId": "subnet-0c83fb7904b24fcd8",
            "VpcId": "vpc-0b22c42f51fdb0dcc",
            "OwnerId": "522373083963",
            "AssignIpv6AddressOnCreation": false,
            "Ipv6CidrBlockAssociationSet": [],
            "Tags": [
                {
                    "Key": "Name",
                    "Value": "Public Subnet - ap-northeast-2b"
                }
            ],
            "SubnetArn": "arn:aws:ec2:ap-northeast-2:522373083963:subnet/subnet-0c83fb7904b24fcd8"
        },
        {
            "AvailabilityZone": "ap-northeast-2b",
            "AvailabilityZoneId": "apne2-az2",
            "AvailableIpAddressCount": 251,
            "CidrBlock": "10.100.3.0/24",
            "DefaultForAz": false,
            "MapPublicIpOnLaunch": false,
            "State": "available",
            "SubnetId": "subnet-024e00d05b78a7b10",
            "VpcId": "vpc-0b22c42f51fdb0dcc",
            "OwnerId": "522373083963",
            "AssignIpv6AddressOnCreation": false,
            "Ipv6CidrBlockAssociationSet": [],
            "Tags": [
                {
                    "Key": "Name",
                    "Value": "Private Subnet - ap-northeast-2b"
                }
            ],
            "SubnetArn": "arn:aws:ec2:ap-northeast-2:522373083963:subnet/subnet-024e00d05b78a7b10"
        }
    ]
}
Public subnet과 Private subnet이 2개의 가용영역에 배치된 걸 확인 할 수 있다. NAT Gateway 정보를 확인해 보자.
#  aws ec2 describe-nat-gateways
{
    "NatGateways": [
        {
            "CreateTime": "2019-06-30T17:24:06.000Z",
            "NatGatewayAddresses": [
                {
                    "AllocationId": "eipalloc-02e98bc2373661a12",
                    "NetworkInterfaceId": "eni-010eae84a6d2d6b10",
                    "PrivateIp": "10.100.0.75",
                    "PublicIp": "13.125.104.150"
                }
            ],
            "NatGatewayId": "nat-086cb496e5638f1ab",
            "State": "available",
            "SubnetId": "subnet-04a9e32c2d120e3db",
            "VpcId": "vpc-0b22c42f51fdb0dcc",
            "Tags": []
        }
    ]
}
Public subnet에 전개된 것을 확인 할 수 있다.
aws ec2 describe-route-tables --filters "Name=vpc-id,Values=vpc-0b22c42f51fdb0dcc"
{
    "RouteTables": [
        {
            "Associations": [
                {
                    "Main": false,
                    "RouteTableAssociationId": "rtbassoc-0d88c73aacd13c265",
                    "RouteTableId": "rtb-06af924fba0573df9",
                    "SubnetId": "subnet-085a9890bad2c315f"
                },
                {
                    "Main": false,
                    "RouteTableAssociationId": "rtbassoc-041db9aaf61c917e4",
                    "RouteTableId": "rtb-06af924fba0573df9",
                    "SubnetId": "subnet-024e00d05b78a7b10"
                }
            ],
            "PropagatingVgws": [],
            "RouteTableId": "rtb-06af924fba0573df9",
            "Routes": [
                {
                    "DestinationCidrBlock": "10.100.0.0/16",
                    "GatewayId": "local",
                    "Origin": "CreateRouteTable",
                    "State": "active"
                },
                {
                    "DestinationCidrBlock": "0.0.0.0/0",
                    "NatGatewayId": "nat-086cb496e5638f1ab",
                    "Origin": "CreateRoute",
                    "State": "active"
                }
            ],
            "Tags": [
                {
                    "Key": "Name",
                    "Value": "Private Subnet Route Table"
                }
            ],
            "VpcId": "vpc-0b22c42f51fdb0dcc"
        },
        {
            "Associations": [
                {
                    "Main": false,
                    "RouteTableAssociationId": "rtbassoc-04b91f19661b8a7bf",
                    "RouteTableId": "rtb-0aec7d07ce3ea6efe",
                    "SubnetId": "subnet-04a9e32c2d120e3db"
                },
                {
                    "Main": false,
                    "RouteTableAssociationId": "rtbassoc-0ab3664a359182289",
                    "RouteTableId": "rtb-0aec7d07ce3ea6efe",
                    "SubnetId": "subnet-0c83fb7904b24fcd8"
                }
            ],
            "PropagatingVgws": [],
            "RouteTableId": "rtb-0aec7d07ce3ea6efe",
            "Routes": [
                {
                    "DestinationCidrBlock": "10.100.0.0/16",
                    "GatewayId": "local",
                    "Origin": "CreateRouteTable",
                    "State": "active"
                },
                {
                    "DestinationCidrBlock": "0.0.0.0/0",
                    "GatewayId": "igw-07a20b7d0c86d2850",
                    "Origin": "CreateRoute",
                    "State": "active"
                }
            ],
            "Tags": [
                {
                    "Key": "Name",
                    "Value": "Public Subnet Route Table"
                }
            ],
            "VpcId": "vpc-0b22c42f51fdb0dcc"
        }
}
Public Subnet Route Table를 보자.
  • Associations에 2개의 public subnet이 설정된 걸 확인 할 수 있다.
  • 0.0.0.0/0은 Internet gateway로 향하고 있다.
Private Subnet Route Table역시 최초에 설계한 모습 그대로 설정된 걸 알 수 있다.

정리

지금까지의 내용을 정리한다.
  • count 메타 데이터 매개변수로 루프를 수행 할 수 있다.
  • HCL에서 제공하는 함수들을 이용해서, 변수와 AWS 리소스로 작업 할 수 있다.
  • Terraform console로 함수들을 미리 테스트 할 수 있다.
  • 완전한 웹 애플리케이션를 terraform으로 만들어보자. 아래의 것들을 포함해야 할 것이다.
    • ELB
    • Target Group
    • RDS
    • Route 53
    • CloudFront