* 본 글에서는 Terraform 코드에 대한 자세한 설명은 진행하지 않습니다. *
- Module은 EC2, VPC, RDS - MySQL 3개만 구현하였고, 나머지는 Resource를 사용하여 인프라를 구현하였다.
- 동적으로 Infra의 설정 값을 변경할 수 있도록 variables.tf를 적절히 활용하였고, 각 Test, Dev, Stg, Prd 등 각 환경마다 변수 값을 다르게 설정할 수 있도록 env/~.tfvars 처럼 tfvars 파일을 생성하였다.
- 아직 Module화, count, for_each 등의 Terraform 함수들을 활용하여 좀 더 간결하여 동적인 Code로 리팩터링하는 작업이 필요할 것으로 보인다.
- Tomcat AMI는 Packer로 생성하였는데, 아래의 링크에서 확인할 수 있다.
아키텍처
이 중에 Code Deploy는 Terraform으로 구성 X
Root Domain은 가비아 도메인인 pingping2.shop을 사용하였으며 Backend로는 S3와 Terraform Lock을 위한 DynamoDB를 사용하였다.
- Public Subnet, Private Subnet, Database Subnet으로 구성하였으며, 만약 Database Subnet의 CIDR을 Variables 선언 부분에 기입하지 않을 경우 동적으로 Terraform이 이를 파악하여 생성하지 않도록 하였음 (count 함수)
- AWS Load Balancer를 사용하면 ACM과 같이 SSL Termination을 LB단에서 적용할 수 있으므로 내부에서는 http 프로토콜을 사용할 수 있으며 좀 더 빠르고 해당 LB를 거친 VM들에 대해서는 https 인증서를 적용하지 않아도 된다.
- AutoScaling Group에서는 Launch Template, Launch Configuration 2가지 중 하나를 사용할 수 있으며 이번에는 Launch Template을 사용하였다. (버전 관리가 가능하기 때문)
- Tomcat과 RDS MySQL은 RDS의 Endpoint로 설정하여 평소에 Primary DB로 쿼리를 하지만 만약 장애가 발생할 경우 페일 오버가 진행되며, Standby DB가 Primary DB로 승격하게 된다.
=> Multi AZ 기능
Terraform으로 구성한 Resource 내역
- VPC
- Subnet
- Route Table
- Internet Gateway
- NAT Gateway
- EC2
- Bastion, Jenkins
- S3 (war 파일 저장을 위한 스토리지)
- Route 53, ACM
- AutoScaling Group, SNS, ASG Policy
- ALB, NLB
- RD
디렉터리 구조
크게 Modules 디렉터리와 Project 디렉터리로 구성되어 있다.
- modules : 여러 프로젝트에서 재사용할 수 있도록 리소스를 모듈화함
- project/common : 특정 프로젝트와 상관없이 공통으로 사용하는 리소스들을 별도 관리하기 위해 구성 (IAM, Route53, tfstate를 위한 S3, DynamoDB 리소스, ..)
=> 3 Tier 프로젝트를 destory해도 Global 서비스들을 유지시키기 위함
- project/3tier : 3 티어 프로젝트와 연관이 있는 리소스들을 한 디렉터리에 모아 관리하기 위함. 각 서비스별로 tf 파일을 구성하였으며 AWS 계정 내에서 동적으로 값을 가져오기 위한 data.tf 및 variables.tf 파일 또한 생성하였다.
- project/3tier/env : 각 환경별로 tfvars 파일을 둠으로써 환경 별 리소스를 세팅 및 같은 인프라이지만 변수 값만 다르게 구현할 수 있도록 하기 위함
- project/3tier/scripts : 쉘 스크립트 및 Terraform 템플릿 파일을 모아놓은 폴더
test.tfvars 파일 예시이며, 아래처럼 본인이 원하는 인프라 값을 기입해주면 인프라가 형성된다.
# Environment
env = "test"
# Profile, Region Name
profile_name = "default"
cred_file = "~/.aws/credentials"
region = "ap-northeast-2"
# VPC Configurations
cidr_block = "10.0.0.0/16" # VPC CIDR Block
public_subnet_cidr = ["10.0.0.0/24", "10.0.1.0/24"]
private_subnet_cidr = ["10.0.10.0/24", "10.0.11.0/24"]
db_subnet_cidr = ["10.0.20.0/24", "10.0.21.0/24"]
available_azs = ["ap-northeast-2a", "ap-northeast-2c"]
# EC2 Configurations
default_instance_type = "t3.small"
key_pair = "your-key"
jenkins_instance_type = "t3.small"
jenkins_instance_profile = "test_profile"
# Security Groups
# tuple : list of {description, source_cidr, from, to, protocol}
source_bastion_cidrs = [
{
desc = "Bastion SSH Inbound",
cidrs = ["0.0.0.0/0"],
from = 22,
to = 22,
protocol = "tcp",
},
]
source_web_cidrs = [
{
desc = "WEB HTTP Inbound",
cidrs = ["0.0.0.0/0"],
from = 80,
to = 80,
protocol = "tcp",
},
]
source_was_cidrs = [
{
desc = "WAS HTTP Inbound",
cidrs = ["0.0.0.0/0"],
from = 8080,
to = 8080,
protocol = "tcp",
},
]
// Launch Template
web_instance_type = "t2.small"
was_instance_type = "t2.small"
web_ami_version = "v0.1"
was_ami_version = "v0.2"
web_lt_default_version = 1
was_lt_default_version = 1
// AutoScaling
web_asg_version = "$Default"
was_asg_version = "$Default"
web_asg_capacity = {
desired = 1
min = 1,
max = 2,
}
was_asg_capacity = {
desired = 1
min = 1,
max = 2,
}
// ASG Policy
// 1. WEB
web_adjustment_type = "ChangeInCapacity"
web_cpu_scaleup_threshold = 60
web_cpu_scaledown_threshold = 20
// 2. WAS
was_adjustment_type = "ChangeInCapacity"
was_cpu_scaleup_threshold = 60
was_cpu_scaledown_threshold = 20
was_instance_profile = "CodeDeployRoleProfile"
// SNS
asg_noti_endpoint = "example@gmail.com"
// Route 53, ALB, ACM
domain = "example.com"
// Database
// Global
db_name = "global"
// Param Group
db_family = "mysql5.7"
// DB Instance
db_identifier = "test-mysql-57"
db_engine = "mysql"
db_engine_version = "5.7"
db_allocated_storage = "30"
db_instance_class = "db.t3.micro"
db_multi_az = false
db_username = "admin"
db_password = "passw0rd1!"
// Option Group
db_engine_name = "mysql"
db_major_engine_version = "5.7"
- 아래는 Directory 구조이다.
❯ tree | grep -v tfstate
.
├── README.md
├── images
│ └── archutecture.png
├── modules
│ ├── ec2
│ │ ├── README.md
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── variables.tf
│ │ └── version.tf
│ ├── lb
│ │ ├── main.tf
│ │ ├── output.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ ├── rds
│ │ ├── mysql
│ │ │ ├── README.md
│ │ │ ├── data.tf
│ │ │ ├── main.tf
│ │ │ ├── output.tf
│ │ │ ├── provider.tf
│ │ │ ├── test_vars
│ │ │ │ └── test.tfvars
│ │ │ └── variables.tf
│ │ └── postgresql
│ └── vpc
│ ├── README.md
│ ├── main.tf
│ ├── output.tf
│ ├── provider.tf
│ └── variables.tf
└── project
├── 3Tier
│ ├── asg.tf
│ ├── asg_policy.tf
│ ├── data.tf
│ ├── ec2.tf
│ ├── ec2_security_group.tf
│ ├── env
│ │ └── test.tfvars
│ ├── outputs.tf
│ ├── provider.tf
│ ├── rds.tf
│ ├── scripts
│ │ ├── common
│ │ │ ├── amz2_init.sh
│ │ │ └── jenkins_install.sh
│ │ ├── was
│ │ │ ├── setenv.sh
│ │ │ ├── shutdown.sh
│ │ │ ├── startup.sh
│ │ │ ├── tomcat.service
│ │ │ ├── tomcat_install.sh
│ │ │ └── was_userdata.sh
│ │ └── web
│ │ └── nginx_tomcat.tftpl
│ ├── variables.tf
│ ├── vpc.tf
│ ├── was-nlb.tf
│ └── web-alb.tf
├── common
│ ├── acm
│ │ ├── data.tf
│ │ ├── main.tf
│ │ ├── output.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ ├── etc
│ │ ├── backend.tf
│ │ ├── provider.tf
│ │ ├── s3.tf
│ │ └── variables.tf
│ ├── iam
│ │ ├── data.tf
│ │ ├── main.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ └── route53
│ ├── main.tf
│ ├── provider.tf
│ └── variables.tf
├── packer
│ └── was
│ ├── packer.pkrvars.hcl
│ ├── tomcat_ami.pkr.hcl
│ └── tomcat_install.sh
└── test
├── data.tf
├── provider.tf
└── variables.tf
3 Tier Project 내부 파일 설명
├── 3Tier
│ ├── asg.tf
│ ├── config
│ │ ├── amz2_init.sh
│ │ ├── nginx_tomcat.tftpl
│ │ ├── setenv.sh
│ │ ├── shutdown.sh
│ │ ├── startup.sh
│ │ └── tomcat.service
│ ├── data.tf
│ ├── ec2.tf
│ ├── ec2_security_group.tf
│ ├── env
│ │ └── test.tfvars
│ ├── outputs.tf
│ ├── provider.tf
│ ├── rds.tf
│ ├── terraform.tfstate
│ ├── terraform.tfstate.backup
│ ├── variables.tf
│ ├── vpc.tf
│ ├── was-nlb.tf
│ └── web-alb.tf
vpc.tf (Module 사용)
: VPC, Subnet Group, Route Table, Route Table Attachment, .. 등의 리소스를 생성
ec2.tf (Module 사용)
: Bastion, 그 외 ec2들을 생성
ec2_security_group.tf
: EC2에서 사용하는 Security Group, Security Group Rule
web-alb.tf , was-nlb.tf
: WEB, WAS 로드 밸런서 생성, Target Group 생성
rds.tf (Module 사용)
: Option Group, Param Group, Subnet Group, DB Instance 리소스 생성 (MySQL)
asg.tf
: Autoscaling 및 Launch Template, ASG Attachment 리소스 생성
config 디렉터리
: templatefile 함수를 사용하기 위한 tftpl 파일, 초기화 shell script 등을 모아놓은 디렉터리
env 디렉터리
: 환경에 맞는 변수를 설정하기 위한 파일들을 생성해놓는 곳
그 외
data.tf : AWS로부터 데이터를 가져오기 위한 data 블럭들을 모아놓음
variables.tf : 변수 생성
asg.tf 파일
모든 파일을 설명하면 글이 길어지기 때문에 그 중 하나인 asg.tf에 대해서만 간략히 설명
1. Launch Template 생성
: 기 생성해 놓은 WEB AMI, WAS AMI를 통해 Launch Template 생성
2. 생성해둔 Launch Template를 통해 ASG 생성
3. 기 생성해둔 LB의 Target Group의 VM들을 ASG와 연결시키기 위한 Attachment 작업
// Launch Template
resource "aws_launch_template" "web" {
name = "${var.env}-web-launch_template"
disable_api_termination = var.disable_api_termination
image_id = data.aws_ami.web.image_id
instance_type = var.web_instance_type
key_name = var.key_pair
vpc_security_group_ids = [aws_security_group.web.id, aws_security_group.bastion_common.id]
tags = {
Name = "${var.env}-web-launch_template"
}
default_version = var.web_lt_default_version
user_data = base64encode(templatefile(
"${path.module}/scripts/web/nginx_tomcat.tftpl",
{
BACKEND_LB = aws_lb.was.dns_name,
BACKEND_PORT = var.was_port
ROOT_DOMAIN = var.domain
}
))
}
resource "aws_launch_template" "was" {
name = "${var.env}-was-launch_template"
disable_api_termination = var.disable_api_termination
image_id = data.aws_ami.was.image_id
instance_type = var.was_instance_type
key_name = var.key_pair
vpc_security_group_ids = [aws_security_group.was.id, aws_security_group.bastion_common.id]
tags = {
Name = "${var.env}-was-launch_template"
}
default_version = var.was_lt_default_version
user_data = base64encode(file("./scripts/was/was_userdata.sh"))
iam_instance_profile {
name = var.was_instance_profile
}
}
// AutoScaling Group
// WEB
resource "aws_autoscaling_group" "web" {
name = "${var.env}-web-asg"
// VPC, Network
vpc_zone_identifier = module.main_vpc.private_subnets_ids
// Capacity
min_size = var.web_asg_capacity.min
max_size = var.web_asg_capacity.max
desired_capacity = var.web_asg_capacity.desired
// Launch Template Setting
launch_template {
id = aws_launch_template.web.id
version = var.web_asg_version
}
force_delete = true
timeouts {
delete = "15m"
}
lifecycle {
ignore_changes = [load_balancers, target_group_arns]
}
tag {
key = "Name"
value = "${var.env}-web"
propagate_at_launch = true
}
tag {
key = "ASG"
value = true
propagate_at_launch = true
}
}
// WAS
resource "aws_autoscaling_group" "was" {
name = "${var.env}-was-asg"
// VPC, Network
vpc_zone_identifier = module.main_vpc.private_subnets_ids
// Capacity
min_size = var.was_asg_capacity.min
max_size = var.was_asg_capacity.max
desired_capacity = var.was_asg_capacity.desired
// Launch Template Setting
launch_template {
id = aws_launch_template.was.id
version = var.was_asg_version
}
force_delete = true
timeouts {
delete = "15m"
}
lifecycle {
ignore_changes = [load_balancers, target_group_arns]
}
tag {
key = "Name"
value = "${var.env}-was"
propagate_at_launch = true
}
tag {
key = "ASG"
value = true
propagate_at_launch = true
}
}
// AutoScaling Group Attachment
resource "aws_autoscaling_attachment" "web" {
autoscaling_group_name = aws_autoscaling_group.web.id
alb_target_group_arn = aws_lb_target_group.web_http.id
}
resource "aws_autoscaling_attachment" "was" {
autoscaling_group_name = aws_autoscaling_group.was.id
alb_target_group_arn = aws_lb_target_group.was_http.id
}
Template 파일에 동적으로 변수를 넣어주기 위한 templatefile 함수 사용
참고 (아래 URL을 참고하여 작성하였음)
- https://www.terraform.io/docs/language/functions/base64encode.html
- https://learn.hashicorp.com/tutorials/terraform/functions
- https://www.terraform.io/docs/language/functions/templatefile.html
user_data 부분에는 Launch Template를 통해 생성될 EC2의 초기 스크립트를 기입할 수 있으며 이를 templatefile 이란 Terraform에서 제공하는 함수를 통해 동적으로 변수를 기입할 수 있다.
BACKEND_LB와 BACKEND_PORT, ROOT_DOMAIN 3개의 변수를 설정하였다.
추가로 base64encode 함수를 통해 인코딩해주어야 함
user_data = base64encode(templatefile(
"./config/nginx_tomcat.tftpl",
{
BACKEND_LB = aws_lb.was.dns_name
BACKEND_PORT = var.was_port
ROOT_DOMAIN = "pingping2.shop"
}
))
아래는 nginx_tomcat.tftpl 파일이다.
변수를 사용할 때는 ${variable} 형식으로 사용하면 된다.
결과
- 정적 분석
terraform fmt && terraform validate
- Plan
terraform plan -var-file=./env/test.tfvars
- Apply
terraform apply -var-file=./env/test.tfvars -auto-approve
- ASG
- SNS
- ASG Event 발생 시 SNS
- RDS
- Static Content
- Dynamic Content
: Tomcat 서버에서 처리. 아래는 단순 index.jsp를 hello world로 변경해서 고양이가 안뜨는 것일 뿐
- Destroy
terraform destroy -var-file=./env/test.tfvars
이로써 destroy 이후 apply 하여도 동일한 구성의 인프라가 계속 생성되도록 하였다.
이후에 WAR Demo 파일을 Tomcat에 배포하고 ASG를 활용한 Blue Green 배포를 진행하겠다.