<?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>Rds on Tarragon</title><link>https://tarrragon.github.io/blog/tags/rds/</link><description>Recent content in Rds 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/rds/index.xml" rel="self" type="application/rss+xml"/><item><title>部署順序與資料庫上 IaC</title><link>https://tarrragon.github.io/blog/infra/05-core-services/deployment-order-database/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/05-core-services/deployment-order-database/</guid><description>&lt;p>地基就緒後，依「地基 → 上層」的順序把實際承載業務的服務寫進 IaC。&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">身分（IAM）&lt;/a>、&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 設計">網路（VPC / subnet）&lt;/a>與&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>構成底層平面，這一層在它們之上描述資料庫、運算、儲存與入口 — 業務流量真正落地的地方。順序與依賴的表達方式決定了這層能不能被乾淨地重建、拆除與演進。共通原則是：描述服務的「身分與接線」，而非把每個執行期參數都塞進程式碼。&lt;/p>
&lt;p>本篇先確立依賴圖怎麼驅動部署順序，再展開核心服務裡最需要謹慎描述的一類 — 資料庫。資料庫持有無法重建的狀態，它的 IaC 描述比其他 stateless 資源多出保護策略、連線管理與讀寫分流三個維度。&lt;/p>
&lt;h2 id="核心服務的部署順序">核心服務的部署順序&lt;/h2>
&lt;p>核心服務的部署順序由依賴方向決定：被依賴的先建，依賴別人的後建。網路與身分是幾乎所有上層服務的共同前置 — 資料庫要放進私有 subnet、運算要套用 IAM role 才能讀 S3、load balancer 要掛在公開 subnet 並引用 security group。這些底層平面若還沒成形，上層資源會在 apply 時因為找不到 subnet ID 或 role ARN 而失敗，或更糟，建在預設 VPC 裡繞過了所有隔離設計。&lt;/p>
&lt;p>把順序交給 IaC 工具的依賴圖自動推導，比人工排序可靠。當運算資源的定義引用了 subnet 與 security group 的資源屬性，Terraform 會解析出「subnet 先於運算」的邊，apply 時自動排程。人工維護一份「先做 A 再做 B」的清單會隨資源增加而失準，依賴圖則隨程式碼本身演進。&lt;/p>
&lt;h3 id="四層依賴結構">四層依賴結構&lt;/h3>
&lt;p>依賴圖的典型展開順序呈現四層結構：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層次&lt;/th>
 &lt;th>資源&lt;/th>
 &lt;th>依賴來源&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1&lt;/td>
 &lt;td>VPC、subnet、security group、IAM role&lt;/td>
 &lt;td>無（地基層，由模組二到四建立）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2&lt;/td>
 &lt;td>RDS、ElastiCache、S3 bucket&lt;/td>
 &lt;td>引用 subnet group、security group&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3&lt;/td>
 &lt;td>ECS service / EKS workload、RDS Proxy&lt;/td>
 &lt;td>引用 subnet、IAM role、DB 端點&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4&lt;/td>
 &lt;td>ALB、listener、target group、ACM 憑證&lt;/td>
 &lt;td>引用 public subnet、security group、ECS&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這四層不需要手動編排。只要程式碼裡的引用關係正確，Terraform 就會自動按這個順序 apply。當 plan 輸出的順序看起來不合直覺 — 例如 ALB 先於 ECS — 通常代表某個引用斷了、兩者之間沒有依賴邊。&lt;/p>
&lt;h3 id="順序失控的徵兆">順序失控的徵兆&lt;/h3>
&lt;p>順序失控的早期徵兆是：某個上層資源的定義裡寫了一串 hardcode 的 subnet ID 或 VPC ID。&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="c1"># 硬編碼 ID — 依賴圖斷裂，底層重建時上層不會跟上
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_db_subnet_group&amp;#34; &amp;#34;private&amp;#34;&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"> subnet_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;subnet-0abc123&amp;#34;, &amp;#34;subnet-0def456&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這段 code 跟底層的 subnet 資源沒有引用關係。底層一旦重建、ID 改變，上層不會自動跟上，state 與雲端現實之間的不一致（即 drift）就此產生。修法是把硬編碼的 ID 換成對底層資源屬性的引用：&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="c1"># 引用資源屬性 — 依賴圖自動推導，底層重建時上層自動取得新 ID
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_db_subnet_group&amp;#34; &amp;#34;private&amp;#34;&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"> subnet_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="k">for&lt;/span> &lt;span class="k">s&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="k">aws_subnet&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">private&lt;/span> &lt;span class="err">:&lt;/span> &lt;span class="k">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">id&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>跨 state 的情境（網路地基與核心服務分屬不同 state）則用 data source 取代直接引用 — 這個取捨在&lt;a href="https://tarrragon.github.io/blog/infra/05-core-services/stateful-protection-dependency/" data-link-title="Stateful 資源保護與跨服務依賴表達" data-link-desc="stateful 資源的保護策略（multi-AZ、備份、刪除保護）、stateful 與 stateless 的操作差異，以及用 output 與 data source 表達服務間依賴">服務依賴與跨 state 引用&lt;/a>展開。&lt;/p></description><content:encoded><![CDATA[<p>地基就緒後，依「地基 → 上層」的順序把實際承載業務的服務寫進 IaC。<a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">身分（IAM）</a>、<a href="/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">網路（VPC / subnet）</a>與<a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">環境分離</a>構成底層平面，這一層在它們之上描述資料庫、運算、儲存與入口 — 業務流量真正落地的地方。順序與依賴的表達方式決定了這層能不能被乾淨地重建、拆除與演進。共通原則是：描述服務的「身分與接線」，而非把每個執行期參數都塞進程式碼。</p>
<p>本篇先確立依賴圖怎麼驅動部署順序，再展開核心服務裡最需要謹慎描述的一類 — 資料庫。資料庫持有無法重建的狀態，它的 IaC 描述比其他 stateless 資源多出保護策略、連線管理與讀寫分流三個維度。</p>
<h2 id="核心服務的部署順序">核心服務的部署順序</h2>
<p>核心服務的部署順序由依賴方向決定：被依賴的先建，依賴別人的後建。網路與身分是幾乎所有上層服務的共同前置 — 資料庫要放進私有 subnet、運算要套用 IAM role 才能讀 S3、load balancer 要掛在公開 subnet 並引用 security group。這些底層平面若還沒成形，上層資源會在 apply 時因為找不到 subnet ID 或 role ARN 而失敗，或更糟，建在預設 VPC 裡繞過了所有隔離設計。</p>
<p>把順序交給 IaC 工具的依賴圖自動推導，比人工排序可靠。當運算資源的定義引用了 subnet 與 security group 的資源屬性，Terraform 會解析出「subnet 先於運算」的邊，apply 時自動排程。人工維護一份「先做 A 再做 B」的清單會隨資源增加而失準，依賴圖則隨程式碼本身演進。</p>
<h3 id="四層依賴結構">四層依賴結構</h3>
<p>依賴圖的典型展開順序呈現四層結構：</p>
<table>
  <thead>
      <tr>
          <th>層次</th>
          <th>資源</th>
          <th>依賴來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>VPC、subnet、security group、IAM role</td>
          <td>無（地基層，由模組二到四建立）</td>
      </tr>
      <tr>
          <td>2</td>
          <td>RDS、ElastiCache、S3 bucket</td>
          <td>引用 subnet group、security group</td>
      </tr>
      <tr>
          <td>3</td>
          <td>ECS service / EKS workload、RDS Proxy</td>
          <td>引用 subnet、IAM role、DB 端點</td>
      </tr>
      <tr>
          <td>4</td>
          <td>ALB、listener、target group、ACM 憑證</td>
          <td>引用 public subnet、security group、ECS</td>
      </tr>
  </tbody>
</table>
<p>這四層不需要手動編排。只要程式碼裡的引用關係正確，Terraform 就會自動按這個順序 apply。當 plan 輸出的順序看起來不合直覺 — 例如 ALB 先於 ECS — 通常代表某個引用斷了、兩者之間沒有依賴邊。</p>
<h3 id="順序失控的徵兆">順序失控的徵兆</h3>
<p>順序失控的早期徵兆是：某個上層資源的定義裡寫了一串 hardcode 的 subnet ID 或 VPC ID。</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="c1"># 硬編碼 ID — 依賴圖斷裂，底層重建時上層不會跟上
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">resource</span> <span class="s2">&#34;aws_db_subnet_group&#34; &#34;private&#34;</span> {
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">  subnet_ids</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subnet-0abc123&#34;, &#34;subnet-0def456&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">}</span></span></code></pre></div><p>這段 code 跟底層的 subnet 資源沒有引用關係。底層一旦重建、ID 改變，上層不會自動跟上，state 與雲端現實之間的不一致（即 drift）就此產生。修法是把硬編碼的 ID 換成對底層資源屬性的引用：</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="c1"># 引用資源屬性 — 依賴圖自動推導，底層重建時上層自動取得新 ID
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">resource</span> <span class="s2">&#34;aws_db_subnet_group&#34; &#34;private&#34;</span> {
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">  subnet_ids</span> <span class="o">=</span> <span class="p">[</span><span class="k">for</span> <span class="k">s</span> <span class="k">in</span> <span class="k">aws_subnet</span><span class="p">.</span><span class="k">private</span> <span class="err">:</span> <span class="k">s</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">}</span></span></code></pre></div><p>跨 state 的情境（網路地基與核心服務分屬不同 state）則用 data source 取代直接引用 — 這個取捨在<a href="/blog/infra/05-core-services/stateful-protection-dependency/" data-link-title="Stateful 資源保護與跨服務依賴表達" data-link-desc="stateful 資源的保護策略（multi-AZ、備份、刪除保護）、stateful 與 stateless 的操作差異，以及用 output 與 data source 表達服務間依賴">服務依賴與跨 state 引用</a>展開。</p>
<h3 id="隱性依賴與-depends_on">隱性依賴與 depends_on</h3>
<p>自動推導涵蓋的是「引用屬性時產生的邊」。少數情況下兩個資源之間有依賴卻沒有屬性引用 — 例如一個 IAM policy attachment 必須在某個 role 被 ECS task 使用之前完成，但 task 引用的是 role ARN 而非 attachment 的輸出。這時用 <code>depends_on</code> 顯式宣告邊：</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_service&#34; &#34;api&#34;</span> {<span class="c1">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">  # ...
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span><span class="n">  depends_on</span> <span class="o">=</span> <span class="p">[</span><span class="k">aws_iam_role_policy_attachment</span><span class="p">.</span><span class="k">ecs_task_s3</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">}</span></span></code></pre></div><p><code>depends_on</code> 應該只出現在自動推導覆蓋不了的場景。如果一個 module 裡到處都是 <code>depends_on</code>，通常代表引用關係寫得不夠明確，該把隱性依賴改成屬性引用。</p>
<h2 id="資料庫rds">資料庫（RDS）</h2>
<p>資料庫是核心服務裡最需要謹慎描述的資源，因為它持有無法重建的狀態。IaC 定義它的 instance class、引擎版本、所在的 subnet group（決定它落在哪些私有 subnet）、套用的 parameter group 與 security group。連線端點不要硬編碼，改用資源 output 暴露給上層運算引用，這樣端點隨主庫 failover 或重建而改變時，上層引用自動更新。</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">  identifier</span>             <span class="o">=</span> <span class="s2">&#34;app-${var.env}-primary&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  engine</span>                 <span class="o">=</span> <span class="s2">&#34;postgres&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  engine_version</span>         <span class="o">=</span> <span class="s2">&#34;16.3&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  instance_class</span>         <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">db_instance_class</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  allocated_storage</span>      <span class="o">=</span> <span class="m">100</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">  storage_encrypted</span>      <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">  db_subnet_group_name</span>   <span class="o">=</span> <span class="k">aws_db_subnet_group</span><span class="p">.</span><span class="k">private</span><span class="p">.</span><span class="k">name</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">  vpc_security_group_ids</span> <span class="o">=</span> <span class="p">[</span><span class="k">aws_security_group</span><span class="p">.</span><span class="k">db</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">  multi_az</span>                  <span class="o">=</span><span class="n"> var.env</span> <span class="o">==</span> <span class="s2">&#34;prod&#34;</span> <span class="err">?</span> <span class="kt">true</span> <span class="err">:</span> <span class="kt">false</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">  backup_retention_period</span>   <span class="o">=</span><span class="n"> var.env</span> <span class="o">==</span> <span class="s2">&#34;prod&#34;</span> <span class="err">?</span> <span class="m">14</span> <span class="err">:</span> <span class="m">1</span>
</span></span><span class="line"><span class="ln">14</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">15</span><span class="cl"><span class="n">  deletion_protection</span>       <span class="o">=</span><span class="n"> var.env</span> <span class="o">==</span> <span class="s2">&#34;prod&#34;</span> <span class="err">?</span> <span class="kt">true</span> <span class="err">:</span> <span class="kt">false</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">  skip_final_snapshot</span>       <span class="o">=</span><span class="n"> var.env</span> <span class="o">==</span> <span class="s2">&#34;prod&#34;</span> <span class="err">?</span> <span class="kt">false</span> <span class="err">:</span> <span class="kt">true</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">  final_snapshot_identifier</span> <span class="o">=</span><span class="n"> var.env</span> <span class="o">==</span> <span class="s2">&#34;prod&#34; ? &#34;app-prod-final-${formatdate(&#34;YYYYMMDD&#34;, timestamp())}&#34;</span> <span class="err">:</span> <span class="k">null</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">  tags</span> <span class="o">=</span><span class="n"> { service</span> <span class="o">=</span> <span class="s2">&#34;payments&#34;</span> }
</span></span><span class="line"><span class="ln">20</span><span class="cl">}
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">output</span> <span class="s2">&#34;db_endpoint&#34;</span> {
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="n">  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">24</span><span class="cl">}</span></span></code></pre></div><h3 id="加密的不可逆性">加密的不可逆性</h3>
<p><code>storage_encrypted = true</code> 確保磁碟層級的加密在資源建立時就生效。RDS 不支援事後對既有 instance 開加密 — 漏了只能重建。補救路徑是匯出快照、用加密 KMS key 複製快照成加密版本、再用加密快照還原成新 instance。這個過程需要停機或切換端點，對已經承載流量的 production 資料庫代價很高。prod 的 RDS 若 <code>storage_encrypted</code> 為 false，這筆技術債越早處理越便宜。</p>
<h3 id="parameter-group-的角色">parameter group 的角色</h3>
<p>parameter group 定義資料庫引擎層級的行為參數（如 <code>max_connections</code>、<code>work_mem</code>、<code>log_min_duration_statement</code>），是 RDS instance 的設定骨架。IaC 描述 parameter group 的好處是讓這些參數進版本控制 — 有人改了 <code>max_connections</code> 會出現在 PR diff 裡，而不是某天在 Console 改了沒人知道。</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_parameter_group&#34; &#34;postgres16&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  family</span> <span class="o">=</span> <span class="s2">&#34;postgres16&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  name</span>   <span class="o">=</span> <span class="s2">&#34;app-${var.env}-pg16&#34;</span>
</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">parameter</span> {
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">    name</span>  <span class="o">=</span> <span class="s2">&#34;log_min_duration_statement&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">    value</span> <span class="o">=</span> <span class="s2">&#34;1000&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  }
</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="k">parameter</span> {
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">    name</span>  <span class="o">=</span> <span class="s2">&#34;shared_preload_libraries&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">    value</span> <span class="o">=</span> <span class="s2">&#34;pg_stat_statements&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  }
</span></span><span class="line"><span class="ln">14</span><span class="cl">}</span></span></code></pre></div><p>修改 parameter group 的某些參數需要重啟 RDS instance（稱為 <code>apply_method = &quot;pending-reboot&quot;</code>），修改前要先確認這個參數屬於「立即生效」還是「要重啟」。在 Terraform plan 裡不會明確標示重啟，要靠 AWS 文件交叉比對。</p>
<h3 id="連線管理">連線管理</h3>
<p>運算到資料庫之間有一段常被略過的接線：連線管理。無狀態運算水平擴張時，每個實例各自開連線，容易把資料庫的連線數打滿。一個 ECS service 從 5 個 task 擴到 50 個、每個 task 開 10 條連線，就從 50 條跳到 500 條 — 而一台 <code>db.r6g.large</code> 的 <code>max_connections</code> 預設約在 1600 左右，500 條已經吃掉三分之一。</p>
<p>出現「擴運算反而拖垮 DB」的訊號時，解法是引入連線池或受管的連線代理。RDS Proxy 是 AWS 的受管方案：它在運算與 RDS 之間當一層連線池，把下游的數百條短連線收斂成對 RDS 的少量長連線。在 IaC 裡一併定義，輸出 proxy 端點給運算引用：</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_proxy&#34; &#34;app&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  name</span>                   <span class="o">=</span> <span class="s2">&#34;app-${var.env}-proxy&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  engine_family</span>          <span class="o">=</span> <span class="s2">&#34;POSTGRESQL&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  role_arn</span>               <span class="o">=</span> <span class="k">aws_iam_role</span><span class="p">.</span><span class="k">rds_proxy</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  vpc_subnet_ids</span>         <span class="o">=</span> <span class="p">[</span><span class="k">for</span> <span class="k">s</span> <span class="k">in</span> <span class="k">aws_subnet</span><span class="p">.</span><span class="k">private</span> <span class="err">:</span> <span class="k">s</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  vpc_security_group_ids</span> <span class="o">=</span> <span class="p">[</span><span class="k">aws_security_group</span><span class="p">.</span><span class="k">db</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">auth</span> {
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">    auth_scheme</span> <span class="o">=</span> <span class="s2">&#34;SECRETS&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">    secret_arn</span>  <span class="o">=</span> <span class="k">aws_secretsmanager_secret</span><span class="p">.</span><span class="k">db_password</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  }
</span></span><span class="line"><span class="ln">12</span><span class="cl">}
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">output</span> <span class="s2">&#34;db_proxy_endpoint&#34;</span> {
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">  value</span> <span class="o">=</span> <span class="k">aws_db_proxy</span><span class="p">.</span><span class="k">app</span><span class="p">.</span><span class="k">endpoint</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">}</span></span></code></pre></div><p>運算端引用 <code>db_proxy_endpoint</code> 而非 <code>db_endpoint</code>，連線管理就從各 task 自己處理轉成由 proxy 統一收斂。RDS Proxy 同時提供 failover 的連線保持 — 主庫切換到 standby 時，proxy 維護的連線不會全部斷開重建，應用端感受到的是短暫延遲而非連線錯誤。</p>
<p>判讀是否需要 RDS Proxy 的訊號是連線數成長曲線：如果運算的擴縮範圍固定且連線數上限遠低於 <code>max_connections</code>，直連即可；如果運算會頻繁擴縮或連線數可能逼近上限，proxy 值得引入。proxy 本身有額外成本（按 vCPU 計費），不是所有環境都划算 — dev 環境通常直連就夠。</p>
<h3 id="read-replica">read replica</h3>
<p>當讀流量遠大於寫、且能容忍副本的複寫延遲（通常是毫秒到秒級）時，read replica 是把讀請求導離主庫的下一步。replica 在 IaC 裡用獨立資源描述，引用主庫的 identifier：</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;read_replica&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  identifier</span>             <span class="o">=</span> <span class="s2">&#34;app-${var.env}-replica&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  replicate_source_db</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">identifier</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  instance_class</span>         <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">db_replica_class</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  vpc_security_group_ids</span> <span class="o">=</span> <span class="p">[</span><span class="k">aws_security_group</span><span class="p">.</span><span class="k">db</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">}
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">output</span> <span class="s2">&#34;db_replica_endpoint&#34;</span> {
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">  value</span> <span class="o">=</span> <span class="k">aws_db_instance</span><span class="p">.</span><span class="k">read_replica</span><span class="p">.</span><span class="k">endpoint</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">}</span></span></code></pre></div><p>運算端依讀寫分流引用不同端點 — 寫走 <code>db_endpoint</code>（或 <code>db_proxy_endpoint</code>），讀走 <code>db_replica_endpoint</code>。這個分流邏輯屬於應用層的責任，infra 只負責把端點暴露出來。</p>
<p>read replica 的邊界要講清楚：它緩解讀流量對主庫的壓力，但它不是備份。replica 會同步複製主庫的所有變更 — 包括誤刪的資料。需要還原到某個時間點的保護由 backup retention 與 PITR（point-in-time recovery）提供，這兩者的 IaC 描述在 <a href="/blog/infra/05-core-services/stateful-protection-dependency/" data-link-title="Stateful 資源保護與跨服務依賴表達" data-link-desc="stateful 資源的保護策略（multi-AZ、備份、刪除保護）、stateful 與 stateless 的操作差異，以及用 output 與 data source 表達服務間依賴">stateful 保護策略</a>。</p>
<h3 id="引擎版本升級的取捨">引擎版本升級的取捨</h3>
<p>RDS 引擎版本（<code>engine_version</code>）寫進 IaC 後，版本升級就成為一個需要 PR review 的變更。升級分 minor 和 major：minor 升級（16.2 → 16.3）通常向後相容、可在維護視窗自動套用；major 升級（15 → 16）可能有 breaking change，需要先在 dev 環境驗證、備份、排維護窗口。</p>
<p>在 IaC 裡把 <code>engine_version</code> 寫死是刻意的選擇 — 它阻止 AWS 在背景自動升級 major 版本，讓版本變更必須走 PR。代價是需要定期檢查是否有 EOL 版本還在用。如果 <code>engine_version</code> 指向的版本已經超過 AWS 的支援期限，Terraform apply 會在某天失敗（AWS 會強制升級），這比主動升級更不可控。</p>
<p>資料庫在規模放大後的治理維度也會改變。<a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">Netflix 把分散的 Aurora 叢集整併</a>後成本降了 28%——多個團隊各自開的 RDS instance 加起來的閒置容量遠超一個整併後的叢集。infra 層的教訓是 RDS 的 IaC 描述不只管單一 instance 的設定，長期還要管叢集的分布與合併策略。另一個維度是合規需求驅動的資料落地：<a href="/blog/backend/09-performance-capacity/cases/hard-rock-digital-cockroachdb-sports-betting/" data-link-title="9.C41 Hard Rock Digital：CockroachDB on AWS Outposts、Wire Act 合規 &#43; 跨州單一邏輯 DB" data-link-desc="Hard Rock Digital 用 CockroachDB 跨 AWS Outposts &#43; US-East-1、Wire Act 強制資料留州、單一邏輯 DB 解多州 sportsbook、100 node 32 vCPU 撐 Super Bowl">Hard Rock Digital 因為 Wire Act 法規要求資料留在特定州</a>，用 AWS Outposts 在地端跑運算——這類情境下 infra 的 region 與可用區選擇由法規約束驅動，而非純技術決策。</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 group 引用 private subnet</li>
<li>→ <a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二：身分與憑證地基</a>：RDS Proxy 的 IAM role 與 secret 存取</li>
<li>→ <a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四：環境分離與模組化</a>：prod / dev 用同一個 module、不同參數值</li>
<li>→ <a href="/blog/infra/05-core-services/stateful-protection-dependency/" data-link-title="Stateful 資源保護與跨服務依賴表達" data-link-desc="stateful 資源的保護策略（multi-AZ、備份、刪除保護）、stateful 與 stateless 的操作差異，以及用 output 與 data source 表達服務間依賴">stateful 保護與跨 state 引用</a>：backup retention、deletion protection、multi-AZ 的完整討論</li>
<li>→ <a href="/blog/infra/05-core-services/compute-ecs-eks/" data-link-title="運算平台上 IaC — ECS 與 EKS" data-link-desc="容器運算平台的 IaC 描述：ECS 與 EKS 選型、task definition 與映像版本解耦、IAM task role 分離、auto-scaling 策略">運算上 IaC</a>：運算端怎麼引用資料庫端點</li>
<li>→ <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">backend 模組一：資料庫</a>：schema 設計、migration、query 層面的服務端討論</li>
</ul>
]]></content:encoded></item><item><title>模組五：核心服務上 IaC</title><link>https://tarrragon.github.io/blog/infra/05-core-services/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/05-core-services/</guid><description>&lt;p>地基就緒後，依「地基 → 上層」的順序把實際承載業務的服務寫進 IaC。前四個模組建立的身分、網路與環境分離是底層平面，這一層在它們之上描述資料庫、運算、儲存與入口 — 業務流量真正落地的地方。順序與依賴的表達方式決定了這層能不能被乾淨地重建、拆除與演進。&lt;/p>
&lt;h2 id="上核心服務的順序">上核心服務的順序&lt;/h2>
&lt;p>核心服務的部署順序由依賴方向決定：被依賴的先建，依賴別人的後建。網路與身分是幾乎所有上層服務的共同前置 — 資料庫要放進私有 subnet、運算要套用 IAM role 才能讀 S3、load balancer 要掛在公開 subnet 並引用 security group。這些底層平面若還沒成形，上層資源會在 apply 時因為找不到 subnet ID 或 role ARN 而失敗，或更糟，建在預設 VPC 裡繞過了所有隔離設計。&lt;/p>
&lt;p>把順序交給 IaC 工具的依賴圖自動推導，比人工排序可靠。當運算資源的定義引用了 subnet 與 security group 的資源屬性，Terraform 會解析出「subnet 先於運算」的邊，apply 時自動排程。人工維護一份「先做 A 再做 B」的清單會隨資源增加而失準，依賴圖則隨程式碼本身演進。&lt;/p>
&lt;p>順序失控的早期徵兆是：某個上層資源的定義裡寫了一串 hardcode 的 subnet ID 或 VPC ID，代表它沒有透過依賴圖連到底層平面。底層一旦重建、ID 改變，上層不會自動跟上，state 與雲端現實之間的不一致（即 drift）就此產生。把硬編碼的 ID 換成對底層資源屬性或 data source 的引用，順序才會回到工具掌控之內。&lt;/p>
&lt;h2 id="各類服務怎麼描述">各類服務怎麼描述&lt;/h2>
&lt;p>四類核心服務承擔不同責任，IaC 描述它們時關注的屬性也不同。共通原則是：描述服務的「身分與接線」，而非把每個執行期參數都塞進程式碼。&lt;/p>
&lt;p>&lt;strong>資料庫（RDS）&lt;/strong> 是這層裡最需要謹慎描述的資源，因為它持有無法重建的狀態。IaC 定義它的 instance class、引擎版本、所在的 subnet group（決定它落在哪些私有 subnet）、套用的 parameter group 與 security group。連線端點不要硬編碼，改用資源 output 暴露給上層運算引用。&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"> identifier&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;app-prod-primary&amp;#34;&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"> engine&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;postgres&amp;#34;&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"> engine_version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;16.3&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"> instance_class&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;db.r6g.large&amp;#34;&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"> db_subnet_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_db_subnet_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">private&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">name&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"> vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="k">aws_security_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">id&lt;/span>&lt;span class="p">]&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>&lt;strong>運算（ECS / EKS）&lt;/strong> 描述的是業務程式碼的執行載體。重點屬性是它跑在哪些 subnet、套用哪個 task / pod 的 IAM role、掛到哪個 load balancer 的 target group，以及與容器映像版本解耦 — 映像 tag 通常由 CI/CD 在部署期注入，不寫死在 infra 程式碼裡。這層只描述「運算容量與接線」，實際跑什麼版本由部署流程決定，這個邊界讓 infra 變更與應用發布各走各的節奏。&lt;/p>
&lt;p>ECS 與 EKS 在這裡被併寫，但兩者的維運模型不同、存在實際選型：ECS 是受管的容器編排，控制平面由雲商代管、心智負擔低，接線概念貼近 AWS 原生資源；EKS 是受管的 Kubernetes，換來跨雲可攜的生態與更細的編排控制，代價是要承擔 Kubernetes 自身的運維面（升級、附加元件、RBAC）。團隊已有 Kubernetes 能力或需要其生態時 EKS 的成本才划算，否則 ECS 的低負擔通常是預設起點。IaC 描述的接線骨架相近，差異主要落在編排層的資源類型。&lt;/p>
&lt;p>運算到資料庫之間還有一段常被略過的接線：連線管理。無狀態運算水平擴張時，每個實例各自開連線，容易把資料庫的連線數打滿 — 出現「擴運算反而拖垮 DB」的訊號時，要引入連線池或受管的連線代理（如 RDS Proxy），把連線收斂後再進資料庫，這層也可寫進 IaC 並輸出端點給運算引用。當讀流量遠大於寫、且能容忍副本的複寫延遲時，read replica 是把讀請求導離主庫的下一步，運算端依讀寫分流引用不同端點。&lt;/p>
&lt;p>&lt;strong>儲存（S3）&lt;/strong> 描述的是 bucket 的存在、命名、加密設定、版本控制與存取政策。bucket 本身幾乎沒有重建代價意義上的狀態問題 — 困難在它「裝的東西」。空 bucket 可隨時重建，裝了正式資料的 bucket 與 RDS 一樣不可隨意 destroy。描述時把加密、public access block、生命週期規則寫進去，這些是安全與成本的預設防線。&lt;/p>
&lt;p>&lt;strong>入口（ALB）&lt;/strong> 描述流量進入系統的第一站。它定義 listener（監聽哪些 port 與協定）、target group（流量導向哪些運算後端）、health check 條件與 TLS 憑證。ALB 本身是 stateless 的 — 重建一個 load balancer 不會遺失資料，但會換掉它的 DNS 名稱，所以對外服務通常在它前面再掛一層穩定的 DNS 記錄。健康檢查的路徑與閾值是這裡最常被忽略的判讀點：閾值太寬鬆會把壞掉的後端留在輪替裡，太嚴格會在部署瞬間誤判健康的新實例。HTTPS listener 引用的 TLS 憑證也屬於這層的接線 — 憑證由 ACM 簽發與自動續期，IaC 用憑證資源描述它（涵蓋網域與驗證方式），再把憑證 ARN 接到 listener 上，讓「憑證存在、續期、掛載」整條鏈都進版本控制，而非在 Console 手動上傳一份會過期沒人盯的憑證。&lt;/p></description><content:encoded><![CDATA[<p>地基就緒後，依「地基 → 上層」的順序把實際承載業務的服務寫進 IaC。前四個模組建立的身分、網路與環境分離是底層平面，這一層在它們之上描述資料庫、運算、儲存與入口 — 業務流量真正落地的地方。順序與依賴的表達方式決定了這層能不能被乾淨地重建、拆除與演進。</p>
<h2 id="上核心服務的順序">上核心服務的順序</h2>
<p>核心服務的部署順序由依賴方向決定：被依賴的先建，依賴別人的後建。網路與身分是幾乎所有上層服務的共同前置 — 資料庫要放進私有 subnet、運算要套用 IAM role 才能讀 S3、load balancer 要掛在公開 subnet 並引用 security group。這些底層平面若還沒成形，上層資源會在 apply 時因為找不到 subnet ID 或 role ARN 而失敗，或更糟，建在預設 VPC 裡繞過了所有隔離設計。</p>
<p>把順序交給 IaC 工具的依賴圖自動推導，比人工排序可靠。當運算資源的定義引用了 subnet 與 security group 的資源屬性，Terraform 會解析出「subnet 先於運算」的邊，apply 時自動排程。人工維護一份「先做 A 再做 B」的清單會隨資源增加而失準，依賴圖則隨程式碼本身演進。</p>
<p>順序失控的早期徵兆是：某個上層資源的定義裡寫了一串 hardcode 的 subnet ID 或 VPC ID，代表它沒有透過依賴圖連到底層平面。底層一旦重建、ID 改變，上層不會自動跟上，state 與雲端現實之間的不一致（即 drift）就此產生。把硬編碼的 ID 換成對底層資源屬性或 data source 的引用，順序才會回到工具掌控之內。</p>
<h2 id="各類服務怎麼描述">各類服務怎麼描述</h2>
<p>四類核心服務承擔不同責任，IaC 描述它們時關注的屬性也不同。共通原則是：描述服務的「身分與接線」，而非把每個執行期參數都塞進程式碼。</p>
<p><strong>資料庫（RDS）</strong> 是這層裡最需要謹慎描述的資源，因為它持有無法重建的狀態。IaC 定義它的 instance class、引擎版本、所在的 subnet group（決定它落在哪些私有 subnet）、套用的 parameter group 與 security group。連線端點不要硬編碼，改用資源 output 暴露給上層運算引用。</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">  identifier</span>             <span class="o">=</span> <span class="s2">&#34;app-prod-primary&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">  engine</span>                 <span class="o">=</span> <span class="s2">&#34;postgres&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">  engine_version</span>         <span class="o">=</span> <span class="s2">&#34;16.3&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">  instance_class</span>         <span class="o">=</span> <span class="s2">&#34;db.r6g.large&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">  db_subnet_group_name</span>   <span class="o">=</span> <span class="k">aws_db_subnet_group</span><span class="p">.</span><span class="k">private</span><span class="p">.</span><span class="k">name</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">  vpc_security_group_ids</span> <span class="o">=</span> <span class="p">[</span><span class="k">aws_security_group</span><span class="p">.</span><span class="k">db</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">}</span></span></code></pre></div><p><strong>運算（ECS / EKS）</strong> 描述的是業務程式碼的執行載體。重點屬性是它跑在哪些 subnet、套用哪個 task / pod 的 IAM role、掛到哪個 load balancer 的 target group，以及與容器映像版本解耦 — 映像 tag 通常由 CI/CD 在部署期注入，不寫死在 infra 程式碼裡。這層只描述「運算容量與接線」，實際跑什麼版本由部署流程決定，這個邊界讓 infra 變更與應用發布各走各的節奏。</p>
<p>ECS 與 EKS 在這裡被併寫，但兩者的維運模型不同、存在實際選型：ECS 是受管的容器編排，控制平面由雲商代管、心智負擔低，接線概念貼近 AWS 原生資源；EKS 是受管的 Kubernetes，換來跨雲可攜的生態與更細的編排控制，代價是要承擔 Kubernetes 自身的運維面（升級、附加元件、RBAC）。團隊已有 Kubernetes 能力或需要其生態時 EKS 的成本才划算，否則 ECS 的低負擔通常是預設起點。IaC 描述的接線骨架相近，差異主要落在編排層的資源類型。</p>
<p>運算到資料庫之間還有一段常被略過的接線：連線管理。無狀態運算水平擴張時，每個實例各自開連線，容易把資料庫的連線數打滿 — 出現「擴運算反而拖垮 DB」的訊號時，要引入連線池或受管的連線代理（如 RDS Proxy），把連線收斂後再進資料庫，這層也可寫進 IaC 並輸出端點給運算引用。當讀流量遠大於寫、且能容忍副本的複寫延遲時，read replica 是把讀請求導離主庫的下一步，運算端依讀寫分流引用不同端點。</p>
<p><strong>儲存（S3）</strong> 描述的是 bucket 的存在、命名、加密設定、版本控制與存取政策。bucket 本身幾乎沒有重建代價意義上的狀態問題 — 困難在它「裝的東西」。空 bucket 可隨時重建，裝了正式資料的 bucket 與 RDS 一樣不可隨意 destroy。描述時把加密、public access block、生命週期規則寫進去，這些是安全與成本的預設防線。</p>
<p><strong>入口（ALB）</strong> 描述流量進入系統的第一站。它定義 listener（監聽哪些 port 與協定）、target group（流量導向哪些運算後端）、health check 條件與 TLS 憑證。ALB 本身是 stateless 的 — 重建一個 load balancer 不會遺失資料，但會換掉它的 DNS 名稱，所以對外服務通常在它前面再掛一層穩定的 DNS 記錄。健康檢查的路徑與閾值是這裡最常被忽略的判讀點：閾值太寬鬆會把壞掉的後端留在輪替裡，太嚴格會在部署瞬間誤判健康的新實例。HTTPS listener 引用的 TLS 憑證也屬於這層的接線 — 憑證由 ACM 簽發與自動續期，IaC 用憑證資源描述它（涵蓋網域與驗證方式），再把憑證 ARN 接到 listener 上，讓「憑證存在、續期、掛載」整條鏈都進版本控制，而非在 Console 手動上傳一份會過期沒人盯的憑證。</p>
<h2 id="stateful-資源的特殊處理">stateful 資源的特殊處理</h2>
<p>stateful 資源的 IaC 描述要把「保護狀態」當成第一類需求，而非事後補上的選項。RDS 是典型 — 它的高可用、備份與還原能力全都能、也應該用程式碼描述，這樣保護策略本身就進入版本控制與審查流程，而非散落在某人手動點過的 Console 設定裡。</p>
<p>multi-AZ 用一個布林屬性開啟，背後是 RDS 在另一個可用區維護同步副本。它解的是可用性：主庫故障時 failover 到 standby，但這個切換有秒級到一兩分鐘的窗口而非零停機，期間連線會中斷重連。要先界定它的邊界，才不會把它當成超出職責的工具。standby 副本是熱備不可讀，所以 multi-AZ 不提供讀取擴展 — 要分攤讀流量得另開 read replica 或改用 multi-AZ cluster 形態。它也不防邏輯損壞：誤刪一張表或一筆錯誤的批次更新會同步複製到 standby，這類風險由 backup 與時間點還原（PITR）負責，與 multi-AZ 的可用性職責正交，兩者要分別配置。</p>
<p>backup 用保留天數與備份視窗描述，RDS 依此每日自動快照並保留交易日誌以支援還原到任意時間點。自動備份的保留上限是 35 天，更長的留存要靠手動快照或匯出到 S3 自行管理。下方 <code>backup_retention_period</code> 取 14 是以 RPO 與合規要求反推的結果 — 一般營運場景 14 天足以涵蓋「發現問題到決定還原」的時間差，受監理或需要更長追溯窗口的服務則往 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&#34;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">}</span></span></code></pre></div><p>該在 review 攔下的訊號是：正式環境的 stateful 資源若 <code>backup_retention_period</code> 為 0 或 <code>deletion_protection</code> 為 false，代表狀態保護沒有寫進程式碼。把這些屬性視為正式資料庫的硬性下限，而非可調的偏好。</p>
<h2 id="stateful-與-stateless-的差異怎麼影響操作">stateful 與 stateless 的差異怎麼影響操作</h2>
<p>stateful 與 stateless 資源的根本差別在重建代價，這個差別會傳導到刪除保護與 drift 風險的處理方式。stateless 資源（ECS service、ALB、無狀態運算）重建只是換一組新實例，幾分鐘內恢復、沒有資料損失，所以它們可以被頻繁地 destroy 與 recreate，是 IaC 最擅長的對象。</p>
<p>stateful 資源（RDS、裝了資料的 S3、持久化 volume）重建意味著資料遺失或漫長的還原，代價可能是數小時的停機與不可逆的損失。這個差別帶來三個操作後果。第一，刪除保護是必要的：stateful 資源開啟 deletion protection，讓「不小心 destroy」需要先顯式關閉保護這一步，多一道人為確認。第二，state drift 的容忍度不同：stateless 資源的 drift 可以靠重建抹平，stateful 資源的 drift（例如有人手動改了 parameter group）要謹慎處理，因為 IaC 的「修正回程式碼狀態」動作可能觸發重啟或重建。第三，變更的審查強度不同：改動 stateful 資源的 plan 輸出要逐行看，特別警惕任何顯示為 <code>replace</code>（先刪後建）而非 <code>update in-place</code> 的項目 — 對資料庫而言這通常代表資料會被丟棄。</p>
<p>實務上把這個差別寫進流程：stateful 資源的變更走更嚴格的 PR review 與分階段套用，這部分的自動化護欄在「模組七：infra 走 PR 流程與自動化護欄」展開。</p>
<h2 id="服務之間的依賴怎麼表達">服務之間的依賴怎麼表達</h2>
<p>服務間依賴用 output 與 data source 表達，讓引用關係成為程式碼裡可追蹤的邊，而非靠人記憶的隱性約定。同一個 state 內，直接引用資源屬性即可建立依賴 — 運算資源引用資料庫的端點 output，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">output</span> <span class="s2">&#34;db_endpoint&#34;</span> {
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">  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">3</span><span class="cl">}</span></span></code></pre></div><p>跨 state（例如網路地基與核心服務分屬不同 Terraform state，呼應「模組四：環境分離與模組化」的拆分）時，下游用 data source 唯讀地讀取上游已建立的資源。下游查詢上游的 VPC 與 subnet，取得 ID 來放置自己的資源，而不複製貼上硬編碼的值。</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-prod&#34;</span> }
</span></span><span class="line"><span class="ln">3</span><span class="cl">}</span></span></code></pre></div><p>兩種方式的取捨在耦合與隔離之間。同 state 引用最直接、依賴圖最完整，但 state 越大、單次 apply 的爆炸半徑越大。跨 state 的 data source 把爆炸半徑切小、讓網路地基能獨立演進，代價是依賴關係跨越了 state 邊界、需要約定上游一定先 apply。判讀訊號是：若一份核心服務程式碼裡出現大量寫死的 ID，通常代表該用 data source 而沒用 — 這是日後上游重建時 drift 與 broken reference 的來源。把硬編碼的引用換成 data source，依賴關係才會在程式碼裡顯性化、可被工具與 review 看見。</p>
<p>服務都接上後，下一個關注點是讓它們可被觀測 — log 與 metric 與服務同生命週期建立，這部分在「模組六：可觀測性與 log 同生命週期」展開。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/infra/05-core-services/deployment-order-database/" data-link-title="部署順序與資料庫上 IaC" data-link-desc="核心服務的依賴圖決定部署順序，資料庫作為第一批上層服務需要最謹慎的 IaC 描述 — 涵蓋 RDS 接線、連線管理、read replica 與端點暴露">部署順序與資料庫上 IaC</a></td>
          <td>依賴圖決定部署順序，RDS 接線、連線管理、read replica 與端點暴露</td>
      </tr>
      <tr>
          <td><a href="/blog/infra/05-core-services/compute-ecs-eks/" data-link-title="運算平台上 IaC — ECS 與 EKS" data-link-desc="容器運算平台的 IaC 描述：ECS 與 EKS 選型、task definition 與映像版本解耦、IAM task role 分離、auto-scaling 策略">運算平台上 IaC — ECS 與 EKS</a></td>
          <td>ECS 與 EKS 選型、task definition 與映像版本解耦、IAM task role、auto-scaling</td>
      </tr>
      <tr>
          <td><a href="/blog/infra/05-core-services/storage-s3/" data-link-title="儲存上 IaC — S3 bucket 的安全與生命週期" data-link-desc="S3 bucket 的加密、版本控制、公開存取封鎖、生命週期規則、bucket policy 與事件通知怎麼寫進 IaC，讓儲存的安全與成本防線可審查可追蹤">儲存上 IaC — S3 bucket 的安全與生命週期</a></td>
          <td>加密、版本控制、公開存取封鎖、生命週期規則、bucket policy 與事件通知</td>
      </tr>
      <tr>
          <td><a href="/blog/infra/05-core-services/loadbalancer-alb/" data-link-title="入口上 IaC — ALB、TLS 與健康檢查" data-link-desc="Application Load Balancer 的 listener、target group、健康檢查閾值設計，以及用 ACM 把 TLS 憑證的簽發、驗證與掛載整條鏈寫進版本控制">入口上 IaC — ALB、TLS 與健康檢查</a></td>
          <td>listener、target group、健康檢查閾值設計、ACM 憑證與 DNS 別名</td>
      </tr>
      <tr>
          <td><a href="/blog/infra/05-core-services/stateful-protection-dependency/" data-link-title="Stateful 資源保護與跨服務依賴表達" data-link-desc="stateful 資源的保護策略（multi-AZ、備份、刪除保護）、stateful 與 stateless 的操作差異，以及用 output 與 data source 表達服務間依賴">Stateful 資源保護與跨服務依賴表達</a></td>
          <td>multi-AZ 邊界、備份保留、刪除保護、stateful vs stateless 操作差異、output 與 data source</td>
      </tr>
      <tr>
          <td><a href="/blog/infra/05-core-services/acm-tls-dns-setup/" data-link-title="ACM 憑證、DNS 與 HTTPS 設定" data-link-desc="從 Route 53 hosted zone 到 ACM 憑證申請、DNS 驗證、ALB HTTPS listener 與 HTTP 重導的完整設定流程">ACM 憑證、DNS 與 HTTPS 設定</a></td>
          <td>hosted zone、DNS 驗證、TLS listener、HTTP redirect、SAN 憑證、續期監控</td>
      </tr>
      <tr>
          <td><a href="/blog/infra/05-core-services/ecs-fargate-cost-optimization/" data-link-title="ECS Fargate 成本分析與優化" data-link-desc="Fargate 的計價模型、與 EC2 launch type 的成本交叉點、Spot 與 Savings Plans 的折扣機制、task 規格的 rightsizing 方法，以及何時該切回 EC2">ECS Fargate 成本分析與優化</a></td>
          <td>Fargate vs EC2 成本比較、Fargate Spot、Savings Plans、task rightsizing</td>
      </tr>
  </tbody>
</table>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend 模組五：部署平台</a>：PaaS / container 平台跑在這層之上</li>
<li>→ <a href="/blog/devops/" data-link-title="DevOps 實務指南" data-link-desc="負載平衡、水平擴展、流量管控、服務探活、容量規劃、高可用、突發流量、成本管理 — 服務營運的工程基礎">devops 實務指南</a>：這些服務上線後的運行期維運</li>
</ul>
]]></content:encoded></item></channel></rss>