พื้นฐานไวยากรณ์ HCL สำหรับ Terraform

HashiCorp Configuration Language หรือ HCL เป็นภาษาที่ออกแบบมาเพื่ออธิบายโครงสร้างพื้นฐาน (infrastructure) ในรูปแบบประกาศ (declarative) อ่านง่ายทั้งคนและเครื่อง ใช้เป็นภาษาหลักใน Terraform, Vault, Consul และ Nomad ผู้ที่เพิ่งเริ่มต้นควรเข้าใจไวยากรณ์พื้นฐานก่อน เพราะทุก resource ที่เขียนในโปรเจกต์ล้วนอยู่ในรูปแบบเดียวกัน หากสะดุดกับ syntax ตั้งแต่ต้นจะแก้บั๊กยากขึ้นเมื่อโปรเจกต์ใหญ่ขึ้น

บทความนี้จะสรุปองค์ประกอบหลักของ HCL ได้แก่ block, argument, expression, type ข้อมูล การอ้างอิงข้ามทรัพยากร การใช้ comment และตัวอย่างที่พบในไฟล์ .tf จริง ผู้อ่านจะสามารถนำไปประยุกต์เขียน configuration ได้ทันที

โครงสร้างของ Block

Block คือหน่วยพื้นฐานที่สุด ประกอบด้วย type, label (อาจมีหนึ่งหรือหลายตัว) และ body ปีกกาครอบเนื้อหาไว้ด้านใน ตัวอย่างของ block ใน Terraform ได้แก่ terraform, provider, resource, variable, output, module เป็นต้น

resource "aws_instance" "web" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"
  tags = {
    Name = "web-server"
  }
}

ในตัวอย่างนี้ resource คือ block type, "aws_instance" คือ resource type, "web" คือ local name ที่ใช้อ้างอิงภายในไฟล์ และภายในปีกกาคือ body ที่บรรจุ argument ของ resource

Argument และ Attribute

Argument คือการกำหนดค่าให้ identifier ในรูปแบบ name = value ค่าที่ส่งให้อาจเป็น literal (string, number, bool), list, map หรือ expression ที่อ้างถึงค่าที่คำนวณจากที่อื่น HCL อนุญาตให้จัดเรียงบรรทัดอย่างอิสระและใช้ comma หรือ newline เป็นตัวคั่น

# string
region = "ap-southeast-1"

# number
instance_count = 3

# boolean
enabled = true

# list
zones = ["a", "b", "c"]

# map
tags = {
  Environment = "production"
  Owner       = "platform-team"
}

Type ข้อมูลหลัก

  • Primitive: string, number, bool — ชนิดพื้นฐาน
  • Collection: list, set, map — เก็บค่าหลายตัวเรียงหรือ key-value
  • Structural: object, tuple — ใช้ผสมชนิดหลากหลายได้
  • Null: ค่าว่างที่หมายถึง “ไม่ตั้งค่า” ใช้ omit argument ที่ไม่ต้องการส่ง

Expression และ Interpolation

Expression คือสูตรที่คำนวณค่าได้ขณะรัน plan/apply ใช้อ้างอิง variable, resource attribute, data source หรือ built-in function รูปแบบการอ้างอิง: var.ชื่อ สำหรับ input variable, local.ชื่อ สำหรับ local value, และ ประเภท.ชื่อ.attribute สำหรับ resource

variable "env" {
  default = "dev"
}

resource "aws_s3_bucket" "logs" {
  bucket = "logs-${var.env}-${random_id.suffix.hex}"
}

resource "aws_instance" "app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.env == "prod" ? "t3.large" : "t3.micro"
}

รูปแบบ ${...} เรียกว่า string interpolation ใช้ฝัง expression ลงใน string ได้ตรง ๆ ส่วน ?: คือ conditional expression สำหรับเลือกค่าตามเงื่อนไข

Built-in Function ที่ใช้บ่อย

HCL มี function สำเร็จรูปจำนวนมาก ช่วยจัดการ string, collection, encoding, hash, filesystem และอื่น ๆ โดยไม่ต้องเขียนโค้ดเอง

upper("hello")                    # "HELLO"
join("-", ["web", "01"])          # "web-01"
length(["a", "b", "c"])           # 3
lookup(var.tags, "Env", "dev")    # หา key "Env" ใน map
file("${path.module}/user.sh")    # อ่านไฟล์ในโฟลเดอร์ module
jsonencode({name = "app"})        # สร้าง JSON
cidrsubnet("10.0.0.0/16", 8, 2)   # "10.0.2.0/24"

Comment

HCL รองรับ comment สามแบบ: # และ // สำหรับ single line และ /* ... */ สำหรับ multi-line การเขียน comment สำคัญต่อทีมมาก เพราะ config ที่ซับซ้อนอาจต้องอธิบาย business logic ที่ code เองไม่สื่อ

# comment ลักษณะ shell-style
// comment ลักษณะ C-style

/*
 * สามารถขยายได้หลายบรรทัด
 * เหมาะกับอธิบาย header
 */
resource "aws_instance" "web" {
  # ...
}

Heredoc String

เมื่อต้องใส่ text หลายบรรทัด เช่น user-data script หรือ policy JSON การใช้ heredoc จะสะดวกกว่าใส่ escape ทุกบรรทัด ใช้ <<EOT เปิด และ EOT ปิด (ใส่ชื่อ marker อะไรก็ได้) หากต้องการให้ตัด whitespace ต้นบรรทัด ใช้ <<-EOT แทน

user_data = <<-EOT
  #!/bin/bash
  apt-get update
  apt-get install -y nginx
  systemctl enable nginx
EOT

Reference ข้าม Resource

จุดแข็งหลักของ HCL คือ dependency graph อัตโนมัติ เมื่อ resource หนึ่งอ้างถึง attribute ของอีก resource Terraform จะสร้าง resource ที่ถูกอ้างก่อนให้เสร็จเสมอ ไม่ต้องกำหนดลำดับเอง

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "web" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"
}

บรรทัด aws_vpc.main.id บอกให้สร้าง VPC ก่อน จึงนำ id ไปใส่ที่ subnet การใช้ graph แบบนี้ช่วยลดข้อผิดพลาดมากเมื่อมี resource หลายสิบตัวเกี่ยวข้องกัน

Meta-argument ที่สำคัญ

  • count — จำลอง resource เป็นจำนวน N ตัว เข้าถึงผ่าน index
  • for_each — สร้าง resource ตาม key ของ map หรือ set
  • depends_on — ระบุ dependency แบบ explicit เมื่อไม่มี attribute reference
  • lifecycle — ควบคุมพฤติกรรมเช่น create_before_destroy, prevent_destroy
  • provider — ระบุ provider alias เมื่อใช้หลาย region/account

แนวทางเขียน HCL ให้อ่านง่าย

  • จัด indent 2 spaces เสมอ (Terraform convention) และรัน terraform fmt ก่อน commit
  • จัดกลุ่ม argument: ค่า required ด้านบน, ค่า optional ด้านล่าง, block ซ้อนอยู่ท้ายสุด
  • หลีกเลี่ยงบรรทัดยาวเกิน 100 ตัวอักษร หากจำเป็นให้แยก expression เข้า local value
  • ตั้งชื่อ resource เป็น snake_case และสั้น กระชับ อธิบายหน้าที่
  • แยกไฟล์ตามหน้าที่: main.tf, variables.tf, outputs.tf, providers.tf

สรุป

HCL เป็นภาษาที่อ่านง่ายแต่มีความสามารถสูงเพียงพอต่อการอธิบายโครงสร้างพื้นฐานจริงในระบบ production องค์ประกอบหลักคือ block ซึ่งมี argument ภายใน รองรับ type ข้อมูลหลากหลาย และ expression ที่อ้างอิงข้ามทรัพยากรได้โดยอัตโนมัติ ผู้เริ่มต้นควรฝึกเขียนตัวอย่างง่ายหลาย ๆ ไฟล์ และใช้ terraform fmt กับ terraform validate เพื่อให้โค้ดสะอาดและถูกไวยากรณ์ก่อน apply เมื่อคุ้นกับโครงสร้างเหล่านี้แล้ว การอ่าน configuration ของคนอื่นจะง่ายขึ้นมาก และการขยายโปรเจกต์ไปสู่ module, workspace หรือ multi-cloud จะไม่เป็นอุปสรรค