VPC
VPC 생성
처음으로 VPC를 구성합니다. 자신의 환경에 맞게 cidr_block 값을 변경해주세요.
# main.tf
resource "aws_vpc" "DEV-VPC" {
assign_generated_ipv6_cidr_block = false
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
instance_tenancy = "default"
tags = {
Name = "DEV-VPC"
Service = "DEV"
}
}
Subnet
Terraform Function – Count
일반적으로 한 개의 서브넷을 만들기 위하여 한 개의 리소스 블록을 생성합니다.
# main.tf
resource "aws_subnet" "DEV-Subnet" {
vpc_id = aws_vpc.DEV-VPC.id
cidr_block = "10.0.0.0/24"
tags = {
Name = "DEV-Subnet"
Service = "DEV"
}
}
두 개를 만들어야 한다면 리소스 블록을 두 개 만들면 됩니다.
# main.tf
resource "aws_subnet" "DEV-Subnet1" {
vpc_id = aws_vpc.DEV-VPC.id
cidr_block = "10.0.0.0/24"
tags = {
Name = "DEV-Subnet"
Service = "DEV"
}
}
resource "aws_subnet" "DEV-Subnet2" {
vpc_id = aws_vpc.DEV-VPC.id
cidr_block = "10.0.1.0/24"
tags = {
Name = "DEV-Subnet"
Service = "DEV"
}
}
추가로 서브넷을 더 생성할 때는, 리소스 블록을 복사합니다. 하지만
cidr_block = "10.0.0.0/24" #DEV-Subnet1
cidr_block = "10.0.1.0/24" #DEV-Subnet2
이때 Terraform에서 제공하는
count = 2
반복하고 싶은 숫자를 입력하면, 해당 블록은 해당 숫자만큼 반복하여 실행합니다.
Python Function – Range
Terraform에서
# count.py
count = range(2)
print(list(count)) # -> [0, 1]
반복문에서도 range로 생성한 배열을 사용할 수 있습니다.
for index in count:
print(f"이번 순서: {index}, CIDR Block: 10.0.{index}.0/24")
현재 값은 index로 확인합니다.
이번 순서: 0, CIDR Block: 10.0.0.0/24
이번 순서: 1, CIDR Block: 10.0.1.0/24
Subnet 생성
DEV-VPC 내부에 서브넷을 생성합니다.
count.index로 현재 값을 가져와 cidr_block을 동적으로 할당합니다.
# main.tf
resource "aws_subnet" "DEV-Subnet" {
count = 2
vpc_id = aws_vpc.DEV-VPC.id
cidr_block = "10.0.${count.index}.0/24"
tags = {
Name = "DEV-Subnet"
Service = "DEV"
}
}
동적으로 생성한 서브넷은 아래 2개 입니다.
[10.0.0.0/24, 10.0.1.0/24]
보안그룹
보안그룹 생성
DEV-VPC에 속하는 보안그룹을 생성합니다. Port는 80, 443을 열어줍니다.
# main.tf
resource "aws_security_group" "DEV-FrontEnd-ALB" {
name = "DEV-FrontEnd-ALB-SG"
vpc_id = aws_vpc.DEV-VPC.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
prefix_list_ids = []
}
tags = {
Name = "DEV-FrontEnd-ALB-SG"
Service = "DEV"
}
}
ALB
Splat Expression
DEV-Subnet를 Count로 2개 생성합니다. 생성한 결과로 Terraform은 아래와 같이 2개의 Subnet을 관리합니다.
DEV-Subnet = [
{"id": "subnet-096", ...},
{"id": "subnet-abd", ...}
]
Load Balancer를 생성할 때, DEV-Subnet에 속한 2개의 Subnet을 모두 추가하고 싶습니다.
이럴 때,
subnets = aws_subnet.DEV-Subnet[*].id
아래는
subnets = [subnet-096..., subnet-abd2...]
ALB 구성
보안그룹과 서브넷을 참조하여 로드밸런서를 생성합니다.
# main.tf
resource "aws_lb" "DEV-Front-ALB" {
idle_timeout = 60
internal = false
name = "DEV-Front-ALB"
security_groups = [aws_security_group.DEV-FrontEnd-ALB.id]
subnets = aws_subnet.DEV-Subnet[*].id
tags = {
Name = "DEV-Front-ALB"
Service = "DEV"
}
}
변수 생성 – 대상
variables.tf 파일에 타겟의 이름과 포트, 경로를 작성합니다.
이렇게 하면, 타겟이 변경될 때 variables.tf 파일만 수정하여 대응할 수 있습니다.
# variables.tf
variable "targets" {
default = [
{
name = "user1"
port = 1010
path = "/user"
},
name = "user2"
port = 2020
path = "/v2/user"
}
]
}
변수 생성 – 유지보수
EC2의 유형을 변경하거나, ALB Target Group에서 제외하는 경우 이를 Terraform으로 제어할 수 있습니다.
# variables.tf
variable "maintenance-mode" {
default = {
user = {
ec2 = 0
alb = 0
},
web = {
ec2 = 0
alb = 0
}
(...)
}
}
0은 일반적인 상황에 적용하고, 유지보수 모드로 들어갈 경우 1을 사용합니다.
유지보수할 서버를 선택하여 main.tf의 리소스에서 제외할 수 있습니다.
# variables.tf
variable "maintenance-instance" {
default = 0
}
서버가 4대일 때, 두번째 서버 유지보수는
Target group ( + for_each)
variables.tf에서 정의한 변수 targets를 대상으로 target group을 생성합니다.
이 때 리소스를 동적으로 생성할 수 있는 for_each 표현을 활용합니다.
for_each = { for target in var.targets : target.name => target }
Terraform언어의
배열 속 요소를 변수
const array1 = ['a', 'b', 'c'];
array1.forEach(element => console.log(element));
// expected output: "a"
// expected output: "b"
// expected output: "c"
가장 중요한 것은 배열 내 변수를 순서대로 불러와
Terraform에서 사용하는 문법과 Javascript에서 사용하는 문법을 비교해봅니다.
반복할 배열(Array)을 지정합니다.
# Terraform
for target in var.targets
// Javascript
array1
배열에 포함된 요소로 수행할 내용을 정의합니다.
# Terraform
target.name => target
// Javascript
element => console.log(element)
for_each 변수는 반복하여
# main.tf
resource "aws_lb_target_group" "ALB-Target-User" {
for_each = { for target in var.targets : target.name => target }
vpc_id = aws_vpc.PROD-VPC.id
name = "Front-ALB-${each.value.name}-TG"
port = each.value.port
protocol = "HTTP"
tags = {
Name = "Front-ALB-${each.value.name}-Target-Group"
Path = each.value.path
}
}
Target Group Attatchment ( + 조건문, Element, Floor)
Target Group Attachment는 유지보수 유무에 따라 리소스 생성 개수를 동적으로 변화합니다.
이 때 필요한 문법이 바로 조건문입니다.
count = condition ? A : B
count 변수는 조건이 참인 경우 A를 할당받고 거짓인 경우는 B를 할당받습니다.
USER EC2 한 대를 ALB에서 제외하려는 상황이라고 가정해보죠.
EC2의 유형은 변경하지 않고 ALB만 제외하기 때문에 alb 값을 0에서 1로 변경합니다.
# variables.tf
variable "maintenance-mode" {
default = {
user = {
ec2 = 0
alb = 1
},
(...)
}
}
만약 아래와 같은 조건이 있는 경우라면, user의 alb값을 1로 바꿨기 때문에 조건을 만족하게 됩니다.
var.maintenance-mode["user"]["alb"] == 1
조건을 만족할 때, Target Group Attatchment는
length(aws_lb_target_group.ALB-Target-User) * (length(aws_instance.user) - 1)
1을 제외하는 이유는 유지보수를 위하여 한 대를 제외하였기 때문입니다.
만약 조건을 만족하지 않은 경우라면 어떨까요?
그 때는
length(aws_lb_target_group.ALB-Target-User) * length(aws_instance.user)
생성할 리소스의 개수는 정했습니다.
다음으로 필요한 값은 Target Group Arn입니다.
먼저 Arn을 동적으로 추가하기 위하여 사용할
element(["a", "b", "c"], 1)
결과는 배열 내 두번째에 위치하고 있는 문자열
floor(4.9)
소수점 첫째 자리인 9를 버려, 결과는 숫자 4를 반환합니다.
유지보수가 진행된다면, arn는 아래의 값을 할당받습니다.
aws_lb_target_group.ALB-Target-User[element(var.targets, floor(count.index / (length(aws_instance.user) - 1))).name].arn
Target Group Arn은 Floor의 값이 변화할 때 Arn의 값이 변합니다. 즉 A A A / B B B / C C C 순서로 할당합니다.
Target Id를 동적으로 할당할 때 사용하는 연산자는
10 % 8 # 2
4 % 4 # 0
3 % 4 # 3
2 % 4 # 2
결과는 주석으로 나타냈으니 참고하세요.
유지보수 모드인 경우 Target Id는 아래의 값을 할당받습니다.
aws_instance.user[[for number in range(length(aws_instance.user)) : number if number != var.maintenance-instance][count.index % (length(aws_instance.user) - 1)]].id
유지보수 중인 인스턴스를 제외한 나머지 인스턴스를 추가합니다. Id는 A B C / A B C / A B C 순서로 할당합니다.
모든 코드를 정리하면 아래와 같이 표현할 수 있습니다.
resource "aws_lb_target_group_attachment" "ALB-Target-Attach-User" {
count = (
var.maintenance-mode["user"]["alb"] == 1 ?
length(aws_lb_target_group.ALB-Target-User) * (length(aws_instance.user) - 1) :
length(aws_lb_target_group.ALB-Target-User) * length(aws_instance.user)
)
target_group_arn = (
var.maintenance-mode["user"]["alb"] == 1 ?
aws_lb_target_group.ALB-Target-User[element(var.targets, floor(count.index / (length(aws_instance.user) - 1))).name].arn :
aws_lb_target_group.ALB-Target-User[element(var.targets, floor(count.index / length(aws_instance.user))).name].arn
)
target_id = (
var.maintenance-mode["user"]["alb"] == 1 ?
aws_instance.user[[for number in range(length(aws_instance.user)) : number if number != var.maintenance-instance][count.index % (length(aws_instance.user) - 1)]].id :
aws_instance.user[count.index % length(aws_instance.user)].id
)
}
Listener rule
경로 기반으로 작성할 경우 실제 경로 뒤에 “/*” 을 추가합니다.
resource "aws_lb_listener_rule" "https-user" {
count = length(aws_lb_target_group.ALB-Target-User)
listener_arn = aws_lb_listener.https.arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.ALB-Target-User[element(var.targets, count.index).name].arn
}
condition {
path_pattern {
values = ["${aws_lb_target_group.ALB-Target-User[element(var.targets, count.index).name].tags["Path"]}/*"]
}
}
}
이 설명은 테라폼과는 관련없지만 생성 시 알게된 부분이라 추가하였습니다.
마지막으로,
끝까지 읽어주신 모든 분들께 감사드립니다.
다음 글 보기
이전 글 보기