Cloud/IaC

[Terraform] 간단한 3-tier 아키텍쳐 - VPC

Omoknooni 2024. 2. 17. 19:30

본격적으로 조금 더 복잡한 내용으로 들어가보도록한다.

많이 사용되는 WEB, WAS, DB 구조인 3-tier 아키텍쳐를 Terraform Code로 구성해보도록한다.

 

아키텍쳐 구성도

 

 

구성할 요소가 상당히 많은 것을 볼 수 있다.

이번 글에서는 네트워킹 요소(VPC)부터 구성하고, 그 다음으로 Application(WEB, WAS, DB)를 구축해보도록 한다.

 

Terraform 코드 작성

전체적인 프로젝트는 module화 시켜 최대한 가독성있게 제작했다.

(모듈화를 시킴으로써 각 모듈에서 필요한 요소들을 가져오고 내보내는 과정을 익힐 수 있었다. [output / variables])

├── main.tf
├── module
│   ├── application			# EC2, ALB 등의 application 요소
│   │   ├── main.tf
│   │   ├── output.tf
│   │   └── variables.tf
│   ├── db				# RDS 요소
│   │   ├── main.tf
│   │   ├── output.tf
│   │   └── variables.tf
│   └── vpc				# 네트워킹 요소
│       ├── main.tf
│       ├── output.tf
│       └── variables.tf
├── output.tf
├── provider.tf
└── variables.tf

 

 

네트워킹 요소에서 생성할 리소스 목록은 대략 아래와 같다.

  • VPC
  • 서브넷 8개 (퍼블릭 2개, 프라이빗 6개)
  • 라우팅 테이블 2개 (퍼블릭, 프라이빗)
  • 인터넷 게이트웨이
  • NAT 게이트웨이
  • EIP (NAT 게이트웨이에 연결)

 

먼저 VPC와 서브넷을 구성해본다.

VPC와 서브넷의 CIDR 범위는 variable로 입력을 받도록 구성한다.

추가로 서브넷이 생성될 AZ도 일단은 variable로 처리를 해주었다.

## module/vpc/variables.tf
variable "vpc_cidr" {
    default = "192.168.0.0/16"
}

variable "subnet_cidr" {
    default = "192.168.x.0/24"
}

variable "availability_zone_list" {
    type = list(string)
    default = [ "ap-northeast-2a", "ap-northeast-2c" ]
}

 

 

 

 

다음으로 이 variable들을 바탕으로 VPC와 서브넷을 구성한다.

여기서 서브넷을 통 8개를 생성해야하는데, 하나씩 생성하게되면 코드가 불필요하게 길어지게 된다.

이 부분을 완화해보기 위해 count를 사용해보았다.

## module/vpc/main.tf
resource "aws_vpc" "simple-3tier-vpc" {
    cidr_block = var.vpc_cidr
    enable_dns_hostnames = true
    enable_dns_support = true
}

# subnet
resource "aws_subnet" "simple-3tier-public-subnet" {
    count = 2
    vpc_id = aws_vpc.simple-3tier-vpc.id
    cidr_block = replace(var.subnet_cidr, "x", 1 + count.index) # 192.168.1.0/24, 192.168.2.0/24
    availability_zone = element(var.availability_zone_list, count.index)
}

resource "aws_subnet" "simple-3tier-application-subnet" {
    count = 4
    vpc_id = aws_vpc.simple-3tier-vpc.id
    cidr_block = replace(var.subnet_cidr, "x", 3 + count.index) # 192.168.3.0/24, 192.168.4.0/24, 192.168.5.0/24, 192.168.6.0/24
    availability_zone = element(var.availability_zone_list, count.index)
}

resource "aws_subnet" "simple-3tier-db-subnet" {
    count = 2
    vpc_id = aws_vpc.simple-3tier-vpc.id
    cidr_block = replace(var.subnet_cidr, "x", 7 + count.index) # 192.168.7.0/24, 192.168.8.0/24
    availability_zone = element(var.availability_zone_list, count.index)
}

 

 

CIDR 범위를 x가 포함된 문자열로 받아 서브넷을 생성하는 부분에서 이 x를 count.index 값으로 치환시켜 3종류의 서브넷을 각각 생성할 수 있었다.

물론 모든 CIDR에 대응할 수 있는 완벽한 방법은 아니나, Simple하게 구성하는 과정이므로 이렇게 적용을 시켜보았다.

 

그리고 서브넷의 AZ도 2종류(ap-northeast-2a, ap-northeast-2c)로 나누어서 생성을 했는데, 여기서는 element를 사용했다.

element는 리스트로부터 하나의 원소를 추출하는 함수로, 리스트와 추출하고자 하는 인덱스 값으로 해당 인덱스의 값을 추출할 수 있다.

 

각각의 공식 Docs는 아래에 첨부

 

The count Meta-Argument - Configuration Language | Terraform | HashiCorp Developer

Count helps you efficiently manage nearly identical infrastructure resources without writing a separate block for each one.

developer.hashicorp.com

 

 

element - Functions - Configuration Language | Terraform | HashiCorp Developer

The element function retrieves a single element from a list.

developer.hashicorp.com

 

 

다음으로 NAT 게이트웨이와 IGW를 생성해보도록 한다.

## module/vpc/main.tf
# IGW
resource "aws_internet_gateway" "simple-3tier-igw" {
    vpc_id = aws_vpc.simple-3tier-vpc.id
}


# NAT GW - EIP
resource "aws_eip" "simple-3tier-natgw-eip" {
    domain = "vpc"
}

# NAT GW - natgw
resource "aws_nat_gateway" "simple-3tier-natgw" {
    allocation_id = aws_eip.simple-3tier-natgw-eip.id
    subnet_id = aws_subnet.simple-3tier-public-subnet.*.id[1]
}

 

NAT 게이트웨이와 연결할 EIP를 같이 생성하고 연결해주었다. 여기서 NAT 게이트웨이는 퍼블릭 서브넷에 생성해주어야한다.

 

 

이제 생성한 NAT 게이트웨이와 인터넷 게이트웨이를 라우팅 테이블에 연결하고, 라우팅 테이블을 각각 해당하는 서브넷에 연결해주도록 한다.

# Route Table
resource "aws_route_table" "simple-3tier-pub-rt" {
    vpc_id = aws_vpc.simple-3tier-vpc.id

    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = aws_internet_gateway.simple-3tier-igw.id
    }
}

resource "aws_route_table" "simple-3tier-priv-rt" {
    vpc_id = aws_vpc.simple-3tier-vpc.id

    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = aws_nat_gateway.simple-3tier-natgw.id
    }
}

# Route table Associate
resource "aws_route_table_association" "simple-3tier-pub-asso" {
    count = 2
    subnet_id = aws_subnet.simple-3tier-public-subnet.*.id[count.index]
    route_table_id = aws_route_table.simple-3tier-pub-rt.id
}

resource "aws_route_table_association" "simple-3tier-application-asso" {
    count = 4
    subnet_id = aws_subnet.simple-3tier-application-subnet.*.id[count.index]
    route_table_id = aws_route_table.simple-3tier-priv-rt.id
}

resource "aws_route_table_association" "simple-3tier-db-asso" {
    count = 2
    subnet_id = aws_subnet.simple-3tier-db-subnet.*.id[count.index]
    route_table_id = aws_route_table.simple-3tier-priv-rt.id
}

 

여기서도 count를 사용해서 반복되는 코드를 줄일 수 있다.

퍼블릭 서브넷에 사용할 라우팅 테이블에는 인터넷 게이트웨이를 바라보도록 연결하고, 프라이빗 서브넷에 사용할 라우팅 테이블에는 NAT 게이트웨이를 바라보도록 연결해주었다.

 

 

마지막으로 VPC 모듈에서 생성한 리소스 중 다른 모듈에서 사용하기 위해 output으로 처리하도록 한다.

## module/vpc/output.tf
output "vpc_id" {
    value = aws_vpc.simple-3tier-vpc.id
}

output "public_subnet_id" {
    value = aws_subnet.simple-3tier-public-subnet.*.id
}

output "application_subnet_id" {
    value = aws_subnet.simple-3tier-application-subnet.*.id
}

output "db_subnet_id" {
    value = aws_subnet.simple-3tier-db-subnet.*.id
}

 

 

이렇게 3tier 프로젝트의 네트워킹 요소만 구성해보았다.

해당 프로젝트는 깃헙 레포에서 확인할 수 있다.