Automasi Infrastruktur Multi-Cloud Immutable Menggunakan Terraform dan Ansible di AWS
1. Konteks Industri & Analisis Akar Masalah
Di era komputasi modern, pengelolaan infrastruktur web yang bergantung pada Configuration Drift (penyimpangan konfigurasi) secara manual adalah bom waktu. Pendekatan tradisional di mana administrator sistem melakukan SSH langsung ke instans AWS EC2 untuk memperbarui paket, memodifikasi direktori /etc/nginx/nginx.conf, atau menambal modul PHP-FPM menciptakan lingkungan yang rapuh, tidak dapat direproduksi, dan rentan terhadap kegagalan senyap (silent failure).
Masalah mendasar ini memicu munculnya arsitektur infrastruktur immutable (tidak dapat diubah). Dalam paradigma immutable, instans server tidak pernah diperbarui secara in-place. Jika ada perubahan pada sistem operasi, dependensi runtime Go/Node.js, atau konfigurasi web server Nginx, instans lama dihancurkan dan diganti dengan instans baru yang dibangun dari golden image atau dipProvisioning ulang secara otomatis.
Sebelum penerapan automasi penuh menggunakan Terraform dan Ansible:
- Waktu Pemulihan (MTTR): Membutuhkan waktu berjam-jam untuk men-debug server yang rusak akibat pembaruan dependensi yang gagal di tengah malam.
- Konsistensi Lingkungan: Lingkungan staging sering kali berbeda dengan production, menyebabkan bug yang lolos ke produksi (works on my machine).
- Keamanan: Kunci SSH tersebar di antara banyak insinyur, meningkatkan permukaan serangan (attack surface) terhadap infrastruktur kritis.
Setelah penerapan automasi infrastruktur berbasis Terraform dan Ansible:
- Reproduksibilitas 100%: Seluruh topografi jaringan VPC, subnet, route table, hingga keamanan WAF dan SSL didefinisikan sebagai kode (Infrastructure as Code).
- Zero-Downtime Deployment: Pembaruan instans backend (PHP-FPM, Node.js, Go) dilakukan melalui mekanisme Blue-Green atau Rolling Update yang dikoordinasikan secara presisi.
- Auditabilitas Tinggi: Setiap perubahan infrastruktur tercatat dalam riwayat Git, memungkinkan rollback instan dalam hitungan detik jika terjadi anomali performa.
2. Konsep Inti & Deep Dive Arsitektur
Arsitektur yang kita bangun memadukan orkestrasi infrastruktur tingkat rendah (AWS VPC, Subnet, EC2, S3 Remote State) dengan manajemen konfigurasi tingkat tinggi (Ansible Playbook untuk instalasi Nginx, PHP-FPM, MySQL client, Redis, Supervisor, dan Fail2ban).
+-----------------------------------------------------------------------------------+
| AWS Cloud |
| |
| +-----------------------------------------------------------------------------+ |
| | VPC (10.0.0.0/16) | |
| | | |
| | +-----------------------+ +---------------------+ | |
| | | Public Subnet (AZ-a) | | Public Subnet (AZ-b)| | |
| | | | | | | |
| | | +-----------------+ | | +---------------+ | | |
| | | | Application | | <--- Dynamic Inventory | | Application | | | |
| | | | EC2 Instance | | <--- Ansible Provision | | EC2 Instance | | | |
| | | +-----------------+ | | +---------------+ | | |
| | +-----------------------+ +---------------------+ | |
| | ^ ^ | |
| | |--------------------+---------------------------| | |
| | | | |
| | v | |
| | +--------------------------+ | |
| | | Internet Gateway (IGW) | | |
| | +--------------------------+ | |
| | | |
| +-----------------------------------------------------------------------------+ |
+-----------------------------------------------------------------------------------+
Alur kerja eksekusi (Workflow Execution) sistem ini berjalan secara sekuensial namun terintegrasi:
- Inisialisasi Terraform: Terraform mengunduh provider AWS, membaca konfigurasi remote state dari Amazon S3, dan mengunci state menggunakan DynamoDB.
- Provisioning Infrastruktur: Terraform membuat VPC, Internet Gateway, Route Tables, Security Groups, dan meluncurkan instans EC2 menggunakan Cloud-Init dasar.
- Dynamic Inventory Discovery: Ansible memanfaatkan plugin AWS EC2 (
aws_ec2) untuk mendeteksi IP publik dan privat dari instans yang baru saja dibuat secara dinamis tanpa hardcode. - Konfigurasi Server: Ansible mengeksekusi playbooks untuk memasang Nginx, mengonfigurasi PHP-FPM / Go binary, menyetel izin direktori web, memasang Fail2ban, dan memastikan layanan sistem berjalan stabil.
3. Perbandingan Komparatif / Analisis Pendekatan
| Parameter Komparasi | Pendekatan Tradisional (Manual SSH + Bash Script) | Pendekatan Modern (Terraform + Ansible Immutable) | Dampak pada Sumber Daya |
|---|---|---|---|
| Waktu Provisioning | 45 - 90 menit per server | 3 - 5 menit otomatis penuh | Efisiensi CPU/RAM saat setup meningkat drastis. |
| Manajemen State | Tidak ada (manual tracking di spreadsheet) | Terpusat di AWS S3 dengan State Locking DynamoDB | Mencegah konflik race condition saat tim bekerja bersama. |
| Keamanan Akses | Sering menggunakan SSH Password & Shared Keys | Kunci SSH sementara (Ephemeral keys) / AWS SSM Session Manager | Mengurangi risiko kebocoran kredensial root. |
| Skalabilitas | Sulit dan rawan error saat skala > 10 server | Skalabilitas linear tanpa batas (Horizontal Scaling) | Beban latensi jaringan terdistribusi secara optimal. |
[!IMPORTANT] Jangan pernah menyimpan file
terraform.tfstatedi penyimpanan lokal komputer pengembang saat bekerja dalam tim. Gunakan Remote Backend S3 + DynamoDB untuk mencegah korupsi state dan memastikan konsistensi locking.
4. Prasyarat Sistem & Persiapan Lingkungan
Sebelum menjalankan kode di bawah ini, pastikan mesin lokal atau lingkungan CI/CD Anda memiliki dependensi berikut:
- Terraform CLI: Versi
>= 1.5.0 - Ansible: Versi
>= 2.15dengan koleksiamazon.awsterpasang. - AWS CLI v2: Terkonfigurasi dengan kredensial yang memiliki hak akses penuh ke layanan VPC, EC2, S3, dan IAM.
- Kunci SSH lokal: Berada di
~/.ssh/id_rsa.pubuntuk akses bootstrap awal.
Verifikasi instalasi dengan perintah berikut di terminal:
terraform version
ansible --version
aws sts get-caller-identity
5. Panduan Implementasi Langkah demi Langkah
Langkah 1: Konfigurasi Remote Backend & Provider Terraform
Buat struktur direktori proyek:
mkdir -p terraform-ansible-aws/{terraform/modules/vpc,terraform/modules/ec2,ansible/playbooks,ansible/inventory}
cd terraform-ansible-aws
Buat file terraform/backend.tf:
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "prod-infrastructure-state-bucket-unique-xyz"
key = "web-cluster/terraform.tfstate"
region = "ap-southeast-1"
dynamodb_table = "terraform-lock-table"
encrypt = true
}
}
provider "aws" {
region = var.aws_region
}
Langkah 2: Implementasi Modul VPC
Buat file terraform/modules/vpc/variables.tf:
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidrs" {
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
variable "availability_zones" {
type = list(string)
default = ["ap-southeast-1a", "ap-southeast-1b"]
}
Buat file terraform/modules/vpc/main.tf:
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "production-vpc"
}
}
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "production-igw"
}
}
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 = "production-public-subnet-${count.index + 1}"
}
}
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 = "production-public-rt"
}
}
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
}
Buat file terraform/modules/vpc/outputs.tf:
output "vpc_id" {
value = aws_vpc.main.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
Langkah 3: Implementasi Modul EC2 & Security Groups
Buat file terraform/modules/ec2/variables.tf:
variable "vpc_id" {
type = string
}
variable "public_subnet_ids" {
type = list(string)
}
variable "instance_type" {
type = string
default = "t3.medium"
}
variable "ssh_public_key" {
type = string
}
Buat file terraform/modules/ec2/main.tf:
resource "aws_security_group" "web_sg" {
name = "production-web-sg"
description = "Allow HTTP, HTTPS, and SSH"
vpc_id = var.vpc_id
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
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"]
}
tags = {
Name = "production-web-sg"
}
}
resource "aws_key_pair" "deployer" {
key_name = "production-deployer-key"
public_key = var.ssh_public_key
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "web" {
count = 2
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = var.public_subnet_ids[count.index % length(var.public_subnet_ids)]
vpc_security_group_ids = [aws_security_group.web_sg.id]
key_name = aws_key_pair.deployer.key_name
tags = {
Name = "production-web-node-${count.index + 1}"
Role = "webserver"
}
}
Buat file terraform/modules/ec2/outputs.tf:
output "web_instance_public_ips" {
value = aws_instance.web[*].public_ip
}
Langkah 4: Root Terraform Configuration
Buat file terraform/main.tf:
variable "aws_region" {
type = string
default = "ap-southeast-1"
}
variable "ssh_public_key" {
type = string
default = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD..." # Ganti dengan public key Anda
}
module "vpc" {
source = "./modules/vpc"
vpc_cidr = "10.100.0.0/16"
public_subnet_cidrs = ["10.100.1.0/24", "10.100.2.0/24"]
availability_zones = ["ap-southeast-1a", "ap-southeast-1b"]
}
module "ec2" {
source = "./modules/ec2"
vpc_id = module.vpc.vpc_id
public_subnet_ids = module.vpc.public_subnet_ids
instance_type = "t3.medium"
ssh_public_key = var.ssh_public_key
}
output "cluster_public_ips" {
value = module.ec2.web_instance_public_ips
}
Langkah 5: Konfigurasi Ansible Dynamic Inventory & Playbooks
Buat file ansible/inventory/aws_ec2.yml untuk integrasi otomatis inventaris AWS:
plugin: amazon.aws.aws_ec2
regions:
- ap-southeast-1
filters:
tag:Role: webserver
instance-state-name: running
keyed_groups:
- key: tags.Role
prefix: role
host_key_checking: false
Buat file ansible/playbooks/site.yml untuk konfigurasi penuh Nginx, PHP-FPM, dan keamanan server:
---
- hosts: role_webserver
become: yes
vars:
php_version: "8.2"
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install essential packages
apt:
name:
- curl
- git
- ufw
- fail2ban
- software-properties-common
state: present
- name: Add PPA for PHP
apt_repository:
repo: ppa:ondrej/php
state: present
- name: Install Nginx and PHP-FPM
apt:
name:
- nginx
- php{{ php_version }}-fpm
- php{{ php_version }}-cli
- php{{ php_version }}-mysql
- php{{ php_version }}-redis
- php{{ php_version }}-mbstring
- php{{ php_version }}-xml
- php{{ php_version }}-curl
state: present
- name: Start and enable Nginx
service:
name: nginx
state: started
enabled: yes
- name: Start and enable PHP-FPM
service:
name: php{{ php_version }}-fpm
state: started
enabled: yes
- name: Configure Nginx default virtual host
copy:
dest: "/etc/nginx/sites-available/default"
content: |
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.php index.html index.htm;
server_name _;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php{{ php_version }}-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}
notify: Restart Nginx
- name: Ensure web root directory exists
file:
path: /var/www/html
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Deploy index.php test file
copy:
dest: "/var/www/html/index.php"
content: "<?php echo '<h1>Infrastruktur Immutable Aktif - AWS Terraform Ansible</h1><p>PHP Version: ' . phpversion() . '</p>'; ?>"
owner: www-data
group: www-data
mode: '0644'
handlers:
- name: Restart Nginx
service:
name: nginx
state: restarted
[!TIP] Jalankan
ansible-inventory -i ansible/inventory/aws_ec2.yml --graphuntuk memverifikasi bahwa Ansible berhasil mendeteksi instans EC2 yang baru saja dibuat oleh Terraform melalui API AWS sebelum menjalankan playbook.
6. Bedah Kode & Penjelasan Detail Baris Kunci
enable_dns_hostnames = truepada VPC: Parameter ini krusial dalam arsitektur web modern agar instans EC2 mendapatkan nama DNS publik yang dapat diresolusi secara otomatis oleh AWS DNS Resolver. Tanpa ini, komunikasi internal antar-layanan berbasis nama host akan gagal.count.index % length(var.public_subnet_cidrs)pada EC2: Logika modulus ini memastikan bahwa jika Anda meluncurkan 5 instans web namun hanya memiliki 2 Availability Zone/Subnet, instans akan didistribusikan secara merata (round-robin) di antara subnet yang tersedia guna menjaga tinggi ketersediaan (High Availability).- Plugin
amazon.aws.aws_ec2di Ansible: Memungkinkan Ansible bertindak sebagai klien AWS API yang cerdas. Alih-alih merawat filehostsstatis yang mudah usang, Ansible melakukan query langsung ke tag AWS (tag:Role: webserver) secara real-time.
7. Skenario Produksi & Studi Kasus Dunia Nyata
Dalam skenario lalu lintas tinggi (high concurrency e-commerce flash sale), infrastruktur di atas harus diperluas dengan penambahan Application Load Balancer (ALB) di depan instans EC2 dan Auto Scaling Group (ASG).
Untuk mengintegrasikan Ansible dengan arsitektur Auto Scaling, pendekatan standar adalah membangun Golden AMI menggunakan Packer sebelum Terraform dijalankan. Packer akan menjalankan skrip Ansible di atas AMI kosong, membuat snapshot AMI baru, dan Terraform kemudian meluncurkan instans menggunakan AMI siap pakai tersebut tanpa perlu menunggu Ansible melakukan instalasi saat booting server.
8. Common Pitfalls, Anti-Patterns & Solusi
- Anti-Pattern 1: Hardcoding IP Address di Ansible Inventory.
- Solusi: Selalu gunakan dynamic inventory berbasis tag AWS seperti yang dicontohkan di atas agar skalabilitas horizontal tidak terhalang oleh perubahan IP instans.
- Anti-Pattern 2: Melakukan
terraform destroytanpa mencadangkan database eksternal.- Solusi: Gunakan atribut
lifecycle { prevent_destroy = true }pada sumber daya penyimpanan kritis seperti database RDS atau S3 bucket utama untuk mencegah penghapusan yang tidak sengaja.
- Solusi: Gunakan atribut
- Pitfalls 3: Timeout SSH saat Ansible pertama kali berjalan.
- Solusi: Tambahkan jeda waktu tunggu (sleep) atau blok tunggu koneksi SSH di Cloud-Init agar daemon SSH benar-benar siap menerima koneksi sebelum Ansible mulai mengeksekusi tugas.
9. Benchmark, Metrik Kinerja & Tuning Performa
Untuk menguji performa server web Nginx + PHP-FPM yang baru diprovisioning, jalankan benchmark menggunakan tool wrk dari mesin penguji terpisah:
wrk -t12 -c400 -d30s http://<AWS_EC2_PUBLIC_IP>/
Parameter Tuning Lanjutan pada /etc/sysctl.conf untuk menangani 10.000+ koneksi konkuren (C10K problem):
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 65535
Terapkan perubahan ini menggunakan perintah:
sudo sysctl -p
10. Checklist Penerapan Produksi & Kesimpulan
Sebelum meluncurkan sistem ke lingkungan produksi publik, pastikan checklist berikut terpenuhi:
- State Terraform disimpan di S3 dengan enkripsi KMS dan penguncian DynamoDB diaktifkan.
- Kunci SSH akses root dinonaktifkan di
/etc/ssh/sshd_config, digantikan dengan autentikasi berbasis kunci publik yang aman. - Aturan Security Group AWS membatasi port SSH (22) hanya untuk rentang IP kantor atau Bastion Host VPN internal.
- Fail2ban dikonfigurasi untuk memblokir alamat IP penyerang setelah 3 kali kegagalan autentikasi SSH atau HTTP.
- Sertifikat SSL/TLS HTTPS terpasang melalui Let's Encrypt dengan pembaruan otomatis via Cron.
Dengan memadukan ketepatan deklaratif Terraform dan fleksibilitas konfigurasi prosedural Ansible, Anda telah membangun fondasi infrastruktur web enterprise yang tahan banting, aman, dan siap menghadapi lonjakan traffic ekstrem tanpa rasa khawatir.