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

การติดตั้ง Terraform และโครงสร้างโปรเจกต์

ติดตั้ง Terraform ผ่าน version manager แทนที่จะ pin binary เดียวไว้ตายตัว เขียน GCP resource จริงตัวแรก แล้วแยกออกเป็นไฟล์หลายไฟล์ตามชื่อที่เป็นธรรมเนียม — ซึ่งจริง ๆ แล้ว Terraform ไม่ได้บังคับสักไฟล์เดียว

คุณดาวน์โหลด Terraform binary เดียวจาก HashiCorp มาวางไว้ใน PATH ได้ และก็ใช้งานได้ดีถ้าทดลองอยู่คนเดียวโปรเจกต์เดียว แต่พอต้องทำงานมากกว่าหนึ่งโปรเจกต์ วิธีนี้จะเริ่มมีปัญหา repository หนึ่งเขียนไว้กับ Terraform 1.5 อีกอันต้องการ feature ของ 1.9 ส่วนอีกอันย้ายไป OpenTofu แล้ว การคอยสลับ binary กลาง (global) ตัวเดียวให้ตรงกับโปรเจกต์ที่กำลังทำอยู่ตลอดเวลาเป็นงานที่น่าเบื่อและพลาดง่าย

version manager แก้ปัญหานี้ด้วยการติดตั้งหลายเวอร์ชันเก็บไว้คู่กัน แล้วสลับใช้ทีละโปรเจกต์ โดยมักอิงจาก version file ที่ commit ไว้ใน repository tenv เป็นตัวเลือกที่ดีเพราะไม่ได้ผูกกับเครื่องมือตัวเดียว จัดการ Terraform, OpenTofu, Terragrunt และ Atmos ได้จาก CLI ตัวเดียว ซึ่งสำคัญมากเมื่อทีมของคุณใช้ Terragrunt คู่กับ Terraform (จะพูดถึงในคอร์สนี้ตอนหลัง)

Terminal window
# install tenv (macOS, via Homebrew)
brew install tenv
# install and pin a specific Terraform version for the current directory
tenv tf install 1.9.0
tenv tf use 1.9.0
# tenv reads a .terraform-version file (or the terraform block's
# required_version constraint) to pick the right version automatically
terraform version

ไม่ว่าจะติดตั้งด้วยวิธีไหน คำสั่งที่ใช้งานกับ terraform binary ในแต่ละวันก็เหมือนกันทุกประการ — version manager เปลี่ยนแค่วิธีที่ binary นั้นมาอยู่บนเครื่องคุณเท่านั้น

โปรเจกต์ Terraform ก็แค่ directory ที่มีไฟล์ .tf หนึ่งไฟล์หรือมากกว่า ตัวที่เล็กที่สุดแต่ใช้งานได้จริงประกาศ provider กับ resource เดียว

terraform {
required_version = ">= 1.7.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
provider "google" {
project = "acme-app"
region = "us-central1"
}
resource "google_storage_bucket" "reports" {
name = "acme-monthly-reports"
location = "US"
}

นี่คือ configuration ที่สมบูรณ์และ apply ได้ทันที ไม่ต้องมี VPC ไม่ต้องมี networking ไม่มีข้อกำหนดล่วงหน้าอะไรเลยนอกจาก GCP credentials ในเครื่อง (Application Default Credentials ตั้งค่าด้วย gcloud auth application-default login) google_storage_bucket เป็น resource แรกที่ดีด้วยเหตุผลนี้เป๊ะ ๆ เพราะไม่ต้องพึ่งอะไรที่ต้องมีอยู่ก่อนเลย

โปรเจกต์จริงจะแยกไฟล์เดียวนั้นออกเป็นหลายไฟล์ ตามธรรมเนียมที่ใช้กัน

providers.tf
terraform {
required_version = ">= 1.7.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
provider "google" {
project = "acme-app"
region = "us-central1"
}
variables.tf
variable "bucket_name" {
description = "Name of the storage bucket to create"
type = string
default = "acme-monthly-reports"
}
main.tf
resource "google_storage_bucket" "reports" {
name = var.bucket_name
location = "US"
}
outputs.tf
output "bucket_self_link" {
description = "Self link of the created storage bucket"
value = google_storage_bucket.reports.self_link
}

ชื่อ main.tf, variables.tf, outputs.tf, providers.tf และ versions.tf เป็น ธรรมเนียม ไม่ใช่ข้อบังคับของ Terraform Terraform ไม่ได้สนใจชื่อไฟล์เลยแม้แต่น้อย แต่โหลดทุกไฟล์ .tf ใน directory มารวมกันเป็น configuration เดียว และไม่สนว่าคุณจะตั้งชื่อไฟล์ว่า main.tf, zzz.tf หรือ whatever.tf resource ที่อยู่ใน outputs.tf กับ variable ที่อ้างอิงถึงใน main.tf ทำงานเหมือนกันเป๊ะกับตอนที่ทุกอย่างอยู่ในไฟล์เดียว Terraform สร้าง dependency graph แบบเดียวกันเสมอไม่ว่าจะแยกไฟล์แบบไหน การแยกตามหน้าที่ (providers, variables, resources, outputs) มีไว้เพื่อให้คนที่เปิด directory ขึ้นมารู้ว่าต้องไปดูตรงไหนเท่านั้น ไม่ใช่เพราะ Terraform ต้องการเส้นแบ่งนั้น

flowchart TB
  subgraph dir["Project directory"]
    main["main.tf"]
    vars["variables.tf"]
    outs["outputs.tf"]
    prov["providers.tf"]
    ver["versions.tf"]
  end
  main --> combined["One combined Terraform configuration"]
  vars --> combined
  outs --> combined
  prov --> combined
  ver --> combined
Terraform โหลดทุกไฟล์ .tf ใน directory มารวมเป็น configuration เดียว

มีสองคำสั่งที่ควรรันก่อน commit ทุกครั้ง และรันบ่อยแค่ไหนก็ไม่มีต้นทุนอะไรเลย

terraform fmt เขียนไฟล์ทับในที่เดิมให้เป็น formatting มาตรฐานของ Terraform เช่น indentation ที่สม่ำเสมอ เครื่องหมาย = ที่จัดตำแหน่งตรงกัน spacing ที่สม่ำเสมอ แก้แค่ style ไม่ได้แก้ logic

Terminal window
terraform fmt

terraform validate ตรวจว่า configuration สอดคล้องกันเองภายใน เช่น HCL syntax ที่ถูกต้อง argument ที่ type ตรง และ variable ที่ถูกอ้างอิงถึงมีอยู่จริง จับ typo และความผิดพลาดเชิงโครงสร้างได้ตั้งแต่ก่อนที่จะติดต่อ GCP ด้วยซ้ำ

Terminal window
terraform validate
Success! The configuration is valid.

สิ่งที่ validate ไม่ได้ทำ คือตรวจสอบอะไรกับ GCP API จริง validate ไม่รู้เลยว่าโปรเจกต์ของคุณชน service quota หรือยัง ชื่อ bucket ที่คุณเลือกถูกใช้ไปแล้วทั่วโลกหรือเปล่า หรือ service account ของคุณมีสิทธิ์สร้าง resource นั้นจริงไหม ทั้งหมดนี้จะโผล่มาให้เห็นตอนรัน terraform plan เท่านั้น เพราะเป็นคำสั่งแรกที่ติดต่อกับ GCP จริง ๆ configuration ที่ผ่าน validate ได้อย่างสมบูรณ์ก็ยังพังตอน plan หรือ apply ได้อยู่ดี

Terraform สนใจไหมว่า resource ถูกประกาศไว้ใน main.tf หรือไฟล์ชื่ออื่น
terraform fmt ทำอะไร
terraform validate ตรวจสอบอะไร
ทำไมการผ่าน terraform validate ถึงไม่การันตีว่าจะไม่มีปัญหาฝั่ง GCP จริงเกิดขึ้น