把声明式管理延伸到集群之外:用 GitOps 管理 Cloudflare 资源的实录
原始來源與檔名:2026-07-28T095951+0800-把声明式管理延伸到集群之外用 GitOps 管理 Cloudflare 资源的实录.md
SOURCE | 資訊源評估
- 準確性:高 - 作者提供了極為詳盡的實戰架構與除錯紀錄,涵蓋 Crossplane、Terraform、Cloudflare (Tunnel/Worker/D1) 之間複雜的交互邊界,踩坑細節真實。
- 易理解性:中 - 要求讀者對 Kubernetes, GitOps (ArgoCD/Burrito), Terraform (HCL), 以及邊緣運算網路架構有深度認知。
- 閱讀策略建議:SRE 與基礎設施工程師必讀。重點關注「邊界設計」的取捨哲學,以及「DNS 零停機遷移」的實務手法。
NAPKIN | 餐巾紙
餐巾紙公式
Infrastructure as Code = Terraform (快照/低頻) + Crossplane CRs (常駐調和/高頻) + Helm Hooks (過程性任務) 不要迷信單一工具,系統狀態的邊界劃分應基於「變更頻率」與「失誤波及面」。
一句話
作者透過整合 Terraform 與 Crossplane,將 Cloudflare 邊緣網路的設定(DNS、Worker、Tunnel)全數納入 GitOps 體系,徹底消滅了 Web 控制台的手動操作與不可追溯的狀態迷霧。
餐巾紙草圖
┌───────────────────────
│ 流量路徑
│ 邊緣(Worker) ──[不中]──> Tunnel ──> K8s (cloudflared) ──> Ingress
├───────────────────────
│ 管理路徑 (Git 倉庫分層)
│ ├── Terraform (快照): Zone, Tunnel, D1 (變更少/影響大)
│ ├── Crossplane (常駐控制器): DNS, Worker 腳本 (高頻/防手賤)
│ ├── Helm Hook Job (過程): SQL 遷移 (必須順序執行)
│ └── Burrito Operator (可見性): 只跑 Plan 告警漂移,不自動 Apply
└──
ROUND 1: SKELETON | 骨架掃描
- 核心問題:K8s 內部已經實現高度 GitOps 化,但邊緣層(如 Cloudflare 的 DNS、Worker)依然依賴手動控制台或命令列工具(如 wrangler),導致「真相的來源」分裂,產生運維黑洞。
- 核心答案:利用 Terraform 與 Crossplane 將 Cloudflare 的所有資源代碼化,並依據「變更頻率」與「狀態性質」劃分工具邊界,將邊緣配置徹底納入 K8s 的聲明式管理。
- 論證結構:
- 動機與價值:為何要抹平內外的管理不對稱。
- 最終架構:流量路徑與管理路徑的雙重視角。
- 技術選型與邊界設計:為何不硬造 CR,Terraform 與 Crossplane 的分工邏輯。
- 實戰踩坑紀錄:DNS 零停機遷移、無入站端口集群、HCL 解析錯誤、永久 Diff 消除。
- 結論:算總帳,聲明式管理帶來的組織與安全收益。
章節骨架(條列)
- 為什麼要做到這個地步 (消滅半套 IaC 的狀態迷霧)
- 最終的架構 (邊緣處理 + Tunnel 下行 + Git 單點真相)
- 選型 (Terraform 快照 vs. Crossplane 常駐調和)
- 邊界設計:不硬造 CR (引導歸 TF,過程歸 Job,聲明歸 CR)
- DNS 遷移:零停機的前提 (先複製、後切換)
- Tunnel:沒有入站端口的集群 (透過 cloudflared 打通)
- 記錄進 CR:看著簡單,坑照樣有 (K8s 名稱限制)
- Worker 部署:只收構建產物的流水線
- 真實應用暴露出來的問題 (HCL ${ 解析錯誤、永久 diff)
- 閉合迴路的可見性 (Burrito 監聽漂移)
- 機密放在哪裡
- 從設計角度算總帳 (狀態在哪,運維成本就在哪)
ROUND 2: DISSECTION | 血肉解剖
論證鏈
┌── 痛點:狀態一旦橫跨兩套管理體系(Git vs 控制台),故障排查成本極高,半年後無人記得某條 DNS 紀錄為何存在。
│
├── 原則:將「調和循環 (reconcile loop)」引入邊緣網路。宣告與現實一旦偏離,控制器自動拉回。
│
├── 設計:基於資源特性解耦。不常動的(Zone/Tunnel)交給 Terraform (快照);常改且易遭人手動破壞的(DNS/Worker)交給 Crossplane (控制器);有順序依賴的(SQL 遷移)交給 Helm Hook (Job)。
│
└── 成果:手動操作降為 1 次(改 Nameserver),實現基礎設施代碼化、無入站暴露、零停機。
3 個關鍵證據
- 棄用 Wrangler 的原因:Wrangler 是一種命令式工具,deploy 成功那一瞬間的狀態,在 git 裡沒有對應物,缺乏控制器不斷對齊現實的「調和循環」。
- HCL 字符地雷 (Bug 解析):Crossplane 的 upjet 架構底層仍是 Terraform。當 Worker 的 JS 壓縮代碼中出現
${時,會觸發 HCL 模板解析崩潰。這證明了「將代碼字串硬塞入聲明式 CR」潛在的封裝滲透風險。 - DNS 零停機策略:先在 Cloudflare 建 Zone,以 CR 複製所有紀錄為
DNS only (不代理),最後才在註冊商切換 Nameserver。證明了基礎設施遷移的核心原則:「先備份/並行,最後切換,切換後不動」。
隱形假設與邊界
- 假設:維護者(作者本人)具備極強的 Terraform/K8s 除錯能力,能夠承受早期開源專案(如 5 顆星的 Crossplane upjet provider)帶來的風險與維護成本。
- 邊界:文章明確指出,如果只是幾條 DNS 紀錄且無邊緣邏輯,這套架構是「殺雞用牛刀」;且作者不追求極端的「全部自動 Apply」,在 Terraform 層保留了手動 Apply 以防禦災難。
ROUND 3: SOUL | 靈魂提取
- 作者盲點:使用 upjet 將 Terraform Provider 機械轉換為 Crossplane Provider,雖然能快速獲得能力,但由於底層依賴 HCL 渲染,在面對複雜字串(如 JS 產物)時極為脆弱,這是架構上難以根除的技術債。
- 知識連接:軟體工程中的單一真相來源 (SSOT - Single Source of Truth)。在分散式系統中,只要有兩個地方都能改寫狀態,腦裂 (Split-brain) 就必然發生,運維黑洞因此產生。
- 行動觸發:基礎設施團隊應立即盤點:我們的雲端資源中,有哪些是「只能靠截圖和記憶」來維護的?這就是下一個潛在的 P0 事故爆發點。
留白提問(2 題)
- 在大規模團隊中,如何防止開發人員為了圖方便,直接在 Cloudflare 控制台手動覆寫 Crossplane 的 CR 狀態,引發頻繁的「狀態拉鋸戰」?
- 將 Worker 的 JS 構建產物直接存入 K8s 的 Script CR 中,當腳本體積逼近 K8s etcd 的 1MB 限制時,這套部署管線是否會面臨崩潰?
跨域映射
這就像是把城市的外圍城牆(邊緣網路)與市中心(K8s 集群)交由同一套都市計畫委員會(Git 倉庫)管理。以前城牆是民兵(控制台)隨意修補,現在全部改為標準化藍圖,城牆只要被人敲掉一塊,建築機器人(控制器)就會立刻按圖紙補好。
DEEP READ | 精讀指引
- 边界设计:不硬造 CR
- 推薦理由:這是通篇最具架構師智慧的一段。許多狂熱的 GitOps 信徒試圖將所有事物塞進 YAML (CR),而作者冷靜地指出:引導歸 Terraform,過程歸 Job。運維的安全優先於工具的純粹性。
- 真实应用暴露出来的问题
- 推薦理由:極具價值的踩坑血淚史。關於
${引發 HCL 解析崩潰的根因分析,以及「永久 Diff 如同癌症」的警世格言,能讓採用 Crossplane 的工程師少走數週的彎路。
- 推薦理由:極具價值的踩坑血淚史。關於
STRUCTURE MAP | 全書結構圖
┌── 動機:消滅「集權 K8s」與「混沌邊緣 (控制台)」的狀態撕裂
│
├── 最終架構全景
│ ├── 流量:Cloudflare 邊緣 (Worker) → Tunnel → K8s Ingress
│ └── 管理:Git (SSOT) → Terraform/Crossplane → Cloudflare API
│
├── 技術選型與邊界劃分 (架構師決策)
│ ├── Terraform:建置 Zone/Tunnel (低頻、快照式)
│ ├── Crossplane CR:DNS/Worker 腳本 (高頻、常駐調和防手動破壞)
│ └── Helm Hook Job:SQL 資料庫遷移 (必須依序執行的過程性任務)
│
├── 實戰踩坑紀錄
│ ├── DNS 遷移:複製數據 → 關閉代理並行 → 切換 NS (零停機)
│ ├── 命名限制:K8s 名稱不容許底線與點號 (範本層處理)
│ ├── HCL 地雷:JS 的 `${` 導致 upjet 解析崩潰 (關閉變數壓縮解決)
│ └── 狀態漂移:不可妥協的「永久 Diff」必須根除
│
└── 系統觀收斂
├── 工具層疊:自動復原交給機器 (CR) + 影響範圍大的交給人確認 (TF 卡片)
└── 核心哲學:狀態在哪裡,運維成本就在哪裡。
把声明式管理延伸到集群之外:用 GitOps 管理 Cloudflare 资源的实录 (Architectural Deep Dive)
前言/背景
當 Kubernetes 內部已實現高度的 GitOps (如一切依賴 Helm, ArgoCD),外部的雲端基礎設施(如 DNS、邊緣網路)卻往往仍依賴控制台點擊或命令列工具,這導致了「真相」的割裂。本文作者分享了如何耗費一週時間,透過整合 Terraform 與 Crossplane,將 Cloudflare 的所有資源納入單一 Git 倉庫進行聲明式管理,徹底消除運維黑洞。
章節詳細總結
動機與系統邊界設計 (架構哲學)
- 拒絕半套 IaC:如果邊緣配置只存在於控制台,故障發生時工程師必須在腦中縫合兩個世界的狀態。棄用 Wrangler 的原因在於它缺乏「調和循環 (reconcile loop)」,部署後的狀態隨時可能與 Git 發生偏移。
- 不硬造 CR (Custom Resource):作者展現了極高的架構自制力,拒絕將所有事物塞入 K8s YAML。
- 引導資源 (Zone/Tunnel/D1):交給 Terraform 建立,因為它們極少變動,是其他資源的前提。
- 高頻易動資源 (DNS/Worker):交給 Crossplane 的 CR,藉由控制器的常駐調和,防止有人在控制台手動竄改。
- 過程性任務 (SQL 遷移):交給 Helm Hook 觸發 Job 來循序執行,並寫入歷史表保證冪等性,因為這類任務無法用聲明式語言表達。
DNS 遷移與零入站集群
- 零停機 DNS 遷移:先在 Cloudflare 建立 Pending 狀態的 Zone,將原 DNS 紀錄轉為 CR 並全數同步(關閉代理模式),最後一刻才在註冊商更改 Nameserver。這保證了在 DNS 傳播期間,兩邊的解析結果完全一致。
- Tunnel 實現安全架構:摒棄傳統的公開 80/443 埠,K8s 內部僅部署
cloudflared。它主動向 Cloudflare 建立只出不進的長連接,並將所有流量匯入單一的ingress-nginx。這使得集群沒有任何對外的入站暴露,且舊有的 Ingress 路由規則完全不需修改。
踩坑實錄:HCL 崩潰與永久 Diff
- 致命的 HCL 解析錯誤:當部署 Worker 代碼時,如果 JS 壓縮產物中包含單一
$變數並緊跟大括號(即${),會被 Crossplane 底層的upjet(Terraform 包裝器) 誤認為是 HCL 的插值語法,導致部署卡死。解法是放棄特定的變數壓縮策略,並在構建腳本中加入字串攔截。 - 不可容忍的永久 Diff:Terraform 若漏寫某個 API 默認屬性,會導致每次 Plan 都顯示有差異。作者強調「永久 Diff 猶如癌症」,它會讓運維人員對狀態告警麻木,掩蓋真正的配置漂移,必須徹底剷除。
閉合迴路的可見性 (Burrito Operator)
- 在 Crossplane 管轄之外的 Terraform 資源,作者利用 Burrito Operator 來監聽漂移。
- 出於安全考量(避免半夜被 Terraform 自動套用搞垮基礎設施),Operator 只負責運行 Plan 並在介面上產生「狀態卡片」。只有在人眼確認安全後,才於本地執行 Apply。
- 層層疊加的防禦:波及面小的高頻資源交由機器(CR)在幾分鐘內自動復原;影響深遠的底層資源交由人類(TF 卡片)確認。
總結與結論
- 單一真相來源 (SSOT) 是運維的底線:系統狀態在哪裡,運維成本就在哪裡。把邊緣網路和集群內部的配置收束於同一個 Git 倉庫,能將災難恢復簡化為一次 Apply。
- 工具選型取決於「變更頻率」與「影響範圍」:Terraform 的快照特性適合低頻、高風險的底層資源;Crossplane 的常駐調和特性適合高頻、易遭人為破壞的配置。
- 基礎設施代碼化 (IaC) 的附帶價值是「強制文檔化」:當所有的 DNS 紀錄都必須以代碼提交時,每一次變更都必須寫下 Context 與註解,這徹底解決了「半年後沒人知道這條 TXT 紀錄為何存在」的組織記憶流失問題。
- 警惕框架封裝滲透 (Leaky Abstraction):Crossplane 透過 upjet 包裝 Terraform 所引發的 HCL 解析崩潰,提醒工程師在享受自動生成框架便利的同時,必須深諳其底層運作機制。