Skip to main content

πŸ› οΈ Langkah Step-by-Step Setup Terraform

βœ… Arsitektur yang Akan Dibuat:

+----------------+       +--------------------+
| Azure VM (Ubuntu) | <--> | Azure MySQL Database |
| Private IP       |       | Private Endpoint     |
+----------------+       +--------------------+
        |
        v
+------------------------+
| Azure Storage Account  |
| Private Endpoint       |
+------------------------+

Bagus! Struktur Terraform yang dipi

βœ… Step 1: Persiapan Install Terraform di WSL

  1. sudo apt update && sudo apt install -y software-properties-common gnupg curl
  2. curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
  3. echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com
  4. $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
  5. sudo apt update && sudo apt install -y terraform
  6. terraform -v

βœ…Β Step 2: Mengatur Kredensial AZURE

Terraform menggunakan salah satu dari cara berikut untuk mengakses akun Azure kamu, berikut beberapa metode autentikasi yang umum digunakan di Azure untuk Terraform:


βœ… Metode 1: Azure CLI Login (paling sederhana untuk personal use)

Langkah:
  1. Login ke Azure CLI:

    az login
    
  2. Pastikan subscription aktif:

    az account set --subscription "<SUBSCRIPTION_NAME_OR_ID>"
    
  3. Setelah login berhasil, Terraform akan otomatis menggunakan session ini saat kamu menjalankan:

    terraform init
    terraform apply
    

🟒 Kelebihan: Simple, cocok untuk pengembangan lokal.
πŸ”΄ Kekurangan: Tidak cocok untuk otomatisasi di CI/CD (karena butuh login manual).


βœ… Metode 2: Service Principal (disarankan untuk CI/CD atau automation)

Langkah:

  1. Buat Service Principal:

    az ad sp create-for-rbac --name "terraform-sp" --role="Contributor" --scopes="/subscriptions/<your-subscription-id>"
    

    Output akan seperti ini:

    {
      "appId": "xxxxx",
      "displayName": "terraform-sp",
      "password": "yyyyy",
      "tenant": "zzzzz"
    }
    
  2. Set environment variable (Linux/macOS):

    export ARM_CLIENT_ID=xxxxx
    export ARM_CLIENT_SECRET=yyyyy
    export ARM_SUBSCRIPTION_ID=<your-subscription-id>
    export ARM_TENANT_ID=zzzzz
    

    Atau di Windows (PowerShell):

    $env:ARM_CLIENT_ID="xxxxx"
    $env:ARM_CLIENT_SECRET="yyyyy"
    $env:ARM_SUBSCRIPTION_ID="<your-subscription-id>"
    $env:ARM_TENANT_ID="zzzzz"
    
  3. Jalankan Terraform seperti biasa:

    terraform init
    terraform apply
    

🟒 Kelebihan: Aman untuk automation, bisa disimpan di pipeline/secret CI/CD
πŸ”΄ Kekurangan: Perlu setup sedikit lebih panjang


πŸ”’ Metode Lain (Opsional)

  • Managed Identity (untuk VM di Azure yang menjalankan Terraform)

  • OIDC Workload Identity Federation (untuk GitHub Actions + Azure, lebih secure daripada menyimpan secret langsung)


Jika kamu hanya menggunakan Terraform dari laptop pribadi untuk deploy resource MPN, maka pakai metode az login saja sudah cukup.
Tapi untuk pipeline atau tim DevOps, sebaiknya pakai Service Principal.


βœ… Step 3: Koneksi Internet

Karena Terraform akan:

  • Menghubungi Azure API

  • Mendownload provider plugin

Pastikan WSL kamu punya akses internet (cek ping google.com atau curl aws.amazon.com).

βœ…Β Step 4: Struktur Terraform sederhana

Struktur Terraform yang dipisah-pisah per file memang praktik terbaik (modular dan rapi). Berikut adalah struktur direktori dan isi file untuk:

  • VM Ubuntu (sebagai Web Server)

  • Azure Database for MySQL

  • Storage Account

  • Virtual Network + Subnet + NSG

  • Private Endpoints


πŸ“ Struktur Folder

terraform/
β”œβ”€β”€ main.tf
β”œβ”€β”€ mysql.tf
β”œβ”€β”€ network.tf
β”œβ”€β”€ output.tf
β”œβ”€β”€ private_endpoints.tf
β”œβ”€β”€ provider.tf
β”œβ”€β”€ storage.tf
β”œβ”€β”€ variables.tf
└── vm.tf

1. main.tf

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }

  required_version = ">= 1.3.0"
}

2. variables.tf

variable "location" {
  default = "southeastasia"
}

variable "resource_group_name" {
  default = "rg-wid-research"
}

variable "vm_admin_username" {
  default = "azureuser"
}

variable "vm_admin_password" {
  default = "P@ssw0rd1234!" # Untuk testing, sebaiknya pakai `terraform.tfvars` dan jangan upload ke repo
}

3. network.tf

resource "azurerm_resource_group" "main" {
  name     = var.resource_group_name
  location = var.location
}

resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-webserver"
  address_space       = ["10.0.0.0/16"]
  location            = var.location
  resource_group_name = azurerm_resource_group.main.name
}

# Subnet untuk VM/Webserver
resource "azurerm_subnet" "subnet" {
  name                 = "subnet-main"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.1.0/24"]
}

# Subnet khusus untuk MySQL Flexible Server (dengan delegasi)
resource "azurerm_subnet" "subnet_mysql" {
  name                 = "subnet-mysql"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.2.0/24"]

  delegation {
    name = "mysql-delegation"

    service_delegation {
      name = "Microsoft.DBforMySQL/flexibleServers"
      actions = [
        "Microsoft.Network/virtualNetworks/subnets/action"
      ]
    }
  }
}

#resource link antara vnet dan private DNS
#resource "azurerm_private_dns_zone_virtual_network_link" "mysql" {
#  name                  = "mysql-vnet-link"
#  resource_group_name   = azurerm_resource_group.main.name
#  private_dns_zone_name = azurerm_private_dns_zone.mysql.name
#  virtual_network_id    = azurerm_virtual_network.vnet.id
#  registration_enabled  = false
#}

resource "azurerm_network_security_group" "nsg" {
  name                = "nsg-webserver"
  location            = var.location
  resource_group_name = azurerm_resource_group.main.name

  security_rule {
    name                       = "Allow-SSH"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  security_rule {
    name                       = "Allow-HTTP"
    priority                   = 1002
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

resource "azurerm_subnet_network_security_group_association" "nsg_assoc" {
  subnet_id                 = azurerm_subnet.subnet.id
  network_security_group_id = azurerm_network_security_group.nsg.id
}

4. vm.tf

resource "azurerm_network_interface" "nic" {
  name                = "nic-webserver"
  location            = var.location
  resource_group_name = azurerm_resource_group.main.name

  ip_configuration {
    name                          = "ipconfig1"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.vm_public.id
  }
}

resource "azurerm_public_ip" "vm_public" {
  name                = "publicip-webserver"
  location            = var.location
  resource_group_name = azurerm_resource_group.main.name
  allocation_method   = "Dynamic"
}

resource "azurerm_linux_virtual_machine" "vm" {
  name                            = "vm-webserver"
  location                        = var.location
  resource_group_name             = azurerm_resource_group.main.name
  size                            = "Standard_B1s"
  admin_username                  = var.vm_admin_username
  admin_password                  = var.vm_admin_password
  disable_password_authentication = false

  network_interface_ids = [
    azurerm_network_interface.nic.id,
  ]

  os_disk {
    name                 = "osdisk-webserver"
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts"
    version   = "latest"
  }

  tags = {
    owner       = "WID"
    Team        = "MS"
    Description = "Resources For WID project"
    Environment = "Research"
  }
}

5. mysql.tf

resource "azurerm_mysql_flexible_server" "mysql" {
  name                   = "wid-mysql-dbserver"
  location               = var.location
  resource_group_name    = azurerm_resource_group.main.name
  administrator_login    = "mysqladmin"
  administrator_password = "MyS3cur3P@ssw0rd"
  sku_name               = "B_Standard_B1ms"
  version                = "8.0.21"
  zone                   = "1"

  storage {
    size_gb = 20
  }

  # high_availability {
  #   mode = "Disabled"
  # }

  delegated_subnet_id = azurerm_subnet.subnet_mysql.id
  private_dns_zone_id = azurerm_private_dns_zone.mysql.id

#  depends_on = [azurerm_subnet_network_security_group_association.nsg_assoc]
depends_on = [
  azurerm_subnet.subnet,
  azurerm_subnet_network_security_group_association.nsg_assoc,
  time_sleep.wait_for_dns_link
]

  tags = {
    owner       = "WID"
    Team        = "MS"
    Description = "Resources For WID project"
    Environment = "Research"
  }
}

6. storage.tf

resource "azurerm_storage_account" "storage" {
  name                     = "widstoragewebapp01"
  resource_group_name      = azurerm_resource_group.main.name
  location                 = var.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
  #  allow_blob_public_access = false
}

7. outputs.tf

output "vm_public_ip" {
  value = azurerm_public_ip.vm_public.ip_address
}

output "mysql_fqdn" {
  value = azurerm_mysql_flexible_server.mysql.fqdn
}

output "storage_account_name" {
  value = azurerm_storage_account.storage.name
}

8.Β private_endpoints.tf

resource "azurerm_private_dns_zone" "mysql" {
  name                = "privatelink.mysql.database.azure.com"
  resource_group_name = azurerm_resource_group.main.name
}

resource "azurerm_private_dns_zone_virtual_network_link" "mysql_link" {
  name                  = "mysql-dnslink"
  resource_group_name   = azurerm_resource_group.main.name
  private_dns_zone_name = azurerm_private_dns_zone.mysql.name
  virtual_network_id    = azurerm_virtual_network.vnet.id
}

#Tambahkan delay sampai proses tahap ini selesai agar tidak membuat error dalam pembuatan mysql flexibleServers
resource "time_sleep" "wait_for_dns_link" {
  depends_on = [azurerm_private_dns_zone_virtual_network_link.mysql_link]
  create_duration = "60s" # Atur sesuai kebutuhan, misal 60 detik
}

Atau kamu bisa nge clone/pull dari repository saya di https://github.com/iniwidi/terraform-azure


9.Β provider.tf

provider "azurerm" {
  features {
    resource_group {
        prevent_deletion_if_contains_resources = false
    }
  }
}

πŸ”„ Cara Deploy

terraform init
terraform plan
terraform apply

Tidak semuanyaΒ gratis β€” beberapa resource yang dibuat dalam konfigurasi Terraform ini berbayar. Berikut penjelasan per resource dan apakah termasuk gratis (Free Tier) atau berbayar, berdasarkan Azure Free Tier dan pengalaman praktis:


πŸ’‘ Ringkasan Tipe Resource

Resource Free? Catatan
Resource Group βœ… Gratis Hanya wadah/logis.
Virtual Network + Subnet βœ… Gratis Tidak dikenakan biaya.
Network Security Group (NSG) βœ… Gratis NSG itu sendiri gratis; trafiknya bisa dihitung ke bandwidth.
Linux Virtual Machine (VM) ⚠️ Tergantung size Free hanya untuk B1s selama 750 jam/bulan (di Free Tier).
Azure MySQL Flexible Server ❌ Berbayar Tidak termasuk dalam Free Tier. Paling murah mulai dari ~Rp50rb+/bln.
Azure Storage Account βœ…/⚠️ Terbatas Gratis 5 GB LRS blob storage masuk Free Tier; selebihnya berbayar.
Private Endpoint (MySQL + Storage) ❌ Berbayar Dikenakan biaya per jam per endpoint (~Rp20rb+/bln per endpoint).
Public IP Address (optional) ⚠️ Terbatas Gratis Dynamic Public IP tidak dikenai biaya saat tidak dipakai terus-menerus.

πŸ” Rekomendasi Agar Hemat (Selama Uji Coba / Belajar)

  1. Gunakan VM Standard_B1s di lokasi yang masuk Free Tier (contoh: East US, West Europe).

  2. Hapus Private Endpoint saat tidak diperlukan (karena ini yang biasanya diam-diam mahal).

  3. Gunakan local MySQL di dalam VM jika tidak butuh Azure Database MySQL.

  4. Monitor billing dari Azure Portal:


πŸ“˜ Tips Tambahan

Kalau kamu ingin belajar full tanpa biaya, alternatif:

  • Gunakan 1 VM Ubuntu, dan install sendiri MySQL + Nginx/Apache + PHP di dalamnya.

  • Simulasi private communication via localhost atau VNet, tanpa Private Endpoint.

  • Storage bisa pakai disk tambahan VM (tanpa Storage Account).