Terraform Module

실제 운영환경은 dev, stg, prd로 구분되어 있으나 VPC 운영 환경은 동일하게 구성되어 있는 경우가 많은데, 이처럼 동일 환경을 구성하기위해 테라폼을 이용하는 경우 'Terraform 사용 가이드 [1]'을 참고하여 작성하게 되면 모든 tf파일을 복사, 수정해야하는 번거로움이 생긴다. 수정한다고 하더라도 내용 편집에 실수하게 되면 구성이 안되거나 환경이 달라질 수 있다. 이러한 불편함을 해결하기 위해 Module이라는 기능이 있다.

 

 모듈 구성 및 내용 설명

위 구성에 대해 설명하자면 modules 디렉토리에는 리소스를 정의하여 변수처리만 하였고 dev,prd 에는 실제 변수에 대한 정보를 입력한 후 modules 디렉토리를 바라보게 하였다.

이처럼 구성하게 되면 앞서 설명한 내용처럼 구성은 동일(modules)하며 운영환경(dev,prd)만 다르게 하여 생성할 수 있게 된다.

 

 Modules Dir


    1. main.tf

모듈이 사용할 리소스를 정의한다.

VPC 구성에 있어 필요한 정보를 변수로 지정하여 생성한다.

# VPC

resource "aws_vpc" "Terra" {

  cidr_block           = "${var.cidr}"

 

  tags = "${merge(var.tags, map("Name", format("%s", var.name)))}"

}

* 외부에서 입력받는 변수값의 경우 “${var.xxxx}”로 가져온다.

* merge는 2개 이상의 map을 병합한다. 여기서는 data에서 default로 선언한 tags 맵에 리소스마다 새로운 포맷으로 Name tag 를 추가하기 위해서 사용한다.


# internet gateway

resource "aws_internet_gateway" "Terra" {

  vpc_id = "${aws_vpc.Terra.id}"

 

  tags = "${merge(var.tags, map("Name", format("%s-IGW", var.name)))}"

}

 

# default network ACL

resource "aws_default_network_acl" "dev_default" {

  default_network_acl_id = "${aws_vpc.Terra.default_network_acl_id}"

 

  ingress {

    protocol   = -1

    rule_no    = 100

    action     = "allow"

    cidr_block = "0.0.0.0/0"

    from_port  = 0

    to_port    = 0

  }

 

  egress {

    protocol   = -1

    rule_no    = 100

    action     = "allow"

    cidr_block = "0.0.0.0/0"

    from_port  = 0

    to_port    = 0

  }

 

  subnet_ids = flatten([

     "${aws_subnet.public.*.id}",

     "${aws_subnet.private.*.id}",

    "${aws_subnet.database.*.id}",

  ])

 

  tags = "${merge(var.tags, map("Name", format("%s-default", var.name)))}"

}

* 여러개의 subnet을 생성할 경우 [${aws_subnet.public.0.id}, ${aws_subnet.public.1.id}] 로 나열하는 대신 ${aws_subnet.public.*.id}와 같이 축약해서 표현할 수 있다.


# default security group

resource "aws_default_security_group" "dev_default" {

  vpc_id = "${aws_vpc.Terra.id}"

 

  ingress {

    protocol  = -1

    self      = true

    from_port = 0

    to_port   = 0

  }

 

  egress {

    from_port   = 0

    to_port     = 0

    protocol    = "-1"

    cidr_blocks = ["0.0.0.0/0"]

  }

 

  tags = "${merge(var.tags, map("Name", format("%s-default", var.name)))}"

}

 

# public subnet

resource "aws_subnet" "public" {

  count = "${length(var.public_subnets)}"

 

  vpc_id            = "${aws_vpc.Terra.id}"

  cidr_block        = "${var.public_subnets[count.index]}"

  availability_zone = "${var.region}${var.az[count.index]}"

 

  tags = "${merge(var.tags, map("Name", format("%s-public-%s", var.name, var.az[count.index])))}"

}

* count 키워드를 사용한다. count 키워드는 해당 resource를 count에 입력한 수만큼 반복해서 생성한다.

 count를 사용해 생성한 리소스는 앞에서 설명한 것과 같이 zero-based index나, *를 사용해서 접근할 수 있다. ex) ${aws_subnet.public.0.id} or ${aws_subnet.public.*.id}

* count.index에는 0 부터 1씩 index가 증가한 값이 입력된다.

 

# private subnet

resource "aws_subnet" "private" {

  count = "${length(var.private_subnets)}"

 

  vpc_id            = "${aws_vpc.Terra.id}"

  cidr_block        = "${var.private_subnets[count.index]}"

  availability_zone = "${var.region}${var.az[count.index]}"

 

  tags = "${merge(var.tags, map("Name", format("%s-private-%s", var.name, var.az[count.index])))}"

}

 

# private database subnet

resource "aws_subnet" "database" {

  count = "${length(var.database_subnets)}"

 

  vpc_id            = "${aws_vpc.Terra.id}"

  cidr_block        = "${var.database_subnets[count.index]}"

  availability_zone = "${var.region}${var.az[count.index]}"

 

  tags = "${merge(var.tags, map("Name", format("%s-private-db-%s", var.name, var.az[count.index])))}"

}

 

resource "aws_db_subnet_group" "database" {

  count = "${length(var.database_subnets) > 0 ? 1 : 0}"

 

  name        = "${var.name}"

  description = "Database subnet group for ${var.name}"

  subnet_ids  = "${aws_subnet.database.*.id}"

 

  tags = "${merge(var.tags, map("Name", format("%s", var.name)))}"

}

 

# EIP for NAT gateway

resource "aws_eip" "nat" {

  count = "${length(var.az)}"

 

  vpc = true

}

 

# NAT gateway

resource "aws_nat_gateway" "Terra" {

  count = "${length(var.az)}"

 

  allocation_id = "${aws_eip.nat.*.id[count.index]}"

  subnet_id     = "${aws_subnet.public.*.id[count.index]}"

}

 

# public route table

resource "aws_route_table" "public" {

  vpc_id = "${aws_vpc.Terra.id}"

 

  route {

    cidr_block = "0.0.0.0/0"

    gateway_id = "${aws_internet_gateway.Terra.id}"

  }

 

  tags = "${merge(var.tags, map("Name", format("%s-public", var.name)))}"

}

 

# private route table

resource "aws_route_table" "private" {

  count = "${length(var.az)}"

 

  vpc_id = "${aws_vpc.Terra.id}"

 

  route {

    cidr_block     = "0.0.0.0/0"

    nat_gateway_id = "${aws_nat_gateway.Terra.*.id[count.index]}"

  }

 

  tags = "${merge(var.tags, map("Name", format("%s-private-%s", var.name, var.az[count.index])))}"

}

 

# route table association

resource "aws_route_table_association" "public" {

  count = "${length(var.public_subnets)}"

 

  subnet_id      = "${aws_subnet.public.*.id[count.index]}"

  route_table_id = "${aws_route_table.public.id}"

}

 

resource "aws_route_table_association" "private" {

  count = "${length(var.private_subnets)}"

 

  subnet_id      = "${aws_subnet.private.*.id[count.index]}"

  route_table_id = "${aws_route_table.private.*.id[count.index]}"

}

 

resource "aws_route_table_association" "database" {

  count = "${length(var.database_subnets)}"

 

  subnet_id      = "${aws_subnet.database.*.id[count.index]}"

  route_table_id = "${aws_route_table.private.*.id[count.index]}"

}

 

    2. variable.tf

        모듈이 사용할 때 입력받는 변수의 타입을 정의한다.

variable "name" {

  description = "Resource Name Prefix"

  type        = "string"

}

variable "cidr" {

  description = "VPC CIDR block"

  type        = "string"

}

variable "region" {

  description = "region"

  type        = "string"

}

variable "public_subnets" {

  description = "Public Subnet IP List"

  type        = "list"

}

variable "private_subnets" {

  description = "Private Subnet IP List"

  type        = "list"

}

variable "database_subnets" {

  description = "Database Subnet IP List"

  type        = "list"

}

variable "az" {

  description = "Availability Zones List"

  type        = "list"

}

variable "tags" {

  description = "Tag Map"

  type        = "map"

}

 

    3. outputs.tf

        output 변수를 정의한다.

# VPC

output "vpc_id" {

  description = "VPC ID"

  value       = "${aws_vpc.Terra.id}"

}

 

output "vpc_cidr_block" {

  description = "VPC CIDR block"

  value       = "${aws_vpc.Terra.cidr_block}"

}

 

output "default_security_group_id" {

  description = "VPC default Security Group ID"

  value       = "${aws_vpc.Terra.default_security_group_id}"

}

 

output "default_network_acl_id" {

  description = "VPC default network ACL ID"

  value       = "${aws_vpc.Terra.default_network_acl_id}"

}

 

# internet gateway

output "igw_id" {

  description = "Interget Gateway ID"

  value       = "${aws_internet_gateway.Terra.id}"

}

 

# subnets

output "private_subnets_ids" {

  description = "Private Subnet ID LIST"

  value       = ["${aws_subnet.private.*.id}"]

}

 

output "public_subnets_ids" {

  description = "Public Subnet ID List"

  value       = ["${aws_subnet.public.*.id}"]

}

 

output "database_subnets_ids" {

  description = "Database Subnet ID List"

  value       = ["${aws_subnet.database.*.id}"]

}

 

output "database_subnet_group_ids" {

  description = "Database Subnet Group ID List"

  value       = "${aws_db_subnet_group.database.*.id}"

}

 

# route tables

output "public_route_table_ids" {

  description = "Public Route Table ID List"

  value       = ["${aws_route_table.public.*.id}"]

}

 

output "private_route_table_ids" {

  description = "Private Route Table ID List"

  value       = ["${aws_route_table.private.*.id}"]

}

 

# NAT gateway

output "nat_ids" {

  description = "NAT Gateway EIP ID List"

  value       = ["${aws_eip.nat.*.id}"]

}

 

output "nat_public_ips" {

  description = "NAT Gateway EIP List"

  value       = ["${aws_eip.nat.*.public_ip}"]

}

 

output "natgw_ids" {

  description = "NAT Gateway ID List"

  value       = ["${aws_nat_gateway.Terra.*.id}"]

}

 

 Data(Dev,Prd) Dir


    1. Provider.tf

        프로바이더 셋업을 위한 aws 프로바이더를 생성한다.

provider "aws" {

  access_key = "AKIATNOLZTHQMIT5WZAH"

  secret_key = "Ht6EJVoinHiY6B9VpSZaLyGcnnEIU10/f5qsAGxv"

  region     = "ap-northeast-2"

}


    2. maker.tf

        실제 구성할 변수값을 입력한다.

module "vpc" {

  source = "../modules"

 

  name = "terra"

  cidr = "100.100.0.0/16"

  region = "ap-northeast-2"

 

  az               = ["a", "c"]

  public_subnets   = ["100.100.11.0/24", "100.100.12.0/24"]

  private_subnets  = ["100.100.21.0/24", "100.100.22.0/24"]

  database_subnets = ["100.100.201.0/24", "100.100.202.0/24"]

  

  tags = {

    "TerraformManaged" = "true"

  }

}



 웹 콘솔 내 구성 화면


    1. VPC

   


2. Subnet

   


3. Routing Table

   

    - terra-public > igw 연결 (외부)

        연결 서브넷 : terra-public-a, terra-public-c

    - terra-private-a > nat (내부)

        연결 서브넷 : terra-private-a, terra-private-db-a

    - terra-private-c > nat (내부)

        연결 서브넷 : terra-private-c, terra-private-db-c


4. IGW

   


5. EIP

   


6. NAT

   


7. Network ACL

   

   

   


8. 보안그룹

   


이처럼 VPC 환경은 한번 모듈로 구성해놓으면 손쉽게 생성&삭제가 가능하다.

이후 stg환경을 구성한다고 하면 Data Dir만 신규 생성&편집하여 Apply하면 된다.