第 5 章:IaC 入門 — 用 Terraform 佈建雲端基礎設施
這一章在做什麼? 我們第一次碰雲端,但先不碰會花錢的東西。先理解「基礎設施即程式碼(IaC)」的概念,裝好 gcloud 與 terraform、完成 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 授權
- 安裝 gcloud CLI(GCP 的官方命令列工具):
# macOS(擇一)
brew install --cask google-cloud-sdk
# 或參考官方安裝:https://cloud.google.com/sdk/docs/install
gcloud version
- 安裝 Terraform:
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform version # 預期 >= 1.6
- 登入並設定專案(把
你的-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 = false:destroy時不要把 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
}
output在apply後印出有用的值。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 plan回No 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_id 與 gcloud 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 步驟。