第 5 章:IaC 入門 — 用 Terraform 佈建雲端基礎設施

第 5 章:IaC 入門 — 用 Terraform 佈建雲端基礎設施

這一章在做什麼? 我們第一次碰雲端,但先不碰會花錢的東西。先理解「基礎設施即程式碼(IaC)」的概念,裝好 gcloudterraform、完成 GCP 授權,然後用 Terraform 寫出第一份設定:啟用之後會用到的 API、建立一個部署用的服務帳號做完這章,你會掌握 Terraform 的核心循環 init → plan → apply → destroy 與「狀態(state)」概念——這是後面所有雲端章節的操作基礎。

為什麼要 IaC? 因為在雲端 console 用滑鼠點出來的東西,沒人記得點了什麼、也很難重現或交接。把「要哪些資源」寫成設定檔,就能版控、可重現、可一鍵建立或銷毀。

💸 本章只建免費資源:啟用 API 與建立服務帳號不收費。真正會計費的 Cloud Run / Cloud SQL 從第 6 章才出現。但我們現在就把 destroy 的習慣學起來。


步驟 1:先搞懂 IaC、宣告式、Terraform

IaC(Infrastructure as Code,基礎設施即程式碼)

用設定檔(程式碼)描述你要的雲端資源,而不是在網頁 console 手動點。好處:可版控、可重現、可審查、可一鍵銷毀。

宣告式(declarative)

你描述「我要的最終狀態」(例如「我要一個叫 nlt-app 的服務帳號」),工具自己算出該做什麼來達成。對比「命令式」是你一步步下指令。

🔁 前端類比:宣告式就像 React——你宣告「UI 應該長這樣」,React 自己算出要怎麼更新 DOM。Terraform 是「雲端資源的 React」:你宣告「基礎設施應該長這樣」,它算出要建立/修改/刪除什麼。

Terraform

最主流的 IaC 工具。你用它的語言(HCL)寫設定,它透過各家雲的 provider(外掛)去建立資源,並把「我建了哪些東西」記在一個**狀態檔(state)**裡。

核心四個指令(整章會一直用):

指令 作用
terraform init 下載 provider、初始化(每個新專案/新 provider 跑一次)
terraform plan 預演:列出「將會建立/修改/刪除什麼」,但不真的動手
terraform apply 真的執行變更,達成你宣告的狀態
terraform destroy 把這份設定建立的資源全部銷毀(省錢關鍵指令)

步驟 2:安裝工具並完成 GCP 授權

  1. 安裝 gcloud CLI(GCP 的官方命令列工具):
# macOS(擇一)
brew install --cask google-cloud-sdk
# 或參考官方安裝:https://cloud.google.com/sdk/docs/install
gcloud version
  1. 安裝 Terraform
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform version    # 預期 >= 1.6
  1. 登入並設定專案(把 你的-project-id 換成第 1 章記下的 Project ID):
gcloud auth login                                  # 開瀏覽器登入你的 Google 帳號
gcloud config set project 你的-project-id

# 關鍵:提供 Terraform 用的「應用程式預設憑證 (ADC)」
gcloud auth application-default login

這一步在做什麼? gcloud auth login 是給你「人」用 gcloud;application-default login 則是給「程式」(Terraform 的 google provider)用的憑證。少了它,terraform apply 會報認證錯誤。


步驟 3:寫第一份 Terraform 設定

code/terraform/ 底下建立幾個 .tf 檔。Terraform 會把同資料夾所有 .tf 視為一份設定。

3-1 terraform/versions.tf — 版本約束

# code/terraform/versions.tf
terraform {
  required_version = ">= 1.6"

  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 6.0"
    }
  }
}
  • terraform { ... } 區塊設定 Terraform 本身。
  • required_providers:宣告我們要用 hashicorp/google 這個 provider,~> 6.0 表示「6.x 系列」。

3-2 terraform/variables.tf — 可調參數

# code/terraform/variables.tf
variable "project_id" {
  description = "你的 GCP 專案 ID"
  type        = string
}

variable "region" {
  description = "資源所在 region"
  type        = string
  default     = "asia-east1" # 台灣;想用美國可改 us-central1
}
  • variable 區塊宣告「輸入參數」,讓設定可重用、不寫死。
  • project_id 沒給 default,所以必填(等下用 tfvars 提供)。

3-3 terraform/main.tf — 實際資源

# code/terraform/main.tf
provider "google" {
  project = var.project_id
  region  = var.region
}

# 啟用之後章節會用到的 API(啟用 API 不收費)
locals {
  services = [
    "run.googleapis.com",       # Cloud Run(第 6 章)
    "sqladmin.googleapis.com",  # Cloud SQL(第 6 章)
    "iam.googleapis.com",       # IAM
    "compute.googleapis.com",   # 運算/網路
    "container.googleapis.com", # GKE(第 8 章)
  ]
}

resource "google_project_service" "enabled" {
  for_each = toset(local.services)

  project            = var.project_id
  service            = each.value
  disable_on_destroy = false # destroy 時不停用 API,避免牽動其他資源
}

# 部署用的服務帳號(之後 Cloud Run 會以它的身分執行)
resource "google_service_account" "app" {
  project      = var.project_id
  account_id   = "nlt-app"
  display_name = "no-logic-trade app runtime"
}

逐段解釋(這是你的第一份 HCL):

  • provider "google" { ... }:設定 google provider 用哪個專案、哪個 region。var.project_id 是「引用變數」的語法。
  • locals { ... }:定義本地變數(這裡是一份要啟用的 API 清單)。
  • resource "google_project_service" "enabled":宣告一個資源。
    • 格式是 resource "資源類型" "你取的名字"google_project_service 代表「啟用某個 GCP API」。
    • for_each = toset(local.services)對清單裡每個 API 各建一個(迴圈的宣告式版本)。
    • each.value:迴圈當下的值(某個 API 名稱)。
    • disable_on_destroy = falsedestroy 時不要把 API 停用。
  • resource "google_service_account" "app":建立一個服務帳號(machine 用的身分),account_id = "nlt-app"

3-4 terraform/outputs.tf — 輸出值

# code/terraform/outputs.tf
output "app_service_account_email" {
  description = "app 執行用的服務帳號 email"
  value       = google_service_account.app.email
}
  • outputapply 後印出有用的值。google_service_account.app.email 是「引用某資源的屬性」(資源類型.名字.屬性)。

3-5 提供變數值、並保護敏感檔案

建立 terraform/terraform.tfvars(Terraform 會自動讀取它當變數值):

# code/terraform/terraform.tfvars
project_id = "你的-project-id"
region     = "asia-east1"

並在 code/.gitignore 補上 Terraform 相關忽略(狀態檔可能含敏感資訊,絕不進版控):

# code/.gitignore(追加)
# Terraform
**/.terraform/*
*.tfstate
*.tfstate.*
*.tfvars

⚠️ 為什麼忽略 *.tfstate 狀態檔記錄了你建立的所有資源,第 6 章起還會包含資料庫密碼。它一旦外洩等於把雲端設定全交出去。本機練習用本地 state 即可;團隊協作才需要把 state 放到遠端(如 GCS bucket,本教學不展開)。


步驟 4:跑起來 — init / plan / apply

cd code/terraform

terraform init      # 下載 google provider(第一次或換 provider 時跑)
terraform plan      # 預演:看它「打算」做什麼
terraform apply     # 真的執行;它會再列一次計畫並要你輸入 yes 確認
  • plan 會列出一堆「+」(將建立),例如 5 個 google_project_service + 1 個 google_service_account
  • apply 輸入 yes 後開始建立。完成會印出 outputs:
Apply complete! Resources: 6 added, 0 changed, 0 destroyed.

Outputs:
app_service_account_email = "nlt-app@你的-project-id.iam.gserviceaccount.com"

此時資料夾會多出一個 terraform.tfstate——這就是 Terraform 的「記帳本」,記著它建了哪些東西。


✅ 如何驗證這一章成功

驗證 1:Terraform 自己確認狀態一致

cd code/terraform
terraform plan
# 預期:No changes. Your infrastructure matches the configuration.

「沒有變更」代表「你宣告的」與「雲端實際的」一致。

驗證 2:用 gcloud 從另一側確認

# 服務帳號真的建出來了
gcloud iam service-accounts list --project 你的-project-id
# 預期清單裡有 nlt-app@...iam.gserviceaccount.com

# API 真的啟用了
gcloud services list --enabled --project 你的-project-id | grep -E 'run|sqladmin|container'
# 預期看到 run.googleapis.com、sqladmin.googleapis.com、container.googleapis.com
  • [ ] terraform apply 成功並印出 SA email。
  • [ ] terraform planNo changes
  • [ ] gcloud 查得到 nlt-app 服務帳號與已啟用的 API。

關於 destroy(這次先不要做)

terraform destroy 會把這份設定建立的東西全部銷毀。但本章建的 API 與服務帳號是免費的、而且第 6 章會用到,所以這次先保留。先認識指令即可:

# 示範用,現在先不要執行(第 6 章之後每次學完才 destroy)
terraform destroy

🧯 常見錯誤 / 踩雷點

症狀 原因 解法
terraform: command not found 沒裝或沒進 PATH brew install hashicorp/tap/terraform;重開終端機
apply 報認證錯誤(could not find default credentials 沒做 ADC 登入 執行 gcloud auth application-default login
Error 403 ... permission denied 啟用 API 你的帳號在該專案權限不足,或沒開帳單 確認你是專案 Owner/Editor、且已連結帳單(第 1 章)
啟用 API 後建資源仍說 API 沒開 API 啟用有幾十秒延遲 等一下再 apply;Terraform 通常會自動處理相依順序
project_id 找不到 / 用錯專案 terraform.tfvars 沒填或填錯 確認 tfvars 的 project_idgcloud config get-value project 一致
不小心把 terraform.tfstate.tfvars commit 上去 .gitignore 沒涵蓋 補上忽略規則;git rm --cached 移除;若 state 已含密碼要撤換
手動去 console 改了資源,plan 出現非預期差異 「漂移(drift)」:實際狀態和設定不一致 盡量別手動改;用 terraform apply 拉回一致,或更新設定

下一步 → 第 6 章:雲平台部署 — 把容器部署到 Cloud Run(free tier 優先) 我們把第 4 章推到 GHCR 的 image 真正部署上雲:用 Terraform 佈建 Cloud Run(會縮到零、免費額度大)與一個最小的 Cloud SQL(PostgreSQL),把 app 跑在網際網路上。💸 這章開始會產生費用,章末有明確的 destroy 步驟。