<?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>模組零：infra 是什麼，為什麼 day 1 就要鋪地基 on Tarragon</title><link>https://tarrragon.github.io/blog/infra/00-infra-mindset/</link><description>Recent content in 模組零：infra 是什麼，為什麼 day 1 就要鋪地基 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/infra/00-infra-mindset/index.xml" rel="self" type="application/rss+xml"/><item><title>從單一環境到環境分離：infra 需求的浮現過程</title><link>https://tarrragon.github.io/blog/infra/00-infra-mindset/one-machine-to-environments/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/00-infra-mindset/one-machine-to-environments/</guid><description>&lt;p>多數服務的起點是一台運算實例加一台資料庫，部署方式是 SSH 進去拉 code 再重啟。這個結構在單人、單環境、低變更頻率的條件下運作正常，但它的隱性前提是：所有設定只有一份，且只有一個人在操作。機器的配置存在操作者的記憶裡，資料庫參數存在 Console 頁面上，security group 規則是建立時隨手設的。這些設定沒有被記錄在任何能回溯或重建的地方。&lt;/p>
&lt;p>這個結構的操作極限會在兩個時間點浮現：第一次需要在正式環境以外的地方驗證變更時，以及第二個人開始操作同一組資源時。以下依序說明每個階段的操作現實與對應的 infra 需求。&lt;/p>
&lt;h2 id="資料庫變更需要驗證環境">資料庫變更需要驗證環境&lt;/h2>
&lt;p>應用新增功能時經常需要改資料庫的表結構 — 加欄位、改索引、拆表。這類操作（database migration）如果語法有誤或邏輯有缺，可能導致服務中斷或資料不一致。正常做法是先在非正式環境驗證通過，再推到 production 執行。&lt;/p>
&lt;p>單一環境的情況下沒有驗證的場所。三種應對方式各有不同的風險邊界：&lt;/p>
&lt;p>&lt;strong>直接在 production 執行&lt;/strong>。成本最低，風險最高。migration 腳本跑下去的那一刻，正在使用服務的使用者直接承受後果 — 一個鎖住整張大表的 &lt;code>ALTER TABLE&lt;/code> 會讓所有查詢卡住，一個 &lt;code>DROP COLUMN&lt;/code> 刪錯欄位會造成不可逆的資料遺失。服務規模小、使用者少時代價尚可承受；一旦服務開始承載營收或外部依賴，這個做法的風險代價就超過了它省下的時間。&lt;/p>
&lt;p>&lt;strong>手動複製一套環境&lt;/strong>。到 Console 上照 production 的設定重新建一台 EC2、開一台 RDS、配一組 security group，得到一套「看起來一樣」的 staging。migration 先在 staging 驗證再推 production。這解決了驗證場所的問題，但引入了漂移問題 — 下一節說明。&lt;/p>
&lt;p>&lt;strong>用程式碼描述環境，讓工具複製&lt;/strong>。把 production 的設定寫成描述檔，用 Terraform 或 OpenTofu 根據同一份描述建出 staging。初始成本比手動複製高（要學工具、寫描述檔），但它保證了手動複製保證不了的一件事：staging 和 production 的結構來自同一份描述，差異只存在於刻意不同的參數（機器規格、備份天數）。這就是 Infrastructure as Code（IaC）的起點。&lt;/p>
&lt;h2 id="手動複製的環境會漂移">手動複製的環境會漂移&lt;/h2>
&lt;p>手動複製的 staging 在建立當天跟 production 一致。一個月後通常不再一致。&lt;/p>
&lt;p>漂移的來源是日常操作中的局部調整：staging 的 security group 多了一條規則（某次除錯時加的，事後忘了刪）、production 的 RDS 參數被調過（線上出現慢查詢，DBA 改了 &lt;code>work_mem&lt;/code> 但沒同步 staging）、staging 的 IAM role 多了一條 policy（測試新功能時加的，測完沒拿掉）。每一筆差異都很小，小到不值得專門同步，但它們會累積。&lt;/p>
&lt;p>漂移引爆的時機跟產生的時機通常隔很遠。一個 migration 在 staging 通過、推到 production 失敗，排查半天後發現是一個月前的參數調整造成的 — staging 的 &lt;code>work_mem&lt;/code> 跟 production 不同，剛好影響了這次 migration 的執行計畫。這種因果關係跨越時間的錯誤，排查成本遠高於錯誤本身。&lt;/p>
&lt;p>漂移的根源是「兩套環境各自獨立維護」。只要兩份設定各自存在，同步就完全依賴操作者的記憶與紀律，而記憶會衰退、紀律會在壓力下鬆懈。結構性的解法是讓兩套環境共用同一份設定，差異只存在於刻意控制的參數。&lt;/p>
&lt;h2 id="同一份描述不同的參數">同一份描述、不同的參數&lt;/h2>
&lt;p>IaC 工具消除漂移的方式，是把環境的結構寫成一份 module，用不同的參數值建出不同環境。程式碼只有一份，結構保證相同；差異全部收斂在參數裡，每一處「故意不同」都是明確且可審查的。&lt;/p>
&lt;p>一個描述資料庫的 module：&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">variable&lt;/span> &lt;span class="s2">&amp;#34;instance_class&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"> type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">variable&lt;/span> &lt;span class="s2">&amp;#34;backup_retention_days&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"> type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">number&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"> default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">7&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;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&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;main&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&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">12&lt;/span>&lt;span class="cl">&lt;span class="n"> instance_class&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">instance_class&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="n"> backup_retention_period&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">backup_retention_days&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Production 傳入大機器和長備份，staging 傳入小機器和短備份：&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"># production
&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">module&lt;/span> &lt;span class="s2">&amp;#34;database&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"> source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;./modules/database&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"> 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"> 5&lt;/span>&lt;span class="cl">&lt;span class="n"> backup_retention_days&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"> 6&lt;/span>&lt;span class="cl">}&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1"># staging
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">module&lt;/span> &lt;span class="s2">&amp;#34;database&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n"> source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;./modules/database&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&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.t3.small&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="n"> backup_retention_days&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>兩個環境跑的是同一段 module 程式碼。引擎版本、連線方式、安全設定完全相同（寫在 module 裡、不是參數），差異只有機器規格和備份天數（刻意透過參數控制）。改動 module 一次、兩個環境同時生效，漂移的空間被結構性消除。&lt;/p>
&lt;p>IaC 工具會維護一份 state 記錄，追蹤每個環境裡實際建了哪些資源和它們的屬性。改了程式碼後跑 &lt;code>terraform plan&lt;/code>，工具會比對新的程式碼和 state 的差異，列出「會新增 / 修改 / 刪除什麼」。確認差異符合預期後才執行 &lt;code>apply&lt;/code>。state 的角色與安全存放方式在&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> 展開，環境的目錄結構與 module 設計在&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></description><content:encoded><![CDATA[<p>多數服務的起點是一台運算實例加一台資料庫，部署方式是 SSH 進去拉 code 再重啟。這個結構在單人、單環境、低變更頻率的條件下運作正常，但它的隱性前提是：所有設定只有一份，且只有一個人在操作。機器的配置存在操作者的記憶裡，資料庫參數存在 Console 頁面上，security group 規則是建立時隨手設的。這些設定沒有被記錄在任何能回溯或重建的地方。</p>
<p>這個結構的操作極限會在兩個時間點浮現：第一次需要在正式環境以外的地方驗證變更時，以及第二個人開始操作同一組資源時。以下依序說明每個階段的操作現實與對應的 infra 需求。</p>
<h2 id="資料庫變更需要驗證環境">資料庫變更需要驗證環境</h2>
<p>應用新增功能時經常需要改資料庫的表結構 — 加欄位、改索引、拆表。這類操作（database migration）如果語法有誤或邏輯有缺，可能導致服務中斷或資料不一致。正常做法是先在非正式環境驗證通過，再推到 production 執行。</p>
<p>單一環境的情況下沒有驗證的場所。三種應對方式各有不同的風險邊界：</p>
<p><strong>直接在 production 執行</strong>。成本最低，風險最高。migration 腳本跑下去的那一刻，正在使用服務的使用者直接承受後果 — 一個鎖住整張大表的 <code>ALTER TABLE</code> 會讓所有查詢卡住，一個 <code>DROP COLUMN</code> 刪錯欄位會造成不可逆的資料遺失。服務規模小、使用者少時代價尚可承受；一旦服務開始承載營收或外部依賴，這個做法的風險代價就超過了它省下的時間。</p>
<p><strong>手動複製一套環境</strong>。到 Console 上照 production 的設定重新建一台 EC2、開一台 RDS、配一組 security group，得到一套「看起來一樣」的 staging。migration 先在 staging 驗證再推 production。這解決了驗證場所的問題，但引入了漂移問題 — 下一節說明。</p>
<p><strong>用程式碼描述環境，讓工具複製</strong>。把 production 的設定寫成描述檔，用 Terraform 或 OpenTofu 根據同一份描述建出 staging。初始成本比手動複製高（要學工具、寫描述檔），但它保證了手動複製保證不了的一件事：staging 和 production 的結構來自同一份描述，差異只存在於刻意不同的參數（機器規格、備份天數）。這就是 Infrastructure as Code（IaC）的起點。</p>
<h2 id="手動複製的環境會漂移">手動複製的環境會漂移</h2>
<p>手動複製的 staging 在建立當天跟 production 一致。一個月後通常不再一致。</p>
<p>漂移的來源是日常操作中的局部調整：staging 的 security group 多了一條規則（某次除錯時加的，事後忘了刪）、production 的 RDS 參數被調過（線上出現慢查詢，DBA 改了 <code>work_mem</code> 但沒同步 staging）、staging 的 IAM role 多了一條 policy（測試新功能時加的，測完沒拿掉）。每一筆差異都很小，小到不值得專門同步，但它們會累積。</p>
<p>漂移引爆的時機跟產生的時機通常隔很遠。一個 migration 在 staging 通過、推到 production 失敗，排查半天後發現是一個月前的參數調整造成的 — staging 的 <code>work_mem</code> 跟 production 不同，剛好影響了這次 migration 的執行計畫。這種因果關係跨越時間的錯誤，排查成本遠高於錯誤本身。</p>
<p>漂移的根源是「兩套環境各自獨立維護」。只要兩份設定各自存在，同步就完全依賴操作者的記憶與紀律，而記憶會衰退、紀律會在壓力下鬆懈。結構性的解法是讓兩套環境共用同一份設定，差異只存在於刻意控制的參數。</p>
<h2 id="同一份描述不同的參數">同一份描述、不同的參數</h2>
<p>IaC 工具消除漂移的方式，是把環境的結構寫成一份 module，用不同的參數值建出不同環境。程式碼只有一份，結構保證相同；差異全部收斂在參數裡，每一處「故意不同」都是明確且可審查的。</p>
<p>一個描述資料庫的 module：</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">variable</span> <span class="s2">&#34;instance_class&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  type</span> <span class="o">=</span> <span class="k">string</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">variable</span> <span class="s2">&#34;backup_retention_days&#34;</span> {
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  type</span>    <span class="o">=</span> <span class="k">number</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">  default</span> <span class="o">=</span> <span class="m">7</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">resource</span> <span class="s2">&#34;aws_db_instance&#34; &#34;main&#34;</span> {
</span></span><span class="line"><span class="ln">11</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">12</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">instance_class</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="k">var</span><span class="p">.</span><span class="k">backup_retention_days</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">}</span></span></code></pre></div><p>Production 傳入大機器和長備份，staging 傳入小機器和短備份：</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"># production
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">module</span> <span class="s2">&#34;database&#34;</span> {
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  source</span>                <span class="o">=</span> <span class="s2">&#34;./modules/database&#34;</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="s2">&#34;db.r6g.large&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  backup_retention_days</span> <span class="o">=</span> <span class="m">14</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">}<span class="c1">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># staging
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="k">module</span> <span class="s2">&#34;database&#34;</span> {
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">  source</span>                <span class="o">=</span> <span class="s2">&#34;./modules/database&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">  instance_class</span>        <span class="o">=</span> <span class="s2">&#34;db.t3.small&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">  backup_retention_days</span> <span class="o">=</span> <span class="m">3</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">}</span></span></code></pre></div><p>兩個環境跑的是同一段 module 程式碼。引擎版本、連線方式、安全設定完全相同（寫在 module 裡、不是參數），差異只有機器規格和備份天數（刻意透過參數控制）。改動 module 一次、兩個環境同時生效，漂移的空間被結構性消除。</p>
<p>IaC 工具會維護一份 state 記錄，追蹤每個環境裡實際建了哪些資源和它們的屬性。改了程式碼後跑 <code>terraform plan</code>，工具會比對新的程式碼和 state 的差異，列出「會新增 / 修改 / 刪除什麼」。確認差異符合預期後才執行 <code>apply</code>。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> 展開，環境的目錄結構與 module 設計在<a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四：環境分離與模組化</a> 展開。</p>
<h2 id="環境分離牽出的後續關注點">環境分離牽出的後續關注點</h2>
<p>環境分離解決了「在哪裡驗證」和「為什麼 staging 跟 production 不同」的問題。但多環境運行後，一組後續的操作需求會依序浮現，每一個對應 infra 的一個能力層：</p>
<p><strong>身分與權限隔離</strong>。三個環境代表三組資源。如果所有人對所有環境都有完整操作權限，一次誤操作就可能改壞 production。production 的修改權限應該比 staging 嚴格、操作身分應該分開。這是<a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二：身分與憑證地基</a>的範圍。</p>
<p><strong>變更審查流程</strong>。多人同時操作 infra 時，沒有經過 review 的變更會互相覆蓋。把 infra 變更接上跟應用程式碼相同的 PR 流程 — 開分支、自動跑 plan、review 通過才 apply — 讓每一次改動都有提案、審查和歷史。這是<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>
<p><strong>機密值管理</strong>。資料庫密碼、API key 這些機密值在有版本控制之前可能直接寫在 <code>.env</code> 或 CI 變數裡。一旦有了 IaC 和 git，這些值如果跟著程式碼進了版本歷史，就會隨著每一次 clone 擴散。機密值要存在專用的密鑰管理服務裡，程式碼只持有指向它的參照。這是<a href="/blog/infra/08-governance-habits/" data-link-title="模組八：治理好習慣 — 規模長大後不失控的最小節奏" data-link-desc="tagging 規範、secrets 不進 code、成本可見性、最小可行節奏，規模長大後不失控">模組八：治理好習慣</a>的範圍。</p>
<p><strong>可觀測性</strong>。三個環境各自需要 log、metric 和告警，這些監控要跟環境本身一起建立，而非等服務中斷後才發現沒有可查的資料。這是<a href="/blog/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性與 log</a> 的範圍。</p>
<p><strong>網路邊界</strong>。三個環境如果共用同一個網段和防火牆規則，staging 的某個被入侵的服務可能橫向觸及 production 的資料庫。每個環境需要有自己的網路邊界。這是<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>
<p>這些關注點的共同根源是同一件事：當服務從單人單環境長成多人多環境，原本藏在記憶和手動操作裡的決策，必須變成可描述、可審查、可重建的規則。整套教材的地圖在<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>：導入 IaC 之前的低成本護欄</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>：state 與 IaC 工具的選型與起步</li>
<li>→ <a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四：環境分離與模組化</a>：目錄結構、module、參數化的完整設計</li>
</ul>
]]></content:encoded></item><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>拿到雲端帳號的第一天</title><link>https://tarrragon.github.io/blog/infra/00-infra-mindset/first-day-with-cloud-account/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/00-infra-mindset/first-day-with-cloud-account/</guid><description>&lt;p>這篇寫給一種特定的讀者：你的專業可能是後端、前端、資料工程或其他領域，但因為組織需要，你被指派處理雲端基礎設施。公司（或主管）給了你一個 AWS / GCP / Azure 帳號，你登入之後看到一個很大的 Console，不確定該做什麼、也不確定動了什麼會出事。&lt;/p>
&lt;p>這是 infra 工作最常見的真實入口。比起從零自學建一套環境，「接到指派、拿到帳號、搞清楚狀況」才是多數工程師第一次碰 infra 的方式。&lt;/p>
&lt;p>這篇用 AWS 為主要範例。GCP 和 Azure 的判讀邏輯相同（安全底線 → 現況盤點 → 路線分流），但具體服務名稱、IAM 模型和 Console 操作位置不同。&lt;/p>
&lt;h2 id="第一小時安全底線">第一小時：安全底線&lt;/h2>
&lt;p>登入帳號後，在做任何其他事之前先完成這些。這些步驟的共同目的是確保帳號的存取控制處於安全狀態——雲端帳號被入侵的代價遠高於本機電腦被入侵，因為雲端資源可以在幾分鐘內被大量建立（產生帳單）或被刪除（資料遺失）。&lt;/p>
&lt;h3 id="確認-root-帳號的-mfa">確認 root 帳號的 MFA&lt;/h3>
&lt;p>Root 帳號是雲端環境的最高權限，能做任何事，包括關閉整個帳號。如果 root 帳號沒有 MFA（Multi-Factor Authentication，多因子驗證），任何拿到 root 密碼的人都能完全控制整個環境。&lt;/p>
&lt;p>確認路徑（AWS）：Console 右上角帳號名稱 → Security credentials → Multi-factor authentication (MFA)。如果顯示「No MFA device」，立刻設定一個——手機 app（Google Authenticator / Authy）或硬體 key（YubiKey）都可以。&lt;/p>
&lt;p>如果你拿到的帳號是公司用 AWS Organizations 開出來的子帳號，子帳號 root 的密碼和 MFA 是獨立的——管理帳號無法代設。子帳號 root 通常需要先用帳號 email 做密碼重置才能首次登入。確認 root MFA 後，日常操作用 IAM Identity Center 登入。&lt;/p>
&lt;h3 id="確認你的登入身分">確認你的登入身分&lt;/h3>
&lt;p>你登入用的是哪種身分？這決定了你的權限範圍和操作方式。&lt;/p>
&lt;p>&lt;strong>IAM user&lt;/strong>：Console 右上角會顯示 &lt;code>username @ account-id&lt;/code>。這是最傳統的登入方式——帳號管理員幫你建了一個使用者，給了你一組帳密。&lt;/p>
&lt;p>&lt;strong>IAM Identity Center（SSO）&lt;/strong>：你透過一個特別的登入頁面（通常是 &lt;code>https://d-xxxxxxxxxx.awsapps.com/start&lt;/code>）登入，然後選擇帳號和角色。這是較新的做法，多帳號組織常用。&lt;/p>
&lt;p>&lt;strong>Root 帳號&lt;/strong>：Console 右上角顯示帳號 email 而非 username。如果你拿到的是 root 帳號的帳密，日常操作應該換成 IAM user 或 SSO 登入——root 帳號只在需要 root-only 操作（如設定 MFA、關閉帳號）時使用。建立 IAM user 的方式見模組一的&lt;a href="https://tarrragon.github.io/blog/infra/01-minimal-iac/iac-tool-state-backend/" data-link-title="IaC 工具選型與 state 地基" data-link-desc="Terraform / OpenTofu / CDK / Pulumi 的選型判準，state 作為 IaC 工具對現實的唯一記憶，以及 remote state backend 的自管與託管路線">動手前的前提&lt;/a>段。&lt;/p>
&lt;h3 id="檢查既存的-access-key">檢查既存的 access key&lt;/h3>
&lt;p>帳號如果被前人用過，可能有暴露風險的 access key——之前的管理員建了 IAM user、生了 key，但那組 key 可能已經寫在某個 Git repo 或環境變數裡而沒有停用。&lt;/p>
&lt;p>確認路徑：Console → IAM → Users → 逐一點每個 user → Security credentials 分頁 → Access keys。檢查每組 key 的狀態（Active / Inactive）和建立時間。超過 90 天未 rotate 的 Active key 是風險——帳號接手後優先 rotate 或停用這些 key。如果帳號裡沒有任何 IAM user，這步跳過。&lt;/p>
&lt;h3 id="確認-cloudtrail-是否開啟">確認 CloudTrail 是否開啟&lt;/h3>
&lt;p>CloudTrail 記錄帳號內所有 API 操作（誰在什麼時間做了什麼）。AWS 預設會開啟 90 天的事件歷史，但長期保存需要建一個 Trail 把 log 寫到 S3。&lt;/p>
&lt;p>確認路徑：Console 搜尋 CloudTrail → Dashboard。如果有 Trail 已建立，表示操作紀錄有長期保存。如果只有預設的 Event history，90 天前的紀錄會消失——這是一個需要但不緊急的改善點，&lt;a href="https://tarrragon.github.io/blog/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性&lt;/a>會展開。&lt;/p></description><content:encoded><![CDATA[<p>這篇寫給一種特定的讀者：你的專業可能是後端、前端、資料工程或其他領域，但因為組織需要，你被指派處理雲端基礎設施。公司（或主管）給了你一個 AWS / GCP / Azure 帳號，你登入之後看到一個很大的 Console，不確定該做什麼、也不確定動了什麼會出事。</p>
<p>這是 infra 工作最常見的真實入口。比起從零自學建一套環境，「接到指派、拿到帳號、搞清楚狀況」才是多數工程師第一次碰 infra 的方式。</p>
<p>這篇用 AWS 為主要範例。GCP 和 Azure 的判讀邏輯相同（安全底線 → 現況盤點 → 路線分流），但具體服務名稱、IAM 模型和 Console 操作位置不同。</p>
<h2 id="第一小時安全底線">第一小時：安全底線</h2>
<p>登入帳號後，在做任何其他事之前先完成這些。這些步驟的共同目的是確保帳號的存取控制處於安全狀態——雲端帳號被入侵的代價遠高於本機電腦被入侵，因為雲端資源可以在幾分鐘內被大量建立（產生帳單）或被刪除（資料遺失）。</p>
<h3 id="確認-root-帳號的-mfa">確認 root 帳號的 MFA</h3>
<p>Root 帳號是雲端環境的最高權限，能做任何事，包括關閉整個帳號。如果 root 帳號沒有 MFA（Multi-Factor Authentication，多因子驗證），任何拿到 root 密碼的人都能完全控制整個環境。</p>
<p>確認路徑（AWS）：Console 右上角帳號名稱 → Security credentials → Multi-factor authentication (MFA)。如果顯示「No MFA device」，立刻設定一個——手機 app（Google Authenticator / Authy）或硬體 key（YubiKey）都可以。</p>
<p>如果你拿到的帳號是公司用 AWS Organizations 開出來的子帳號，子帳號 root 的密碼和 MFA 是獨立的——管理帳號無法代設。子帳號 root 通常需要先用帳號 email 做密碼重置才能首次登入。確認 root MFA 後，日常操作用 IAM Identity Center 登入。</p>
<h3 id="確認你的登入身分">確認你的登入身分</h3>
<p>你登入用的是哪種身分？這決定了你的權限範圍和操作方式。</p>
<p><strong>IAM user</strong>：Console 右上角會顯示 <code>username @ account-id</code>。這是最傳統的登入方式——帳號管理員幫你建了一個使用者，給了你一組帳密。</p>
<p><strong>IAM Identity Center（SSO）</strong>：你透過一個特別的登入頁面（通常是 <code>https://d-xxxxxxxxxx.awsapps.com/start</code>）登入，然後選擇帳號和角色。這是較新的做法，多帳號組織常用。</p>
<p><strong>Root 帳號</strong>：Console 右上角顯示帳號 email 而非 username。如果你拿到的是 root 帳號的帳密，日常操作應該換成 IAM user 或 SSO 登入——root 帳號只在需要 root-only 操作（如設定 MFA、關閉帳號）時使用。建立 IAM user 的方式見模組一的<a href="/blog/infra/01-minimal-iac/iac-tool-state-backend/" data-link-title="IaC 工具選型與 state 地基" data-link-desc="Terraform / OpenTofu / CDK / Pulumi 的選型判準，state 作為 IaC 工具對現實的唯一記憶，以及 remote state backend 的自管與託管路線">動手前的前提</a>段。</p>
<h3 id="檢查既存的-access-key">檢查既存的 access key</h3>
<p>帳號如果被前人用過，可能有暴露風險的 access key——之前的管理員建了 IAM user、生了 key，但那組 key 可能已經寫在某個 Git repo 或環境變數裡而沒有停用。</p>
<p>確認路徑：Console → IAM → Users → 逐一點每個 user → Security credentials 分頁 → Access keys。檢查每組 key 的狀態（Active / Inactive）和建立時間。超過 90 天未 rotate 的 Active key 是風險——帳號接手後優先 rotate 或停用這些 key。如果帳號裡沒有任何 IAM user，這步跳過。</p>
<h3 id="確認-cloudtrail-是否開啟">確認 CloudTrail 是否開啟</h3>
<p>CloudTrail 記錄帳號內所有 API 操作（誰在什麼時間做了什麼）。AWS 預設會開啟 90 天的事件歷史，但長期保存需要建一個 Trail 把 log 寫到 S3。</p>
<p>確認路徑：Console 搜尋 CloudTrail → Dashboard。如果有 Trail 已建立，表示操作紀錄有長期保存。如果只有預設的 Event history，90 天前的紀錄會消失——這是一個需要但不緊急的改善點，<a href="/blog/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性</a>會展開。</p>
<p>現階段只需要確認 CloudTrail 存在，不需要馬上改它。</p>
<h3 id="設定帳單警報">設定帳單警報</h3>
<p>雲端帳單是開放式的——資源跑著就持續產生費用，被入侵後被開出大量資源更可能在幾小時內累積數千美元帳單。設一個帳單警報，超過閾值時收到通知。</p>
<p>設定路徑（AWS）：Console 搜尋 Billing → Budgets → Create budget → Cost budget。設一個月預算（如 $50 或 $100，依你的環境規模），超過 80% 和 100% 時發 email 通知。</p>
<h2 id="帳號現況判讀空帳號還是有東西">帳號現況判讀：空帳號還是有東西？</h2>
<p>安全底線做完後，下一步是搞清楚帳號的現況。這決定了你接下來走哪條路線。</p>
<h3 id="怎麼判斷">怎麼判斷</h3>
<p>EC2 Dashboard 只顯示當前 region 的資源。Console 右上角有 region 選擇器——先切幾個主要 region（us-east-1、ap-northeast-1、ap-southeast-1）看一下，確認資源是否分散在不同 region。</p>
<p>打開 EC2 Dashboard（Console 搜尋 EC2）。如果 Running instances 是 0、沒有 volumes、沒有 security groups（除了 default）——大概率是空帳號。也檢查 Lambda（Console 搜尋 Lambda → Functions）——如果有 function 在跑但 EC2 是空的，可能是 serverless 架構，帳號不是空的。</p>
<p>再看 S3（Console 搜尋 S3）。S3 是全域服務，不分 region。如果沒有 bucket，或只有 CloudTrail 的 log bucket——大概率是空帳號。</p>
<p>如果有正在跑的 EC2 instance、有 Lambda function、有 RDS 資料庫、有 S3 bucket 存著資料——這是一個有東西的帳號，可能是前人建的、可能是其他團隊在用的。</p>
<h3 id="空帳號--從零建置">空帳號 → 從零建置</h3>
<p>帳號是空的，你要從零開始建基礎設施。這是最乾淨的起點。</p>
<p>路線：先讀<a href="/blog/infra/00-infra-mindset/" data-link-title="模組零：infra 是什麼，為什麼 day 1 就要鋪地基" data-link-desc="基礎設施的責任邊界、成熟度階梯，以及地基為什麼總在環境爆炸時才被看見">模組零</a>建立心智模型（什麼是 infra、成熟度階梯），然後照模組一到五的順序走。模組一的<a href="/blog/infra/01-minimal-iac/iac-tool-state-backend/" data-link-title="IaC 工具選型與 state 地基" data-link-desc="Terraform / OpenTofu / CDK / Pulumi 的選型判準，state 作為 IaC 工具對現實的唯一記憶，以及 remote state backend 的自管與託管路線">動手前的前提</a>段會帶你設好本機工具和認證。</p>
<h3 id="有東西的帳號--接手維運">有東西的帳號 → 接手維運</h3>
<p>帳號裡已經有資源在跑。你需要先搞清楚「有什麼」「誰建的」「哪些還在用」，再決定怎麼處理。</p>
<p>路線：讀<a href="/blog/infra/takeover/" data-link-title="接手維運：別人建的環境怎麼接管" data-link-desc="接手前人的專案時，怎麼在不搞壞東西的前提下盤點現況、建立維運能力、逐步正規化 — 從無 SSH 的 FTP 環境到有半套 IaC 的雲端環境都適用">接手維運</a>模組。它按環境類型（全手動的遺留環境、部分有 IaC、多帳號結構）分篇，教你怎麼盤點、怎麼在不搞壞的前提下逐步接管。</p>
<h3 id="不確定--先盤點再說">不確定 → 先盤點再說</h3>
<p>如果帳號裡有東西但你不確定是不是還在用、能不能動，先盤點。以下指令需要 AWS CLI 並完成認證——安裝和 <code>aws configure</code> 設定見模組一的<a href="/blog/infra/01-minimal-iac/iac-tool-state-backend/" data-link-title="IaC 工具選型與 state 地基" data-link-desc="Terraform / OpenTofu / CDK / Pulumi 的選型判準，state 作為 IaC 工具對現實的唯一記憶，以及 remote state backend 的自管與託管路線">前提段</a>（macOS 快速安裝：<code>brew install awscli &amp;&amp; aws configure</code>）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 列出所有 region 的 EC2 instance</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">for</span> region in <span class="k">$(</span>aws ec2 describe-regions --query <span class="s1">&#39;Regions[].RegionName&#39;</span> --output text<span class="k">)</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;=== </span><span class="nv">$region</span><span class="s2"> ===&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  aws ec2 describe-instances --region <span class="s2">&#34;</span><span class="nv">$region</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="se"></span>    --query <span class="s1">&#39;Reservations[].Instances[].[InstanceId,State.Name,Tags[?Key==`Name`].Value|[0]]&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="se"></span>    --output table
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">done</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="c1"># 列出所有 S3 bucket</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">aws s3 ls
</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="c1"># 列出所有 RDS instance</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">aws rds describe-db-instances <span class="se">\
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;DBInstances[].[DBInstanceIdentifier,Engine,DBInstanceStatus]&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="se"></span>  --output table</span></span></code></pre></div><p>這些指令只做讀取，不會改變任何東西。如果輸出很多資源，去讀<a href="/blog/infra/takeover/" data-link-title="接手維運：別人建的環境怎麼接管" data-link-desc="接手前人的專案時，怎麼在不搞壞東西的前提下盤點現況、建立維運能力、逐步正規化 — 從無 SSH 的 FTP 環境到有半套 IaC 的雲端環境都適用">接手維運</a>再決定下一步。如果幾乎是空的，走「從零建置」路線。</p>
<h2 id="雲端-console-的基本導覽">雲端 Console 的基本導覽</h2>
<p>AWS Console 列出幾百個服務，日常 infra 工作常用的集中在以下幾個：</p>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>做什麼</th>
          <th>什麼時候用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>EC2</td>
          <td>虛擬機器（運算）</td>
          <td>看有什麼機器在跑、管 security group</td>
      </tr>
      <tr>
          <td>S3</td>
          <td>物件儲存</td>
          <td>放檔案、放 Terraform state、放 log</td>
      </tr>
      <tr>
          <td>IAM</td>
          <td>身分與權限</td>
          <td>管使用者、角色、權限</td>
      </tr>
      <tr>
          <td>VPC</td>
          <td>虛擬網路</td>
          <td>管網路拓撲、子網路、路由</td>
      </tr>
      <tr>
          <td>RDS</td>
          <td>託管資料庫</td>
          <td>看有沒有資料庫在跑</td>
      </tr>
      <tr>
          <td>CloudWatch</td>
          <td>監控與 log</td>
          <td>看 metric、設 alarm、查 log</td>
      </tr>
      <tr>
          <td>CloudTrail</td>
          <td>操作審計</td>
          <td>查誰做了什麼</td>
      </tr>
      <tr>
          <td>Billing</td>
          <td>帳單</td>
          <td>看花了多少錢</td>
      </tr>
  </tbody>
</table>
<p>Console 左上角的搜尋列可以直接搜服務名稱，不用從選單找。</p>
<p>每個服務在 Console 上的操作都有一個對應的 AWS CLI 指令和 API 呼叫。這個對應關係是 IaC 的基礎——<a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一</a>會教怎麼把 Console 上的操作轉成程式碼。</p>
<h2 id="你接下來該讀什麼">你接下來該讀什麼</h2>
<p>根據你的情境選一條路線：</p>
<table>
  <thead>
      <tr>
          <th>你的情境</th>
          <th>路線</th>
          <th>從哪裡開始</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>完全沒碰過雲端、想先理解概念</td>
          <td>入門認識</td>
          <td><a href="/blog/infra/00-infra-mindset/personal-project-to-infra/" data-link-title="雲端部署裡已經存在的 infra 元件" data-link-desc="VPC、security group、IAM、儲存 — 這些元件在任何雲端部署裡都已經在運作，差別在於有沒有被有意識地管理">個人專案到團隊服務</a></td>
      </tr>
      <tr>
          <td>空帳號、要從零建 infra</td>
          <td>從零建置</td>
          <td><a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一：最小可行 IaC</a></td>
      </tr>
      <tr>
          <td>帳號有東西、要接手維運</td>
          <td>接手前人專案</td>
          <td><a href="/blog/infra/takeover/" data-link-title="接手維運：別人建的環境怎麼接管" data-link-desc="接手前人的專案時，怎麼在不搞壞東西的前提下盤點現況、建立維運能力、逐步正規化 — 從無 SSH 的 FTP 環境到有半套 IaC 的雲端環境都適用">接手維運</a></td>
      </tr>
      <tr>
          <td>手動環境、暫時無法導入 IaC</td>
          <td>還沒有 IaC</td>
          <td><a href="/blog/infra/before-infra/" data-link-title="模組負一：還沒有 infra 的環境怎麼盡量做好" data-link-desc="手動點起家的環境怎麼守底線、降低未來納管成本、辨識何時該開始導入 IaC — 給還沒有能力上 IaC 的真實起點">模組負一：還沒有 infra 的環境</a></td>
      </tr>
      <tr>
          <td>要跟主管解釋為什麼要做 infra</td>
          <td>說服決策者</td>
          <td><a href="/blog/infra/09-driving-adoption/infra-explained-for-non-engineers/" data-link-title="給非工程背景決策者的 infra 說明" data-link-desc="從管理視角解釋基礎設施在解決什麼營運問題、不做的代價、出事怎麼處理，讓參與資源決策的人能判斷投入的優先級">給非工程人員的 infra 說明</a></td>
      </tr>
      <tr>
          <td>拿到一台主機、要從 OS 層連入初始化</td>
          <td>機器初始化</td>
          <td><a href="/blog/linux/install/" data-link-title="Linux 安裝與機器初始化" data-link-desc="在 VM 或新機器從零裝好 Linux、判讀安裝程式選項、驗證最小系統、或要從外部連入跑 bootstrap 時回來讀">Linux 安裝與機器初始化</a></td>
      </tr>
  </tbody>
</table>
<p>如果你不確定自己屬於哪種情境，先做完本篇的「帳號現況判讀」再決定。</p>
]]></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></channel></rss>