HCL, Provider และ Resource
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”configuration ของ Terraform ทุกส่วนคือ block เขียนด้วย HCL — terraform, provider, resource และ data คือสี่ตัวที่คุณจะเขียนบ่อยที่สุด รวมกันแล้วสี่ตัวนี้บอก Terraform ว่าต้องโหลด plugin อะไร คุยกับ AWS ยังไง ต้องสร้างอะไร และต้องอ่านอะไร
HCL block syntax
หัวข้อที่มีชื่อว่า “HCL block syntax”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 ด้วย ${...} แต่ถ้าอ้างอิงค่าอื่นตรง ๆ (bucket = aws_s3_bucket.reports.id) ไม่ต้องครอบด้วย ${} — syntax แบบนั้นใช้เฉพาะตอนอยู่ข้างใน string ที่ยาวกว่านั้น
# A single-line comment.// Also a single-line comment.
resource "aws_s3_bucket" "reports" { bucket = "acme-monthly-reports-${var.environment}" # interpolation inside a string}terraform block กับ provider block
หัวข้อที่มีชื่อว่า “terraform block กับ provider block”terraform block คือการตั้งค่า Terraform เอง ว่า Terraform เวอร์ชันไหนรัน configuration นี้ได้ และต้องใช้ provider อะไรบ้าง รวมถึงแต่ละ provider มาจากไหนและเวอร์ชันไหนที่รับได้
terraform { required_version = ">= 1.7.0"
required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } }}source คือ registry address ของ provider (hashicorp/aws ชี้ไปที่ AWS provider ตัวจริงบน Terraform Registry) ส่วน version เป็น constraint ไม่ใช่การ pin ตายตัว — ~> 5.0 อนุญาตทุก release ในสาย 5.x
จากนั้น provider block ตั้งค่า instance หนึ่งของ provider ที่ประกาศไว้ด้านบน สำหรับ AWS ตรงนี้คือที่อยู่ของ region (และในโปรเจกต์จริง ๆ ก็มีเรื่องอย่าง assumed role ด้วย)
provider "aws" { region = "us-east-1"}resource block: สอง label ที่มีความหมาย
หัวข้อที่มีชื่อว่า “resource block: สอง label ที่มีความหมาย”resource block คือวิธีบอก Terraform ว่า “สิ่งนี้ควรมีอยู่” resource block มี label สองตัวเสมอ
resource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t3.micro"}- label แรก
aws_instanceคือ type ของ resource ถูกกำหนดโดย provider และเป็นตัวตัดสินว่า argument ไหนใช้ได้ และ AWS API ตัวไหนที่ Terraform จะเรียก - label ที่สอง
webคือ name ที่คุณเลือกเอง มีความหมายแค่ภายใน configuration นี้เท่านั้น
รวมกันแล้วทั้งสองตัวคือ address ของ resource นั้น คือ aws_instance.web ที่ใช้อ้างอิง attribute ของ resource นี้จากที่ไหนก็ได้ใน configuration ของคุณ เช่น aws_instance.web.id หรือ aws_instance.web.public_ip
data block: อ่านโดยไม่เป็นเจ้าของ
หัวข้อที่มีชื่อว่า “data block: อ่านโดยไม่เป็นเจ้าของ”data block อ่านข้อมูลของสิ่งที่มีอยู่แล้ว คือ infrastructure ที่ configuration นี้ไม่ได้จัดการและจะไม่มีวันสร้าง อัปเดต หรือลบ นี่คือ read-only lookup ของ Terraform
data "aws_ami" "latest" { most_recent = true owners = ["amazon"]
filter { name = "name" values = ["al2023-ami-*-x86_64"] }}
resource "aws_instance" "web" { ami = data.aws_ami.latest.id instance_type = "t3.micro"}data.aws_ami.latest.id อ่านเหมือน resource address เป๊ะ ๆ แค่มี data. นำหน้า — label type กับ name ทำงานแบบเดียวกัน แต่ไม่มีอะไรจาก data block ที่จะโผล่เป็น create, update หรือ destroy ใน plan เลย เพราะมีหน้าที่อ่านอย่างเดียว
Dependency graph
หัวข้อที่มีชื่อว่า “Dependency graph”Terraform ไม่ได้ apply configuration ของคุณเรียงจากบนลงล่างตามที่เขียนไว้ แต่สร้าง DAG (directed acyclic graph) ของทุก resource และ data source แล้วใช้กราฟนั้นหาลำดับที่ถูกต้อง — พร้อมทั้งดูว่า resource ตัวไหนเป็นอิสระจากกันพอที่จะสร้าง พร้อมกัน ได้
edge ในกราฟนั้นมาจากสองที่
- Implicit dependency เมื่อไหร่ที่ argument ของ resource หนึ่งอ้างอิง attribute ของอีก resource หนึ่ง (เช่น
aws_instance.webอ้างอิงdata.aws_ami.latest.idหรือ subnet อ้างอิง ID ของ VPC) Terraform จะสรุปเองว่า resource ที่ถูกอ้างอิงต้องมีอยู่ก่อน - Explicit dependency คือ
depends_onบน resource ใช้ตอนที่ resource หนึ่งขึ้นกับอีกตัวจริง ๆ แต่ความสัมพันธ์นั้นไม่โผล่ผ่าน attribute reference ไหนเลย (เช่น IAM policy ที่ต้องมีอยู่ก่อนแอปเริ่มพึ่งพา โดยไม่มี argument เชื่อมกันตรง ๆ)
resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16"}
resource "aws_subnet" "app" { vpc_id = aws_vpc.main.id # implicit dependency on aws_vpc.main cidr_block = "10.0.1.0/24"}
resource "aws_instance" "web" { subnet_id = aws_subnet.app.id # implicit dependency on aws_subnet.app ami = data.aws_ami.latest.id instance_type = "t3.micro"}flowchart LR vpc["aws_vpc.main"] --> subnet["aws_subnet.app"] --> instance["aws_instance.web"] bucket["aws_s3_bucket.logs (unrelated, applies in parallel)"]