728x90
반응형
SMALL
https://pabeba.tistory.com/304
이전 시간에 그냥 장황하게 글을 작성하고... 떠나버렸죠. 저도 열심히 분석하고 글을 다시 써보려고 합니다.

이 글의 목적이 운영 가능한(Production-Ready) 고가용성 인프라 구축에 대한 글이라는 것을 계속 생각하면서 코드를 분석해보아요.
1. 기본 환경 및 변수 정의 파일
📄 provider.tf
terraform {
required_version = ">=1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~>6.0"
}
}
}
provider "aws" {
region = var.region
default_tags {
tags = local.common_tags
}
}
- 핵심 역할: 테라폼 버전을 최소 1.0 이상으로 강제하고 AWS 프로바이더 메이저 버전(v6.0대)을 명시합니다.
- 코드 분석 포인트:
- default_tags 블록 활용: provider 블록 내에 default_tags를 선언하여, 이 코드베이스에서 생성되는 모든 AWS 리소스에 공통 태그(Project, Environment, ManageBy)가 자동으로 주입됩니다. 리소스마다 태그 코드를 중복 작성할 필요가 없어 유지를 매우 깔끔하게 해줍니다.
📄 variables.tf
variable "region" { default = "us-east-2" }
variable "environment" { default = "dev" }
variable "instance_type" { default = "t3.micro" }
variable "web_desired_capacity" { default = 2 }
variable "was_desired_capacity" { default = 2 }
variable "db_instance_class" { default = "db.t3.micro" }
variable "db_name" { default = "appdb" }
variable "db_username" { default = "adminuser" }
- 핵심 역할: 전체 코드베이스에서 유연하게 스펙을 변경할 수 있는 8개의 핵심 변수를 정의합니다.
- 코드 분석 포인트:
- 리전(us-east-2), 인스턴스 스펙(t3.micro), ASG(Auto Scailing Group) 원하는 기본 대수(각 2대) 및 DB 설정값을 중앙에서 관리하여, .tfvars 파일만 교체하면 개발/운영 환경 스펙을 한 번에 제어할 수 있도록 설계되었습니다.
📄 locals.tf
locals {
project = "DE-AI-07-IaC-3tier-V1"
common_tags = {
Project = local.project
Environment = var.environment
ManageBy = "Terraform"
}
azs = {
a = "us-east-2a"
c = "us-east-2c"
}
public_subnets = { a = "10.0.1.0/24", c = "10.0.2.0/24" }
app_subnets = { a = "10.0.11.0/24", c = "10.0.12.0/24" }
db_subnets = { a = "10.0.21.0/24", c = "10.0.22.0/24" }
}
- 핵심 역할: 코드 내부에서만 사용하는 고정 상수 및 서브넷/가용영역 반복 매핑용 Map 데이터를 정의합니다.
- 코드 분석 포인트:
- azs, public_subnets, app_subnets, db_subnets를 가용영역 Key(a, c) 기준 Map 객체로 구성하여, 다른 .tf 파일에서 for_each 문법을 사용해 다중 서브넷을 동적으로 다룰 수 있는 근간이 됩니다.
2. 보안 및 인증 파일
📄 iam.tf
#####################################################
# EC2가 SSM Session Manager를 사용 할 수 있도록 IAM Role 정의 적용
# -> 서브넷의 유형에 상관없이 접속가능, pen 필요 X
#####################################################
# EC2가 IAM 역할을 맡을 수 있도록 허용
data "aws_iam_policy_document" "ec2_assume_role" {
statement {
actions = ["sts:AssumeRole"]
# 서비스명 ec2 조회 조건 지정
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
# 2. role 생성
resource "aws_iam_role" "ec2_ssm" {
# 프로젝트명을 role에 반영하여 구분할 수 있게 처리
# 역할에 policy(정책)
name = "${local.project}-EC2-SSM-Role"
assume_role_policy = data.aws_iam_policy_document.ec2_assume_role.json
}
# 3. AmazonSSMManagedInstanceCore(AWS 관리형 정책)에 role 연결
resource "aws_iam_role_policy_attachment" "ssm_core" {
role = aws_iam_role.ec2_ssm.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
# 4. EC2 구성시 적용할 IAM 인스턴스 프로필 생성 -> Launch_template에서 ec2 구성할때 사용
resource "aws_iam_instance_profile" "ec2_ssm" {
name = "${local.project}-EC2-SSM-Profile"
role = aws_iam_role.ec2_ssm.name
}
- 핵심 역할: EC2 인스턴스가 SSH 키 파일(.pem)이나 22번 포트 개방 없이 AWS Systems Manager(SSM) Session Manager로 터미널 세션에 안전하게 접근할 수 있도록 IAM 역할과 인스턴스 프로필을 생성합니다.
- 코드 분석 포인트:
- AWS 관리형 정책인 AmazonSSMManagedInstanceCore를 부여함으로써, Private Subnet에 격리된 WEB/WAS EC2에 원격 접속을 제공합니다.
📄 sg.tf
- 핵심 역할: 3-Tier 계층 간 트래픽 흐름을 엄격하게 제한하는 보안 그룹(Security Group) 체이닝을 구성합니다.
- 코드 분석 포인트:
- public_alb만 외부 0.0.0.0/0 포트 80을 오픈하고, web SG는 public_alb SG ID만 허용, internal_alb SG는 web SG ID만 허용, was SG는 internal_alb SG ID만 허용, rds SG는 was SG ID만 허용하여 이전 계층의 보안 그룹 ID만을 허용하는 철저한 방화벽 체인을 구현했습니다.
[Internet (HTTP 80)] ➔ [Public ALB SG] ➔ [WEB SG] ➔ [Internal ALB SG (8000)] ➔ [WAS SG] ➔ [RDS SG (3306)]
3. 네트워크 및 인프라 파이프라인 파일
📄 vpc.tf
resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" ... }
resource "aws_subnet" "public" { for_each = local.public_subnets ... }
resource "aws_subnet" "app" { for_each = local.app_subnets ... }
resource "aws_subnet" "db" { for_each = local.db_subnets ... }
resource "aws_nat_gateway" "nat" { for_each = local.azs ... }
- 핵심 역할: Multi-AZ 네트워크 망(VPC, 서브넷, IGW, NAT GW, 라우팅 테이블)을 구축합니다.
- 코드 분석 포인트:
- for_each = local.public_subnets / app_subnets / db_subnets 구문을 통해, 가용영역 a와 c에 총 6개의 서브넷(Public 2, App Private 2, DB Private 2)을 단 몇 줄의 코드 반복으로 깔끔하게 생성합니다.
- 가용 영역마다 독립된 NAT Gateway를 배치하고 (for_each = local.azs) App Private 라우팅 테이블에 각각 연결하여 한쪽 데이터센터가 마비되어도 다른 쪽 AZ의 Outbound 통신이 영향을 받지 않는 고가용성 구조를 실현했습니다.
📄 alb.tf
#################################
# public ALB : Internet <-> ALB <-> WEB ASG
#################################
resource "aws_lb" "public" {
name = "${local.project}-public-alb"
internal = false
load_balancer_type = "application"
# 보안그룹 - 퍼블릭 ALB
security_groups = [aws_security_group.public_alb.id]
# 퍼블릭 서브넷 2개 (가용영역별 a, c) 각각 id를 추출(리스트 컴프리핸션 방식 문법) 반영
subnets = [for subnet in aws_subnet.public : subnet.id]
tags = { Name = "${local.project}-PUBLIC-ALB" }
}
# ALB가 주기적으로 대상 EC2의 상태를 체크하는 설정(헬스체크등)
resource "aws_lb_target_group" "web" {
name = "${local.project}-web-tg"
port = 80
protocol = "HTTP"
target_type = "instance"
vpc_id = aws_vpc.main.id
# 주기적으로 대상 EC2 점검 -> 요청 -> 응답에 대한 규칙 설정
health_check {
enabled = true # 헬스 체크 사용 여부
path = "/health" # 상태 체크하는 URL 값(경로)
protocol = "HTTP" # 통신 방식
matcher = "200" # 정상 응답 코드 값
interval = 30 # 헬스 체크 간격 (초)
timeout = 5 # 응답 대기 시간 (초)
healthy_threshold = 2 # 연속 몇번(2번) 성공해야 정상 상태로 인지(상태변경)
unhealthy_threshold = 2 # 연속 몇번(2번) 실패해야 비정상 상태로 인지(상태변경)
}
tags = { Name = "${local.project}-WEB-TG" }
}
# ALB가 80포트로 받은 HTTP 요청을 web target_group으로 전달하도록 설정(리슨)
# 아래 구성을 통해서 헬스 체크가 작동됨
resource "aws_lb_listener" "public_http" {
load_balancer_arn = aws_lb.public.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}
#################################
# internal ALB : WEB ASG <-> Internal ALB <-> WAS ASG
#################################
resource "aws_lb" "internal" {
name = "${local.project}-In-alb"
internal = true
load_balancer_type = "application"
security_groups = [aws_security_group.internal_alb.id]
# APP 프라이빗 서브넷 2개 (가용영역별 a, c) 각각 id를 추출(리스트 컴프리핸션 방식 문법) 반영
subnets = [for subnet in aws_subnet.app : subnet.id]
tags = { Name = "${local.project}-INTERNAL-ALB" }
}
# was 직접 점검 => 8000번 접속, 모두 상위 설정값과 동일
resource "aws_lb_target_group" "was" {
name = "${local.project}-was-tg"
port = 8000
protocol = "HTTP"
target_type = "instance"
vpc_id = aws_vpc.main.id
health_check {
enabled = true
path = "/health"
protocol = "HTTP"
matcher = "200"
interval = 30
timeout = 5
healthy_threshold = 2
unhealthy_threshold = 2
}
tags = { Name = "${local.project}-WAS-TG" }
}
# 포트제외, 대상제외 구성 모두 동일함
resource "aws_lb_listener" "internal_http" {
load_balancer_arn = aws_lb.internal.arn
port = 8000
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.was.arn
}
}
- 핵심 역할: 외부 인터넷 접속용 Public ALB와 Web-WAS 계층 간 연결을 담당하는 Internal ALB를 생성합니다.
- 코드 분석 포인트:
- Target Group Health Check 설정: /health 경로로 30초마다 요청을 보내 200 OK 응답이 연속 2회 올 경우 정상으로 판단합니다 (healthy_threshold = 2).
- 타겟 그룹 주소와 리스너를 결합하여, 트래픽을 비정상 서버로부터 즉시 차단하는 자동 라우팅 시스템을 구현했습니다.
- arn : Amazon Resource Name
4. 서버 생성 및 스케일링 정책 파일
📄 data.tf
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023*-x86_64"]
}
}
- 핵심 역할: AWS에서 제공하는 최신 Amazon Linux 2023 (AL2023) AMI를 동적으로 동기화하여 가져옵니다.
- 코드 분석 포인트: AMI ID를 하드코딩하지 않고 데이터 소스로 불러와 최신 패치가 적용된 인프라 이미지를 보장합니다.
📄 launch_template.tf
###################################
# web ec2용 -> launch template 요소(ASG 내에서 사용)를 사용하여 생성됨
###################################
resource "aws_launch_template" "web" {
name_prefix = "${local.project}-WEB-" # 증감이 수시로 발생해도 중복 x
image_id = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
# SSM 접속을 위해 프로파링 설정 -> iam.tf 구성
iam_instance_profile {
name = aws_iam_instance_profile.ec2_ssm.name
}
# 보안 그룹 지정
vpc_security_group_ids = [aws_security_group.web.id]
# 서버 구성후 초기 작업 -> 쉘스크립트를 읽어서 => base64 인코딩처리 => 실행되게 구성
user_data = base64encode(templatefile("${path.module}/userdata-web.sh.tftpl", {
# WEB -> proxy -> Internal ALB (이 리소스의 IP 혹은 dns 알아야 전달)
# 해당 값을 가져가서 nginx conf 파일을 완성함
internal_alb_dns = aws_lb.internal.dns_name
}))
# 태그
tag_specifications {
resource_type = "instance"
tags = merge(local.common_tags, {
Name = "${local.project}-WEB"
Tier = "web"
})
}
# 삭제전 생성 -> ec2 수 유지에 대한 기준
lifecycle {
create_before_destroy = true
}
}
###################################
# was ec2용 -> launch template 요소(ASG 내에서 사용)를 사용하여 생성됨
###################################
resource "aws_launch_template" "was" {
name_prefix = "${local.project}-WAS-"
image_id = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
iam_instance_profile {
name = aws_iam_instance_profile.ec2_ssm.name
}
vpc_security_group_ids = [aws_security_group.was.id]
user_data = base64encode(templatefile("${path.module}/userdata-was.sh.tftpl", {
# rds 세팅값 설정
db_host = aws_db_instance.mysql.address
db_port = aws_db_instance.mysql.port
db_name = var.db_name
db_user = var.db_username
}))
tag_specifications {
resource_type = "instance"
tags = merge(local.common_tags, {
Name = "${local.project}-WAS"
Tier = "was"
})
}
lifecycle {
create_before_destroy = true
}
}
- 핵심 역할: ASG가 EC2를 신규 생성할 때 필요한 명세서(템플릿)를 정의합니다.
- 코드 분석 포인트:
- templatefile 함수를 사용하여 userdata-web.sh.tftpl 템플릿 파일에 Internal ALB의 DNS 주소를 인자로 주입한 뒤, 이를 base64encode로 변환하여 EC2의 UserData로 넘겨줍니다.
- create_before_destroy = true 릴리즈 라이프사이클을 적용하여 기존 서버 삭제 전 새 서버를 먼저 생성해 인스턴스 수를 유지합니다.
📄 asg.tf
#############################################
# WEB/WAS EC2를 각각 최소 2대 유지하도록 구성
# CPU 측정(트레픽 나름 비례) -> 50% 기준 설정(사내별 상이함)
# -> Ec2의 개수를 증감(최대 4대(설정)까지):Scaling -> 자동(Auto) 처리
#############################################
# 증감 등
resource "aws_autoscaling_group" "web" {
name = "${local.project}-WEB-ASG"
# 최소 크기 - 최소 2대는 상시 유지 -> 장애 발생 하더라도 2개로 맞춘다!!
min_size = 2
# 최초 디자인된 개수
desired_capacity = var.web_desired_capacity
# 최대 확장되는 개수
max_size = 4
# 두(n)개 서브넷의 가용 영역별로 구성
vpc_zone_identifier = [for subnet in aws_subnet.app : subnet.id]
# 로드밸런서 타겟 그룹 배치
target_group_arns = [aws_lb_target_group.web.arn]
# 헬스 체크 ELB(ALB)로 구성
health_check_type = "ELB"
# 서버구성후 180초 동안 헬스 체크 실패는 무시( 최초 구성시 로드, 업데이트등 정상 x)
health_check_grace_period = 180
# ec2를 launch_template 이용하여 구성하겠다 (상세 구성 내용-런치 템플릿(기존 ec2.tf))
launch_template {
# 커스텀으로 구성한 런치 탬플릿 참조
id = aws_launch_template.web.id
# 런치 템플릿의 최신버전
version = "$Latest"
}
# 내용변경(버전변경) -> 교체(순차)
instance_refresh {
# 장애/업그레이드등 이슈로 교체시 -> 순차적 처리 -> (1개 신규 생성 -> 1개 삭제..)
strategy = "Rolling"
# 순차 교체 세부 조건
preferences {
# 최소 50% 성능 유지(헬스 체크등)
min_healthy_percentage = 50
}
}
# 태그 - 블록 반복 -> 기존태그(3개) + 신규 태그(2개) 합병하여 동적 eC2에 동적 세팅 샘플
dynamic "tag" {
# merge() 합병 - 기존태그(3개) + 신규 태그(2개) 합병
for_each = merge(local.common_tags, {
Name = "${local.project}-WEB"
Tier = "web"
})
content {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}
}
resource "aws_autoscaling_group" "was" {
name = "${local.project}-WAS-ASG"
min_size = 2
# 초기 구성 개수 타겟
desired_capacity = var.was_desired_capacity
max_size = 4
vpc_zone_identifier = [for subnet in aws_subnet.app : subnet.id]
# alb 타겟 그룹 다름
target_group_arns = [aws_lb_target_group.was.arn]
health_check_type = "ELB"
# 240초 동안 헬스 체크 오류는 무시. was가 구성상 더 시간이 소요됨
health_check_grace_period = 240
launch_template {
# ec2 구성 상이
id = aws_launch_template.was.id
version = "$Latest"
}
instance_refresh {
strategy = "Rolling"
preferences {
min_healthy_percentage = 50
}
}
dynamic "tag" {
for_each = merge(local.common_tags, {
Name = "${local.project}-WAS"
Tier = "was"
})
content {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}
}
# CPU 측정등
resource "aws_autoscaling_policy" "web_cpu" {
name = "${local.project}-WEB-CPU-50"
autoscaling_group_name = aws_autoscaling_group.web.name
# 정책 타입 : 목표 추적 방식으로 스케일링 처리
policy_type = "TargetTrackingScaling"
# 설정 목표
# 목적 : 설정된 지표가 목표값 근처에 유지되도록 ec2 수를 조정
target_tracking_configuration {
# 평가 기준
predefined_metric_specification {
# AWS 제공하는 지표 활용 => CPU 사용량
predefined_metric_type = "ASGAverageCPUUtilization"
}
# 타겟값 50=> 평균 CPU 사용량 50% 높음(낮음) -> EC2 증가(감소) 자동 스케일링 진행
# 2 ~ 4 사이에 구성
target_value = 50
}
}
resource "aws_autoscaling_policy" "was_cpu" {
name = "${local.project}-WAS-CPU-50"
autoscaling_group_name = aws_autoscaling_group.was.name
policy_type = "TargetTrackingScaling"
target_tracking_configuration {
predefined_metric_specification {
predefined_metric_type = "ASGAverageCPUUtilization"
}
target_value = 50
}
}
- 핵심 역할: Web 및 WAS 서버에 대한 오토스케일링 그룹(ASG) 및 CPU 부하 기반 스케일링 정책을 지정합니다.
- 코드 분석 포인트:
- Instance Refresh (Rolling 배포): 런치 템플릿 버전 변경 시 무중단으로 신규 서버를 하나씩 스와프 교체하는 배포 전략이 정의되어 있습니다.
- Target Tracking Policy: 평균 CPU 사용량이 50%를 초과하면 자동으로 인스턴스를 늘리고, 떨어지면 줄이는 지능형 스케일링 정책이 적용되었습니다.
5. 데이터베이스 파이프라인 파일
📄 rds.tf
############################################
# RDS Subnet Group - 서로 다른 AZ의 DB 서브넷 사용
############################################
resource "aws_db_subnet_group" "main" {
name = "de-ai-07-db-subnet-group"
subnet_ids = [for subnet in aws_subnet.db : subnet.id]
tags = { Name = "${local.project}-DB-SUBNET-GROUP" }
}
############################################
# RDS MySQL Multi-AZ
# manage_master_user_password=true:
# 비밀번호를 코드에 저장하지 않고 AWS Secrets Manager가 관리
############################################
resource "aws_db_instance" "mysql" {
identifier = "de-ai-07-mysql-v1"
engine = "mysql"
instance_class = var.db_instance_class
allocated_storage = 20
max_allocated_storage = 100
storage_type = "gp3"
storage_encrypted = true
db_name = var.db_name
username = var.db_username
manage_master_user_password = true
multi_az = true
publicly_accessible = false
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
backup_retention_period = 7
backup_window = "18:00-19:00"
maintenance_window = "sun:19:00-sun:20:00"
deletion_protection = false
skip_final_snapshot = true
apply_immediately = true
tags = { Name = "${local.project}-MYSQL" }
}
- 핵심 역할: AWS RDS MySQL Multi-AZ 고가용성 데이터베이스를 생성합니다.
- 코드 분석 포인트:
- manage_master_user_password = true: DB 루트 비밀번호를 테라폼 코드에 평문으로 남기지 않고 AWS Secrets Manager와 자동 연동하여 암호화 관리합니다.
- multi_az = true로 데이터베이스의 Standby 복제본을 상시 유지합니다.
6. 서버 부팅 스크립트 템플릿 파일
📄 userdata-web.sh.tftpl
- 핵심 역할: Web EC2 초기화 시 실행되는 Bash 스크립트 템플릿입니다.
- 코드 분석 포인트:
- dnf update -y 및 nginx 설치를 진행합니다.
- 테라폼에서 동적으로 주입받은 ${internal_alb_dns} 변수를 Nginx 설정을 작성할 때 적용하여, 모든 라우팅 트래픽을 Internal ALB(WAS 계층)로 프록시 포워딩(proxy_pass)하도록 구성합니다.
📄 userdata-was.sh.tftpl
- 핵심 역할: WAS EC2 초기화 시 실행되는 컨테이너 기반 부팅 스크립트 템플릿입니다.
- 코드 분석 포인트:
- docker 엔진을 설치 및 활성화합니다.
- /opt/app 디렉토리에 FastAPI 소스코드(app.py), requirements.txt, Dockerfile을 직접 생성합니다.
- docker build를 수행하여 이미지를 빌드하고, 환경변수(DB_HOST, DB_NAME 등)를 주입받아 Docker 컨테이너를 8000번 포트로 가동시킵니다.
📄 outputs.tf
output "public_alb_url" {
description = "브라우저 접속 주소"
value = "http://${aws_lb.public.dns_name}"
}
output "public_alb_dns" {
value = aws_lb.public.dns_name
}
output "internal_alb_dns" {
value = aws_lb.internal.dns_name
}
output "rds_endpoint" {
value = aws_db_instance.mysql.endpoint
}
output "rds_master_secret_arn" {
description = "AWS Secrets Manager에서 관리되는 RDS 관리자 비밀번호 Secret ARN"
value = try(aws_db_instance.mysql.master_user_secret[0].secret_arn, null)
}
output "web_asg_name" {
value = aws_autoscaling_group.web.name
}
output "was_asg_name" {
value = aws_autoscaling_group.was.name
}
- 핵심 역할: 프로비저닝 완료 후 개발자가 브라우저로 접속할 Public ALB 주소와 백엔드/클라이언트가 참조할 RDS 엔드포인트 정보를 터미널 화면에 종합 출력합니다.
💡 요약 및 코드베이스 총평
이 tf-step5 프로젝트 파일들은 단순한 인프라 생성을 넘어, 완전 자동화된 컨테이너 가동(UserData), 비밀번호 유출 방지(Secrets Manager), Multi-AZ 기반의 고가용성 네트워크망, 그리고 보안 그룹 체이닝까지 반영된 고품질의 테라폼 표준 코드베이스입니다.
오늘의 한 마디
솔직히 아직도 정확히 알 수 없고 암기도 할 수 없습니다. 하지만 어떠어떠한 것이 사용되었는지는 조금은 알아볼 수 있을 것 같습니다.
이전 교육에서도 처음에는 하나도 이해가 되지 않고 기억도 나지 않았습니다. 하지만 이것을 계속 사용하고 다시 보고 또 보면 제 몸에 체득되지 않을까 싶습니다. 매일 몇번씩 쳐다보면서 복습한다고 생각하면 될 것 같습니다. 집에서 다시 한번 보면서 무슨 내용인지 다시 분석해 보겠습니다.
처음부터 잘하면 재미없습니다. 점점 성장하는 자신을 보면서 행복을 느껴보세요. 그렇다면 이 세상을 아주 행복하고 재미나게 살아갈 수 있을 겁니다.
반응형
LIST