# 학습 목표
Terraform을 이용한 AWS 아키텍처 구성
# 해결 과제
다음의 아키텍처를 terraform을 이용해 작성합니다.

STEP 1: 자습서: DB 인스턴스에 사용할 Amazon VPC 생성
- VPC 및 서브넷 생성
- VPC 보안 그룹 생성
- DB 서브넷 그룹 생성
STEP 2: EC2 인스턴스 생성
만들어야 하는 사양은 다음과 같습니다.
- AMI: Ubuntu Server 18
- 인스턴스 타입: t2.micro
- 사용자 데이터
- 키 페어: 수동으로 만들고 EC2에 할당합니다.
Advanced Challenges
STEP 3: 자습서: DB 인스턴스 생성
- 자습서에 표시된 사양대로 RDS 인스턴스를 생성합니다.
STEP 4: 애플리케이션 로드 밸런서 및 Auto Scaling Group 적용
- Auto Scaling Group은 최소 2개, 최대 10개로 설정해 놓습니다.
# 실습 자료
# 과제 항목별 진행 상황
단일 파일로 구성한 예
### terraform block ###
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.35"
}
}
}
### provider block ###
provider "aws" {
region = "ap-northeast-2"
default_tags {
tags = {
name = "tf-sprint"
}
}
}
## aws_availability_zones 지정
## ap-northeast-2b,d 제외 t2.micro intancetype을 사용하기 위함
data "aws_availability_zones" "available" {
state = "available"
exclude_names = ["ap-northeast-2b", "ap-northeast-2d"]
}
## VPC 구성을 module로 정의
## module 을 구문에 삽입 후 terraform init 을 하게 되면
## 자동으로 module이 .terraform 폴더로 pull이 된다.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "4.0.1"
name = "tf-vpc"
cidr = "10.0.0.0/16"
azs = data.aws_availability_zones.available.names
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.3.0/24", "10.0.4.0/24"]
enable_dns_hostnames = true
enable_dns_support = true
enable_nat_gateway = true
}
## EC2 AMI 정의 ubuntu로 설정
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-20230424"]
}
}
## EC2 Instance 템플릿 정의
resource "aws_launch_template" "tf-ec2" {
name_prefix = "tf-sprint-ec2-"
image_id = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
user_data = "${base64encode(<<-EOF
#!/bin/bash
sudo apt update -y;sudo apt install -y mysql-client-core-5.7
echo '<h1>Terraform Apply Complete</h1>' > index.html
echo '<b>SHOW DATABASES</b><p style="color:red; white-space:pre;">' >> index.html
MYSQL_PWD="${aws_db_instance.tfRDS.password}" mysql -h '${aws_db_instance.tfRDS.address}' -u '${aws_db_instance.tfRDS.username}' -e 'show databases' >> index.html
echo '</p><h1>DB Access Complete</h1>' >> index.html
sudo nohup busybox httpd -f -p 80 &
EOF
)}"
key_name = "PCKEC2"
network_interfaces {
associate_public_ip_address = true
security_groups = [aws_security_group.tfEC2sg.id]
}
}
## Autoscaling Group 정의
resource "aws_autoscaling_group" "tfASG" {
name = "tfASG"
min_size = 2
max_size = 10
desired_capacity = 2
launch_template {
id = aws_launch_template.tf-ec2.id
}
vpc_zone_identifier = module.vpc.public_subnets
tag {
key = "Name"
value = "Terraform X AWS - ASG"
propagate_at_launch = true
#(필수)이 ASG를 통해 시작된 Amazon EC2 인스턴스에 태그를 전파
#EC2 Deploy 할때마다 해당 태그가 설정됨
}
}
## Application Load Balancer
resource "aws_lb" "tfALB" {
name = "tfALB"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.tfALBsg.id,]
subnets = module.vpc.public_subnets
}
## ALB Listener
resource "aws_lb_listener" "tfALBListener" {
load_balancer_arn = aws_lb.tfALB.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.tfALBtg.arn
}
}
## ALB target group
resource "aws_lb_target_group" "tfALBtg" {
name = "Terraform-ALB-TargetGroup"
port = 80
protocol = "HTTP"
vpc_id = module.vpc.vpc_id
}
## Register targetgroup and Include as pending below
resource "aws_autoscaling_attachment" "tfASGattach" {
autoscaling_group_name = aws_autoscaling_group.tfASG.id
alb_target_group_arn = aws_lb_target_group.tfALBtg.arn
}
### Security Group Create ###
resource "aws_security_group" "tfEC2sg" {
name = "Terraform-EC2-HTTP,SSH-Access"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.tfALBsg.id]
#ALB 보안그룹(tfALBsg)으로 들어온 트래픽만 허용
}
ingress {
from_port = 22
to_port = 22
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"]
}
vpc_id = module.vpc.vpc_id
}
# IP 프로토콜 번호 "-1"은 일반적으로 "모든 프로토콜" 또는 "전체"를 의미
# "-1"로 설정된 경우, IP 패킷은 모든 프로토콜을 지원하는 것을 의미
resource "aws_security_group" "tfALBsg" {
name = "Terraform-ALB-HTTP-Access"
ingress {
from_port = 80
to_port = 80
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"]
}
vpc_id = module.vpc.vpc_id
}
resource "aws_security_group" "tfRDSsg" {
name = "tf_RDS_sg"
vpc_id = module.vpc.vpc_id
ingress {
from_port = 3306
to_port = 3306
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"]
}
}
##DataBase Instance 생성 정의
resource "aws_db_instance" "tfRDS" {
identifier = "mydb01"
allocated_storage = "10"
engine = "mysql"
engine_version = "8.0.32"
instance_class = "db.t2.micro"
db_name = "terraformRDS"
username = "root"
password = "toor0515"
vpc_security_group_ids = [aws_security_group.tfRDSsg.id]
db_subnet_group_name = aws_db_subnet_group.tfRDSsubnets.id
skip_final_snapshot = true
}
resource "aws_db_subnet_group" "tfRDSsubnets" {
name = "terraform-rds-subnet-group"
subnet_ids = module.vpc.private_subnets
}
로드 밸런스 DNS 로 접속 결과 메인 파일 안에 쉘 스크립트를 삽입하여 EC2에서 RDS로 접속이 가능한지 자동으로 확인
# 위의 main.tf 코드 참고 아래는 정확한 변수명을 생략하였으며
# 스크립트 실행을 하면서 index.html 파일로 값이 어떻게 들어가는지 보여줌
#!/bin/bash
sudo apt update -y
sudo apt install -y mysql-client-core-5.7
echo '<h1>Terraform Apply Complete</h1>' > index.html
echo '<b>SHOW DATABASES</b><p style="color:red; white-space:pre;">' >> index.html
MYSQL_PWD="${[DB_password]}" mysql -h [RDS_Endpoint] -u '[username]' -e 'show databases' >> index.html
echo '</p><h1>DB Access Complete</h1>' >> index.html
sudo nohup busybox httpd -f -p 80 &
user_data = "${base64encode(<<-EOF
#!/bin/bash
sudo apt update -y;sudo apt install -y mysql-client-core-5.7
echo '<h1>Terraform Apply Complete</h1>' > index.html
echo '<b>SHOW DATABASES</b><p style="color:red; white-space:pre;">' >> index.html
MYSQL_PWD="${aws_db_instance.tfRDS.password}" mysql -h '${aws_db_instance.tfRDS.address}' -u '${aws_db_instance.tfRDS.username}' -e 'show databases' >> index.html
echo '</p><h1>DB Access Complete</h1>' >> index.html
sudo nohup busybox httpd -f -p 80 &
EOF
)}"
<--! index.html -->
<h1>Terraform Apply Complete</h1>
<b>SHOW DATABASES</b><p style="color:red; white-space:pre;">
<--! 쿼리 결과 -->
Database
information_schema
mysql
performance_schema
sys
terraformRDS
</p><h1>DB Access Complete</h1>

모듈 단위로 구성한 예
모듈 구조

1. vpc
main.tf
terraform {
# Location of vpc state file
backend "s3" {
bucket = "bighead-tfstate"
key = "VPC/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "ap-northeast-2"
}
vpc.tf
# Main VPC
resource "aws_vpc" "main_vpc" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "main_vpc"
}
}
subnet.tf
# Public Subnet - AZ(a)
resource "aws_subnet" "web_pub_subnet_a" {
vpc_id = aws_vpc.main_vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "WEB-Pub-subnet-a"
}
}
# Private Subnet - AZ(a)
resource "aws_subnet" "rds_pvt_subnet_a" {
vpc_id = aws_vpc.main_vpc.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "RDS-Pvt-subnet-a"
}
}
# Public Subnet - AZ(c)
resource "aws_subnet" "web_pub_subnet_c" {
vpc_id = aws_vpc.main_vpc.id
cidr_block = "10.0.3.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "WEB-Pub-subnet-c"
}
}
# Private Subnet - AZ(c)
resource "aws_subnet" "rds_pvt_subnet_c" {
vpc_id = aws_vpc.main_vpc.id
cidr_block = "10.0.4.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "RDS-Pvt-subnet-c"
}
}
# RDS Subnet Group
# Private Subnet - AZ(a,c)
resource "aws_db_subnet_group" "rds_subnet_group" {
name = "rds_subnet_group"
subnet_ids = [aws_subnet.rds_pvt_subnet_a.id, aws_subnet.rds_pvt_subnet_c.id]
tags = {
Name = "RDS-subnet-group"
}
}
route-table.tf
# Public Route Table - Internet Gateway
resource "aws_route_table" "pub_route_table" {
vpc_id = aws_vpc.main_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main_igw.id
}
tags = {
Name = "Pub-route-table"
}
}
# Private Route Table - NAT Gateway
resource "aws_route_table" "pvt_route_table" {
vpc_id = aws_vpc.main_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_nat_gateway.main_ngw.id
}
tags = {
Name = "Pvt-route-table"
}
}
# Public Route Table Association - Public Subnet - AZ(a)
resource "aws_route_table_association" "pub_route_table_association_a" {
subnet_id = aws_subnet.web_pub_subnet_a.id
route_table_id = aws_route_table.pub_route_table.id
}
# Public Route Table Association - Public Subnet - AZ(c)
resource "aws_route_table_association" "pub_route_table_association_c" {
subnet_id = aws_subnet.web_pub_subnet_c.id
route_table_id = aws_route_table.pub_route_table.id
}
# Private Route Table Association - Private Subnet - AZ(a)
resource "aws_route_table_association" "pvt_route_table_association_a" {
subnet_id = aws_subnet.rds_pvt_subnet_a.id
route_table_id = aws_route_table.pvt_route_table.id
}
# Private Route Table Association - Private Subnet - AZ(c)
resource "aws_route_table_association" "pvt_route_table_association_c" {
subnet_id = aws_subnet.rds_pvt_subnet_c.id
route_table_id = aws_route_table.pvt_route_table.id
}
gateway.tf
# Internet Gateway
resource "aws_internet_gateway" "main_igw" {
vpc_id = aws_vpc.main_vpc.id
tags = {
Name = "main-igw"
}
}
# NAT Gateway
resource "aws_nat_gateway" "main_ngw" {
allocation_id = aws_eip.ngw_eip.id
subnet_id = aws_subnet.web_pub_subnet_c.id
tags = {
Name = "main-ngw"
}
}
security-group.tf
# Web Server Security Group
resource "aws_security_group" "web_security_group" {
name = "web-security-group"
description = "Web Server Security Group"
vpc_id = aws_vpc.main_vpc.id
tags = {
Name = "web-security-group"
}
}
# RDB Server Security Group
resource "aws_security_group" "rds_security_group" {
name = "rds-security-group"
description = "RDB Server Security Group"
vpc_id = aws_vpc.main_vpc.id
tags = {
Name = "rds-security-group"
}
}
# ALB Security Group
resource "aws_security_group" "alb_security_group" {
name = "alb-security-group"
description = "ALB Security Group"
vpc_id = aws_vpc.main_vpc.id
tags = {
Name = "alb-security-group"
}
}
# Web Server Security Group Ingress
# 80 port
resource "aws_vpc_security_group_ingress_rule" "web_security_group_ingress_http" {
security_group_id = aws_security_group.web_security_group.id
cidr_ipv4 = "0.0.0.0/0"
from_port = 80
to_port = 80
ip_protocol = "tcp"
}
# Web Server Security Group Ingress
# 22 port
resource "aws_vpc_security_group_ingress_rule" "web_security_group_ingress_ssh" {
security_group_id = aws_security_group.web_security_group.id
cidr_ipv4 = "0.0.0.0/0"
from_port = 22
to_port = 22
ip_protocol = "tcp"
}
# Web Server Security Group Egress
# Any
resource "aws_vpc_security_group_egress_rule" "web_security_group_egress" {
security_group_id = aws_security_group.web_security_group.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = -1
}
# RDB Server Security Group Ingress
# 3306 port
resource "aws_vpc_security_group_ingress_rule" "rds_security_group_ingress" {
security_group_id = aws_security_group.rds_security_group.id
referenced_security_group_id = aws_security_group.web_security_group.id
from_port = 3306
to_port = 3306
ip_protocol = "tcp"
}
# RDB Server Security Group Egress
# Any
resource "aws_vpc_security_group_egress_rule" "rds_security_group_egress" {
security_group_id = aws_security_group.rds_security_group.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = -1
}
# ALB Security Group Ingress
# 80 port
resource "aws_vpc_security_group_ingress_rule" "alb_security_group_ingress_http" {
security_group_id = aws_security_group.alb_security_group.id
cidr_ipv4 = "0.0.0.0/0"
from_port = 80
to_port = 80
ip_protocol = "tcp"
}
# ALB Security Group Egress
# Any
resource "aws_vpc_security_group_egress_rule" "alb_security_group_egress" {
security_group_id = aws_security_group.alb_security_group.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = -1
}
eip.tf
# EIP of NAT Gateway
resource "aws_eip" "ngw_eip" {
vpc = true
}
outputs.tf
# Main VPC ID
output "vpc_id" {
description = "ID of the Main VPC"
value = aws_vpc.main_vpc.id
}
# Public Subnet ID - AZ(a)
output "subnet_pub_a_id" {
description = "ID of the WEB-Pub-subnet-a"
value = aws_subnet.web_pub_subnet_a.id
}
# Private Subnet ID - AZ(a)
output "subnet_pvt_a_id" {
description = "ID of the RDS-Pvt-subnet-a"
value = aws_subnet.rds_pvt_subnet_a.id
}
# Public Subnet ID - AZ(c)
output "subnet_pub_c_id" {
description = "ID of the WEB-Pub-subnet-c"
value = aws_subnet.web_pub_subnet_c.id
}
# Private Subnet ID - AZ(c)
output "subnet_pvt_c_id" {
description = "ID of the RDS-Pvt-subnet-c"
value = aws_subnet.rds_pvt_subnet_c.id
}
# RDS Subnet Group Name
output "subnet_group_rds_name" {
description = "Name of the RDS-subnet-group"
value = aws_db_subnet_group.rds_subnet_group.name
}
# Web Server Security Group ID
output "web_security_group_id" {
description = "ID of the Web Server Security Group"
value = aws_security_group.web_security_group.id
}
# RDB Server Security Group ID
output "rds_security_group_id" {
description = "ID of the RDB Server Security Group"
value = aws_security_group.rds_security_group.id
}
# ALB Security Group ID
output "alb_security_group_id" {
description = "ID of the Application Load Balancer Security Group"
value = aws_security_group.alb_security_group.id
}
2. ec2
main.tf
terraform {
# Location of ec2 state file
backend "s3" {
bucket = "bighead-tfstate"
key = "EC2/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "ap-northeast-2"
}
data.tf
# Remote State of VPC
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "bighead-tfstate"
key = "VPC/terraform.tfstate"
region = "ap-northeast-2"
}
}
variables.tf
# Web Server Port
variable "server_port" {
description = "Server Port of the Web Server"
type = number
default = 80
}
ec2.tf
# EC2 Instance - Web Server
resource "aws_instance" "web_server" {
ami = "ami-017c15d9ff8294f00"
instance_type = "t2.micro"
subnet_id = data.terraform_remote_state.vpc.outputs.subnet_pub_c_id
availability_zone = "ap-northeast-2c"
vpc_security_group_ids = [data.terraform_remote_state.vpc.outputs.web_security_group_id]
associate_public_ip_address = true # Associate a public IP address
key_name = "my-instance-key-pair"
# Start httpd Web Server
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
sudo nohup busybox httpd -f -p ${var.server_port} &
EOF
tags = {
Name = "bighead-webserver"
}
}
alb.tf
# Application Load Balancer - Web Server
resource "aws_lb" "web_server_alb" {
name = "bighead-webserver-alb"
internal = false
load_balancer_type = "application"
security_groups = [data.terraform_remote_state.vpc.outputs.alb_security_group_id]
subnets = [data.terraform_remote_state.vpc.outputs.subnet_pub_a_id, data.terraform_remote_state.vpc.outputs.subnet_pub_c_id]
}
# Target Group for Application Load Balancer
resource "aws_lb_target_group" "web_server_alb_target" {
name = "webserver-alb-target"
port = 80
protocol = "HTTP"
vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id
}
# Listener for Application Load Balancer
resource "aws_lb_listener" "web_server_alb_listener" {
load_balancer_arn = aws_lb.web_server_alb.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web_server_alb_target.id
}
}
autoscaling.tf
# Launch Template - Web Server
resource "aws_launch_template" "web_server_template" {
name = "webserver-template"
image_id = "ami-017c15d9ff8294f00"
instance_type = "t2.micro"
key_name = "my-instance-key-pair"
network_interfaces {
associate_public_ip_address = true # Associate a public IP address
security_groups = [data.terraform_remote_state.vpc.outputs.web_security_group_id]
}
tag_specifications {
resource_type = "instance"
tags = {
Name = "bighead-webserver"
}
}
# Start httpd Web Server
user_data = "${base64encode(<<-EOF
#!/bin/bash
echo "Hello, World" > index.html
sudo nohup busybox httpd -f -p ${var.server_port} &
EOF
)}"
}
# Autoscaling Group - Web Server
resource "aws_autoscaling_group" "web_server_asg" {
name = "webserver-autoscaling-group"
vpc_zone_identifier = [data.terraform_remote_state.vpc.outputs.subnet_pub_a_id, data.terraform_remote_state.vpc.outputs.subnet_pub_c_id]
desired_capacity = 2
max_size = 10
min_size = 2
target_group_arns = [aws_lb_target_group.web_server_alb_target.id]
launch_template {
id = aws_launch_template.web_server_template.id
version = "$Latest"
}
}
3. rds
main.tf
terraform {
# Location of rds state file
backend "s3" {
bucket = "bighead-tfstate"
key = "RDS/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "ap-northeast-2"
}
data.tf
# Remote State of VPC
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "bighead-tfstate"
key = "VPC/terraform.tfstate"
region = "ap-northeast-2"
}
}
rds.tf
# RDS Instance - MySQL
resource "aws_db_instance" "mysql-db" {
allocated_storage = 10
identifier = "mysql-bighead"
db_name = "bighead"
engine = "mysql"
engine_version = "8.0.28"
instance_class = "db.t3.micro"
username = "admin"
manage_master_user_password = true # Managing the master password with Secrets Manager
parameter_group_name = "default.mysql8.0"
db_subnet_group_name = data.terraform_remote_state.vpc.outputs.subnet_group_rds_name
vpc_security_group_ids = [data.terraform_remote_state.vpc.outputs.rds_security_group_id]
skip_final_snapshot = true
port = 3306
}
# TROUBLE SHOOTING LOG
💡 Autoscaling Group 생성 시 오류 발생 Error: creating Auto Scaling Group (webserver-autoscaling-group): ValidationError: You must use a valid fully-formed launch template. Security group sg-063a69a18d2ceb979 and subnet subnet-0c524365a4ac5a7d9 belong to different networks.
원인
Autoscaling Group 생성 시
availability_zones
옵션을 지정하면, 해당 가용 영역의 기본 서브넷이 Autoscaling Group의 서브넷으로 선택되는데, 해당 서브넷이 Launch Template에 지정되어 있는 보안 그룹의 서브넷과 달라 오류가 발생함.
해결 방안
availability_zones
대신vpc_zone_identifier
옵션을 사용해, Autoscaling Group에 서브넷을 직접 지정하여 해결
💡 EC2 Public DNS로 SSH 접속 불가
원인
보안그룹에서 소스 대상을
ALB용으로 생성한 보안그룹
으로 지정했기 때문
resource "aws_security_group" "tfEC2sg" {
name = "Terraform-EC2-HTTP,SSH-Access"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.tfALBsg.id]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.tfALBsg.id] # 문제의 코드
}
egress {
.
.
.
해결 방안
resource "aws_security_group" "tfEC2sg" {
name = "Terraform-EC2-HTTP,SSH-Access"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.tfALBsg.id]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # 모든 대상으로 추가
security_groups = [aws_security_group.tfALBsg.id]
}
egress {
.
.
.
# 피드백
오태경 | 박찬규 |
---|---|
직접 코드로 인프라를 구성해보니 AWS 리소스별 옵션값들을 더 자세히 알 수 있었다. |