Workshop: Provision Cloud VPS Infrastructure ด้วย Terraform

Workshop นี้เป็นตัวอย่างภาคปฏิบัติของการ provision Cloud VPS ด้วย Terraform ตั้งแต่เริ่มต้น — ผู้อ่านจะได้เห็นการเขียน configuration, การตั้งค่า provider, การสร้าง instance, การติดตั้ง web server ผ่าน cloud-init และการ destroy resource หลังใช้งาน

เป้าหมายคือสร้าง VPS instance ที่รัน Nginx พร้อม firewall rule เปิดพอร์ต 80/443 จบใน one-click ผ่าน terraform apply โดยใช้ DigitalOcean เป็น provider (เปลี่ยนเป็น AWS, GCP หรือ provider อื่นได้ด้วยหลักเดียวกัน)

โครงสร้างโปรเจกต์

vps-workshop/
├── main.tf              # resource หลัก
├── variables.tf         # input variable
├── outputs.tf           # output หลัง apply
├── terraform.tfvars     # ค่าจริง (gitignore)
├── cloud-init.yaml      # user data สำหรับ bootstrap
└── .gitignore

สร้างไฟล์ทั้งหมดในโฟลเดอร์เดียวกัน ไม่แยก module เพื่อความกระชับ — พอโปรเจกต์โตขึ้นค่อย refactor เป็น module ตามบทความก่อนหน้า

ขั้นที่ 1 — Provider และ Authentication

ประกาศ provider ใน main.tf — ใช้ API token จาก environment variable DIGITALOCEAN_TOKEN หลีกเลี่ยงการ hardcode ลง state

terraform {
  required_version = ">= 1.5"
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

provider "digitalocean" {
  # อ่าน token จาก env: export DIGITALOCEAN_TOKEN=dop_v1_xxx
}

ขั้นที่ 2 — Input Variables

# variables.tf
variable "project_name" {
  type        = string
  default     = "web-workshop"
  description = "prefix สำหรับตั้งชื่อ resource"
}

variable "region" {
  type    = string
  default = "sgp1"
}

variable "droplet_size" {
  type    = string
  default = "s-1vcpu-1gb"
}

variable "ssh_key_fingerprint" {
  type        = string
  description = "fingerprint ของ SSH key ที่ upload ไว้ใน DO แล้ว"
}

variable "allowed_ssh_cidr" {
  type    = list(string)
  default = ["0.0.0.0/0"]  # จำกัดเป็น IP office จริง ๆ
}

กรอกค่าจริงในไฟล์ terraform.tfvars ซึ่ง .gitignore จะกัน commit credential

# terraform.tfvars
ssh_key_fingerprint = "aa:bb:cc:dd:ee:ff:00:11:22:33"
allowed_ssh_cidr    = ["203.0.113.10/32"]

ขั้นที่ 3 — Cloud-Init Script

ใช้ cloud-init ติดตั้ง Nginx และเปิด service อัตโนมัติหลัง boot — ไม่ต้อง SSH เข้าไปตั้งค่าด้วยมือ

# cloud-init.yaml
#cloud-config
package_update: true
package_upgrade: true
packages:
  - nginx
  - ufw

write_files:
  - path: /var/www/html/index.html
    content: |
      <!DOCTYPE html>
      <html>
      <head><title>Workshop VPS</title></head>
      <body><h1>Provisioned by Terraform</h1></body>
      </html>

runcmd:
  - ufw allow 'Nginx Full'
  - ufw allow 'OpenSSH'
  - ufw --force enable
  - systemctl enable nginx
  - systemctl restart nginx

ขั้นที่ 4 — Resource หลัก

# main.tf (ต่อจาก provider block)

resource "digitalocean_droplet" "web" {
  name     = "${var.project_name}-web"
  region   = var.region
  size     = var.droplet_size
  image    = "ubuntu-22-04-x64"
  ssh_keys = [var.ssh_key_fingerprint]
  user_data = file("${path.module}/cloud-init.yaml")

  tags = [var.project_name, "managed-by-terraform"]
}

resource "digitalocean_firewall" "web" {
  name = "${var.project_name}-fw"

  droplet_ids = [digitalocean_droplet.web.id]

  inbound_rule {
    protocol         = "tcp"
    port_range       = "22"
    source_addresses = var.allowed_ssh_cidr
  }
  inbound_rule {
    protocol         = "tcp"
    port_range       = "80"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }
  inbound_rule {
    protocol         = "tcp"
    port_range       = "443"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol              = "tcp"
    port_range            = "1-65535"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }
  outbound_rule {
    protocol              = "udp"
    port_range            = "1-65535"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }
}

ขั้นที่ 5 — Output

# outputs.tf
output "public_ip" {
  value       = digitalocean_droplet.web.ipv4_address
  description = "เอา IP นี้ไปเปิดใน browser"
}

output "ssh_command" {
  value = "ssh root@${digitalocean_droplet.web.ipv4_address}"
}

ขั้นที่ 6 — Workflow การ Apply

# ตั้ง token
export DIGITALOCEAN_TOKEN=dop_v1_xxxxxxxxxxxxxxxxx

# init
terraform init

# preview
terraform plan

# apply
terraform apply -auto-approve

# ดู output
terraform output
# public_ip = "167.172.xxx.xxx"
# ssh_command = "ssh [email protected]"

รอประมาณ 60-90 วินาที ให้ cloud-init รันเสร็จ แล้วเปิด browser ไปที่ public IP — ควรเห็นหน้า HTML ที่ cloud-init เขียนไว้

ขั้นที่ 7 — การทดสอบและ Validation

# ตรวจ HTTP response
curl -I http://$(terraform output -raw public_ip)
# HTTP/1.1 200 OK
# Server: nginx/1.18.0

# ตรวจ firewall rule ที่ active
ssh root@$(terraform output -raw public_ip) ufw status

# ตรวจว่า droplet ของเราอยู่ใน state
terraform state list
# digitalocean_droplet.web
# digitalocean_firewall.web

ขั้นที่ 8 — Update และ Rebuild

ลองแก้ droplet_size เป็น s-2vcpu-2gb ใน terraform.tfvars แล้ว plan ใหม่ — จะเห็นว่าการเปลี่ยน size ต้อง recreate instance (destroy + create)

terraform plan
# Plan: 1 to add, 0 to change, 1 to destroy.
# droplet จะถูกลบและสร้างใหม่ — public IP จะเปลี่ยน
# ถ้าต้องการ IP เดิมต้อง attach floating IP แยก

ขั้นที่ 9 — Destroy

terraform destroy -auto-approve
# ลบ droplet + firewall ทั้งหมดที่ Terraform สร้างขึ้น
# state file ว่างเปล่า ไม่มีค่า resource อยู่
# ป้องกันค่าใช้จ่ายค้างหลัง workshop

ข้อควรระวัง

  • ห้าม commit terraform.tfvars หรือไฟล์ที่มี credential ลง git — ต้องอยู่ใน .gitignore
  • state file (terraform.tfstate) เก็บ sensitive value ต้อง backup และจำกัดสิทธิ์การเข้าถึง
  • Workshop ใช้ backend แบบ local ถ้าไป production ต้องเปลี่ยนเป็น remote backend (S3/GCS/Azure Storage)
  • จำกัด allowed_ssh_cidr ให้เป็น IP office จริง ไม่ใช้ 0.0.0.0/0 ใน production
  • ตั้ง tags ทุก resource เพื่อให้ billing/tracking ง่ายภายหลัง

ต่อยอดจาก Workshop

  • เพิ่ม digitalocean_floating_ip เพื่อให้ IP คงที่แม้ recreate droplet
  • ใช้ count หรือ for_each สร้างหลาย droplet พร้อม load balancer
  • เพิ่ม DNS record ด้วย digitalocean_record ชี้โดเมนเข้า droplet
  • ติดตั้ง Certbot ผ่าน cloud-init เพื่อ auto-SSL
  • ย้าย state ไป S3-compatible storage ของ Cloudflare R2 หรือ DO Spaces

สรุป

Workshop นี้สาธิตวงจร provisioning แบบ end-to-end ตั้งแต่เขียน configuration, plan, apply, ไปจนถึง destroy — ใช้เวลาไม่ถึง 10 นาทีก็เห็นผลจริง และเมื่อเข้าใจ pattern นี้แล้วสามารถ scale ไปทำ infrastructure ที่ซับซ้อนกว่าได้ด้วยหลักการเดียวกัน

หัวใจสำคัญของการใช้ IaC คือ configuration ที่ version control ได้และ reproducible — รันใหม่กี่ครั้งก็ได้ infrastructure หน้าตาเดียวกัน ไม่มี snowflake server ให้ปวดหัวภายหลัง ใครที่ workshop นี้ผ่านแล้วจะเริ่มเห็นภาพของการจัดการ infrastructure แบบมืออาชีพได้ชัดเจนขึ้น