ปัญหาก่อนมี Infrastructure as Code
สมัยก่อน ตั้ง infra ด้วยการกด UI:
- Login AWS console → click EC2 → กด launch instance → เลือก AMI → ตั้ง security group
- กลับมาดูในอีก 6 เดือน ลืมหมดแล้วว่าตั้งอะไรไว้บ้าง
- มีคนใหม่เข้าทีม — อธิบายตามภาพ screenshot
- Production พังเพราะคนเผลอแก้ผ่าน UI
Infrastructure as Code (IaC) = เขียน "infra ที่ต้องการ" เป็นไฟล์ code commit ลง git tool จัดการให้ตรงตามที่เขียน
ข้อดี:
- Review ได้เหมือน code
- Diff ดูได้ว่าใครแก้อะไรเมื่อไหร่
- Reproduce environment เหมือนกันทุกที่ (dev/staging/prod)
- Rollback ได้ด้วย git revert
Tool หลักในตลาด
- Terraform (HashiCorp / OpenTofu fork) — popular ที่สุด รองรับทุก cloud
- Pulumi — เขียนเป็น TypeScript/Python/Go แทน HCL
- AWS CloudFormation — เฉพาะ AWS
- Ansible — focus ที่ config management มากกว่า provision
บทความนี้ใช้ Terraform (ตอนนี้คนนิยมใช้ OpenTofu ที่เป็น open-source fork หลัง HashiCorp เปลี่ยน license — syntax เหมือนกัน)
ติดตั้ง
# macOS
brew install terraform
# Linux
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
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
# verify
terraform version
หรือใช้ OpenTofu (เกือบเหมือนกัน):
brew install opentofu # คำสั่งคือ tofu แทน terraform
ตัวอย่างแรก — DigitalOcean Droplet
main.tf:
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.0"
}
}
}
provider "digitalocean" {
token = var.do_token
}
variable "do_token" {
description = "DigitalOcean API token"
type = string
sensitive = true
}
resource "digitalocean_droplet" "web" {
image = "ubuntu-24-04-x64"
name = "web-01"
region = "sgp1"
size = "s-1vcpu-1gb"
ssh_keys = [data.digitalocean_ssh_key.main.id]
}
data "digitalocean_ssh_key" "main" {
name = "my-ssh-key"
}
output "droplet_ip" {
value = digitalocean_droplet.web.ipv4_address
}
terraform.tfvars (ไม่ commit เข้า git):
do_token = "dop_v1_xxxxxxxxxxx"
.gitignore:
*.tfvars
*.tfstate
*.tfstate.backup
.terraform/
คำสั่งหลัก
# init — download provider + ตั้ง backend
terraform init
# plan — ดูว่าจะเปลี่ยนอะไรบ้าง (ยังไม่ apply)
terraform plan
# apply — execute การเปลี่ยน
terraform apply
# show — ดู state ปัจจุบัน
terraform show
# destroy — ลบทุก resource ที่จัดการอยู่
terraform destroy
# fmt — format ไฟล์
terraform fmt
# validate — ตรวจสอบ syntax
terraform validate
flow ปกติ: edit → terraform plan → ตรวจสอบ → terraform apply → confirm
State file — ใจกลางของ Terraform
หลัง apply Terraform สร้าง terraform.tfstate เก็บ "ของจริงในตอนนี้คืออะไร"
ครั้งถัดไปที่ apply Terraform จะ:
- อ่าน state
- เทียบกับ config ใน
.tf - คำนวณว่าต้องเปลี่ยน/เพิ่ม/ลบอะไร
state file สำคัญมาก — หายไป Terraform ไม่รู้ว่ามี resource อะไรของตัวเองในที่จริง = ต้อง import กลับมาทีละตัว
Remote state — ทีมต้องใช้
ทำงานคนเดียว state อยู่ local ก็ได้ ทำงานทีม ต้องเก็บ state ส่วนกลาง + lock เพื่อกัน 2 คน apply พร้อมกัน
ตัวเลือก:
- Terraform Cloud (HashiCorp) — มี free tier
- AWS S3 + DynamoDB — popular ที่สุด
- GCS สำหรับ GCP
- HTTP backend — เช่น GitLab managed Terraform state
ตัวอย่างใช้ S3:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
Variables / Outputs / Locals
# variable — input
variable "environment" {
type = string
default = "production"
validation {
condition = contains(["development", "staging", "production"], var.environment)
error_message = "Must be development/staging/production"
}
}
# local — value ที่คำนวณภายใน
locals {
app_name = "myapp"
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
# resource — สิ่งที่จะสร้าง
resource "digitalocean_droplet" "web" {
name = "${local.app_name}-${var.environment}"
# ...
}
# output — ค่าที่ส่งออก ดูภายหลังได้
output "ip" {
value = digitalocean_droplet.web.ipv4_address
}
ส่ง variable ตอน apply:
terraform apply -var="environment=staging"
# หรือใส่ใน .tfvars
terraform apply -var-file="staging.tfvars"
Module — แชร์ config ระหว่าง project
สร้าง modules/web-server/main.tf ที่ encapsulate การสร้าง droplet + DNS + firewall
ใช้:
module "web_prod" {
source = "./modules/web-server"
environment = "production"
size = "s-2vcpu-4gb"
}
module "web_staging" {
source = "./modules/web-server"
environment = "staging"
size = "s-1vcpu-1gb"
}
หรือใช้ module จาก Terraform Registry:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.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.101.0/24", "10.0.102.0/24"]
}
Workspace — แยก environment ใน state เดียว
terraform workspace new staging
terraform workspace new production
terraform workspace select staging
terraform apply
terraform workspace select production
terraform apply
เห็น workspace ใน config:
resource "digitalocean_droplet" "web" {
name = "web-${terraform.workspace}"
}
แต่ pattern นี้มี debate — บางคนแนะนำให้แยก folder ดีกว่า workspace
Pattern ที่ใช้ใน production
โครงสร้างที่ทีมหลายๆ ที่ใช้:
infrastructure/
├── modules/
│ ├── web-server/
│ ├── database/
│ └── networking/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ └── production/
└── README.md
แต่ละ environment เป็น folder แยก state แยก ไม่มี config ปนกัน
ใช้กับ CI/CD
GitHub Actions workflow:
name: Terraform
on:
pull_request:
paths: [infrastructure/**]
push:
branches: [main]
paths: [infrastructure/**]
jobs:
terraform:
runs-on: ubuntu-latest
defaults:
run:
working-directory: infrastructure/environments/production
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform fmt -check
- run: terraform validate
- name: Plan
if: github.event_name == 'pull_request'
run: terraform plan -no-color
env:
DO_TOKEN: ${{ secrets.DO_TOKEN }}
- name: Apply
if: github.event_name == 'push'
run: terraform apply -auto-approve
env:
DO_TOKEN: ${{ secrets.DO_TOKEN }}
PR เห็น plan ก่อน merge → push เข้า main = apply
ข้อผิดพลาดที่เจอบ่อย
Commit terraform.tfstate เข้า git — มี secret อยู่ในนั้น (database password, API key) ใส่ .gitignore ตั้งแต่วันแรก
Apply ตอน PR ยังไม่ merge — แล้วเพื่อนใน team apply ทับ — เลยต้องตั้ง remote state lock
Hardcode resource ID — ใช้ data lookup แทน:
# ❌
resource "..." "..." {
vpc_id = "vpc-12345"
}
# ✅
data "aws_vpc" "main" {
tags = { Name = "main" }
}
resource "..." "..." {
vpc_id = data.aws_vpc.main.id
}
ลืม terraform plan ก่อน apply — ดู plan ทุกครั้ง อ่านบรรทัดที่ขึ้น - (ลบ) อย่างละเอียด
สรุป
IaC เปลี่ยนชีวิต DevOps — เปลี่ยน infra เป็น artifact ที่ review/diff/rollback ได้
เริ่มเล็กก่อน — แค่ droplet + DNS record ก็พอ พอชินแล้วค่อยขยายไป VPC, RDS, Load Balancer
อ่านเพิ่ม: Cloudflare ฟรี — Cloudflare มี Terraform provider ตั้ง DNS, page rule ผ่าน Terraform ได้