🏠 Home
Home > Terraform
Slide 1 of 15

⚙️ Belajar Terraform

Infrastructure as Code untuk Semua Orang

Learn by Doing

Apa itu Terraform?

Terraform adalah tools Infrastructure as Code (IaC) dari HashiCorp yang memungkinkan kamu mendefinisikan infrastruktur dalam file konfigurasi.

Konsep Dasar IaC:

  • Tulis kode → Definisi infrastruktur dalam file
  • Plan → Review perubahan sebelum eksekusi
  • Apply → Buat/modifikasi infrastruktur
  • Idempotent → Hasil sama walau di-run berkali-kali

Terraform vs Tools Lain:

Tool Language Cloud
Terraform HCL (HashiCorp Configuration Language) Multi-cloud (AWS, GCP, Azure, Cloudflare, dll)
AWS CloudFormation JSON/YAML AWS only
Pulumi TypeScript, Python, Go, C# Multi-cloud
Ansible YAML Config management + IaC

Instalasi Terraform

Terraform berjalan sebagai binary tunggal, tidak perlu instalasi kompleks.

Download Manual (Linux):

# Download binary
wget https://releases.hashicorp.com/terraform/1.10.0/terraform_1.10.0_linux_amd64.zip

# Extract
unzip terraform_1.10.0_linux_amd64.zip

# Move ke PATH
sudo mv terraform /usr/local/bin/

# Verify
terraform version

Verify dengan GPG (Recommended):

# Download release signatures
wget https://releases.hashicorp.com/terraform/1.10.0/terraform_1.10.0_SHA256SUMS
wget https://releases.hashicorp.com/terraform/1.10.0/terraform_1.10.0_SHA256SUMS.sig

# Import HashiCorp GPG key
gpg --recv-keys 51852D87348FFC4C

# Verify signature
gpg --verify terraform_1.10.0_SHA256SUMS.sig terraform_1.10.0_SHA256SUMS

# Check checksum
sha256sum -c terraform_1.10.0_SHA256SUMS 2>&1 | grep terraform
Tips: Gunakan tfenv atau tenv untuk manage multiple Terraform versions

Providers

Providers adalah plugin yang menghubungkan Terraform ke layanan/infrastruktur target.

Provider memiliki resource types yang bisa di-manage. Contoh: aws_instance, cloudflare_zone, digitalocean_droplet

Provider Block:

# AWS Provider
provider "aws" {
  region = "ap-southeast-1"
  access_key = "AKIA..."        # atau via environment/AWS_PROFILE
  secret_key = "..."
}

# Cloudflare Provider
provider "cloudflare" {
  api_token = "your-api-token"
}

# DigitalOcean Provider
provider "digitalocean" {
  token = "your-token"
}

Provider Versions (Penting untuk Production):

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4.0"
    }
  }
}
Terraform Registry: Cari provider di registry.terraform.io - ada 1000+ providers!

Resources (Part 1)

Resource adalah block paling penting di Terraform. Represents infrastructure object.

Sintaks Dasar:

resource "type" "name" {
  # configuration
}

Contoh: S3 Bucket di AWS:

resource "aws_s3_bucket" "website" {
  bucket = "my-website-assets-2024"

  tags = {
    Name        = "My Website Assets"
    Environment = "production"
    ManagedBy   = "Terraform"
  }
}

resource "aws_s3_bucket_website_configuration" "website" {
  bucket = aws_s3_bucket.website.id

  index_document {
    suffix = "index.html"
  }

  error_document {
    key = "error.html"
  }
}

Referencing Resources:

# Format: resource_type.resource_name.attribute
aws_s3_bucket.website.bucket
aws_s3_bucket.website.id
Convention: Gunakan nama deskriptif, misal aws_s3_bucket" "assets" bukan "bucket"

Resources (Part 2)

Resource Dependencies:

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id  # explicit dependency
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true
}

Count Meta-Argument:

resource "aws_instance" "server" {
  count = 3

  ami           = "ami-12345678"
  instance_type = "t3.micro"

  tags = {
    Name = "Server ${count.index}"
  }
}

# Referencing: aws_instance.server[0], aws_instance.server[1], dll

for_each Meta-Argument:

variable "users" {
  type = map(object({
    email = string
    role  = string
  }))
  default = {
    alice = { email = "[email protected]", role = "admin" }
    bob   = { email = "[email protected]", role = "developer" }
  }
}

resource "aws_iam_user" "users" {
  for_each = var.users
  name     = each.key
  tags     = {
    Email = each.value.email
    Role  = each.value.role
  }
}
Best Practice: Gunakan for_each daripada count jika value tidak ordinal (tidak berbasis index)

Variables

Variables membuat konfigurasi reusable dan fleksibel.

Variable Types:

variable "region" {
  type        = string
  description = "AWS region"
  default     = "ap-southeast-1"
}

variable "instance_count" {
  type        = number
  description = "Jumlah instance"
  default     = 3
}

variable "enable_monitoring" {
  type        = bool
  description = "Aktifkan CloudWatch monitoring"
  default     = true
}

variable "availability_zones" {
  type    = list(string)
  default = ["ap-southeast-1a", "ap-southeast-1b"]
}

variable "tags" {
  type    = map(string)
  default = {
    Environment = "production"
    ManagedBy   = "Terraform"
  }
}

Variable Validation (Terraform 0.13+):

variable "environment" {
  type    = string
  default = "production"

  validation {
    condition     = contains(["dev", "staging", "production"], var.environment)
    error_message = "Environment harus dev, staging, atau production."
  }
}

.tfvars Files:

# terraform.tfvars (auto-loaded)
region = "ap-southeast-1"
environment = "production"

# atau manual: terraform apply -var-file="production.tfvars"

Output Values

Output menampilkan nilai setelah apply, berguna untuk integrasi dengan tools lain.

Sintaks:

output "name" {
  description = "Deskripsi output"
  value       = expression
  sensitive   = true/false
}

Contoh:

output "bucket_name" {
  description = "Nama S3 bucket"
  value       = aws_s3_bucket.website.bucket
}

output "bucket_arn" {
  description = "ARN S3 bucket"
  value       = aws_s3_bucket.website.arn
}

output "instance_ips" {
  description = "IP addresses dari semua instance"
  value       = [for inst in aws_instance.server : inst.private_ip]
}

output "db_password" {
  description = "Database password"
  value       = aws_db_instance.db.password
  sensitive   = true  # tidak akan ditampilkan di terminal
}

Menampilkan Output:

# Setelah apply
terraform output
terraform output bucket_name
terraform output -raw bucket_name  # raw string tanpa quotes

# Di file lain (remote state)
data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "my-terraform-state"
    key    = "network/terraform.tfstate"
  }
}

# Gunakan output
subnet_id = data.terraform_remote_state.network.outputs.subnet_id

Terraform State

State adalah file yang menyimpan mapping antara konfigurasi dan real infrastructure.

Peringatan: State berisi data sensitif! Lindungi selalu file ini.

Local vs Remote State:

# Local State (default, disimpan di terraform.tfstate)
terraform {
  backend "local" {
    path = "terraform.tfstate"
  }
}

# Remote State dengan S3 + DynamoDB (Production)
terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "prod/terraform.tfstate"
    region         = "ap-southeast-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

Kenapa Remote State?

State Locking:

# DynamoDB table untuk state locking
resource "aws_dynamodb_table" "terraform_locks" {
  name         = "terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}
Best Practice: Selalu gunakan remote state dengan locking untuk production. Local state hanya untuk development/testing.

Modules

Modules adalah container untuk multiple resources yang digunakan bersama.

Mengapa Modules?

Struktur Module:

modules/
└── networking/
    ├── main.tf      # resources
    ├── variables.tf # input variables
    ├── outputs.tf   # output values
    └── versions.tf  # provider versions

Memanggil Module:

module "vpc" {
  source = "./modules/networking"

  name       = "my-vpc"
  cidr_block = "10.0.0.0/16"
  enable_nat = true
}

# Referencing module outputs
module.vpc.private_subnet_ids

Terraform Registry (Public Modules):

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["ap-southeast-1a", "ap-southeast-1b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.11.0/24", "10.0.12.0/24"]
}
Tips: Mulai dengan module sederhana, evolusi seiring kebutuhan. Jangan over-engineer di awal.

Real-world: Static Site dengan Cloudflare DNS

Contoh lengkap: Deploy static site dengan Caddy server + Cloudflare DNS.

Struktur Project:

static-site/
├── main.tf
├── variables.tf
├── outputs.tf
├── .gitignore
└── README.md

main.tf (Complete Example):

terraform {
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4.0"
    }
  }
}

provider "cloudflare" {
  api_token = var.cloudflare_api_token
}

# Zone/Domain configuration
resource "cloudflare_zone" "main" {
  zone = var.domain
}

# DNS A Record - point ke server
resource "cloudflare_record" "www" {
  zone_id = cloudflare_zone.main.id
  name    = "www"
  value   = var.server_ip
  type    = "A"
  proxied = true  # Cloudflare proxy (CDN + protection)
}

# DNS A Record - root domain
resource "cloudflare_record" "root" {
  zone_id = cloudflare_zone.main.id
  name    = "@"
  value   = var.server_ip
  type    = "A"
  proxied = true
}

# Caddy Server configuration (untuk provisioning server)
resource "null_resource" "caddy_setup" {
  connection {
    type        = "ssh"
    user        = "ubuntu"
    host        = var.server_ip
    private_key = var.ssh_private_key
    agent       = false
  }

  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update && sudo apt-get install -y caddy",
      "sudo caddy adapt --config /etc/caddy/Caddyfile",
      "sudo systemctl restart caddy"
    ]
  }
}

# Generate Caddyfile
resource "local_file" "caddyfile" {
  filename = "${path.module}/Caddyfile"
  content  = <<-EOT
    ${var.domain} {
      root * /var/www/html
      file_server
      encode gzip
    }
    www.${var.domain} {
      redirect https://${var.domain}
    }
  EOT
}

variables.tf:

variable "cloudflare_api_token" {
  type        = string
  description = "Cloudflare API Token"
  sensitive   = true
}

variable "domain" {
  type        = string
  description = "Domain name"
  default     = "example.com"
}

variable "server_ip" {
  type        = string
  description = "Server IP address"
}

variable "ssh_private_key" {
  type        = string
  description = "SSH private key for server access"
  sensitive   = true
}

Workspaces

Workspaces memungkinkan multiple infrastructure states dalam satu configuration.

Kapan Gunakan Workspaces?

Catatan: Workspace ≠ Git branch. Workspace adalah feature Terraform, bukan VCS.

Workspace Commands:

# List workspaces
terraform workspace list

# Create workspace
terraform workspace new production

# Switch workspace
terraform workspace select production

# Show current workspace
terraform workspace show

# Delete workspace (must be not current)
terraform workspace delete dev

Using Workspaces dalam Code:

# Deteksi environment dari workspace
locals {
  environment = terraform.workspace
  is_prod     = terraform.workspace == "production"
}

# Gunakan untuk conditional resources
resource "aws_instance" "server" {
  count = local.is_prod ? 3 : 1

  ami           = var.ami_id
  instance_type = local.is_prod ? "t3.large" : "t3.micro"

  tags = {
    Name      = "server-${count.index}-${local.environment}"
    Workspace = local.environment
  }
}

Backend dengan Workspaces:

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "${terraform.workspace}/terraform.tfstate"
    region         = "ap-southeast-1"
    dynamodb_table = "terraform-locks"
  }
}
Alternative: Banyak tim lebih suka pakai separate directories (infra/dev/, infra/prod/) daripada workspaces untuk kejelasan yang lebih baik.

Import Existing Infrastructure

Terraform bisa import infrastruktur yang sudah ada agar bisa di-manage via code.

Import Command:

# Sintaks dasar
terraform import [options] ADDRESS ID

# Contoh: Import existing S3 bucket
terraform import aws_s3_bucket.my_bucket my-bucket-name

# Import dengan config yang sudah ada
terraform import module.vpc.aws_vpc.main vpc-12345678

Import Block (Terraform 1.5+):

import {
  id = "i-1234567890abcdef0"
  to = aws_instance.web
}

import {
  id = "prod-web-server"
  to = aws_instance.web
}

# Dengan provider-specific ID
import {
  id = "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0"
  to = aws_instance.web
}

Workflow Import:

  1. Tulis resource config kosong
  2. Run terraform import
  3. Run terraform plan untuk lihat state differences
  4. Update config sampai plan = 0 changes
  5. Commit config + generated state

generate-config Command (Experimental):

# Generate config dari existing resource (Terraform 1.6+)
terraform plan -generate-config-out=generated.tf
Import Tips: Import hanya membuat Terraform aware of existing resources. Kamu tetap perlu menulis config yang benar agar plan = 0.

Best Practices

Directory Structure:

infra/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── .terraform.lock.hcl
│   └── prod/
│       ├── main.tf
│       ├── variables.tf
│       └── .terraform.lock.hcl
├── modules/
│   ├── networking/
│   ├── compute/
│   └── database/
└── global/
    └── s3-state-backend/

.gitignore Wajib:

# Terraform
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.*
crash.log
crash.*.log

# Override
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Local .terraform directories
**/.terraform/*

# .tfvars files (kalau ada secrets)
*.tfvars
*.tfvars.json

# IDE
.vscode/
.idea/

Provider Version Pinning:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"  # Lock major version
    }
  }
}

Remote State dengan versioning:

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-southeast-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

Security Checklist:

Cheat Sheet & Next Steps

Essential Commands:

# Initialize (download providers, setup backend)
terraform init

# Format code (jaga konsistensi)
terraform fmt

# Validate syntax (tanpa akses provider)
terraform validate

# Preview changes
terraform plan
terraform plan -var-file="prod.tfvars"

# Apply changes
terraform apply
terraform apply -var-file="prod.tfvars"
terraform apply -auto-approve  # skip approval (hati-hati!)

# Destroy infrastructure
terraform destroy
terraform destroy -target=aws_instance.server  # destroy spesifik

# State management
terraform state list
terraform state mv aws_instance.old aws_instance.new  # rename
terraform state rm aws_instance.deleted  # remove dari state
terraform show

# Output
terraform output
terraform output -raw variable_name

Essential Files:

File Fungsi
main.tf Resource definitions
variables.tf Input variable definitions
outputs.tf Output value definitions
providers.tf Provider configuration
versions.tf Terraform & provider version constraints
terraform.tfvars Variable values (auto-loaded)

Next Steps - Learn More:

Kuis: Terraform

Apa format file Terraform?

Command apa untuk apply perubahan?

Apa fungsi Terraform state?