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 แบบมืออาชีพได้ชัดเจนขึ้น

