<?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>Stateful on Tarragon</title><link>https://tarrragon.github.io/blog/tags/stateful/</link><description>Recent content in Stateful 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/stateful/index.xml" rel="self" type="application/rss+xml"/><item><title>Stateful 資源保護與跨服務依賴表達</title><link>https://tarrragon.github.io/blog/infra/05-core-services/stateful-protection-dependency/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/05-core-services/stateful-protection-dependency/</guid><description>&lt;p>核心服務寫進 IaC 之後，stateful 資源需要一套與 stateless 截然不同的保護與操作規範。資料庫、裝了正式資料的 S3 bucket、持久化 volume 這類資源的共同特性是：重建代價極高甚至不可逆。運算節點掛了重開一台，資料刪了就是刪了。這個差別會傳導到 IaC 的描述方式、變更的審查強度、以及 drift 的處理策略。&lt;/p>
&lt;p>本篇同時處理服務之間依賴的表達方式 — output 與 data source — 因為依賴表達直接影響 stateful 資源的爆炸半徑：同一份 state 裡的資料庫跟運算綁在一起 apply，還是拆成獨立 state 各自演進，決定了一次 apply 失敗會波及多少資源。&lt;/p>
&lt;h2 id="stateful-資源的保護策略">stateful 資源的保護策略&lt;/h2>
&lt;p>stateful 資源的 IaC 描述要把「保護狀態」當成第一類需求，而非事後補上的選項。保護的三個面向 — 可用性、可還原性、防誤刪 — 各自對應不同的機制，混在一起談會讓判斷失焦。&lt;/p>
&lt;h3 id="multi-az-的職責邊界">multi-AZ 的職責邊界&lt;/h3>
&lt;p>multi-AZ 用一個布林屬性開啟，背後是 RDS 在另一個可用區維護同步副本。它承擔的是可用性：主庫所在的可用區故障時，RDS 自動 failover 到 standby，服務在秒級到一兩分鐘的窗口後恢復。&lt;/p>
&lt;p>multi-AZ 的邊界要明確界定，因為把它當成超出職責的工具會在事故裡踩空：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>standby 是熱備不可讀&lt;/strong>。multi-AZ 的 standby 不接受任何查詢流量，所以它不提供讀取擴展。要分攤讀流量得另開 read replica，這是另一個資源、另一個端點、另一套複寫延遲要管。&lt;/li>
&lt;li>&lt;strong>failover 有切換窗口&lt;/strong>。切換期間應用的資料庫連線會中斷、需要重連。應用層如果沒有處理連線中斷的重試邏輯，failover 就會變成一段可見的服務中斷，而非透明切換。&lt;/li>
&lt;li>&lt;strong>它不防邏輯損壞&lt;/strong>。誤刪一張 table、一筆錯誤的批次 UPDATE、一段有 bug 的 migration script — 這些操作會同步複製到 standby。multi-AZ 防的是硬體與可用區故障，邏輯損壞的防線是備份與時間點還原（PITR）。&lt;/li>
&lt;/ul>
&lt;p>這三條邊界說明 multi-AZ 和 backup 的職責正交：前者解可用性，後者解可還原性。兩者要分別配置、分別驗證。成本參考：multi-AZ RDS 的費用約為 single-AZ 的兩倍（standby instance 按相同規格計費）。這筆費用對應的能力是可用區故障時的分鐘級自動 failover——判斷值不值得時，用主庫所承載的服務停機每小時的商業代價來衡量。&lt;/p>
&lt;h3 id="備份保留與時間點還原">備份保留與時間點還原&lt;/h3>
&lt;p>backup 用保留天數與備份視窗描述。RDS 依此每日自動快照並保留交易日誌，以支援還原到任意時間點（PITR）。自動備份的保留上限是 35 天，更長的留存要靠手動快照或匯出到 S3 自行管理。&lt;/p>
&lt;p>&lt;code>backup_retention_period&lt;/code> 取多少天，以 RPO（Recovery Point Objective）與合規要求反推。RPO 問的是「出事時最多能接受遺失多久的資料」— PITR 能還原到最近 5 分鐘內的時間點，但前提是自動備份有開、交易日誌有保留。保留天數決定的是「能回溯多遠」：14 天是 AWS RDS 自動備份 35 天上限的保守折衷，足以涵蓋多數營運場景下「發現問題到決定還原」的時間差；受監理的服務往 30 天推，以滿足稽核追溯窗口。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_db_instance&amp;#34; &amp;#34;primary&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n"> multi_az&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="n"> backup_retention_period&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">14&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n"> backup_window&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;03:00-04:00&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="n"> deletion_protection&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n"> skip_final_snapshot&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="n"> final_snapshot_identifier&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;app-prod-final-${formatdate(&amp;#34;YYYYMMDD&amp;#34;, timestamp())}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>備份視窗選在流量低谷（如 UTC 凌晨），避免快照 IO 跟尖峰流量競爭。手動快照用獨立資源描述，常見用途是重大變更前的保險點 — 大版本升級、schema migration、或任何會改變資料結構的操作。&lt;/p>
&lt;h3 id="刪除保護與-final-snapshot">刪除保護與 final snapshot&lt;/h3>
&lt;p>&lt;code>deletion_protection = true&lt;/code> 讓 &lt;code>terraform destroy&lt;/code> 無法直接刪除這個 instance — 要先用另一次 apply 把保護關掉，這一步本身就會出現在 plan 裡、被 review 攔住。&lt;code>skip_final_snapshot = false&lt;/code> 確保即使確實要刪，也會先拍一份最終快照。兩者搭配是正式資料庫的硬性下限。&lt;/p>
&lt;p>該在 review 攔下的訊號是：正式環境的 stateful 資源若 &lt;code>backup_retention_period&lt;/code> 為 0 或 &lt;code>deletion_protection&lt;/code> 為 false，代表狀態保護沒有寫進程式碼。把這些屬性視為正式資料庫的預設值，而非可調的偏好。&lt;/p>
&lt;p>S3 bucket 的保護同理但機制不同。versioning 讓覆寫或刪除的物件可以回到先前版本；MFA delete 要求刪除前提供第二因素驗證；lifecycle rule 控制舊版本的保留時間 — 這三者分別對應「可還原」「防誤刪」「控成本」三個職責，見&lt;a href="https://tarrragon.github.io/blog/infra/05-core-services/storage-s3/" data-link-title="儲存上 IaC — S3 bucket 的安全與生命週期" data-link-desc="S3 bucket 的加密、版本控制、公開存取封鎖、生命週期規則、bucket policy 與事件通知怎麼寫進 IaC，讓儲存的安全與成本防線可審查可追蹤">儲存（S3）&lt;/a>。&lt;/p>
&lt;h3 id="跨-region-災難復原的邊界">跨 region 災難復原的邊界&lt;/h3>
&lt;p>multi-AZ 解的是可用區級故障 — 單一資料中心出問題時，同 region 的另一個可用區接手。跨 region 的災難復原（cross-region read replica、S3 cross-region replication、Route 53 failover routing）屬於更高級的可用性投資，解的是整個 region 不可用的極端情境。它的成本與複雜度顯著上升：跨 region 複寫有延遲、failover routing 需要健康檢查與 DNS TTL 配合、兩個 region 的 infra 要各自維護。多數服務在單 region 的 multi-AZ + 備份做完之後再評估是否需要跨 region，依據是業務的 RTO（Recovery Time Objective）對 region 級故障的容忍度。&lt;/p></description><content:encoded><![CDATA[<p>核心服務寫進 IaC 之後，stateful 資源需要一套與 stateless 截然不同的保護與操作規範。資料庫、裝了正式資料的 S3 bucket、持久化 volume 這類資源的共同特性是：重建代價極高甚至不可逆。運算節點掛了重開一台，資料刪了就是刪了。這個差別會傳導到 IaC 的描述方式、變更的審查強度、以及 drift 的處理策略。</p>
<p>本篇同時處理服務之間依賴的表達方式 — output 與 data source — 因為依賴表達直接影響 stateful 資源的爆炸半徑：同一份 state 裡的資料庫跟運算綁在一起 apply，還是拆成獨立 state 各自演進，決定了一次 apply 失敗會波及多少資源。</p>
<h2 id="stateful-資源的保護策略">stateful 資源的保護策略</h2>
<p>stateful 資源的 IaC 描述要把「保護狀態」當成第一類需求，而非事後補上的選項。保護的三個面向 — 可用性、可還原性、防誤刪 — 各自對應不同的機制，混在一起談會讓判斷失焦。</p>
<h3 id="multi-az-的職責邊界">multi-AZ 的職責邊界</h3>
<p>multi-AZ 用一個布林屬性開啟，背後是 RDS 在另一個可用區維護同步副本。它承擔的是可用性：主庫所在的可用區故障時，RDS 自動 failover 到 standby，服務在秒級到一兩分鐘的窗口後恢復。</p>
<p>multi-AZ 的邊界要明確界定，因為把它當成超出職責的工具會在事故裡踩空：</p>
<ul>
<li><strong>standby 是熱備不可讀</strong>。multi-AZ 的 standby 不接受任何查詢流量，所以它不提供讀取擴展。要分攤讀流量得另開 read replica，這是另一個資源、另一個端點、另一套複寫延遲要管。</li>
<li><strong>failover 有切換窗口</strong>。切換期間應用的資料庫連線會中斷、需要重連。應用層如果沒有處理連線中斷的重試邏輯，failover 就會變成一段可見的服務中斷，而非透明切換。</li>
<li><strong>它不防邏輯損壞</strong>。誤刪一張 table、一筆錯誤的批次 UPDATE、一段有 bug 的 migration script — 這些操作會同步複製到 standby。multi-AZ 防的是硬體與可用區故障，邏輯損壞的防線是備份與時間點還原（PITR）。</li>
</ul>
<p>這三條邊界說明 multi-AZ 和 backup 的職責正交：前者解可用性，後者解可還原性。兩者要分別配置、分別驗證。成本參考：multi-AZ RDS 的費用約為 single-AZ 的兩倍（standby instance 按相同規格計費）。這筆費用對應的能力是可用區故障時的分鐘級自動 failover——判斷值不值得時，用主庫所承載的服務停機每小時的商業代價來衡量。</p>
<h3 id="備份保留與時間點還原">備份保留與時間點還原</h3>
<p>backup 用保留天數與備份視窗描述。RDS 依此每日自動快照並保留交易日誌，以支援還原到任意時間點（PITR）。自動備份的保留上限是 35 天，更長的留存要靠手動快照或匯出到 S3 自行管理。</p>
<p><code>backup_retention_period</code> 取多少天，以 RPO（Recovery Point Objective）與合規要求反推。RPO 問的是「出事時最多能接受遺失多久的資料」— PITR 能還原到最近 5 分鐘內的時間點，但前提是自動備份有開、交易日誌有保留。保留天數決定的是「能回溯多遠」：14 天是 AWS RDS 自動備份 35 天上限的保守折衷，足以涵蓋多數營運場景下「發現問題到決定還原」的時間差；受監理的服務往 30 天推，以滿足稽核追溯窗口。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_db_instance&#34; &#34;primary&#34;</span> {
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">  multi_az</span>                  <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">  backup_retention_period</span>   <span class="o">=</span> <span class="m">14</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">  backup_window</span>             <span class="o">=</span> <span class="s2">&#34;03:00-04:00&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">  deletion_protection</span>       <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">  skip_final_snapshot</span>       <span class="o">=</span> <span class="kt">false</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">  final_snapshot_identifier</span> <span class="o">=</span> <span class="s2">&#34;app-prod-final-${formatdate(&#34;YYYYMMDD&#34;, timestamp())}&#34;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">}</span></span></code></pre></div><p>備份視窗選在流量低谷（如 UTC 凌晨），避免快照 IO 跟尖峰流量競爭。手動快照用獨立資源描述，常見用途是重大變更前的保險點 — 大版本升級、schema migration、或任何會改變資料結構的操作。</p>
<h3 id="刪除保護與-final-snapshot">刪除保護與 final snapshot</h3>
<p><code>deletion_protection = true</code> 讓 <code>terraform destroy</code> 無法直接刪除這個 instance — 要先用另一次 apply 把保護關掉，這一步本身就會出現在 plan 裡、被 review 攔住。<code>skip_final_snapshot = false</code> 確保即使確實要刪，也會先拍一份最終快照。兩者搭配是正式資料庫的硬性下限。</p>
<p>該在 review 攔下的訊號是：正式環境的 stateful 資源若 <code>backup_retention_period</code> 為 0 或 <code>deletion_protection</code> 為 false，代表狀態保護沒有寫進程式碼。把這些屬性視為正式資料庫的預設值，而非可調的偏好。</p>
<p>S3 bucket 的保護同理但機制不同。versioning 讓覆寫或刪除的物件可以回到先前版本；MFA delete 要求刪除前提供第二因素驗證；lifecycle rule 控制舊版本的保留時間 — 這三者分別對應「可還原」「防誤刪」「控成本」三個職責，見<a href="/blog/infra/05-core-services/storage-s3/" data-link-title="儲存上 IaC — S3 bucket 的安全與生命週期" data-link-desc="S3 bucket 的加密、版本控制、公開存取封鎖、生命週期規則、bucket policy 與事件通知怎麼寫進 IaC，讓儲存的安全與成本防線可審查可追蹤">儲存（S3）</a>。</p>
<h3 id="跨-region-災難復原的邊界">跨 region 災難復原的邊界</h3>
<p>multi-AZ 解的是可用區級故障 — 單一資料中心出問題時，同 region 的另一個可用區接手。跨 region 的災難復原（cross-region read replica、S3 cross-region replication、Route 53 failover routing）屬於更高級的可用性投資，解的是整個 region 不可用的極端情境。它的成本與複雜度顯著上升：跨 region 複寫有延遲、failover routing 需要健康檢查與 DNS TTL 配合、兩個 region 的 infra 要各自維護。多數服務在單 region 的 multi-AZ + 備份做完之後再評估是否需要跨 region，依據是業務的 RTO（Recovery Time Objective）對 region 級故障的容忍度。</p>
<p>跨 region 的 infra 投資在 B2B SaaS 的合約義務下更容易成立。<a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">Genesys 的客服平台跨 15 個 region 用 DynamoDB 達成 99.999% 可用性</a>——年停機只有 5 分鐘。對 B2B SaaS 來說，客戶服務中斷等於客戶的終端使用者打不通電話，可用性是合約義務而非行銷敘述。infra 層的判斷依據是：multi-AZ 不夠用（業務需要跨 region failover）的情況通常由合約 SLA 驅動，而非技術判斷驅動。</p>
<h2 id="stateful-與-stateless-的操作差異">stateful 與 stateless 的操作差異</h2>
<p>stateful 與 stateless 資源的根本差別在重建代價。這個差別傳導到三個操作後果，每一個都影響日常的 PR review 與 apply 流程。</p>
<h3 id="刪除保護的必要性">刪除保護的必要性</h3>
<p>stateless 資源（ECS service、ALB、無狀態運算）重建只是換一組新實例，幾分鐘內恢復、沒有資料損失，所以它們可以被頻繁地 destroy 與 recreate — 這是 IaC 最擅長的對象。stateful 資源重建意味著資料遺失或漫長的還原，代價可能是數小時的停機與不可逆的損失。開啟 deletion protection 讓「不小心 destroy」需要先顯式關閉保護這一步，多一道人為確認。</p>
<h3 id="drift-容忍度">drift 容忍度</h3>
<p>stateless 資源的 drift 可以靠重建抹平 — apply 一次就回到程式碼的狀態，副作用只是新實例的短暫滾動更新。stateful 資源的 drift 要謹慎處理，因為 IaC 的「修正回程式碼狀態」動作可能觸發重啟甚至重建。</p>
<p>一個常見的情境：某人手動改了 RDS 的 parameter group，Terraform plan 顯示要把它改回程式碼的版本。這個改回動作是 <code>update in-place</code>（改設定、不重建）還是 <code>replace</code>（先刪後建），取決於哪個參數被改了 — 某些 parameter 的修改需要重啟，而某些需要整個 instance 重建。判讀方式是先跑 plan、看 drift 修正的結果，<code>update in-place</code> 通常安全（可能觸發重啟），<code>replace</code> 對資料庫意味著先刪後建，在 prod 上需要額外的確認。</p>
<h3 id="變更審查強度">變更審查強度</h3>
<p>改動 stateful 資源的 plan 輸出要逐行看，特別警惕任何顯示為 <code>replace</code>（<code>-/+</code>）或標記 <code>forces replacement</code> 的項目。某些欄位的改動看似無害但會觸發 replace：</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>預期行為</th>
          <th>實際行為</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>RDS <code>identifier</code> 改名</td>
          <td>改個名字而已</td>
          <td>forces replacement</td>
      </tr>
      <tr>
          <td>RDS <code>engine_version</code> 大版本</td>
          <td>升級引擎版本</td>
          <td>可能 replace 或 in-place</td>
      </tr>
      <tr>
          <td>RDS <code>storage_type</code> 變更</td>
          <td>換儲存類型</td>
          <td>部分組合 forces replacement</td>
      </tr>
      <tr>
          <td>S3 bucket <code>bucket</code> 改名</td>
          <td>改個名字而已</td>
          <td>forces replacement</td>
      </tr>
  </tbody>
</table>
<p>Review 時看到 stateful 資源出現 <code>forces replacement</code>，在 prod 路徑上幾乎都該先暫停、確認回退路徑（手動快照是否已拍）再決定是否繼續。常見做法是把這個差別寫進流程：stateful 資源的變更走更嚴格的 PR review 與分階段套用（先在 dev apply 驗證、確認是 in-place 後再推 prod），自動化護欄在<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>
<h2 id="服務之間的依賴怎麼表達">服務之間的依賴怎麼表達</h2>
<p>服務間依賴用 output 與 data source 表達，讓引用關係成為程式碼裡可追蹤的邊，而非靠人記憶的隱性約定。引用方式的選擇直接影響 state 的大小與爆炸半徑。</p>
<h3 id="同-state-內的引用">同 state 內的引用</h3>
<p>同一個 state 內，直接引用資源屬性即可建立依賴。運算資源引用資料庫的端點，IaC 自動推導出「資料庫先於運算」的邊，也讓端點變更時上層自動取得新值：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_ecs_task_definition&#34; &#34;api&#34;</span> {
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">  container_definitions</span> <span class="o">=</span> <span class="k">jsonencode</span><span class="p">([</span>{
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">    environment</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">      { name</span> <span class="o">=</span><span class="n"> &#34;DB_HOST&#34;, value</span> <span class="o">=</span> <span class="k">aws_db_instance</span><span class="p">.</span><span class="k">primary</span><span class="p">.</span><span class="k">endpoint</span> }
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  }<span class="p">])</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">}</span></span></code></pre></div><p>同 state 引用的好處是依賴圖最完整 — apply 一次就把所有引用解析到正確的值。代價是 state 越大、單次 apply 的爆炸半徑越大。一份包含網路、資料庫、運算、LB 的 state，一次 apply 失敗可能讓所有資源處於半完成狀態。</p>
<h3 id="跨-state-的-data-source">跨 state 的 data source</h3>
<p>跨 state（例如網路地基與核心服務分屬不同 Terraform state，呼應<a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四：環境分離與模組化</a>的拆分）時，下游用 data source 唯讀地讀取上游已建立的資源：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">data</span> <span class="s2">&#34;aws_vpc&#34; &#34;main&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  tags</span> <span class="o">=</span><span class="n"> { Name</span> <span class="o">=</span> <span class="s2">&#34;app-${var.env}&#34;</span> }
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">}
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">data</span> <span class="s2">&#34;aws_subnets&#34; &#34;private&#34;</span> {
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="k">filter</span> {
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">    name</span>   <span class="o">=</span> <span class="s2">&#34;vpc-id&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">    values</span> <span class="o">=</span> <span class="p">[</span><span class="k">data</span><span class="p">.</span><span class="k">aws_vpc</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  }
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">  tags</span> <span class="o">=</span><span class="n"> { tier</span> <span class="o">=</span> <span class="s2">&#34;private&#34;</span> }
</span></span><span class="line"><span class="ln">11</span><span class="cl">}</span></span></code></pre></div><p>下游查詢上游的 VPC 與 subnet，取得 ID 來放置自己的資源，而不複製貼上硬編碼的值。</p>
<h3 id="同-state-vs-跨-state-的取捨">同 state vs 跨 state 的取捨</h3>
<p>兩種方式的取捨在耦合與隔離之間：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>同 state 引用</th>
          <th>跨 state data source</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>依賴圖</td>
          <td>完整、自動推導</td>
          <td>跨 state 邊界，需約定上游先 apply</td>
      </tr>
      <tr>
          <td>爆炸半徑</td>
          <td>state 越大、單次 apply 越大</td>
          <td>各 state 獨立、爆炸半徑小</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>少量緊密耦合的資源</td>
          <td>地基層與服務層分離</td>
      </tr>
      <tr>
          <td>drift 風險</td>
          <td>低（引用自動追蹤）</td>
          <td>中（上游重建後 data source 可能查不到）</td>
      </tr>
  </tbody>
</table>
<p>用 grep 搜一遍核心服務的 HCL：如果出現大量寫死的 subnet ID 或 VPC ID，代表該用 data source 而沒用。這些硬編碼是日後上游重建時 drift 與 broken reference 的來源。把它們換成 data source，依賴關係才會在程式碼裡顯性化、可被工具與 review 看見。</p>
<p>data source 查詢的可靠性取決於查詢條件的穩定度。用 <code>tags</code> 查比用 <code>Name</code> 查更穩 — tag 是自己定義的、可控的值，而某些資源的 Name 可能在重建時改變。用 <code>terraform_remote_state</code> data source 直接讀上游的 state output 是最精確的方式，但它把兩份 state 的 backend 設定耦合在一起，上游搬 state 時下游也要跟著改。在團隊規模小、state 拆分不多的階段，<code>terraform_remote_state</code> 的耦合代價通常可接受；團隊變大後，用 tag-based data source 或 SSM Parameter Store 當中間層，能讓上下游各自獨立演進。</p>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<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>：核心服務落在哪些 subnet、security group 怎麼引用</li>
<li>→ <a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四：環境分離與模組化</a>：跨 state 的拆分策略</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>：stateful 變更的自動化護欄</li>
</ul>
]]></content:encoded></item></channel></rss>