ข้ามไปยังเนื้อหา

HCL, Provider และ Resource

configuration ของ Terraform ทุกส่วนคือ block เขียนด้วย HCL — terraform, provider, resource และ data คือสี่ตัวที่คุณจะเขียนบ่อยที่สุด รวมกันแล้วสี่ตัวนี้บอก Terraform ว่าต้องโหลด plugin อะไร คุยกับ GCP ยังไง ต้องสร้างอะไร และต้องอ่านอะไร

HCL (HashiCorp Configuration Language) สร้างจาก block รูปแบบทั่วไปคือ block type ตามด้วย label ที่ครอบด้วยเครื่องหมายคำพูดศูนย์ตัวขึ้นไป แล้วปิดท้ายด้วย body ของ argument ใน {}

block_type "label1" "label2" {
argument = value
}

อย่าง resource block มี label สองตัว ส่วน provider block มี label ตัวเดียว comment ใช้ # หรือ // สำหรับบรรทัดเดียว และ /* */ สำหรับหลายบรรทัด string รองรับ interpolation ด้วย ${...} แต่ถ้าอ้างอิงค่าอื่นตรง ๆ (name = google_storage_bucket.reports.name) ไม่ต้องครอบด้วย ${} — syntax แบบนั้นใช้เฉพาะตอนอยู่ข้างใน string ที่ยาวกว่านั้น

# A single-line comment.
// Also a single-line comment.
resource "google_storage_bucket" "reports" {
name = "acme-monthly-reports-${var.environment}" # interpolation inside a string
}

terraform block คือการตั้งค่า Terraform เอง ว่า Terraform เวอร์ชันไหนรัน configuration นี้ได้ และต้องใช้ provider อะไรบ้าง รวมถึงแต่ละ provider มาจากไหนและเวอร์ชันไหนที่รับได้

terraform {
required_version = ">= 1.7.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}

source คือ registry address ของ provider (hashicorp/google ชี้ไปที่ Google Cloud provider ตัวจริงบน Terraform Registry) ส่วน version เป็น constraint ไม่ใช่การ pin ตายตัว — ~> 6.0 อนุญาตทุก release ในสาย 6.x

จากนั้น provider block ตั้งค่า instance หนึ่งของ provider ที่ประกาศไว้ด้านบน สำหรับ GCP ตรงนี้คือที่อยู่ของ project กับ region (และในโปรเจกต์จริง ๆ ก็มีเรื่องอย่าง credential ด้วย)

provider "google" {
project = "acme-app"
region = "us-central1"
}

resource block คือวิธีบอก Terraform ว่า “สิ่งนี้ควรมีอยู่” resource block มี label สองตัวเสมอ

resource "google_compute_instance" "web" {
name = "web-vm"
machine_type = "e2-medium"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "debian-cloud/debian-12"
}
}
network_interface {
network = "default"
}
}
  • label แรก google_compute_instance คือ type ของ resource ถูกกำหนดโดย provider และเป็นตัวตัดสินว่า argument ไหนใช้ได้ และ GCP API ตัวไหนที่ Terraform จะเรียก
  • label ที่สอง web คือ name ที่คุณเลือกเอง มีความหมายแค่ภายใน configuration นี้เท่านั้น

รวมกันแล้วทั้งสองตัวคือ address ของ resource นั้น คือ google_compute_instance.web ที่ใช้อ้างอิง attribute ของ resource นี้จากที่ไหนก็ได้ใน configuration ของคุณ เช่น google_compute_instance.web.id หรือ google_compute_instance.web.self_link

data block อ่านข้อมูลของสิ่งที่มีอยู่แล้ว คือ infrastructure ที่ configuration นี้ไม่ได้จัดการและจะไม่มีวันสร้าง อัปเดต หรือลบ นี่คือ read-only lookup ของ Terraform

data "google_compute_image" "debian" {
family = "debian-12"
project = "debian-cloud"
}
resource "google_compute_instance" "web" {
name = "web-vm"
machine_type = "e2-medium"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = data.google_compute_image.debian.self_link
}
}
network_interface {
network = "default"
}
}

data.google_compute_image.debian.self_link อ่านเหมือน resource address เป๊ะ ๆ แค่มี data. นำหน้า — label type กับ name ทำงานแบบเดียวกัน แต่ไม่มีอะไรจาก data block ที่จะโผล่เป็น create, update หรือ destroy ใน plan เลย เพราะมีหน้าที่อ่านอย่างเดียว

Terraform ไม่ได้ apply configuration ของคุณเรียงจากบนลงล่างตามที่เขียนไว้ แต่สร้าง DAG (directed acyclic graph) ของทุก resource และ data source แล้วใช้กราฟนั้นหาลำดับที่ถูกต้อง — พร้อมทั้งดูว่า resource ตัวไหนเป็นอิสระจากกันพอที่จะสร้าง พร้อมกัน ได้

edge ในกราฟนั้นมาจากสองที่

  • Implicit dependency เมื่อไหร่ที่ argument ของ resource หนึ่งอ้างอิง attribute ของอีก resource หนึ่ง (เช่น google_compute_subnetwork.app อ้างอิง google_compute_network.vpc.id หรือ instance อ้างอิง ID ของ subnetwork) Terraform จะสรุปเองว่า resource ที่ถูกอ้างอิงต้องมีอยู่ก่อน
  • Explicit dependency คือ depends_on บน resource ใช้ตอนที่ resource หนึ่งขึ้นกับอีกตัวจริง ๆ แต่ความสัมพันธ์นั้นไม่โผล่ผ่าน attribute reference ไหนเลย (เช่น IAM binding ที่ต้องมีอยู่ก่อนแอปเริ่มพึ่งพา โดยไม่มี argument เชื่อมกันตรง ๆ)
resource "google_compute_network" "vpc" {
name = "app-network"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "app" {
name = "app-subnet"
ip_cidr_range = "10.0.1.0/24"
region = "us-central1"
network = google_compute_network.vpc.id # implicit dependency on google_compute_network.vpc
}
resource "google_compute_instance" "web" {
name = "web-vm"
machine_type = "e2-medium"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = data.google_compute_image.debian.self_link
}
}
network_interface {
subnetwork = google_compute_subnetwork.app.id # implicit dependency on google_compute_subnetwork.app
}
}
flowchart LR
  network["google_compute_network.vpc"] --> subnet["google_compute_subnetwork.app"] --> instance["google_compute_instance.web"]
  bucket["google_storage_bucket.logs (unrelated, applies in parallel)"]
Terraform builds a DAG from references, applying independent branches in parallel
ใน `resource "google_compute_instance" "web" { ... }` สอง label นี้คืออะไร
ความต่างหลักระหว่าง resource block กับ data block คืออะไร
Terraform ตัดสินใจลำดับการสร้าง resource ยังไง
ข้อไหนอธิบาย required_providers ใน terraform block ได้ถูกต้อง