◎ 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 = "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"
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하면 된다.