Infrastructure as Code (IaC) เปลี่ยนวิธีที่เราจัดการ Infrastructure จากการ Click ผ่าน Console มาเป็นการเขียน Code ที่ Version Control ได้ Review ได้ Test ได้ และ Reproduce ได้ แต่แค่ใช้ IaC Tool อย่างเดียวยังไม่พอ — คุณต้องรู้ Patterns ที่ถูกต้องด้วย
บทความนี้จะสอน IaC Patterns ที่ใช้จริงใน Production ตั้งแต่หลักการพื้นฐานจนถึงแนวปฏิบัติขั้นสูง ครอบคลุม Terraform, Pulumi, CDK และ Tools อื่นๆ
หลักการพื้นฐานของ IaC
Idempotency
รัน Code เดิมกี่ครั้งก็ได้ผลลัพธ์เหมือนกัน ไม่สร้าง Resource ซ้ำ ไม่พัง State
Immutability
แทนที่จะแก้ไข Server ที่มีอยู่ (Mutable) ให้สร้าง Server ใหม่แล้วทำลายตัวเก่า (Immutable) ลด Configuration Drift
Declarative vs Imperative
| แนวทาง | คำอธิบาย | ตัวอย่าง Tools |
|---|---|---|
| Declarative | บอกว่า "ต้องการอะไร" — Tool จัดการให้ | Terraform, CloudFormation, Crossplane |
| Imperative | บอกว่า "ทำอย่างไร" — สั่งทีละขั้นตอน | Ansible, Shell Scripts, Pulumi (บางส่วน) |
IaC Tools Landscape 2026
| Tool | ภาษา | แนวทาง | จุดเด่น | Provider Support |
|---|---|---|---|---|
| Terraform | HCL | Declarative | Ecosystem ใหญ่ที่สุด, Multi-cloud | ทุก Cloud + SaaS |
| OpenTofu | HCL | Declarative | Fork ของ Terraform (Open Source) | เหมือน Terraform |
| Pulumi | Python/TS/Go/C# | Declarative+ | ใช้ภาษา Programming จริง | ทุก Cloud |
| AWS CDK | TS/Python/Java/C# | Declarative+ | สร้าง CloudFormation จาก Code | AWS เท่านั้น |
| CDKTF | TS/Python/Go/C# | Declarative+ | CDK + Terraform Provider | ทุก Cloud |
| CloudFormation | YAML/JSON | Declarative | Native AWS, ไม่ต้อง State file | AWS เท่านั้น |
| Crossplane | YAML (K8s CRDs) | Declarative | จัดการ Cloud ผ่าน Kubernetes | ทุก Cloud |
| Ansible | YAML | Imperative | Configuration Management ดีมาก | ทุก Cloud + On-prem |
Module / Component Patterns
Module คือหน่วยย่อยที่สามารถ Reuse ได้ เหมือน Function ในการเขียนโปรแกรม:
Terraform Module Structure
# โครงสร้าง Module
modules/
├── vpc/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
├── eks/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── rds/
├── main.tf
├── variables.tf
└── outputs.tf
# เรียกใช้ Module
module "vpc" {
source = "./modules/vpc"
# หรือจาก Registry
# source = "terraform-aws-modules/vpc/aws"
# version = "5.5.0"
name = "production-vpc"
cidr_block = "10.0.0.0/16"
azs = ["ap-southeast-1a", "ap-southeast-1b", "ap-southeast-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = false
}
Module Versioning
# ใช้ Version Pinning เสมอ
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.5" # >= 5.5.0, < 6.0.0
}
# สำหรับ Private Module Registry
module "app" {
source = "app.terraform.io/myorg/app-stack/aws"
version = "2.1.0"
}
Environment Management
Pattern 1: Workspaces (Terraform)
# สร้าง Workspace ต่อ Environment
terraform workspace new staging
terraform workspace new production
# สลับ
terraform workspace select staging
# ใน Code ใช้ terraform.workspace
locals {
env = terraform.workspace
instance_type = {
staging = "t3.small"
production = "t3.xlarge"
}
}
resource "aws_instance" "app" {
instance_type = local.instance_type[local.env]
}
Pattern 2: Directory-based (แนะนำ)
# โครงสร้างแยก Directory ต่อ Environment
infrastructure/
├── modules/ # Shared modules
│ ├── vpc/
│ ├── eks/
│ └── rds/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ └── production/
│ ├── main.tf
│ ├── terraform.tfvars
│ └── backend.tf
# แต่ละ Environment มี State file แยก = ปลอดภัยกว่า
terraform destroy จะไปลบ Production โดยไม่ตั้งใจ
State Management Patterns
Remote State
# backend.tf — เก็บ State ใน S3 + DynamoDB Lock
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "production/vpc/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
# สร้าง DynamoDB Table สำหรับ State Locking
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
State Splitting
# แยก State ตาม Layer เพื่อลดความเสี่ยง
# Layer 1: Network (เปลี่ยนน้อย)
infrastructure/network/
→ state: s3://state/network/terraform.tfstate
# Layer 2: Data (Database, Cache)
infrastructure/data/
→ state: s3://state/data/terraform.tfstate
# Layer 3: Compute (เปลี่ยนบ่อย)
infrastructure/compute/
→ state: s3://state/compute/terraform.tfstate
# อ้างอิง State ข้าม Layer ด้วย data source
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "mycompany-terraform-state"
key = "production/network/terraform.tfstate"
region = "ap-southeast-1"
}
}
resource "aws_instance" "app" {
subnet_id = data.terraform_remote_state.network.outputs.private_subnet_ids[0]
}
Secret Management in IaC
# WRONG: อย่าเก็บ Secret ใน Code
resource "aws_db_instance" "main" {
password = "SuperSecret123!" # NEVER DO THIS!
}
# Pattern 1: Vault Integration
data "vault_generic_secret" "db" {
path = "secret/data/production/db"
}
resource "aws_db_instance" "main" {
password = data.vault_generic_secret.db.data["password"]
}
# Pattern 2: AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db" {
secret_id = "production/db-password"
}
resource "aws_db_instance" "main" {
password = jsondecode(data.aws_secretsmanager_secret_version.db.secret_string)["password"]
}
# Pattern 3: SOPS (Encrypted files in Git)
# sops --encrypt --age age1... secrets.yaml > secrets.enc.yaml
data "sops_file" "secrets" {
source_file = "secrets.enc.yaml"
}
resource "aws_db_instance" "main" {
password = data.sops_file.secrets.data["db_password"]
}
Testing IaC
Static Analysis (ไม่ต้อง Apply)
# Terraform Validate
terraform validate
# Terraform Plan Review
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan | jq .
# tfsec — Security Scanner
tfsec .
# ผลลัพธ์: แจ้งเตือนเรื่อง Security เช่น S3 bucket ไม่เข้ารหัส
# Checkov — Policy-as-Code Scanner
checkov -d .
# ตรวจ 1000+ rules ครอบคลุม AWS, Azure, GCP, K8s
# KICS — Infrastructure Queries
kics scan -p .
Integration Testing (Terratest)
// Go: ใช้ Terratest สร้าง Infrastructure จริง แล้ว Test แล้วทำลาย
func TestVpcModule(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"name": "test-vpc",
"cidr_block": "10.99.0.0/16",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcID)
// ตรวจสอบว่า VPC สร้างจริง
vpc := aws.GetVpcById(t, vpcID, "ap-southeast-1")
assert.Equal(t, "10.99.0.0/16", vpc.CidrBlock)
}
Policy-as-Code (OPA / Sentinel)
# OPA (Open Policy Agent) Rego Policy
# ห้ามสร้าง Public S3 Bucket
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
resource.change.after.acl == "public-read"
msg := sprintf("S3 bucket '%s' must not be public", [resource.address])
}
# ห้ามใช้ Instance ใหญ่เกินไป
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_instance"
not startswith(resource.change.after.instance_type, "t3.")
msg := sprintf("Instance '%s' must use t3.* type", [resource.address])
}
CI/CD for IaC
GitOps Workflow
# GitHub Actions: Terraform CI/CD
name: Terraform
on:
pull_request:
paths: ['infrastructure/**']
push:
branches: [main]
paths: ['infrastructure/**']
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Terraform Init
run: terraform init
working-directory: infrastructure/environments/production
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Validate
run: terraform validate
- name: tfsec Security Scan
uses: aquasecurity/tfsec-action@v1.0.0
- name: Terraform Plan
if: github.event_name == 'pull_request'
run: terraform plan -no-color -out=plan.tfplan
- name: Comment Plan on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
// Post plan output as PR comment
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve plan.tfplan
Atlantis (Self-hosted)
# atlantis.yaml — Terraform PR Automation
version: 3
projects:
- name: production-vpc
dir: infrastructure/environments/production/vpc
workflow: production
autoplan:
when_modified: ["*.tf", "*.tfvars"]
enabled: true
workflows:
production:
plan:
steps:
- init
- plan:
extra_args: ["-lock=false"]
apply:
steps:
- apply
# PR Comment Commands:
# atlantis plan — Run plan
# atlantis apply — Apply changes (requires approval)
Drift Detection and Remediation
Drift เกิดเมื่อ Infrastructure จริงไม่ตรงกับ Code (เช่น คนไปแก้ผ่าน Console):
# ตรวจจับ Drift
terraform plan -detailed-exitcode
# Exit code 0 = no changes
# Exit code 1 = error
# Exit code 2 = changes detected (DRIFT!)
# Remediation Options:
# 1. terraform apply — บังคับให้ตรงกับ Code
# 2. terraform import — Import resource ที่สร้างมือเข้ามา
# 3. terraform state rm — ลบ resource จาก State (ไม่ลบจริง)
# Automated Drift Detection (Cron)
# รัน terraform plan ทุก 6 ชั่วโมง แล้ว Alert ถ้าเจอ Drift
0 */6 * * * cd /infra/production && terraform plan -detailed-exitcode || alert "Drift detected!"
IaC for Multi-Cloud
# Terraform: Multi-Cloud in one project
provider "aws" {
region = "ap-southeast-1"
alias = "singapore"
}
provider "google" {
project = "my-gcp-project"
region = "asia-southeast1"
}
# AWS Resources
resource "aws_s3_bucket" "primary" {
provider = aws.singapore
bucket = "my-primary-data"
}
# GCP Resources (DR)
resource "google_storage_bucket" "dr" {
name = "my-dr-data"
location = "ASIA-SOUTHEAST1"
}
# Cross-cloud networking
resource "aws_vpn_gateway" "aws_side" {
vpc_id = module.vpc.vpc_id
}
resource "google_compute_vpn_gateway" "gcp_side" {
name = "aws-interconnect"
network = module.gcp_vpc.network_id
}
Monorepo vs Polyrepo for IaC
| แนวทาง | จุดเด่น | จุดด้อย | เหมาะกับ |
|---|---|---|---|
| Monorepo | ค้นหาง่าย, Atomic changes, Shared modules ง่าย | CI ช้า, Permission ซับซ้อน | ทีมเล็ก-กลาง |
| Polyrepo | แยก Permission ชัด, CI เร็ว, Team ownership | Code duplication, Dependency management | ทีมใหญ่, หลาย Business Unit |
# Monorepo Structure
infrastructure/
├── modules/ # Shared modules
├── environments/
│ ├── dev/
│ ├── staging/
│ └── production/
├── policies/ # OPA policies
├── tests/ # Terratest
├── .github/workflows/ # CI/CD
└── Makefile
# Polyrepo Structure
org/infra-modules # Shared module library
org/infra-network # Network team owns this
org/infra-compute # Compute team owns this
org/infra-data # Data team owns this
Cost Estimation (Infracost)
# ติดตั้ง
brew install infracost
# ดูค่าใช้จ่ายก่อน Apply
infracost breakdown --path .
# ผลลัพธ์:
# Name Monthly Qty Unit Monthly Cost
# aws_instance.app
# ├─ Instance usage 730 hrs $60.74
# ├─ root_block_device 50 GB $5.00
# aws_rds_cluster.main
# ├─ Instance usage 730 hrs $175.20
# ├─ Storage 100 GB $10.00
# OVERALL TOTAL $250.94/mo
# เปรียบเทียบ Cost ก่อน/หลังเปลี่ยน
infracost diff --path . --compare-to infracost-base.json
# ใส่ใน PR Comment
infracost comment github \
--path /tmp/infracost.json \
--repo myorg/infra \
--pull-request 42 \
--github-token $GITHUB_TOKEN
IaC Documentation (terraform-docs)
# ติดตั้ง
brew install terraform-docs
# สร้าง README.md อัตโนมัติจาก Terraform code
terraform-docs markdown table ./modules/vpc > ./modules/vpc/README.md
# ผลลัพธ์: สร้างตาราง Inputs, Outputs, Resources, Requirements
# ใส่ใน pre-commit hook เพื่อ Auto-update
# .pre-commit-config.yaml
repos:
- repo: https://github.com/terraform-docs/terraform-docs
rev: v0.18.0
hooks:
- id: terraform-docs-go
args: ["markdown", "table", "--output-file", "README.md"]
Migration from ClickOps to IaC
- Inventory — สำรวจ Resource ทั้งหมดที่มี (ใช้ AWS Config, Cloud Asset Inventory)
- Import — นำ Resource เข้า Terraform State
- Generate — ใช้ Tool สร้าง Code จาก Resource ที่มี
- Validate —
terraform planต้องแสดง "No changes" - Iterate — Refactor Code ให้ใช้ Modules
# Import Resource ที่สร้างมือ
terraform import aws_instance.app i-0123456789abcdef
# ใช้ Terraformer สร้าง Code อัตโนมัติ
terraformer import aws --resources=vpc,subnet,sg --regions=ap-southeast-1
# ใช้ terraform plan เช็คว่า Import ถูกต้อง
terraform plan
# Expected: "No changes. Your infrastructure matches the configuration."
IaC Anti-Patterns (สิ่งที่ไม่ควรทำ)
- Hardcoded Values — ใส่ค่าตรงๆ ใน Code แทนที่จะใช้ Variables
- God Module — Module เดียวทำทุกอย่าง (VPC + EKS + RDS + App) ควรแยกเป็น Module ย่อย
- No State Locking — หลายคน Apply พร้อมกัน State พัง
- Secrets in Code — เก็บ Password, API Key ใน .tf files
- No Plan Review — Apply โดยไม่ดู Plan ก่อน
- Manual State Manipulation — แก้ State file ด้วยมือ
- Ignoring Drift — ปล่อยให้คนแก้ผ่าน Console โดยไม่ Import กลับ
- No Testing — ไม่มี Validate, tfsec, หรือ Integration Test
- Unpinned Versions — ไม่ Pin Provider/Module Version ทำให้ Build ไม่ Reproducible
สรุป
Infrastructure as Code ไม่ใช่แค่เรื่องของ Tool แต่เป็นวิธีคิดที่ว่า Infrastructure ควรถูกจัดการเหมือน Software — มี Version Control, Code Review, Testing, CI/CD และ Documentation ที่ดี
ในปี 2026 Terraform (หรือ OpenTofu) ยังคงเป็น Tool ที่ได้รับความนิยมสูงสุด แต่ Pulumi และ CDK กำลังเติบโตเร็วมากสำหรับทีมที่ต้องการใช้ภาษา Programming จริงๆ ไม่ว่าจะเลือก Tool ไหน สิ่งสำคัญที่สุดคือ Patterns ที่ถูกต้อง: แยก State, ใช้ Modules, Test ก่อน Apply, และอย่าเก็บ Secret ใน Code
