๐Ÿ“ ํ˜„์žฌ ๊ตฌ์กฐ ๊ธฐ๋ฐ˜ ์ž‘์—… ๊ฐ€์ด๋“œ

ํ˜„์žฌ๋Š” main.tf ๋‹จ์ผ ํŒŒ์ผ์ด๋‹ˆ๊นŒ, ๊ธฐ์กด ํŒŒ์ผ์— ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์•ˆ๋‚ดํ• ๊ฒŒ์š”.


1 Private Subnet ์ถ”๊ฐ€ (๊ฐ€์žฅ ๋จผ์ €)

variables.tf์— ์ถ”๊ฐ€

# === Private Subnet ===
variable "private_subnet_a_cidr" {
  default = "10.0.11.0/24"
}
variable "private_subnet_b_cidr" {
  default = "10.0.12.0/24"
}

main.tf ์„œ๋ธŒ๋„ท ์„น์…˜ ์•„๋ž˜์— ์ถ”๊ฐ€

# ============================================
# Private Subnets (Backend EC2, RDS์šฉ)
# ============================================
resource "aws_subnet" "private_a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_subnet_a_cidr
  availability_zone = data.aws_availability_zones.available.names[0]
  
  tags = { Name = "${var.project_name}-private-a" }
}
 
resource "aws_subnet" "private_b" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_subnet_b_cidr
  availability_zone = data.aws_availability_zones.available.names[1]
  
  tags = { Name = "${var.project_name}-private-b" }
}
 
# Private Route Table (NAT ์—†์ด ์ผ๋‹จ ์ƒ์„ฑ)
resource "aws_route_table" "private_rt" {
  vpc_id = aws_vpc.main.id
  # NAT Gateway ์ถ”๊ฐ€ ์‹œ ์—ฌ๊ธฐ์— route ๋ธ”๋ก ์ถ”๊ฐ€
  tags = { Name = "${var.project_name}-private-rt" }
}
 
resource "aws_route_table_association" "private_a" {
  subnet_id      = aws_subnet.private_a.id
  route_table_id = aws_route_table.private_rt.id
}
 
resource "aws_route_table_association" "private_b" {
  subnet_id      = aws_subnet.private_b.id
  route_table_id = aws_route_table.private_rt.id
}

2 RDS PostgreSQL ์ถ”๊ฐ€

variables.tf์— ์ถ”๊ฐ€

# === RDS ===
variable "db_username" {
  default = "pposiraegi"
}
variable "db_password" {
  description = "RDS master password"
  sensitive   = true
}
variable "db_name" {
  default = "ecommerce"
}

terraform.tfvars์— ์ถ”๊ฐ€

db_password = "์—ฌ๊ธฐ์—_๊ฐ•๋ ฅํ•œ_๋น„๋ฐ€๋ฒˆํ˜ธ_์ž…๋ ฅ"

main.tf Security Group ์„น์…˜์— ์ถ”๊ฐ€

# ============================================
# RDS Security Group
# ============================================
resource "aws_security_group" "rds_sg" {
  vpc_id = aws_vpc.main.id
  name   = "${var.project_name}-rds-sg"
 
  ingress {
    description     = "PostgreSQL from Backend EC2"
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.backend_sg.id]
  }
 
  tags = { Name = "${var.project_name}-rds-sg" }
}

main.tf ๋งจ ์•„๋ž˜์— RDS ๋ฆฌ์†Œ์Šค ์ถ”๊ฐ€

# ============================================
# RDS PostgreSQL
# ============================================
resource "aws_db_subnet_group" "main" {
  name       = "${var.project_name}-db-subnet-group"
  subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id]
  
  tags = { Name = "${var.project_name}-db-subnet-group" }
}
 
resource "aws_db_instance" "postgres" {
  identifier = "${var.project_name}-postgres"
  
  # ์—”์ง„ ์„ค์ •
  engine         = "postgres"
  engine_version = "15.4"
  
  # ์ธ์Šคํ„ด์Šค ์„ค์ •
  instance_class    = "db.t3.micro"  # ํ”„๋ฆฌํ‹ฐ์–ด
  allocated_storage = 20
  storage_type      = "gp2"
  
  # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •
  db_name  = var.db_name
  username = var.db_username
  password = var.db_password
  
  # ๋„คํŠธ์›Œํฌ ์„ค์ •
  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.rds_sg.id]
  publicly_accessible    = false
  
  # ๊ฐ€์šฉ์„ฑ ์„ค์ • (๋น„์šฉ ์ ˆ๊ฐํ•˜๋ ค๋ฉด false)
  multi_az = false
  
  # ๊ธฐํƒ€ ์„ค์ •
  skip_final_snapshot = true  # ๊ฐœ๋ฐœ์šฉ: ์‚ญ์ œ ์‹œ ์Šค๋ƒ…์ƒท ์•ˆ ์ฐ์Œ
  
  tags = { Name = "${var.project_name}-postgres" }
}

outputs.tf์— ์ถ”๊ฐ€

output "rds_endpoint" {
  description = "RDS PostgreSQL endpoint"
  value       = aws_db_instance.postgres.endpoint
}
 
output "rds_port" {
  description = "RDS PostgreSQL port"
  value       = aws_db_instance.postgres.port
}

3 user_data.sh ์ˆ˜์ • (RDS ์—ฐ๊ฒฐ)

# docker-compose.override.yml ์ƒ์„ฑ ๋ถ€๋ถ„์„ ์•„๋ž˜๋กœ ๊ต์ฒด
 
cat > /home/ec2-user/app/docker-compose.override.yml <<EOF
services:
  backend:
    environment:
      # RDS ์—ฐ๊ฒฐ ์ •๋ณด
      PROD_DB_URL: "jdbc:postgresql://${rds_endpoint}/${db_name}"
      PROD_DB_USERNAME: "${db_username}"
      PROD_DB_PASSWORD: "${db_password}"
      
      # ๊ธฐ์กด ์„ค์ •
      JWT_SECRET: "${jwt_secret}"
      CORS_ALLOWED_ORIGINS: "${cors_allowed_origins}"
      SPRING_PROFILES_ACTIVE: "prod"
EOF

main.tf EC2 user_data ๋ถ€๋ถ„ ์ˆ˜์ •

resource "aws_instance" "backend" {
  # ... ๊ธฐ์กด ์„ค์ • ์œ ์ง€ ...
  
  user_data = templatefile("user_data.sh", {
    jwt_secret           = var.jwt_secret
    cors_allowed_origins = "https://${aws_cloudfront_distribution.frontend.domain_name}"
    # RDS ๋ณ€์ˆ˜ ์ถ”๊ฐ€
    rds_endpoint = aws_db_instance.postgres.endpoint
    db_name      = var.db_name
    db_username  = var.db_username
    db_password  = var.db_password
  })
  
  # โš ๏ธ ์ค‘์š”: RDS๊ฐ€ ๋จผ์ € ์ƒ์„ฑ๋˜์–ด์•ผ EC2๊ฐ€ ์—ฐ๊ฒฐ ๊ฐ€๋Šฅ
  depends_on = [aws_db_instance.postgres]
}

4 Bastion Host ์ถ”๊ฐ€ (์„ ํƒ์‚ฌํ•ญ)

main.tf Security Group ์„น์…˜์— ์ถ”๊ฐ€

resource "aws_security_group" "bastion_sg" {
  vpc_id = aws_vpc.main.id
  name   = "${var.project_name}-bastion-sg"
 
  ingress {
    description = "SSH from my IP"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.my_ip]
  }
 
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
 
  tags = { Name = "${var.project_name}-bastion-sg" }
}

main.tf EC2 ์„น์…˜์— ์ถ”๊ฐ€

resource "aws_instance" "bastion" {
  ami                         = var.ec2_ami
  instance_type               = "t3.micro"
  subnet_id                   = aws_subnet.public_a.id
  vpc_security_group_ids      = [aws_security_group.bastion_sg.id]
  key_name                    = aws_key_pair.main_key.key_name
  associate_public_ip_address = true
 
  tags = { Name = "${var.project_name}-bastion" }
}

backend_sg SSH ๊ทœ์น™ ์ˆ˜์ • (Bastion๋งŒ ํ—ˆ์šฉ)

# ๊ธฐ์กด backend_sg์˜ SSH ingress๋ฅผ ์ด๋ ‡๊ฒŒ ๋ณ€๊ฒฝ
ingress {
  description     = "SSH from Bastion only"
  from_port       = 22
  to_port         = 22
  protocol        = "tcp"
  security_groups = [aws_security_group.bastion_sg.id]  # cidr_blocks ๋Œ€์‹ 
}

outputs.tf์— ์ถ”๊ฐ€

output "bastion_ip" {
  value = aws_instance.bastion.public_ip
}
 
output "ssh_to_backend" {
  description = "SSH via Bastion"
  value       = "ssh -J ec2-user@${aws_instance.bastion.public_ip} ec2-user@${aws_instance.backend.private_ip}"
}

๐Ÿš€ ์ ์šฉ ์ˆœ์„œ (์ค‘์š”!)

# 1. ๋ณ€์ˆ˜ ํ™•์ธ
terraform plan
 
# 2. Private Subnet๋งŒ ๋จผ์ € (๋น ๋ฆ„)
terraform apply -target=aws_subnet.private_a -target=aws_subnet.private_b
 
# 3. RDS ์ƒ์„ฑ (5~10๋ถ„ ์†Œ์š”)
terraform apply -target=aws_db_instance.postgres
 
# 4. ๋‚˜๋จธ์ง€ ์ „์ฒด
terraform apply

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ

ํ•ญ๋ชฉ์ฃผ์˜์ 
RDS ์ƒ์„ฑ ์‹œ๊ฐ„5~10๋ถ„ ๊ฑธ๋ฆผ, ๊ธฐ๋‹ค๋ ค์•ผ ํ•จ
db_passwordterraform.tfvars์— ์ž…๋ ฅ, ์ ˆ๋Œ€ ์ปค๋ฐ‹ ๊ธˆ์ง€
EC2 ๊ต์ฒดsubnet ๋ณ€๊ฒฝ ์‹œ EC2๊ฐ€ ์žฌ์ƒ์„ฑ๋จ โ†’ ๊ธฐ์กด ๋ฐ์ดํ„ฐ ๋‚ ์•„๊ฐ
๋น„์šฉRDS db.t3.micro๋Š” ํ”„๋ฆฌํ‹ฐ์–ด ์ ์šฉ ๊ฐ€๋Šฅ

๐Ÿ“‹ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โ–ก variables.tf์— private_subnet, db ๋ณ€์ˆ˜ ์ถ”๊ฐ€
โ–ก terraform.tfvars์— db_password ์ž…๋ ฅ
โ–ก main.tf์— Private Subnet ์ถ”๊ฐ€
โ–ก main.tf์— RDS Security Group ์ถ”๊ฐ€  
โ–ก main.tf์— RDS ๋ฆฌ์†Œ์Šค ์ถ”๊ฐ€
โ–ก user_data.sh์— RDS ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ถ”๊ฐ€
โ–ก outputs.tf์— rds_endpoint ์ถ”๊ฐ€
โ–ก terraform plan์œผ๋กœ ๋ณ€๊ฒฝ์‚ฌํ•ญ ํ™•์ธ
โ–ก terraform apply ์‹คํ–‰
โ–ก seed.sh๋กœ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ์ฃผ์ž