<?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>Vpc on Tarragon</title><link>https://tarrragon.github.io/blog/tags/vpc/</link><description>Recent content in Vpc 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/vpc/index.xml" rel="self" type="application/rss+xml"/><item><title>網路地基 — VPC、subnet 分層與 security group 設計</title><link>https://tarrragon.github.io/blog/infra/03-network-foundation/vpc-subnet-security-group/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/03-network-foundation/vpc-subnet-security-group/</guid><description>&lt;p>網路地基要先於核心服務存在。VPC、subnet、route table 與 security group 構成一張「服務能落在哪、誰能跟誰講話」的地圖，資料庫、運算節點與對外入口都得落在這張地圖規劃好的格子裡。先把邊界畫清楚，後面每個核心服務上線時只需要選一塊已經定義好安全等級的位置，而不是邊開服務邊補洞。&lt;/p>
&lt;p>這篇文章建立四層邊界：最外層的 VPC 隔離、中層的 public / private subnet 切分、流量進出的 route table 與 NAT、以及最貼近服務的 security group。每一層解決的問題不同，疊起來才是一個可審計、可收斂的網路。&lt;/p>
&lt;h2 id="vpc網路隔離的最外層邊界">VPC：網路隔離的最外層邊界&lt;/h2>
&lt;p>VPC（Virtual Private Cloud）先圈定整個系統的網路地址空間 — 一塊邏輯隔離的私有網段，是其餘所有網路切分的起點。在 VPC 裡開出來的所有資源預設只看得到同一個 VPC 內的成員，與其他 VPC、與其他帳號的網路天然隔離。它是後面所有切分動作的容器 — 沒有 VPC，subnet 與 security group 無處依附。&lt;/p>
&lt;h3 id="cidr-規劃一次決定事後難改">CIDR 規劃：一次決定、事後難改&lt;/h3>
&lt;p>建立 VPC 時最關鍵的決策是 CIDR 區塊的大小。這個範圍要一次規劃足夠大，因為事後擴張地址空間在多數雲上是麻煩且容易出錯的操作。AWS 雖然允許在 VPC 上追加 secondary CIDR，但追加的網段不能與原有的重疊，也不是所有服務都能自然使用跨 CIDR 的 subnet，routing 的複雜度會因此上升。&lt;/p>
&lt;p>CIDR 規劃要同時考慮三件事。第一是容量：&lt;code>/16&lt;/code> 提供約六萬五千個位址，對多數單一環境的 VPC 足夠寬裕，切成 &lt;code>/24&lt;/code> 的 subnet 也有 256 個可用子網。第二是不重疊：未來若要透過 VPC peering、Transit Gateway 或 VPN 把這個 VPC 接回地端機房或其他環境，重疊的 CIDR 會讓路由無法解析。三個環境各自是 &lt;code>10.0.0.0/16&lt;/code>，在彼此不需要互連時不是問題，但一旦要開 peering 就會撞車 — 這時候改 CIDR 的代價是重建整個 VPC。第三是預留：如果公司同時有多個 VPC（不同環境或不同產品線），用連續但不重疊的大段分配（如 dev &lt;code>10.0.0.0/16&lt;/code>、staging &lt;code>10.1.0.0/16&lt;/code>、prod &lt;code>10.2.0.0/16&lt;/code>）讓路由表更乾淨。&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_vpc&amp;#34; &amp;#34;main&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"> cidr_block&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;10.0.0.0/16&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"> enable_dns_support&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n"> enable_dns_hostnames&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="n"> tags&lt;/span> &lt;span class="o">=&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"> Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;platform-prod&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="n"> Environment&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;production&amp;#34;&lt;/span>
&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>enable_dns_support&lt;/code> 和 &lt;code>enable_dns_hostnames&lt;/code> 在多數場景都該開啟。沒開 DNS hostname 時，EC2 instance 不會拿到可解析的 hostname，某些服務依賴 DNS 尋址而非 IP（如 VPC endpoint 的 private DNS），關著會讓它們靜靜失敗而不報錯。&lt;/p>
&lt;p>判讀訊號：規劃 CIDR 時先問「這個環境三年後會有幾個 subnet、跨幾個可用區、要不要跟其他 VPC 或地端互連」。風險集中在地址耗盡與網段衝突 — 兩者都得在開第一個 subnet 之前定案。VPC 只負責隔離與定址，它不決定哪個服務能對外，那是 subnet 與 security group 的工作。環境之間的 VPC 該怎麼分，是&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;h2 id="public-與-private-subnet-的切分原則">public 與 private subnet 的切分原則&lt;/h2>
&lt;p>一塊資源對外暴露到什麼程度，取決於它被放進哪個 subnet。VPC 內部按可用區與暴露程度切出來的子網段，決定資源有沒有一條通往網際網路的路徑。判斷一個資源該放 public 還是 private，問題只有一個：它需不需要被網際網路直接定址。&lt;/p>
&lt;h3 id="兩類-subnet-的定位">兩類 subnet 的定位&lt;/h3>
&lt;p>public subnet 放的是必須接收外部入站流量的元件 — 對外的負載平衡器、NAT Gateway、堡壘主機（bastion）。這些資源透過 route table 連到 Internet Gateway，因此能被外部 IP 直接觸及。private subnet 放的是只該在內網被存取的元件 — 應用伺服器、資料庫、快取、內部佇列。它們沒有通往 Internet Gateway 的路由，外部無法主動連入，需要對外時才透過 NAT 出去。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Subnet 類型&lt;/th>
 &lt;th>典型住戶&lt;/th>
 &lt;th>對外路徑&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>public&lt;/td>
 &lt;td>對外 LB、NAT Gateway、bastion&lt;/td>
 &lt;td>經 Internet Gateway 雙向&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>private&lt;/td>
 &lt;td>應用節點、資料庫、快取、佇列&lt;/td>
 &lt;td>僅經 NAT 單向出站、不可入站&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>public subnet 的真實樣貌是「薄薄一層」：它通常只住負載平衡器與 NAT 這類入口設施，而不是業務邏輯。常見陷阱是為了 SSH 方便把應用伺服器直接開在 public subnet 並配公網 IP，等於把每一台業務主機的管理埠暴露在掃描流量下 — 全球的 bot 會在秒級頻率對公網 IP 的 22 埠嘗試登入。private subnet 的住戶反而是系統的主體 — 資料庫放這裡是因為它一旦能被外網定址，攻擊面就從「打穿入口層」變成「直接連資料庫埠試密碼」。&lt;/p></description><content:encoded><![CDATA[<p>網路地基要先於核心服務存在。VPC、subnet、route table 與 security group 構成一張「服務能落在哪、誰能跟誰講話」的地圖，資料庫、運算節點與對外入口都得落在這張地圖規劃好的格子裡。先把邊界畫清楚，後面每個核心服務上線時只需要選一塊已經定義好安全等級的位置，而不是邊開服務邊補洞。</p>
<p>這篇文章建立四層邊界：最外層的 VPC 隔離、中層的 public / private subnet 切分、流量進出的 route table 與 NAT、以及最貼近服務的 security group。每一層解決的問題不同，疊起來才是一個可審計、可收斂的網路。</p>
<h2 id="vpc網路隔離的最外層邊界">VPC：網路隔離的最外層邊界</h2>
<p>VPC（Virtual Private Cloud）先圈定整個系統的網路地址空間 — 一塊邏輯隔離的私有網段，是其餘所有網路切分的起點。在 VPC 裡開出來的所有資源預設只看得到同一個 VPC 內的成員，與其他 VPC、與其他帳號的網路天然隔離。它是後面所有切分動作的容器 — 沒有 VPC，subnet 與 security group 無處依附。</p>
<h3 id="cidr-規劃一次決定事後難改">CIDR 規劃：一次決定、事後難改</h3>
<p>建立 VPC 時最關鍵的決策是 CIDR 區塊的大小。這個範圍要一次規劃足夠大，因為事後擴張地址空間在多數雲上是麻煩且容易出錯的操作。AWS 雖然允許在 VPC 上追加 secondary CIDR，但追加的網段不能與原有的重疊，也不是所有服務都能自然使用跨 CIDR 的 subnet，routing 的複雜度會因此上升。</p>
<p>CIDR 規劃要同時考慮三件事。第一是容量：<code>/16</code> 提供約六萬五千個位址，對多數單一環境的 VPC 足夠寬裕，切成 <code>/24</code> 的 subnet 也有 256 個可用子網。第二是不重疊：未來若要透過 VPC peering、Transit Gateway 或 VPN 把這個 VPC 接回地端機房或其他環境，重疊的 CIDR 會讓路由無法解析。三個環境各自是 <code>10.0.0.0/16</code>，在彼此不需要互連時不是問題，但一旦要開 peering 就會撞車 — 這時候改 CIDR 的代價是重建整個 VPC。第三是預留：如果公司同時有多個 VPC（不同環境或不同產品線），用連續但不重疊的大段分配（如 dev <code>10.0.0.0/16</code>、staging <code>10.1.0.0/16</code>、prod <code>10.2.0.0/16</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_vpc&#34; &#34;main&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  cidr_block</span>           <span class="o">=</span> <span class="s2">&#34;10.0.0.0/16&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  enable_dns_support</span>   <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  enable_dns_hostnames</span> <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  tags</span> <span class="o">=</span> {
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">    Name</span>        <span class="o">=</span> <span class="s2">&#34;platform-prod&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">    Environment</span> <span class="o">=</span> <span class="s2">&#34;production&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  }
</span></span><span class="line"><span class="ln">10</span><span class="cl">}</span></span></code></pre></div><p><code>enable_dns_support</code> 和 <code>enable_dns_hostnames</code> 在多數場景都該開啟。沒開 DNS hostname 時，EC2 instance 不會拿到可解析的 hostname，某些服務依賴 DNS 尋址而非 IP（如 VPC endpoint 的 private DNS），關著會讓它們靜靜失敗而不報錯。</p>
<p>判讀訊號：規劃 CIDR 時先問「這個環境三年後會有幾個 subnet、跨幾個可用區、要不要跟其他 VPC 或地端互連」。風險集中在地址耗盡與網段衝突 — 兩者都得在開第一個 subnet 之前定案。VPC 只負責隔離與定址，它不決定哪個服務能對外，那是 subnet 與 security group 的工作。環境之間的 VPC 該怎麼分，是<a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四：環境分離與模組化</a>的主題。</p>
<h2 id="public-與-private-subnet-的切分原則">public 與 private subnet 的切分原則</h2>
<p>一塊資源對外暴露到什麼程度，取決於它被放進哪個 subnet。VPC 內部按可用區與暴露程度切出來的子網段，決定資源有沒有一條通往網際網路的路徑。判斷一個資源該放 public 還是 private，問題只有一個：它需不需要被網際網路直接定址。</p>
<h3 id="兩類-subnet-的定位">兩類 subnet 的定位</h3>
<p>public subnet 放的是必須接收外部入站流量的元件 — 對外的負載平衡器、NAT Gateway、堡壘主機（bastion）。這些資源透過 route table 連到 Internet Gateway，因此能被外部 IP 直接觸及。private subnet 放的是只該在內網被存取的元件 — 應用伺服器、資料庫、快取、內部佇列。它們沒有通往 Internet Gateway 的路由，外部無法主動連入，需要對外時才透過 NAT 出去。</p>
<table>
  <thead>
      <tr>
          <th>Subnet 類型</th>
          <th>典型住戶</th>
          <th>對外路徑</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>public</td>
          <td>對外 LB、NAT Gateway、bastion</td>
          <td>經 Internet Gateway 雙向</td>
      </tr>
      <tr>
          <td>private</td>
          <td>應用節點、資料庫、快取、佇列</td>
          <td>僅經 NAT 單向出站、不可入站</td>
      </tr>
  </tbody>
</table>
<p>public subnet 的真實樣貌是「薄薄一層」：它通常只住負載平衡器與 NAT 這類入口設施，而不是業務邏輯。常見陷阱是為了 SSH 方便把應用伺服器直接開在 public subnet 並配公網 IP，等於把每一台業務主機的管理埠暴露在掃描流量下 — 全球的 bot 會在秒級頻率對公網 IP 的 22 埠嘗試登入。private subnet 的住戶反而是系統的主體 — 資料庫放這裡是因為它一旦能被外網定址，攻擊面就從「打穿入口層」變成「直接連資料庫埠試密碼」。</p>
<h3 id="跨可用區冗餘">跨可用區冗餘</h3>
<p>每個 subnet 綁定單一可用區（Availability Zone）。高可用設計通常是每種角色跨至少兩個可用區各開一個 subnet：兩個 public、兩個 private，讓單一可用區故障時另一區的同類 subnet 還能承接。subnet 的 CIDR 切法要留足空間 — 如果 VPC 是 <code>/16</code>，每個 subnet 用 <code>/20</code>（約四千個位址）可以在三個可用區各開 public + private 共六個 subnet，還有大量空間留給未來擴展。</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">locals</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  azs</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;ap-northeast-1a&#34;, &#34;ap-northeast-1c&#34;, &#34;ap-northeast-1d&#34;</span><span class="p">]</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">resource</span> <span class="s2">&#34;aws_subnet&#34; &#34;public&#34;</span> {
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  for_each</span>          <span class="o">=</span> <span class="k">toset</span><span class="p">(</span><span class="k">local</span><span class="p">.</span><span class="k">azs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">  vpc_id</span>            <span class="o">=</span> <span class="k">aws_vpc</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">  cidr_block</span>        <span class="o">=</span> <span class="k">cidrsubnet</span><span class="p">(</span><span class="k">aws_vpc</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">cidr_block</span><span class="p">,</span> <span class="m">4</span><span class="p">,</span> <span class="k">index</span><span class="p">(</span><span class="k">local</span><span class="p">.</span><span class="k">azs</span><span class="p">,</span> <span class="k">each</span><span class="p">.</span><span class="k">key</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">  availability_zone</span> <span class="o">=</span> <span class="k">each</span><span class="p">.</span><span class="k">key</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</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;public-${each.key}&#34;</span> }
</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">resource</span> <span class="s2">&#34;aws_subnet&#34; &#34;private&#34;</span> {
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">  for_each</span>          <span class="o">=</span> <span class="k">toset</span><span class="p">(</span><span class="k">local</span><span class="p">.</span><span class="k">azs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">  vpc_id</span>            <span class="o">=</span> <span class="k">aws_vpc</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">  cidr_block</span>        <span class="o">=</span> <span class="k">cidrsubnet</span><span class="p">(</span><span class="k">aws_vpc</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">cidr_block</span><span class="p">,</span> <span class="m">4</span><span class="p">,</span> <span class="k">index</span><span class="p">(</span><span class="k">local</span><span class="p">.</span><span class="k">azs</span><span class="p">,</span> <span class="k">each</span><span class="p">.</span><span class="k">key</span><span class="p">)</span> <span class="err">+</span> <span class="k">length</span><span class="p">(</span><span class="k">local</span><span class="p">.</span><span class="k">azs</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">  availability_zone</span> <span class="o">=</span> <span class="k">each</span><span class="p">.</span><span class="k">key</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</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;private-${each.key}&#34;</span> }
</span></span><span class="line"><span class="ln">21</span><span class="cl">}</span></span></code></pre></div><p><code>cidrsubnet</code> 函式自動切分子網段，避免手動計算 CIDR。第二個參數 <code>4</code> 表示在 <code>/16</code> 基礎上加 4 bit 得到 <code>/20</code>，第三個參數是序號。public 與 private 各佔不同序號區間，保證不重疊。</p>
<p>對外入口怎麼把流量分到跨可用區的 private 後端，是 devops 層負載平衡的範圍。這裡只要確保 subnet 的地圖在多 AZ 下對稱。</p>
<h2 id="route-table-與-nat流量的進出路徑">route table 與 NAT：流量的進出路徑</h2>
<p>離開一個 subnet 的封包往哪走，逐條寫在 route table 這組轉送規則裡 — 它掛在 subnet 上，是封包出口方向的依據。一個 subnet 是 public 還是 private，技術上的差別就在它關聯的 route table 裡有沒有一條指向 Internet Gateway 的預設路由。subnet 的對外性質由它關聯的 route table 賦予，而非寫在 subnet 自身的屬性。</p>
<h3 id="public-與-private-的路由差異">public 與 private 的路由差異</h3>
<p>public subnet 的 route table 有一條 <code>0.0.0.0/0 → Internet Gateway</code>，讓未知目的地的流量直接出網、也讓外部可達。private subnet 的 route table 則把 <code>0.0.0.0/0</code> 指向 NAT Gateway。</p>
<p>NAT（Network Address Translation）解決的問題是：private subnet 的資源需要主動對外（拉套件、呼叫第三方 API、抓 OS 更新），但不能因此變得可被外部入站連入。NAT 讓出站流量借用一個公網位址出去、把回應導回原請求者，同時不開放任何外部主動發起的連線。</p>
<h3 id="每-az-一個-nat-vs-共享-nat-的取捨">每 AZ 一個 NAT vs 共享 NAT 的取捨</h3>
<p>NAT Gateway 是綁定單一可用區的資源 — 一個 NAT Gateway 活在某一個 public subnet，也就活在那個可用區裡。這帶來一個架構取捨：</p>
<p><strong>共享 NAT（成本優先）</strong>：全部 private subnet 的 route table 都指向同一個 NAT。用一份 NAT 成本服務整個 VPC，代價是把 NAT 所在的可用區變成出站方向的單點 — 該可用區故障時，所有 private subnet 的對外連線同時中斷，即使其他可用區的節點本身健康。</p>
<p><strong>每 AZ 一個 NAT（可用性優先）</strong>：每個可用區各放一個 NAT Gateway，並讓每一區的 private subnet route table 指向同區的 NAT。出站路徑與 subnet 的跨可用區冗餘對齊，單一 AZ 故障只影響該區。每個 NAT Gateway 的固定月費約 $32 加流量費 $0.045/GB 處理量。三個可用區各一個就是三倍固定費。這筆成本與業務對出站中斷的容忍度對齊——如果單一可用區故障導致全部出站中斷可接受（例如有重試機制），共享 NAT 的成本效益較高。</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_eip&#34; &#34;nat&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  for_each</span> <span class="o">=</span> <span class="k">toset</span><span class="p">(</span><span class="k">local</span><span class="p">.</span><span class="k">azs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  domain</span>   <span class="o">=</span> <span class="s2">&#34;vpc&#34;</span>
</span></span><span class="line"><span class="ln"> 4</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;nat-${each.key}&#34;</span> }
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">}
</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 class="k">resource</span> <span class="s2">&#34;aws_nat_gateway&#34; &#34;per_az&#34;</span> {
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">  for_each</span>      <span class="o">=</span> <span class="k">aws_subnet</span><span class="p">.</span><span class="k">public</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">  allocation_id</span> <span class="o">=</span> <span class="k">aws_eip</span><span class="p">.</span><span class="k">nat</span><span class="p">[</span><span class="k">each</span><span class="p">.</span><span class="k">key</span><span class="p">].</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">  subnet_id</span>     <span class="o">=</span> <span class="k">each</span><span class="p">.</span><span class="k">value</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">11</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;nat-${each.key}&#34;</span> }
</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">resource</span> <span class="s2">&#34;aws_route_table&#34; &#34;private&#34;</span> {
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">  for_each</span> <span class="o">=</span> <span class="k">aws_subnet</span><span class="p">.</span><span class="k">private</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">  vpc_id</span>   <span class="o">=</span> <span class="k">aws_vpc</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="k">route</span> {
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">    cidr_block</span>     <span class="o">=</span> <span class="s2">&#34;0.0.0.0/0&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">    nat_gateway_id</span> <span class="o">=</span> <span class="k">aws_nat_gateway</span><span class="p">.</span><span class="k">per_az</span><span class="p">[</span><span class="k">each</span><span class="p">.</span><span class="k">key</span><span class="p">].</span><span class="k">id</span>
</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></span><span class="line"><span class="ln">23</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;private-rt-${each.key}&#34;</span> }
</span></span><span class="line"><span class="ln">24</span><span class="cl">}
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_route_table_association&#34; &#34;private&#34;</span> {
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="n">  for_each</span>       <span class="o">=</span> <span class="k">aws_subnet</span><span class="p">.</span><span class="k">private</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="n">  subnet_id</span>      <span class="o">=</span> <span class="k">each</span><span class="p">.</span><span class="k">value</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="n">  route_table_id</span> <span class="o">=</span> <span class="k">aws_route_table</span><span class="p">.</span><span class="k">private</span><span class="p">[</span><span class="k">each</span><span class="p">.</span><span class="k">key</span><span class="p">].</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">}</span></span></code></pre></div><p>判讀訊號：private subnet 的服務拉不到外部套件、或第三方 API 全部逾時，先查它關聯的 route table 有沒有指向健康的 NAT；若只有某一個可用區的節點受影響，多半是那一區的 NAT 或其所在 subnet 出狀況。</p>
<h3 id="nat-的成本邊界">NAT 的成本邊界</h3>
<p>NAT Gateway 按處理流量計費（每 GB 一個費率），把大量出站流量長期走 NAT 會讓帳單可觀。常見的高流量場景包括：備份上傳到 S3、跨區資料同步、大量 API 呼叫。對於走向 AWS 自家服務的流量，成本效益較好的做法是用 VPC Endpoint（Gateway 型或 Interface 型）讓流量直連服務、繞過 NAT。S3 與 DynamoDB 的 Gateway Endpoint 是免費的，光是把 S3 備份流量從 NAT 改走 Gateway Endpoint 就能在流量大的環境省下可觀的費用。</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_vpc_endpoint&#34; &#34;s3&#34;</span> {
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">  vpc_id</span>       <span class="o">=</span> <span class="k">aws_vpc</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">  service_name</span> <span class="o">=</span> <span class="s2">&#34;com.amazonaws.ap-northeast-1.s3&#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="n">  route_table_ids</span> <span class="o">=</span> <span class="p">[</span><span class="k">for</span> <span class="k">rt</span> <span class="k">in</span> <span class="k">aws_route_table</span><span class="p">.</span><span class="k">private</span> <span class="err">:</span> <span class="k">rt</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 class="n">  tags</span> <span class="o">=</span><span class="n"> { Name</span> <span class="o">=</span> <span class="s2">&#34;s3-gateway-endpoint&#34;</span> }
</span></span><span class="line"><span class="ln">8</span><span class="cl">}</span></span></code></pre></div><p>NAT 的數量取捨與出站成本的更完整討論在 <a href="/blog/devops/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing、成本監控告警">devops 模組八：成本管理</a>。route table 與 NAT 只管「能不能出去、走哪條路」，至於某個埠允不允許連，是 security group 的職責。</p>
<h2 id="security-group-設計最小開放">security group 設計：最小開放</h2>
<p>一條連線究竟能不能打到某個埠，由 security group 逐埠拍板 — 它是掛在資源網卡（ENI）層級的有狀態防火牆，規則描述的是哪些來源連得進這個資源。它是貼著服務的最後一道網路邊界 — 即使封包順著 route table 抵達了 private subnet，security group 仍能逐埠決定放不放行。「有狀態」的意思是：放行一條入站連線後，對應的回應出站自動允許，規則只需描述入站方向想開放什麼。</p>
<h3 id="用-group-引用取代-ip-範圍">用 group 引用取代 IP 範圍</h3>
<p>設計原則是最小開放：每條規則只開「這個服務確實需要被誰連的那個埠」。資料庫的 security group 入站只允許來自應用層 security group 的資料庫埠，而不是某個 IP 範圍。用 security group 互相引用、而非寫死網段，是因為應用節點會隨擴縮而換 IP — 引用來源 group 讓規則跟著成員身分走、不跟著位址走。</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_security_group&#34; &#34;app&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  name_prefix</span> <span class="o">=</span> <span class="s2">&#34;app-&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  vpc_id</span>      <span class="o">=</span> <span class="k">aws_vpc</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln"> 4</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-sg&#34;</span> }
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">}
</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 class="k">resource</span> <span class="s2">&#34;aws_security_group&#34; &#34;database&#34;</span> {
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">  name_prefix</span> <span class="o">=</span> <span class="s2">&#34;db-&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">  vpc_id</span>      <span class="o">=</span> <span class="k">aws_vpc</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">10</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;db-sg&#34;</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 class="k">resource</span> <span class="s2">&#34;aws_security_group_rule&#34; &#34;db_from_app&#34;</span> {
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">  type</span>                     <span class="o">=</span> <span class="s2">&#34;ingress&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">  from_port</span>                <span class="o">=</span> <span class="m">5432</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">  to_port</span>                  <span class="o">=</span> <span class="m">5432</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">  protocol</span>                 <span class="o">=</span> <span class="s2">&#34;tcp&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">  security_group_id</span>        <span class="o">=</span> <span class="k">aws_security_group</span><span class="p">.</span><span class="k">database</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">  source_security_group_id</span> <span class="o">=</span> <span class="k">aws_security_group</span><span class="p">.</span><span class="k">app</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">}</span></span></code></pre></div><p>這條規則表達的語意是「資料庫只接受來自 app group 成員的 5432 連線」。app 的 instance 數量從 2 台增長到 20 台時，規則本身不需要改 — 新 instance 只要也掛了 <code>app</code> 的 security group 就自動被允許。</p>
<h3 id="00000-的盤點紀律">0.0.0.0/0 的盤點紀律</h3>
<p>把入站來源設成 <code>0.0.0.0/0</code> 等於允許整個網際網路連這個埠。對資料庫埠（5432、3306、6379）或管理埠（22、3389）這麼做，會讓服務暴露在持續性的自動掃描與暴力嘗試下。</p>
<p>合理出現 <code>0.0.0.0/0</code> 的位置只有對外負載平衡器的 80 / 443 入站 — 因為它的工作本來就是接收公開流量。其餘所有 <code>0.0.0.0/0</code> 都該被質疑。</p>
<p>盤點的做法：列出所有 security group，過濾 source 是 <code>0.0.0.0/0</code> 的 ingress rule，逐條問「這個埠確實需要全世界都連得到嗎」。在 CLI 上可以用一條查詢掃：</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">aws ec2 describe-security-groups <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;SecurityGroups[].{
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s1">    ID:GroupId,
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="s1">    Name:GroupName,
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="s1">    OpenPorts:IpPermissions[?IpRanges[?CidrIp==`0.0.0.0/0`]].[FromPort,ToPort]
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="s1">  }&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="se"></span>  --output table</span></span></code></pre></div><p>資料庫埠、SSH、內部 API 出現在這份清單上就是該收斂的目標。管理埠的存取更安全的替代方案是 SSM Session Manager — 它讓你透過 IAM 權限建立 shell session，完全不需要開 22 埠，連線經由 Systems Manager 的控制通道走、不走公網，同時自動留下 session log。誰能透過 IAM 改動這些規則，銜接<a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二：身分與憑證地基</a>。</p>
<p>在 CI 層面，<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>用 tfsec / checkov 做靜態掃描，自動攔截「敏感埠 + 全開 CIDR」的組合，把 security group 的盤點從人工定期做變成每次 PR 自動做。</p>
<p>邊界設備漏洞帶來的教訓同樣適用於 security group 設計。<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/check-point-cve-2024-24919-vpn-info-disclosure/" data-link-title="7.R7.3.12 Check Point 2024：VPN 資訊外洩與會話風險" data-link-desc="邊界設備資訊外洩漏洞可快速轉為憑證與會話濫用風險">Check Point CVE-2024-24919</a> 事件顯示 VPN 邊界設備的資訊外洩漏洞可以直接轉為憑證與會話濫用，攻擊路徑是「邊界入口 → 會話竊取 → 內部橫向擴散」。<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-bleed-2023-session-hijack/" data-link-title="7.R7.3.3 Citrix Bleed 2023：會話被劫持與重放風險" data-link-desc="邊界設備會話資料外洩後，如何演變成帳號與服務風險">Citrix Bleed 2023</a> 則是邊界設備的會話資料外洩導致重放攻擊。這兩個案例的 infra 層教訓是：邊界設備（VPN concentrator、ADC、bastion）的 security group 只開必要的管理埠，且事件後需要全域 session/token 失效流程。</p>
<p>網路控制面的自動化也有風險。<a href="/blog/backend/07-security-data-protection/cases/cloudflare-route-leak-2026/" data-link-title="7.C1 Cloudflare：2026 Route Leak 事件" data-link-desc="BGP 路由政策自動化失誤如何回寫控制面治理。">Cloudflare 2026 Route Leak</a> 事件中，自動化路由政策配置的錯誤導致流量擁塞。infra 層的教訓是：路由與 security group 規則的自動化變更需要 pre-check 與影響範圍評估，且要有快速撤回機制——這正是 <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>的 plan → review → apply 流程要擋的。</p>
<h2 id="nacl-與-security-group-的分工">NACL 與 security group 的分工</h2>
<p>subnet 這一層還有另一道防火牆 — network ACL（NACL），它與 security group 分工在兩個層級。</p>
<table>
  <thead>
      <tr>
          <th>屬性</th>
          <th>Security Group</th>
          <th>NACL</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>掛在哪裡</td>
          <td>資源網卡（ENI）</td>
          <td>Subnet</td>
      </tr>
      <tr>
          <td>狀態</td>
          <td>有狀態（回程自動放行）</td>
          <td>無狀態（回程要另寫規則）</td>
      </tr>
      <tr>
          <td>規則方向</td>
          <td>只寫入站</td>
          <td>入站與出站各寫</td>
      </tr>
      <tr>
          <td>能否 deny</td>
          <td>只能列允許清單</td>
          <td>支援顯式 deny</td>
      </tr>
      <tr>
          <td>評估順序</td>
          <td>所有規則一起評估</td>
          <td>按規則編號順序，命中即停</td>
      </tr>
  </tbody>
</table>
<p>NACL 的特點是無狀態與支援顯式 deny。無狀態意味著放行了入站不代表回應的出站自動放行，回程封包得自己對得上另一條出站規則 — 這讓 NACL 的維護比 security group 複雜。支援顯式 deny 則是它獨有的能力：security group 只能說「誰可以進」，NACL 能說「誰一定不能進」，這在需要 subnet 邊界封鎖特定已知惡意網段時有用。</p>
<p>多數設計的主力是 security group：它貼著服務、用 group 互相引用就能表達「誰能連誰」，已經涵蓋大部分最小開放需求。NACL 留給少數情境 — 需要在 subnet 邊界擋掉一整段已知惡意網段、或要對某類流量做顯式 deny 時才展開。多數環境讓 NACL 維持預設全通、把存取控制集中在 security group，是可以接受的選擇。重點是知道這一層存在、在需要 subnet 層粗篩時記得它。</p>
<h2 id="為什麼網路要先於核心服務鋪好">為什麼網路要先於核心服務鋪好</h2>
<p>網路地基先行，是因為核心服務的安全位置由網路拓樸決定，而不是反過來。資料庫該落在哪個 private subnet、它的 security group 只接受哪個來源、它的出站走不走 NAT — 這些都是服務「出生時」就該確定的屬性。</p>
<p>先有規劃好的 subnet 與 security group，新服務上線只是挑一塊已定義安全等級的位置放進去。網路還沒鋪就先開服務，則往往落在預設 VPC 與寬鬆規則上。預設 VPC 是所有人共享的、CIDR 不可控的、security group 預設全通的 — 把正式服務放在這裡，等於跳過了所有隔離設計。事後再回頭收斂，要在服務已經有流量、有依賴的情況下改網段與防火牆，風險和協調成本都高得多。</p>
<p>這也呼應<a href="/blog/infra/00-infra-mindset/" data-link-title="模組零：infra 是什麼，為什麼 day 1 就要鋪地基" data-link-desc="基礎設施的責任邊界、成熟度階梯，以及地基為什麼總在環境爆炸時才被看見">模組零：infra 是什麼</a>的 day-1 鐵律：邊界與隔離屬於一開始就該存在的地基，不是長出問題後才補的修補。網路規劃好之後，照「從零建置」路線下一步先進<a href="/blog/infra/04-environment-separation/" data-link-title="模組四：環境分離與模組化" data-link-desc="dev / staging / prod 切分、目錄結構 vs workspace、用可重用 module 避免環境漂移">模組四：環境分離與模組化</a>確定環境怎麼切，再讓核心服務落進這些 subnet（見<a href="/blog/infra/05-core-services/" data-link-title="模組五：核心服務上 IaC" data-link-desc="資料庫、運算、儲存、load balancer 怎麼寫進基礎設施程式碼，以及上線順序">模組五：核心服務上 IaC</a>）。</p>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二：身分與憑證地基</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>：環境之間的 VPC 怎麼分</li>
<li>→ <a href="/blog/infra/05-core-services/" data-link-title="模組五：核心服務上 IaC" data-link-desc="資料庫、運算、儲存、load balancer 怎麼寫進基礎設施程式碼，以及上線順序">模組五：核心服務上 IaC</a>：核心服務怎麼落進規劃好的 subnet</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>：tfsec / checkov 自動攔截 security group 全開</li>
<li>→ <a href="/blog/devops/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing、成本監控告警">devops 模組八：成本管理</a>：NAT 與出站流量的成本取捨</li>
<li>→ <a href="/blog/infra/03-network-foundation/security-group-audit-cleanup/" data-link-title="Security Group 稽核與清理" data-link-desc="盤點所有 security group 規則、找出 0.0.0.0/0 全開與未使用的 SG、依賴檢查後安全刪除、自動化治理">Security Group 稽核與清理</a>：SG 規則盤點、未使用 SG 識別、清理工作流</li>
</ul>
]]></content:encoded></item><item><title>模組三：網路地基 — VPC 與分層</title><link>https://tarrragon.github.io/blog/infra/03-network-foundation/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/03-network-foundation/</guid><description>&lt;p>網路地基要先於核心服務存在。VPC、subnet、route table 與 security group 構成一張「服務能落在哪、誰能跟誰講話」的地圖，資料庫、運算節點與對外入口都得落在這張地圖規劃好的格子裡。先把邊界畫清楚，後面每個核心服務上線時只需要選一塊已經定義好安全等級的位置，而不是邊開服務邊補洞。&lt;/p>
&lt;p>這一章建立四層邊界：最外層的 VPC 隔離、中層的 public / private subnet 切分、流量進出的 route table 與 NAT、以及最貼近服務的 security group。每一層解決的問題不同，疊起來才是一個可審計、可收斂的網路。&lt;/p>
&lt;h2 id="vpc網路隔離的最外層邊界">VPC：網路隔離的最外層邊界&lt;/h2>
&lt;p>VPC（Virtual Private Cloud）先圈定整個系統的網路地址空間 — 一塊邏輯隔離的私有網段，是其餘所有網路切分的起點。在 VPC 裡開出來的所有資源預設只看得到同一個 VPC 內的成員，與其他 VPC、與其他帳號的網路天然隔離。它是後面所有切分動作的容器 — 沒有 VPC，subnet 與 security group 無處依附。&lt;/p>
&lt;p>建立 VPC 時最關鍵的決策是 CIDR 區塊的大小，例如 &lt;code>10.0.0.0/16&lt;/code> 提供約六萬五千個位址。這個範圍要一次規劃足夠大，因為事後擴張地址空間在多數雲上是麻煩且容易出錯的操作。同時要避免與公司其他網段重疊：未來若要透過 VPC peering、Transit Gateway 或 VPN 把這個 VPC 接回地端機房或其他環境，重疊的 CIDR 會讓路由無法解析。&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_vpc&amp;#34; &amp;#34;main&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"> cidr_block&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;10.0.0.0/16&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"> enable_dns_support&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n"> enable_dns_hostnames&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="n"> tags&lt;/span> &lt;span class="o">=&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"> Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;platform-main&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="n"> Environment&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;production&amp;#34;&lt;/span>
&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>判讀訊號：規劃 CIDR 時先問「這個環境三年後會有幾個 subnet、跨幾個可用區、要不要接地端」。風險集中在地址耗盡與網段衝突 — 兩者都得在開第一個 subnet 之前定案。邊界是：VPC 只負責隔離與定址，它不決定哪個服務能對外，那是 subnet 與 security group 的工作。環境之間的 VPC 該怎麼分，是「模組四：環境分離與模組化」的主題，這裡只先確保單一 VPC 的地址規劃站得住。&lt;/p>
&lt;h2 id="public-與-private-subnet-的切分原則">public 與 private subnet 的切分原則&lt;/h2>
&lt;p>一塊資源對外暴露到什麼程度，取決於它被放進哪個 subnet — VPC 內部按可用區與暴露程度切出來的子網段，決定資源有沒有一條通往網際網路的路徑。判斷一個資源該放 public 還是 private，問題只有一個：它需不需要被網際網路直接定址。&lt;/p>
&lt;p>public subnet 放的是必須接收外部入站流量的元件 — 對外的負載平衡器、需要公開的 NAT Gateway、堡壘主機（bastion）。這些資源透過 route table 連到 Internet Gateway，因此能被外部 IP 直接觸及。private subnet 放的是只該在內網被存取的元件 — 應用伺服器、資料庫、快取、內部佇列。它們沒有通往 Internet Gateway 的路由，外部無法主動連入，需要對外時才透過 NAT 出去。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Subnet 類型&lt;/th>
 &lt;th>典型住戶&lt;/th>
 &lt;th>對外路徑&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>public&lt;/td>
 &lt;td>對外 LB、NAT Gateway、bastion&lt;/td>
 &lt;td>經 Internet Gateway 雙向&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>private&lt;/td>
 &lt;td>應用節點、資料庫、快取、佇列&lt;/td>
 &lt;td>僅經 NAT 單向出站、不可入站&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>public subnet 的真實樣貌是「薄薄一層」：它通常只住負載平衡器與 NAT 這類入口設施，而不是業務邏輯。常見陷阱是為了 SSH 方便把應用伺服器直接開在 public subnet 並配公網 IP，等於把每一台業務主機的管理埠暴露在掃描流量下。private subnet 的住戶反而是系統的主體 — 資料庫放這裡是因為它一旦能被外網定址，攻擊面就從「打穿入口層」變成「直接連資料庫埠試密碼」。&lt;/p>
&lt;p>每個 subnet 綁定單一可用區，所以高可用設計通常是每種角色跨至少兩個可用區各開一個 subnet：兩個 public、兩個 private，讓單一可用區故障時另一區的同類 subnet 還能承接。對外入口怎麼把流量分到跨可用區的 private 後端，是「devops 模組一：負載平衡」的範圍。&lt;/p>
&lt;h2 id="route-table-與-nat流量的進出路徑">route table 與 NAT：流量的進出路徑&lt;/h2>
&lt;p>離開一個 subnet 的封包往哪走，逐條寫在 route table 這組轉送規則裡 — 它掛在 subnet 上，是封包出口方向的依據。一個 subnet 是 public 還是 private，技術上的差別就在它關聯的 route table 裡有沒有一條指向 Internet Gateway 的預設路由。換句話說，subnet 的對外性質由它關聯的 route table 賦予，而非寫在 subnet 自身。&lt;/p></description><content:encoded><![CDATA[<p>網路地基要先於核心服務存在。VPC、subnet、route table 與 security group 構成一張「服務能落在哪、誰能跟誰講話」的地圖，資料庫、運算節點與對外入口都得落在這張地圖規劃好的格子裡。先把邊界畫清楚，後面每個核心服務上線時只需要選一塊已經定義好安全等級的位置，而不是邊開服務邊補洞。</p>
<p>這一章建立四層邊界：最外層的 VPC 隔離、中層的 public / private subnet 切分、流量進出的 route table 與 NAT、以及最貼近服務的 security group。每一層解決的問題不同，疊起來才是一個可審計、可收斂的網路。</p>
<h2 id="vpc網路隔離的最外層邊界">VPC：網路隔離的最外層邊界</h2>
<p>VPC（Virtual Private Cloud）先圈定整個系統的網路地址空間 — 一塊邏輯隔離的私有網段，是其餘所有網路切分的起點。在 VPC 裡開出來的所有資源預設只看得到同一個 VPC 內的成員，與其他 VPC、與其他帳號的網路天然隔離。它是後面所有切分動作的容器 — 沒有 VPC，subnet 與 security group 無處依附。</p>
<p>建立 VPC 時最關鍵的決策是 CIDR 區塊的大小，例如 <code>10.0.0.0/16</code> 提供約六萬五千個位址。這個範圍要一次規劃足夠大，因為事後擴張地址空間在多數雲上是麻煩且容易出錯的操作。同時要避免與公司其他網段重疊：未來若要透過 VPC peering、Transit Gateway 或 VPN 把這個 VPC 接回地端機房或其他環境，重疊的 CIDR 會讓路由無法解析。</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_vpc&#34; &#34;main&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  cidr_block</span>           <span class="o">=</span> <span class="s2">&#34;10.0.0.0/16&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  enable_dns_support</span>   <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  enable_dns_hostnames</span> <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  tags</span> <span class="o">=</span> {
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">    Name</span>        <span class="o">=</span> <span class="s2">&#34;platform-main&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">    Environment</span> <span class="o">=</span> <span class="s2">&#34;production&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  }
</span></span><span class="line"><span class="ln">10</span><span class="cl">}</span></span></code></pre></div><p>判讀訊號：規劃 CIDR 時先問「這個環境三年後會有幾個 subnet、跨幾個可用區、要不要接地端」。風險集中在地址耗盡與網段衝突 — 兩者都得在開第一個 subnet 之前定案。邊界是：VPC 只負責隔離與定址，它不決定哪個服務能對外，那是 subnet 與 security group 的工作。環境之間的 VPC 該怎麼分，是「模組四：環境分離與模組化」的主題，這裡只先確保單一 VPC 的地址規劃站得住。</p>
<h2 id="public-與-private-subnet-的切分原則">public 與 private subnet 的切分原則</h2>
<p>一塊資源對外暴露到什麼程度，取決於它被放進哪個 subnet — VPC 內部按可用區與暴露程度切出來的子網段，決定資源有沒有一條通往網際網路的路徑。判斷一個資源該放 public 還是 private，問題只有一個：它需不需要被網際網路直接定址。</p>
<p>public subnet 放的是必須接收外部入站流量的元件 — 對外的負載平衡器、需要公開的 NAT Gateway、堡壘主機（bastion）。這些資源透過 route table 連到 Internet Gateway，因此能被外部 IP 直接觸及。private subnet 放的是只該在內網被存取的元件 — 應用伺服器、資料庫、快取、內部佇列。它們沒有通往 Internet Gateway 的路由，外部無法主動連入，需要對外時才透過 NAT 出去。</p>
<table>
  <thead>
      <tr>
          <th>Subnet 類型</th>
          <th>典型住戶</th>
          <th>對外路徑</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>public</td>
          <td>對外 LB、NAT Gateway、bastion</td>
          <td>經 Internet Gateway 雙向</td>
      </tr>
      <tr>
          <td>private</td>
          <td>應用節點、資料庫、快取、佇列</td>
          <td>僅經 NAT 單向出站、不可入站</td>
      </tr>
  </tbody>
</table>
<p>public subnet 的真實樣貌是「薄薄一層」：它通常只住負載平衡器與 NAT 這類入口設施，而不是業務邏輯。常見陷阱是為了 SSH 方便把應用伺服器直接開在 public subnet 並配公網 IP，等於把每一台業務主機的管理埠暴露在掃描流量下。private subnet 的住戶反而是系統的主體 — 資料庫放這裡是因為它一旦能被外網定址，攻擊面就從「打穿入口層」變成「直接連資料庫埠試密碼」。</p>
<p>每個 subnet 綁定單一可用區，所以高可用設計通常是每種角色跨至少兩個可用區各開一個 subnet：兩個 public、兩個 private，讓單一可用區故障時另一區的同類 subnet 還能承接。對外入口怎麼把流量分到跨可用區的 private 後端，是「devops 模組一：負載平衡」的範圍。</p>
<h2 id="route-table-與-nat流量的進出路徑">route table 與 NAT：流量的進出路徑</h2>
<p>離開一個 subnet 的封包往哪走，逐條寫在 route table 這組轉送規則裡 — 它掛在 subnet 上，是封包出口方向的依據。一個 subnet 是 public 還是 private，技術上的差別就在它關聯的 route table 裡有沒有一條指向 Internet Gateway 的預設路由。換句話說，subnet 的對外性質由它關聯的 route table 賦予，而非寫在 subnet 自身。</p>
<p>public subnet 的 route table 有一條 <code>0.0.0.0/0 → Internet Gateway</code>，讓未知目的地的流量直接出網、也讓外部可達。private subnet 的 route table 則把 <code>0.0.0.0/0</code> 指向 NAT Gateway。NAT（Network Address Translation）解決的問題是：private subnet 的資源需要主動對外（拉套件、呼叫第三方 API、抓 OS 更新），但不能因此變得可被外部入站連入。NAT 讓出站流量借用一個公網位址出去、把回應導回原請求者，同時不開放任何外部主動發起的連線。</p>
<p>NAT Gateway 的核心取捨是成本與可用性。它是綁定單一可用區的資源 — 一個 NAT Gateway 活在某一個 public subnet、也就活在那個可用區裡。若全部 private subnet 的 route table 都指向同一個 NAT，這個設計用一份 NAT 成本服務整個 VPC，代價是把 NAT 所在的可用區變成出站方向的單點：該可用區故障時，所有 private subnet 的對外連線同時中斷，即使其他可用區的節點本身健康。要讓出站路徑與 subnet 的跨可用區冗餘對齊，做法是每個可用區各放一個 NAT Gateway，並讓每一區的 private subnet route table 指向同區的 NAT。下面用 <code>for_each</code> 在每個可用區建立一個 NAT，再讓每個 private subnet 的 route table 走本區出口。</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_nat_gateway&#34; &#34;per_az&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  for_each</span>      <span class="o">=</span> <span class="k">aws_subnet</span><span class="p">.</span><span class="k">public</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  allocation_id</span> <span class="o">=</span> <span class="k">aws_eip</span><span class="p">.</span><span class="k">nat</span><span class="p">[</span><span class="k">each</span><span class="p">.</span><span class="k">key</span><span class="p">].</span><span class="k">id</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  subnet_id</span>     <span class="o">=</span> <span class="k">each</span><span class="p">.</span><span class="k">value</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">}
</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 class="k">resource</span> <span class="s2">&#34;aws_route_table&#34; &#34;private&#34;</span> {
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">  for_each</span> <span class="o">=</span> <span class="k">aws_subnet</span><span class="p">.</span><span class="k">private</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">  vpc_id</span>   <span class="o">=</span> <span class="k">aws_vpc</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="k">route</span> {
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">    cidr_block</span>     <span class="o">=</span> <span class="s2">&#34;0.0.0.0/0&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">    nat_gateway_id</span> <span class="o">=</span> <span class="k">aws_nat_gateway</span><span class="p">.</span><span class="k">per_az</span><span class="p">[</span><span class="k">each</span><span class="p">.</span><span class="k">key</span><span class="p">].</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  }
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</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;private-rt-${each.key}&#34;</span> }
</span></span><span class="line"><span class="ln">17</span><span class="cl">}</span></span></code></pre></div><p>每個可用區一個 NAT 是可用性優先的版本；若環境對成本敏感、且能接受出站在單一可用區故障時短暫中斷，也可以退回單一 NAT，但要把它當成明示的取捨、而非預設。判讀訊號：private subnet 的服務拉不到外部套件、或第三方 API 全部逾時，先查它關聯的 route table 有沒有指向健康的 NAT；若只有某一個可用區的節點受影響，多半是那一區的 NAT 或其所在 subnet 出狀況。風險與成本在這裡交會 — NAT Gateway 按處理流量計費，把大量出站流量（例如備份上傳、跨區同步）長期走 NAT 會讓帳單可觀，這類流量較划算的做法是改走 VPC Endpoint 直連雲服務、繞過 NAT。NAT 的數量取捨與出站成本在「devops 模組八：成本管理」有更完整的討論。邊界是：route table 與 NAT 只管「能不能出去、走哪條路」，至於某個埠允不允許連，是 security group 的職責。</p>
<h2 id="security-group-設計最小開放">security group 設計：最小開放</h2>
<p>一條連線究竟能不能打到某個埠，由 security group 逐埠拍板 — 它是掛在資源網卡層級的有狀態防火牆，規則描述的是哪些來源連得進這個資源。它是貼著服務的最後一道網路邊界 — 即使封包順著 route table 抵達了 private subnet，security group 仍能逐埠決定放不放行。它有狀態的意思是：放行一條入站連線後，對應的回應出站自動允許，規則只需描述入站方向想開放什麼。</p>
<p>設計原則是最小開放：每條規則只開「這個服務確實需要被誰連的那個埠」。資料庫的 security group 入站只允許來自應用層 security group 的資料庫埠，而不是某個 IP 範圍。用 security group 互相引用、而非寫死網段，是因為應用節點會隨擴縮而換 IP，引用來源 group 讓規則跟著成員身分走、不跟著位址走。</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_security_group_rule&#34; &#34;db_from_app&#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="s2">&#34;ingress&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">  from_port</span>                <span class="o">=</span> <span class="m">5432</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">  to_port</span>                  <span class="o">=</span> <span class="m">5432</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">  protocol</span>                 <span class="o">=</span> <span class="s2">&#34;tcp&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">  security_group_id</span>        <span class="o">=</span> <span class="k">aws_security_group</span><span class="p">.</span><span class="k">database</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">  source_security_group_id</span> <span class="o">=</span> <span class="k">aws_security_group</span><span class="p">.</span><span class="k">app</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">}</span></span></code></pre></div><p>要特別防的是 <code>0.0.0.0/0</code> 全開。把入站來源設成 <code>0.0.0.0/0</code> 等於允許整個網際網路連這個埠，對資料庫埠（5432、3306、6379）或管理埠（22、3389）這麼做，會讓服務暴露在持續性的自動掃描與暴力嘗試下。合理出現 <code>0.0.0.0/0</code> 的位置只有對外負載平衡器的 80 / 443 入站 — 因為它的工作本來就是接收公開流量。判讀訊號：盤點所有 security group，列出 source 是 <code>0.0.0.0/0</code> 的規則，逐條問「這個埠真的需要全世界都連得到嗎」；資料庫埠、SSH、內部 API 出現在這份清單上就是該收斂的目標。管理埠的存取較划算的替代方案是 SSM Session Manager 或堡壘主機，把 22 埠從公網清單上拿掉。誰能透過 IAM 改動這些規則，銜接「模組二：身分與憑證地基」。</p>
<p>subnet 這一層還有另一道防火牆 — network ACL（NACL），它與 security group 分工在兩個層級。NACL 掛在 subnet 上、作用於進出整個 subnet 的流量，而且是無狀態的：入站與出站要各寫一條規則，放行了入站不代表回應的出站自動放行，回程封包得自己對得上另一條規則。security group 則掛在資源網卡（ENI）層、有狀態，放行入站後對應回應自動允許。兩者的另一個差別是 NACL 支援顯式 deny、security group 只能列允許清單，所以 NACL 適合做 subnet 層的粗篩或針對特定來源的明確封鎖。實務上多數設計的主力是 security group：它貼著服務、用 group 互相引用就能表達「誰能連誰」，已經涵蓋大部分最小開放需求。NACL 留給少數情境 — 需要在 subnet 邊界擋掉一整段已知惡意網段、或要對某類流量做顯式 deny 時才展開；多數環境讓 NACL 維持預設全通、把存取控制集中在 security group，是可以接受的選擇，重點是知道這一層存在、在需要 subnet 層粗篩時記得它。</p>
<h2 id="為什麼網路要先於核心服務鋪好">為什麼網路要先於核心服務鋪好</h2>
<p>網路地基先行，是因為核心服務的安全位置由網路拓樸決定，而不是反過來。資料庫該落在哪個 private subnet、它的 security group 只接受哪個來源、它的出站走不走 NAT — 這些都是服務「出生時」就該確定的屬性。先有規劃好的 subnet 與 security group，新服務上線只是挑一塊已定義安全等級的位置放進去；網路還沒鋪就先開服務，則往往落在預設 VPC 與寬鬆規則上，事後再回頭收斂，要在服務已經有流量、有依賴的情況下改網段與防火牆，風險和協調成本都高得多。</p>
<p>這也呼應「模組零：infra 是什麼」的 day-1 鐵律：邊界與隔離屬於一開始就該存在的地基，不是長出問題後才補的修補。網路規劃好之後，照「從零建置」路線下一步先進「模組四：環境分離與模組化」確定環境怎麼切，再讓核心服務落進這些 subnet。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/infra/03-network-foundation/vpc-subnet-security-group/" data-link-title="網路地基 — VPC、subnet 分層與 security group 設計" data-link-desc="VPC CIDR 規劃、public / private subnet 切分、route table 與 NAT 的可用性成本取捨、security group 最小開放設計，以及 NACL 的定位">網路地基 — VPC、subnet 分層與 security group 設計</a></td>
          <td>VPC CIDR 規劃、public / private subnet 切分、route table 與 NAT 的可用性成本取捨、security group 最小開放設計與 NACL 定位</td>
      </tr>
      <tr>
          <td><a href="/blog/infra/03-network-foundation/security-group-audit-cleanup/" data-link-title="Security Group 稽核與清理" data-link-desc="盤點所有 security group 規則、找出 0.0.0.0/0 全開與未使用的 SG、依賴檢查後安全刪除、自動化治理">Security Group 稽核與清理</a></td>
          <td>0.0.0.0/0 偵測、未使用 SG 識別、依賴檢查、清理工作流、自動化治理</td>
      </tr>
  </tbody>
</table>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二：身分與憑證地基</a>：誰有權改動 security group 與路由表</li>
<li>→ <a href="/blog/infra/05-core-services/" data-link-title="模組五：核心服務上 IaC" data-link-desc="資料庫、運算、儲存、load balancer 怎麼寫進基礎設施程式碼，以及上線順序">模組五：核心服務上 IaC</a>：核心服務怎麼落進規劃好的 subnet</li>
<li>→ <a href="/blog/devops/01-load-balancing/" data-link-title="模組一：負載平衡與反向代理" data-link-desc="流量進來怎麼分給多個服務實例 — nginx / HAProxy / DNS round-robin 的選型和健康檢查路由設計">devops 模組一：負載平衡</a>：入口流量怎麼分到 private subnet 的後端</li>
<li>→ <a href="/blog/devops/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing、成本監控告警">devops 模組八：成本管理</a>：NAT 與出站流量的成本取捨</li>
</ul>
]]></content:encoded></item><item><title>VPC（Virtual Private Cloud）</title><link>https://tarrragon.github.io/blog/infra/knowledge-cards/vpc/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/knowledge-cards/vpc/</guid><description>&lt;p>VPC（Virtual Private Cloud）是雲端帳號內的一塊邏輯隔離私有網段，是其餘所有網路切分的起點。在 VPC 裡開出來的所有資源預設只看得到同一個 VPC 內的成員，與其他 VPC、與其他帳號的網路天然隔離。沒有 VPC，&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">security group&lt;/a> 無處依附。&lt;/p>
&lt;p>VPC 用 &lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/cidr/" data-link-title="CIDR（Classless Inter-Domain Routing）" data-link-desc="用前綴長度表示 IP 地址範圍的表示法，決定 VPC 與 subnet 的地址空間大小">CIDR&lt;/a> 區塊定義地址空間。建立時的 CIDR 大小是一次性決策——事後擴張地址空間在多數雲端平台上是麻煩且容易出錯的操作（AWS 允許追加 secondary CIDR，但追加的網段在 routing 與服務相容性上有限制）。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>VPC 是&lt;a href="https://tarrragon.github.io/blog/infra/03-network-foundation/vpc-subnet-security-group/" data-link-title="網路地基 — VPC、subnet 分層與 security group 設計" data-link-desc="VPC CIDR 規劃、public / private subnet 切分、route table 與 NAT 的可用性成本取捨、security group 最小開放設計，以及 NACL 的定位">模組三：網路地基&lt;/a>的最外層邊界。Infra 系列的網路設計從 VPC 開始：先圈定地址空間，再往內切 subnet、掛 route table、設 security group。環境之間的 VPC 怎麼分（每個環境一個 VPC），屬於&lt;a href="https://tarrragon.github.io/blog/infra/04-environment-separation/directory-module-parameterization/" data-link-title="環境分離與模組化 — 目錄結構、module 參數化與 retrofit 路徑" data-link-desc="用目錄結構在第一天就隔開 dev 與 prod 的 state，用 module 讓環境共用同一套邏輯只差參數，以及已經單環境跑起來後怎麼安全拆分">模組四：環境分離&lt;/a>的設計決策。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>VPC 設計需要關注的訊號：CIDR 空間快用完（subnet 切不出新的子網段）、需要跟其他 VPC 或地端互連時發現 CIDR 重疊（peering 無法建立）、服務被放在預設 VPC 裡（預設 VPC 是所有人共享的、CIDR 不可控的、security group 預設全通的）。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>規劃 VPC 時要決定：&lt;/p>
&lt;ul>
&lt;li>CIDR 大小：&lt;code>/16&lt;/code> 提供約六萬五千個位址，對多數單一環境足夠&lt;/li>
&lt;li>不重疊：多個 VPC（不同環境或產品線）用連續但不重疊的大段分配&lt;/li>
&lt;li>DNS 設定：&lt;code>enable_dns_support&lt;/code> 和 &lt;code>enable_dns_hostnames&lt;/code> 在多數場景都該開啟&lt;/li>
&lt;li>預設 VPC 的處理：正式服務不該放在預設 VPC，新帳號的預設 VPC 可以刪除或保留唯讀&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> — VPC 內按可用區與暴露程度切出的子網段&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — 掛在資源上的有狀態防火牆&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/cidr/" data-link-title="CIDR（Classless Inter-Domain Routing）" data-link-desc="用前綴長度表示 IP 地址範圍的表示法，決定 VPC 與 subnet 的地址空間大小">CIDR&lt;/a> — VPC 的地址空間定義方式&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="讓 private subnet 的資源主動對外連線、同時不被外部入站觸及的網路地址轉換服務">NAT&lt;/a> — 讓 private subnet 出站的地址轉換機制&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>VPC（Virtual Private Cloud）是雲端帳號內的一塊邏輯隔離私有網段，是其餘所有網路切分的起點。在 VPC 裡開出來的所有資源預設只看得到同一個 VPC 內的成員，與其他 VPC、與其他帳號的網路天然隔離。沒有 VPC，<a href="/blog/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet</a> 與 <a href="/blog/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">security group</a> 無處依附。</p>
<p>VPC 用 <a href="/blog/infra/knowledge-cards/cidr/" data-link-title="CIDR（Classless Inter-Domain Routing）" data-link-desc="用前綴長度表示 IP 地址範圍的表示法，決定 VPC 與 subnet 的地址空間大小">CIDR</a> 區塊定義地址空間。建立時的 CIDR 大小是一次性決策——事後擴張地址空間在多數雲端平台上是麻煩且容易出錯的操作（AWS 允許追加 secondary CIDR，但追加的網段在 routing 與服務相容性上有限制）。</p>
<h2 id="概念位置">概念位置</h2>
<p>VPC 是<a href="/blog/infra/03-network-foundation/vpc-subnet-security-group/" data-link-title="網路地基 — VPC、subnet 分層與 security group 設計" data-link-desc="VPC CIDR 規劃、public / private subnet 切分、route table 與 NAT 的可用性成本取捨、security group 最小開放設計，以及 NACL 的定位">模組三：網路地基</a>的最外層邊界。Infra 系列的網路設計從 VPC 開始：先圈定地址空間，再往內切 subnet、掛 route table、設 security group。環境之間的 VPC 怎麼分（每個環境一個 VPC），屬於<a href="/blog/infra/04-environment-separation/directory-module-parameterization/" data-link-title="環境分離與模組化 — 目錄結構、module 參數化與 retrofit 路徑" data-link-desc="用目錄結構在第一天就隔開 dev 與 prod 的 state，用 module 讓環境共用同一套邏輯只差參數，以及已經單環境跑起來後怎麼安全拆分">模組四：環境分離</a>的設計決策。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>VPC 設計需要關注的訊號：CIDR 空間快用完（subnet 切不出新的子網段）、需要跟其他 VPC 或地端互連時發現 CIDR 重疊（peering 無法建立）、服務被放在預設 VPC 裡（預設 VPC 是所有人共享的、CIDR 不可控的、security group 預設全通的）。</p>
<h2 id="設計責任">設計責任</h2>
<p>規劃 VPC 時要決定：</p>
<ul>
<li>CIDR 大小：<code>/16</code> 提供約六萬五千個位址，對多數單一環境足夠</li>
<li>不重疊：多個 VPC（不同環境或產品線）用連續但不重疊的大段分配</li>
<li>DNS 設定：<code>enable_dns_support</code> 和 <code>enable_dns_hostnames</code> 在多數場景都該開啟</li>
<li>預設 VPC 的處理：正式服務不該放在預設 VPC，新帳號的預設 VPC 可以刪除或保留唯讀</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> — VPC 內按可用區與暴露程度切出的子網段</li>
<li><a href="/blog/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — 掛在資源上的有狀態防火牆</li>
<li><a href="/blog/infra/knowledge-cards/cidr/" data-link-title="CIDR（Classless Inter-Domain Routing）" data-link-desc="用前綴長度表示 IP 地址範圍的表示法，決定 VPC 與 subnet 的地址空間大小">CIDR</a> — VPC 的地址空間定義方式</li>
<li><a href="/blog/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="讓 private subnet 的資源主動對外連線、同時不被外部入站觸及的網路地址轉換服務">NAT</a> — 讓 private subnet 出站的地址轉換機制</li>
</ul>
]]></content:encoded></item></channel></rss>