L
Infrastructure as Code (IaC) β€’ Literally Web ID

Automated Infrastructure Provisioning Menggunakan Terraform di AWS

⏱️ 15 menit baca β€’ πŸ“… 19 Oct 2026 β€’ πŸ‘οΈ 5 views
Automated Infrastructure Provisioning Menggunakan Terraform di AWS

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap operasional sistem berbasis komputasi awan modern, manajemen infrastruktur manual melalui GUI web console (ClickOps) telah menjadi salah satu sumber kegagalan sistem paling kritis di level enterprise. Pendekatan ad-hoc ini memicu apa yang dikenal di industri sebagai Configuration Driftβ€”kondisi di mana konfigurasi riil infrastruktur produksi berdeviasi secara substansial dari dokumentasi, diagram arsitektur, atau environment staging.

Bottleneck operasional yang sering ditemui meliputi:

  1. Human Error pada Security Layer: Terbukanya port sensitif (seperti MySQL 3306 atau Redis 6379) ke subnet publik (0.0.0.0/0) akibat ketidaktelitian konfigurasi Security Group secara manual di AWS Management Console.
  2. Snowflake Servers: Server yang dibangun secara manual dengan dependensi paket sistem operasi yang tidak terdokumentasi, membuat proses disaster recovery memakan waktu berjam-jam bahkan berhari-hari karena konfigurasi sistem tidak dapat direproduksi secara deterministik.
  3. Ketiadaan Audit Trail dan Version Control: Tidak tersedianya riwayat perubahan konfigurasi jaringan, siapa yang mengubah nilai MTU, merotasi route table, atau menurunkan kapasitas alokasi IOPS pada EBS storage.
Aspek Evaluasi Sebelum: Manual/ClickOps & Ad-hoc Bash Sesudah: Declarative IaC (Terraform)
Reproducibility Nol; konfigurasi bergantung pada catatan manual dan ingatan engineer. Deterministik 100%; seluruh infrastruktur didefinisikan dalam kode tervirtualisasi.
Auditability & Compliance Audit log terdistribusi pada AWS CloudTrail tanpa representasi state utuh. Riwayat Git commit mencatat setiap perubahan, reviewer pull request, dan riwayat rollback.
Disaster Recovery (RTO) Jam hingga hari untuk membangun ulang seluruh topologi VPC dan sub-layanan. Menit; dieksekusi via pipeline otomatis (terraform apply).
State Verification Manual checking via CLI/Console yang rawan terlewat. Deteksi deviasi instan melalui terraform plan berbasis cryptographic hash.
Environment Parity Dev, Staging, dan Prod memiliki deviasi konfigurasi tersembunyi. Identik; menggunakan modul kode yang sama dengan injeksi variabel parameter terisolasi.

Pendekatan Declarative Infrastructure as Code (IaC) melalui Terraform mengubah paradigma rekayasa infrastruktur dari instruksional imperatif ("jalankan script X lalu restart service Y") menjadi penetapan target akhir kondisi sistem ("sediakan arsitektur dengan spesifikasi Z, dan biarkan engine mengalkulasi langkah transisinya").


2. Konsep Inti & Deep Dive Arsitektur

Cara Kerja di Balik Layar: The Declarative Engine

Terraform tidak bekerja layaknya shell script linier. Ketika berkas berekstensi .tf dievaluasi, Terraform Core membangun sebuah Directed Acyclic Graph (DAG). Node dalam graph merepresentasikan resource (misalnya aws_vpc, aws_subnet, aws_instance), sementara edge merepresentasikan dependensi antar-resource (misalnya subnet bergantung pada ketersediaan VPC ID).

+-------------------------------------------------------------+
|                        Terraform Core                       |
|  +--------------------+             +--------------------+  |
|  | HCL Configuration  | ----------> | Dependency Graph   |  |
|  +--------------------+             |       (DAG)        |  |
|                                     +--------------------+  |
|                                                |            |
|                                                v            |
|  +--------------------+             +--------------------+  |
|  | Current State File | ----------> | State Comparator   |  |
|  |   (terraform.tfstate)            |    & Delta Plan    |  |
|  +--------------------+             +--------------------+  |
+-------------------------------------------------------------+
                                 |
             [RPC Calls via Terraform Plugin Protocol]
                                 v
+-------------------------------------------------------------+
|                      AWS Provider Engine                    |
|  +--------------------+             +--------------------+  |
|  |   EC2 / VPC API    |             |   RDS / S3 API     |  |
|  +--------------------+             +--------------------+  |
+-------------------------------------------------------------+
                                 |
                      [HTTPS AWS REST Endpoints]
                                 v
+-------------------------------------------------------------+
|                       AWS Cloud Edge                        |
+-------------------------------------------------------------+

Siklus hidup eksekusi terdiri atas 4 fase mendasar:

  1. terraform init: Mengunduh provider plugin (AWS provider binary) dan menginisialisasi remote backend backend S3/DynamoDB.
  2. terraform refresh: Mengirimkan query API ke AWS untuk mencocokkan status riil resource saat ini dengan metadata yang tersimpan di state file.
  3. terraform plan: Membandingkan konfigurasi HCL lokal dengan kondisi state file terkini. Graph engine mengalkulasi delta (apa yang harus dibuat +, diubah ~, atau dimusnahkan -) tanpa mengeksekusi mutasi pada cloud.
  4. terraform apply: Mengonversi plan menjadi panggilan API concurrent (secara paralel berdasarkan tree dependensi) menuju target endpoint AWS.

Desain Arsitektur Jaringan Produksi

Untuk meng-host stack web server (Nginx reverse proxy + Node.js/PHP-FPM backend) dan database engine (MySQL RDS) secara aman, arsitektur harus membagi jaringan menjadi 3 tingkatan (tiers) di minimal 2 Availability Zones (AZ):

                         Internet Traffic
                                |
                                v
                 +-----------------------------+
                 |     Internet Gateway (IGW)  |
                 +-----------------------------+
                                |
================================|================================ [VPC: 10.0.0.0/16]
                                v
    +-------------------------------------------------------+
    | [PUBLIC TIER]                                         |
    |                                                       |
    |   AZ-a: Public Subnet (10.0.1.0/24)                   |
    |   +-----------------------------------------------+   |
    |   | Application Load Balancer (ALB)               |   |
    |   | NAT Gateway AZ-a                              |   |
    |   +-----------------------------------------------+   |
    |                                                       |
    |   AZ-b: Public Subnet (10.0.2.0/24)                   |
    |   +-----------------------------------------------+   |
    |   | Application Load Balancer (ALB Node 2)        |   |
    |   | NAT Gateway AZ-b                              |   |
    |   +-----------------------------------------------+   |
    +-------------------------------------------------------+
                                |
           +--------------------+--------------------+
           |                                         |
           v                                         v
    +-------------------------------------------------------+
    | [APPLICATION TIER - PRIVATE SUBNETS]                  |
    | Route: 0.0.0.0/0 -> NAT Gateway (Egress Only)         |
    |                                                       |
    |   AZ-a: Private App Subnet (10.0.10.0/24)             |
    |   +-----------------------------------------------+   |
    |   | EC2: Nginx + PHP-FPM / Node.js Engine         |   |
    |   +-----------------------------------------------+   |
    |                                                       |
    |   AZ-b: Private App Subnet (10.0.20.0/24)             |
    |   +-----------------------------------------------+   |
    |   | EC2: Nginx + PHP-FPM / Node.js Engine         |   |
    |   +-----------------------------------------------+   |
    +-------------------------------------------------------+
                                |
           +--------------------+--------------------+
           |                                         |
           v                                         v
    +-------------------------------------------------------+
    | [DATABASE TIER - ISOLATED PRIVATE SUBNETS]            |
    | Route: No Internet / No NAT Gateway Route             |
    |                                                       |
    |   AZ-a: Private DB Subnet (10.0.100.0/24)             |
    |   +-----------------------------------------------+   |
    |   | RDS MySQL Primary (Port 3306)                 |   |
    |   +-----------------------------------------------+   |
    |                                                       |
    |   AZ-b: Private DB Subnet (10.0.200.0/24)             |
    |   +-----------------------------------------------+   |
    |   | RDS MySQL Standby / Read-Replica              |   |
    |   +-----------------------------------------------+   |
    +-------------------------------------------------------+
=================================================================

3. Perbandingan Komparatif Pendekatan IaC

Memilih tool IaC harus disesuaikan dengan kapabilitas tim, batas toleransi risiko vendor lock-in, dan kompleksitas ekosistem target.

Parameter Evaluasi HashiCorp Terraform AWS CloudFormation / CDK Pulumi Ansible (Cloud Modules)
Paradigma Declarative Declarative (CFN) / Imperative (CDK) Imperative / Object-Oriented Procedural / Idempotent Imperative
Bahasa Konfigurasi HCL (HashiCorp Config Lang) JSON/YAML (CFN) atau TS/Python/Go (CDK) TypeScript, Python, Go, C# YAML Playbooks
State Management External Explicit (.tfstate) via S3/GCS/Consul Managed Implicit via AWS Internal Engine Managed Cloud Service / Custom Backend Stateless (Mengharuskan query langsung ke target host)
Multi-Cloud Readiness Luas; provider resmi untuk AWS, GCP, Azure, Cloudflare Terkunci pada ekosistem AWS Luas; multi-cloud native Luas, tetapi kurang efisien untuk resource lifecycle cloud
Dependency Resolution Otomatis via Dynamic Directed Acyclic Graph (DAG) Otomatis via internal CFN engine Otomatis via promise/async tree runtime Sekuensial deklaratif manual via playbook order
Ecosystem & Community Standar defacto industri IaC global Sangat kuat di komunitas AWS Sedang, terus bertumbuh Sangat kuat di ranah OS Config Management

[!IMPORTANT] Terraform sangat unggul dalam memprovisi infrastructure fabric (jaringan, firewall, database instans, DNS, CDN). Namun, untuk configuration management level OS (patching security Linux, kompilasi modul Apache/Nginx kompleks, konfigurasi Postfix/Mail server), pola terbaik enterprise adalah memadukan Terraform (memprovisi VM dan base image) dengan Packer (golden AMI) atau Ansible (post-boot configuration).


4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum menulis modul infrastruktur, persiapkan dependensi lokal dan autentikasi kredensial AWS berikut:

1. Spesifikasi Binary & Tools

  • Terraform CLI: Versi 1.7.0 atau yang lebih baru.
  • AWS CLI: Versi 2.15.0 atau yang lebih baru.
  • OpenSSL & SSH-Keygen: Untuk pembuatan keypair administratif darurat.

Verifikasi instalasi di terminal:

terraform version
# Terraform v1.7.5
# on linux_amd64

aws --version
# aws-cli/2.15.15 Python/3.11.6 Linux/6.5.0-generic

2. Konfigurasi IAM Principle of Least Privilege

Pastikan entitas eksekutor (IAM User atau IAM Role CI/CD) memiliki policy yang mengizinkan manipulasi VPC, EC2, RDS, S3, dan DynamoDB. Hindari penggunaan policy AdministratorAccess pada runner CI/CD produksi.

3. Bootstrap Remote State Storage & Locking Engine

Jangan gunakan lokal file untuk menyimpan state. Eksekusi script bootstrap berikut sekali saja via AWS CLI untuk menyiapkan S3 bucket terenkripsi dan tabel penguncian DynamoDB:

# Variabel Penamaan
export AWS_REGION="ap-southeast-1"
export STATE_BUCKET_NAME="enterprise-tfstate-prod-$(openssl rand -hex 4)"
export LOCK_TABLE_NAME="terraform-lock-table-prod"

# 1. Buat S3 Bucket untuk State Storage
aws s3api create-bucket \
    --bucket "${STATE_BUCKET_NAME}" \
    --region "${AWS_REGION}" \
    --create-bucket-configuration LocationConstraint="${AWS_REGION}"

# 2. Wajibkan Enkripsi AES256 pada Bucket
aws s3api put-bucket-encryption \
    --bucket "${STATE_BUCKET_NAME}" \
    --server-side-encryption-configuration '{
        "Rules": [{
            "ApplyServerSideEncryptionByDefault": {
                "SSEAlgorithm": "AES256"
            }
        }]
    }'

# 3. Aktifkan Versioning Bucket untuk Rollback State jika Terjadi Kerusakan Data
aws s3api put-bucket-versioning \
    --bucket "${STATE_BUCKET_NAME}" \
    --versioning-configuration Status=Enabled

# 4. Blokir Total Akses Publik S3
aws s3api put-public-access-block \
    --bucket "${STATE_BUCKET_NAME}" \
    --public-access-block-configuration '{
        "BlockPublicAcls": true,
        "IgnorePublicAcls": true,
        "BlockPublicPolicy": true,
        "RestrictPublicBuckets": true
    }'

# 5. Buat DynamoDB Table untuk State Concurrency Locking (Kunci: LockID string)
aws dynamodb create-table \
    --table-name "${LOCK_TABLE_NAME}" \
    --attribute-definitions AttributeName=LockID,AttributeType=S \
    --key-schema AttributeName=LockID,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST \
    --region "${AWS_REGION}"

5. Panduan Implementasi Langkah demi Langkah

Struktur direktori modular yang akan kita bangun mengikuti standar korporat:

terraform-aws-web-stack/
β”œβ”€β”€ backend.tf
β”œβ”€β”€ providers.tf
β”œβ”€β”€ variables.tf
β”œβ”€β”€ outputs.tf
β”œβ”€β”€ vpc.tf
β”œβ”€β”€ security_groups.tf
β”œβ”€β”€ compute.tf
β”œβ”€β”€ database.tf
└── terraform.tfvars

File 1: backend.tf

Menghubungkan Terraform core ke S3 remote backend dan DynamoDB state lock.

terraform {
  required_version = ">= 1.7.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.40.0"
    }
  }

  backend "s3" {
    bucket         = "enterprise-tfstate-prod-d3a9b1c2" # Ganti sesuai nama bucket bootstrap Anda
    key            = "production/web-infrastructure/terraform.tfstate"
    region         = "ap-southeast-1"
    dynamodb_table = "terraform-lock-table-prod"
    encrypt        = true
  }
}

File 2: providers.tf

Mendefinisikan provider AWS dan default resource tags untuk kepatuhan tata kelola biaya (FinOps).

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      Environment = var.environment
      ManagedBy   = "Terraform"
      Project     = var.project_name
      Owner       = "DevOps Infrastructure Team"
    }
  }
}

File 3: variables.tf

Mendefinisikan skema variabel beserta aturan validasi nilai input.

variable "aws_region" {
  type        = string
  description = "Region AWS target deployment."
  default     = "ap-southeast-1"
}

variable "environment" {
  type        = string
  description = "Target environment (prod/staging/dev)."
  default     = "prod"

  validation {
    condition     = contains(["prod", "staging", "dev"], var.environment)
    error_message = "Environment harus salah satu dari: prod, staging, atau dev."
  }
}

variable "project_name" {
  type        = string
  description = "Nama namespace proyek untuk identifikasi tag dan prefix resource."
  default     = "core-enterprise"
}

variable "vpc_cidr" {
  type        = string
  description = "Blok CIDR untuk VPC utama."
  default     = "10.0.0.0/16"
}

variable "availability_zones" {
  type        = list(string)
  description = "Daftar Availability Zones yang dialokasikan."
  default     = ["ap-southeast-1a", "ap-southeast-1b"]
}

variable "public_subnet_cidrs" {
  type        = list(string)
  description = "Alokasi CIDR untuk public subnets."
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "private_app_subnet_cidrs" {
  type        = list(string)
  description = "Alokasi CIDR untuk private web application subnets."
  default     = ["10.0.10.0/24", "10.0.20.0/24"]
}

variable "private_db_subnet_cidrs" {
  type        = list(string)
  description = "Alokasi CIDR untuk isolated private database subnets."
  default     = ["10.0.100.0/24", "10.0.200.0/24"]
}

variable "ec2_instance_type" {
  type        = string
  description = "Tipe instans EC2 untuk application server worker."
  default     = "t3.medium"
}

variable "db_allocated_storage" {
  type        = number
  description = "Ukuran storage awal database RDS MySQL (GB)."
  default     = 50
}

variable "db_max_allocated_storage" {
  type        = number
  description = "Batas maksimal autoscaling storage database RDS MySQL (GB)."
  default     = 200
}

variable "db_instance_class" {
  type        = string
  description = "Kelas instans komputasi RDS database."
  default     = "db.t3.medium"
}

variable "db_name" {
  type        = string
  description = "Nama default database engine MySQL."
  default     = "enterprisedb"
}

variable "db_username" {
  type        = string
  description = "Root administrative username untuk RDS MySQL."
  default     = "appadmin"
  sensitive   = true
}

variable "db_password" {
  type        = string
  description = "Root administrative password untuk RDS MySQL."
  sensitive   = true
}

File 4: vpc.tf

Menyusun isolasi topologi Virtual Private Cloud, Subnetting 3-Tier, Route Tables, Internet Gateway, dan Egress NAT Gateways.

# 1. Definisi VPC Utama
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "${var.project_name}-${var.environment}-vpc"
  }
}

# 2. Internet Gateway untuk Ingress & Egress Publik
resource "aws_internet_gateway" "gw" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.project_name}-${var.environment}-igw"
  }
}

# 3. Public Subnets (ALB & NAT Gateways)
resource "aws_subnet" "public" {
  count                   = length(var.public_subnet_cidrs)
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.public_subnet_cidrs[count.index]
  availability_zone       = var.availability_zones[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.project_name}-${var.environment}-public-sub-${var.availability_zones[count.index]}"
    Tier = "Public"
  }
}

# 4. Elastic IP untuk Setiap NAT Gateway (Multi-AZ Resiliency)
resource "aws_eip" "nat" {
  count  = length(var.public_subnet_cidrs)
  domain = "vpc"

  tags = {
    Name = "${var.project_name}-${var.environment}-nat-eip-${var.availability_zones[count.index]}"
  }
}

# 5. NAT Gateways (Satu di Setiap AZ untuk Mencegah Single Point of Failure)
resource "aws_nat_gateway" "nat" {
  count         = length(var.public_subnet_cidrs)
  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id

  tags = {
    Name = "${var.project_name}-${var.environment}-nat-gw-${var.availability_zones[count.index]}"
  }

  depends_on = [aws_internet_gateway.gw]
}

# 6. Private Application Subnets (EC2 Web / App Servers)
resource "aws_subnet" "private_app" {
  count             = length(var.private_app_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_app_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "${var.project_name}-${var.environment}-private-app-sub-${var.availability_zones[count.index]}"
    Tier = "PrivateApp"
  }
}

# 7. Private Database Subnets (RDS MySQL / Redis - Isolasi Tanpa Egress Internet)
resource "aws_subnet" "private_db" {
  count             = length(var.private_db_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_db_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "${var.project_name}-${var.environment}-private-db-sub-${var.availability_zones[count.index]}"
    Tier = "PrivateDatabase"
  }
}

# 8. Route Table untuk Public Subnet
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.gw.id
  }

  tags = {
    Name = "${var.project_name}-${var.environment}-public-rt"
  }
}

# 9. Asosiasi Route Table Public
resource "aws_route_table_association" "public" {
  count          = length(var.public_subnet_cidrs)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

# 10. Route Tables untuk Private App Subnets (Mengarah ke NAT Gateway Lokal per AZ)
resource "aws_route_table" "private_app" {
  count  = length(var.private_app_subnet_cidrs)
  vpc_id = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat[count.index].id
  }

  tags = {
    Name = "${var.project_name}-${var.environment}-priv-app-rt-${var.availability_zones[count.index]}"
  }
}

# 11. Asosiasi Route Table Private App
resource "aws_route_table_association" "private_app" {
  count          = length(var.private_app_subnet_cidrs)
  subnet_id      = aws_subnet.private_app[count.index].id
  route_table_id = aws_route_table.private_app[count.index].id
}

# 12. Route Table untuk Database (Isolated, hanya rute lokal VPC)
resource "aws_route_table" "private_db" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.project_name}-${var.environment}-priv-db-rt"
  }
}

# 13. Asosiasi Route Table Private DB
resource "aws_route_table_association" "private_db" {
  count          = length(var.private_db_subnet_cidrs)
  subnet_id      = aws_subnet.private_db[count.index].id
  route_table_id = aws_route_table.private_db.id
}

File 5: security_groups.tf

Menyusun aturan firewall software secara berjenjang (chained security groups).

# 1. Security Group untuk External Application Load Balancer
resource "aws_security_group" "alb" {
  name        = "${var.project_name}-${var.environment}-alb-sg"
  description = "Mengizinkan ingress HTTP/HTTPS publik dan egress terbatas ke app server."
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "Inbound HTTP dari public"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "Inbound HTTPS TLS dari public"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    description = "Mengizinkan outbound traffic sepenuhnya"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project_name}-${var.environment}-alb-sg"
  }
}

# 2. Security Group untuk Web Application Servers (EC2)
resource "aws_security_group" "web_app" {
  name        = "${var.project_name}-${var.environment}-web-app-sg"
  description = "Membatasi akses: HANYA menerima trafik masuk dari ALB Security Group."
  vpc_id      = aws_vpc.main.id

  ingress {
    description     = "Reverse Proxy traffic dari ALB pada port 80"
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.alb.id]
  }

  egress {
    description = "Outbound ke NAT Gateway untuk package update dan API calls"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project_name}-${var.environment}-web-app-sg"
  }
}

# 3. Security Group untuk Database Layer (RDS MySQL)
resource "aws_security_group" "db" {
  name        = "${var.project_name}-${var.environment}-db-sg"
  description = "HANYA mengizinkan koneksi MySQL dari EC2 Web App Security Group."
  vpc_id      = aws_vpc.main.id

  ingress {
    description     = "Koneksi MySQL port 3306 eksklusif dari application instances"
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.web_app.id]
  }

  egress {
    description = "Blokir outbound inisiasi dari database; zero egress requirement"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["127.0.0.1/32"]
  }

  tags = {
    Name = "${var.project_name}-${var.environment}-db-sg"
  }
}

File 6: compute.tf

Menyiapkan Application Load Balancer, Launch Template, dan Auto Scaling Group yang mengonfigurasi stack web server (Nginx + runtime) secara otomatis via cloud-init userdata.

# 1. Mengambil AMI Ubuntu 22.04 LTS Resmi Terbaru
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical Official Owner ID

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

# 2. IAM Role untuk SSM Agent (Menghilangkan Kebutuhan Port SSH 22 Terbuka)
resource "aws_iam_role" "app_ec2_role" {
  name = "${var.project_name}-${var.environment}-ec2-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action    = "sts:AssumeRole"
      Effect    = "Allow"
      Principal = { Service = "ec2.amazonaws.com\
🏷️ Tags: #Nginx #High Concurrency #Performance #Sysadmin #Web Server