← กลับ
TerraformIaCInfrastructure

Terraform เริ่มต้น — Infrastructure as Code คืออะไร ทำไมต้องใช้

เลิก click UI ใน AWS console เปลี่ยนทุกอย่างเป็น code review ได้ deploy ซ้ำได้ ทุกการเปลี่ยน infra อยู่ใน git

2026-03-02อ่าน 6 นาทีใหม่

ปัญหาก่อนมี 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 จะ:

  1. อ่าน state
  2. เทียบกับ config ใน .tf
  3. คำนวณว่าต้องเปลี่ยน/เพิ่ม/ลบอะไร

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 ได้

← ดูบทความอื่น