ใช้ Terraform จัดการ Infrastructure as Code บน VPS

ใช้ Terraform จัดการ Infrastructure as Code บน VPS

มิถุนายน 2026 · อ่าน 8 นาที

Terraform เป็นเครื่องมือ Infrastructure as Code (IaC) ที่ช่วยให้คุณประกาศการตั้งค่า VPS, เครือข่าย, DNS, และทรัพยากรคลาวด์อื่นๆ ในไฟล์ .tf ชนิดข้อความ แล้วใช้ terraform plan, apply, destroy เพื่อสร้าง/อัปเดต/ลบ infrastructure ได้ซ้ำได้ สถานะ (state) ของทรัพยากรทั้งหมดเก็บไว้ในไฟล์ terraform.tfstate เพื่อให้ Terraform รู้ว่าปัจจุบันมีอะไรอยู่บน cloud provider นั้น ข้อดีของ Terraform คือ version control, collaboration ง่าย, consistent deployment, และสามารถ destroy environment เก่าและสร้างใหม่ได้เร็ว

ติดตั้ง Terraform

Terraform มี binary สำหรับ Linux, macOS, Windows ให้ดาวน์โหลดจากเว็บราชการของ HashiCorp ที่ terraform.io เพื่อติดตั้งบน Ubuntu/Debian:

wget -O- https://apt.releases.hashicorp.com/gpg | \
  gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
  https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
  sudo tee /etc/apt/sources.list.d/hashicorp.list

sudo apt update && sudo apt install terraform

สำหรับ CentOS/RHEL:

sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum install terraform

ตรวจสอบเวอร์ชันที่ติดตั้ง:

terraform version
Tip: ควรติดตั้ง Terraform เวอร์ชันล่าสุดเสมอ เพราะแต่ละเวอร์ชันอาจมี syntax ใหม่ และ bug fix มี

สร้างไฟล์ .tf ประกาศ VPS

สร้างโฟลเดอร์ project ใหม่ แล้วสร้างไฟล์ main.tf, variables.tf, outputs.tf ตัวอย่างการสร้าง VPS บน Linode เป็น provider:

# main.tf
terraform {
  required_version = ">= 1.0"
  required_providers {
    linode = {
      source  = "linode/linode"
      version = "~> 2.0"
    }
  }
}

provider "linode" {
  token = var.linode_token
}

resource "linode_instance" "web_server" {
  label             = "web-server-01"
  image             = "linode/ubuntu22.04"
  region            = "ap-south"
  type              = "g6-standard-2"
  authorized_keys   = [var.ssh_public_key]
  root_pass         = var.root_password
  swap_size         = 512

  tags = ["web", "production"]

  lifecycle {
    ignore_changes = [authorized_keys, root_pass]
  }
}

resource "linode_firewall" "web_firewall" {
  label = "web-firewall"

  inbound {
    label    = "allow-https"
    action   = "ACCEPT"
    protocol = "TCP"
    ports    = "443"
    ipv4     = ["0.0.0.0/0"]
  }

  inbound {
    label    = "allow-http"
    action   = "ACCEPT"
    protocol = "TCP"
    ports    = "80"
    ipv4     = ["0.0.0.0/0"]
  }

  inbound {
    label    = "allow-ssh"
    action   = "ACCEPT"
    protocol = "TCP"
    ports    = "22"
    ipv4     = ["0.0.0.0/0"]
  }

  outbound {
    label      = "allow-outbound"
    action     = "ACCEPT"
    protocol   = "ALL"
    ipv4       = ["0.0.0.0/0"]
    ipv6       = ["::/0"]
  }

  linodes = [linode_instance.web_server.id]
}

resource "linode_domain" "example_domain" {
  domain      = "example.com"
  type        = "master"
  soa_email   = "[email protected]"
  description = "Managed by Terraform"
}

resource "linode_domain_record" "web_a_record" {
  domain_id   = linode_domain.example_domain.id
  name        = "web"
  record_type = "A"
  target      = linode_instance.web_server.ip_address
  ttl         = 3600
}

ไฟล์ variables.tf เก็บตัวแปรที่เปลี่ยนแปลงได้:

variable "linode_token" {
  type        = string
  description = "Linode API token for authentication"
  sensitive   = true
}

variable "ssh_public_key" {
  type        = string
  description = "SSH public key content (from ~/.ssh/id_rsa.pub)"
  sensitive   = false
}

variable "root_password" {
  type        = string
  description = "Root password for VPS instance"
  sensitive   = true
}

ไฟล์ outputs.tf ส่งออกข้อมูลที่สำคัญ:

output "instance_ip" {
  value       = linode_instance.web_server.ip_address
  description = "The public IP address of the web server"
}

output "domain_name" {
  value       = linode_domain.example_domain.domain
  description = "The managed domain name"
}

Terraform Workflow — Init, Plan, Apply, Destroy

การใช้ Terraform มี 4 ขั้นตอนหลัก:

1. Init — เริ่มต้น project:

terraform init

คำสั่งนี้ download provider plugins (linode, aws, ฯลฯ) มาเก็บในโฟลเดอร์ .terraform/ และสร้างไฟล์ .terraform.lock.hcl เพื่อล็อก version ของ plugins ที่ใช้

2. Plan — แสดงแผนการเปลี่ยนแปลง:

terraform plan -var-file="terraform.tfvars" > tfplan.txt

คำสั่งนี้ตรวจสอบไฟล์ .tf แล้วแสดงว่าจะสร้าง/อัปเดต/ลบทรัพยากรอะไรบ้าง ก่อน apply จริง ถ้าต้องการบันทึกผลลัพธ์ลงไฟล์ให้ใช้ -out=tfplan

3. Apply — สร้างหรืออัปเดต infrastructure:

terraform apply "tfplan.txt"

คำสั่งนี้สร้างทรัพยากรจริงบน cloud provider ตามแผน plan ที่สร้างไว้ หลังจากทำเสร็จ Terraform จะบันทึก state ลงไฟล์ terraform.tfstate

4. Destroy — ลบ infrastructure ทั้งหมด:

terraform destroy -var-file="terraform.tfvars" -auto-approve

คำสั่งนี้ลบทรัพยากรทั้งหมดที่สร้างโดย Terraform ถ้าไม่ใส่ -auto-approve จะถามยืนยันก่อน

จัดการ State File

State file (terraform.tfstate) เป็นหัวใจของ Terraform เพราะมันเก็บสถานะของทรัพยากรทั้งหมด Terraform อ่าน state แล้วเปรียบเทียบกับไฟล์ .tf เพื่อรู้ว่าต้องทำอะไร ห้ามแก้ไข state file ด้วยมือ เพราะทำให้ Terraform งง

ควรเก็บ state ไว้ใน remote backend (เช่น S3, Consul, Terraform Cloud) แทนเก็บโลคัล เพื่อให้ team ได้ access ร่วมกัน ตัวอย่างเก็บ state บน S3:

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
  }
}

ในการแรกของการใช้ remote backend ต้องเตรียม S3 bucket และ DynamoDB table ให้พร้อมก่อน แล้วรัน terraform init ให้ Terraform upload state ขึ้น S3

Tip: ใช้ terraform state list, terraform state show, terraform state rm เพื่อจัดการ state ที่ไม่ต้องการ ห้ามใช้ terraform state mv มนุษย์ปกติ

Terraform Modules — จัดระเบียบ Code

เมื่อ infrastructure โตขึ้น ต้องจัดระเบียบโค้ด Terraform ให้เป็นชุดๆ เรียก modules โดย module คือ folder ที่มีไฟล์ main.tf, variables.tf, outputs.tf ของมัน ตัวอย่าง:

# modules/web_server/main.tf
resource "linode_instance" "web" {
  label             = var.server_label
  image             = var.image_id
  region            = var.region
  type              = var.instance_type
  authorized_keys   = var.authorized_keys
  tags              = var.tags
}

# modules/web_server/variables.tf
variable "server_label" {
  type = string
}

variable "image_id" {
  type = string
}

# ... อื่นๆ

# modules/web_server/outputs.tf
output "instance_id" {
  value = linode_instance.web.id
}

output "ip_address" {
  value = linode_instance.web.ip_address
}

แล้ว call module จากไฟล์ main.tf หลัก:

module "web_server" {
  source = "./modules/web_server"

  server_label    = "prod-web-01"
  image_id        = "linode/ubuntu22.04"
  region          = "ap-south"
  instance_type   = "g6-standard-2"
  authorized_keys = [var.ssh_public_key]
  tags            = ["web", "production"]
}

วิธีนี้ช่วยให้สามารถสร้าง VPS หลายตัวด้วย module เดียว หรือ reuse module ร่วมกันหลาย project ได้

ตรวจสอบว่า Terraform ทำงานถูกต้อง

หลังจากเขียน .tf files ต้องตรวจสอบ syntax และการตั้งค่าให้แน่ว่าถูกต้อง:

# 1. ตรวจสอบ syntax
terraform validate

# 2. ฟอร์แมต code ให้เป็นมาตรฐาน
terraform fmt -recursive

# 3. ดูแผนการเปลี่ยนแปลง โดยไม่ execute
terraform plan -var-file="terraform.tfvars" | head -50

# 4. ตรวจสอบ state ปัจจุบัน
terraform state list

# 5. ดู output values หลังจาก apply
terraform output

ตัวอย่างผลลัพธ์หลังเช็ค:

$ terraform validate
Success! The configuration is valid.

$ terraform fmt -check -diff
- root_pass         = var.root_password
+ root_pass = var.root_password

$ terraform plan
Terraform will perform the following actions:

  # linode_instance.web_server will be created
  + resource "linode_instance" "web_server" {
      + id              = (known after apply)
      + ip_address      = (known after apply)
      + label           = "web-server-01"
      + region          = "ap-south"
      + type            = "g6-standard-2"
    }

Plan: 3 to add, 0 to change, 0 to destroy.

$ terraform state list
linode_instance.web_server
linode_firewall.web_firewall
linode_domain.example_domain
linode_domain_record.web_a_record

$ terraform output
instance_ip = "139.162.123.45"
domain_name = "example.com"
Tip: สร้าง CI/CD pipeline (GitHub Actions, GitLab CI) เพื่อรัน terraform validate และ terraform plan อัตโนมัติทุกครั้ง push code ขึ้น repo

แก้ปัญหาที่พบบ่อย

Error: "Invalid or missing token"

สาเหตุ: ไม่ได้ใส่ API token ของ cloud provider ถูกต้อง หรือ token หมดอายุ

วิธีแก้: ตรวจสอบว่าตั้งค่า terraform.tfvars ถูกต้อง

# terraform.tfvars
linode_token        = "your-api-token-here"
ssh_public_key      = file("~/.ssh/id_rsa.pub")
root_password       = "SecurePassword123!"

หรือตั้ง environment variable:

export TF_VAR_linode_token="your-api-token-here"

Error: "Instance already exists" (Import conflict)

สาเหตุ: มีทรัพยากรที่สร้างไว้แล้วบน cloud provider แต่ Terraform ไม่รู้ (ไม่มีใน state file)

วิธีแก้: import ทรัพยากรเก่าเข้า state ก่อน

terraform import linode_instance.web_server 123456789

ที่ 123456789 คือ instance ID ที่ได้จาก cloud console หรือ API การ import จะเพิ่มทรัพยากรเข้า state ให้ Terraform รู้และจัดการต่อไป

Error: "Backend initialization failed"

สาเหตุ: backend (S3 bucket หรือ remote storage) ไม่พร้อม หรือ credentials ไม่ถูกต้อง

วิธีแก้: ตรวจสอบการตั้งค่า backend.tf และค่อยรัน terraform init

# ต้องเตรียม S3 bucket ก่อน
aws s3 mb s3://my-terraform-state

# ตั้งค่า S3 versioning เพื่อ recovery
aws s3api put-bucket-versioning \
  --bucket my-terraform-state \
  --versioning-configuration Status=Enabled

# ถ้า AWS credentials ไม่ถูกต้อง ตั้ง environment variables
export AWS_ACCESS_KEY_ID="your-key"
export AWS_SECRET_ACCESS_KEY="your-secret"

terraform init

Error: "Argument or block type not found"

สาเหตุ: attribute name ผิด หรือ version ของ provider ไม่รองรับ attribute นั้น

วิธีแก้: ตรวจสอบเวอร์ชัน provider และ documentation

# อ่าน provider schema
terraform providers schema -json | jq '.provider_schemas."registry.terraform.io/linode/linode"' | less

# อัปเดต .terraform.lock.hcl ให้เป็น version ล่าสุด
terraform init -upgrade

คำถามที่พบบ่อย

1. ฉันมี VPS เก่าที่สร้างไว้แล้วด้วยมือ จะ import เข้า Terraform ได้ไหม?

ได้ สร้างไฟล์ .tf ที่สอดคล้องกับทรัพยากรเก่า แล้วใช้คำสั่ง terraform import เพื่อดึง resource เข้า state file ตัวอย่าง terraform import linode_instance.old_server 987654321 ที่ 987654321 คือ instance ID จาก cloud console หลังจาก import จะมี resource ใหม่ใน state แล้ว Terraform จะจัดการมันต่อไป

2. state file มี password และ sensitive data อยู่ เหมาะสำหรับ commit ลง Git ไหม?

ไม่ควร state file มี sensitive data หลายชิ้น ต้องเก็บไว้ที่ปลอดภัย โดยเก็บ state บน remote backend (S3, Terraform Cloud, Consul) ที่มี encryption และ access control บนคอมพิวเตอร์ของตัวเอง ให้เพิ่ม terraform.tfstate ลงไฟล์ .gitignore ห้าม commit สู่ Git repo ถ้าต้องการให้ team access state ร่วมกัน ให้เก็บบน Terraform Cloud ด้วย account ซึ่งแต่ละคนลง login แล้วสามารถ access ได้โดยปลอดภัย

3. Terraform ใช้เวลานานตอน apply ได้ไหม ฉันรอไม่ไหวจริงๆ

ได้ apply อาจใช้เวลานานหลายนาทีถ้า infrastructure ใหญ่ หรือ provider response ช้า ส่วน destroy ก็อาจใช้เวลานาน เพราะต้องรอให้ resource ทั้งหมด terminate โดยสมบูรณ์ ถ้ารอไม่ไหว ให้เปิด terminal ใหม่ และรัน terraform show เพื่อดูว่า apply ยังทำงานอยู่หรือไม่ ถ้า process เสร็จแล้ว state file จะ update โดยอัตโนมัติ ห้าม Ctrl+C ขณะ apply เพราะจะทำให้ state หลุดออกจากความสอดคล้องกับ resource จริง