這課怎麼做?
先讀變更與 state 的觀念,再做離線 plan 審核,依四回合證據寫出審核結論。最後用原有的 landing zone 情境,把判讀方法帶回團隊設計。
觀念閱讀約 50 分鐘;離線練習約 35 分鐘,可分次進行。離線部分只需瀏覽器與自己的筆記/文字編輯器,不需安裝 Terraform、登入雲端或啟用 Billing。這課提供的是合成 plan 片段,不是部署 Lab;判讀成果是逐項變更表與審核備忘錄。
IaC 管的是變更,不只是建立資源
手動在 Console 建一個 VPC 並不難,困難的是半年後回答:當時為什麼這樣設?誰改過?測試環境和正式環境差在哪裡?如果重建,會不會少一條路由?
Infrastructure as Code(IaC)把期望設定寫成可版本控制的檔案,讓變更可以 review、preview、執行和追蹤。它能提高一致性,但不會自動保證每次結果完全相同:provider version、API default、外部資料、quota 和人工變更都可能造成差異。因此 IaC 還要搭配版本鎖定、state、測試和 drift 管理。

圖解:版本化設定、受保護的 state 與實際資源是三份必須持續比對的真相;變更還要經過檢查、plan、人工 review、policy gate 與 apply,並把 drift 和執行結果帶回下一輪。
Terraform 的三份真相
排查 Terraform 問題時,分開看三件事:
- Configuration:程式碼希望環境長什麼樣子。
- State:Terraform 記得哪些 resource instance,以及它們和遠端資源的對應。
- Remote system:Google Cloud 現在真正存在的資源。
terraform plan 會比較這三者並提出動作。它不是永久有效的承諾。未指定 saved plan 的 terraform apply 會重新規劃;指定 saved plan 則執行該檔案記錄的動作,不會自動納入後來修改的 configuration。輸入或環境變動時,應依流程重新產生並審核 plan;即使使用 saved plan,遠端變動與執行期錯誤仍可能讓操作失敗,不能把它當成服務一定健康的保證。兩種模式見 terraform apply。
流程圖暫時無法顯示。可先查看下方的流程與節點文字,或重新整理頁面後再試。
查看流程與節點文字
箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。
flowchart TB
C["Configuration<br/>期望狀態"] --> P["terraform plan"]
S["State<br/>Resource identity<br/>與已知屬性"] --> P
R["Remote system<br/>Google Cloud<br/>實際狀態"] --> P
P --> D["Proposed create/<br/>update/<br/>replace/delete"]
D --> V{"Reviewer 驗證<br/>blast radius<br/>且 inputs 未變?"}
V -->|"否"| C
V -->|"是"| A["terraform apply"]
A --> R
A --> S
M["Console/<br/>API 手動變更"] --> R
R -->|"下一次<br/>refresh/plan"| P Provider 與版本
google provider 提供穩定版資源與欄位;google-beta 可在功能正式進入 stable provider 前提供支援。Beta 不等於永遠不能用於 production,stable 也不代表沒有升級風險。應根據功能成熟度、支援狀態和變更風險決定,並鎖定 provider constraints、提交 dependency lock file、在升級前跑 plan 和測試。
State 要像敏感營運資料一樣保護
團隊不應把 production state 留在個人筆電。使用 Cloud Storage remote backend 時,至少考慮:
- 開啟 uniform bucket-level access 與 public access prevention。
- 用 IAM 限制能讀寫 state 的 principal,因為 state 可能包含敏感值。
- 開啟 Object Versioning,並用 lifecycle policy 控制舊版本成本。
- 視要求使用 CMEK、audit logs 與分離管理專案。
- 避免
force_destroy,並為 bucket 刪除設保護流程。
GCS backend 支援 state locking,能降低同時 apply 的衝突,但 pipeline 仍應避免多個 job 操作同一份 state。一般也不建議直接替 state bucket 加不可逆的 retention lock;Terraform 需要更新和刪除 state object,鎖錯可能讓正常操作卡住。
可參考 Google 的 Store Terraform state in Cloud Storage 範例,再依組織要求縮小權限。
Module 是介面,不是複製貼上
Module 可以把常見的 VPC、project 或服務設定封裝成團隊介面。好 module 通常具備:
- 清楚而少量的 inputs,不把 provider 每個欄位都原樣暴露。
- 安全預設值,危險選項需要明確開啟。
- Outputs 能支援後續組合,不洩漏不必要的敏感資料。
- Semantic version、upgrade notes 和自動化測試。
- Ownership,知道誰負責 review provider 變更與修補。
Cloud Foundation Toolkit(CFT)提供 Google 維護的 Terraform modules 和 blueprints,可作為 landing zone 與常見架構的參考或起點。採用前仍要 review 版本、輸入、組織政策與營運模型;官方 module 不是「不用理解就直接套用」。
三種常見執行方式
自管 Terraform pipeline
把 Terraform 放在 Cloud Build、GitHub Actions 或其他 CI 平台,適合已有成熟 pipeline、需要自訂測試和多雲整合的團隊。相對地,團隊要自行管理 runner、credentials、state、concurrency 和 approval flow。
Infrastructure Manager
Infrastructure Manager(Infra Manager)是 Google Cloud 的受管理 IaC 服務,以 Terraform configuration 部署和管理 Google Cloud infrastructure。它提供 deployment、revision、preview,並替每個 deployment 管理相關 state 和執行環境。
它適合希望使用 Terraform、但不想自己組整套 apply runner 和 state workflow 的團隊。Infra Manager 管的是基礎設施生命週期;應用版本的漸進式交付仍要用 Cloud Build、Cloud Deploy 或其他交付工具。詳情可看 Infrastructure Manager overview。
Config Connector
Config Connector 透過 Kubernetes CRDs 和 controllers 管理支援的 Google Cloud resources。團隊提交 Kubernetes object,controller 持續把實際資源 reconcile 到 spec 所描述的 desired state,並在 status 和 events 回報結果。
它適合已經以 Kubernetes API、RBAC 和 GitOps 工作的團隊,但要接受:
- Reconciliation 是 eventual consistency,不是 apply 完立刻全部就緒。
- 支援資源和欄位要查 Config Connector reference。
- Controller 需要雲端權限,刪除 policy 與 resource ownership 要先定義。
- Kubernetes etcd 不是一句話就能取代所有 state 思考;desired state、observed status 和外部 resource lifecycle 仍要管理。
Terraform 和 Config Connector 都能管理 Google Cloud 資源。選擇重點是團隊工作流、控制面、資源支援與 ownership,避免同一個資源同時被兩套 controller 管理。
流程圖暫時無法顯示。可先查看下方的流程與節點文字,或重新整理頁面後再試。
查看流程與節點文字
箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。
flowchart LR
Q["先決定團隊要使用的<br/>Source of truth 與 control plane"] --> W{"現有 CI 是否成熟,且需要<br/>自訂測試/多雲整合?"}
W -->|"是"| T["Self-managed Terraform pipeline<br/>自行管理 runner、state、identity、approval"]
W -->|"否"| G{"要用 Terraform 管 Google Cloud,<br/>但不想自建 apply runtime?"}
G -->|"是"| I["Infrastructure Manager<br/>Deployment、revision、preview、managed state"]
G -->|"否"| K{"團隊已用 Kubernetes API、<br/>RBAC 與 GitOps?"}
K -->|"是"| C["Config Connector<br/>CRDs+continuous reconciliation"]
K -->|"否"| E["補齊需求與營運能力後再選"]
T --> O["每個 resource 指定唯一 owner/controller"]
I --> O
C --> O 一條安全的 Terraform pipeline
Production pipeline 可以拆成這幾步:
- PR 執行
fmt、validate、lint、module tests 與 policy checks。 - 使用固定版本的 Terraform 和 providers 產生 plan。
- 保存 machine-readable plan artifact、摘要與執行身分,供 reviewer 檢查 create/update/delete/replace。
- 高風險環境需要獨立核准;核准者不應只看「plan succeeded」。
- Apply 使用專用 service account 和最小權限,優先透過短效憑證/service account impersonation,不在 repository 存長效 key。
- Apply 後執行 smoke test、policy verification 與監控檢查。
- 失敗時保存 log 和 state,不要讓多個人同時手動重跑。
不要在每個 merge 後無條件自動 apply production。是否自動化到哪一步,要看變更風險、blast radius、法規與團隊的復原能力。
流程圖暫時無法顯示。可先查看下方的流程與節點文字,或重新整理頁面後再試。
查看流程與節點文字
箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。
sequenceDiagram
participant D as Developer
participant C as CI
participant R as Reviewer/Approver
participant A as Apply Service Account
participant G as Google Cloud
participant O as Audit/Monitoring
D->>C: Open PR with versioned configuration
C->>C: fmt、validate、lint、tests、policy checks
C->>C: 使用固定 Terraform/Provider 產生 plan
C-->>R: Plan artifact+create/update/delete/replace 摘要
alt Reviewer 拒絕或 inputs 已改變
R-->>D: 要求修正並重新 plan
else 核准精確 plan
R->>C: Approve deployment
C->>A: 取得短效 credential/impersonation
A->>G: Apply with least privilege
G-->>C: Resource results+state update
C->>G: Smoke test+policy verification
C->>O: Logs、plan identity、result、alerts
C-->>D: Deployment status
end Source repository 的現況
Cloud Build 可以和 GitHub、GitLab、Bitbucket 等來源整合,也可依需求使用 Secure Source Manager。Cloud Source Repositories 從 2024-06-17 起已不提供給新客戶,因此不應再把它寫成所有新架構的預設程式碼來源。既有客戶要依官方支援狀態規劃。
GitOps、Config Sync 與 Policy Controller
GitOps 把版本庫當作 desired configuration 的來源,controller 持續同步和回報 drift。在 GKE 環境中:
- Config Sync 同步 Kubernetes configuration。
- Policy Controller 對送進 Kubernetes API 的 objects 執行 policy constraints。
- Config Connector 可把支援的 Google Cloud resources 表示成 Kubernetes objects。
因此「Policy Controller 能禁止公開 Cloud Storage bucket」只有在該 bucket 透過受支援的 Config Connector object 管理、且 constraint 有涵蓋時才成立。它不會自動攔住所有人在 Console、Terraform 或 API 建的任意 bucket。組織層級的強制控制仍要評估 Organization Policy、IAM 與其他 Google Cloud controls。
Drift 也不應一律自動改回去。緊急 incident 期間的手動修復若被 controller 立刻覆蓋,反而會擴大問題。團隊要定義 break-glass 流程、暫停 reconciliation 的方式,以及事後把改動帶回 Git 的責任。
流程圖暫時無法顯示。可先查看下方的流程與節點文字,或重新整理頁面後再試。
查看流程與節點文字
箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。
flowchart TB
D["偵測 Desired<br/>與 Actual drift"] --> I{"正在處理緊急 Incident?"}
I -->|"否"| R["Review drift cause<br/>與 blast radius"]
R --> C{"哪一份狀態<br/>應成為真相?"}
C -->|"Git 正確"| X["經核准後<br/>Reconcile/<br/>Apply 回去"]
C -->|"Actual change<br/>應保留"| A["把變更帶回<br/>Code/State<br/>再 review 與 apply"]
I -->|"是"| P["啟動 Break-glass<br/>必要時暫停<br/>reconciliation"]
P --> F["執行最小修復<br/>並完整 Audit"]
F --> B["Incident 穩定後<br/>把修復編碼回<br/>Git 與 tests"]
B --> V["重新 plan,<br/>確認不會逆轉修復"]
V --> S["恢復<br/>reconciliation"]
X --> M["監控 Desired=Actual"]
A --> M
S --> M Deployment Manager 已結束支援
Google Cloud Deployment Manager 已於 2026-03-31 結束支援,官方不再提供支援與新功能,預計 2027-06-30 之後關閉服務與 API。過去由 Deployment Manager 建立的個別 Google Cloud resources 仍會運作,也能用一般產品工具管理,不應再依賴 Deployment Manager 更新。
現在遇到舊 Deployment Manager 環境,工作不是在選擇題裡背「Terraform 比較新」,而是:
- 盤點 deployment、template、依賴和實際 resources。
- 決定使用 Infra Manager、自管 Terraform 或其他工具。
- Import/adopt 既有 resources,確認 configuration、state 和 remote system 一致。
- 先在低風險環境演練,避免 migration 意外重建 production resources。
官方狀態與遷移說明見 Deployment Manager deprecation。
流程圖暫時無法顯示。可先查看下方的流程與節點文字,或重新整理頁面後再試。
查看流程與節點文字
箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。
flowchart TB
I["Inventory deployments、templates、<br/>dependencies 與實際 resources"] --> O["確認 Resource ownership、<br/>criticality 與現有 drift"]
O --> T{"選新的管理控制面"}
T -->|"Managed Terraform"| M["Infrastructure<br/>Manager"]
T -->|"Existing CI/Multi-cloud"| S["Self-managed<br/>Terraform"]
T -->|"Kubernetes GitOps"| K["Config Connector<br/>僅限支援<br/>resources"]
M --> C["撰寫等價 desired configuration"]
S --> C
K --> C
C --> A["Import/Adopt,不先重建"]
A --> P["Plan/Preview 必須顯示<br/>無意外 delete/replace"]
P --> L["先在低風險環境演練"]
L --> G{"驗證 ownership、state、<br/>permissions 與 rollback?"}
G -->|"否"| C
G -->|"是"| R["分批接管 Production resources"] Cloud Shell 與 Cloud Code 放在哪裡?
Cloud Shell 提供瀏覽器中的臨時開發與管理環境,預裝 gcloud、kubectl 等常用工具,適合快速診斷和教學。Terraform CLI 自 2026 年 6 月 20 日起不再預裝:要在 Cloud Shell 用 Terraform,得照 HashiCorp 的說明自行安裝,可以寫進環境自訂腳本($HOME/.customize_environment)在啟動時安裝,或把執行檔裝在家目錄,讓它跨工作階段保留(見官方 Install Terraform)。Cloud Shell 不是 production pipeline,也不應成為只有某個人知道怎麼操作的部署主機。
Cloud Code 是 VS Code 與 IntelliJ 的開發擴充,協助本機開發、瀏覽 Google Cloud resources,以及處理 GKE、Cloud Run 等工作流。兩者提高個人操作效率,但治理仍要回到 repository、pipeline、IAM 和 audit trail。
離線練習:這份 plan 現在可以核准嗎?
本站合成、精簡的 plan 判讀教材;不是 provider 實測,也不是完整 terraform show -json 輸出。外層 review_context 是教材資料,不是 Terraform 欄位。此 JSON 不能交給 terraform apply。所有名稱、位址、密碼標記與修訂識別均為虛構。
下載本課合成證據(JSON)
可直接在下方閱讀,不必下載。下載檔案包含本課八列變更、一次 drift 與審核背景,不含參考判讀。最外層是本站教材封裝;plan_excerpt 才是依官方欄位語意編寫的精簡片段,省略了完整 configuration、planned values 等內容。它不是 saved binary plan、HCL 或可套用設定,也未由 Google provider 執行產生。
先固定審核範圍
- 本次需求:升級 checkout 計算層並維持入口與結帳服務可用;未授權刪除封存資料、擴大 IAM 或逆轉事故修復。
- 目標:demo-production,workspace
checkout-demo;兩者都是虛構標籤。 - 事故證據:INC-DEMO-7:應用已改在 TCP 8443 服務,值班人員將 checkout-internal 從 443 改成 8443;事故尚未結案,修復尚未回寫 Git。
- 資料保留:demo-checkout-archive 仍含需保留的訂單封存;未提供可驗證的移轉副本、還原演練或刪除核准。
先用 5 分鐘建立八列變更表,保留 address、mode、actions 的原始順序、是否含刪除、影響與待查 owner。用 20 分鐘完成四回合,再用 10 分鐘寫審核備忘錄。先留下自己的判讀,再展開參考。
讀 JSON 時先辨認這些欄位
| 欄位 | 本課要辨認的語意 |
|---|---|
resource_changes | 下一次提議執行的動作;保留 actions 的先後順序 |
resource_drift | Refresh 時觀察到的外部變動,與下一次動作分開看 |
before/after | 變更前與目前可知的變更後內容;不能只靠 null 推論值會被清空 |
after_unknown | 對應位置為 true 時,表示值在 plan 時尚未確定 |
before_sensitive/after_sensitive | 標示需保護的值,不代表 JSON 已遮蔽或加密 |
replace_paths | 指出導致替換的欄位路徑;不應用它是否存在判定有無替換 |
表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。
format_version 是 JSON 表示格式,不是 Terraform CLI/provider 版本。實務上還要記錄工具與 dependency lock file。本課只討論下載片段中的欄位,不能用這個精簡練習取代完整 plan、policy 與部署驗證。欄位語意可對照 HashiCorp JSON output format。
本練習的欄位與命令模式於 2026-10-28 核對官方文件;IAM 權限範圍可另查 Google Cloud roles overview。案例中的核准範圍與事故狀態則是本課自訂條件。
四回合:先判讀,再核對理由
回合 1:替換順序與資料刪除
逐項寫出動作及先後順序。哪些包含刪除?先建後刪能否保證入口不中斷?封存 bucket 還缺什麼證據?
{
"resource_changes": [
{
"address": "google_compute_instance.checkout",
"mode": "managed",
"type": "google_compute_instance",
"name": "checkout",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"delete",
"create"
],
"before": {
"name": "checkout-blue"
},
"after": {
"name": "checkout-green"
},
"after_unknown": {},
"before_sensitive": {},
"after_sensitive": {},
"replace_paths": [
[
"name"
]
]
}
},
{
"address": "google_compute_address.ingress",
"mode": "managed",
"type": "google_compute_address",
"name": "ingress",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"create",
"delete"
],
"before": {
"name": "ingress-blue",
"address": "192.0.2.10"
},
"after": {
"name": "ingress-green",
"address": null
},
"after_unknown": {
"address": true
},
"before_sensitive": {},
"after_sensitive": {},
"replace_paths": [
[
"name"
]
]
}
},
{
"address": "google_storage_bucket.archive",
"mode": "managed",
"type": "google_storage_bucket",
"name": "archive",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"delete"
],
"before": {
"name": "demo-checkout-archive",
"location": "ASIA-EAST1"
},
"after": null,
"after_unknown": {},
"before_sensitive": {},
"after_sensitive": {}
}
}
]
}先寫再看:回合 1 的參考判讀
- checkout 是先刪後建的替換;ingress 是先建後刪的替換。兩者都包含 delete,不能只找 actions 等於 ["delete"]。replace_paths 說明這份教材中的替換原因;真實 plan 也可能因 taint 或其他原因替換,不能用這個欄位是否存在代替 actions。
- 先建後刪不保證零中斷:仍需足夠 quota/容量、名稱與依賴相容性,以及新位址、DNS/流量與結帳旅程的切換驗證。這份片段沒有證明以上條件已成立。
- archive 是單純刪除,且與保留需求衝突。暫緩這份 plan,找資料 owner 確認保留要求、可驗證副本與還原、核准與清理範圍;不能以「之後再建同名 bucket」視為資料回復。
回合 2:未確定值、敏感值與讀取
password 和 number 的 null 各代表什麼?讀 data source 算不算建立一個雲端資源?若真實 JSON 含敏感值,你會如何分享審核證據?
{
"resource_changes": [
{
"address": "google_sql_user.checkout",
"mode": "managed",
"type": "google_sql_user",
"name": "checkout",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"update"
],
"before": {
"name": "checkout",
"password": "LAB_PLACEHOLDER_OLD"
},
"after": {
"name": "checkout",
"password": null
},
"after_unknown": {
"password": true
},
"before_sensitive": {
"password": true
},
"after_sensitive": {
"password": true
}
}
},
{
"address": "data.google_project.current",
"mode": "data",
"type": "google_project",
"name": "current",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"read"
],
"before": null,
"after": {
"project_id": "demo-checkout-project",
"number": null
},
"after_unknown": {
"number": true
},
"before_sensitive": {},
"after_sensitive": {}
}
}
]
}先寫再看:回合 2 的參考判讀
- 兩個欄位都被 after_unknown 標為 true,代表 plan 時尚未確定,不能直接讀成清空、空字串或安全預設。unknown 是需要補驗證的資訊,不等於每種未知都必須拒絕;本例密碼輪替需驗證應用更新、連線與失敗處理。
- SQL user 的 password 同時標為 sensitive。這兩個維度彼此獨立:已知值也可能敏感,未知值也可能敏感。LAB_PLACEHOLDER_OLD 只是合成標記;真實 saved plan/JSON 仍可能含明文敏感資料,不能把 sensitive 當成檔案已加密或完成遮蔽。
- data.google_project.current 的 mode 是 data、動作是 read;它不是一次 managed resource create,也不是變更失敗。分享時只提供必要且已檢查遮蔽的摘要,原始 artifact 存在受控位置並限制存取與保留。
回合 3:Drift 與權限擴大
比較 resource_drift 與 resource_changes:哪個是已觀察到的改動,哪個是接下來要做的動作?現在 apply 會如何影響事故修復?IAM 的 create 數量少,就代表低風險嗎?
{
"resource_changes": [
{
"address": "google_compute_firewall.checkout",
"mode": "managed",
"type": "google_compute_firewall",
"name": "checkout",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"update"
],
"before": {
"name": "checkout-internal",
"allow": [
{
"protocol": "tcp",
"ports": [
"8443"
]
}
]
},
"after": {
"name": "checkout-internal",
"allow": [
{
"protocol": "tcp",
"ports": [
"443"
]
}
]
},
"after_unknown": {},
"before_sensitive": {},
"after_sensitive": {}
}
},
{
"address": "google_project_iam_member.release",
"mode": "managed",
"type": "google_project_iam_member",
"name": "release",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"create"
],
"before": null,
"after": {
"project": "demo-checkout-project",
"role": "roles/owner",
"member": "serviceAccount:release@demo-checkout-project.iam.gserviceaccount.com"
},
"after_unknown": {},
"before_sensitive": {},
"after_sensitive": {}
}
}
],
"resource_drift": [
{
"address": "google_compute_firewall.checkout",
"mode": "managed",
"type": "google_compute_firewall",
"name": "checkout",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"update"
],
"before": {
"name": "checkout-internal",
"allow": [
{
"protocol": "tcp",
"ports": [
"443"
]
}
]
},
"after": {
"name": "checkout-internal",
"allow": [
{
"protocol": "tcp",
"ports": [
"8443"
]
}
]
},
"after_unknown": {},
"before_sensitive": {},
"after_sensitive": {}
}
}
]
}先寫再看:回合 3 的參考判讀
- resource_drift 記錄觀察到的 443 → 8443;resource_changes 提議的是 8443 → 443。不能把 drift 再加進下一次操作計數,也不能只看 update 就忽略它會逆轉尚在使用的事故修復。
- 依 INC-DEMO-7 先和 incident owner 確認要保留的修復,把適當設定與測試回寫 Git,再重新產生並審核 plan。這不是一律接受 Console 變更,也不是立即 apply 以消除 drift。
- roles/owner 是本次未授權的權限擴大。即使只有一個 create,也應暫緩,請身分 owner 提出最小權限需求與驗證;不能用低變更數替代權限影響審查。
回合 4:核准的是哪一份 plan?
這列 no-op 能證明整個系統安全嗎?plan-A 的核准能否沿用到 plan-B?你會留下什麼資訊,讓執行者能確認核准對象?
{
"review_context": {
"requested_change": "升級 checkout 計算層並維持入口與結帳服務可用;未授權刪除封存資料、擴大 IAM 或逆轉事故修復。",
"environment": "demo-production",
"workspace": "checkout-demo",
"previous_approval": {
"artifact_id": "plan-A",
"source_revision": "demo-revision-a",
"decision": "只核准 plan-A,不含後續新 plan"
},
"candidate": {
"artifact_id": "plan-B",
"source_revision": "demo-revision-b",
"decision": "待審核"
},
"incident": "INC-DEMO-7:應用已改在 TCP 8443 服務,值班人員將 checkout-internal 從 443 改成 8443;事故尚未結案,修復尚未回寫 Git。",
"archive": "demo-checkout-archive 仍含需保留的訂單封存;未提供可驗證的移轉副本、還原演練或刪除核准。",
"missing_evidence": [
"替換期間的容量、入口切換與使用者旅程驗證",
"新位址的 DNS/流量切換與回復驗證",
"密碼輪替後的應用連線與失敗處理",
"release 身分的最小權限需求"
]
},
"resource_changes": [
{
"address": "google_compute_network.shared",
"mode": "managed",
"type": "google_compute_network",
"name": "shared",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"no-op"
],
"before": {
"name": "demo-shared",
"auto_create_subnetworks": false
},
"after": {
"name": "demo-shared",
"auto_create_subnetworks": false
},
"after_unknown": {},
"before_sensitive": {},
"after_sensitive": {}
}
}
]
}先寫再看:回合 4 的參考判讀
- no-op 只表示這列在這次 plan 沒有提議動作;它不能證明其他資源、有效 IAM、未納管資源或整體服務都安全。片段也不是完整 plan,不能拿來核准真實 production。
- 舊核准僅對應 plan-A/demo-revision-a,候選已是 plan-B/demo-revision-b,不能沿用。本例有未授權刪除、權限擴大與逆轉修復等具體阻擋,決策應為暫緩,列出 owner 與重審條件。
- 真實流程應綁定 source revision、環境/workspace/backend、工具與 dependency lock file、輸入及精確 saved-plan artifact(例如受控 artifact ID 與雜湊)、審核者與驗證條件。JSON 是閱讀用表示;核准與 apply 要對上原始 saved plan。重新產生 plan 必須重新審核;saved-plan apply 不會再詢問互動確認,核准關卡要在執行前完成。
交付:逐項變更表與審核備忘錄
先核對八列 resource_changes 是否都已列入你的變更表,將「資源列數」和「動作次數」分開。兩種替換都各含一次 create 與 delete,data read 與 no-op 另列;resource_drift 不再加進提議動作計數。寫完再展開核對。
完成變更表後再看:本例的計數與阻擋條件
本例共八列:先刪後建替換一列、先建後刪替換一列、單純刪除一列、update 兩列、create 一列、data read 一列、no-op 一列。依這些 managed resource 動作計,為 3 add、2 change、3 destroy;兩列替換同時計入 add 與 destroy,不能把八列直接讀成八次新增。
本例應暫緩 plan-B:封存刪除、roles/owner 與逆轉事故修復都超出本次核准範圍;替換、入口切換與密碼輪替的驗證也還缺證據。after_unknown 本身不等於失敗,no-op 也不等於整體安全。結論要指向具體資源、owner 與重審條件,不能只寫「有風險」。
可複製這份空白範本到自己的筆記;每個阻擋項目都要填入具體 address、證據與負責人。
IaC 變更審核備忘錄
候選 artifact/source revision:
目標環境/workspace/backend 識別:
本次授權範圍:
八列變更表(每列保留 address、mode、actions 原始順序):
1.
2.
3.
4.
5.
6.
7.
8.
動作計數(add/change/destroy;read/no-op 另列):
決策(暫緩/需補證據/可送核准)與理由:
阻擋項目(address、證據、owner、解除條件):
Drift 的保留或回復決策與 incident owner:
未確定值的取得時點、部署後驗證與失敗處理:
敏感 artifact 的受控位置、遮蔽摘要與存取/保留規則:
下一份 plan 的識別、重審者與使用者旅程驗證:
完成標準是:八列無遺漏、替換順序與計數正確、分開 unknown/sensitive 與 drift/提議動作、列出具體阻擋,並把審核綁定到正確 artifact。這份範本沒有自動評分或核准能力;閱讀、複製與下載不會替你完成課程或改寫既有工作坊草稿。請自行保存筆記;本練習沒有建立雲端資源,結束時只需決定是否保留自己下載的合成檔案。
回到真實 pipeline 前
真實流程中的 saved plan、JSON 與 log 都可能含敏感資料;不要直接貼到公開 PR。只分享必要且已檢查遮蔽的摘要,原始 artifact 放在受控位置。sensitive 是資訊標記,不是加密;詳見 terraform show 與 saved plan 的資料保護注意事項。
若 CI 使用 terraform plan -detailed-exitcode,0 是成功且無差異,1 是錯誤,2 是成功且有差異;2 不是部署核准,也不應被當成一般失敗後直接略過。這與 terraform plan -json 的串流 UI 事件不同:本課讀的是 terraform show -json 形式的 plan 表示。真實執行仍需身分、backend/locking、完整 plan、政策與部署後驗證;本練習沒有執行這些操作。
情境練習:建立多團隊 landing zone
假設平台團隊要替 20 個產品團隊建立 folders、projects、Shared VPC 和安全基線。
動手前先寫下你的設計順序:state 怎麼切、誰來 review plan、apply 用什麼身分、部署後要驗證什麼。寫完再展開對照。
寫完再展開:參考設計
可以這樣設計:
- 先定義 folder/project ownership、billing、network 和 security boundaries。
- 評估 CFT modules,但用自己的 tests 和 policies 驗證輸出。
- 將 organization、network 和 workload stacks 拆成 blast radius 合理的 states,不把全公司塞進一份 state。
- State bucket 放在受控管理專案,開啟 versioning、PAP、UBLA 和最小權限。
- PR pipeline 產生 plan;平台 reviewer 檢查 replace/delete 和 IAM 變更;production apply 使用專用 service account。
- 部署後驗證 Organization Policy、log sinks、Shared VPC attachments 和必要告警。
- 每次 provider/module 升級先在測試 organization 或低風險 stack 驗證。
這樣 IaC 才是治理流程,而不是把 Console 點擊翻譯成 HCL。
課後檢查
往下看重點之前,先試著回答這幾題;答不出來,括號裡是該回去重讀的那一節。
- 排查 Terraform 問題時,要分開看哪三份狀態?(回〈Terraform 的三份真相〉)
- Remote state 為什麼要限制讀寫、保留版本,還要避免同時 apply?(回〈State 要像敏感營運資料一樣保護〉)
- GitOps 的 policy 能不能擋住所有人在 Console 或 Terraform 建的資源?為什麼?(回〈GitOps、Config Sync 與 Policy Controller〉)
- 你的八列變更表是否漏算替換中的 delete?為什麼本例必須暫緩 plan-B?(回〈四回合:先判讀,再核對理由〉)
本課重點
- IaC 提高可重現性,但 provider、default、state、quota 和人工變更仍會造成差異。
- Terraform configuration、state 和 remote resources 是三個要分開檢查的狀態。
- Remote state 要限制讀寫、保留版本並避免互相衝突的 apply。
- CFT 是可評估的 module/blueprint 起點,不是每個 landing zone 都必須照抄。
- Infra Manager 管受管理的 Terraform infrastructure deployment;Config Connector 使用 Kubernetes reconciliation。
- Production pipeline 要保留 plan、核准、短效身分、apply 結果與驗證紀錄。
- Deployment Manager 已在 2026-03-31 結束支援,既有資源要遷移管理方式。
- GitOps policy 只會控制它實際看得到的 objects,不能取代組織層 controls。
延伸閱讀
- Terraform on Google Cloud
- Infrastructure Manager overview
- Config Connector overview
- Managing infrastructure as code with Terraform and Cloud Build
下一步
下一課會進入安全與合規。你會看到 IaC 流程中的 service account、組織政策和資料邊界,如何和 IAM 架構接在一起。