<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Mindset on Tarragon</title><link>https://tarrragon.github.io/blog/tags/mindset/</link><description>Recent content in Mindset on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 26 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/mindset/index.xml" rel="self" type="application/rss+xml"/><item><title>infra 的責任邊界、成熟度階梯與 day 1 鐵律</title><link>https://tarrragon.github.io/blog/infra/00-infra-mindset/infra-responsibility-maturity/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/00-infra-mindset/infra-responsibility-maturity/</guid><description>&lt;p>基礎設施（infrastructure，簡稱 infra）是承載應用程式的那層資源與規則：運算、網路、身分、儲存、可觀測性，以及定義它們如何被建立、變更、回收的治理機制。它的責任是讓應用程式有一個可被信任、可被重建、可被審計的執行環境。本篇建立的責任邊界、成熟度階梯與 day 1 鐵律，是後續所有 infra 模組共用的心智模型，其他章節會直接引用這裡定義的詞彙。&lt;/p>
&lt;h2 id="infra-的責任邊界">infra 的責任邊界&lt;/h2>
&lt;p>infra 承擔的是「應用程式之下、作業系統之上」那層共享資源的供應與治理。把責任拆成五個面向比較好對齊：每一面都有自己的失效模式，混在一起談會讓判斷失焦。&lt;/p>
&lt;h3 id="運算compute">運算（compute）&lt;/h3>
&lt;p>運算負責「程式跑在哪、用多少資源、怎麼擴縮」。它的衡量點是容量與彈性：流量尖峰時能不能長出更多實例、閒置時能不能縮回去省錢。一台手動開的 VM 也是運算資源，差別只在它是否被納入可重建的描述。&lt;/p>
&lt;p>運算涵蓋的光譜從 VM（EC2 instance）到容器（ECS task、Kubernetes pod）到 serverless function（Lambda）。抽象層級越高，infra 需要直接管理的細節越少——VM 要管 OS 更新與磁碟擴容，容器只需管映像與編排，serverless 幾乎只管程式碼與觸發條件。但抽象層級不改變運算的基本問題：它跑在什麼網路裡、用什麼身分存取其他資源、出了問題怎麼查。這些「接線」正是 infra 其他四個面向的職責。&lt;/p>
&lt;p>運算層常見的失效模式有兩類。第一類是容量不足：流量上來了但 auto-scaling 沒設或設錯，新實例來不及啟動就超時，表現為使用者端的 502 或延遲飆高。這類事故的排查路徑是先看 scaling policy 的觸發條件與 cooldown 是否跟真實流量匹配，再看運算節點的啟動時間是否在可接受的範圍內。第二類是殭屍資源：跑完的測試機器沒關，停掉的開發環境仍掛著 EBS volume，閒置著燒錢卻沒人發現。殭屍資源的判讀訊號是 CPU 使用率長期趨近於零且沒有對外連線——靠定期盤點加上 tag 過濾最能系統性地收斂，詳見&lt;a href="https://tarrragon.github.io/blog/infra/08-governance-habits/" data-link-title="模組八：治理好習慣 — 規模長大後不失控的最小節奏" data-link-desc="tagging 規範、secrets 不進 code、成本可見性、最小可行節奏，規模長大後不失控">模組八：治理好習慣&lt;/a>。&lt;/p>
&lt;h3 id="網路network">網路（network）&lt;/h3>
&lt;p>誰能連到誰、流量走哪條路？這兩個問題的答案在網路層。VPC 切分、子網路、route table、security group 把可達性變成明確規則，而非預設全通。邊界沒畫清楚時，一個被入侵的服務就能橫向打穿整個環境。&lt;/p>
&lt;p>網路的失效模式分兩極。過度開放的代價是安全事故：一條 security group 入站規則寫成 &lt;code>0.0.0.0/0&lt;/code> 允許任何來源連到資料庫埠（5432、3306），等於把密碼驗證當作唯一防線，而暴力嘗試的掃描流量在公網上是持續的。意外隔離的代價是服務中斷：有人改了一條 route table 的預設路由，導致 private subnet 的服務失去出站能力——拉不到外部套件、連不上第三方 API，服務看起來在跑但功能全部退化。兩者在平時都不被注意，事故發生時才現形。排查網路問題的第一步通常是「這個封包走的那條路上，每一層有沒有放行」——route table → NACL → security group，逐層確認。網路地基的系統性設計在&lt;a href="https://tarrragon.github.io/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">模組三：網路地基&lt;/a>展開。&lt;/p>
&lt;h3 id="身分與憑證identity">身分與憑證（identity）&lt;/h3>
&lt;p>即使網路邊界畫得完美，一把權限過大的 access key 外洩了，攻擊者可以用 API 繞過所有網路規則直接操作資源——身分與憑證是五個面向中失守代價最高的一層。它的職責是讓人、服務、CI pipeline 各拿剛好夠用的權限（最小權限），並確保憑證有明確的生命週期。&lt;/p>
&lt;p>身分層的失效模式有兩類常見形態。權限擴散指的是一個 role 隨時間累積了遠超本職所需的權限——每次需求都加一條新的 action，卻從來沒人收斂已經用不到的舊權限。典型場景是一個 CI role 一開始只需要讀 S3、後來加了建 ECR image、再後來加了改 RDS parameter group，半年後這個 role 的 policy 有三十幾行 action，其中只有不到一半還在使用。憑證散落則指同一把 access key 被複製到越來越多地方——CI 環境變數、開發者筆電的 &lt;code>~/.aws/credentials&lt;/code>、某段部署腳本裡的 hardcode。每多一個副本就多一個外洩點，而外洩後的回退要找出所有副本同步輪替，這在手動環境裡幾乎做不到。這兩者的完整處理在&lt;a href="https://tarrragon.github.io/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二：身分與憑證地基&lt;/a>。&lt;/p>
&lt;h3 id="儲存storage">儲存（storage）&lt;/h3>
&lt;p>運算可以隨時重建，資料一旦遺失通常無法重來——這條分界線劃出了儲存層的職責。備份策略、版本保留、刪除保護構成儲存的三道防線，每一道都要在出事前就驗證過，而非事後才發現沒開。&lt;/p>
&lt;p>儲存涵蓋從物件儲存（S3）到區塊儲存（EBS）到受管資料庫（RDS）的底層磁碟。這些資源的共同特性是它們承載狀態，而狀態的失效模式跟運算不同——運算節點掛了重開一台就好，資料刪了就是刪了。具體的失效場景包括：一台 RDS 沒開刪除保護（deletion protection），有人清理開發資源時誤刪了 production 的資料庫；一個 S3 bucket 沒開 versioning，一段錯誤的腳本把整批物件覆寫成空內容，回不去了；一份 EBS snapshot 只保留了 3 天，周五出事、周一上班才發現，快照已經被自動清除。把刪除保護、備份保留天數、版本控制這些防線寫進 IaC，讓保護策略本身成為可審查、可追蹤的程式碼，是&lt;a href="https://tarrragon.github.io/blog/infra/05-core-services/" data-link-title="模組五：核心服務上 IaC" data-link-desc="資料庫、運算、儲存、load balancer 怎麼寫進基礎設施程式碼，以及上線順序">模組五：核心服務上 IaC&lt;/a> 的重點之一。&lt;/p>
&lt;h3 id="可觀測性observability">可觀測性（observability）&lt;/h3>
&lt;p>可觀測性負責「系統現在發生什麼、出事後查得到嗎」。它把 log、metric、trace 變成可查詢的事實來源。這層常被當成事後再補的附加品，但它和被它觀測的服務應該同生命週期一起建立。&lt;/p>
&lt;p>後補的可觀測性有一個結構性缺陷：出事之前沒有監控，代表出事當下最關鍵的那段資料不存在——知道服務「現在壞了」，但看不到「壞之前發生了什麼」。CPU 從什麼時候開始上升、錯誤率從哪個部署開始出現、某個 API 的延遲從什麼時候劣化——這些問題的答案需要連續的歷史資料，而歷史資料只能在事前就開始收集。另一個常見失效是 alarm 設了但通知沒有接到人：alarm 綁到一個 SNS topic，topic 的 subscription 是某個已停用的 email，值班工程師從頭到尾沒收到通知，直到使用者自己回報。可觀測性的 IaC 描述在&lt;a href="https://tarrragon.github.io/blog/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性與 log&lt;/a>。&lt;/p>
&lt;h3 id="五面的共同根源">五面的共同根源&lt;/h3>
&lt;p>這五面的共同點是：它們都不是應用功能，使用者看不到，但任何一面崩了，上面的功能全部跟著崩。這正是地基隱形的根源——它的價值只在缺席時被感知。&lt;/p>
&lt;h2 id="地基為什麼隱形">地基為什麼隱形&lt;/h2>
&lt;p>infra 的特性是「運作正常時完全不被感知，失效時才一次現形」。地基鋪得好的環境，工程師每天部署、擴縮、改設定，卻幾乎不會意識到底下有一層在支撐，因為它安靜地做對了每件事。這種隱形讓 infra 在資源排序上長期吃虧：看得見的功能有人催，看不見的地基沒人提。&lt;/p>
&lt;p>現形的時刻通常是環境失效的時刻，而且會在不同規模的團隊裡反覆出現——差別只在影響範圍。&lt;/p>
&lt;p>沒有描述檔的資源在需要重建時，必須從 Console 逐頁反推它的設定——屬於哪個 VPC、掛了哪些 security group、用了什麼 IAM role。這些資訊散落在不同頁面，拼湊一個資源的完整設定要半天，而且每個找到的設定都帶著「不確定是不是還有漏掉的」疑慮。&lt;/p></description><content:encoded><![CDATA[<p>基礎設施（infrastructure，簡稱 infra）是承載應用程式的那層資源與規則：運算、網路、身分、儲存、可觀測性，以及定義它們如何被建立、變更、回收的治理機制。它的責任是讓應用程式有一個可被信任、可被重建、可被審計的執行環境。本篇建立的責任邊界、成熟度階梯與 day 1 鐵律，是後續所有 infra 模組共用的心智模型，其他章節會直接引用這裡定義的詞彙。</p>
<h2 id="infra-的責任邊界">infra 的責任邊界</h2>
<p>infra 承擔的是「應用程式之下、作業系統之上」那層共享資源的供應與治理。把責任拆成五個面向比較好對齊：每一面都有自己的失效模式，混在一起談會讓判斷失焦。</p>
<h3 id="運算compute">運算（compute）</h3>
<p>運算負責「程式跑在哪、用多少資源、怎麼擴縮」。它的衡量點是容量與彈性：流量尖峰時能不能長出更多實例、閒置時能不能縮回去省錢。一台手動開的 VM 也是運算資源，差別只在它是否被納入可重建的描述。</p>
<p>運算涵蓋的光譜從 VM（EC2 instance）到容器（ECS task、Kubernetes pod）到 serverless function（Lambda）。抽象層級越高，infra 需要直接管理的細節越少——VM 要管 OS 更新與磁碟擴容，容器只需管映像與編排，serverless 幾乎只管程式碼與觸發條件。但抽象層級不改變運算的基本問題：它跑在什麼網路裡、用什麼身分存取其他資源、出了問題怎麼查。這些「接線」正是 infra 其他四個面向的職責。</p>
<p>運算層常見的失效模式有兩類。第一類是容量不足：流量上來了但 auto-scaling 沒設或設錯，新實例來不及啟動就超時，表現為使用者端的 502 或延遲飆高。這類事故的排查路徑是先看 scaling policy 的觸發條件與 cooldown 是否跟真實流量匹配，再看運算節點的啟動時間是否在可接受的範圍內。第二類是殭屍資源：跑完的測試機器沒關，停掉的開發環境仍掛著 EBS volume，閒置著燒錢卻沒人發現。殭屍資源的判讀訊號是 CPU 使用率長期趨近於零且沒有對外連線——靠定期盤點加上 tag 過濾最能系統性地收斂，詳見<a href="/blog/infra/08-governance-habits/" data-link-title="模組八：治理好習慣 — 規模長大後不失控的最小節奏" data-link-desc="tagging 規範、secrets 不進 code、成本可見性、最小可行節奏，規模長大後不失控">模組八：治理好習慣</a>。</p>
<h3 id="網路network">網路（network）</h3>
<p>誰能連到誰、流量走哪條路？這兩個問題的答案在網路層。VPC 切分、子網路、route table、security group 把可達性變成明確規則，而非預設全通。邊界沒畫清楚時，一個被入侵的服務就能橫向打穿整個環境。</p>
<p>網路的失效模式分兩極。過度開放的代價是安全事故：一條 security group 入站規則寫成 <code>0.0.0.0/0</code> 允許任何來源連到資料庫埠（5432、3306），等於把密碼驗證當作唯一防線，而暴力嘗試的掃描流量在公網上是持續的。意外隔離的代價是服務中斷：有人改了一條 route table 的預設路由，導致 private subnet 的服務失去出站能力——拉不到外部套件、連不上第三方 API，服務看起來在跑但功能全部退化。兩者在平時都不被注意，事故發生時才現形。排查網路問題的第一步通常是「這個封包走的那條路上，每一層有沒有放行」——route table → NACL → security group，逐層確認。網路地基的系統性設計在<a href="/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">模組三：網路地基</a>展開。</p>
<h3 id="身分與憑證identity">身分與憑證（identity）</h3>
<p>即使網路邊界畫得完美，一把權限過大的 access key 外洩了，攻擊者可以用 API 繞過所有網路規則直接操作資源——身分與憑證是五個面向中失守代價最高的一層。它的職責是讓人、服務、CI pipeline 各拿剛好夠用的權限（最小權限），並確保憑證有明確的生命週期。</p>
<p>身分層的失效模式有兩類常見形態。權限擴散指的是一個 role 隨時間累積了遠超本職所需的權限——每次需求都加一條新的 action，卻從來沒人收斂已經用不到的舊權限。典型場景是一個 CI role 一開始只需要讀 S3、後來加了建 ECR image、再後來加了改 RDS parameter group，半年後這個 role 的 policy 有三十幾行 action，其中只有不到一半還在使用。憑證散落則指同一把 access key 被複製到越來越多地方——CI 環境變數、開發者筆電的 <code>~/.aws/credentials</code>、某段部署腳本裡的 hardcode。每多一個副本就多一個外洩點，而外洩後的回退要找出所有副本同步輪替，這在手動環境裡幾乎做不到。這兩者的完整處理在<a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二：身分與憑證地基</a>。</p>
<h3 id="儲存storage">儲存（storage）</h3>
<p>運算可以隨時重建，資料一旦遺失通常無法重來——這條分界線劃出了儲存層的職責。備份策略、版本保留、刪除保護構成儲存的三道防線，每一道都要在出事前就驗證過，而非事後才發現沒開。</p>
<p>儲存涵蓋從物件儲存（S3）到區塊儲存（EBS）到受管資料庫（RDS）的底層磁碟。這些資源的共同特性是它們承載狀態，而狀態的失效模式跟運算不同——運算節點掛了重開一台就好，資料刪了就是刪了。具體的失效場景包括：一台 RDS 沒開刪除保護（deletion protection），有人清理開發資源時誤刪了 production 的資料庫；一個 S3 bucket 沒開 versioning，一段錯誤的腳本把整批物件覆寫成空內容，回不去了；一份 EBS snapshot 只保留了 3 天，周五出事、周一上班才發現，快照已經被自動清除。把刪除保護、備份保留天數、版本控制這些防線寫進 IaC，讓保護策略本身成為可審查、可追蹤的程式碼，是<a href="/blog/infra/05-core-services/" data-link-title="模組五：核心服務上 IaC" data-link-desc="資料庫、運算、儲存、load balancer 怎麼寫進基礎設施程式碼，以及上線順序">模組五：核心服務上 IaC</a> 的重點之一。</p>
<h3 id="可觀測性observability">可觀測性（observability）</h3>
<p>可觀測性負責「系統現在發生什麼、出事後查得到嗎」。它把 log、metric、trace 變成可查詢的事實來源。這層常被當成事後再補的附加品，但它和被它觀測的服務應該同生命週期一起建立。</p>
<p>後補的可觀測性有一個結構性缺陷：出事之前沒有監控，代表出事當下最關鍵的那段資料不存在——知道服務「現在壞了」，但看不到「壞之前發生了什麼」。CPU 從什麼時候開始上升、錯誤率從哪個部署開始出現、某個 API 的延遲從什麼時候劣化——這些問題的答案需要連續的歷史資料，而歷史資料只能在事前就開始收集。另一個常見失效是 alarm 設了但通知沒有接到人：alarm 綁到一個 SNS topic，topic 的 subscription 是某個已停用的 email，值班工程師從頭到尾沒收到通知，直到使用者自己回報。可觀測性的 IaC 描述在<a href="/blog/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性與 log</a>。</p>
<h3 id="五面的共同根源">五面的共同根源</h3>
<p>這五面的共同點是：它們都不是應用功能，使用者看不到，但任何一面崩了，上面的功能全部跟著崩。這正是地基隱形的根源——它的價值只在缺席時被感知。</p>
<h2 id="地基為什麼隱形">地基為什麼隱形</h2>
<p>infra 的特性是「運作正常時完全不被感知，失效時才一次現形」。地基鋪得好的環境，工程師每天部署、擴縮、改設定，卻幾乎不會意識到底下有一層在支撐，因為它安靜地做對了每件事。這種隱形讓 infra 在資源排序上長期吃虧：看得見的功能有人催，看不見的地基沒人提。</p>
<p>現形的時刻通常是環境失效的時刻，而且會在不同規模的團隊裡反覆出現——差別只在影響範圍。</p>
<p>沒有描述檔的資源在需要重建時，必須從 Console 逐頁反推它的設定——屬於哪個 VPC、掛了哪些 security group、用了什麼 IAM role。這些資訊散落在不同頁面，拼湊一個資源的完整設定要半天，而且每個找到的設定都帶著「不確定是不是還有漏掉的」疑慮。</p>
<p>一次安全稽核要求列出所有對外開放的連接埠，才發現 security group 散落在三個帳號、沒人說得清哪條規則還有用。有些規則是兩年前為了某個已經下線的服務開的，但沒人敢刪——萬一那條規則還被某個看不到的服務依賴呢？稽核結果是「我們列出了 37 條規則，其中 12 條無法確認是否仍在使用」。</p>
<p>一台資料庫磁碟滿了要擴容，才發現它從來沒進過任何納管流程。改它的 instance class 或磁碟大小，在 Console 上操作意味著可能觸發重啟，而這台資料庫是 production 唯一的寫入端點。操作時無法預測影響範圍，因為沒有可對照的描述檔；不操作則等著服務因為磁碟寫不進去而停擺。</p>
<p>這些場景有一個共同的累積模式：每一次「這次先手動救」的決定本身是合理的——救火當下沒有時間走流程。問題在於這些決定的殘留會堆疊。手動改了一條 security group 但沒記錄，下一個月又手動改了另一條，半年後沒人說得清哪些規則是原始設計、哪些是臨時補丁。每一次救火都在增加下一次排查的成本，而這個成本在平時完全隱形，只在下一次事故裡一次性浮現。</p>
<p>隱形債務的徵兆很直接：當團隊開始用這些語言描述某項資源，債就已經在累積——「不敢動那台機器」代表依賴關係不可見；「只有某某知道怎麼改」代表知識沒有沉澱在程式碼裡；「上次碰它好像出過事」代表變更缺乏 review 與回退機制；「那個先別管，能跑就好」代表技術債被刻意延後、沒有 tripwire。</p>
<p>地基的價值無法在平順時被看見，只能在它缺席的代價裡被回推，所以它需要一條和功能不同的論證路徑——這條路徑怎麼用商業語言講給上層聽，是<a href="/blog/infra/09-driving-adoption/" data-link-title="模組九：怎麼把 infra 推動起來" data-link-desc="技術正確不等於推得動 — 信任赤字、期望值對齊、知識共享，infra 落地的組織課題">模組九：怎麼把 infra 推動起來</a>的主題。</p>
<h2 id="day-1-鋪地基與事後補的成本差">day 1 鋪地基與事後補的成本差</h2>
<p>在資源剛開始長出來時就用程式碼描述它，和等環境長大後再回頭納管，兩者的成本差距是非線性的。早期鋪地基的成本接近固定：寫一份描述檔、建一個 state、設一條 pipeline，環境只有三五個資源時這些都很輕。事後補的成本則隨資源數量、相互依賴與「不確定能不能動」的恐懼一起放大。</p>
<p>事後納管的痛具體長這樣：一個手動建出來的資源要納入 IaC，得先把它當前的真實狀態完整反推成程式碼（import）。這個過程要逐欄比對 Console 上的設定——一個 RDS instance 的 parameter group、backup retention、storage type、multi-AZ 設定，Console 上看到什麼 HCL 裡就得寫什麼，漏一個欄位下次 apply 就可能把線上設定改掉。資源彼此有依賴時，納管順序也得排——一個 security group 引用另一個 security group 作為 source，兩個都還沒進 IaC 時，要決定哪個先 import、程式碼怎麼暫時處理另一個的引用。當這些手動資源還是線上服務正在用的，整個納管過程等於在開著的引擎上換零件。</p>
<p>import 之後的第一次 <code>plan</code> 是真正的考驗。如果 HCL 跟雲端現實有任何落差——哪怕只是一個 tag 的大小寫不同、或某個欄位在 Console 上有預設值但 HCL 裡沒寫——plan 會把那些落差列為需要修改的變更。在 stateless 資源上這只是小修正，在 production 的 RDS 上如果 plan 判定需要 replace（先刪後建），那就是一個會造成資料遺失的操作，必須在 apply 之前被攔截。手動環境累積的資源越多，這類 plan 裡的「驚喜」越多，整理每一個驚喜都要時間和注意力。這就是事後補的成本隨時間複利的具體機制。</p>
<p>務實的判準不是「day 1 就把所有東西寫成完美的 IaC」，而是「day 1 就讓新長出來的資源預設走可重建的路徑」。多數早期環境合理的選擇是讓地基類資源（網路、身分、state 本身）從一開始就在程式碼裡，而把還在高速試錯的應用層資源留一點手動彈性，等形狀穩定再納管。</p>
<p>哪些資源屬於「地基類」的判斷依據是回頭改的代價。VPC 的 CIDR 一旦確定、裡面的 subnet 都分配出去了，要改地址範圍幾乎等於重建整個網路。IAM 的 role 和 policy 一旦被多個服務引用，改動任一條的影響範圍是整個授權模型。state 後端的 bucket 和 lock table 如果第一天沒設好、用了本地 state，後續要搬到 remote backend 要處理 state migration——而 state 搬遷失敗可能讓工具失去對所有資源的記憶。這類地基的回頭成本是階梯式的（一旦長歪就很貴）。應用層資源的回頭成本是線性到多項式的（越晚越貴但不至於一步跳崖）。差別在於：前者的回頭成本固定，後者隨時間複利。<a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一：最小可行 IaC</a> 會示範這條最小路徑怎麼落地。</p>
<h2 id="成熟度階梯">成熟度階梯</h2>
<p>infra 的成熟度可以排成一條從「全手動」到「全程式碼治理」的階梯，每一階用「資源怎麼被建立與變更」來定義。這條階梯是全系列共用的座標：後續模組描述某個能力時，會說它對應到哪一階，所以這裡先把刻度釘清楚。</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>名稱</th>
          <th>資源怎麼被建立</th>
          <th>真實狀態的來源</th>
          <th>對應模組</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>Console 手動</td>
          <td>在網頁介面點選建立</td>
          <td>只存在於雲端，無描述</td>
          <td><a href="/blog/infra/before-infra/" data-link-title="模組負一：還沒有 infra 的環境怎麼盡量做好" data-link-desc="手動點起家的環境怎麼守底線、降低未來納管成本、辨識何時該開始導入 IaC — 給還沒有能力上 IaC 的真實起點">模組負一</a></td>
      </tr>
      <tr>
          <td>1</td>
          <td>腳本化</td>
          <td>用 CLI 或腳本建立</td>
          <td>腳本，但無狀態追蹤</td>
          <td>—</td>
      </tr>
      <tr>
          <td>2</td>
          <td>宣告式 IaC</td>
          <td>寫描述檔、由工具 apply</td>
          <td>state 檔記錄已建資源</td>
          <td><a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一</a></td>
      </tr>
      <tr>
          <td>3</td>
          <td>環境分離</td>
          <td>同一份模組套用多環境</td>
          <td>各環境獨立 state</td>
          <td><a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四</a></td>
      </tr>
      <tr>
          <td>4</td>
          <td>PR 流程治理</td>
          <td>變更走 PR、CI 自動 plan</td>
          <td>state + 版控歷史 + 審查紀錄</td>
          <td><a href="/blog/infra/07-infra-as-pr/" data-link-title="模組七：infra 走 PR 流程與自動化護欄" data-link-desc="infra 變更走 PR → plan → review diff → 合併 → apply，配 fmt / validate / tflint / checkov / tfsec 與 Atlantis 自動化，讓基礎設施可審查、可回溯、可交接">模組七</a></td>
      </tr>
  </tbody>
</table>
<h3 id="第-0-階console-手動">第 0 階：Console 手動</h3>
<p>所有環境的起點，也是該優先離開的一階。特徵是真實狀態只存在雲端，沒有任何離線描述，所以無法 review、無法重建、無法回答「這個環境長什麼樣」。它不是錯誤的起點，是還沒鋪地基的起點。</p>
<p>問自己兩個問題：「我們的 VPC 長什麼樣」能不能不打開 Console 就回答？「上一次 security group 什麼時候改過」能不能不翻 CloudTrail 就查到？兩題都要靠手動查，就還在第零階。停在這一階的環境怎麼盡量做好，見<a href="/blog/infra/before-infra/" data-link-title="模組負一：還沒有 infra 的環境怎麼盡量做好" data-link-desc="手動點起家的環境怎麼守底線、降低未來納管成本、辨識何時該開始導入 IaC — 給還沒有能力上 IaC 的真實起點">模組負一：還沒有 infra 的手動環境</a>。</p>
<h3 id="第-1-階腳本化">第 1 階：腳本化</h3>
<p>把建立動作寫成 CLI 或 shell 腳本，比手動可重複，但腳本只描述「怎麼建」，不追蹤「現在有什麼」。重跑同一支腳本可能重複建立或報錯，因為它不知道資源已經存在。</p>
<p>這一階的常見陷阱是誤以為「有腳本就等於有 IaC」。差別在狀態這塊地基——一份 <code>setup.sh</code> 能把環境從零建起來，但它回答不了「跑完後環境裡有哪些資源」「哪些資源是這個腳本建的、哪些是之前手動建的」「如果腳本裡的設定改了，下次重跑會不會把現有資源改壞」。這些都是 state 要解的問題。辨認自己在哪一階的方式是試一次：刪掉某個資源後重跑腳本，能自動把它補回來而不影響其他資源，那就已經在接近第 2 階的行為；重跑會報「already exists」錯誤或重複建立，就還在第 1 階。</p>
<h3 id="第-2-階宣告式-iac">第 2 階：宣告式 IaC</h3>
<p>地基真正成形的一階：用 Terraform / OpenTofu 這類工具寫下「環境應該長什麼樣」，工具負責比對現況與描述、算出差異再套用。state 檔在這裡誕生，成為「目前納管了哪些資源」的事實來源。</p>
<h3 id="怎麼知道自己在第-2-階">怎麼知道自己在第 2 階</h3>
<p>試回答一個問題：能不能從程式碼把整個環境在另一個帳號重建出來？「可以，apply 一次就好」代表 IaC 覆蓋率足夠。「大部分可以，但有些東西還是要手動補」——那些手動補的部分就是下一批該 import 的資源。另一個觀察角度：跑 <code>terraform plan</code> 時如果出現大量 drift（state 與現實不符），代表有人繞過 IaC 直接在 Console 改東西，Console 唯讀紀律在鬆動。工具選型與 state 管理的具體做法在<a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一：最小可行 IaC</a>。</p>
<h3 id="第-3-階環境分離">第 3 階：環境分離</h3>
<p>把同一份描述模組化，套用到 dev / staging / production 等多個環境，各自獨立 state。它解決的問題是「在 staging 驗證過的變更，能用同一套描述安全地推到 production」。</p>
<p>判讀訊號：dev 和 prod 的設定差異是否全部表達在參數裡、還是散落在不同的 code 分支中。如果 prod 目錄裡有一段 dev 目錄沒有的 code，那段 code 就是從來沒在低環境驗證過的生產設定——這是漂移的起點。另一個訊號：如果部署到 staging 和部署到 production 走的是兩條不同的 pipeline 或手動流程，代表環境分離只做了一半。完整切法在<a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四：環境分離與模組化</a>。</p>
<h3 id="第-4-階pr-流程治理">第 4 階：PR 流程治理</h3>
<p>把 infra 變更接上和應用程式碼相同的協作流程：變更走 pull request，CI 自動跑 plan 把預期差異貼上來，人審查後才 apply。到這一階，infra 的每次變更都有提案、審查、歷史與回退點。</p>
<p>用兩個問題定位：任意一次 infra 變更，能不能在 git log 裡找到對應的 PR、看到 plan 輸出、知道誰 review 的？如果某些變更是直接在 main 上 push 的、或是某人在本地 apply 的，代表流程有漏洞。更進一步：主要負責 infra 的人請假時，其他人能不能只靠讀 repo 就理解現狀並安全地改一個小設定？完整的治理護欄在<a href="/blog/infra/07-infra-as-pr/" data-link-title="模組七：infra 走 PR 流程與自動化護欄" data-link-desc="infra 變更走 PR → plan → review diff → 合併 → apply，配 fmt / validate / tflint / checkov / tfsec 與 Atlantis 自動化，讓基礎設施可審查、可回溯、可交接">模組七：infra 走 PR 流程</a>。</p>
<h3 id="階梯不是單向命令">階梯不是單向命令</h3>
<p>這條階梯是一把對齊現況的尺，用來判斷某項資源該停在哪一階，不是越高越好的單向命令。停在哪一階的依據是務實節奏——一個只有三個人、五個資源的早期團隊，強上第四階的 PR 流程，review 成本可能超過它擋下的風險。反過來，一個已經有二十個人在改 infra 的團隊，停在第二階不走 PR，就是在賭每次 apply 都不會出錯。</p>
<h2 id="早期新創的務實節奏">早期新創的務實節奏</h2>
<p>早期團隊的合理目標是「地基類資源先上到階梯第 2 階，應用層資源容許暫時留在低階」，而不是一步衝到第 4 階。資源有限、需求還在劇烈變動的階段，把全部資源都套上完整治理流程，收益正的機率不高——治理的固定成本會壓到本來就稀缺的開發頻寬。</p>
<p>判斷節奏的依據是「這項資源的形狀穩不穩、動它的代價高不高」：</p>
<table>
  <thead>
      <tr>
          <th>資源類型</th>
          <th>形狀穩定度</th>
          <th>改錯代價</th>
          <th>判準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>VPC / subnet</td>
          <td>高</td>
          <td>極高</td>
          <td>day 1 進 IaC</td>
      </tr>
      <tr>
          <td>IAM role / policy</td>
          <td>高</td>
          <td>極高</td>
          <td>day 1 進 IaC</td>
      </tr>
      <tr>
          <td>state backend</td>
          <td>高</td>
          <td>極高</td>
          <td>day 1 進 IaC</td>
      </tr>
      <tr>
          <td>RDS（已穩定的）</td>
          <td>中高</td>
          <td>極高</td>
          <td>形狀確定後立刻進</td>
      </tr>
      <tr>
          <td>對外 LB</td>
          <td>中</td>
          <td>高</td>
          <td>開始有流量就進</td>
      </tr>
      <tr>
          <td>應用層 EC2 / ECS</td>
          <td>低到中</td>
          <td>中</td>
          <td>開始被依賴或第二人要改時進</td>
      </tr>
      <tr>
          <td>測試用臨時資源</td>
          <td>低</td>
          <td>低</td>
          <td>可以留在手動，設 tag 方便清理</td>
      </tr>
  </tbody>
</table>
<h3 id="day-1-鐵律">day 1 鐵律</h3>
<p>網路拓撲、身分權限、state 後端這三類地基資源，一旦長歪回頭改的代價極高，值得 day 1 就進 IaC——這是少數接近「該照做」的硬判準，因為它牽涉安全邊界：</p>
<ul>
<li><strong>VPC / subnet</strong>：CIDR 一旦確定、subnet 分配出去，改地址範圍幾乎等於重建整個網路（見<a href="/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">模組三</a>）</li>
<li><strong>IAM role / policy</strong>：權限模型被多個服務引用後，改動任一條的影響範圍是整個授權體系（見<a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二</a>）</li>
<li><strong>state backend</strong>：state 的存放位置與鎖機制如果第一天沒設好，後續 state migration 失敗可能讓工具失去對所有資源的記憶（見<a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一</a>）</li>
</ul>
<p>反過來，一個還在每週改三次規格的功能用的運算資源，過早凍進嚴格流程反而拖慢試錯。這時容許它手動，但設一條 tripwire：當它開始被線上流量依賴、或開始有第二個人需要改它時，就是把它納管的時機。</p>
<p>tripwire 的操作方式是在建立資源時就決定「觸發納管的條件」，而非等到某天靈感來了才想到要 import。例如：一台跑開發用途的 EC2，建立時在內部文件標記「當這台開始接 staging 或 production 流量時納管」；一個 S3 bucket 正在測試用，標記「當開始存正式用戶上傳的檔案時納管」。tripwire 讓「什麼時候該進 IaC」變成一個可追蹤的條件，而非一個持續被拖延的意願。</p>
<h3 id="兩個反向誤判">兩個反向誤判</h3>
<p>過度設計和放任手動是這個階段的兩個反向誤判。</p>
<p>過度設計的訊號：環境只有五個資源，卻已經有多層抽象模組和還用不到的多環境結構，維護抽象的時間比省下的時間多。常見的觸發是照搬最佳實踐文章的全部教條——三層 module 嵌套、Terragrunt 全家桶、每個資源都有 <code>for_each</code>——結果團隊裡只有一個人看得懂這套結構。對這類過度設計的自測是：「如果今天不做這個抽象，三個月後補的成本是多少？」如果答案是花一小時就能補，那就三個月後再說。</p>
<p>放任手動的訊號：每次有人問「這個怎麼建的」都只能去翻某個人的記憶，地基債務在無聲累積。放任手動的常見藉口是「我們還在早期、先把功能做出來再說」——這句話在創業前三個月合理，但如果三個月後還在這麼說、而環境已經有二十個資源、三個人在改，債就開始複利了。</p>
<p>務實節奏就是在這兩者之間，讓地基先穩、讓應用層保留試錯彈性，再隨著形狀固定逐項往階梯上推。</p>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/before-infra/" data-link-title="模組負一：還沒有 infra 的環境怎麼盡量做好" data-link-desc="手動點起家的環境怎麼守底線、降低未來納管成本、辨識何時該開始導入 IaC — 給還沒有能力上 IaC 的真實起點">模組負一：還沒有 infra 的手動環境</a>：階梯第 0 階的環境怎麼盡量做好</li>
<li>→ <a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一：最小可行 IaC</a>：地基資源跨上成熟度階梯第 2 階的最小路徑</li>
<li>→ <a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二：身分與憑證地基</a>：身分層的權限收斂與憑證生命週期</li>
<li>→ <a href="/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">模組三：網路地基</a>：網路層的隔離、路由與 security group 設計</li>
<li>→ <a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四：環境分離與模組化</a>：成熟度階梯第 3 階的切法</li>
<li>→ <a href="/blog/infra/05-core-services/" data-link-title="模組五：核心服務上 IaC" data-link-desc="資料庫、運算、儲存、load balancer 怎麼寫進基礎設施程式碼，以及上線順序">模組五：核心服務上 IaC</a>：運算與儲存資源的 IaC 描述</li>
<li>→ <a href="/blog/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性與 log</a>：可觀測性同生命週期管理</li>
<li>→ <a href="/blog/infra/07-infra-as-pr/" data-link-title="模組七：infra 走 PR 流程與自動化護欄" data-link-desc="infra 變更走 PR → plan → review diff → 合併 → apply，配 fmt / validate / tflint / checkov / tfsec 與 Atlantis 自動化，讓基礎設施可審查、可回溯、可交接">模組七：infra 走 PR 流程</a>：成熟度階梯第 4 階的治理護欄</li>
<li>→ <a href="/blog/infra/08-governance-habits/" data-link-title="模組八：治理好習慣 — 規模長大後不失控的最小節奏" data-link-desc="tagging 規範、secrets 不進 code、成本可見性、最小可行節奏，規模長大後不失控">模組八：治理好習慣</a>：殭屍資源盤點與 tagging 規範</li>
<li>→ <a href="/blog/infra/09-driving-adoption/" data-link-title="模組九：怎麼把 infra 推動起來" data-link-desc="技術正確不等於推得動 — 信任赤字、期望值對齊、知識共享，infra 落地的組織課題">模組九：怎麼把 infra 推動起來</a>：地基的價值怎麼用商業語言講給上層聽</li>
</ul>
]]></content:encoded></item><item><title>雲端部署裡已經存在的 infra 元件</title><link>https://tarrragon.github.io/blog/infra/00-infra-mindset/personal-project-to-infra/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/00-infra-mindset/personal-project-to-infra/</guid><description>&lt;p>任何一次雲端部署都會用到基礎設施元件 — 網路隔離、存取控制、儲存、身分認證。即使從來沒有手動設定過這些東西，雲端平台也會用預設值替你建立它們。這篇文章把那些藏在預設值裡的 infra 元件逐一攤開，說明各自解決什麼問題，以及不管理它們時會在什麼時間點造成什麼後果。&lt;/p>
&lt;h2 id="每次部署都會觸及的四個元件">每次部署都會觸及的四個元件&lt;/h2>
&lt;p>在 AWS Console 上建立一台 EC2 instance 時，精靈流程的每一步各對應一個 infra 元件。Console 把它們包進填表流程裡，讓建立動作看起來只是「選規格 → 按確認 → 機器出現」，但每一步的選擇都在決定這台機器的網路位置、存取邊界與儲存策略。&lt;/p>
&lt;h3 id="vpc-與-subnet">VPC 與 subnet&lt;/h3>
&lt;p>Network settings 那一步，Console 預設選一個 default VPC。VPC（Virtual Private Cloud）是雲端帳號裡的一塊邏輯隔離網段 — 裡面的機器彼此可達，外部流量要經過明確的入口才進得來。subnet 是 VPC 裡再切出來的子區域，決定機器落在哪個可用區（availability zone）以及對外暴露的程度。&lt;/p>
&lt;p>default VPC 在每個 region 自動存在，它的特性是所有 subnet 都是 public（有對外路由）、security group 預設接受部分入站流量。這組預設值讓部署能快速完成，但它的隱含假設是「所有資源都可以對外」— 把資料庫放進 default VPC 時，資料庫的網路位置跟對外的 web server 在同一層，沒有隔離。&lt;/p>
&lt;h3 id="security-group">Security group&lt;/h3>
&lt;p>同一個精靈流程會出現 security group 選項。security group 是掛在機器網路介面上的防火牆規則，決定哪些來源 IP、哪些 port 的流量可以進出。&lt;/p>
&lt;p>預設建立的 security group 通常開放 SSH（port 22）給 &lt;code>0.0.0.0/0&lt;/code> — 任何 IP 都能嘗試連線。對一台短期測試機來說，這讓操作者能連進去；對一台開始承載服務的機器來說，全球的自動掃描工具會在上線幾分鐘內開始對 SSH port 嘗試登入。這條規則是功能正確的（SSH 能連），但安全邊界是開放的（誰都能試）。&lt;/p>
&lt;h3 id="iam">IAM&lt;/h3>
&lt;p>登入 Console 本身就用到了 IAM（Identity and Access Management）。IAM 管理「誰能對哪些資源做什麼操作」。首次註冊時使用的 root account 擁有帳號內所有權限，用 root 做日常操作等於每次都拿著能開所有門的萬能鑰匙。&lt;/p>
&lt;p>開發者與 IAM 的第一個交集通常是 access key — 一組靜態憑證，讓 CLI 工具或部署腳本能用程式化方式操作雲端資源。這把 key 被存進 &lt;code>~/.aws/credentials&lt;/code> 或專案的 &lt;code>.env&lt;/code> 檔後，它就是一個有權限的身分憑證，決定了持有者能動多少東西。key 沒有到期時間，權限範圍取決於它綁定的 IAM user 或 role 被授予了什麼 policy。&lt;/p>
&lt;h3 id="儲存">儲存&lt;/h3>
&lt;p>EC2 附帶的 EBS volume 是儲存層 infra。預設大小通常是 8 GB，預設沒有加密，預設沒有快照排程。磁碟裡只有 OS 跟應用程式時，壞了重建即可。一旦上面開始跑資料庫、存使用者檔案，磁碟裡就有了不可重建的狀態，「壞了重建」這個退路就消失了。&lt;/p>
&lt;h3 id="預設值的共同特性">預設值的共同特性&lt;/h3>
&lt;p>VPC、subnet、security group、IAM、EBS — 這些在每次部署時全部自動存在或被預設建立。預設值的設計目標是「讓部署能完成」，而非「讓環境安全且可管理」。兩者之間的落差會在特定時間點浮現。&lt;/p>
&lt;h2 id="不管理這些元件的後果">不管理這些元件的後果&lt;/h2>
&lt;p>infra 元件不被管理時，後果不會立刻出現 — 它們在特定條件觸發時一次浮現。以下是依觸發頻率排列的常見情境。&lt;/p>
&lt;h3 id="環境無法重建">環境無法重建&lt;/h3>
&lt;p>帳號需要遷移、機器需要在另一個 region 重建、或者某個資源損壞需要從頭來過。這時才發現：security group 開了哪些規則、RDS 的 parameter group 改了哪些值、S3 bucket 的 CORS policy 怎麼設的 — 這些設定散落在 Console 各頁面，唯一的重建方式是逐頁翻 Console 比對。&lt;/p>
&lt;p>可重建性的判準：能不能在空白帳號裡，不靠記憶、不靠翻舊帳號 Console，把環境完整重建出來。&lt;/p>
&lt;h3 id="憑證外洩">憑證外洩&lt;/h3>
&lt;p>access key 被推進 git 歷史 — &lt;code>.env&lt;/code> 檔忘記加進 &lt;code>.gitignore&lt;/code>，一次 push 就把 key 送上了公開 repo。GitHub 上有自動掃描工具在監控 commit，從 push 到 key 被利用可能只需要幾分鐘。常見的攻擊操作是在帳號裡開大量高規格 instance 跑礦機，帳單可以在幾小時內衝到數千美元。&lt;/p>
&lt;p>即使立刻撤銷 key，git 歷史裡的 key 還在 — 每個 clone 過 repo 的人都有一份副本。回退代價取決於 key 的權限範圍：如果綁的是 AdministratorAccess，攻擊者能做的事等於帳號擁有者能做的所有事。&lt;/p>
&lt;h3 id="誤刪資源">誤刪資源&lt;/h3>
&lt;p>在 Console 清理資源時刪錯一個 security group，另一台還在跑的機器引用了它 — 網路規則瞬間歸零，服務斷線。Console 沒有「刪了會影響什麼」的預覽，確認按下去就生效。&lt;/p></description><content:encoded><![CDATA[<p>任何一次雲端部署都會用到基礎設施元件 — 網路隔離、存取控制、儲存、身分認證。即使從來沒有手動設定過這些東西，雲端平台也會用預設值替你建立它們。這篇文章把那些藏在預設值裡的 infra 元件逐一攤開，說明各自解決什麼問題，以及不管理它們時會在什麼時間點造成什麼後果。</p>
<h2 id="每次部署都會觸及的四個元件">每次部署都會觸及的四個元件</h2>
<p>在 AWS Console 上建立一台 EC2 instance 時，精靈流程的每一步各對應一個 infra 元件。Console 把它們包進填表流程裡，讓建立動作看起來只是「選規格 → 按確認 → 機器出現」，但每一步的選擇都在決定這台機器的網路位置、存取邊界與儲存策略。</p>
<h3 id="vpc-與-subnet">VPC 與 subnet</h3>
<p>Network settings 那一步，Console 預設選一個 default VPC。VPC（Virtual Private Cloud）是雲端帳號裡的一塊邏輯隔離網段 — 裡面的機器彼此可達，外部流量要經過明確的入口才進得來。subnet 是 VPC 裡再切出來的子區域，決定機器落在哪個可用區（availability zone）以及對外暴露的程度。</p>
<p>default VPC 在每個 region 自動存在，它的特性是所有 subnet 都是 public（有對外路由）、security group 預設接受部分入站流量。這組預設值讓部署能快速完成，但它的隱含假設是「所有資源都可以對外」— 把資料庫放進 default VPC 時，資料庫的網路位置跟對外的 web server 在同一層，沒有隔離。</p>
<h3 id="security-group">Security group</h3>
<p>同一個精靈流程會出現 security group 選項。security group 是掛在機器網路介面上的防火牆規則，決定哪些來源 IP、哪些 port 的流量可以進出。</p>
<p>預設建立的 security group 通常開放 SSH（port 22）給 <code>0.0.0.0/0</code> — 任何 IP 都能嘗試連線。對一台短期測試機來說，這讓操作者能連進去；對一台開始承載服務的機器來說，全球的自動掃描工具會在上線幾分鐘內開始對 SSH port 嘗試登入。這條規則是功能正確的（SSH 能連），但安全邊界是開放的（誰都能試）。</p>
<h3 id="iam">IAM</h3>
<p>登入 Console 本身就用到了 IAM（Identity and Access Management）。IAM 管理「誰能對哪些資源做什麼操作」。首次註冊時使用的 root account 擁有帳號內所有權限，用 root 做日常操作等於每次都拿著能開所有門的萬能鑰匙。</p>
<p>開發者與 IAM 的第一個交集通常是 access key — 一組靜態憑證，讓 CLI 工具或部署腳本能用程式化方式操作雲端資源。這把 key 被存進 <code>~/.aws/credentials</code> 或專案的 <code>.env</code> 檔後，它就是一個有權限的身分憑證，決定了持有者能動多少東西。key 沒有到期時間，權限範圍取決於它綁定的 IAM user 或 role 被授予了什麼 policy。</p>
<h3 id="儲存">儲存</h3>
<p>EC2 附帶的 EBS volume 是儲存層 infra。預設大小通常是 8 GB，預設沒有加密，預設沒有快照排程。磁碟裡只有 OS 跟應用程式時，壞了重建即可。一旦上面開始跑資料庫、存使用者檔案，磁碟裡就有了不可重建的狀態，「壞了重建」這個退路就消失了。</p>
<h3 id="預設值的共同特性">預設值的共同特性</h3>
<p>VPC、subnet、security group、IAM、EBS — 這些在每次部署時全部自動存在或被預設建立。預設值的設計目標是「讓部署能完成」，而非「讓環境安全且可管理」。兩者之間的落差會在特定時間點浮現。</p>
<h2 id="不管理這些元件的後果">不管理這些元件的後果</h2>
<p>infra 元件不被管理時，後果不會立刻出現 — 它們在特定條件觸發時一次浮現。以下是依觸發頻率排列的常見情境。</p>
<h3 id="環境無法重建">環境無法重建</h3>
<p>帳號需要遷移、機器需要在另一個 region 重建、或者某個資源損壞需要從頭來過。這時才發現：security group 開了哪些規則、RDS 的 parameter group 改了哪些值、S3 bucket 的 CORS policy 怎麼設的 — 這些設定散落在 Console 各頁面，唯一的重建方式是逐頁翻 Console 比對。</p>
<p>可重建性的判準：能不能在空白帳號裡，不靠記憶、不靠翻舊帳號 Console，把環境完整重建出來。</p>
<h3 id="憑證外洩">憑證外洩</h3>
<p>access key 被推進 git 歷史 — <code>.env</code> 檔忘記加進 <code>.gitignore</code>，一次 push 就把 key 送上了公開 repo。GitHub 上有自動掃描工具在監控 commit，從 push 到 key 被利用可能只需要幾分鐘。常見的攻擊操作是在帳號裡開大量高規格 instance 跑礦機，帳單可以在幾小時內衝到數千美元。</p>
<p>即使立刻撤銷 key，git 歷史裡的 key 還在 — 每個 clone 過 repo 的人都有一份副本。回退代價取決於 key 的權限範圍：如果綁的是 AdministratorAccess，攻擊者能做的事等於帳號擁有者能做的所有事。</p>
<h3 id="誤刪資源">誤刪資源</h3>
<p>在 Console 清理資源時刪錯一個 security group，另一台還在跑的機器引用了它 — 網路規則瞬間歸零，服務斷線。Console 沒有「刪了會影響什麼」的預覽，確認按下去就生效。</p>
<p>資料庫的誤刪代價更大。RDS instance 被刪除時如果沒有開啟刪除保護、沒有 snapshot，資料永久消失。手動環境裡沒有自動防護，保護要靠人記得去開。</p>
<h3 id="變更不可追溯">變更不可追溯</h3>
<p>某次改了 security group 規則讓某個 API 能通，隔週另一個服務斷線。排查時發現是那條規則影響了未知的依賴，但沒有變更紀錄，「上次改了什麼」只存在改動者的記憶裡。Console 不標記規則的新增時間，要查得去 CloudTrail 翻 API 呼叫日誌。</p>
<h2 id="多人協作時的放大效應">多人協作時的放大效應</h2>
<p>一個人操作時，所有隱性知識都在自己腦裡。第二個人加入時，這套隱性知識立刻變成障礙。</p>
<p>身分管理的第一個問題是：共用 access key 還是建新的 IAM user。共用 key 代表兩人的操作在 CloudTrail 裡無法區分是誰做的；建新 user 需要決定權限範圍 — 給太寬怕誤操作，給太窄什麼都做不了。</p>
<p>變更衝突是第二個問題。Console 沒有鎖機制 — 兩人可以同時打開同一個 security group 的編輯頁面，各自修改不同規則，後存的覆蓋先存的，沒有提示。一人改了設定沒通知另一人，排查時不確定「這條規則是原本就有的還是新加的」。</p>
<p>這些問題的共同根源是環境狀態只存在於 Console 和個別人的記憶裡，沒有所有人都能讀到的、可比對差異的事實來源。Infrastructure as Code（IaC）把環境描述寫進程式碼，讓事實來源從記憶變成 repo 裡可以 diff、可以 review 的檔案 — 這是<a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一：最小可行 IaC</a> 的主題。</p>
<h2 id="依規模遞增的-infra-需求">依規模遞增的 infra 需求</h2>
<p>infra 的複雜度隨服務的使用者數量、團隊大小與合規要求遞增，但核心責任在每個規模都相同：讓環境可被理解、可被重建、可被安全地變更。</p>
<p>單人運維時，infra 的最小需求是盤點（知道環境裡有什麼）、描述（能重建）、憑證管理（access key 不外洩）。這三件事不需要 Terraform — 一份手動清單、固定命名規則、把 key 換成短期憑證，就覆蓋了最高代價的風險。做法見<a href="/blog/infra/before-infra/" data-link-title="模組負一：還沒有 infra 的環境怎麼盡量做好" data-link-desc="手動點起家的環境怎麼守底線、降低未來納管成本、辨識何時該開始導入 IaC — 給還沒有能力上 IaC 的真實起點">模組負一：還沒有 infra 的環境</a>。</p>
<p>多人協作時，需要變更可追溯和最小權限。IaC 在這個階段開始產生收益，因為「從程式碼看環境」比「翻 Console」快，而且程式碼可以 review。做法見<a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一</a>。</p>
<p>服務有營收、團隊超過十人時，需要環境分離（dev 與 prod 不互相干擾）、自動化護欄（變更走 PR 流程）、可觀測性（出事時查得到）。這些能力疊加在前面兩層之上。完整的能力階梯見<a href="/blog/infra/00-infra-mindset/" data-link-title="模組零：infra 是什麼，為什麼 day 1 就要鋪地基" data-link-desc="基礎設施的責任邊界、成熟度階梯，以及地基為什麼總在環境爆炸時才被看見">模組零：infra 是什麼</a>。</p>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/00-infra-mindset/" data-link-title="模組零：infra 是什麼，為什麼 day 1 就要鋪地基" data-link-desc="基礎設施的責任邊界、成熟度階梯，以及地基為什麼總在環境爆炸時才被看見">模組零：infra 是什麼</a>：五個責任面向與成熟度階梯（從全手動到全程式碼治理的五階分級）的完整定義</li>
<li>→ <a href="/blog/infra/before-infra/" data-link-title="模組負一：還沒有 infra 的環境怎麼盡量做好" data-link-desc="手動點起家的環境怎麼守底線、降低未來納管成本、辨識何時該開始導入 IaC — 給還沒有能力上 IaC 的真實起點">模組負一：還沒有 infra 的環境</a>：手動環境怎麼守底線、降低未來納管成本</li>
<li>→ <a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一：最小可行 IaC</a>：第一行 IaC 從哪裡開始</li>
<li>→ <a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二：身分與憑證地基</a>：access key 的風險與替代方案</li>
</ul>
]]></content:encoded></item><item><title>模組零：infra 是什麼，為什麼 day 1 就要鋪地基</title><link>https://tarrragon.github.io/blog/infra/00-infra-mindset/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/00-infra-mindset/</guid><description>&lt;p>基礎設施（infrastructure，簡稱 infra）是承載應用程式的那層資源與規則：運算、網路、身分、儲存、可觀測性，以及定義它們如何被建立、變更、回收的治理機制。它的責任是讓應用程式有一個可被信任、可被重建、可被審計的執行環境。本章建立的責任邊界、成熟度階梯與 day 1 鐵律，是後續所有 infra 模組共用的心智模型，其他章節會直接引用這裡定義的詞彙。&lt;/p>
&lt;h2 id="infra-的責任邊界">infra 的責任邊界&lt;/h2>
&lt;p>infra 承擔的是「應用程式之下、作業系統之上」那層共享資源的供應與治理。把責任拆成五個面向比較好對齊：每一面都有自己的失效模式，混在一起談會讓判斷失焦。&lt;/p>
&lt;p>運算（compute）負責「程式跑在哪、用多少資源、怎麼擴縮」。它的衡量點是容量與彈性：流量尖峰時能不能長出更多實例、閒置時能不能縮回去省錢。一台手動開的 VM 也是運算資源，差別只在它是否被納入可重建的描述。&lt;/p>
&lt;p>網路（network）負責「誰能連到誰、流量走哪條路」。它的責任是把可達性變成明確規則，而非預設全通。VPC 切分、子網路、security group 都屬於這層，邊界沒畫清楚時，一個被入侵的服務就能橫向打穿整個環境。&lt;/p>
&lt;p>身分與憑證（identity）負責「誰能對哪些資源做什麼操作」。它承擔最小權限的落地：人、服務、CI pipeline 各拿剛好夠用的權限，憑證有明確的生命週期。這層失守的代價最高，因為它是其他所有資源的閘門。&lt;/p>
&lt;p>儲存（storage）負責「資料放哪、能不能還原」。它的責任是持久性與可回復性：備份策略、版本保留、刪除保護。運算可以隨時重建，資料一旦遺失通常無法重來，所以這層的回退路徑要在出事前就驗證過。&lt;/p>
&lt;p>可觀測性（observability）負責「系統現在發生什麼、出事後查得到嗎」。它把 log、metric、trace 變成可查詢的事實來源。這層常被當成事後再補的附加品，但它和被它觀測的服務應該同生命週期一起建立，補在後面的可觀測性往往缺了出事當下最關鍵的那段資料。&lt;/p>
&lt;p>這五面的共同點是：它們都不是應用功能，使用者看不到，但任何一面崩了，上面的功能全部跟著崩。這正是地基隱形的根源。&lt;/p>
&lt;h2 id="地基為什麼隱形">地基為什麼隱形&lt;/h2>
&lt;p>infra 的特性是「運作正常時完全不被感知，失效時才一次現形」。地基鋪得好的環境，工程師每天部署、擴縮、改設定，卻幾乎不會意識到底下有一層在支撐，因為它安靜地做對了每件事。這種隱形讓 infra 在資源排序上長期吃虧：看得見的功能有人催，看不見的地基沒人提。&lt;/p>
&lt;p>現形的時刻通常是環境爆炸的時刻。一個沒有人記得怎麼建的服務掛了，才發現它是某位早期工程師在 Console 手動點出來的，沒有任何描述檔；一次安全稽核要求列出所有對外開放的連接埠，才發現 security group 散落在三個帳號、沒人說得清哪條規則還有用；一台資料庫磁碟滿了要擴容，才發現它從來沒進過任何納管流程，動它等於拆未爆彈。&lt;/p>
&lt;p>隱形債務的徵兆很直接：當團隊開始用「不敢動那台機器」「只有某某知道怎麼改」來描述某項資源，債就已經在累積。地基的價值無法在平順時被看見，只能在它缺席的代價裡被回推，所以它需要一條和功能不同的論證路徑——這條路徑怎麼用商業語言講給上層聽，是「模組九：怎麼把 infra 推動起來」的主題。&lt;/p>
&lt;h2 id="day-1-鋪地基與事後補的成本差">day 1 鋪地基與事後補的成本差&lt;/h2>
&lt;p>在資源剛開始長出來時就用程式碼描述它，和等環境長大後再回頭納管，兩者的成本差距是非線性的。早期鋪地基的成本接近固定：寫一份描述檔、建一個 state、設一條 pipeline，環境只有三五個資源時這些都很輕。事後補的成本則隨資源數量、相互依賴與「不確定能不能動」的恐懼一起放大。&lt;/p>
&lt;p>事後納管的痛具體長這樣：一個手動建出來的資源要納入 IaC，得先把它當前的真實狀態完整反推成程式碼（import），這個過程要逐欄比對 Console 上的設定，漏一個欄位下次 apply 就可能把線上設定改掉。資源彼此有依賴時，納管順序也得排——先納管的資源引用了還沒納管的資源，描述就接不起來。當這些手動資源還是線上服務正在用的，整個納管過程等於在開著的引擎上換零件。&lt;/p>
&lt;p>務實的判準不是「day 1 就把所有東西寫成完美的 IaC」，而是「day 1 就讓新長出來的資源預設走可重建的路徑」。多數早期環境划得來的選擇，是讓地基類資源（網路、身分、state 本身）從一開始就在程式碼裡，而把還在高速試錯的應用層資源留一點手動彈性，等形狀穩定再納管。差別在於：前者的回頭成本固定，後者隨時間複利。「模組一：最小可行 IaC」會示範這條最小路徑怎麼落地。&lt;/p>
&lt;h2 id="成熟度階梯">成熟度階梯&lt;/h2>
&lt;p>infra 的成熟度可以排成一條從「全手動」到「全程式碼治理」的階梯，每一階用「資源怎麼被建立與變更」來定義。這條階梯是全系列共用的座標：後續模組描述某個能力時，會說它對應到哪一階，所以這裡先把刻度釘清楚。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>階段&lt;/th>
 &lt;th>名稱&lt;/th>
 &lt;th>資源怎麼被建立&lt;/th>
 &lt;th>真實狀態的來源&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>0&lt;/td>
 &lt;td>Console 手動&lt;/td>
 &lt;td>在網頁介面點選建立&lt;/td>
 &lt;td>只存在於雲端，無描述&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1&lt;/td>
 &lt;td>腳本化&lt;/td>
 &lt;td>用 CLI 或腳本建立&lt;/td>
 &lt;td>腳本，但無狀態追蹤&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2&lt;/td>
 &lt;td>宣告式 IaC&lt;/td>
 &lt;td>寫描述檔、由工具 apply&lt;/td>
 &lt;td>state 檔記錄已建資源&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3&lt;/td>
 &lt;td>環境分離&lt;/td>
 &lt;td>同一份模組套用多環境&lt;/td>
 &lt;td>各環境獨立 state&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4&lt;/td>
 &lt;td>PR 流程治理&lt;/td>
 &lt;td>變更走 PR、CI 自動 plan&lt;/td>
 &lt;td>state + 版控歷史 + 審查紀錄&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>第 0 階「Console 手動」是所有環境的起點，也是必須最快離開的一階。它的特徵是真實狀態只存在雲端，沒有任何離線描述，所以無法 review、無法重建、無法回答「這個環境長什麼樣」。它不是錯誤的起點，是還沒鋪地基的起點。&lt;/p>
&lt;p>第 1 階「腳本化」把建立動作寫成 CLI 或 shell 腳本，比手動可重複，但腳本只描述「怎麼建」，不追蹤「現在有什麼」。重跑同一支腳本可能重複建立或報錯，因為它不知道資源已經存在。這一階的常見陷阱是誤以為「有腳本就等於有 IaC」，差的是狀態這塊地基。&lt;/p>
&lt;p>第 2 階「宣告式 IaC」是地基真正成形的一階：用 Terraform / OpenTofu 這類工具寫下「環境應該長什麼樣」，工具負責比對現況與描述、算出差異再套用。state 檔在這裡誕生，成為「目前納管了哪些資源」的事實來源。這一階的判讀訊號是：能不能從程式碼把整個環境在另一個帳號重建出來。&lt;/p>
&lt;p>第 3 階「環境分離」把同一份描述模組化，套用到 dev / staging / production 等多個環境，各自獨立 state。它解決的問題是「在 staging 驗證過的變更，能用同一套描述安全地推到 production」。「模組四：環境分離與模組化」專講這一階的切法。&lt;/p>
&lt;p>第 4 階「PR 流程治理」把 infra 變更接上和應用程式碼相同的協作流程：變更走 pull request，CI 自動跑 plan 把預期差異貼上來，人審查後才 apply。到這一階，infra 的每次變更都有提案、審查、歷史與回退點。「模組七：infra 走 PR 流程」會完整展開這套護欄。&lt;/p>
&lt;p>這條階梯是一把對齊現況的尺，用來判斷某項資源該停在哪一階，不是越高越好的單向命令。停在哪一階的依據，是務實節奏。&lt;/p>
&lt;h2 id="早期新創的務實節奏">早期新創的務實節奏&lt;/h2>
&lt;p>早期團隊的合理目標是「地基類資源先上到階梯第 2 階，應用層資源容許暫時留在低階」，而不是一步衝到第 4 階。資源有限、需求還在劇烈變動的階段，把全部資源都套上完整治理流程，划得來的機率不高——治理的固定成本會壓到本來就稀缺的開發頻寬。&lt;/p>
&lt;p>判斷節奏的依據是「這項資源的形狀穩不穩、動它的代價高不高」。網路拓撲、身分權限、state 後端這類地基，一旦長歪回頭改的代價極高，值得 day 1 就進 IaC，這是少數接近「該照做」的硬判準，因為它牽涉安全邊界。反過來，一個還在每週改三次規格的功能用的運算資源，過早凍進嚴格流程反而拖慢試錯，這時容許它手動、但設一條 tripwire：當它開始被線上流量依賴、或開始有第二個人需要改它時，就是把它納管的時機。&lt;/p>
&lt;p>過度設計和放任手動是這個階段的兩個反向誤判。過度設計的訊號是：環境只有五個資源，卻已經有多層抽象模組和還用不到的多環境結構，維護抽象的時間比省下的時間多。放任手動的訊號是：每次有人問「這個怎麼建的」都只能去翻某個人的記憶，地基債務在無聲累積。務實節奏就是在這兩者之間，讓地基先穩、讓應用層保留試錯彈性，再隨著形狀固定逐項往階梯上推。&lt;/p>
&lt;h2 id="章節文章">章節文章&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>文章&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/infra/00-infra-mindset/personal-project-to-infra/" data-link-title="雲端部署裡已經存在的 infra 元件" data-link-desc="VPC、security group、IAM、儲存 — 這些元件在任何雲端部署裡都已經在運作，差別在於有沒有被有意識地管理">個人專案到團隊服務：infra 在哪裡出現&lt;/a>&lt;/td>
 &lt;td>從 side project 部署到雲端的過程，看見 VPC、security group、IAM 這些元件其實早就在運作&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/infra/00-infra-mindset/one-machine-to-environments/" data-link-title="從單一環境到環境分離：infra 需求的浮現過程" data-link-desc="單一 EC2 &amp;#43; RDS 的結構在需要測試環境、多人協作時會撞到哪些操作極限，以及環境分離怎麼牽出身分、網路、變更流程等後續 infra 關注點">一台機器到三個環境：infra 解決的問題&lt;/a>&lt;/td>
 &lt;td>從一台 EC2 到需要 dev / staging / prod 三個環境的過程中，infra 的每一個關注點怎麼自然浮現&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/infra/00-infra-mindset/infra-responsibility-maturity/" data-link-title="infra 的責任邊界、成熟度階梯與 day 1 鐵律" data-link-desc="基礎設施承擔五個面向的責任，每一面都有獨立的失效模式；成熟度階梯用來對齊現況而非追求滿分，day 1 鐵律則劃出早期團隊該優先鋪的地基">責任邊界、成熟度階梯與 day 1 鐵律&lt;/a>&lt;/td>
 &lt;td>五個責任面向的失效模式、成熟度階梯的五個刻度、day 1 鐵律與早期團隊的務實節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/infra/00-infra-mindset/first-day-with-cloud-account/" data-link-title="拿到雲端帳號的第一天" data-link-desc="被指派 infra 工作、拿到 AWS 或 GCP 帳號、不確定該先做什麼時讀 — 第一小時安全底線、帳號現況判讀、後續學習路線分流">拿到雲端帳號的第一天&lt;/a>&lt;/td>
 &lt;td>被指派 infra 工作時的第一小時安全底線、帳號現況判讀、後續學習路線分流&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/infra/before-infra/" data-link-title="模組負一：還沒有 infra 的環境怎麼盡量做好" data-link-desc="手動點起家的環境怎麼守底線、降低未來納管成本、辨識何時該開始導入 IaC — 給還沒有能力上 IaC 的真實起點">模組負一：還沒有 infra 的手動環境&lt;/a>：階梯第 0 階的環境怎麼盡量做好&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一：最小可行 IaC&lt;/a>：地基資源跨上成熟度階梯第 2 階的最小路徑&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四：環境分離與模組化&lt;/a>：成熟度階梯第 3 階的切法&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/infra/07-infra-as-pr/" data-link-title="模組七：infra 走 PR 流程與自動化護欄" data-link-desc="infra 變更走 PR → plan → review diff → 合併 → apply，配 fmt / validate / tflint / checkov / tfsec 與 Atlantis 自動化，讓基礎設施可審查、可回溯、可交接">模組七：infra 走 PR 流程&lt;/a>：成熟度階梯第 4 階的治理護欄&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/infra/09-driving-adoption/" data-link-title="模組九：怎麼把 infra 推動起來" data-link-desc="技術正確不等於推得動 — 信任赤字、期望值對齊、知識共享，infra 落地的組織課題">模組九：怎麼把 infra 推動起來&lt;/a>：地基的價值怎麼用商業語言講給上層聽&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/install/" data-link-title="Linux 安裝與機器初始化" data-link-desc="在 VM 或新機器從零裝好 Linux、判讀安裝程式選項、驗證最小系統、或要從外部連入跑 bootstrap 時回來讀">Linux 安裝與機器初始化&lt;/a>：拿到雲端主機後從 OS 層連入、跑 bootstrap 的前置，跟 infra 的資源管理是上下游；主機連不到 / 起不來時的診斷見 &lt;a href="https://tarrragon.github.io/blog/linux/debug/machine-unreachable/" data-link-title="機器連不到或起不來" data-link-desc="遠端機器突然 SSH 連不上、虛擬機開不了機、或懷疑磁碟滿引發連鎖故障時，從主機側與網路層的權威狀態往下定位是哪一環斷了">機器連不到或起不來&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>基礎設施（infrastructure，簡稱 infra）是承載應用程式的那層資源與規則：運算、網路、身分、儲存、可觀測性，以及定義它們如何被建立、變更、回收的治理機制。它的責任是讓應用程式有一個可被信任、可被重建、可被審計的執行環境。本章建立的責任邊界、成熟度階梯與 day 1 鐵律，是後續所有 infra 模組共用的心智模型，其他章節會直接引用這裡定義的詞彙。</p>
<h2 id="infra-的責任邊界">infra 的責任邊界</h2>
<p>infra 承擔的是「應用程式之下、作業系統之上」那層共享資源的供應與治理。把責任拆成五個面向比較好對齊：每一面都有自己的失效模式，混在一起談會讓判斷失焦。</p>
<p>運算（compute）負責「程式跑在哪、用多少資源、怎麼擴縮」。它的衡量點是容量與彈性：流量尖峰時能不能長出更多實例、閒置時能不能縮回去省錢。一台手動開的 VM 也是運算資源，差別只在它是否被納入可重建的描述。</p>
<p>網路（network）負責「誰能連到誰、流量走哪條路」。它的責任是把可達性變成明確規則，而非預設全通。VPC 切分、子網路、security group 都屬於這層，邊界沒畫清楚時，一個被入侵的服務就能橫向打穿整個環境。</p>
<p>身分與憑證（identity）負責「誰能對哪些資源做什麼操作」。它承擔最小權限的落地：人、服務、CI pipeline 各拿剛好夠用的權限，憑證有明確的生命週期。這層失守的代價最高，因為它是其他所有資源的閘門。</p>
<p>儲存（storage）負責「資料放哪、能不能還原」。它的責任是持久性與可回復性：備份策略、版本保留、刪除保護。運算可以隨時重建，資料一旦遺失通常無法重來，所以這層的回退路徑要在出事前就驗證過。</p>
<p>可觀測性（observability）負責「系統現在發生什麼、出事後查得到嗎」。它把 log、metric、trace 變成可查詢的事實來源。這層常被當成事後再補的附加品，但它和被它觀測的服務應該同生命週期一起建立，補在後面的可觀測性往往缺了出事當下最關鍵的那段資料。</p>
<p>這五面的共同點是：它們都不是應用功能，使用者看不到，但任何一面崩了，上面的功能全部跟著崩。這正是地基隱形的根源。</p>
<h2 id="地基為什麼隱形">地基為什麼隱形</h2>
<p>infra 的特性是「運作正常時完全不被感知，失效時才一次現形」。地基鋪得好的環境，工程師每天部署、擴縮、改設定，卻幾乎不會意識到底下有一層在支撐，因為它安靜地做對了每件事。這種隱形讓 infra 在資源排序上長期吃虧：看得見的功能有人催，看不見的地基沒人提。</p>
<p>現形的時刻通常是環境爆炸的時刻。一個沒有人記得怎麼建的服務掛了，才發現它是某位早期工程師在 Console 手動點出來的，沒有任何描述檔；一次安全稽核要求列出所有對外開放的連接埠，才發現 security group 散落在三個帳號、沒人說得清哪條規則還有用；一台資料庫磁碟滿了要擴容，才發現它從來沒進過任何納管流程，動它等於拆未爆彈。</p>
<p>隱形債務的徵兆很直接：當團隊開始用「不敢動那台機器」「只有某某知道怎麼改」來描述某項資源，債就已經在累積。地基的價值無法在平順時被看見，只能在它缺席的代價裡被回推，所以它需要一條和功能不同的論證路徑——這條路徑怎麼用商業語言講給上層聽，是「模組九：怎麼把 infra 推動起來」的主題。</p>
<h2 id="day-1-鋪地基與事後補的成本差">day 1 鋪地基與事後補的成本差</h2>
<p>在資源剛開始長出來時就用程式碼描述它，和等環境長大後再回頭納管，兩者的成本差距是非線性的。早期鋪地基的成本接近固定：寫一份描述檔、建一個 state、設一條 pipeline，環境只有三五個資源時這些都很輕。事後補的成本則隨資源數量、相互依賴與「不確定能不能動」的恐懼一起放大。</p>
<p>事後納管的痛具體長這樣：一個手動建出來的資源要納入 IaC，得先把它當前的真實狀態完整反推成程式碼（import），這個過程要逐欄比對 Console 上的設定，漏一個欄位下次 apply 就可能把線上設定改掉。資源彼此有依賴時，納管順序也得排——先納管的資源引用了還沒納管的資源，描述就接不起來。當這些手動資源還是線上服務正在用的，整個納管過程等於在開著的引擎上換零件。</p>
<p>務實的判準不是「day 1 就把所有東西寫成完美的 IaC」，而是「day 1 就讓新長出來的資源預設走可重建的路徑」。多數早期環境划得來的選擇，是讓地基類資源（網路、身分、state 本身）從一開始就在程式碼裡，而把還在高速試錯的應用層資源留一點手動彈性，等形狀穩定再納管。差別在於：前者的回頭成本固定，後者隨時間複利。「模組一：最小可行 IaC」會示範這條最小路徑怎麼落地。</p>
<h2 id="成熟度階梯">成熟度階梯</h2>
<p>infra 的成熟度可以排成一條從「全手動」到「全程式碼治理」的階梯，每一階用「資源怎麼被建立與變更」來定義。這條階梯是全系列共用的座標：後續模組描述某個能力時，會說它對應到哪一階，所以這裡先把刻度釘清楚。</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>名稱</th>
          <th>資源怎麼被建立</th>
          <th>真實狀態的來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>Console 手動</td>
          <td>在網頁介面點選建立</td>
          <td>只存在於雲端，無描述</td>
      </tr>
      <tr>
          <td>1</td>
          <td>腳本化</td>
          <td>用 CLI 或腳本建立</td>
          <td>腳本，但無狀態追蹤</td>
      </tr>
      <tr>
          <td>2</td>
          <td>宣告式 IaC</td>
          <td>寫描述檔、由工具 apply</td>
          <td>state 檔記錄已建資源</td>
      </tr>
      <tr>
          <td>3</td>
          <td>環境分離</td>
          <td>同一份模組套用多環境</td>
          <td>各環境獨立 state</td>
      </tr>
      <tr>
          <td>4</td>
          <td>PR 流程治理</td>
          <td>變更走 PR、CI 自動 plan</td>
          <td>state + 版控歷史 + 審查紀錄</td>
      </tr>
  </tbody>
</table>
<p>第 0 階「Console 手動」是所有環境的起點，也是必須最快離開的一階。它的特徵是真實狀態只存在雲端，沒有任何離線描述，所以無法 review、無法重建、無法回答「這個環境長什麼樣」。它不是錯誤的起點，是還沒鋪地基的起點。</p>
<p>第 1 階「腳本化」把建立動作寫成 CLI 或 shell 腳本，比手動可重複，但腳本只描述「怎麼建」，不追蹤「現在有什麼」。重跑同一支腳本可能重複建立或報錯，因為它不知道資源已經存在。這一階的常見陷阱是誤以為「有腳本就等於有 IaC」，差的是狀態這塊地基。</p>
<p>第 2 階「宣告式 IaC」是地基真正成形的一階：用 Terraform / OpenTofu 這類工具寫下「環境應該長什麼樣」，工具負責比對現況與描述、算出差異再套用。state 檔在這裡誕生，成為「目前納管了哪些資源」的事實來源。這一階的判讀訊號是：能不能從程式碼把整個環境在另一個帳號重建出來。</p>
<p>第 3 階「環境分離」把同一份描述模組化，套用到 dev / staging / production 等多個環境，各自獨立 state。它解決的問題是「在 staging 驗證過的變更，能用同一套描述安全地推到 production」。「模組四：環境分離與模組化」專講這一階的切法。</p>
<p>第 4 階「PR 流程治理」把 infra 變更接上和應用程式碼相同的協作流程：變更走 pull request，CI 自動跑 plan 把預期差異貼上來，人審查後才 apply。到這一階，infra 的每次變更都有提案、審查、歷史與回退點。「模組七：infra 走 PR 流程」會完整展開這套護欄。</p>
<p>這條階梯是一把對齊現況的尺，用來判斷某項資源該停在哪一階，不是越高越好的單向命令。停在哪一階的依據，是務實節奏。</p>
<h2 id="早期新創的務實節奏">早期新創的務實節奏</h2>
<p>早期團隊的合理目標是「地基類資源先上到階梯第 2 階，應用層資源容許暫時留在低階」，而不是一步衝到第 4 階。資源有限、需求還在劇烈變動的階段，把全部資源都套上完整治理流程，划得來的機率不高——治理的固定成本會壓到本來就稀缺的開發頻寬。</p>
<p>判斷節奏的依據是「這項資源的形狀穩不穩、動它的代價高不高」。網路拓撲、身分權限、state 後端這類地基，一旦長歪回頭改的代價極高，值得 day 1 就進 IaC，這是少數接近「該照做」的硬判準，因為它牽涉安全邊界。反過來，一個還在每週改三次規格的功能用的運算資源，過早凍進嚴格流程反而拖慢試錯，這時容許它手動、但設一條 tripwire：當它開始被線上流量依賴、或開始有第二個人需要改它時，就是把它納管的時機。</p>
<p>過度設計和放任手動是這個階段的兩個反向誤判。過度設計的訊號是：環境只有五個資源，卻已經有多層抽象模組和還用不到的多環境結構，維護抽象的時間比省下的時間多。放任手動的訊號是：每次有人問「這個怎麼建的」都只能去翻某個人的記憶，地基債務在無聲累積。務實節奏就是在這兩者之間，讓地基先穩、讓應用層保留試錯彈性，再隨著形狀固定逐項往階梯上推。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/infra/00-infra-mindset/personal-project-to-infra/" data-link-title="雲端部署裡已經存在的 infra 元件" data-link-desc="VPC、security group、IAM、儲存 — 這些元件在任何雲端部署裡都已經在運作，差別在於有沒有被有意識地管理">個人專案到團隊服務：infra 在哪裡出現</a></td>
          <td>從 side project 部署到雲端的過程，看見 VPC、security group、IAM 這些元件其實早就在運作</td>
      </tr>
      <tr>
          <td><a href="/blog/infra/00-infra-mindset/one-machine-to-environments/" data-link-title="從單一環境到環境分離：infra 需求的浮現過程" data-link-desc="單一 EC2 &#43; RDS 的結構在需要測試環境、多人協作時會撞到哪些操作極限，以及環境分離怎麼牽出身分、網路、變更流程等後續 infra 關注點">一台機器到三個環境：infra 解決的問題</a></td>
          <td>從一台 EC2 到需要 dev / staging / prod 三個環境的過程中，infra 的每一個關注點怎麼自然浮現</td>
      </tr>
      <tr>
          <td><a href="/blog/infra/00-infra-mindset/infra-responsibility-maturity/" data-link-title="infra 的責任邊界、成熟度階梯與 day 1 鐵律" data-link-desc="基礎設施承擔五個面向的責任，每一面都有獨立的失效模式；成熟度階梯用來對齊現況而非追求滿分，day 1 鐵律則劃出早期團隊該優先鋪的地基">責任邊界、成熟度階梯與 day 1 鐵律</a></td>
          <td>五個責任面向的失效模式、成熟度階梯的五個刻度、day 1 鐵律與早期團隊的務實節奏</td>
      </tr>
      <tr>
          <td><a href="/blog/infra/00-infra-mindset/first-day-with-cloud-account/" data-link-title="拿到雲端帳號的第一天" data-link-desc="被指派 infra 工作、拿到 AWS 或 GCP 帳號、不確定該先做什麼時讀 — 第一小時安全底線、帳號現況判讀、後續學習路線分流">拿到雲端帳號的第一天</a></td>
          <td>被指派 infra 工作時的第一小時安全底線、帳號現況判讀、後續學習路線分流</td>
      </tr>
  </tbody>
</table>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/before-infra/" data-link-title="模組負一：還沒有 infra 的環境怎麼盡量做好" data-link-desc="手動點起家的環境怎麼守底線、降低未來納管成本、辨識何時該開始導入 IaC — 給還沒有能力上 IaC 的真實起點">模組負一：還沒有 infra 的手動環境</a>：階梯第 0 階的環境怎麼盡量做好</li>
<li>→ <a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一：最小可行 IaC</a>：地基資源跨上成熟度階梯第 2 階的最小路徑</li>
<li>→ <a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四：環境分離與模組化</a>：成熟度階梯第 3 階的切法</li>
<li>→ <a href="/blog/infra/07-infra-as-pr/" data-link-title="模組七：infra 走 PR 流程與自動化護欄" data-link-desc="infra 變更走 PR → plan → review diff → 合併 → apply，配 fmt / validate / tflint / checkov / tfsec 與 Atlantis 自動化，讓基礎設施可審查、可回溯、可交接">模組七：infra 走 PR 流程</a>：成熟度階梯第 4 階的治理護欄</li>
<li>→ <a href="/blog/infra/09-driving-adoption/" data-link-title="模組九：怎麼把 infra 推動起來" data-link-desc="技術正確不等於推得動 — 信任赤字、期望值對齊、知識共享，infra 落地的組織課題">模組九：怎麼把 infra 推動起來</a>：地基的價值怎麼用商業語言講給上層聽</li>
<li>→ <a href="/blog/linux/install/" data-link-title="Linux 安裝與機器初始化" data-link-desc="在 VM 或新機器從零裝好 Linux、判讀安裝程式選項、驗證最小系統、或要從外部連入跑 bootstrap 時回來讀">Linux 安裝與機器初始化</a>：拿到雲端主機後從 OS 層連入、跑 bootstrap 的前置，跟 infra 的資源管理是上下游；主機連不到 / 起不來時的診斷見 <a href="/blog/linux/debug/machine-unreachable/" data-link-title="機器連不到或起不來" data-link-desc="遠端機器突然 SSH 連不上、虛擬機開不了機、或懷疑磁碟滿引發連鎖故障時，從主機側與網路層的權威狀態往下定位是哪一環斷了">機器連不到或起不來</a></li>
</ul>
]]></content:encoded></item></channel></rss>