<?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 知識卡 on Tarragon</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/</link><description>Recent content in Infra 知識卡 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 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/index.xml" rel="self" type="application/rss+xml"/><item><title>Infrastructure as Code (IaC)</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/</guid><description>&lt;p>Infrastructure as Code（IaC）的核心概念是用版本控制的程式碼描述基礎設施應該長什麼樣，再由工具負責比對「程式碼描述的目標狀態」與「雲端上的實際狀態」，算出差異並收斂。這個機制把基礎設施從「某個人在 Console 手動點出來的東西」變成「可版本控制、可 review、可重建的描述」——最常見的落地語言是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL&lt;/a>。&lt;/p>
&lt;p>IaC 工具分兩條路線：宣告式 DSL（Terraform / OpenTofu，用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL&lt;/a> 描述資源）與程式語言（AWS CDK / Pulumi，用 TypeScript / Python / Go 生成資源）。兩者都能達成「用程式碼描述、由工具收斂」的目標，差別在閱讀門檻與抽象能力。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>IaC 是 infra 系列的根概念，貫穿所有模組。&lt;a href="https://tarrragon.github.io/blog/devops/infra/00-infra-mindset/infra-responsibility-maturity/" data-link-title="infra 的責任邊界、成熟度階梯與 day 1 鐵律" data-link-desc="基礎設施承擔五個面向的責任，每一面都有獨立的失效模式；成熟度階梯用來對齊現況而非追求滿分，day 1 鐵律則劃出早期團隊該優先鋪的地基">成熟度階梯&lt;/a>的第二階（宣告式 IaC）是 IaC 正式生效的起點，第三階（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/environment-separation/" data-link-title="Environment Separation（環境分離）" data-link-desc="把同一套基礎設施定義複製成多份隔離的執行實例，各有獨立 state 與故障半徑">環境分離&lt;/a>）和第四階（PR 流程治理）都建立在 IaC 之上。沒有 IaC，後續所有模組的能力都無法落地。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要 IaC 的訊號是規模與協作的函數：環境數量超過一套、多人同時改資源、環境事故頻率上升、外部稽核要求變更紀錄。詳見&lt;a href="https://tarrragon.github.io/blog/devops/infra/before-infra/manual-environment-baseline/" data-link-title="手動環境的可控底線與納管準備" data-link-desc="還沒有 IaC 的環境怎麼守住底線、讓變更可追溯、降低未來納管成本，以及辨識何時該開始導入 IaC">模組負一：該開始導入 IaC 的訊號&lt;/a>。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>IaC 要讓程式碼成為基礎設施的 source of truth，採用時的決定分別處理程式碼用什麼寫、state 放在哪裡、誰還能繞過程式碼直接改、以及哪些資源先納管：&lt;/p>
&lt;ul>
&lt;li>工具選型：宣告式 DSL vs 程式語言，取捨在審查透明度 vs 抽象複用能力&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State&lt;/a> 的存放：remote backend 的選擇與保護&lt;/li>
&lt;li>Console 唯讀紀律：所有寫入操作回到程式碼，Console 只作觀察&lt;/li>
&lt;li>納管範圍：哪些資源先進 IaC、哪些暫時留在手動&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State&lt;/a> — IaC 工具追蹤現實的記憶機制&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift&lt;/a> — state 與現實不一致時的狀態&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/environment-separation/" data-link-title="Environment Separation（環境分離）" data-link-desc="把同一套基礎設施定義複製成多份隔離的執行實例，各有獨立 state 與故障半徑">環境分離&lt;/a> — 同一份 IaC 描述套用到多環境&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">Configuration Management（組態管理）&lt;/a> — IaC 建出主機之後，主機內部的套件、設定檔與服務由組態管理工具管&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Infrastructure as Code（IaC）的核心概念是用版本控制的程式碼描述基礎設施應該長什麼樣，再由工具負責比對「程式碼描述的目標狀態」與「雲端上的實際狀態」，算出差異並收斂。這個機制把基礎設施從「某個人在 Console 手動點出來的東西」變成「可版本控制、可 review、可重建的描述」——最常見的落地語言是 <a href="/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL</a>。</p>
<p>IaC 工具分兩條路線：宣告式 DSL（Terraform / OpenTofu，用 <a href="/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL</a> 描述資源）與程式語言（AWS CDK / Pulumi，用 TypeScript / Python / Go 生成資源）。兩者都能達成「用程式碼描述、由工具收斂」的目標，差別在閱讀門檻與抽象能力。</p>
<h2 id="概念位置">概念位置</h2>
<p>IaC 是 infra 系列的根概念，貫穿所有模組。<a href="/blog/devops/infra/00-infra-mindset/infra-responsibility-maturity/" data-link-title="infra 的責任邊界、成熟度階梯與 day 1 鐵律" data-link-desc="基礎設施承擔五個面向的責任，每一面都有獨立的失效模式；成熟度階梯用來對齊現況而非追求滿分，day 1 鐵律則劃出早期團隊該優先鋪的地基">成熟度階梯</a>的第二階（宣告式 IaC）是 IaC 正式生效的起點，第三階（<a href="/blog/devops/infra/knowledge-cards/environment-separation/" data-link-title="Environment Separation（環境分離）" data-link-desc="把同一套基礎設施定義複製成多份隔離的執行實例，各有獨立 state 與故障半徑">環境分離</a>）和第四階（PR 流程治理）都建立在 IaC 之上。沒有 IaC，後續所有模組的能力都無法落地。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要 IaC 的訊號是規模與協作的函數：環境數量超過一套、多人同時改資源、環境事故頻率上升、外部稽核要求變更紀錄。詳見<a href="/blog/devops/infra/before-infra/manual-environment-baseline/" data-link-title="手動環境的可控底線與納管準備" data-link-desc="還沒有 IaC 的環境怎麼守住底線、讓變更可追溯、降低未來納管成本，以及辨識何時該開始導入 IaC">模組負一：該開始導入 IaC 的訊號</a>。</p>
<h2 id="設計責任">設計責任</h2>
<p>IaC 要讓程式碼成為基礎設施的 source of truth，採用時的決定分別處理程式碼用什麼寫、state 放在哪裡、誰還能繞過程式碼直接改、以及哪些資源先納管：</p>
<ul>
<li>工具選型：宣告式 DSL vs 程式語言，取捨在審查透明度 vs 抽象複用能力</li>
<li><a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State</a> 的存放：remote backend 的選擇與保護</li>
<li>Console 唯讀紀律：所有寫入操作回到程式碼，Console 只作觀察</li>
<li>納管範圍：哪些資源先進 IaC、哪些暫時留在手動</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State</a> — IaC 工具追蹤現實的記憶機制</li>
<li><a href="/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift</a> — state 與現實不一致時的狀態</li>
<li><a href="/blog/devops/infra/knowledge-cards/environment-separation/" data-link-title="Environment Separation（環境分離）" data-link-desc="把同一套基礎設施定義複製成多份隔離的執行實例，各有獨立 state 與故障半徑">環境分離</a> — 同一份 IaC 描述套用到多環境</li>
<li><a href="/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">Configuration Management（組態管理）</a> — IaC 建出主機之後，主機內部的套件、設定檔與服務由組態管理工具管</li>
</ul>
]]></content:encoded></item><item><title>State（IaC 狀態檔）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/</guid><description>&lt;p>State 是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> 工具用來記錄「上一次 apply 之後，每個資源在雲端長什麼樣」的快照。它的作用是讓工具能算出「程式碼描述的目標」與「雲端上的現況」之間的最小差異。沒有 state，工具每次都得把所有資源重新查一遍才知道該不該動，而且無法分辨「這個資源是我建的、該由我管」還是「別人手動建的、不歸我管」。&lt;/p>
&lt;p>State 裡通常含有資源的真實 ID、相依關係，以及部分敏感屬性（例如資料庫的初始密碼、private key 的輸出值）。這帶來兩條硬邊界：state 不能進 git（含敏感值，推進版控等於把密碼寫進每個 clone 的歷史）、state 不能只放本地（本地 state 的失敗模式是記憶綁在一台筆電上，多人並行 apply 會互相覆蓋）。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>State 是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> 的記憶機制。&lt;a href="https://tarrragon.github.io/blog/devops/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 的自管與託管路線">模組一：最小可行 IaC&lt;/a> 的核心主題就是怎麼把 state 管好——remote backend、加密、鎖機制。State 管不好，後續所有 IaC 操作都建立在不可靠的記憶上。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>State 出問題的訊號包括：&lt;code>terraform plan&lt;/code> 顯示大量非預期的變更（state 與現實不一致）、兩個人同時 apply 後環境出現矛盾狀態、&lt;code>state list&lt;/code> 的資源數與 Console 上看到的不一致。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>state 的設定裡事後最難改的是分割策略：資源分進不同的 state 之後，要移動就得在兩份 state 之間逐個資源遷移。存放位置、加密、版本保留與鎖機制管的是 state 的安全與復原：防止兩次 apply 同時寫入、限制外洩的範圍、在損壞後回捲到上一個版本：&lt;/p>
&lt;ul>
&lt;li>存放位置：S3 + DynamoDB（自管）vs Terraform Cloud（託管），取捨在維運負擔 vs 控制權&lt;/li>
&lt;li>加密：state 含敏感值，落地加密（S3 SSE）是底線&lt;/li>
&lt;li>版本保留：bucket versioning 讓 state 損壞時能回捲到上一個正確版本&lt;/li>
&lt;li>鎖機制：防止兩個人同時 apply 互相覆蓋&lt;/li>
&lt;li>分割策略：一個大 state vs 多個小 state，取捨在引用便利性 vs 影響範圍控制&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> — state 是 IaC 工具的核心依賴&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift&lt;/a> — state 與現實的落差&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">Configuration Management（組態管理）&lt;/a> — 由 Terraform 的輸出產生 Ansible 的 inventory 時，那份檔案的內容記在 state 裡&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/hands-on/multi-host-manifest-walkthrough/" data-link-title="多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理" data-link-desc="要照著部署多台主機的維護清單並執行檢查腳本，或要讀跨主機執行時 ansible 的 exit code 與每台的 rc、門檻造成的 WARN、清單格式錯與連不上的主機各自印出什麼時，或要確認 Terraform 寫出的 inventory 的檔案權限、`--check` 預覽與實際部署結果不同的情形時，查在可拋棄的容器上實際跑過的指令、輸出與遇到的錯誤">多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理&lt;/a> — 本機 state 放在掛進容器的工作目錄、清理時先 &lt;code>terraform destroy&lt;/code> 再刪控制端的實際操作&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>State 是 <a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> 工具用來記錄「上一次 apply 之後，每個資源在雲端長什麼樣」的快照。它的作用是讓工具能算出「程式碼描述的目標」與「雲端上的現況」之間的最小差異。沒有 state，工具每次都得把所有資源重新查一遍才知道該不該動，而且無法分辨「這個資源是我建的、該由我管」還是「別人手動建的、不歸我管」。</p>
<p>State 裡通常含有資源的真實 ID、相依關係，以及部分敏感屬性（例如資料庫的初始密碼、private key 的輸出值）。這帶來兩條硬邊界：state 不能進 git（含敏感值，推進版控等於把密碼寫進每個 clone 的歷史）、state 不能只放本地（本地 state 的失敗模式是記憶綁在一台筆電上，多人並行 apply 會互相覆蓋）。</p>
<h2 id="概念位置">概念位置</h2>
<p>State 是 <a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> 的記憶機制。<a href="/blog/devops/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 的自管與託管路線">模組一：最小可行 IaC</a> 的核心主題就是怎麼把 state 管好——remote backend、加密、鎖機制。State 管不好，後續所有 IaC 操作都建立在不可靠的記憶上。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>State 出問題的訊號包括：<code>terraform plan</code> 顯示大量非預期的變更（state 與現實不一致）、兩個人同時 apply 後環境出現矛盾狀態、<code>state list</code> 的資源數與 Console 上看到的不一致。</p>
<h2 id="設計責任">設計責任</h2>
<p>state 的設定裡事後最難改的是分割策略：資源分進不同的 state 之後，要移動就得在兩份 state 之間逐個資源遷移。存放位置、加密、版本保留與鎖機制管的是 state 的安全與復原：防止兩次 apply 同時寫入、限制外洩的範圍、在損壞後回捲到上一個版本：</p>
<ul>
<li>存放位置：S3 + DynamoDB（自管）vs Terraform Cloud（託管），取捨在維運負擔 vs 控制權</li>
<li>加密：state 含敏感值，落地加密（S3 SSE）是底線</li>
<li>版本保留：bucket versioning 讓 state 損壞時能回捲到上一個正確版本</li>
<li>鎖機制：防止兩個人同時 apply 互相覆蓋</li>
<li>分割策略：一個大 state vs 多個小 state，取捨在引用便利性 vs 影響範圍控制</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> — state 是 IaC 工具的核心依賴</li>
<li><a href="/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift</a> — state 與現實的落差</li>
<li><a href="/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">Configuration Management（組態管理）</a> — 由 Terraform 的輸出產生 Ansible 的 inventory 時，那份檔案的內容記在 state 裡</li>
<li><a href="/blog/devops/operations/09-host-lifecycle/hands-on/multi-host-manifest-walkthrough/" data-link-title="多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理" data-link-desc="要照著部署多台主機的維護清單並執行檢查腳本，或要讀跨主機執行時 ansible 的 exit code 與每台的 rc、門檻造成的 WARN、清單格式錯與連不上的主機各自印出什麼時，或要確認 Terraform 寫出的 inventory 的檔案權限、`--check` 預覽與實際部署結果不同的情形時，查在可拋棄的容器上實際跑過的指令、輸出與遇到的錯誤">多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理</a> — 本機 state 放在掛進容器的工作目錄、清理時先 <code>terraform destroy</code> 再刪控制端的實際操作</li>
</ul>
]]></content:encoded></item><item><title>Drift（設定漂移）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/drift/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/drift/</guid><description>&lt;p>Drift 指的是 IaC 的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">state&lt;/a> 記錄與雲端上的實際資源狀態之間的不一致。最常見的來源是有人繞過 IaC、直接在 Console 手動修改資源設定——state 不知道這次改動發生了，下一次 &lt;code>plan&lt;/code> 時工具會把手動改的設定判定為「不在我的記憶裡、要修正回程式碼的版本」。&lt;/p>
&lt;p>Drift 的代價會延遲浮現。手動改的當下看起來沒問題——設定改了、服務正常。問題出在後續某次不相關的 &lt;code>apply&lt;/code>：工具用過時的 state 去比對，把手動改的設定覆蓋掉，服務因此斷線，而且在 PR 裡看不到這件事發生過。Drift 累積越多，每次 &lt;code>apply&lt;/code> 的不確定性越高，最終團隊會開始害怕跑 &lt;code>apply&lt;/code>，IaC 名存實亡。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Drift 是 Console 唯讀鐵律存在的根本理由。&lt;a href="https://tarrragon.github.io/blog/devops/infra/01-minimal-iac/console-readonly-minimal-viable/" data-link-title="Console 唯讀鐵律與最小可行資源集合" data-link-desc="Console 只用來看不用來改的操作紀律、drift 的延遲浮現與偵測，以及能跑出第一個完整 apply 迴路的最小資源集合">模組一：Console 唯讀鐵律&lt;/a>用權限機制（人類身分唯讀、寫入權限留給自動化身分，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a>）讓「在 Console 改不動」成為預設狀態，從源頭消除 drift 的產生。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>Drift 存在的訊號：&lt;code>terraform plan&lt;/code> 在沒人改過程式碼的情況下顯示變更（代表有人在 Console 動了東西）、團隊開始說「跑 plan 前先看看有沒有奇怪的差異」、某次例行 apply 意外改掉了不該改的設定。&lt;/p>
&lt;p>偵測 drift 的主動方式是定期跑 &lt;code>terraform plan&lt;/code> 但不 apply，把 diff 輸出當成 drift 偵測的報告。plan 只比對 state 裡有的資源：有人在主控台另外新增、不在 state 裡的資源（例如寫成獨立資源的一條防火牆規則），plan 照樣回報 &lt;code>No changes&lt;/code>，範圍見 &lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/multi-host-manifest/" data-link-title="多台主機的維護清單：值的分層與合併的位置、用 Ansible 的 inventory 與範本產生每台的清單、由 Terraform 的輸出產生 inventory、跨主機執行的 exit code，以及單台清單的適用範圍" data-link-desc="把單台的主機維護清單擴充到多台主機：每台相同、依角色不同、每台不同的值怎麼分層，合併放在部署時產生每台一份、由檢查腳本讀多份、依角色過濾還是執行時覆寫，用 Ansible 的 inventory、群組變數與範本產生 TOML 清單時的變數合併、兼任角色、型別、語法撞名、跳脫與產生時的驗證，由 Terraform 的輸出產生 inventory、terraform plan 能取代哪些主控台上的確認，對多台執行檢查腳本時 exit code 的讀法與執行身分的代價，以及單台清單夠用的條件">多台主機的維護清單：值的分層與合併的位置、用 Ansible 的 inventory 與範本產生每台的清單、由 Terraform 的輸出產生 inventory、跨主機執行的 exit code，以及單台清單的適用範圍&lt;/a>〈清單裡要到主控台確認的項目與 terraform plan 的比對範圍〉（從比對機制推論，沒有實測）。Terraform Cloud 有內建的 drift detection 功能，定期比對 state 與雲端現實。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Drift 出現之後，團隊先要判斷 Console 上那次手動修改是失誤還是有理由的修正，偵測、修正與預防的設計都從這個判斷出發：&lt;/p>
&lt;ul>
&lt;li>偵測頻率：每次 PR 觸發 plan（被動偵測）vs 定期排程 plan（主動偵測）&lt;/li>
&lt;li>修正方向：把雲端改回程式碼的版本（&lt;code>apply&lt;/code>），還是把程式碼改成雲端的版本（更新 HCL）——取捨在「程式碼是 source of truth」vs「手動改的設定有它的理由」&lt;/li>
&lt;li>預防機制：Console 唯讀權限、CI gate 攔截未經 review 的 apply&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State&lt;/a> — drift 是 state 與現實的落差&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> — drift 破壞 IaC 的 source of truth 地位&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">Configuration Management（組態管理）&lt;/a> — 主機內部的套件與設定檔跟宣告不一致，由組態管理工具處理&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Drift 指的是 IaC 的 <a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">state</a> 記錄與雲端上的實際資源狀態之間的不一致。最常見的來源是有人繞過 IaC、直接在 Console 手動修改資源設定——state 不知道這次改動發生了，下一次 <code>plan</code> 時工具會把手動改的設定判定為「不在我的記憶裡、要修正回程式碼的版本」。</p>
<p>Drift 的代價會延遲浮現。手動改的當下看起來沒問題——設定改了、服務正常。問題出在後續某次不相關的 <code>apply</code>：工具用過時的 state 去比對，把手動改的設定覆蓋掉，服務因此斷線，而且在 PR 裡看不到這件事發生過。Drift 累積越多，每次 <code>apply</code> 的不確定性越高，最終團隊會開始害怕跑 <code>apply</code>，IaC 名存實亡。</p>
<h2 id="概念位置">概念位置</h2>
<p>Drift 是 Console 唯讀鐵律存在的根本理由。<a href="/blog/devops/infra/01-minimal-iac/console-readonly-minimal-viable/" data-link-title="Console 唯讀鐵律與最小可行資源集合" data-link-desc="Console 只用來看不用來改的操作紀律、drift 的延遲浮現與偵測，以及能跑出第一個完整 apply 迴路的最小資源集合">模組一：Console 唯讀鐵律</a>用權限機制（人類身分唯讀、寫入權限留給自動化身分，見 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a>）讓「在 Console 改不動」成為預設狀態，從源頭消除 drift 的產生。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>Drift 存在的訊號：<code>terraform plan</code> 在沒人改過程式碼的情況下顯示變更（代表有人在 Console 動了東西）、團隊開始說「跑 plan 前先看看有沒有奇怪的差異」、某次例行 apply 意外改掉了不該改的設定。</p>
<p>偵測 drift 的主動方式是定期跑 <code>terraform plan</code> 但不 apply，把 diff 輸出當成 drift 偵測的報告。plan 只比對 state 裡有的資源：有人在主控台另外新增、不在 state 裡的資源（例如寫成獨立資源的一條防火牆規則），plan 照樣回報 <code>No changes</code>，範圍見 <a href="/blog/devops/operations/09-host-lifecycle/multi-host-manifest/" data-link-title="多台主機的維護清單：值的分層與合併的位置、用 Ansible 的 inventory 與範本產生每台的清單、由 Terraform 的輸出產生 inventory、跨主機執行的 exit code，以及單台清單的適用範圍" data-link-desc="把單台的主機維護清單擴充到多台主機：每台相同、依角色不同、每台不同的值怎麼分層，合併放在部署時產生每台一份、由檢查腳本讀多份、依角色過濾還是執行時覆寫，用 Ansible 的 inventory、群組變數與範本產生 TOML 清單時的變數合併、兼任角色、型別、語法撞名、跳脫與產生時的驗證，由 Terraform 的輸出產生 inventory、terraform plan 能取代哪些主控台上的確認，對多台執行檢查腳本時 exit code 的讀法與執行身分的代價，以及單台清單夠用的條件">多台主機的維護清單：值的分層與合併的位置、用 Ansible 的 inventory 與範本產生每台的清單、由 Terraform 的輸出產生 inventory、跨主機執行的 exit code，以及單台清單的適用範圍</a>〈清單裡要到主控台確認的項目與 terraform plan 的比對範圍〉（從比對機制推論，沒有實測）。Terraform Cloud 有內建的 drift detection 功能，定期比對 state 與雲端現實。</p>
<h2 id="設計責任">設計責任</h2>
<p>Drift 出現之後，團隊先要判斷 Console 上那次手動修改是失誤還是有理由的修正，偵測、修正與預防的設計都從這個判斷出發：</p>
<ul>
<li>偵測頻率：每次 PR 觸發 plan（被動偵測）vs 定期排程 plan（主動偵測）</li>
<li>修正方向：把雲端改回程式碼的版本（<code>apply</code>），還是把程式碼改成雲端的版本（更新 HCL）——取捨在「程式碼是 source of truth」vs「手動改的設定有它的理由」</li>
<li>預防機制：Console 唯讀權限、CI gate 攔截未經 review 的 apply</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State</a> — drift 是 state 與現實的落差</li>
<li><a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> — drift 破壞 IaC 的 source of truth 地位</li>
<li><a href="/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">Configuration Management（組態管理）</a> — 主機內部的套件與設定檔跟宣告不一致，由組態管理工具處理</li>
</ul>
]]></content:encoded></item><item><title>VPC（Virtual Private Cloud）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/</guid><description>&lt;p>VPC（Virtual Private Cloud）是雲端帳號內的一塊邏輯隔離私有網段，是其餘所有網路切分的起點。在 VPC 裡開出來的所有資源預設只看得到同一個 VPC 內的成員，與其他 VPC、與其他帳號的網路天然隔離。沒有 VPC，&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/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/devops/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/devops/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 開始：先圈定地址空間，再往內切 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet&lt;/a>、掛 route table、設 security group。環境之間的 VPC 怎麼分（每個環境一個 VPC），屬於&lt;a href="https://tarrragon.github.io/blog/devops/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 的地址空間在建立時定下、事後擴張受限，而網段一旦跟其他 VPC 或地端網路重疊，兩邊就無法建立 peering 或互連；規劃時先定的是：&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/devops/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/devops/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/devops/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/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">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/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet</a> 與 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">security group</a> 無處依附。</p>
<p>VPC 用 <a href="/blog/devops/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/devops/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 開始：先圈定地址空間，再往內切 <a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet</a>、掛 route table、設 security group。環境之間的 VPC 怎麼分（每個環境一個 VPC），屬於<a href="/blog/devops/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 的地址空間在建立時定下、事後擴張受限，而網段一旦跟其他 VPC 或地端網路重疊，兩邊就無法建立 peering 或互連；規劃時先定的是：</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/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> — VPC 內按可用區與暴露程度切出的子網段</li>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — 掛在資源上的有狀態防火牆</li>
<li><a href="/blog/devops/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/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT</a> — 讓 private subnet 出站的地址轉換機制</li>
</ul>
]]></content:encoded></item><item><title>Subnet（子網路）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/</guid><description>&lt;p>Subnet 是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC&lt;/a> 內部按暴露程度（在 AWS 上還按可用區）切出來的子網段。一塊資源對外暴露到什麼程度，取決於它被放進哪個 subnet。在 AWS 上，公開與私有的差別只在該 subnet 關聯的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/route-table/" data-link-title="Route Table" data-link-desc="掛在 subnet 上的流量轉送規則，決定封包離開 subnet 後往哪走">route table&lt;/a> 裡是否有一條指向 Internet Gateway 的預設路由；Oracle Cloud 另外在 subnet 上設一個禁止公開 IP 的屬性，Google Cloud 的 subnet 沒有公開與私有之分（見〈概念位置〉）。&lt;/p>
&lt;p>Subnet 分兩類：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Public subnet&lt;/strong>：route table 有 &lt;code>0.0.0.0/0 → Internet Gateway&lt;/code>，讓資源能被外部 IP 直接觸及。典型住戶是對外負載平衡器、&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT&lt;/a> Gateway。&lt;/li>
&lt;li>&lt;strong>Private subnet&lt;/strong>：route table 把 &lt;code>0.0.0.0/0&lt;/code> 指向 NAT Gateway，外部無法主動連入。典型住戶是應用伺服器、資料庫、快取。&lt;/li>
&lt;/ul>
&lt;p>Public subnet 的真實樣貌是「薄薄一層」——它通常只住入口設施，業務邏輯跟資料儲存都在 private subnet。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Subnet 是&lt;a href="https://tarrragon.github.io/blog/devops/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>的中層邊界。&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC&lt;/a> 定好地址空間後，subnet 決定「哪些資源能被外網碰到、哪些只能在內網存取」。在 AWS 上每個 subnet 綁定單一可用區，高可用設計通常是每種角色跨至少兩個可用區各開一個 subnet；Google Cloud 與 Oracle Cloud 的 subnet 可以跨區域內的可用區（Oracle Cloud 叫 regional subnet），見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）&lt;/a>。公開與私有的區分在各家的實作也不同，Oracle Cloud 另用 subnet 的屬性禁止公開 IP、Google Cloud 沒有這個區分，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則&lt;/a>。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>Subnet 配置有問題的訊號：應用伺服器被放在 public subnet 並配了公網 IP（管理埠暴露在掃描流量下）、private subnet 的服務拉不到外部套件（route table 沒指向健康的 NAT）、新服務上線時找不到適合的 subnet（CIDR 切得太小、空間不夠）。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>subnet 建立後 CIDR 無法修改、而且在 AWS 上綁定單一可用區，第一次規劃時的切法與分佈就決定了之後能放多少服務、能在哪一個可用區故障時繼續運作：&lt;/p>
&lt;ul>
&lt;li>CIDR 切法：VPC 是 &lt;code>/16&lt;/code> 時，每個 subnet 用 &lt;code>/20&lt;/code>（約四千位址）可以在三個可用區各開 public + private 共六個 subnet&lt;/li>
&lt;li>跨可用區對稱：每種角色至少跨兩個 AZ，讓單一 AZ 故障時另一區能承接&lt;/li>
&lt;li>public 的住戶限制：只放入口設施，業務邏輯一律放 private&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC&lt;/a> — subnet 的容器&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT&lt;/a> — 讓 private subnet 出站的機制&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — 掛在資源上的埠級存取控制&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Subnet 是 <a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> 內部按暴露程度（在 AWS 上還按可用區）切出來的子網段。一塊資源對外暴露到什麼程度，取決於它被放進哪個 subnet。在 AWS 上，公開與私有的差別只在該 subnet 關聯的 <a href="/blog/devops/infra/knowledge-cards/route-table/" data-link-title="Route Table" data-link-desc="掛在 subnet 上的流量轉送規則，決定封包離開 subnet 後往哪走">route table</a> 裡是否有一條指向 Internet Gateway 的預設路由；Oracle Cloud 另外在 subnet 上設一個禁止公開 IP 的屬性，Google Cloud 的 subnet 沒有公開與私有之分（見〈概念位置〉）。</p>
<p>Subnet 分兩類：</p>
<ul>
<li><strong>Public subnet</strong>：route table 有 <code>0.0.0.0/0 → Internet Gateway</code>，讓資源能被外部 IP 直接觸及。典型住戶是對外負載平衡器、<a href="/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT</a> Gateway。</li>
<li><strong>Private subnet</strong>：route table 把 <code>0.0.0.0/0</code> 指向 NAT Gateway，外部無法主動連入。典型住戶是應用伺服器、資料庫、快取。</li>
</ul>
<p>Public subnet 的真實樣貌是「薄薄一層」——它通常只住入口設施，業務邏輯跟資料儲存都在 private subnet。</p>
<h2 id="概念位置">概念位置</h2>
<p>Subnet 是<a href="/blog/devops/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>的中層邊界。<a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> 定好地址空間後，subnet 決定「哪些資源能被外網碰到、哪些只能在內網存取」。在 AWS 上每個 subnet 綁定單一可用區，高可用設計通常是每種角色跨至少兩個可用區各開一個 subnet；Google Cloud 與 Oracle Cloud 的 subnet 可以跨區域內的可用區（Oracle Cloud 叫 regional subnet），見 <a href="/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）</a>。公開與私有的區分在各家的實作也不同，Oracle Cloud 另用 subnet 的屬性禁止公開 IP、Google Cloud 沒有這個區分，見 <a href="/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則</a>。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>Subnet 配置有問題的訊號：應用伺服器被放在 public subnet 並配了公網 IP（管理埠暴露在掃描流量下）、private subnet 的服務拉不到外部套件（route table 沒指向健康的 NAT）、新服務上線時找不到適合的 subnet（CIDR 切得太小、空間不夠）。</p>
<h2 id="設計責任">設計責任</h2>
<p>subnet 建立後 CIDR 無法修改、而且在 AWS 上綁定單一可用區，第一次規劃時的切法與分佈就決定了之後能放多少服務、能在哪一個可用區故障時繼續運作：</p>
<ul>
<li>CIDR 切法：VPC 是 <code>/16</code> 時，每個 subnet 用 <code>/20</code>（約四千位址）可以在三個可用區各開 public + private 共六個 subnet</li>
<li>跨可用區對稱：每種角色至少跨兩個 AZ，讓單一 AZ 故障時另一區能承接</li>
<li>public 的住戶限制：只放入口設施，業務邏輯一律放 private</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> — subnet 的容器</li>
<li><a href="/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT</a> — 讓 private subnet 出站的機制</li>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — 掛在資源上的埠級存取控制</li>
</ul>
]]></content:encoded></item><item><title>Security Group</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/</guid><description>&lt;p>Security group 是掛在資源網卡（ENI）層級的有狀態防火牆，規則描述的是「哪些來源能連到這個資源的哪個埠」。「有狀態」的意思是放行一條入站連線後，對應的回應出站自動允許——規則只需描述入站方向想開放什麼。這面防火牆依附在資源所在的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC&lt;/a> 網路邊界之內。&lt;/p>
&lt;p>設計原則是最小開放：每條規則只開「這個服務確實需要被誰連的那個埠」。資料庫的 security group 入站只允許來自應用層 security group 的資料庫埠（如 5432），而不是某個 IP 範圍。用 security group 互相引用（source 指向另一個 group 而非 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cidr/" data-link-title="CIDR（Classless Inter-Domain Routing）" data-link-desc="用前綴長度表示 IP 地址範圍的表示法，決定 VPC 與 subnet 的地址空間大小">CIDR&lt;/a>）讓規則跟著成員身分走、不跟著位址走——應用節點會隨擴縮而換 IP，引用 group 不會因此失效。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Security group 是&lt;a href="https://tarrragon.github.io/blog/devops/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>的最內層邊界——貼著服務的最後一道網路防線。即使封包順著 route table 抵達了 private &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet&lt;/a>，security group 仍能逐埠決定放不放行。&lt;a href="https://tarrragon.github.io/blog/devops/infra/07-infra-as-pr/plan-review-apply-guardrails/" data-link-title="infra 走 PR 流程與自動化護欄" data-link-desc="infra 變更走 PR → plan → review diff → 合併 → apply，配 fmt / validate / tflint / checkov / tfsec 與 Atlantis 自動化，讓基礎設施可審查、可回溯、可交接">模組七：infra 走 PR 流程&lt;/a>用 tfsec / checkov 在 CI 攔截 &lt;code>0.0.0.0/0&lt;/code> 全開的規則。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>Security group 需要收斂的訊號：入站來源是 &lt;code>0.0.0.0/0&lt;/code>（允許全網連入），且目標埠是資料庫（5432、3306、6379）或管理埠（22、3389）——合理出現 &lt;code>0.0.0.0/0&lt;/code> 的位置只有對外負載平衡器的 80 / 443。盤點方式是列出所有 source 為 &lt;code>0.0.0.0/0&lt;/code> 的規則，逐條問「這個埠需要全世界都連得到嗎」。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>security group 是封包抵達資源前的最後一道網路檢查：規則寫得太寬會把服務的埠暴露出去，寫成固定位址又會在節點換 IP 時失效。設計時在這兩種失效之間收斂的項目是：&lt;/p>
&lt;ul>
&lt;li>引用方式：用 group 互相引用（推薦）vs 用 CIDR 限定範圍&lt;/li>
&lt;li>開放範圍：只開需要的埠與來源，&lt;code>0.0.0.0/0&lt;/code> 只給對外 LB&lt;/li>
&lt;li>管理埠存取：SSH（22）改用 SSM Session Manager 這類不開入站 port 的做法，或只放行&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">身分感知代理&lt;/a>的位址段，從公網清單上拿掉；各種做法的選擇見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>&lt;/li>
&lt;li>與 NACL 的分工：security group 是主力（有狀態、group 引用），NACL 留給少數需要 subnet 層顯式 deny 的情境&lt;/li>
&lt;li>規則數上限：一條規則只放一段來源時，放一份外部位址清單（例如只放行 CDN 回源的位址）要「段數 × 連接埠數」條，會撞到每個 group 的規則數上限；AWS 文件寫的預設是入站 60 條、IPv4 與 IPv6 分開算，Oracle Cloud 的 NSG 實測是入站加出站合計 120 條，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/cloudflare-origin-pull-walkthrough/" data-link-title="Cloudflare 回源的實測紀錄：Oracle Cloud NSG 只放行 Cloudflare 位址與規則數上限、回源的來源位址、Authenticated Origin Pulls、Worker 偽造 CF-Connecting-IP 與 Tunnel 的出站連接埠" data-link-desc="源站要放在 Cloudflare 後面、想確認只放行 Cloudflare 位址的雲端防火牆放不放得下整份清單、回源實際用哪些位址、Authenticated Origin Pulls 怎麼驗證、Cloudflare Worker 能不能穿過這道防火牆並偽造 CF-Connecting-IP、或 Tunnel 要開哪些出站連接埠時，查 Oracle Cloud 與 Cloudflare 免費方案上實際跑過的指令與輸出">Cloudflare 回源的實測紀錄：Oracle Cloud NSG 只放行 Cloudflare 位址與規則數上限、回源的來源位址、Authenticated Origin Pulls、Worker 偽造 CF-Connecting-IP 與 Tunnel 的出站連接埠&lt;/a> 的〈公開子網路、只放行 Cloudflare 的 NSG 與它的規則數上限〉&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC&lt;/a> — security group 依附的網路容器&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> — security group 與 subnet 各守不同層級的邊界&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）&lt;/a> — 目的主機的入站規則以跳板機的 security group 當來源&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a> — 管理 port 只放行代理位址段的做法&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則&lt;/a> — Oracle Cloud 與 Google Cloud 上對應網卡層規則的元件與合併方式&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Security group 是掛在資源網卡（ENI）層級的有狀態防火牆，規則描述的是「哪些來源能連到這個資源的哪個埠」。「有狀態」的意思是放行一條入站連線後，對應的回應出站自動允許——規則只需描述入站方向想開放什麼。這面防火牆依附在資源所在的 <a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> 網路邊界之內。</p>
<p>設計原則是最小開放：每條規則只開「這個服務確實需要被誰連的那個埠」。資料庫的 security group 入站只允許來自應用層 security group 的資料庫埠（如 5432），而不是某個 IP 範圍。用 security group 互相引用（source 指向另一個 group 而非 <a href="/blog/devops/infra/knowledge-cards/cidr/" data-link-title="CIDR（Classless Inter-Domain Routing）" data-link-desc="用前綴長度表示 IP 地址範圍的表示法，決定 VPC 與 subnet 的地址空間大小">CIDR</a>）讓規則跟著成員身分走、不跟著位址走——應用節點會隨擴縮而換 IP，引用 group 不會因此失效。</p>
<h2 id="概念位置">概念位置</h2>
<p>Security group 是<a href="/blog/devops/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>的最內層邊界——貼著服務的最後一道網路防線。即使封包順著 route table 抵達了 private <a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet</a>，security group 仍能逐埠決定放不放行。<a href="/blog/devops/infra/07-infra-as-pr/plan-review-apply-guardrails/" 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 在 CI 攔截 <code>0.0.0.0/0</code> 全開的規則。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>Security group 需要收斂的訊號：入站來源是 <code>0.0.0.0/0</code>（允許全網連入），且目標埠是資料庫（5432、3306、6379）或管理埠（22、3389）——合理出現 <code>0.0.0.0/0</code> 的位置只有對外負載平衡器的 80 / 443。盤點方式是列出所有 source 為 <code>0.0.0.0/0</code> 的規則，逐條問「這個埠需要全世界都連得到嗎」。</p>
<h2 id="設計責任">設計責任</h2>
<p>security group 是封包抵達資源前的最後一道網路檢查：規則寫得太寬會把服務的埠暴露出去，寫成固定位址又會在節點換 IP 時失效。設計時在這兩種失效之間收斂的項目是：</p>
<ul>
<li>引用方式：用 group 互相引用（推薦）vs 用 CIDR 限定範圍</li>
<li>開放範圍：只開需要的埠與來源，<code>0.0.0.0/0</code> 只給對外 LB</li>
<li>管理埠存取：SSH（22）改用 SSM Session Manager 這類不開入站 port 的做法，或只放行<a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">身分感知代理</a>的位址段，從公網清單上拿掉；各種做法的選擇見<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a></li>
<li>與 NACL 的分工：security group 是主力（有狀態、group 引用），NACL 留給少數需要 subnet 層顯式 deny 的情境</li>
<li>規則數上限：一條規則只放一段來源時，放一份外部位址清單（例如只放行 CDN 回源的位址）要「段數 × 連接埠數」條，會撞到每個 group 的規則數上限；AWS 文件寫的預設是入站 60 條、IPv4 與 IPv6 分開算，Oracle Cloud 的 NSG 實測是入站加出站合計 120 條，見 <a href="/blog/devops/infra/03-network-foundation/hands-on/cloudflare-origin-pull-walkthrough/" data-link-title="Cloudflare 回源的實測紀錄：Oracle Cloud NSG 只放行 Cloudflare 位址與規則數上限、回源的來源位址、Authenticated Origin Pulls、Worker 偽造 CF-Connecting-IP 與 Tunnel 的出站連接埠" data-link-desc="源站要放在 Cloudflare 後面、想確認只放行 Cloudflare 位址的雲端防火牆放不放得下整份清單、回源實際用哪些位址、Authenticated Origin Pulls 怎麼驗證、Cloudflare Worker 能不能穿過這道防火牆並偽造 CF-Connecting-IP、或 Tunnel 要開哪些出站連接埠時，查 Oracle Cloud 與 Cloudflare 免費方案上實際跑過的指令與輸出">Cloudflare 回源的實測紀錄：Oracle Cloud NSG 只放行 Cloudflare 位址與規則數上限、回源的來源位址、Authenticated Origin Pulls、Worker 偽造 CF-Connecting-IP 與 Tunnel 的出站連接埠</a> 的〈公開子網路、只放行 Cloudflare 的 NSG 與它的規則數上限〉</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> — security group 依附的網路容器</li>
<li><a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> — security group 與 subnet 各守不同層級的邊界</li>
<li><a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> — 目的主機的入站規則以跳板機的 security group 當來源</li>
<li><a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a> — 管理 port 只放行代理位址段的做法</li>
<li><a href="/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則</a> — Oracle Cloud 與 Google Cloud 上對應網卡層規則的元件與合併方式</li>
</ul>
]]></content:encoded></item><item><title>NAT Gateway</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nat/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nat/</guid><description>&lt;p>NAT Gateway（Network Address Translation Gateway）的核心職責是讓 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">private subnet&lt;/a> 的資源能主動發起對外連線（拉套件、呼叫第三方 API、下載 OS 更新），同時不開放任何外部主動發起的入站連線。它借用一個公網 IP 把出站封包送出去，再把回應導回原請求者。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>NAT Gateway 在網路地基裡的角色是 private subnet 的出站閘道。它解決的問題是：private subnet 的設計意圖是「外部連不進來」，但服務仍需要主動對外。沒有 NAT，private subnet 的資源完全無法對外通訊 — 連 &lt;code>apt update&lt;/code> 或 &lt;code>pip install&lt;/code> 都做不到。&lt;/p>
&lt;p>傳統的 zonal NAT Gateway 綁定單一可用區，放在某個 public subnet 裡，實際生不生效由 private subnet 的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/route-table/" data-link-title="Route Table" data-link-desc="掛在 subnet 上的流量轉送規則，決定封包離開 subnet 後往哪走">route table&lt;/a> 預設路由決定。這帶來一個架構取捨：共享一個 NAT（成本低、出站方向有單點）還是每個可用區各放一個（成本高、出站與 subnet 冗餘對齊）。AWS 在 2025 年 11 月另外推出 regional 模式的 NAT Gateway：一個 NAT 依工作負載所在的可用區自動擴展，不需要放進 public subnet；新的可用區出現工作負載後，擴展到那一區可能要等上一段時間（AWS 公告寫的是最多 60 分鐘）。選擇時要先確認用的是哪一種模式，上面的單點取捨只適用於 zonal 模式。&lt;/p>
&lt;p>GCP 的對應服務是 Cloud NAT，職責相同，結構不同：它建在 Cloud Router 上、以 region 為單位生效，不需要放進某個 subnet，也沒有可用區層級的單點取捨。管理通道改走代理之後要移除 VM 的外部 IP，出站就改由 Cloud NAT 承接，見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>的〈移除公開 IP 之後的出站路徑〉。沒有外部 IP 的 VM 要存取 Google 自家的 API 時，subnet 開啟 Private Google Access 就能走內部路徑，作用與 AWS 的 VPC Endpoint 相近。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>以下狀況指向 NAT 相關問題：&lt;/p>
&lt;ul>
&lt;li>Private subnet 的服務拉不到外部套件或第三方 API 全部逾時 — 先查 route table 有沒有指向健康的 NAT&lt;/li>
&lt;li>只有某一個可用區的節點受影響 — 該區的 NAT 或其所在 subnet 可能故障&lt;/li>
&lt;li>雲帳單裡 NAT Gateway 的流量費用異常高 — 大量走 NAT 的流量（S3 備份、跨區同步）可用 VPC Endpoint 繞過&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>zonal 模式下每多放一個 NAT Gateway 就多一筆固定月費，少放一個就讓某些可用區的出站依賴別區的 NAT，而經過 NAT 的每 GB 流量另外計費（這兩種費用是 AWS 的收法；Oracle Cloud 的價目表沒有 NAT gateway 這一項，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目&lt;/a>〈查付費主機價格時要輸入的項目〉）；下面的決定都在出站可用性與這兩種費用之間取捨：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>數量&lt;/strong>：每個可用區一個（可用性優先）還是全 VPC 共享一個（成本優先）。每個 NAT 固定月費約 $32 加流量費 $0.045/GB&lt;/li>
&lt;li>&lt;strong>高流量路徑&lt;/strong>：對 AWS 自家服務的流量（S3、DynamoDB）改用 Gateway Endpoint 直連，繞過 NAT 省流量費&lt;/li>
&lt;li>&lt;strong>route table 關聯&lt;/strong>：每個 private subnet 的 route table 要明確指向哪個 NAT&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> — NAT 放在 public subnet、服務放在 private subnet&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC&lt;/a> — NAT 屬於 VPC 內部的出站路徑設施&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>NAT Gateway（Network Address Translation Gateway）的核心職責是讓 <a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">private subnet</a> 的資源能主動發起對外連線（拉套件、呼叫第三方 API、下載 OS 更新），同時不開放任何外部主動發起的入站連線。它借用一個公網 IP 把出站封包送出去，再把回應導回原請求者。</p>
<h2 id="概念位置">概念位置</h2>
<p>NAT Gateway 在網路地基裡的角色是 private subnet 的出站閘道。它解決的問題是：private subnet 的設計意圖是「外部連不進來」，但服務仍需要主動對外。沒有 NAT，private subnet 的資源完全無法對外通訊 — 連 <code>apt update</code> 或 <code>pip install</code> 都做不到。</p>
<p>傳統的 zonal NAT Gateway 綁定單一可用區，放在某個 public subnet 裡，實際生不生效由 private subnet 的 <a href="/blog/devops/infra/knowledge-cards/route-table/" data-link-title="Route Table" data-link-desc="掛在 subnet 上的流量轉送規則，決定封包離開 subnet 後往哪走">route table</a> 預設路由決定。這帶來一個架構取捨：共享一個 NAT（成本低、出站方向有單點）還是每個可用區各放一個（成本高、出站與 subnet 冗餘對齊）。AWS 在 2025 年 11 月另外推出 regional 模式的 NAT Gateway：一個 NAT 依工作負載所在的可用區自動擴展，不需要放進 public subnet；新的可用區出現工作負載後，擴展到那一區可能要等上一段時間（AWS 公告寫的是最多 60 分鐘）。選擇時要先確認用的是哪一種模式，上面的單點取捨只適用於 zonal 模式。</p>
<p>GCP 的對應服務是 Cloud NAT，職責相同，結構不同：它建在 Cloud Router 上、以 region 為單位生效，不需要放進某個 subnet，也沒有可用區層級的單點取捨。管理通道改走代理之後要移除 VM 的外部 IP，出站就改由 Cloud NAT 承接，見<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>的〈移除公開 IP 之後的出站路徑〉。沒有外部 IP 的 VM 要存取 Google 自家的 API 時，subnet 開啟 Private Google Access 就能走內部路徑，作用與 AWS 的 VPC Endpoint 相近。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>以下狀況指向 NAT 相關問題：</p>
<ul>
<li>Private subnet 的服務拉不到外部套件或第三方 API 全部逾時 — 先查 route table 有沒有指向健康的 NAT</li>
<li>只有某一個可用區的節點受影響 — 該區的 NAT 或其所在 subnet 可能故障</li>
<li>雲帳單裡 NAT Gateway 的流量費用異常高 — 大量走 NAT 的流量（S3 備份、跨區同步）可用 VPC Endpoint 繞過</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>zonal 模式下每多放一個 NAT Gateway 就多一筆固定月費，少放一個就讓某些可用區的出站依賴別區的 NAT，而經過 NAT 的每 GB 流量另外計費（這兩種費用是 AWS 的收法；Oracle Cloud 的價目表沒有 NAT gateway 這一項，見 <a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目</a>〈查付費主機價格時要輸入的項目〉）；下面的決定都在出站可用性與這兩種費用之間取捨：</p>
<ul>
<li><strong>數量</strong>：每個可用區一個（可用性優先）還是全 VPC 共享一個（成本優先）。每個 NAT 固定月費約 $32 加流量費 $0.045/GB</li>
<li><strong>高流量路徑</strong>：對 AWS 自家服務的流量（S3、DynamoDB）改用 Gateway Endpoint 直連，繞過 NAT 省流量費</li>
<li><strong>route table 關聯</strong>：每個 private subnet 的 route table 要明確指向哪個 NAT</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> — NAT 放在 public subnet、服務放在 private subnet</li>
<li><a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> — NAT 屬於 VPC 內部的出站路徑設施</li>
</ul>
]]></content:encoded></item><item><title>OIDC Federation（OIDC 聯合）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/oidc/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/oidc/</guid><description>&lt;p>OIDC（OpenID Connect）聯合的核心職責是讓跑在雲外的 CI/CD 平台（GitHub Actions、GitLab CI）用每次執行才簽發、幾分鐘後就失效的短期憑證存取雲端資源，從根本上消除「在 CI 環境裡存放長期 access key」這個攻擊面，信任關係的收斂條件寫在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/trust-policy/" data-link-title="Trust Policy" data-link-desc="IAM role 的信任關係設定，規定哪個身分被允許 assume 這個 role">trust policy&lt;/a> 裡。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>OIDC 聯合在身分與存取憑證地基裡的角色是「雲外機器身分的認證機制」。跑在雲上的 workload（EC2、ECS task）可以用平台原生的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> instance profile 或 task role 取得短期憑證；跑在雲外的 CI/CD 沒有這個管道，OIDC 就是替代方案。&lt;/p>
&lt;p>運作方式是建立信任關係：雲端帳號信任某個外部 identity provider（如 GitHub Actions 的 OIDC issuer），CI 執行時平台簽發一個帶 claim 的 token（描述哪個 repo、哪個 branch、哪個 workflow），雲端用這個 token 換出一段臨時憑證。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>以下狀況指向 OIDC 相關問題：&lt;/p>
&lt;ul>
&lt;li>CI pipeline 裡有 &lt;code>AWS_ACCESS_KEY_ID&lt;/code> 和 &lt;code>AWS_SECRET_ACCESS_KEY&lt;/code> 環境變數 — 這是長期 key，應該替換成 OIDC&lt;/li>
&lt;li>Trust policy 只驗 issuer 不驗 repo — 任何掛在同一個 CI 平台的專案都能假扮這個 role&lt;/li>
&lt;li>Pipeline 突然無法取得權限 — 可能是 trust policy 的 condition 跟 token claim 不匹配（常見於 repo 改名或 branch 改名後）&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>OIDC 聯合拿掉了 CI 環境裡的長期 access key，風險因此移到兩個位置：哪些 token 換得到 role，以及換到之後能做什麼。下面的設定分別收斂這兩個位置：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Trust policy 的 claim 收斂&lt;/strong>：限定 issuer + audience + 特定 repo + 特定 branch，每個條件都收到最緊&lt;/li>
&lt;li>&lt;strong>Role 的權限範圍&lt;/strong>：OIDC 換到的 role 仍然要遵循最小權限 — 只給 pipeline 需要的 action&lt;/li>
&lt;li>&lt;strong>Plan 與 apply 分開的 role&lt;/strong>：plan 只需要 read 權限、apply 需要 write 權限，用兩個 role 降低 PR 階段的風險&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> — OIDC 是 IAM 身分系統的一種外部身分來源&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — OIDC 解的是身分層的認證問題，跟網路層的 security group 正交&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>OIDC（OpenID Connect）聯合的核心職責是讓跑在雲外的 CI/CD 平台（GitHub Actions、GitLab CI）用每次執行才簽發、幾分鐘後就失效的短期憑證存取雲端資源，從根本上消除「在 CI 環境裡存放長期 access key」這個攻擊面，信任關係的收斂條件寫在 <a href="/blog/devops/infra/knowledge-cards/trust-policy/" data-link-title="Trust Policy" data-link-desc="IAM role 的信任關係設定，規定哪個身分被允許 assume 這個 role">trust policy</a> 裡。</p>
<h2 id="概念位置">概念位置</h2>
<p>OIDC 聯合在身分與存取憑證地基裡的角色是「雲外機器身分的認證機制」。跑在雲上的 workload（EC2、ECS task）可以用平台原生的 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> instance profile 或 task role 取得短期憑證；跑在雲外的 CI/CD 沒有這個管道，OIDC 就是替代方案。</p>
<p>運作方式是建立信任關係：雲端帳號信任某個外部 identity provider（如 GitHub Actions 的 OIDC issuer），CI 執行時平台簽發一個帶 claim 的 token（描述哪個 repo、哪個 branch、哪個 workflow），雲端用這個 token 換出一段臨時憑證。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>以下狀況指向 OIDC 相關問題：</p>
<ul>
<li>CI pipeline 裡有 <code>AWS_ACCESS_KEY_ID</code> 和 <code>AWS_SECRET_ACCESS_KEY</code> 環境變數 — 這是長期 key，應該替換成 OIDC</li>
<li>Trust policy 只驗 issuer 不驗 repo — 任何掛在同一個 CI 平台的專案都能假扮這個 role</li>
<li>Pipeline 突然無法取得權限 — 可能是 trust policy 的 condition 跟 token claim 不匹配（常見於 repo 改名或 branch 改名後）</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>OIDC 聯合拿掉了 CI 環境裡的長期 access key，風險因此移到兩個位置：哪些 token 換得到 role，以及換到之後能做什麼。下面的設定分別收斂這兩個位置：</p>
<ul>
<li><strong>Trust policy 的 claim 收斂</strong>：限定 issuer + audience + 特定 repo + 特定 branch，每個條件都收到最緊</li>
<li><strong>Role 的權限範圍</strong>：OIDC 換到的 role 仍然要遵循最小權限 — 只給 pipeline 需要的 action</li>
<li><strong>Plan 與 apply 分開的 role</strong>：plan 只需要 read 權限、apply 需要 write 權限，用兩個 role 降低 PR 階段的風險</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> — OIDC 是 IAM 身分系統的一種外部身分來源</li>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — OIDC 解的是身分層的認證問題，跟網路層的 security group 正交</li>
</ul>
]]></content:encoded></item><item><title>Environment Separation（環境分離）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/environment-separation/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/environment-separation/</guid><description>&lt;p>環境分離的核心職責是讓 dev 的實驗、staging 的驗證、production 的真實流量彼此不可見也不可達 — 在 dev 跑壞一個資料庫、套錯一條 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">security group&lt;/a> 規則時，production 完全無感。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>環境分離在 infra 成熟度階梯上對應第三階。它建立在宣告式 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a>（第二階）的基礎上 — 有了 state 追蹤和模組化描述之後，才能用「同一份 code、不同參數」的方式複製出多個隔離環境。&lt;/p>
&lt;p>分離的實作方式有一條隔離強度光譜：從帳號級（不同雲端帳號，最強隔離）到目錄級（同一 repo 內各環境一個目錄，各自持有 state）到 workspace 級（同一份 code 用執行期切換 state，隔離最弱）。多數早期團隊在目錄級落腳，因為它在顯式邊界與維運成本之間取得平衡。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>以下狀況指向環境分離不足：&lt;/p>
&lt;ul>
&lt;li>在 staging 測試的變更意外影響了 production 的資源 — dev 跟 prod 共用同一份 state&lt;/li>
&lt;li>某人的 &lt;code>terraform apply&lt;/code> 把另一個環境的資源改掉了 — workspace 的隱性狀態切換導致打錯環境&lt;/li>
&lt;li>dev 與 prod 的設定差異散落在 code 裡的 &lt;code>if env == &amp;quot;prod&amp;quot;&lt;/code> 判斷 — 環境差異沒有集中在參數值裡&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>環境分離失效的典型形態是一次 &lt;code>terraform apply&lt;/code> 改到了另一個環境的資源，以下的設計各自封住一條造成這種失效的路徑：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>隔離層級&lt;/strong>：帳號級、目錄級、還是 workspace 級。判斷依據是團隊規模、合規要求、與維運餘裕&lt;/li>
&lt;li>&lt;strong>參數化邊界&lt;/strong>：dev 與 prod 之間的差異全部用參數表達（instance size、multi-AZ、backup retention），module 內部不寫環境判斷&lt;/li>
&lt;li>&lt;strong>state 位址分離&lt;/strong>：每個環境的 state backend 位址獨立，互不交叉&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> — 環境分離的前提是有可重用的 IaC 描述&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State&lt;/a> — 每個環境持有獨立的 state 檔&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift&lt;/a> — 環境分離降低 drift 的跨環境影響範圍&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>環境分離的核心職責是讓 dev 的實驗、staging 的驗證、production 的真實流量彼此不可見也不可達 — 在 dev 跑壞一個資料庫、套錯一條 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">security group</a> 規則時，production 完全無感。</p>
<h2 id="概念位置">概念位置</h2>
<p>環境分離在 infra 成熟度階梯上對應第三階。它建立在宣告式 <a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a>（第二階）的基礎上 — 有了 state 追蹤和模組化描述之後，才能用「同一份 code、不同參數」的方式複製出多個隔離環境。</p>
<p>分離的實作方式有一條隔離強度光譜：從帳號級（不同雲端帳號，最強隔離）到目錄級（同一 repo 內各環境一個目錄，各自持有 state）到 workspace 級（同一份 code 用執行期切換 state，隔離最弱）。多數早期團隊在目錄級落腳，因為它在顯式邊界與維運成本之間取得平衡。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>以下狀況指向環境分離不足：</p>
<ul>
<li>在 staging 測試的變更意外影響了 production 的資源 — dev 跟 prod 共用同一份 state</li>
<li>某人的 <code>terraform apply</code> 把另一個環境的資源改掉了 — workspace 的隱性狀態切換導致打錯環境</li>
<li>dev 與 prod 的設定差異散落在 code 裡的 <code>if env == &quot;prod&quot;</code> 判斷 — 環境差異沒有集中在參數值裡</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>環境分離失效的典型形態是一次 <code>terraform apply</code> 改到了另一個環境的資源，以下的設計各自封住一條造成這種失效的路徑：</p>
<ul>
<li><strong>隔離層級</strong>：帳號級、目錄級、還是 workspace 級。判斷依據是團隊規模、合規要求、與維運餘裕</li>
<li><strong>參數化邊界</strong>：dev 與 prod 之間的差異全部用參數表達（instance size、multi-AZ、backup retention），module 內部不寫環境判斷</li>
<li><strong>state 位址分離</strong>：每個環境的 state backend 位址獨立，互不交叉</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> — 環境分離的前提是有可重用的 IaC 描述</li>
<li><a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State</a> — 每個環境持有獨立的 state 檔</li>
<li><a href="/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift</a> — 環境分離降低 drift 的跨環境影響範圍</li>
</ul>
]]></content:encoded></item><item><title>CloudTrail</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cloudtrail/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cloudtrail/</guid><description>&lt;p>CloudTrail 的核心職責是把 AWS 帳號內每一個 API 呼叫記錄成可查詢的稽核日誌 — 哪個身分、在什麼時間、對哪個資源、呼叫了哪個 API、結果是成功還是拒絕。它是事故排查和合規稽核的事實來源，記錄的「身分」對應 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> 裡的 identity。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>CloudTrail 在 infra 治理裡的角色是「發生了什麼」的最後防線。人工變更日誌記錄「為什麼改」，CloudTrail 記錄「改了什麼」— 兩者一起才能從事故回推到可回退的操作。&lt;/p>
&lt;p>CloudTrail 預設記錄 management event（建立、修改、刪除資源的 API 呼叫）並保留 90 天可查閱。要長期保存或記錄 data event（S3 物件存取、Lambda 呼叫等更細粒度的操作），需要建立 trail 並指定 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/s3/" data-link-title="S3" data-link-desc="AWS 的物件儲存服務，用 bucket &amp;#43; key 組織檔案，提供 versioning、加密、lifecycle 與存取控制">S3&lt;/a> bucket 儲存。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>以下狀況指向 CloudTrail 的使用場景：&lt;/p>
&lt;ul>
&lt;li>事故排查需要回答「誰在過去 24 小時改過這個 security group」— CloudTrail 的 &lt;code>LookupEvents&lt;/code> API 可以按事件名稱、資源類型或使用者名稱查詢&lt;/li>
&lt;li>安全稽核要求提供「過去 90 天內所有 IAM policy 變更的紀錄」— CloudTrail 是標準的證據來源&lt;/li>
&lt;li>發現不預期的資源變更（drift），需要確認是人為操作還是自動化觸發 — CloudTrail 的 &lt;code>userIdentity&lt;/code> 欄位區分人類使用者和 assume-role 的服務&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>事故排查或稽核要調閱紀錄的時候，CloudTrail 得拿得出涵蓋那段期間與那類操作的紀錄，而且內容保持寫入時的原樣；下面的設定分別影響這兩項條件：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>保留期限&lt;/strong>：預設 90 天免費查閱；超過需要建 trail 存到 S3，費用是 S3 儲存成本&lt;/li>
&lt;li>&lt;strong>事件範圍&lt;/strong>：management event 預設開啟；data event（S3 物件讀寫、Lambda invoke）要額外設定，且量大時儲存成本可觀&lt;/li>
&lt;li>&lt;strong>跨帳號整合&lt;/strong>：多帳號架構下，Organization trail 可以把所有帳號的事件集中到一個 S3 bucket&lt;/li>
&lt;li>&lt;strong>存取控制&lt;/strong>：CloudTrail 的 S3 bucket 本身要限制存取 — 能修改稽核日誌等於能掩蓋操作痕跡&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> — CloudTrail 記錄的是 IAM 身分的 API 呼叫&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift&lt;/a> — CloudTrail 是追查 drift 來源（誰手動改了什麼）的工具&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>CloudTrail 的核心職責是把 AWS 帳號內每一個 API 呼叫記錄成可查詢的稽核日誌 — 哪個身分、在什麼時間、對哪個資源、呼叫了哪個 API、結果是成功還是拒絕。它是事故排查和合規稽核的事實來源，記錄的「身分」對應 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> 裡的 identity。</p>
<h2 id="概念位置">概念位置</h2>
<p>CloudTrail 在 infra 治理裡的角色是「發生了什麼」的最後防線。人工變更日誌記錄「為什麼改」，CloudTrail 記錄「改了什麼」— 兩者一起才能從事故回推到可回退的操作。</p>
<p>CloudTrail 預設記錄 management event（建立、修改、刪除資源的 API 呼叫）並保留 90 天可查閱。要長期保存或記錄 data event（S3 物件存取、Lambda 呼叫等更細粒度的操作），需要建立 trail 並指定 <a href="/blog/devops/infra/knowledge-cards/s3/" data-link-title="S3" data-link-desc="AWS 的物件儲存服務，用 bucket &#43; key 組織檔案，提供 versioning、加密、lifecycle 與存取控制">S3</a> bucket 儲存。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>以下狀況指向 CloudTrail 的使用場景：</p>
<ul>
<li>事故排查需要回答「誰在過去 24 小時改過這個 security group」— CloudTrail 的 <code>LookupEvents</code> API 可以按事件名稱、資源類型或使用者名稱查詢</li>
<li>安全稽核要求提供「過去 90 天內所有 IAM policy 變更的紀錄」— CloudTrail 是標準的證據來源</li>
<li>發現不預期的資源變更（drift），需要確認是人為操作還是自動化觸發 — CloudTrail 的 <code>userIdentity</code> 欄位區分人類使用者和 assume-role 的服務</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>事故排查或稽核要調閱紀錄的時候，CloudTrail 得拿得出涵蓋那段期間與那類操作的紀錄，而且內容保持寫入時的原樣；下面的設定分別影響這兩項條件：</p>
<ul>
<li><strong>保留期限</strong>：預設 90 天免費查閱；超過需要建 trail 存到 S3，費用是 S3 儲存成本</li>
<li><strong>事件範圍</strong>：management event 預設開啟；data event（S3 物件讀寫、Lambda invoke）要額外設定，且量大時儲存成本可觀</li>
<li><strong>跨帳號整合</strong>：多帳號架構下，Organization trail 可以把所有帳號的事件集中到一個 S3 bucket</li>
<li><strong>存取控制</strong>：CloudTrail 的 S3 bucket 本身要限制存取 — 能修改稽核日誌等於能掩蓋操作痕跡</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> — CloudTrail 記錄的是 IAM 身分的 API 呼叫</li>
<li><a href="/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift</a> — CloudTrail 是追查 drift 來源（誰手動改了什麼）的工具</li>
</ul>
]]></content:encoded></item><item><title>ECS</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ecs/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ecs/</guid><description>&lt;p>ECS（Elastic Container Service）的核心職責是把容器映像排程到運算資源上執行，並管理它們的生命週期 — 健康檢查、失敗重啟、滾動更新。它是 AWS 上容器工作負載的預設起點，心智負擔低於 Kubernetes（EKS），但編排彈性也較受限。執行單位可以是自管的 EC2 instance，也可以是免運維的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/fargate/" data-link-title="Fargate" data-link-desc="AWS ECS 的無伺服器執行模式，由 AWS 代管運算實例，不需要管 EC2 capacity 或 AMI 更新">Fargate&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>ECS 在核心服務層裡的角色是「應用程式的執行載體」。它跑在 VPC 的 private subnet 裡，用 IAM task role 存取其他 AWS 資源，前面掛 ALB 接收流量。IaC 描述 ECS 時，重點在「接線」（subnet、security group、IAM role、target group）而非容器映像版本 — 映像版本由 CI/CD 在部署期注入。&lt;/p>
&lt;p>ECS 的執行模式分 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2&lt;/a> launch type（自己管運算實例、要管 AMI 更新與 capacity provider）和 Fargate launch type（AWS 代管運算、不需管實例）。Fargate 進一步降低運維面，代價是單位成本較高（同規格約多 20-40%）且不支援 GPU workload。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>以下狀況指向 ECS 相關問題：&lt;/p>
&lt;ul>
&lt;li>Task 頻繁被 kill 後重啟 — 健康檢查失敗或 OOM，先看 task 的 stopped reason 和 CloudWatch log&lt;/li>
&lt;li>部署後新版本遲遲不上線 — rolling update 的 minimum healthy percent 設太高，新 task 啟動空間不足&lt;/li>
&lt;li>Task 無法拉到 ECR image — 通常是 private subnet 沒有 NAT 或 VPC Endpoint 到 ECR&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>ECS 的設計決定多半是在劃責任交界：運算實例歸 AWS 還是團隊管理、權限歸哪個 role、映像版本歸 infra code 還是 CI/CD，最後再依服務的負載型態選擴容指標：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Launch type&lt;/strong>：Fargate（低運維、較高成本）還是 EC2（低成本、要管實例）。多數 web API 的初始選擇是 Fargate，流量穩定後再評估 EC2&lt;/li>
&lt;li>&lt;strong>Task IAM role&lt;/strong>：task execution role（拉 image 和寫 log 用）和 task role（應用程式存取其他 AWS 資源用）是兩個不同的 role，不要混用&lt;/li>
&lt;li>&lt;strong>映像版本解耦&lt;/strong>：task definition 裡的 image tag 由 CI/CD 部署期注入，infra code 不寫死版本號&lt;/li>
&lt;li>&lt;strong>Auto-scaling 指標&lt;/strong>：用 CPU / memory 還是 ALB request count，取決於服務是計算密集還是 IO 密集&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> — ECS task 跑在 private subnet 裡&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — ECS service 套用 security group 控制入站&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> — task role 與 execution role 是 ECS 的兩個身分接線&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB&lt;/a> — 流量透過 ALB target group 導入 ECS task&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>ECS（Elastic Container Service）的核心職責是把容器映像排程到運算資源上執行，並管理它們的生命週期 — 健康檢查、失敗重啟、滾動更新。它是 AWS 上容器工作負載的預設起點，心智負擔低於 Kubernetes（EKS），但編排彈性也較受限。執行單位可以是自管的 EC2 instance，也可以是免運維的 <a href="/blog/devops/infra/knowledge-cards/fargate/" data-link-title="Fargate" data-link-desc="AWS ECS 的無伺服器執行模式，由 AWS 代管運算實例，不需要管 EC2 capacity 或 AMI 更新">Fargate</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>ECS 在核心服務層裡的角色是「應用程式的執行載體」。它跑在 VPC 的 private subnet 裡，用 IAM task role 存取其他 AWS 資源，前面掛 ALB 接收流量。IaC 描述 ECS 時，重點在「接線」（subnet、security group、IAM role、target group）而非容器映像版本 — 映像版本由 CI/CD 在部署期注入。</p>
<p>ECS 的執行模式分 <a href="/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2</a> launch type（自己管運算實例、要管 AMI 更新與 capacity provider）和 Fargate launch type（AWS 代管運算、不需管實例）。Fargate 進一步降低運維面，代價是單位成本較高（同規格約多 20-40%）且不支援 GPU workload。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>以下狀況指向 ECS 相關問題：</p>
<ul>
<li>Task 頻繁被 kill 後重啟 — 健康檢查失敗或 OOM，先看 task 的 stopped reason 和 CloudWatch log</li>
<li>部署後新版本遲遲不上線 — rolling update 的 minimum healthy percent 設太高，新 task 啟動空間不足</li>
<li>Task 無法拉到 ECR image — 通常是 private subnet 沒有 NAT 或 VPC Endpoint 到 ECR</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>ECS 的設計決定多半是在劃責任交界：運算實例歸 AWS 還是團隊管理、權限歸哪個 role、映像版本歸 infra code 還是 CI/CD，最後再依服務的負載型態選擴容指標：</p>
<ul>
<li><strong>Launch type</strong>：Fargate（低運維、較高成本）還是 EC2（低成本、要管實例）。多數 web API 的初始選擇是 Fargate，流量穩定後再評估 EC2</li>
<li><strong>Task IAM role</strong>：task execution role（拉 image 和寫 log 用）和 task role（應用程式存取其他 AWS 資源用）是兩個不同的 role，不要混用</li>
<li><strong>映像版本解耦</strong>：task definition 裡的 image tag 由 CI/CD 部署期注入，infra code 不寫死版本號</li>
<li><strong>Auto-scaling 指標</strong>：用 CPU / memory 還是 ALB request count，取決於服務是計算密集還是 IO 密集</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> — ECS task 跑在 private subnet 裡</li>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — ECS service 套用 security group 控制入站</li>
<li><a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> — task role 與 execution role 是 ECS 的兩個身分接線</li>
<li><a href="/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB</a> — 流量透過 ALB target group 導入 ECS task</li>
</ul>
]]></content:encoded></item><item><title>ALB</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/alb/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/alb/</guid><description>&lt;p>ALB（Application Load Balancer）的核心職責是接收外部流量、根據規則（path、host header）把請求路由到後端的 target group，並用健康檢查持續驗證後端是否能服務。它是系統對外的第一個接觸點，跑在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">public subnet&lt;/a> 裡。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>ALB 在核心服務層裡的角色是「入口設施」。它掛在 public subnet 的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">security group&lt;/a> 上（入站允許 80/443），把流量導向 private subnet 裡的 ECS task 或 EC2 instance。ALB 本身是 stateless 的 — 重建一個 ALB 不會遺失資料，但會換掉它的 DNS 名稱，所以對外服務通常在 ALB 前面掛一個穩定的 Route 53 alias record。&lt;/p>
&lt;p>TLS 終結是 ALB 的標準職責：HTTPS listener 引用 ACM（AWS Certificate Manager）簽發的 TLS 憑證，ALB 處理加解密，後端收到的是 HTTP 明文。憑證由 ACM 自動續期，IaC 用 DNS 驗證方式描述憑證 — 讓「憑證存在、續期、掛載」整條鏈都進版本控制。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>以下狀況指向 ALB 相關問題：&lt;/p>
&lt;ul>
&lt;li>使用者看到 502 — ALB 轉發請求但後端回應異常（健康檢查可能通過但實際請求處理失敗），查 target group 的健康狀態和後端 log&lt;/li>
&lt;li>使用者看到 503 — target group 裡沒有健康的後端，通常是部署期間所有舊 task 停了但新 task 還沒通過健康檢查&lt;/li>
&lt;li>HTTPS 憑證過期警告 — 如果用 ACM 搭配 DNS 驗證，憑證自動續期；看到過期警告代表 DNS 驗證記錄被刪了或 ACM 服務異常&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>ALB 位於外部 client 與後端 target group 的交界：對 client 要保證連線加密、入口名稱穩定，對後端要判斷哪些 target 能接請求，設計決定分屬這兩個方向：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>健康檢查參數&lt;/strong>：檢查路徑（用應用層的 health endpoint、不用根路徑）、間隔、閾值。閾值太寬鬆會把壞掉的後端留在輪替裡，太嚴格會在部署瞬間誤判&lt;/li>
&lt;li>&lt;strong>HTTP → HTTPS redirect&lt;/strong>：port 80 的 listener 設定固定回應 301 redirect 到 443，確保所有流量走加密&lt;/li>
&lt;li>&lt;strong>TLS 憑證&lt;/strong>：用 ACM 搭配 DNS 驗證，讓憑證的簽發和續期自動化&lt;/li>
&lt;li>&lt;strong>穩定 DNS&lt;/strong>：ALB 前面掛 Route 53 alias record，對外暴露的是自己的 domain name 而非 ALB 的隨機 hostname&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> — ALB 跑在 public subnet，後端跑在 private subnet&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — ALB 的 security group 是系統對外唯一合理開放 0.0.0.0/0 的位置（僅限 80/443）&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS&lt;/a> — ALB 透過 target group 把流量導向 ECS task&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層&lt;/a> — ALB 作為 L7 入口在責任鏈的位置、跟單機 nginx 的選擇條件&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>ALB（Application Load Balancer）的核心職責是接收外部流量、根據規則（path、host header）把請求路由到後端的 target group，並用健康檢查持續驗證後端是否能服務。它是系統對外的第一個接觸點，跑在 <a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">public subnet</a> 裡。</p>
<h2 id="概念位置">概念位置</h2>
<p>ALB 在核心服務層裡的角色是「入口設施」。它掛在 public subnet 的 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">security group</a> 上（入站允許 80/443），把流量導向 private subnet 裡的 ECS task 或 EC2 instance。ALB 本身是 stateless 的 — 重建一個 ALB 不會遺失資料，但會換掉它的 DNS 名稱，所以對外服務通常在 ALB 前面掛一個穩定的 Route 53 alias record。</p>
<p>TLS 終結是 ALB 的標準職責：HTTPS listener 引用 ACM（AWS Certificate Manager）簽發的 TLS 憑證，ALB 處理加解密，後端收到的是 HTTP 明文。憑證由 ACM 自動續期，IaC 用 DNS 驗證方式描述憑證 — 讓「憑證存在、續期、掛載」整條鏈都進版本控制。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>以下狀況指向 ALB 相關問題：</p>
<ul>
<li>使用者看到 502 — ALB 轉發請求但後端回應異常（健康檢查可能通過但實際請求處理失敗），查 target group 的健康狀態和後端 log</li>
<li>使用者看到 503 — target group 裡沒有健康的後端，通常是部署期間所有舊 task 停了但新 task 還沒通過健康檢查</li>
<li>HTTPS 憑證過期警告 — 如果用 ACM 搭配 DNS 驗證，憑證自動續期；看到過期警告代表 DNS 驗證記錄被刪了或 ACM 服務異常</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>ALB 位於外部 client 與後端 target group 的交界：對 client 要保證連線加密、入口名稱穩定，對後端要判斷哪些 target 能接請求，設計決定分屬這兩個方向：</p>
<ul>
<li><strong>健康檢查參數</strong>：檢查路徑（用應用層的 health endpoint、不用根路徑）、間隔、閾值。閾值太寬鬆會把壞掉的後端留在輪替裡，太嚴格會在部署瞬間誤判</li>
<li><strong>HTTP → HTTPS redirect</strong>：port 80 的 listener 設定固定回應 301 redirect 到 443，確保所有流量走加密</li>
<li><strong>TLS 憑證</strong>：用 ACM 搭配 DNS 驗證，讓憑證的簽發和續期自動化</li>
<li><strong>穩定 DNS</strong>：ALB 前面掛 Route 53 alias record，對外暴露的是自己的 domain name 而非 ALB 的隨機 hostname</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> — ALB 跑在 public subnet，後端跑在 private subnet</li>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — ALB 的 security group 是系統對外唯一合理開放 0.0.0.0/0 的位置（僅限 80/443）</li>
<li><a href="/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS</a> — ALB 透過 target group 把流量導向 ECS task</li>
<li><a href="/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層</a> — ALB 作為 L7 入口在責任鏈的位置、跟單機 nginx 的選擇條件</li>
</ul>
]]></content:encoded></item><item><title>CIDR（Classless Inter-Domain Routing）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cidr/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cidr/</guid><description>&lt;p>CIDR（Classless Inter-Domain Routing）用前綴長度表示一段 IP 地址範圍。&lt;code>10.0.0.0/16&lt;/code> 表示前 16 bit 是網路位址、後 16 bit 是主機位址，提供約六萬五千個可用位址。前綴越短、範圍越大：&lt;code>/16&lt;/code> 比 &lt;code>/24&lt;/code>（約 256 個位址）大 256 倍。&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC&lt;/a> 和 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet&lt;/a> 的地址空間都用 CIDR 表示。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>CIDR 是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC&lt;/a> 規劃的起點決策。建立 VPC 時指定的 CIDR 區塊決定了這個 VPC 能容納多少 subnet 和多少資源。這個決策在建立後難以修改——事後擴張意味著追加 secondary CIDR，而追加的網段在 routing 與服務相容性上有限制。&lt;/p>
&lt;p>在 infra 系列中，CIDR 規劃出現在&lt;a href="https://tarrragon.github.io/blog/devops/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>的 VPC 段落。Terraform 的 &lt;code>cidrsubnet&lt;/code> 函式可以從 VPC 的 CIDR 自動切出 subnet 的子網段，避免手動計算。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>CIDR 規劃出問題的訊號有兩類。第一類是地址耗盡：subnet 切不出新的子網段、或 subnet 內的 IP 分配用完，新資源無法取得位址。第二類是網段衝突：需要透過 VPC peering、Transit Gateway 或 VPN 互連兩個 VPC 時，發現兩端的 CIDR 重疊，路由無法解析，peering 建立失敗。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>VPC 建立之後，原本的 CIDR 區段改不掉，事後擴張只能追加 secondary CIDR，因此規劃時寫定的區段要先算進日後的成長與互連：&lt;/p>
&lt;ul>
&lt;li>大小：單一環境用 &lt;code>/16&lt;/code> 通常足夠寬裕，切成 &lt;code>/20&lt;/code> 的 subnet 可分配 16 個子網段&lt;/li>
&lt;li>不重疊：多個環境（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;/li>
&lt;li>與地端的協調：如果未來可能接 VPN 回地端機房，CIDR 要避開地端已使用的私有網段&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC&lt;/a> — 用 CIDR 區塊定義的邏輯隔離網段&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> — 從 VPC CIDR 切出的子網段&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>CIDR（Classless Inter-Domain Routing）用前綴長度表示一段 IP 地址範圍。<code>10.0.0.0/16</code> 表示前 16 bit 是網路位址、後 16 bit 是主機位址，提供約六萬五千個可用位址。前綴越短、範圍越大：<code>/16</code> 比 <code>/24</code>（約 256 個位址）大 256 倍。<a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> 和 <a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet</a> 的地址空間都用 CIDR 表示。</p>
<h2 id="概念位置">概念位置</h2>
<p>CIDR 是 <a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> 規劃的起點決策。建立 VPC 時指定的 CIDR 區塊決定了這個 VPC 能容納多少 subnet 和多少資源。這個決策在建立後難以修改——事後擴張意味著追加 secondary CIDR，而追加的網段在 routing 與服務相容性上有限制。</p>
<p>在 infra 系列中，CIDR 規劃出現在<a href="/blog/devops/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>的 VPC 段落。Terraform 的 <code>cidrsubnet</code> 函式可以從 VPC 的 CIDR 自動切出 subnet 的子網段，避免手動計算。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>CIDR 規劃出問題的訊號有兩類。第一類是地址耗盡：subnet 切不出新的子網段、或 subnet 內的 IP 分配用完，新資源無法取得位址。第二類是網段衝突：需要透過 VPC peering、Transit Gateway 或 VPN 互連兩個 VPC 時，發現兩端的 CIDR 重疊，路由無法解析，peering 建立失敗。</p>
<h2 id="設計責任">設計責任</h2>
<p>VPC 建立之後，原本的 CIDR 區段改不掉，事後擴張只能追加 secondary CIDR，因此規劃時寫定的區段要先算進日後的成長與互連：</p>
<ul>
<li>大小：單一環境用 <code>/16</code> 通常足夠寬裕，切成 <code>/20</code> 的 subnet 可分配 16 個子網段</li>
<li>不重疊：多個環境（dev <code>10.0.0.0/16</code>、staging <code>10.1.0.0/16</code>、prod <code>10.2.0.0/16</code>）用連續但不重疊的區段，為日後互連預留空間</li>
<li>與地端的協調：如果未來可能接 VPN 回地端機房，CIDR 要避開地端已使用的私有網段</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> — 用 CIDR 區塊定義的邏輯隔離網段</li>
<li><a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> — 從 VPC CIDR 切出的子網段</li>
</ul>
]]></content:encoded></item><item><title>IAM（Identity and Access Management）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/</guid><description>&lt;p>IAM（Identity and Access Management）是雲端平台用來回答「某個身分能不能對某個資源做某件事」的授權系統。它把授權拆成三個獨立的元件：identity（身分，發起動作的主體）、policy（政策，描述「允許或拒絕對哪些資源做哪些動作」的規則）、role（角色，一組可以被臨時取得的權限集合，常見透過 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/oidc/" data-link-title="OIDC Federation（OIDC 聯合）" data-link-desc="讓 CI/CD 平台用短期 token 取代長期 access key 存取雲端資源的身分聯合機制">OIDC&lt;/a> 換發短期憑證）。這三者的分工是後面所有憑證決策的前提。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>IAM 是&lt;a href="https://tarrragon.github.io/blog/devops/infra/02-identity-credentials/iam-oidc-privilege-boundary/" data-link-title="身分與存取憑證地基 — IAM 模型、OIDC 短期憑證與權限邊界設計" data-link-desc="IAM 的 identity / policy / role 三元件、最小權限的持續收斂、用 OIDC 取代長期 access key，以及 SCP 與 Permissions Boundary 的環境隔離">模組二：身分與存取憑證地基&lt;/a>的核心機制。它決定了誰能動什麼——人、服務、CI pipeline 各拿剛好夠用的權限（最小權限），憑證有明確的生命週期。身分層失守的代價在五個 infra 責任面向中最高，因為它是其他所有資源的閘門。&lt;/p>
&lt;p>在 infra 系列中，IAM 的設計從三個維度展開：最小權限的持續收斂（不是一次設定就結束）、用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/oidc/" data-link-title="OIDC Federation（OIDC 聯合）" data-link-desc="讓 CI/CD 平台用短期 token 取代長期 access key 存取雲端資源的身分聯合機制">OIDC&lt;/a> 短期憑證取代長期 access key、以及跨帳號的權限邊界（SCP + Permissions Boundary）。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>IAM 需要關注的訊號：某個 role 的 policy 有 &lt;code>*:*&lt;/code> 或 &lt;code>AdministratorAccess&lt;/code>（權限過大）；credential report 顯示有長期 access key 超過 90 天未輪替（憑證散落風險）；Access Analyzer 顯示某個 role 的實際使用 action 遠少於授予的 action（權限擴散）；dev 環境的 CI role 能列出 production 的資源（環境隔離失效）。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>IAM 是其他所有資源的閘門，一個權限過寬的 role 等於把它碰得到的資源全部交給持有那份憑證的身分，設計時先劃清身分與權限的邊界，再排定之後收斂的節奏：&lt;/p>
&lt;ul>
&lt;li>身分類型區分：人用 SSO 登入（強制 MFA）、雲上服務用 instance profile / task role、雲外 CI 用 OIDC 聯合&lt;/li>
&lt;li>權限分級：admin / operator / viewer 三級，見&lt;a href="https://tarrragon.github.io/blog/devops/infra/02-identity-credentials/team-access-management/" data-link-title="團隊權限分級與存取管理" data-link-desc="用 admin / operator / viewer 三級劃分團隊成員的雲端操作權限，設計臨時提權流程、定期 access review 節奏，以及 contractor 與外部 vendor 的存取邊界">團隊權限分級&lt;/a>&lt;/li>
&lt;li>環境隔離：每個環境的 role 不能存取其他環境的資源&lt;/li>
&lt;li>收斂節奏：定期用 Access Analyzer 觀察實際使用的 action，收掉沒用到的權限&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/oidc/" data-link-title="OIDC Federation（OIDC 聯合）" data-link-desc="讓 CI/CD 平台用短期 token 取代長期 access key 存取雲端資源的身分聯合機制">OIDC&lt;/a> — 用短期 token 取代長期 access key 的聯合機制&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — 網路層的存取控制（IAM 是 API 層的存取控制）&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cloudtrail/" data-link-title="CloudTrail" data-link-desc="AWS 的 API 層稽核日誌服務，記錄誰在什麼時候對什麼資源做了什麼操作">CloudTrail&lt;/a> — 記錄 IAM 身分的 API 呼叫歷史&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>IAM（Identity and Access Management）是雲端平台用來回答「某個身分能不能對某個資源做某件事」的授權系統。它把授權拆成三個獨立的元件：identity（身分，發起動作的主體）、policy（政策，描述「允許或拒絕對哪些資源做哪些動作」的規則）、role（角色，一組可以被臨時取得的權限集合，常見透過 <a href="/blog/devops/infra/knowledge-cards/oidc/" data-link-title="OIDC Federation（OIDC 聯合）" data-link-desc="讓 CI/CD 平台用短期 token 取代長期 access key 存取雲端資源的身分聯合機制">OIDC</a> 換發短期憑證）。這三者的分工是後面所有憑證決策的前提。</p>
<h2 id="概念位置">概念位置</h2>
<p>IAM 是<a href="/blog/devops/infra/02-identity-credentials/iam-oidc-privilege-boundary/" data-link-title="身分與存取憑證地基 — IAM 模型、OIDC 短期憑證與權限邊界設計" data-link-desc="IAM 的 identity / policy / role 三元件、最小權限的持續收斂、用 OIDC 取代長期 access key，以及 SCP 與 Permissions Boundary 的環境隔離">模組二：身分與存取憑證地基</a>的核心機制。它決定了誰能動什麼——人、服務、CI pipeline 各拿剛好夠用的權限（最小權限），憑證有明確的生命週期。身分層失守的代價在五個 infra 責任面向中最高，因為它是其他所有資源的閘門。</p>
<p>在 infra 系列中，IAM 的設計從三個維度展開：最小權限的持續收斂（不是一次設定就結束）、用 <a href="/blog/devops/infra/knowledge-cards/oidc/" data-link-title="OIDC Federation（OIDC 聯合）" data-link-desc="讓 CI/CD 平台用短期 token 取代長期 access key 存取雲端資源的身分聯合機制">OIDC</a> 短期憑證取代長期 access key、以及跨帳號的權限邊界（SCP + Permissions Boundary）。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>IAM 需要關注的訊號：某個 role 的 policy 有 <code>*:*</code> 或 <code>AdministratorAccess</code>（權限過大）；credential report 顯示有長期 access key 超過 90 天未輪替（憑證散落風險）；Access Analyzer 顯示某個 role 的實際使用 action 遠少於授予的 action（權限擴散）；dev 環境的 CI role 能列出 production 的資源（環境隔離失效）。</p>
<h2 id="設計責任">設計責任</h2>
<p>IAM 是其他所有資源的閘門，一個權限過寬的 role 等於把它碰得到的資源全部交給持有那份憑證的身分，設計時先劃清身分與權限的邊界，再排定之後收斂的節奏：</p>
<ul>
<li>身分類型區分：人用 SSO 登入（強制 MFA）、雲上服務用 instance profile / task role、雲外 CI 用 OIDC 聯合</li>
<li>權限分級：admin / operator / viewer 三級，見<a href="/blog/devops/infra/02-identity-credentials/team-access-management/" data-link-title="團隊權限分級與存取管理" data-link-desc="用 admin / operator / viewer 三級劃分團隊成員的雲端操作權限，設計臨時提權流程、定期 access review 節奏，以及 contractor 與外部 vendor 的存取邊界">團隊權限分級</a></li>
<li>環境隔離：每個環境的 role 不能存取其他環境的資源</li>
<li>收斂節奏：定期用 Access Analyzer 觀察實際使用的 action，收掉沒用到的權限</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/oidc/" data-link-title="OIDC Federation（OIDC 聯合）" data-link-desc="讓 CI/CD 平台用短期 token 取代長期 access key 存取雲端資源的身分聯合機制">OIDC</a> — 用短期 token 取代長期 access key 的聯合機制</li>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — 網路層的存取控制（IAM 是 API 層的存取控制）</li>
<li><a href="/blog/devops/infra/knowledge-cards/cloudtrail/" data-link-title="CloudTrail" data-link-desc="AWS 的 API 層稽核日誌服務，記錄誰在什麼時候對什麼資源做了什麼操作">CloudTrail</a> — 記錄 IAM 身分的 API 呼叫歷史</li>
</ul>
]]></content:encoded></item><item><title>Route Table</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/route-table/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/route-table/</guid><description>&lt;p>Route table 是一組轉送規則，掛在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet&lt;/a> 上，定義「目的地是某個網段的封包該往哪送」。每個 subnet 關聯一張 route table，封包離開 subnet 時逐條比對規則、走最長前綴匹配的那一條。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Route table 決定了一個 subnet 是 public 還是 private。技術上的差別只有一行：route table 裡有沒有一條 &lt;code>0.0.0.0/0 → Internet Gateway&lt;/code> 的預設路由。有這條路由的 subnet 是 public（封包可以直接出網、外部也可以連入）；把預設路由指向 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT Gateway&lt;/a> 的 subnet 是 private（只能主動出站、外部無法入站）。subnet 本身的屬性不含 public/private 標記，性質完全由關聯的 route table 賦予。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>private subnet 的服務突然拉不到外部套件或第三方 API 全部逾時時，排查路徑的第一步是檢查該 subnet 關聯的 route table：預設路由是否指向健康的 NAT Gateway。如果只有某一個可用區的節點受影響，通常是那一區的 NAT Gateway 或其所在 subnet 出狀況。&lt;/p>
&lt;p>另一個常見訊號是新建的 subnet 沒有手動關聯 route table，被 VPC 的 main route table 自動關聯——main route table 的預設設定可能跟預期不符。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>subnet 是 public 還是 private 由它關聯的 route table 決定，而沒有明確關聯的 subnet 會自動套用 main route table，所以路由設計集中在：每個 subnet 的預設路由指向什麼（Internet Gateway / NAT Gateway / Transit Gateway / 無）、VPC 內部流量是否需要自訂路由（peering、endpoint）、以及 main route table 是否該保持空白以避免新 subnet 意外取得對外路由。每一條路由的目的地網段和目標要在 IaC 裡明確描述，讓 route table 的語意可被 review。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> — route table 掛在 subnet 上&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT&lt;/a> — private subnet 的預設路由目標&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC&lt;/a> — route table 存在於 VPC 內&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Route table 是一組轉送規則，掛在 <a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">subnet</a> 上，定義「目的地是某個網段的封包該往哪送」。每個 subnet 關聯一張 route table，封包離開 subnet 時逐條比對規則、走最長前綴匹配的那一條。</p>
<h2 id="概念位置">概念位置</h2>
<p>Route table 決定了一個 subnet 是 public 還是 private。技術上的差別只有一行：route table 裡有沒有一條 <code>0.0.0.0/0 → Internet Gateway</code> 的預設路由。有這條路由的 subnet 是 public（封包可以直接出網、外部也可以連入）；把預設路由指向 <a href="/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT Gateway</a> 的 subnet 是 private（只能主動出站、外部無法入站）。subnet 本身的屬性不含 public/private 標記，性質完全由關聯的 route table 賦予。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>private subnet 的服務突然拉不到外部套件或第三方 API 全部逾時時，排查路徑的第一步是檢查該 subnet 關聯的 route table：預設路由是否指向健康的 NAT Gateway。如果只有某一個可用區的節點受影響，通常是那一區的 NAT Gateway 或其所在 subnet 出狀況。</p>
<p>另一個常見訊號是新建的 subnet 沒有手動關聯 route table，被 VPC 的 main route table 自動關聯——main route table 的預設設定可能跟預期不符。</p>
<h2 id="設計責任">設計責任</h2>
<p>subnet 是 public 還是 private 由它關聯的 route table 決定，而沒有明確關聯的 subnet 會自動套用 main route table，所以路由設計集中在：每個 subnet 的預設路由指向什麼（Internet Gateway / NAT Gateway / Transit Gateway / 無）、VPC 內部流量是否需要自訂路由（peering、endpoint）、以及 main route table 是否該保持空白以避免新 subnet 意外取得對外路由。每一條路由的目的地網段和目標要在 IaC 裡明確描述，讓 route table 的語意可被 review。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> — route table 掛在 subnet 上</li>
<li><a href="/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT</a> — private subnet 的預設路由目標</li>
<li><a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> — route table 存在於 VPC 內</li>
</ul>
]]></content:encoded></item><item><title>SCP (Service Control Policy)</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/scp/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/scp/</guid><description>&lt;p>Service Control Policy（SCP）是 AWS Organizations 裡套用在 OU 或帳號上的權限上限。SCP 不授予權限——它設定一個天花板，限制該範圍內的 IAM 能做什麼。即使帳號內有 &lt;code>AdministratorAccess&lt;/code> 的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> role，SCP deny 的操作仍然被擋下。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>SCP 跟 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> policy 的關係是交集而非覆蓋：一個操作要同時被 SCP 允許且被 IAM policy 允許才會生效。SCP 的設計目的是讓組織管理者設定「即使帳號管理員也做不了」的護欄，常見的 day-1 SCP 包括：禁止關閉 CloudTrail、禁止離開指定 region、禁止刪除 VPC Flow Logs。&lt;/p>
&lt;p>SCP 套用在 OU 上時會繼承給 OU 下所有帳號和子 OU。Management account（Organizations 的根帳號）不受 SCP 約束——這是設計上的逃生門，也是 management account 應該盡量不跑 workload 的原因。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>當帳號內的 IAM role 執行某個操作時收到 &lt;code>AccessDeniedException&lt;/code>、但該 role 的 IAM policy 確實允許該操作，SCP 是第一個要檢查的位置。另一個訊號是新帳號加入 OU 後某些原本能用的服務突然不可用——通常是繼承了 OU 的 SCP deny list。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>SCP 會繼承給 OU 底下每個帳號與子 OU，連帳號管理員都越不過，所以策略選錯時整個 OU 的帳號一起受影響；第一個要定的是用 deny-list 策略（預設全開、明確列出禁止項）還是 allow-list 策略（預設全關、明確列出允許項）。Deny-list 較常見也較易維護——只需要管「哪些該禁」。Allow-list 更嚴格但維護成本高——每次有新服務需求都要更新 SCP。&lt;/p>
&lt;p>套用 SCP 前要確認不會擋到正在運作的服務——先在 sandbox OU 測試，確認既有 workload 不受影響再推到 workload OU。SCP 的變更跟 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> 一樣要走 PR review。跨帳號策略的完整設計見&lt;a href="https://tarrragon.github.io/blog/devops/infra/02-identity-credentials/multi-account-strategy/" data-link-title="跨帳號策略 — Organizations、SCP 與帳號工廠" data-link-desc="用 AWS Organizations 把環境拆成獨立帳號、用 SCP 設定連管理員都越不過的護欄、用帳號工廠讓每個新帳號自帶安全基線">跨帳號策略文章&lt;/a>。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> — SCP 是 IAM policy 的上層天花板&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/environment-separation/" data-link-title="Environment Separation（環境分離）" data-link-desc="把同一套基礎設施定義複製成多份隔離的執行實例，各有獨立 state 與故障半徑">環境分離&lt;/a> — SCP 靠 OU 結構實現環境之間的權限隔離&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Service Control Policy（SCP）是 AWS Organizations 裡套用在 OU 或帳號上的權限上限。SCP 不授予權限——它設定一個天花板，限制該範圍內的 IAM 能做什麼。即使帳號內有 <code>AdministratorAccess</code> 的 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> role，SCP deny 的操作仍然被擋下。</p>
<h2 id="概念位置">概念位置</h2>
<p>SCP 跟 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> policy 的關係是交集而非覆蓋：一個操作要同時被 SCP 允許且被 IAM policy 允許才會生效。SCP 的設計目的是讓組織管理者設定「即使帳號管理員也做不了」的護欄，常見的 day-1 SCP 包括：禁止關閉 CloudTrail、禁止離開指定 region、禁止刪除 VPC Flow Logs。</p>
<p>SCP 套用在 OU 上時會繼承給 OU 下所有帳號和子 OU。Management account（Organizations 的根帳號）不受 SCP 約束——這是設計上的逃生門，也是 management account 應該盡量不跑 workload 的原因。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>當帳號內的 IAM role 執行某個操作時收到 <code>AccessDeniedException</code>、但該 role 的 IAM policy 確實允許該操作，SCP 是第一個要檢查的位置。另一個訊號是新帳號加入 OU 後某些原本能用的服務突然不可用——通常是繼承了 OU 的 SCP deny list。</p>
<h2 id="設計責任">設計責任</h2>
<p>SCP 會繼承給 OU 底下每個帳號與子 OU，連帳號管理員都越不過，所以策略選錯時整個 OU 的帳號一起受影響；第一個要定的是用 deny-list 策略（預設全開、明確列出禁止項）還是 allow-list 策略（預設全關、明確列出允許項）。Deny-list 較常見也較易維護——只需要管「哪些該禁」。Allow-list 更嚴格但維護成本高——每次有新服務需求都要更新 SCP。</p>
<p>套用 SCP 前要確認不會擋到正在運作的服務——先在 sandbox OU 測試，確認既有 workload 不受影響再推到 workload OU。SCP 的變更跟 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> 一樣要走 PR review。跨帳號策略的完整設計見<a href="/blog/devops/infra/02-identity-credentials/multi-account-strategy/" data-link-title="跨帳號策略 — Organizations、SCP 與帳號工廠" data-link-desc="用 AWS Organizations 把環境拆成獨立帳號、用 SCP 設定連管理員都越不過的護欄、用帳號工廠讓每個新帳號自帶安全基線">跨帳號策略文章</a>。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> — SCP 是 IAM policy 的上層天花板</li>
<li><a href="/blog/devops/infra/knowledge-cards/environment-separation/" data-link-title="Environment Separation（環境分離）" data-link-desc="把同一套基礎設施定義複製成多份隔離的執行實例，各有獨立 state 與故障半徑">環境分離</a> — SCP 靠 OU 結構實現環境之間的權限隔離</li>
</ul>
]]></content:encoded></item><item><title>Remote State Backend</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/remote-state-backend/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/remote-state-backend/</guid><description>&lt;p>Remote state backend 是 IaC 工具用來存放 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">state&lt;/a> 的共享儲存機制。它要同時滿足三件事：持久保存（不會因為某台筆電故障而遺失）、防止並行寫入衝突（兩個人不能同時 apply）、以及保護敏感內容（state 內含資源的真實屬性，可能包含密碼或 key）。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>State 是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> 工具對現實的唯一記憶。把它放在本地檔案系統等於把整個基礎設施的記憶綁在一台機器上——換人接手、換台電腦、或兩人同時 apply，記憶就分裂了。Remote state backend 解決的是「讓 state 變成團隊共用的、有保護的事實來源」。&lt;/p>
&lt;p>典型的自管組合是 S3（存放 state 檔、開 versioning 和加密）加上 DynamoDB（提供 apply 時的並行鎖）。託管服務（Terraform Cloud、Spacelift）把存放、鎖和加密包在一起，用月費換掉配置和維運負擔。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>本地 state 的失敗訊號是：跑 &lt;code>terraform plan&lt;/code> 時出現「想刪掉」明知存在的資源——通常代表本地 state 跟雲端實際狀態已經脫節。另一個訊號是兩個人同時跑 apply 但沒有任何鎖機制阻擋——結果是互相覆蓋對方的變更，state 進入不一致狀態。&lt;/p>
&lt;p>Remote backend 設定後，如果 &lt;code>terraform init&lt;/code> 提示 state 遷移確認，代表正在從本地搬到遠端——這是正確的一次性操作，但搬遷過程中不能有其他人在 apply。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Remote state backend 同時保管 state 的完整副本與其中的敏感屬性，存取控制或版本保留一旦出錯，受影響的是整個 IaC 工作流程的依據；選擇時要定的是：自管還是託管（取決於團隊規模和維運餘裕）、state bucket 的加密與存取控制（誰能讀 state 等於誰能看到所有資源的敏感屬性）、versioning 是否開啟（是 state 回捲的唯一退路）、以及鎖表的設定（DynamoDB 的表名和 partition key）。&lt;/p>
&lt;p>State 絕不能進 git——它含明文敏感值，推進版控等於把密碼寫進每個 clone 的歷史裡。Backend 設定本身（bucket name、region、鎖表名稱）寫在 HCL 裡進 git，state 檔本身只存在 backend 裡。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State&lt;/a> — remote backend 存放的對象&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift&lt;/a> — state 與現實不一致時的現象&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> — remote state backend 是 IaC 工具的基礎設施&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Remote state backend 是 IaC 工具用來存放 <a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">state</a> 的共享儲存機制。它要同時滿足三件事：持久保存（不會因為某台筆電故障而遺失）、防止並行寫入衝突（兩個人不能同時 apply）、以及保護敏感內容（state 內含資源的真實屬性，可能包含密碼或 key）。</p>
<h2 id="概念位置">概念位置</h2>
<p>State 是 <a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> 工具對現實的唯一記憶。把它放在本地檔案系統等於把整個基礎設施的記憶綁在一台機器上——換人接手、換台電腦、或兩人同時 apply，記憶就分裂了。Remote state backend 解決的是「讓 state 變成團隊共用的、有保護的事實來源」。</p>
<p>典型的自管組合是 S3（存放 state 檔、開 versioning 和加密）加上 DynamoDB（提供 apply 時的並行鎖）。託管服務（Terraform Cloud、Spacelift）把存放、鎖和加密包在一起，用月費換掉配置和維運負擔。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>本地 state 的失敗訊號是：跑 <code>terraform plan</code> 時出現「想刪掉」明知存在的資源——通常代表本地 state 跟雲端實際狀態已經脫節。另一個訊號是兩個人同時跑 apply 但沒有任何鎖機制阻擋——結果是互相覆蓋對方的變更，state 進入不一致狀態。</p>
<p>Remote backend 設定後，如果 <code>terraform init</code> 提示 state 遷移確認，代表正在從本地搬到遠端——這是正確的一次性操作，但搬遷過程中不能有其他人在 apply。</p>
<h2 id="設計責任">設計責任</h2>
<p>Remote state backend 同時保管 state 的完整副本與其中的敏感屬性，存取控制或版本保留一旦出錯，受影響的是整個 IaC 工作流程的依據；選擇時要定的是：自管還是託管（取決於團隊規模和維運餘裕）、state bucket 的加密與存取控制（誰能讀 state 等於誰能看到所有資源的敏感屬性）、versioning 是否開啟（是 state 回捲的唯一退路）、以及鎖表的設定（DynamoDB 的表名和 partition key）。</p>
<p>State 絕不能進 git——它含明文敏感值，推進版控等於把密碼寫進每個 clone 的歷史裡。Backend 設定本身（bucket name、region、鎖表名稱）寫在 HCL 裡進 git，state 檔本身只存在 backend 裡。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State</a> — remote backend 存放的對象</li>
<li><a href="/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift</a> — state 與現實不一致時的現象</li>
<li><a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> — remote state backend 是 IaC 工具的基礎設施</li>
</ul>
]]></content:encoded></item><item><title>Trust Policy</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/trust-policy/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/trust-policy/</guid><description>&lt;p>Trust policy 是附加在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> role 上的一份 JSON 文件，定義「誰被允許臨時取得（assume）這個 role 的權限」。跟 IAM policy 的差別是：IAM policy 描述「這個 role 能做什麼」，trust policy 描述「誰能變成這個 role」。兩者合在一起才構成完整的授權——先過 trust policy 的門、再受 IAM policy 的限。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Trust policy 是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/oidc/" data-link-title="OIDC Federation（OIDC 聯合）" data-link-desc="讓 CI/CD 平台用短期 token 取代長期 access key 存取雲端資源的身分聯合機制">OIDC&lt;/a> 聯合的核心配件。當 CI/CD 平台（GitHub Actions、GitLab CI）要用短期憑證存取雲端資源時，trust policy 用 OIDC token 裡的 claim（issuer、audience、subject）決定「這個 token 代表的身分能不能 assume 這個 role」。&lt;/p>
&lt;p>Trust policy 的設計要點是 claim 的收斂程度。只驗 issuer 而不驗 repo 和 branch，等於同一個 CI 平台上所有專案都能 assume 這個 role——這是常見的設定陷阱。收到最緊意味著限定到「某個 org 的某個 repo 的某個 branch 或 environment」。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>&lt;code>sts:AssumeRoleWithWebIdentity&lt;/code> 呼叫失敗、回傳 &lt;code>AccessDenied&lt;/code> 時，問題通常在 trust policy 的 condition 比對不上。排查路徑是把 CI 平台簽發的 OIDC token decode（JWT 的 payload 部分），逐一比對 token 裡的 &lt;code>iss&lt;/code>、&lt;code>aud&lt;/code>、&lt;code>sub&lt;/code> 跟 trust policy 的 condition 值。&lt;/p>
&lt;p>另一個訊號是 trust policy 的 condition 用了 &lt;code>StringLike&lt;/code> 但 pattern 太寬（如 &lt;code>repo:my-org/*&lt;/code>），讓非預期的 repo 也能 assume——這類過寬的 trust policy 在安全稽核時會被標記。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>trust policy 只管哪些身分能取得這個 role，取得之後能做什麼由 role 的 IAM policy 另外限制，所以 trust policy 的條件全部圍繞 token 的 claim：允許哪些外部身分 assume（issuer + subject 的精確匹配）、audience 是否需要額外驗證（AWS 預設 &lt;code>sts.amazonaws.com&lt;/code>）、以及是否把 plan role 和 apply role 分開（plan 只需 read-only、apply 需要 write，用兩個 role 各自設不同 trust condition 來區分 branch 或 environment）。&lt;/p>
&lt;p>Trust policy 的變更跟 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> policy 一樣要走 PR review——因為改寬一個 condition 就等於給更多外部身分開門。設定指南見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/02-identity-credentials/oidc-trust-policy-setup/" data-link-title="OIDC Trust Policy 設定指南" data-link-desc="GitHub Actions 與 AWS 之間的 OIDC 聯合設定：建立 provider、設計 trust policy 的 claim 收斂、plan 與 apply role 分離、常見錯誤排查">OIDC Trust Policy 設定指南&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Trust policy 是附加在 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> role 上的一份 JSON 文件，定義「誰被允許臨時取得（assume）這個 role 的權限」。跟 IAM policy 的差別是：IAM policy 描述「這個 role 能做什麼」，trust policy 描述「誰能變成這個 role」。兩者合在一起才構成完整的授權——先過 trust policy 的門、再受 IAM policy 的限。</p>
<h2 id="概念位置">概念位置</h2>
<p>Trust policy 是 <a href="/blog/devops/infra/knowledge-cards/oidc/" data-link-title="OIDC Federation（OIDC 聯合）" data-link-desc="讓 CI/CD 平台用短期 token 取代長期 access key 存取雲端資源的身分聯合機制">OIDC</a> 聯合的核心配件。當 CI/CD 平台（GitHub Actions、GitLab CI）要用短期憑證存取雲端資源時，trust policy 用 OIDC token 裡的 claim（issuer、audience、subject）決定「這個 token 代表的身分能不能 assume 這個 role」。</p>
<p>Trust policy 的設計要點是 claim 的收斂程度。只驗 issuer 而不驗 repo 和 branch，等於同一個 CI 平台上所有專案都能 assume 這個 role——這是常見的設定陷阱。收到最緊意味著限定到「某個 org 的某個 repo 的某個 branch 或 environment」。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p><code>sts:AssumeRoleWithWebIdentity</code> 呼叫失敗、回傳 <code>AccessDenied</code> 時，問題通常在 trust policy 的 condition 比對不上。排查路徑是把 CI 平台簽發的 OIDC token decode（JWT 的 payload 部分），逐一比對 token 裡的 <code>iss</code>、<code>aud</code>、<code>sub</code> 跟 trust policy 的 condition 值。</p>
<p>另一個訊號是 trust policy 的 condition 用了 <code>StringLike</code> 但 pattern 太寬（如 <code>repo:my-org/*</code>），讓非預期的 repo 也能 assume——這類過寬的 trust policy 在安全稽核時會被標記。</p>
<h2 id="設計責任">設計責任</h2>
<p>trust policy 只管哪些身分能取得這個 role，取得之後能做什麼由 role 的 IAM policy 另外限制，所以 trust policy 的條件全部圍繞 token 的 claim：允許哪些外部身分 assume（issuer + subject 的精確匹配）、audience 是否需要額外驗證（AWS 預設 <code>sts.amazonaws.com</code>）、以及是否把 plan role 和 apply role 分開（plan 只需 read-only、apply 需要 write，用兩個 role 各自設不同 trust condition 來區分 branch 或 environment）。</p>
<p>Trust policy 的變更跟 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> policy 一樣要走 PR review——因為改寬一個 condition 就等於給更多外部身分開門。設定指南見 <a href="/blog/devops/infra/02-identity-credentials/oidc-trust-policy-setup/" data-link-title="OIDC Trust Policy 設定指南" data-link-desc="GitHub Actions 與 AWS 之間的 OIDC 聯合設定：建立 provider、設計 trust policy 的 claim 收斂、plan 與 apply role 分離、常見錯誤排查">OIDC Trust Policy 設定指南</a>。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> — trust policy 是 IAM role 的一部分</li>
<li><a href="/blog/devops/infra/knowledge-cards/oidc/" data-link-title="OIDC Federation（OIDC 聯合）" data-link-desc="讓 CI/CD 平台用短期 token 取代長期 access key 存取雲端資源的身分聯合機制">OIDC</a> — trust policy 用 OIDC token 的 claim 做 assume 判斷</li>
</ul>
]]></content:encoded></item><item><title>Deletion Protection</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/deletion-protection/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/deletion-protection/</guid><description>&lt;p>Deletion protection 是雲端平台在資源層級提供的防護機制：開啟後，任何刪除該資源的操作（Console 點按、CLI 指令、&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> 的 destroy）都會被擋下，必須先顯式關閉保護才能執行刪除。這個額外步驟的目的是防止手滑、批次操作誤傷、以及 Terraform plan 裡意外出現的 destroy。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Deletion protection 是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/05-core-services/stateful-protection-dependency/" data-link-title="Stateful 資源保護與跨服務依賴表達" data-link-desc="stateful 資源的保護策略（multi-AZ、備份、刪除保護）、stateful 與 stateless 的操作差異，以及用 output 與 data source 表達服務間依賴">stateful 資源保護&lt;/a>的第一道防線。運算節點可以隨時重建，資料一旦遺失通常無法重來——這條分界線決定了哪些資源該開保護。對 stateful 資源（資料庫、持久化儲存）來說，這是 day-1 該開的設定，不是「等穩定再開」的選項。&lt;/p>
&lt;p>不同 AWS 服務的保護機制名稱不同但行為一致：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>服務&lt;/th>
 &lt;th>屬性名稱&lt;/th>
 &lt;th>保護對象&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/rds/" data-link-title="RDS" data-link-desc="AWS 的受管關聯式資料庫服務，代管備份、更新與 failover，讓使用者專注在 schema 和查詢">RDS&lt;/a>&lt;/td>
 &lt;td>&lt;code>deletion_protection&lt;/code>&lt;/td>
 &lt;td>資料庫 instance&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>EC2&lt;/td>
 &lt;td>&lt;code>disable_api_termination&lt;/code>&lt;/td>
 &lt;td>運算 instance&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>S3&lt;/td>
 &lt;td>MFA delete&lt;/td>
 &lt;td>bucket 版本控制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DynamoDB&lt;/td>
 &lt;td>&lt;code>deletion_protection_enabled&lt;/code>&lt;/td>
 &lt;td>表格&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要開啟 deletion protection 的訊號是資源承載了不可重建的狀態。判斷方式是問一個問題：「這個資源被刪除後，能不能在 10 分鐘內從程式碼或備份完整恢復？」不能的就該開。&lt;/p>
&lt;p>&lt;code>terraform plan&lt;/code> 輸出裡出現 &lt;code>destroy&lt;/code> 或 &lt;code>forces replacement&lt;/code>（&lt;code>-/+&lt;/code>）時，deletion protection 是阻擋意外資料遺失的最後一道閘門。有保護的資源在 apply 時會報錯而非直接刪除，讓操作者有機會停下來確認。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> 描述 stateful 資源時，把 deletion protection 寫進程式碼而非手動在 Console 開啟——這讓保護策略本身成為可審查、可追蹤的設定。同時搭配 &lt;code>skip_final_snapshot = false&lt;/code>（RDS）確保刪除前自動做最後一份快照。&lt;/p>
&lt;p>Deletion protection 擋的是刪除操作，不擋資料覆寫或邏輯損壞——一段錯誤的 UPDATE 不會被 deletion protection 攔截。資料層的完整防線還需要備份保留與時間點還原（PITR），跟 deletion protection 正交。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State&lt;/a> — deletion protection 在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">state&lt;/a> 裡記錄為資源屬性，plan 會顯示保護狀態&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> — 保護策略寫進 IaC 讓它可審查&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Deletion protection 是雲端平台在資源層級提供的防護機制：開啟後，任何刪除該資源的操作（Console 點按、CLI 指令、<a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> 的 destroy）都會被擋下，必須先顯式關閉保護才能執行刪除。這個額外步驟的目的是防止手滑、批次操作誤傷、以及 Terraform plan 裡意外出現的 destroy。</p>
<h2 id="概念位置">概念位置</h2>
<p>Deletion protection 是 <a href="/blog/devops/infra/05-core-services/stateful-protection-dependency/" data-link-title="Stateful 資源保護與跨服務依賴表達" data-link-desc="stateful 資源的保護策略（multi-AZ、備份、刪除保護）、stateful 與 stateless 的操作差異，以及用 output 與 data source 表達服務間依賴">stateful 資源保護</a>的第一道防線。運算節點可以隨時重建，資料一旦遺失通常無法重來——這條分界線決定了哪些資源該開保護。對 stateful 資源（資料庫、持久化儲存）來說，這是 day-1 該開的設定，不是「等穩定再開」的選項。</p>
<p>不同 AWS 服務的保護機制名稱不同但行為一致：</p>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>屬性名稱</th>
          <th>保護對象</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/devops/infra/knowledge-cards/rds/" data-link-title="RDS" data-link-desc="AWS 的受管關聯式資料庫服務，代管備份、更新與 failover，讓使用者專注在 schema 和查詢">RDS</a></td>
          <td><code>deletion_protection</code></td>
          <td>資料庫 instance</td>
      </tr>
      <tr>
          <td>EC2</td>
          <td><code>disable_api_termination</code></td>
          <td>運算 instance</td>
      </tr>
      <tr>
          <td>S3</td>
          <td>MFA delete</td>
          <td>bucket 版本控制</td>
      </tr>
      <tr>
          <td>DynamoDB</td>
          <td><code>deletion_protection_enabled</code></td>
          <td>表格</td>
      </tr>
  </tbody>
</table>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要開啟 deletion protection 的訊號是資源承載了不可重建的狀態。判斷方式是問一個問題：「這個資源被刪除後，能不能在 10 分鐘內從程式碼或備份完整恢復？」不能的就該開。</p>
<p><code>terraform plan</code> 輸出裡出現 <code>destroy</code> 或 <code>forces replacement</code>（<code>-/+</code>）時，deletion protection 是阻擋意外資料遺失的最後一道閘門。有保護的資源在 apply 時會報錯而非直接刪除，讓操作者有機會停下來確認。</p>
<h2 id="設計責任">設計責任</h2>
<p>用 <a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> 描述 stateful 資源時，把 deletion protection 寫進程式碼而非手動在 Console 開啟——這讓保護策略本身成為可審查、可追蹤的設定。同時搭配 <code>skip_final_snapshot = false</code>（RDS）確保刪除前自動做最後一份快照。</p>
<p>Deletion protection 擋的是刪除操作，不擋資料覆寫或邏輯損壞——一段錯誤的 UPDATE 不會被 deletion protection 攔截。資料層的完整防線還需要備份保留與時間點還原（PITR），跟 deletion protection 正交。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State</a> — deletion protection 在 <a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">state</a> 裡記錄為資源屬性，plan 會顯示保護狀態</li>
<li><a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> — 保護策略寫進 IaC 讓它可審查</li>
</ul>
]]></content:encoded></item><item><title>checkov</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/checkov/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/checkov/</guid><description>&lt;p>checkov 是一個開源的靜態分析工具，掃描 Terraform / CloudFormation / Kubernetes 等 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> 程式碼，比對內建的規則庫找出安全漏洞與合規違規。它在 &lt;code>plan&lt;/code> 之前或之後執行、不建立任何雲端資源，所以是 CI pipeline 裡最便宜的安全檢查之一。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>checkov 在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/07-infra-as-pr/plan-review-apply-guardrails/" data-link-title="infra 走 PR 流程與自動化護欄" data-link-desc="infra 變更走 PR → plan → review diff → 合併 → apply，配 fmt / validate / tflint / checkov / tfsec 與 Atlantis 自動化，讓基礎設施可審查、可回溯、可交接">infra PR 流程&lt;/a>裡的位置是 &lt;code>fmt&lt;/code> → &lt;code>validate&lt;/code> → &lt;strong>checkov / tfsec&lt;/strong> → &lt;code>plan&lt;/code>。前兩步檢查語法正確，checkov 檢查語意安全，plan 檢查實際差異。checkov 補的是 reviewer 肉眼容易漏的盲區——一條 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">security group&lt;/a> 規則寫成 &lt;code>0.0.0.0/0&lt;/code> 在 HCL 裡只是一行字串，人會看漏，規則不會。&lt;/p>
&lt;p>三個常見的 IaC 掃描工具各有側重：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>側重&lt;/th>
 &lt;th>維護方&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>checkov&lt;/td>
 &lt;td>安全 + 合規&lt;/td>
 &lt;td>Prisma Cloud (Palo Alto)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>tfsec&lt;/td>
 &lt;td>安全&lt;/td>
 &lt;td>Aqua Security&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>tflint&lt;/td>
 &lt;td>provider 正確性&lt;/td>
 &lt;td>社群&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>checkov 的規則庫最廣（涵蓋 CIS Benchmark、SOC 2、PCI DSS 等合規框架），tfsec 的規則更聚焦安全面，tflint 偏向「這個 instance type 在這個 region 存不存在」的 provider 正確性。三者可疊加使用。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要引入 checkov 的訊號是 PR review 開始漏掉安全問題——S3 bucket 缺 public access block、RDS 沒開加密、IAM policy 過寬。這些問題的 pattern 是固定的、可以用規則比對，不應該靠人記憶來擋。&lt;/p>
&lt;p>checkov 命中後要區分「真漏洞」和「情境合理的例外」。ALB 的 HTTPS listener 在 port 443 開 &lt;code>0.0.0.0/0&lt;/code> 是設計本意，不是漏洞。豁免用行內註解標記並寫理由：&lt;code>#checkov:skip=CKV_AWS_260:ALB public HTTPS listener&lt;/code>。詳細的規則配置與豁免管理見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/07-infra-as-pr/checkov-tfsec-rule-customization/" data-link-title="checkov 與 tfsec 規則配置" data-link-desc="靜態掃描工具的規則選擇策略、自訂規則、豁免管理、false positive 處理與 CI 整合，讓掃描從噪音來源變成可信的品質關卡">checkov 與 tfsec 規則配置&lt;/a>。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>checkov 一開始就啟用全部規則並設為 hard fail 的話，既有程式碼裡的舊寫法也會讓 CI 擋下與它們無關的 PR，所以引入時的設計集中在兩個設定：啟用哪些規則（全部 vs 漸進啟用），以及命中時 CI 要不要擋（hard fail vs warning）。常見的漸進策略是先從高嚴重度規則開始、設為 hard fail，中低嚴重度設為 warning，隨團隊習慣逐步收緊。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> — checkov 掃描的對象&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — checkov 最常攔截的 &lt;code>0.0.0.0/0&lt;/code> 全開規則&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>checkov 是一個開源的靜態分析工具，掃描 Terraform / CloudFormation / Kubernetes 等 <a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> 程式碼，比對內建的規則庫找出安全漏洞與合規違規。它在 <code>plan</code> 之前或之後執行、不建立任何雲端資源，所以是 CI pipeline 裡最便宜的安全檢查之一。</p>
<h2 id="概念位置">概念位置</h2>
<p>checkov 在 <a href="/blog/devops/infra/07-infra-as-pr/plan-review-apply-guardrails/" data-link-title="infra 走 PR 流程與自動化護欄" data-link-desc="infra 變更走 PR → plan → review diff → 合併 → apply，配 fmt / validate / tflint / checkov / tfsec 與 Atlantis 自動化，讓基礎設施可審查、可回溯、可交接">infra PR 流程</a>裡的位置是 <code>fmt</code> → <code>validate</code> → <strong>checkov / tfsec</strong> → <code>plan</code>。前兩步檢查語法正確，checkov 檢查語意安全，plan 檢查實際差異。checkov 補的是 reviewer 肉眼容易漏的盲區——一條 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">security group</a> 規則寫成 <code>0.0.0.0/0</code> 在 HCL 裡只是一行字串，人會看漏，規則不會。</p>
<p>三個常見的 IaC 掃描工具各有側重：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>側重</th>
          <th>維護方</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>checkov</td>
          <td>安全 + 合規</td>
          <td>Prisma Cloud (Palo Alto)</td>
      </tr>
      <tr>
          <td>tfsec</td>
          <td>安全</td>
          <td>Aqua Security</td>
      </tr>
      <tr>
          <td>tflint</td>
          <td>provider 正確性</td>
          <td>社群</td>
      </tr>
  </tbody>
</table>
<p>checkov 的規則庫最廣（涵蓋 CIS Benchmark、SOC 2、PCI DSS 等合規框架），tfsec 的規則更聚焦安全面，tflint 偏向「這個 instance type 在這個 region 存不存在」的 provider 正確性。三者可疊加使用。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要引入 checkov 的訊號是 PR review 開始漏掉安全問題——S3 bucket 缺 public access block、RDS 沒開加密、IAM policy 過寬。這些問題的 pattern 是固定的、可以用規則比對，不應該靠人記憶來擋。</p>
<p>checkov 命中後要區分「真漏洞」和「情境合理的例外」。ALB 的 HTTPS listener 在 port 443 開 <code>0.0.0.0/0</code> 是設計本意，不是漏洞。豁免用行內註解標記並寫理由：<code>#checkov:skip=CKV_AWS_260:ALB public HTTPS listener</code>。詳細的規則配置與豁免管理見 <a href="/blog/devops/infra/07-infra-as-pr/checkov-tfsec-rule-customization/" data-link-title="checkov 與 tfsec 規則配置" data-link-desc="靜態掃描工具的規則選擇策略、自訂規則、豁免管理、false positive 處理與 CI 整合，讓掃描從噪音來源變成可信的品質關卡">checkov 與 tfsec 規則配置</a>。</p>
<h2 id="設計責任">設計責任</h2>
<p>checkov 一開始就啟用全部規則並設為 hard fail 的話，既有程式碼裡的舊寫法也會讓 CI 擋下與它們無關的 PR，所以引入時的設計集中在兩個設定：啟用哪些規則（全部 vs 漸進啟用），以及命中時 CI 要不要擋（hard fail vs warning）。常見的漸進策略是先從高嚴重度規則開始、設為 hard fail，中低嚴重度設為 warning，隨團隊習慣逐步收緊。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> — checkov 掃描的對象</li>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — checkov 最常攔截的 <code>0.0.0.0/0</code> 全開規則</li>
</ul>
]]></content:encoded></item><item><title>Fargate</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/fargate/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/fargate/</guid><description>&lt;p>Fargate 是 AWS &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS&lt;/a> 的一種 launch type，把容器的運算實例交給 AWS 代管。使用 Fargate 時不需要配 EC2 instance、不需要管 capacity provider 的 scaling、不需要更新 AMI——只描述 task 需要多少 vCPU 和記憶體，AWS 負責分配運算資源。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>ECS 有兩種 launch type，差別在運算層的管理責任：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Launch type&lt;/th>
 &lt;th>運算層管理&lt;/th>
 &lt;th>適用情境&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Fargate&lt;/td>
 &lt;td>AWS 代管&lt;/td>
 &lt;td>web API、微服務、批次任務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>EC2&lt;/td>
 &lt;td>自管 instance&lt;/td>
 &lt;td>GPU workload、高密度排程、成本敏感&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Fargate 降低的是運維面（不用管 OS patch、不用管 instance 容量），代價是單位成本較高（同規格約比 EC2 高 20-40%）和啟動延遲（cold start 通常在 30-60 秒，EC2 上的 task 因為 instance 已在所以秒級啟動）。多數 web API 的初始選擇是 Fargate，流量穩定且成本壓力大時再切回 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2&lt;/a> launch type。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>評估是否從 Fargate 切到 EC2 的訊號是月費曲線。Fargate 按 vCPU-hour 和 memory-hour 計費，task 數量少時費用低、管理簡單。當 task 數量穩定在 10-20 個以上且流量模式可預測時，EC2 launch type 搭配 reserved instance 或 Savings Plans 的成本優勢開始顯著——但要承擔 instance 管理的運維負擔。詳細的成本分析見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/05-core-services/ecs-fargate-cost-optimization/" data-link-title="ECS Fargate 成本分析與優化" data-link-desc="Fargate 的計價模型、與 EC2 launch type 的成本交叉點、Spot 與 Savings Plans 的折扣機制、task 規格的 rightsizing 方法，以及何時該切回 EC2">ECS Fargate 成本分析與優化&lt;/a>。&lt;/p>
&lt;p>Fargate Spot 是介於兩者之間的選項：費用約為 on-demand Fargate 的 30%，但 AWS 可以隨時中斷 task（提前 2 分鐘通知）。適合可容忍中斷的 workload（批次處理、非即時的資料轉換），不適合面對使用者的即時 API。常見的混合策略是用 on-demand Fargate 跑基線流量、Fargate Spot 跑彈性擴張的部分。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Fargate 把運算實例交給 AWS 代管之後，團隊能調整的只剩 task 本身的規格與行為，設計集中在：task 的 vCPU / memory 規格（Fargate 只提供固定的幾種 vCPU 與 memory 組合）、是否混用 Spot、以及 health check 的 grace period（Fargate 的 cold start 比 EC2 長，health check 太早判定失敗會讓 task 反覆重啟）。&lt;/p>
&lt;p>task 規格的 rightsizing 靠 CloudWatch Container Insights 的 CPU / memory utilization 決定——p95 使用率低於 30% 代表規格過大、持續高於 80% 代表該升級。&lt;/p></description><content:encoded><![CDATA[<p>Fargate 是 AWS <a href="/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS</a> 的一種 launch type，把容器的運算實例交給 AWS 代管。使用 Fargate 時不需要配 EC2 instance、不需要管 capacity provider 的 scaling、不需要更新 AMI——只描述 task 需要多少 vCPU 和記憶體，AWS 負責分配運算資源。</p>
<h2 id="概念位置">概念位置</h2>
<p>ECS 有兩種 launch type，差別在運算層的管理責任：</p>
<table>
  <thead>
      <tr>
          <th>Launch type</th>
          <th>運算層管理</th>
          <th>適用情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Fargate</td>
          <td>AWS 代管</td>
          <td>web API、微服務、批次任務</td>
      </tr>
      <tr>
          <td>EC2</td>
          <td>自管 instance</td>
          <td>GPU workload、高密度排程、成本敏感</td>
      </tr>
  </tbody>
</table>
<p>Fargate 降低的是運維面（不用管 OS patch、不用管 instance 容量），代價是單位成本較高（同規格約比 EC2 高 20-40%）和啟動延遲（cold start 通常在 30-60 秒，EC2 上的 task 因為 instance 已在所以秒級啟動）。多數 web API 的初始選擇是 Fargate，流量穩定且成本壓力大時再切回 <a href="/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2</a> launch type。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>評估是否從 Fargate 切到 EC2 的訊號是月費曲線。Fargate 按 vCPU-hour 和 memory-hour 計費，task 數量少時費用低、管理簡單。當 task 數量穩定在 10-20 個以上且流量模式可預測時，EC2 launch type 搭配 reserved instance 或 Savings Plans 的成本優勢開始顯著——但要承擔 instance 管理的運維負擔。詳細的成本分析見 <a href="/blog/devops/infra/05-core-services/ecs-fargate-cost-optimization/" data-link-title="ECS Fargate 成本分析與優化" data-link-desc="Fargate 的計價模型、與 EC2 launch type 的成本交叉點、Spot 與 Savings Plans 的折扣機制、task 規格的 rightsizing 方法，以及何時該切回 EC2">ECS Fargate 成本分析與優化</a>。</p>
<p>Fargate Spot 是介於兩者之間的選項：費用約為 on-demand Fargate 的 30%，但 AWS 可以隨時中斷 task（提前 2 分鐘通知）。適合可容忍中斷的 workload（批次處理、非即時的資料轉換），不適合面對使用者的即時 API。常見的混合策略是用 on-demand Fargate 跑基線流量、Fargate Spot 跑彈性擴張的部分。</p>
<h2 id="設計責任">設計責任</h2>
<p>Fargate 把運算實例交給 AWS 代管之後，團隊能調整的只剩 task 本身的規格與行為，設計集中在：task 的 vCPU / memory 規格（Fargate 只提供固定的幾種 vCPU 與 memory 組合）、是否混用 Spot、以及 health check 的 grace period（Fargate 的 cold start 比 EC2 長，health check 太早判定失敗會讓 task 反覆重啟）。</p>
<p>task 規格的 rightsizing 靠 CloudWatch Container Insights 的 CPU / memory utilization 決定——p95 使用率低於 30% 代表規格過大、持續高於 80% 代表該升級。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS</a> — Fargate 是 ECS 的 launch type 之一</li>
<li><a href="/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB</a> — Fargate task 通常掛在 ALB 的 target group 後面</li>
</ul>
]]></content:encoded></item><item><title>phpMyAdmin</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/phpmyadmin/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/phpmyadmin/</guid><description>&lt;p>phpMyAdmin 是一套透過瀏覽器操作 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/mysql/" data-link-title="MySQL" data-link-desc="最廣泛使用的開源關聯式資料庫。MariaDB 是其社群分支。大版本升級（5.7→8.0）有認證方式和查詢行為的 breaking change">MySQL&lt;/a> 和 MariaDB 的 Web 應用程式。它提供圖形介面執行 SQL 查詢、瀏覽資料表、匯出與匯入資料庫、修改 schema（新增欄位、改索引、刪表）、以及管理使用者權限。多數主機商在安裝 cPanel 或 Plesk 時會一併預裝，讓租用主機的使用者不需要 SSH 就能管理資料庫。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>在無 SSH 的主機環境裡，phpMyAdmin 通常是唯一可用的資料庫管理入口。它取代了 &lt;code>mysql&lt;/code> CLI client 和 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/mysqldump/" data-link-title="mysqldump" data-link-desc="MySQL / MariaDB 的 CLI 備份工具，把資料庫匯出成 SQL 語句的純文字檔">&lt;code>mysqldump&lt;/code>&lt;/a> 指令的角色——查詢用 SQL 編輯器、匯出用匯出頁面、匯入用上傳 SQL 檔。接手維運時，phpMyAdmin 是拍下資料庫現況（SQL dump）的主要工具。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>以下情境會遇到 phpMyAdmin：主機面板（cPanel / Plesk）裡有「phpMyAdmin」按鈕可以進入；接手的專案的資料庫操作文件提到「在 phpMyAdmin 裡執行」；或者專案的部署流程包含「登入 phpMyAdmin 匯入 SQL」。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>使用 phpMyAdmin 時要處理三個限制。第一是匯出 timeout：大資料庫（50MB 以上）的匯出可能因為 PHP 的 &lt;code>max_execution_time&lt;/code> 限制而中斷，需要分表匯出或調整 phpMyAdmin 設定。第二是沒有 CLI 可腳本化：所有操作都要手動點擊，無法排程自動備份。第三是安全暴露：phpMyAdmin 掛在 web 上、可被外部存取，如果沒有設密碼保護或 IP 白名單，等於把資料庫管理介面開給全世界。&lt;/p>
&lt;p>如果主機允許遠端 MySQL 連線（port 3306 開放），可以改用桌面工具（DBeaver、TablePlus、HeidiSQL）直連資料庫，繞過 phpMyAdmin 的 timeout 限制。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cpanel/" data-link-title="cPanel" data-link-desc="Web 主機管理面板，提供 PHP 版本切換、cron、email、SSL、備份等功能的圖形介面">cPanel&lt;/a>：phpMyAdmin 通常內嵌在 cPanel 裡&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>phpMyAdmin 是一套透過瀏覽器操作 <a href="/blog/devops/infra/knowledge-cards/mysql/" data-link-title="MySQL" data-link-desc="最廣泛使用的開源關聯式資料庫。MariaDB 是其社群分支。大版本升級（5.7→8.0）有認證方式和查詢行為的 breaking change">MySQL</a> 和 MariaDB 的 Web 應用程式。它提供圖形介面執行 SQL 查詢、瀏覽資料表、匯出與匯入資料庫、修改 schema（新增欄位、改索引、刪表）、以及管理使用者權限。多數主機商在安裝 cPanel 或 Plesk 時會一併預裝，讓租用主機的使用者不需要 SSH 就能管理資料庫。</p>
<h2 id="概念位置">概念位置</h2>
<p>在無 SSH 的主機環境裡，phpMyAdmin 通常是唯一可用的資料庫管理入口。它取代了 <code>mysql</code> CLI client 和 <a href="/blog/devops/infra/knowledge-cards/mysqldump/" data-link-title="mysqldump" data-link-desc="MySQL / MariaDB 的 CLI 備份工具，把資料庫匯出成 SQL 語句的純文字檔"><code>mysqldump</code></a> 指令的角色——查詢用 SQL 編輯器、匯出用匯出頁面、匯入用上傳 SQL 檔。接手維運時，phpMyAdmin 是拍下資料庫現況（SQL dump）的主要工具。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>以下情境會遇到 phpMyAdmin：主機面板（cPanel / Plesk）裡有「phpMyAdmin」按鈕可以進入；接手的專案的資料庫操作文件提到「在 phpMyAdmin 裡執行」；或者專案的部署流程包含「登入 phpMyAdmin 匯入 SQL」。</p>
<h2 id="設計責任">設計責任</h2>
<p>使用 phpMyAdmin 時要處理三個限制。第一是匯出 timeout：大資料庫（50MB 以上）的匯出可能因為 PHP 的 <code>max_execution_time</code> 限制而中斷，需要分表匯出或調整 phpMyAdmin 設定。第二是沒有 CLI 可腳本化：所有操作都要手動點擊，無法排程自動備份。第三是安全暴露：phpMyAdmin 掛在 web 上、可被外部存取，如果沒有設密碼保護或 IP 白名單，等於把資料庫管理介面開給全世界。</p>
<p>如果主機允許遠端 MySQL 連線（port 3306 開放），可以改用桌面工具（DBeaver、TablePlus、HeidiSQL）直連資料庫，繞過 phpMyAdmin 的 timeout 限制。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/cpanel/" data-link-title="cPanel" data-link-desc="Web 主機管理面板，提供 PHP 版本切換、cron、email、SSL、備份等功能的圖形介面">cPanel</a>：phpMyAdmin 通常內嵌在 cPanel 裡</li>
</ul>
]]></content:encoded></item><item><title>FileZilla</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/filezilla/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/filezilla/</guid><description>&lt;p>FileZilla 是一套開源的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ftp/" data-link-title="FTP" data-link-desc="File Transfer Protocol — 檔案傳輸協定，無 SSH 環境的主要檔案管理方式。SFTP 和 FTPS 是其加密變體">FTP&lt;/a> / SFTP / FTPS client，支援 Windows、macOS 和 Linux。它的介面分成本地和遠端兩側的檔案瀏覽器，讓使用者透過拖放或右鍵選單在本機與伺服器之間傳輸檔案。在無 SSH 的主機環境裡，FileZilla 是上傳程式碼和下載備份的主要工具。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ftp/" data-link-title="FTP" data-link-desc="File Transfer Protocol — 檔案傳輸協定，無 SSH 環境的主要檔案管理方式。SFTP 和 FTPS 是其加密變體">FTP&lt;/a> 是無 SSH 環境裡傳輸檔案的主要協定。FileZilla 把 FTP 操作從 CLI（如 &lt;code>ftp&lt;/code> 或 &lt;code>lftp&lt;/code> 指令）包裝成圖形介面，降低操作門檻。接手維運時，FileZilla 的角色是「把整個站台拉回本地」和「把改好的檔案推上 prod」。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>以下情境會用到 FileZilla：接手的專案只有 FTP 帳密沒有 SSH key；部署方式是「FTP 上傳改過的檔案」；或者需要對比本地版本和伺服器版本的差異。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>使用 FileZilla 時有三個關鍵功能和注意事項。&lt;/p>
&lt;p>&lt;strong>站台管理員&lt;/strong>：儲存多組 FTP 連線設定（主機、帳號、密碼、port），避免每次手動輸入。接手時第一步是在站台管理員建好 prod 的連線，並確認協定選擇正確（FTP 明文、FTPS 加密、SFTP 走 SSH）。&lt;/p>
&lt;p>&lt;strong>目錄比較&lt;/strong>：「檢視 → 目錄比較 → 啟用」功能會標示本地與遠端的檔案差異——哪些本地較新、哪些遠端較新、哪些只存在於一邊。上傳前先跑目錄比較可以看到即將改動的範圍。&lt;/p>
&lt;p>&lt;strong>隱藏檔&lt;/strong>：預設不顯示以 &lt;code>.&lt;/code> 開頭的檔案（如 &lt;code>.htaccess&lt;/code>、&lt;code>.env&lt;/code>、&lt;code>.user.ini&lt;/code>）。要在「伺服器 → 強制顯示隱藏檔案」啟用，否則接手時會漏拉這些關鍵設定檔。&lt;/p>
&lt;p>FTP 傳輸是逐檔覆寫、沒有原子性——上傳到一半斷線會讓伺服器上同時存在新舊版本的混合狀態。對關鍵檔案（&lt;code>index.php&lt;/code>、&lt;code>.htaccess&lt;/code>）的上傳需要額外小心。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;p>無。FileZilla 是獨立工具。替代工具包括 WinSCP（Windows）、Cyberduck（macOS）、Transmit（macOS）。&lt;/p></description><content:encoded><![CDATA[<p>FileZilla 是一套開源的 <a href="/blog/devops/infra/knowledge-cards/ftp/" data-link-title="FTP" data-link-desc="File Transfer Protocol — 檔案傳輸協定，無 SSH 環境的主要檔案管理方式。SFTP 和 FTPS 是其加密變體">FTP</a> / SFTP / FTPS client，支援 Windows、macOS 和 Linux。它的介面分成本地和遠端兩側的檔案瀏覽器，讓使用者透過拖放或右鍵選單在本機與伺服器之間傳輸檔案。在無 SSH 的主機環境裡，FileZilla 是上傳程式碼和下載備份的主要工具。</p>
<h2 id="概念位置">概念位置</h2>
<p><a href="/blog/devops/infra/knowledge-cards/ftp/" data-link-title="FTP" data-link-desc="File Transfer Protocol — 檔案傳輸協定，無 SSH 環境的主要檔案管理方式。SFTP 和 FTPS 是其加密變體">FTP</a> 是無 SSH 環境裡傳輸檔案的主要協定。FileZilla 把 FTP 操作從 CLI（如 <code>ftp</code> 或 <code>lftp</code> 指令）包裝成圖形介面，降低操作門檻。接手維運時，FileZilla 的角色是「把整個站台拉回本地」和「把改好的檔案推上 prod」。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>以下情境會用到 FileZilla：接手的專案只有 FTP 帳密沒有 SSH key；部署方式是「FTP 上傳改過的檔案」；或者需要對比本地版本和伺服器版本的差異。</p>
<h2 id="設計責任">設計責任</h2>
<p>使用 FileZilla 時有三個關鍵功能和注意事項。</p>
<p><strong>站台管理員</strong>：儲存多組 FTP 連線設定（主機、帳號、密碼、port），避免每次手動輸入。接手時第一步是在站台管理員建好 prod 的連線，並確認協定選擇正確（FTP 明文、FTPS 加密、SFTP 走 SSH）。</p>
<p><strong>目錄比較</strong>：「檢視 → 目錄比較 → 啟用」功能會標示本地與遠端的檔案差異——哪些本地較新、哪些遠端較新、哪些只存在於一邊。上傳前先跑目錄比較可以看到即將改動的範圍。</p>
<p><strong>隱藏檔</strong>：預設不顯示以 <code>.</code> 開頭的檔案（如 <code>.htaccess</code>、<code>.env</code>、<code>.user.ini</code>）。要在「伺服器 → 強制顯示隱藏檔案」啟用，否則接手時會漏拉這些關鍵設定檔。</p>
<p>FTP 傳輸是逐檔覆寫、沒有原子性——上傳到一半斷線會讓伺服器上同時存在新舊版本的混合狀態。對關鍵檔案（<code>index.php</code>、<code>.htaccess</code>）的上傳需要額外小心。</p>
<h2 id="鄰卡">鄰卡</h2>
<p>無。FileZilla 是獨立工具。替代工具包括 WinSCP（Windows）、Cyberduck（macOS）、Transmit（macOS）。</p>
]]></content:encoded></item><item><title>cPanel</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cpanel/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cpanel/</guid><description>&lt;p>cPanel 是最常見的 Web 主機管理面板，讓租用主機的使用者透過瀏覽器管理伺服器的常用功能——PHP 版本切換、&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cron/" data-link-title="cron" data-link-desc="Unix/Linux 的排程工作系統，按時間表自動執行指令。接手維運時要盤點所有 cron job">cron&lt;/a> job 排程、email 帳號管理、SSL 憑證安裝、檔案管理、資料庫管理、以及完整備份。Plesk 是同類產品，功能範圍相似但介面和設定路徑不同。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>cPanel 是無 SSH 環境裡的「控制中心」。它整合了多種工具的圖形入口：&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin&lt;/a>（資料庫）、檔案管理員（web 版 FTP）、PHP 設定、cron 編輯器、SSL/TLS 管理。接手維運時，第一步是確認有沒有 cPanel 存取權——有的話很多操作（備份、PHP 版本、cron）可以在面板裡完成，不需要 SSH。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>以下情境代表環境有 cPanel：主機商提供了 cPanel 登入 URL（通常是 &lt;code>domain:2083&lt;/code>）；接手時收到的帳密包含「cPanel 帳號」；或者主機商的服務說明提到 cPanel / WHM。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>沒有 SSH 的主機上，備份、PHP 版本與排程多半只能從 cPanel 操作，這些設定也不在任何程式碼庫裡；接手維運時要在面板裡逐項確認的功能是：&lt;/p>
&lt;p>&lt;strong>完整備份&lt;/strong>：「備份精靈」可以一次打包整個帳號（檔案 + 資料庫 + email + cron + DNS 設定）。這是最快的「拍下現況」方式——比 FTP 逐檔拉 + phpMyAdmin 匯出快得多。但完整備份通常只能下載、不能自動排程到外部儲存（部分主機商限制）。&lt;/p>
&lt;p>&lt;strong>PHP 版本選擇器&lt;/strong>：可以切換整個帳號或單一域名的 PHP 版本。升級 PHP 時，可以先在 staging 子域名切到新版本測試、確認沒問題再切主域名。這是無 SSH 環境裡最安全的 PHP 升級方式。&lt;/p>
&lt;p>&lt;strong>cron job 管理&lt;/strong>：圖形介面設定排程任務，語法是 cron 標準格式。接手時要截圖或匯出所有 cron——它們可能是系統運作的隱性依賴（定期清快取、寄報表、同步資料）。&lt;/p>
&lt;p>&lt;strong>SSL/TLS&lt;/strong>：管理 HTTPS 憑證。部分主機商整合了 Let&amp;rsquo;s Encrypt 自動簽發，部分需要手動上傳憑證。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin&lt;/a>：通常內嵌在 cPanel 的「資料庫」區塊裡&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>cPanel 是最常見的 Web 主機管理面板，讓租用主機的使用者透過瀏覽器管理伺服器的常用功能——PHP 版本切換、<a href="/blog/devops/infra/knowledge-cards/cron/" data-link-title="cron" data-link-desc="Unix/Linux 的排程工作系統，按時間表自動執行指令。接手維運時要盤點所有 cron job">cron</a> job 排程、email 帳號管理、SSL 憑證安裝、檔案管理、資料庫管理、以及完整備份。Plesk 是同類產品，功能範圍相似但介面和設定路徑不同。</p>
<h2 id="概念位置">概念位置</h2>
<p>cPanel 是無 SSH 環境裡的「控制中心」。它整合了多種工具的圖形入口：<a href="/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin</a>（資料庫）、檔案管理員（web 版 FTP）、PHP 設定、cron 編輯器、SSL/TLS 管理。接手維運時，第一步是確認有沒有 cPanel 存取權——有的話很多操作（備份、PHP 版本、cron）可以在面板裡完成，不需要 SSH。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>以下情境代表環境有 cPanel：主機商提供了 cPanel 登入 URL（通常是 <code>domain:2083</code>）；接手時收到的帳密包含「cPanel 帳號」；或者主機商的服務說明提到 cPanel / WHM。</p>
<h2 id="設計責任">設計責任</h2>
<p>沒有 SSH 的主機上，備份、PHP 版本與排程多半只能從 cPanel 操作，這些設定也不在任何程式碼庫裡；接手維運時要在面板裡逐項確認的功能是：</p>
<p><strong>完整備份</strong>：「備份精靈」可以一次打包整個帳號（檔案 + 資料庫 + email + cron + DNS 設定）。這是最快的「拍下現況」方式——比 FTP 逐檔拉 + phpMyAdmin 匯出快得多。但完整備份通常只能下載、不能自動排程到外部儲存（部分主機商限制）。</p>
<p><strong>PHP 版本選擇器</strong>：可以切換整個帳號或單一域名的 PHP 版本。升級 PHP 時，可以先在 staging 子域名切到新版本測試、確認沒問題再切主域名。這是無 SSH 環境裡最安全的 PHP 升級方式。</p>
<p><strong>cron job 管理</strong>：圖形介面設定排程任務，語法是 cron 標準格式。接手時要截圖或匯出所有 cron——它們可能是系統運作的隱性依賴（定期清快取、寄報表、同步資料）。</p>
<p><strong>SSL/TLS</strong>：管理 HTTPS 憑證。部分主機商整合了 Let&rsquo;s Encrypt 自動簽發，部分需要手動上傳憑證。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin</a>：通常內嵌在 cPanel 的「資料庫」區塊裡</li>
</ul>
]]></content:encoded></item><item><title>.htaccess</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/htaccess/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/htaccess/</guid><description>&lt;p>&lt;code>.htaccess&lt;/code>（Hypertext Access）是 Apache Web Server 的目錄層級設定檔。它讓使用者在沒有伺服器管理員權限的情況下，覆寫 Apache 的部分全域設定——包括 URL 重寫規則、目錄存取控制、PHP 設定覆寫、HTTPS 強制跳轉、以及 HTTP 安全標頭。每個目錄可以有自己的 &lt;code>.htaccess&lt;/code>，Apache 處理請求時會從根目錄到目標目錄逐層讀取並套用。常搭配 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini&lt;/a> 一起管理 PHP 應用的行為。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>在 Apache 為主的主機環境（多數共享主機和部分 VPS），&lt;code>.htaccess&lt;/code> 是不需要重啟伺服器就能調整行為的設定機制。WordPress、Laravel、Drupal 等 PHP 框架都依賴 &lt;code>.htaccess&lt;/code> 的 URL rewrite 規則來實現 pretty URL（把 &lt;code>/blog/post-title&lt;/code> 轉成 &lt;code>index.php?page=post-title&lt;/code>）。相對地 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx&lt;/a> 沒有對等機制、設定要集中寫在主設定檔。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>站台根目錄有 &lt;code>.htaccess&lt;/code> 檔案（注意它是隱藏檔，FTP client 要啟用「顯示隱藏檔案」才看得到）。上傳目錄（&lt;code>uploads/&lt;/code>）、後台目錄（&lt;code>admin/&lt;/code>、&lt;code>wp-admin/&lt;/code>）可能各有一份獨立的 &lt;code>.htaccess&lt;/code> 做額外的存取控制。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>&lt;code>.htaccess&lt;/code> 的規則分散在各目錄的隱藏檔裡、每次請求即時生效，站台的 URL 結構、存取限制與 PHP 設定可能只記錄在這些檔案裡；接手維運時要逐一找出的內容有：&lt;/p>
&lt;p>&lt;strong>URL rewrite 規則&lt;/strong>：這些規則決定了站台的 URL 結構。亂改或刪除會讓所有內頁都回 404。修改前先備份原始版本。&lt;/p>
&lt;p>&lt;strong>安全設定&lt;/strong>：&lt;code>Options -Indexes&lt;/code> 禁止目錄列表、&lt;code>php_flag engine off&lt;/code> 禁止上傳目錄執行 PHP、&lt;code>Require all denied&lt;/code> 禁止存取 &lt;code>.env&lt;/code> 等機密檔案。這些設定分散在多個目錄的 &lt;code>.htaccess&lt;/code> 裡，接手時要全部找出來。&lt;/p>
&lt;p>&lt;strong>PHP 設定覆寫&lt;/strong>：部分 PHP 設定（如 &lt;code>upload_max_filesize&lt;/code>、&lt;code>max_execution_time&lt;/code>）可以在 &lt;code>.htaccess&lt;/code> 裡用 &lt;code>php_value&lt;/code> 或 &lt;code>php_flag&lt;/code> 指令覆寫。這些覆寫可能不在 &lt;code>php.ini&lt;/code> 裡，只存在於 &lt;code>.htaccess&lt;/code>。&lt;/p>
&lt;p>&lt;strong>遷移到 nginx 的影響&lt;/strong>：nginx 沒有 &lt;code>.htaccess&lt;/code> 的對等機制——所有設定都在集中的 nginx 設定檔裡。從 Apache 遷移到 nginx 時，&lt;code>.htaccess&lt;/code> 裡的每一條規則都要手動轉換成 nginx 語法。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini&lt;/a>：&lt;code>.htaccess&lt;/code> 管 Apache 行為，&lt;code>.user.ini&lt;/code> 管 PHP 行為，兩者互補&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p><code>.htaccess</code>（Hypertext Access）是 Apache Web Server 的目錄層級設定檔。它讓使用者在沒有伺服器管理員權限的情況下，覆寫 Apache 的部分全域設定——包括 URL 重寫規則、目錄存取控制、PHP 設定覆寫、HTTPS 強制跳轉、以及 HTTP 安全標頭。每個目錄可以有自己的 <code>.htaccess</code>，Apache 處理請求時會從根目錄到目標目錄逐層讀取並套用。常搭配 <a href="/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini</a> 一起管理 PHP 應用的行為。</p>
<h2 id="概念位置">概念位置</h2>
<p>在 Apache 為主的主機環境（多數共享主機和部分 VPS），<code>.htaccess</code> 是不需要重啟伺服器就能調整行為的設定機制。WordPress、Laravel、Drupal 等 PHP 框架都依賴 <code>.htaccess</code> 的 URL rewrite 規則來實現 pretty URL（把 <code>/blog/post-title</code> 轉成 <code>index.php?page=post-title</code>）。相對地 <a href="/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx</a> 沒有對等機制、設定要集中寫在主設定檔。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>站台根目錄有 <code>.htaccess</code> 檔案（注意它是隱藏檔，FTP client 要啟用「顯示隱藏檔案」才看得到）。上傳目錄（<code>uploads/</code>）、後台目錄（<code>admin/</code>、<code>wp-admin/</code>）可能各有一份獨立的 <code>.htaccess</code> 做額外的存取控制。</p>
<h2 id="設計責任">設計責任</h2>
<p><code>.htaccess</code> 的規則分散在各目錄的隱藏檔裡、每次請求即時生效，站台的 URL 結構、存取限制與 PHP 設定可能只記錄在這些檔案裡；接手維運時要逐一找出的內容有：</p>
<p><strong>URL rewrite 規則</strong>：這些規則決定了站台的 URL 結構。亂改或刪除會讓所有內頁都回 404。修改前先備份原始版本。</p>
<p><strong>安全設定</strong>：<code>Options -Indexes</code> 禁止目錄列表、<code>php_flag engine off</code> 禁止上傳目錄執行 PHP、<code>Require all denied</code> 禁止存取 <code>.env</code> 等機密檔案。這些設定分散在多個目錄的 <code>.htaccess</code> 裡，接手時要全部找出來。</p>
<p><strong>PHP 設定覆寫</strong>：部分 PHP 設定（如 <code>upload_max_filesize</code>、<code>max_execution_time</code>）可以在 <code>.htaccess</code> 裡用 <code>php_value</code> 或 <code>php_flag</code> 指令覆寫。這些覆寫可能不在 <code>php.ini</code> 裡，只存在於 <code>.htaccess</code>。</p>
<p><strong>遷移到 nginx 的影響</strong>：nginx 沒有 <code>.htaccess</code> 的對等機制——所有設定都在集中的 nginx 設定檔裡。從 Apache 遷移到 nginx 時，<code>.htaccess</code> 裡的每一條規則都要手動轉換成 nginx 語法。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini</a>：<code>.htaccess</code> 管 Apache 行為，<code>.user.ini</code> 管 PHP 行為，兩者互補</li>
</ul>
]]></content:encoded></item><item><title>.env</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dotenv/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dotenv/</guid><description>&lt;p>&lt;code>.env&lt;/code> 是一個純文字檔案，每行一組 &lt;code>KEY=VALUE&lt;/code> 的環境變數定義。它的用途是把機密值（資料庫密碼、API key、SMTP 存取憑證）和環境專屬設定（資料庫 host、debug 模式開關）從程式碼分離出來，讓同一份程式碼在不同環境（開發、staging、production）用不同的設定值，而且機密值不進版本控制。PHP 專案裡執行期設定的另一層在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>&lt;code>.env&lt;/code> 是跨語言的設定分離慣例。PHP 用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/composer/" data-link-title="Composer" data-link-desc="PHP 的套件管理工具，管理專案的第三方依賴、版本鎖定與安全掃描">Composer&lt;/a> 安裝的 &lt;code>vlucas/phpdotenv&lt;/code> 套件讀取、Node.js 用 &lt;code>dotenv&lt;/code> 套件、Python 用 &lt;code>python-dotenv&lt;/code>、Go 用 &lt;code>godotenv&lt;/code>。這些套件的行為相同：程式啟動時讀 &lt;code>.env&lt;/code> 檔案，把裡面的變數載入到執行環境的環境變數裡，讓程式碼用 &lt;code>$_ENV['KEY']&lt;/code>（PHP）或 &lt;code>process.env.KEY&lt;/code>（Node）存取。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>站台根目錄有 &lt;code>.env&lt;/code> 或 &lt;code>.env.production&lt;/code> 檔案；&lt;code>.gitignore&lt;/code> 裡有 &lt;code>.env&lt;/code> 這一行；repo 裡有 &lt;code>.env.example&lt;/code> 或 &lt;code>.env.sample&lt;/code> 列出所有需要的變數但不填實際值。如果接手的專案沒有 &lt;code>.env&lt;/code> 但 &lt;code>config.php&lt;/code> 裡直接寫了資料庫密碼，代表設定分離還沒做——這是接手後應該處理的事。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>使用 &lt;code>.env&lt;/code> 時有三個紀律：&lt;/p>
&lt;p>&lt;strong>不進 Git&lt;/strong>：&lt;code>.env&lt;/code> 包含明文密碼，進了 Git 就跟著每一次 clone、fork、CI 快取擴散。&lt;code>.gitignore&lt;/code> 必須排除 &lt;code>.env&lt;/code>。如果 &lt;code>.env&lt;/code> 已經在 Git 歷史裡，刪掉那一行不夠——密碼留在 history 裡，要輪替所有外洩的密碼。&lt;/p>
&lt;p>&lt;strong>範本檔進 Git&lt;/strong>：repo 裡放一份 &lt;code>.env.example&lt;/code>，列出所有必要的環境變數但不填實際值。新接手的人複製 &lt;code>.env.example&lt;/code> 成 &lt;code>.env&lt;/code>，再填入自己環境的值。&lt;/p>
&lt;p>&lt;strong>不用 &lt;code>.env&lt;/code> 管非機密設定&lt;/strong>：應用程式的功能開關、UI 設定、feature flag 不屬於 &lt;code>.env&lt;/code>——這些設定沒有機密性、應該進版本控制。&lt;code>.env&lt;/code> 只放「換一個環境就要改的值」和「不能被看到的值」。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini&lt;/a>：&lt;code>.env&lt;/code> 管應用程式設定、php.ini 管 PHP runtime 設定&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p><code>.env</code> 是一個純文字檔案，每行一組 <code>KEY=VALUE</code> 的環境變數定義。它的用途是把機密值（資料庫密碼、API key、SMTP 存取憑證）和環境專屬設定（資料庫 host、debug 模式開關）從程式碼分離出來，讓同一份程式碼在不同環境（開發、staging、production）用不同的設定值，而且機密值不進版本控制。PHP 專案裡執行期設定的另一層在 <a href="/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p><code>.env</code> 是跨語言的設定分離慣例。PHP 用 <a href="/blog/devops/infra/knowledge-cards/composer/" data-link-title="Composer" data-link-desc="PHP 的套件管理工具，管理專案的第三方依賴、版本鎖定與安全掃描">Composer</a> 安裝的 <code>vlucas/phpdotenv</code> 套件讀取、Node.js 用 <code>dotenv</code> 套件、Python 用 <code>python-dotenv</code>、Go 用 <code>godotenv</code>。這些套件的行為相同：程式啟動時讀 <code>.env</code> 檔案，把裡面的變數載入到執行環境的環境變數裡，讓程式碼用 <code>$_ENV['KEY']</code>（PHP）或 <code>process.env.KEY</code>（Node）存取。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>站台根目錄有 <code>.env</code> 或 <code>.env.production</code> 檔案；<code>.gitignore</code> 裡有 <code>.env</code> 這一行；repo 裡有 <code>.env.example</code> 或 <code>.env.sample</code> 列出所有需要的變數但不填實際值。如果接手的專案沒有 <code>.env</code> 但 <code>config.php</code> 裡直接寫了資料庫密碼，代表設定分離還沒做——這是接手後應該處理的事。</p>
<h2 id="設計責任">設計責任</h2>
<p>使用 <code>.env</code> 時有三個紀律：</p>
<p><strong>不進 Git</strong>：<code>.env</code> 包含明文密碼，進了 Git 就跟著每一次 clone、fork、CI 快取擴散。<code>.gitignore</code> 必須排除 <code>.env</code>。如果 <code>.env</code> 已經在 Git 歷史裡，刪掉那一行不夠——密碼留在 history 裡，要輪替所有外洩的密碼。</p>
<p><strong>範本檔進 Git</strong>：repo 裡放一份 <code>.env.example</code>，列出所有必要的環境變數但不填實際值。新接手的人複製 <code>.env.example</code> 成 <code>.env</code>，再填入自己環境的值。</p>
<p><strong>不用 <code>.env</code> 管非機密設定</strong>：應用程式的功能開關、UI 設定、feature flag 不屬於 <code>.env</code>——這些設定沒有機密性、應該進版本控制。<code>.env</code> 只放「換一個環境就要改的值」和「不能被看到的值」。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini</a>：<code>.env</code> 管應用程式設定、php.ini 管 PHP runtime 設定</li>
</ul>
]]></content:encoded></item><item><title>php.ini / .user.ini</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/php-ini/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/php-ini/</guid><description>&lt;p>&lt;code>php.ini&lt;/code> 是 PHP 的全域設定檔，控制 PHP 的 runtime 行為——記憶體上限、檔案上傳大小、最大執行時間、錯誤報告層級、時區、session 處理方式。&lt;code>.user.ini&lt;/code> 是 PHP 5.3 之後支援的目錄層級覆寫機制，放在站台目錄裡可以覆寫部分 &lt;code>php.ini&lt;/code> 的設定，不需要伺服器管理員權限；與管應用程式設定的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dotenv/" data-link-title=".env" data-link-desc="存放環境變數的純文字檔案，把機密值從程式碼分離出來">.env&lt;/a> 是不同層級的設定檔。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>&lt;code>php.ini&lt;/code> 由伺服器管理員管理，租用主機的使用者通常不能直接修改。&lt;code>.user.ini&lt;/code> 是使用者層級的設定覆寫——功能上類似 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/htaccess/" data-link-title=".htaccess" data-link-desc="Apache Web Server 的目錄層級設定檔，控制 URL rewrite、存取權限、PHP 設定覆寫與安全標頭">.htaccess&lt;/a> 對 Apache 的角色，但只管 PHP 設定。在 cPanel 環境裡，部分設定也可以透過「PHP 選擇器」的圖形介面調整。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>PHP 行為異常時要檢查的第一個地方。常見的情境：上傳檔案失敗（&lt;code>upload_max_filesize&lt;/code> 太小）、長時間運算被中斷（&lt;code>max_execution_time&lt;/code> 太短）、記憶體不足錯誤（&lt;code>memory_limit&lt;/code> 太低）、看不到錯誤訊息（&lt;code>display_errors&lt;/code> 關閉）。用 &lt;code>phpinfo()&lt;/code> 可以看到每一項設定的目前值和來源（&lt;code>php.ini&lt;/code> / &lt;code>.user.ini&lt;/code> / &lt;code>.htaccess&lt;/code>）。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>同一項 PHP 設定可能同時寫在 &lt;code>php.ini&lt;/code>、&lt;code>.user.ini&lt;/code> 與 &lt;code>.htaccess&lt;/code> 裡，接手維運時先用 &lt;code>phpinfo()&lt;/code> 讀出每一項的目前值與來源，再對照下表確認是否符合業務需求：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>設定&lt;/th>
 &lt;th>作用&lt;/th>
 &lt;th>常見預設值&lt;/th>
 &lt;th>接手時要確認的事&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>memory_limit&lt;/code>&lt;/td>
 &lt;td>PHP 程式的記憶體上限&lt;/td>
 &lt;td>128M&lt;/td>
 &lt;td>大型操作（匯出、圖片處理）是否夠用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>upload_max_filesize&lt;/code>&lt;/td>
 &lt;td>單檔上傳大小上限&lt;/td>
 &lt;td>2M&lt;/td>
 &lt;td>是否符合業務需求&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>post_max_size&lt;/code>&lt;/td>
 &lt;td>POST 請求的總大小上限&lt;/td>
 &lt;td>8M&lt;/td>
 &lt;td>要大於 upload_max_filesize&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>max_execution_time&lt;/code>&lt;/td>
 &lt;td>PHP 腳本最大執行秒數&lt;/td>
 &lt;td>30&lt;/td>
 &lt;td>長時間操作（備份、匯入）是否需要加長&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>error_reporting&lt;/code>&lt;/td>
 &lt;td>顯示哪些層級的錯誤&lt;/td>
 &lt;td>E_ALL&lt;/td>
 &lt;td>開發時開到 E_ALL、production 時關 display_errors&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>display_errors&lt;/code>&lt;/td>
 &lt;td>是否在頁面上顯示錯誤&lt;/td>
 &lt;td>Off&lt;/td>
 &lt;td>production 應該關閉（錯誤寫 log 不顯示給使用者）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;code>.user.ini&lt;/code> 的修改不需要重啟 Apache/nginx，但有快取時間（預設 300 秒，&lt;code>user_ini.cache_ttl&lt;/code>）——改完後要等最多 5 分鐘才生效；它只由 CGI/FastCGI（含 PHP-FPM）讀取，mod_php 下的對應做法是 &lt;code>.htaccess&lt;/code> 的 &lt;code>php_value&lt;/code>。&lt;code>php.ini&lt;/code> 的修改要重新載入讀它的那個程序：mod_php 是重載 Apache，PHP-FPM 是重載 PHP-FPM（web 伺服器不必動），見 &lt;a href="https://tarrragon.github.io/blog/programming/php/01-server-runtime/mod-php/#%e5%be%9e-mod_php-%e6%8f%9b%e5%88%b0-php-fpm-%e6%99%82-htaccess-%e7%9a%84%e8%ae%8a%e5%8c%96" data-link-title="mod_php：PHP 直譯器嵌進 Apache 程序的執行模型" data-link-desc="說明 mod_php 怎麼把 PHP 直譯器載入 Apache 的每個子程序：為什麼它要搭配 prefork MPM、每個子程序都帶著 PHP 對記憶體與並發的影響、所有站台共用 Apache 的執行帳號、php.ini 與 .htaccess 的設定方式，以及從 mod_php 搬到 PHP-FPM 時 .htaccess 的 php_value 與 .user.ini 的差異">mod_php&lt;/a>。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/htaccess/" data-link-title=".htaccess" data-link-desc="Apache Web Server 的目錄層級設定檔，控制 URL rewrite、存取權限、PHP 設定覆寫與安全標頭">.htaccess&lt;/a>：&lt;code>.htaccess&lt;/code> 管 Apache 行為（URL rewrite、存取控制），&lt;code>.user.ini&lt;/code> 管 PHP 行為（記憶體、執行時間），兩者互補&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dotenv/" data-link-title=".env" data-link-desc="存放環境變數的純文字檔案，把機密值從程式碼分離出來">.env&lt;/a>：&lt;code>.env&lt;/code> 管應用程式設定（DB 密碼、API key），&lt;code>php.ini&lt;/code> 管 PHP runtime 設定（記憶體、上傳大小）&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p><code>php.ini</code> 是 PHP 的全域設定檔，控制 PHP 的 runtime 行為——記憶體上限、檔案上傳大小、最大執行時間、錯誤報告層級、時區、session 處理方式。<code>.user.ini</code> 是 PHP 5.3 之後支援的目錄層級覆寫機制，放在站台目錄裡可以覆寫部分 <code>php.ini</code> 的設定，不需要伺服器管理員權限；與管應用程式設定的 <a href="/blog/devops/infra/knowledge-cards/dotenv/" data-link-title=".env" data-link-desc="存放環境變數的純文字檔案，把機密值從程式碼分離出來">.env</a> 是不同層級的設定檔。</p>
<h2 id="概念位置">概念位置</h2>
<p><code>php.ini</code> 由伺服器管理員管理，租用主機的使用者通常不能直接修改。<code>.user.ini</code> 是使用者層級的設定覆寫——功能上類似 <a href="/blog/devops/infra/knowledge-cards/htaccess/" data-link-title=".htaccess" data-link-desc="Apache Web Server 的目錄層級設定檔，控制 URL rewrite、存取權限、PHP 設定覆寫與安全標頭">.htaccess</a> 對 Apache 的角色，但只管 PHP 設定。在 cPanel 環境裡，部分設定也可以透過「PHP 選擇器」的圖形介面調整。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>PHP 行為異常時要檢查的第一個地方。常見的情境：上傳檔案失敗（<code>upload_max_filesize</code> 太小）、長時間運算被中斷（<code>max_execution_time</code> 太短）、記憶體不足錯誤（<code>memory_limit</code> 太低）、看不到錯誤訊息（<code>display_errors</code> 關閉）。用 <code>phpinfo()</code> 可以看到每一項設定的目前值和來源（<code>php.ini</code> / <code>.user.ini</code> / <code>.htaccess</code>）。</p>
<h2 id="設計責任">設計責任</h2>
<p>同一項 PHP 設定可能同時寫在 <code>php.ini</code>、<code>.user.ini</code> 與 <code>.htaccess</code> 裡，接手維運時先用 <code>phpinfo()</code> 讀出每一項的目前值與來源，再對照下表確認是否符合業務需求：</p>
<table>
  <thead>
      <tr>
          <th>設定</th>
          <th>作用</th>
          <th>常見預設值</th>
          <th>接手時要確認的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>memory_limit</code></td>
          <td>PHP 程式的記憶體上限</td>
          <td>128M</td>
          <td>大型操作（匯出、圖片處理）是否夠用</td>
      </tr>
      <tr>
          <td><code>upload_max_filesize</code></td>
          <td>單檔上傳大小上限</td>
          <td>2M</td>
          <td>是否符合業務需求</td>
      </tr>
      <tr>
          <td><code>post_max_size</code></td>
          <td>POST 請求的總大小上限</td>
          <td>8M</td>
          <td>要大於 upload_max_filesize</td>
      </tr>
      <tr>
          <td><code>max_execution_time</code></td>
          <td>PHP 腳本最大執行秒數</td>
          <td>30</td>
          <td>長時間操作（備份、匯入）是否需要加長</td>
      </tr>
      <tr>
          <td><code>error_reporting</code></td>
          <td>顯示哪些層級的錯誤</td>
          <td>E_ALL</td>
          <td>開發時開到 E_ALL、production 時關 display_errors</td>
      </tr>
      <tr>
          <td><code>display_errors</code></td>
          <td>是否在頁面上顯示錯誤</td>
          <td>Off</td>
          <td>production 應該關閉（錯誤寫 log 不顯示給使用者）</td>
      </tr>
  </tbody>
</table>
<p><code>.user.ini</code> 的修改不需要重啟 Apache/nginx，但有快取時間（預設 300 秒，<code>user_ini.cache_ttl</code>）——改完後要等最多 5 分鐘才生效；它只由 CGI/FastCGI（含 PHP-FPM）讀取，mod_php 下的對應做法是 <code>.htaccess</code> 的 <code>php_value</code>。<code>php.ini</code> 的修改要重新載入讀它的那個程序：mod_php 是重載 Apache，PHP-FPM 是重載 PHP-FPM（web 伺服器不必動），見 <a href="/blog/programming/php/01-server-runtime/mod-php/#%e5%be%9e-mod_php-%e6%8f%9b%e5%88%b0-php-fpm-%e6%99%82-htaccess-%e7%9a%84%e8%ae%8a%e5%8c%96" data-link-title="mod_php：PHP 直譯器嵌進 Apache 程序的執行模型" data-link-desc="說明 mod_php 怎麼把 PHP 直譯器載入 Apache 的每個子程序：為什麼它要搭配 prefork MPM、每個子程序都帶著 PHP 對記憶體與並發的影響、所有站台共用 Apache 的執行帳號、php.ini 與 .htaccess 的設定方式，以及從 mod_php 搬到 PHP-FPM 時 .htaccess 的 php_value 與 .user.ini 的差異">mod_php</a>。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/htaccess/" data-link-title=".htaccess" data-link-desc="Apache Web Server 的目錄層級設定檔，控制 URL rewrite、存取權限、PHP 設定覆寫與安全標頭">.htaccess</a>：<code>.htaccess</code> 管 Apache 行為（URL rewrite、存取控制），<code>.user.ini</code> 管 PHP 行為（記憶體、執行時間），兩者互補</li>
<li><a href="/blog/devops/infra/knowledge-cards/dotenv/" data-link-title=".env" data-link-desc="存放環境變數的純文字檔案，把機密值從程式碼分離出來">.env</a>：<code>.env</code> 管應用程式設定（DB 密碼、API key），<code>php.ini</code> 管 PHP runtime 設定（記憶體、上傳大小）</li>
</ul>
]]></content:encoded></item><item><title>DNS</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dns/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dns/</guid><description>&lt;p>DNS（Domain Name System）是把人類可讀的域名（&lt;code>example.com&lt;/code>）轉成機器可達的 IP 位址（&lt;code>93.184.216.34&lt;/code>）的分散式查詢系統。瀏覽器輸入網址後，作業系統先查本地快取、再逐層查詢 DNS server，最終拿到 IP 才能建立連線。DNS 也是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssl-tls/" data-link-title="SSL / TLS" data-link-desc="加密 client 與 server 之間通訊的協定，讓 HTTPS 成為可能。TLS 是 SSL 的後繼者，但 SSL 憑證的稱呼仍廣泛使用">SSL/TLS&lt;/a> 憑證網域驗證依賴的機制之一。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>DNS 在 infra 裡扮演「服務的門牌」角色。平台遷移、環境切換、TLS 憑證驗證都經過 DNS。&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB&lt;/a> 或 CDN 前面通常掛一層 DNS record 作為穩定入口——IP 會隨資源重建而變，DNS 名稱不變。&lt;/p>
&lt;h2 id="常見的記錄類型">常見的記錄類型&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>類型&lt;/th>
 &lt;th>指向什麼&lt;/th>
 &lt;th>典型用途&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>A&lt;/td>
 &lt;td>IPv4 位址&lt;/td>
 &lt;td>主要的域名 → IP 對應&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AAAA&lt;/td>
 &lt;td>IPv6 位址&lt;/td>
 &lt;td>IPv6 環境&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CNAME&lt;/td>
 &lt;td>另一個域名&lt;/td>
 &lt;td>別名（&lt;code>www&lt;/code> → &lt;code>example.com&lt;/code>）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>NS&lt;/td>
 &lt;td>負責管理的 DNS server&lt;/td>
 &lt;td>子域委派（dev.example.com）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>MX&lt;/td>
 &lt;td>郵件伺服器&lt;/td>
 &lt;td>email routing&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>TXT&lt;/td>
 &lt;td>任意文字&lt;/td>
 &lt;td>SPF / DKIM / 域名驗證（ACM）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Alias&lt;/td>
 &lt;td>AWS 特有，指向 ALB 等&lt;/td>
 &lt;td>跟 A record 等效但支援 zone apex&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>平台遷移時 DNS 切換是最後一步也是最不可控的一步——TTL（Time To Live）決定舊記錄被各地 DNS resolver 快取多久。TTL 300 秒代表切換後最多 5 分鐘全部 client 會指向新 IP；TTL 86400（1 天）代表最慢要等一天。遷移前 48 小時先降 TTL 到 300 秒，讓快取過期後所有 resolver 都拿到短 TTL 版本，切換時才能快速生效。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>一筆 DNS 記錄從修改到生效要經過兩段：先由有權限的人在負責的 zone 改記錄，再等各地 resolver 快取的舊記錄過期，設計時的決定分別落在這兩段：誰管這個域名的 zone（Route 53 / Cloudflare / 域名商）、子域怎麼委派（dev / staging 用 NS delegation 交給不同 zone）、TTL 設多少（平常 3600 秒夠用、遷移前降到 300）。ACM 的 DNS 驗證也依賴 DNS——建立 TXT 或 CNAME 記錄證明域名歸屬。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB&lt;/a> — DNS 記錄通常指向 ALB 作為流量入口&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssl-tls/" data-link-title="SSL / TLS" data-link-desc="加密 client 與 server 之間通訊的協定，讓 HTTPS 成為可能。TLS 是 SSL 的後繼者，但 SSL 憑證的稱呼仍廣泛使用">SSL/TLS&lt;/a> — TLS 憑證的 DNS 驗證依賴 DNS record&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層&lt;/a> — DNS 作為責任鏈第一段（門牌、只在連線前查一次、不承載流量）的定位&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>DNS（Domain Name System）是把人類可讀的域名（<code>example.com</code>）轉成機器可達的 IP 位址（<code>93.184.216.34</code>）的分散式查詢系統。瀏覽器輸入網址後，作業系統先查本地快取、再逐層查詢 DNS server，最終拿到 IP 才能建立連線。DNS 也是 <a href="/blog/devops/infra/knowledge-cards/ssl-tls/" data-link-title="SSL / TLS" data-link-desc="加密 client 與 server 之間通訊的協定，讓 HTTPS 成為可能。TLS 是 SSL 的後繼者，但 SSL 憑證的稱呼仍廣泛使用">SSL/TLS</a> 憑證網域驗證依賴的機制之一。</p>
<h2 id="概念位置">概念位置</h2>
<p>DNS 在 infra 裡扮演「服務的門牌」角色。平台遷移、環境切換、TLS 憑證驗證都經過 DNS。<a href="/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB</a> 或 CDN 前面通常掛一層 DNS record 作為穩定入口——IP 會隨資源重建而變，DNS 名稱不變。</p>
<h2 id="常見的記錄類型">常見的記錄類型</h2>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>指向什麼</th>
          <th>典型用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>A</td>
          <td>IPv4 位址</td>
          <td>主要的域名 → IP 對應</td>
      </tr>
      <tr>
          <td>AAAA</td>
          <td>IPv6 位址</td>
          <td>IPv6 環境</td>
      </tr>
      <tr>
          <td>CNAME</td>
          <td>另一個域名</td>
          <td>別名（<code>www</code> → <code>example.com</code>）</td>
      </tr>
      <tr>
          <td>NS</td>
          <td>負責管理的 DNS server</td>
          <td>子域委派（dev.example.com）</td>
      </tr>
      <tr>
          <td>MX</td>
          <td>郵件伺服器</td>
          <td>email routing</td>
      </tr>
      <tr>
          <td>TXT</td>
          <td>任意文字</td>
          <td>SPF / DKIM / 域名驗證（ACM）</td>
      </tr>
      <tr>
          <td>Alias</td>
          <td>AWS 特有，指向 ALB 等</td>
          <td>跟 A record 等效但支援 zone apex</td>
      </tr>
  </tbody>
</table>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>平台遷移時 DNS 切換是最後一步也是最不可控的一步——TTL（Time To Live）決定舊記錄被各地 DNS resolver 快取多久。TTL 300 秒代表切換後最多 5 分鐘全部 client 會指向新 IP；TTL 86400（1 天）代表最慢要等一天。遷移前 48 小時先降 TTL 到 300 秒，讓快取過期後所有 resolver 都拿到短 TTL 版本，切換時才能快速生效。</p>
<h2 id="設計責任">設計責任</h2>
<p>一筆 DNS 記錄從修改到生效要經過兩段：先由有權限的人在負責的 zone 改記錄，再等各地 resolver 快取的舊記錄過期，設計時的決定分別落在這兩段：誰管這個域名的 zone（Route 53 / Cloudflare / 域名商）、子域怎麼委派（dev / staging 用 NS delegation 交給不同 zone）、TTL 設多少（平常 3600 秒夠用、遷移前降到 300）。ACM 的 DNS 驗證也依賴 DNS——建立 TXT 或 CNAME 記錄證明域名歸屬。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB</a> — DNS 記錄通常指向 ALB 作為流量入口</li>
<li><a href="/blog/devops/infra/knowledge-cards/ssl-tls/" data-link-title="SSL / TLS" data-link-desc="加密 client 與 server 之間通訊的協定，讓 HTTPS 成為可能。TLS 是 SSL 的後繼者，但 SSL 憑證的稱呼仍廣泛使用">SSL/TLS</a> — TLS 憑證的 DNS 驗證依賴 DNS record</li>
<li><a href="/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層</a> — DNS 作為責任鏈第一段（門牌、只在連線前查一次、不承載流量）的定位</li>
</ul>
]]></content:encoded></item><item><title>SSL / TLS</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssl-tls/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssl-tls/</guid><description>&lt;p>TLS（Transport Layer Security）加密 client 與 server 之間的通訊，防止中間人竊聽或竄改。HTTPS 就是 HTTP 加上 TLS 加密層。SSL 是 TLS 的前身、所有版本都已被棄用，但「SSL 憑證」這個稱呼仍然廣泛使用——實際上指的是 TLS 憑證。TLS 憑證的核發常搭配 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dns/" data-link-title="DNS" data-link-desc="Domain Name System — 把域名轉成 IP 位址的系統，以及 A record、CNAME、NS、TTL 的角色">DNS&lt;/a> 驗證持有權。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>TLS 在 infra 裡負責「傳輸安全」。&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB&lt;/a> 的 HTTPS listener 需要掛一張 TLS 憑證；ACM（AWS Certificate Manager）提供免費的憑證申請與自動續期；Let&amp;rsquo;s Encrypt 是跨平台的免費 CA（Certificate Authority，憑證簽發機構）。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>TLS 憑證有到期日。過期的憑證會讓瀏覽器顯示安全警告、部分 client 直接拒絕連線。ACM 管理的憑證會自動續期（前提是 DNS 驗證記錄仍然存在）；手動上傳的憑證需要人工追蹤到期日。接手維運時要確認：憑證的簽發者是誰、到期日是什麼時候、續期是自動還是手動。&lt;/p>
&lt;p>用 CLI 查看遠端憑證資訊：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="p">|&lt;/span> openssl s_client -connect example.com:443 2&amp;gt;/dev/null &lt;span class="p">|&lt;/span> openssl x509 -noout -dates -issuer&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>TLS 憑證過期會讓瀏覽器顯示安全警告、部分 client 直接拒絕連線，而續期是否能自動完成，取決於憑證的來源與驗證網域持有權的方式；設定時一併決定的是：憑證從哪裡來（ACM 免費但只能用在 AWS 服務上、Let&amp;rsquo;s Encrypt 免費且跨平台）、驗證方式（DNS 驗證適合自動化、email 驗證較手動）、是否需要多域名的 SAN 憑證（一張憑證涵蓋 &lt;code>example.com&lt;/code> + &lt;code>*.example.com&lt;/code>）、HTTP → HTTPS 的強制跳轉怎麼設。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dns/" data-link-title="DNS" data-link-desc="Domain Name System — 把域名轉成 IP 位址的系統，以及 A record、CNAME、NS、TTL 的角色">DNS&lt;/a> — TLS 憑證的 DNS 驗證依賴 DNS record&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB&lt;/a> — HTTPS listener 需要掛 TLS 憑證&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層&lt;/a> — TLS 終結放在責任鏈哪一層（入口終結 / 透傳 / 重新加密）的架構選擇&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>TLS（Transport Layer Security）加密 client 與 server 之間的通訊，防止中間人竊聽或竄改。HTTPS 就是 HTTP 加上 TLS 加密層。SSL 是 TLS 的前身、所有版本都已被棄用，但「SSL 憑證」這個稱呼仍然廣泛使用——實際上指的是 TLS 憑證。TLS 憑證的核發常搭配 <a href="/blog/devops/infra/knowledge-cards/dns/" data-link-title="DNS" data-link-desc="Domain Name System — 把域名轉成 IP 位址的系統，以及 A record、CNAME、NS、TTL 的角色">DNS</a> 驗證持有權。</p>
<h2 id="概念位置">概念位置</h2>
<p>TLS 在 infra 裡負責「傳輸安全」。<a href="/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB</a> 的 HTTPS listener 需要掛一張 TLS 憑證；ACM（AWS Certificate Manager）提供免費的憑證申請與自動續期；Let&rsquo;s Encrypt 是跨平台的免費 CA（Certificate Authority，憑證簽發機構）。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>TLS 憑證有到期日。過期的憑證會讓瀏覽器顯示安全警告、部分 client 直接拒絕連線。ACM 管理的憑證會自動續期（前提是 DNS 驗證記錄仍然存在）；手動上傳的憑證需要人工追蹤到期日。接手維運時要確認：憑證的簽發者是誰、到期日是什麼時候、續期是自動還是手動。</p>
<p>用 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"><span class="nb">echo</span> <span class="p">|</span> openssl s_client -connect example.com:443 2&gt;/dev/null <span class="p">|</span> openssl x509 -noout -dates -issuer</span></span></code></pre></div><h2 id="設計責任">設計責任</h2>
<p>TLS 憑證過期會讓瀏覽器顯示安全警告、部分 client 直接拒絕連線，而續期是否能自動完成，取決於憑證的來源與驗證網域持有權的方式；設定時一併決定的是：憑證從哪裡來（ACM 免費但只能用在 AWS 服務上、Let&rsquo;s Encrypt 免費且跨平台）、驗證方式（DNS 驗證適合自動化、email 驗證較手動）、是否需要多域名的 SAN 憑證（一張憑證涵蓋 <code>example.com</code> + <code>*.example.com</code>）、HTTP → HTTPS 的強制跳轉怎麼設。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/dns/" data-link-title="DNS" data-link-desc="Domain Name System — 把域名轉成 IP 位址的系統，以及 A record、CNAME、NS、TTL 的角色">DNS</a> — TLS 憑證的 DNS 驗證依賴 DNS record</li>
<li><a href="/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB</a> — HTTPS listener 需要掛 TLS 憑證</li>
<li><a href="/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層</a> — TLS 終結放在責任鏈哪一層（入口終結 / 透傳 / 重新加密）的架構選擇</li>
</ul>
]]></content:encoded></item><item><title>SSH</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/</guid><description>&lt;p>SSH（Secure Shell）是加密的遠端 shell 連線協定，讓操作者在本地終端機執行遠端伺服器上的指令。連線建立後，操作者看到的是遠端伺服器的命令列——可以跑任何該伺服器上安裝的 CLI 工具。SSH 建立安全連線所依賴的公私鑰機制，跟 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssl-tls/" data-link-title="SSL / TLS" data-link-desc="加密 client 與 server 之間通訊的協定，讓 HTTPS 成為可能。TLS 是 SSL 的後繼者，但 SSL 憑證的稱呼仍廣泛使用">SSL/TLS&lt;/a> 以數位憑證驗證伺服器身分同屬非對稱加密的應用。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>SSH 在接手維運的情境裡是一條關鍵分界線：有 SSH 存取就能用 &lt;code>mysqldump&lt;/code>、&lt;code>git&lt;/code>、&lt;code>systemctl&lt;/code> 等 CLI 工具操作伺服器；沒有 SSH 就只能用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ftp/" data-link-title="FTP" data-link-desc="File Transfer Protocol — 檔案傳輸協定，無 SSH 環境的主要檔案管理方式。SFTP 和 FTPS 是其加密變體">FTP&lt;/a> 傳檔案、用 phpMyAdmin 管資料庫、用 cPanel 改設定。兩種情境的操作流程和可用工具完全不同。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>判斷有沒有 SSH 存取：嘗試 &lt;code>ssh user@host&lt;/code>。如果連線成功進入命令列就有；如果 timeout 或被拒，可能是主機不開放 SSH（共享主機常見）、或 port 不是預設的 22、或需要 IP 白名單。cPanel 的「終端機」功能有時提供 web-based SSH，但功能受限。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>SSH 的認證方式有兩種：密碼（簡單但不安全，容易被暴力嘗試）和 SSH key pair（公鑰放在 server 的 &lt;code>~/.ssh/authorized_keys&lt;/code>，私鑰留在 client）。生產環境應該用 key 認證並關閉密碼登入。&lt;/p>
&lt;p>接手維運時要確認：SSH 的登入帳號是什麼、用密碼還是 key、key 在哪裡、有沒有其他人也有存取權限。前任維護者的 SSH key 如果還在 &lt;code>authorized_keys&lt;/code> 裡，離職後應該移除。主機一多，逐台刪除就難以盤點，這時改用隨帳號收回的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login&lt;/a> 或會自動到期的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）&lt;/a>，選擇條件見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>的〈主機登入驗證的做法：SSH 金鑰怎麼發、怎麼收回〉。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 產生 SSH key pair&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">ssh-keygen -t ed25519 -C &lt;span class="s2">&amp;#34;your-email@example.com&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>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 把公鑰加到遠端 server&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ftp/" data-link-title="FTP" data-link-desc="File Transfer Protocol — 檔案傳輸協定，無 SSH 環境的主要檔案管理方式。SFTP 和 FTPS 是其加密變體">FTP&lt;/a> — 沒有 SSH 時的檔案傳輸替代方案&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）&lt;/a> — 由 CA 簽發、會自動到期的 SSH 金鑰&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login&lt;/a> — GCP 上讓登入權限跟著帳號收回的做法&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）&lt;/a> — 只有一台主機對外開 SSH、其餘經由它轉接&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>SSH（Secure Shell）是加密的遠端 shell 連線協定，讓操作者在本地終端機執行遠端伺服器上的指令。連線建立後，操作者看到的是遠端伺服器的命令列——可以跑任何該伺服器上安裝的 CLI 工具。SSH 建立安全連線所依賴的公私鑰機制，跟 <a href="/blog/devops/infra/knowledge-cards/ssl-tls/" data-link-title="SSL / TLS" data-link-desc="加密 client 與 server 之間通訊的協定，讓 HTTPS 成為可能。TLS 是 SSL 的後繼者，但 SSL 憑證的稱呼仍廣泛使用">SSL/TLS</a> 以數位憑證驗證伺服器身分同屬非對稱加密的應用。</p>
<h2 id="概念位置">概念位置</h2>
<p>SSH 在接手維運的情境裡是一條關鍵分界線：有 SSH 存取就能用 <code>mysqldump</code>、<code>git</code>、<code>systemctl</code> 等 CLI 工具操作伺服器；沒有 SSH 就只能用 <a href="/blog/devops/infra/knowledge-cards/ftp/" data-link-title="FTP" data-link-desc="File Transfer Protocol — 檔案傳輸協定，無 SSH 環境的主要檔案管理方式。SFTP 和 FTPS 是其加密變體">FTP</a> 傳檔案、用 phpMyAdmin 管資料庫、用 cPanel 改設定。兩種情境的操作流程和可用工具完全不同。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>判斷有沒有 SSH 存取：嘗試 <code>ssh user@host</code>。如果連線成功進入命令列就有；如果 timeout 或被拒，可能是主機不開放 SSH（共享主機常見）、或 port 不是預設的 22、或需要 IP 白名單。cPanel 的「終端機」功能有時提供 web-based SSH，但功能受限。</p>
<h2 id="設計責任">設計責任</h2>
<p>SSH 的認證方式有兩種：密碼（簡單但不安全，容易被暴力嘗試）和 SSH key pair（公鑰放在 server 的 <code>~/.ssh/authorized_keys</code>，私鑰留在 client）。生產環境應該用 key 認證並關閉密碼登入。</p>
<p>接手維運時要確認：SSH 的登入帳號是什麼、用密碼還是 key、key 在哪裡、有沒有其他人也有存取權限。前任維護者的 SSH key 如果還在 <code>authorized_keys</code> 裡，離職後應該移除。主機一多，逐台刪除就難以盤點，這時改用隨帳號收回的 <a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a> 或會自動到期的 <a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）</a>，選擇條件見<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>的〈主機登入驗證的做法：SSH 金鑰怎麼發、怎麼收回〉。</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"># 產生 SSH key pair</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh-keygen -t ed25519 -C <span class="s2">&#34;your-email@example.com&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 把公鑰加到遠端 server</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host</span></span></code></pre></div><h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ftp/" data-link-title="FTP" data-link-desc="File Transfer Protocol — 檔案傳輸協定，無 SSH 環境的主要檔案管理方式。SFTP 和 FTPS 是其加密變體">FTP</a> — 沒有 SSH 時的檔案傳輸替代方案</li>
<li><a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）</a> — 由 CA 簽發、會自動到期的 SSH 金鑰</li>
<li><a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a> — GCP 上讓登入權限跟著帳號收回的做法</li>
<li><a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> — 只有一台主機對外開 SSH、其餘經由它轉接</li>
</ul>
]]></content:encoded></item><item><title>FTP</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ftp/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ftp/</guid><description>&lt;p>FTP（File Transfer Protocol）是把檔案在本地電腦與遠端伺服器之間上傳/下載的協定。操作者透過 FTP client（如 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/filezilla/" data-link-title="FileZilla" data-link-desc="跨平台的 FTP/SFTP client，提供目錄同步瀏覽和檔案比較功能">FileZilla&lt;/a>）連線到伺服器，看到遠端的目錄結構，用拖放或指令傳輸檔案。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>FTP 在無 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 的環境裡是唯一的檔案管理途徑——程式碼部署靠 FTP 上傳、備份靠 FTP 下載、檔案比對靠 FTP client 的目錄比較功能。它是接手維運模組「無 SSH 環境」路線的核心工具。&lt;/p>
&lt;h2 id="ftp-的變體">FTP 的變體&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>協定&lt;/th>
 &lt;th>加密方式&lt;/th>
 &lt;th>常見情境&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>FTP&lt;/td>
 &lt;td>無加密（明文傳輸）&lt;/td>
 &lt;td>老舊主機、內部網路&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>FTPS&lt;/td>
 &lt;td>FTP + TLS 加密&lt;/td>
 &lt;td>支援 SSL 的主機&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SFTP&lt;/td>
 &lt;td>走 SSH 通道（完全不同協定）&lt;/td>
 &lt;td>有 SSH 存取的伺服器&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>多數 FTP client（FileZilla、WinSCP）同時支援三種協定。如果伺服器有 SSH，用 SFTP 比 FTP 安全且功能更多。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>FTP 操作的三個限制在接手維運時要意識到：第一，非原子操作——檔案逐一上傳，上傳過程中伺服器上同時存在新舊版本的混合狀態。第二，不支援指令執行——只能傳檔案、不能跑腳本或重啟服務。第三，沒有版本控制——上傳覆蓋就是覆蓋，沒有 diff、沒有 rollback。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>用 FTP 部署時要建立的紀律：本地先 Git commit 再上傳（Git 提供版本控制、FTP 只負責傳輸）；上傳前用目錄比較確認差異；關鍵檔案（&lt;code>index.php&lt;/code>、&lt;code>.htaccess&lt;/code>）上傳前先從 server 下載一份備份。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> — 有 SSH 時用 SFTP 或 SCP 替代 FTP&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/filezilla/" data-link-title="FileZilla" data-link-desc="跨平台的 FTP/SFTP client，提供目錄同步瀏覽和檔案比較功能">FileZilla&lt;/a> — 最常用的 FTP client&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>FTP（File Transfer Protocol）是把檔案在本地電腦與遠端伺服器之間上傳/下載的協定。操作者透過 FTP client（如 <a href="/blog/devops/infra/knowledge-cards/filezilla/" data-link-title="FileZilla" data-link-desc="跨平台的 FTP/SFTP client，提供目錄同步瀏覽和檔案比較功能">FileZilla</a>）連線到伺服器，看到遠端的目錄結構，用拖放或指令傳輸檔案。</p>
<h2 id="概念位置">概念位置</h2>
<p>FTP 在無 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 的環境裡是唯一的檔案管理途徑——程式碼部署靠 FTP 上傳、備份靠 FTP 下載、檔案比對靠 FTP client 的目錄比較功能。它是接手維運模組「無 SSH 環境」路線的核心工具。</p>
<h2 id="ftp-的變體">FTP 的變體</h2>
<table>
  <thead>
      <tr>
          <th>協定</th>
          <th>加密方式</th>
          <th>常見情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>FTP</td>
          <td>無加密（明文傳輸）</td>
          <td>老舊主機、內部網路</td>
      </tr>
      <tr>
          <td>FTPS</td>
          <td>FTP + TLS 加密</td>
          <td>支援 SSL 的主機</td>
      </tr>
      <tr>
          <td>SFTP</td>
          <td>走 SSH 通道（完全不同協定）</td>
          <td>有 SSH 存取的伺服器</td>
      </tr>
  </tbody>
</table>
<p>多數 FTP client（FileZilla、WinSCP）同時支援三種協定。如果伺服器有 SSH，用 SFTP 比 FTP 安全且功能更多。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>FTP 操作的三個限制在接手維運時要意識到：第一，非原子操作——檔案逐一上傳，上傳過程中伺服器上同時存在新舊版本的混合狀態。第二，不支援指令執行——只能傳檔案、不能跑腳本或重啟服務。第三，沒有版本控制——上傳覆蓋就是覆蓋，沒有 diff、沒有 rollback。</p>
<h2 id="設計責任">設計責任</h2>
<p>用 FTP 部署時要建立的紀律：本地先 Git commit 再上傳（Git 提供版本控制、FTP 只負責傳輸）；上傳前用目錄比較確認差異；關鍵檔案（<code>index.php</code>、<code>.htaccess</code>）上傳前先從 server 下載一份備份。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> — 有 SSH 時用 SFTP 或 SCP 替代 FTP</li>
<li><a href="/blog/devops/infra/knowledge-cards/filezilla/" data-link-title="FileZilla" data-link-desc="跨平台的 FTP/SFTP client，提供目錄同步瀏覽和檔案比較功能">FileZilla</a> — 最常用的 FTP client</li>
</ul>
]]></content:encoded></item><item><title>cron</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cron/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cron/</guid><description>&lt;p>cron 是 Unix/Linux 系統內建的排程工作管理器，按預定的時間表自動執行指令。一個 cron job 定義「什麼時間跑什麼指令」，系統背景的 cron daemon 負責到時間就執行，管理 cron 通常透過 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 存取伺服器。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>cron 在接手維運時是容易被忽略的隱藏工作——它不像 web 服務有明顯的入口，但可能負責資料庫備份、快取清除、報表產出、日誌清理等關鍵任務。漏掉一個 cron job 可能讓備份停止、快取永不過期、報表不再更新，而且不會立刻有人發現；常見的資料庫備份工具見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/mysqldump/" data-link-title="mysqldump" data-link-desc="MySQL / MariaDB 的 CLI 備份工具，把資料庫匯出成 SQL 語句的純文字檔">mysqldump&lt;/a>。&lt;/p>
&lt;h2 id="crontab-格式">crontab 格式&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl"># 分 時 日 月 週 指令
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">0 3 * * * /usr/bin/php /var/www/backup.php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">*/5 * * * * /usr/bin/curl -s https://example.com/cron/heartbeat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">0 0 1 * * /usr/bin/find /tmp -mtime +7 -delete&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>五個時間欄位依序是分鐘（0-59）、小時（0-23）、日（1-31）、月（1-12）、星期幾（0-7，0 和 7 都是星期日）。&lt;code>*&lt;/code> 代表「每一個」，&lt;code>*/5&lt;/code> 代表「每 5 個」。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>接手維運時盤點 cron job：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 當前使用者的 crontab&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">crontab -l
&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 class="c1"># 所有使用者的 crontab（需要 root）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">for&lt;/span> user in &lt;span class="k">$(&lt;/span>cut -f1 -d: /etc/passwd&lt;span class="k">)&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> crontab -u &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$user&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> -l 2&amp;gt;/dev/null &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;=== &lt;/span>&lt;span class="nv">$user&lt;/span>&lt;span class="s2"> ===&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">done&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 class="c1"># 系統級 cron&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">cat /etc/crontab
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">ls /etc/cron.d/&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>沒有 SSH 時（cPanel 環境），在 cPanel 的「Cron 工作」頁面查看和匯出。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>cron job 跑失敗時不會有使用者看到錯誤畫面，除了排程頻率之外，設計時另外兩項決定的作用是讓失敗被人看見：執行失敗時的通知方式（cron 預設把輸出寄 email，但 email 常沒配好）、日誌記錄（指令的 stdout/stderr 導到 log 檔）。遷移或升級時，cron job 要隨著遷移——忘了搬等於停掉排程但沒人知道。&lt;/p>
&lt;p>用 systemd 的主機上，同一類定期工作也可以寫成 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/systemd-timer/" data-link-title="systemd timer" data-link-desc="排程的工作沒有在預期的時間執行、主機關機期間錯過的排程不知道會不會補跑、要在 cron 與 systemd timer 之間選一種寫定期工作，或讀到 *.timer 檔與 systemctl list-timers 的輸出時">systemd timer&lt;/a>：多寫一個檔，換到的是關機期間錯過的執行開機後補跑、輸出進 journal、失敗有狀態可以接告警。&lt;/p>
&lt;p>雲端替代品：AWS CloudWatch Events / EventBridge、GCP Cloud Scheduler、Azure Logic Apps。這些服務提供 web UI 管理、失敗通知、執行歷史，但需要額外設定。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> — 盤點和管理 cron 需要 SSH 存取&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>cron 是 Unix/Linux 系統內建的排程工作管理器，按預定的時間表自動執行指令。一個 cron job 定義「什麼時間跑什麼指令」，系統背景的 cron daemon 負責到時間就執行，管理 cron 通常透過 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 存取伺服器。</p>
<h2 id="概念位置">概念位置</h2>
<p>cron 在接手維運時是容易被忽略的隱藏工作——它不像 web 服務有明顯的入口，但可能負責資料庫備份、快取清除、報表產出、日誌清理等關鍵任務。漏掉一個 cron job 可能讓備份停止、快取永不過期、報表不再更新，而且不會立刻有人發現；常見的資料庫備份工具見 <a href="/blog/devops/infra/knowledge-cards/mysqldump/" data-link-title="mysqldump" data-link-desc="MySQL / MariaDB 的 CLI 備份工具，把資料庫匯出成 SQL 語句的純文字檔">mysqldump</a>。</p>
<h2 id="crontab-格式">crontab 格式</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># 分 時 日 月 週  指令
</span></span><span class="line"><span class="ln">2</span><span class="cl">0  3  *  *  *    /usr/bin/php /var/www/backup.php
</span></span><span class="line"><span class="ln">3</span><span class="cl">*/5 * *  *  *    /usr/bin/curl -s https://example.com/cron/heartbeat
</span></span><span class="line"><span class="ln">4</span><span class="cl">0  0  1  *  *    /usr/bin/find /tmp -mtime +7 -delete</span></span></code></pre></div><p>五個時間欄位依序是分鐘（0-59）、小時（0-23）、日（1-31）、月（1-12）、星期幾（0-7，0 和 7 都是星期日）。<code>*</code> 代表「每一個」，<code>*/5</code> 代表「每 5 個」。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>接手維運時盤點 cron job：</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"># 當前使用者的 crontab</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">crontab -l
</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 class="c1"># 所有使用者的 crontab（需要 root）</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">for</span> user in <span class="k">$(</span>cut -f1 -d: /etc/passwd<span class="k">)</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  crontab -u <span class="s2">&#34;</span><span class="nv">$user</span><span class="s2">&#34;</span> -l 2&gt;/dev/null <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">&#34;=== </span><span class="nv">$user</span><span class="s2"> ===&#34;</span>
</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"># 系統級 cron</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">cat /etc/crontab
</span></span><span class="line"><span class="ln">11</span><span class="cl">ls /etc/cron.d/</span></span></code></pre></div><p>沒有 SSH 時（cPanel 環境），在 cPanel 的「Cron 工作」頁面查看和匯出。</p>
<h2 id="設計責任">設計責任</h2>
<p>cron job 跑失敗時不會有使用者看到錯誤畫面，除了排程頻率之外，設計時另外兩項決定的作用是讓失敗被人看見：執行失敗時的通知方式（cron 預設把輸出寄 email，但 email 常沒配好）、日誌記錄（指令的 stdout/stderr 導到 log 檔）。遷移或升級時，cron job 要隨著遷移——忘了搬等於停掉排程但沒人知道。</p>
<p>用 systemd 的主機上，同一類定期工作也可以寫成 <a href="/blog/linux/dotfile/knowledge-cards/systemd-timer/" data-link-title="systemd timer" data-link-desc="排程的工作沒有在預期的時間執行、主機關機期間錯過的排程不知道會不會補跑、要在 cron 與 systemd timer 之間選一種寫定期工作，或讀到 *.timer 檔與 systemctl list-timers 的輸出時">systemd timer</a>：多寫一個檔，換到的是關機期間錯過的執行開機後補跑、輸出進 journal、失敗有狀態可以接告警。</p>
<p>雲端替代品：AWS CloudWatch Events / EventBridge、GCP Cloud Scheduler、Azure Logic Apps。這些服務提供 web UI 管理、失敗通知、執行歷史，但需要額外設定。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> — 盤點和管理 cron 需要 SSH 存取</li>
</ul>
]]></content:encoded></item><item><title>nginx</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nginx/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nginx/</guid><description>&lt;p>nginx 是高效能的 Web Server 和 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy&lt;/a>，以非同步事件驅動架構處理大量並發連線。它在全球 web server 市場佔有率與 Apache 並列前二，新部署的伺服器多數選 nginx。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>nginx 在 infra 裡常見的角色有三種：作為 reverse proxy 把請求轉給後端應用（Node.js、PHP-FPM、Python WSGI）、作為靜態檔案伺服器、作為 TLS 終結點處理 HTTPS。&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB&lt;/a> 在雲端環境承擔了部分 nginx 的職責（負載平衡、TLS 終結），但 VPS 環境裡 nginx 仍然是標準選擇。&lt;/p>
&lt;h2 id="跟-apache-的關鍵差別">跟 Apache 的關鍵差別&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>nginx&lt;/th>
 &lt;th>Apache&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>設定模式&lt;/td>
 &lt;td>集中式（&lt;code>/etc/nginx/&lt;/code> 下的設定檔）&lt;/td>
 &lt;td>支援 .htaccess 分散式設定&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>並發模型&lt;/td>
 &lt;td>事件驅動、非阻塞&lt;/td>
 &lt;td>依 MPM：2.4 官方預設 event；裝 mod_php 時退回 prefork（每個連線一個子程序）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>PHP 整合&lt;/td>
 &lt;td>透過 FastCGI（PHP-FPM）&lt;/td>
 &lt;td>mod_php（直接嵌入）或 FastCGI&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>URL rewrite&lt;/td>
 &lt;td>&lt;code>location&lt;/code> + &lt;code>rewrite&lt;/code> 區塊&lt;/td>
 &lt;td>&lt;code>.htaccess&lt;/code> 的 &lt;code>RewriteRule&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>OS 升級或平台遷移時，如果從 Apache 換成 nginx，所有 &lt;code>.htaccess&lt;/code> 規則要手動轉成 nginx 設定：URL rewrite、目錄保護、PHP 設定覆寫、安全標頭。nginx 沒有 &lt;code>.htaccess&lt;/code> 的等價物——所有設定都在集中的設定檔裡，需要 reload nginx 才能生效（Apache 的 &lt;code>.htaccess&lt;/code> 每次請求都重新讀取）。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>nginx 把 Apache 分散在各目錄 &lt;code>.htaccess&lt;/code> 的設定收回同一棵設定樹，每個站台的行為因此都要在這棵樹裡寫明：server block（類似 Apache 的 VirtualHost）怎麼組織、upstream 指向哪個 app server、靜態檔案的 root 路徑、TLS 憑證掛在哪裡、access log 和 error log 的路徑。設定改完跑 &lt;code>nginx -t&lt;/code> 驗證語法後再 &lt;code>nginx -s reload&lt;/code>。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/htaccess/" data-link-title=".htaccess" data-link-desc="Apache Web Server 的目錄層級設定檔，控制 URL rewrite、存取權限、PHP 設定覆寫與安全標頭">.htaccess&lt;/a> — Apache 的分散設定，遷移到 nginx 時需要轉換&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB&lt;/a> — 雲端環境裡承擔部分 nginx 職責&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/apache-mpm/" data-link-title="Apache MPM（Multi-Processing Module）" data-link-desc="在 Apache 設定或 httpd -V 看到 prefork、worker、event，或要判斷 mod_php 為什麼讓 Apache 只能用 prefork、換成 PHP-FPM 之後能改用哪一種時">Apache MPM&lt;/a> — Apache 的並發模型，決定能不能搭配 mod_php&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM&lt;/a> — nginx 用 &lt;code>fastcgi_pass&lt;/code> 轉請求給它的 PHP 程序管理器&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層&lt;/a> — nginx 作為單機 reverse proxy 在請求責任鏈的位置、以及跟雲端 ALB 的選擇條件&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>nginx 是高效能的 Web Server 和 <a href="/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy</a>，以非同步事件驅動架構處理大量並發連線。它在全球 web server 市場佔有率與 Apache 並列前二，新部署的伺服器多數選 nginx。</p>
<h2 id="概念位置">概念位置</h2>
<p>nginx 在 infra 裡常見的角色有三種：作為 reverse proxy 把請求轉給後端應用（Node.js、PHP-FPM、Python WSGI）、作為靜態檔案伺服器、作為 TLS 終結點處理 HTTPS。<a href="/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB</a> 在雲端環境承擔了部分 nginx 的職責（負載平衡、TLS 終結），但 VPS 環境裡 nginx 仍然是標準選擇。</p>
<h2 id="跟-apache-的關鍵差別">跟 Apache 的關鍵差別</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>nginx</th>
          <th>Apache</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>設定模式</td>
          <td>集中式（<code>/etc/nginx/</code> 下的設定檔）</td>
          <td>支援 .htaccess 分散式設定</td>
      </tr>
      <tr>
          <td>並發模型</td>
          <td>事件驅動、非阻塞</td>
          <td>依 MPM：2.4 官方預設 event；裝 mod_php 時退回 prefork（每個連線一個子程序）</td>
      </tr>
      <tr>
          <td>PHP 整合</td>
          <td>透過 FastCGI（PHP-FPM）</td>
          <td>mod_php（直接嵌入）或 FastCGI</td>
      </tr>
      <tr>
          <td>URL rewrite</td>
          <td><code>location</code> + <code>rewrite</code> 區塊</td>
          <td><code>.htaccess</code> 的 <code>RewriteRule</code></td>
      </tr>
  </tbody>
</table>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>OS 升級或平台遷移時，如果從 Apache 換成 nginx，所有 <code>.htaccess</code> 規則要手動轉成 nginx 設定：URL rewrite、目錄保護、PHP 設定覆寫、安全標頭。nginx 沒有 <code>.htaccess</code> 的等價物——所有設定都在集中的設定檔裡，需要 reload nginx 才能生效（Apache 的 <code>.htaccess</code> 每次請求都重新讀取）。</p>
<h2 id="設計責任">設計責任</h2>
<p>nginx 把 Apache 分散在各目錄 <code>.htaccess</code> 的設定收回同一棵設定樹，每個站台的行為因此都要在這棵樹裡寫明：server block（類似 Apache 的 VirtualHost）怎麼組織、upstream 指向哪個 app server、靜態檔案的 root 路徑、TLS 憑證掛在哪裡、access log 和 error log 的路徑。設定改完跑 <code>nginx -t</code> 驗證語法後再 <code>nginx -s reload</code>。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/htaccess/" data-link-title=".htaccess" data-link-desc="Apache Web Server 的目錄層級設定檔，控制 URL rewrite、存取權限、PHP 設定覆寫與安全標頭">.htaccess</a> — Apache 的分散設定，遷移到 nginx 時需要轉換</li>
<li><a href="/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB</a> — 雲端環境裡承擔部分 nginx 職責</li>
<li><a href="/blog/devops/infra/knowledge-cards/apache-mpm/" data-link-title="Apache MPM（Multi-Processing Module）" data-link-desc="在 Apache 設定或 httpd -V 看到 prefork、worker、event，或要判斷 mod_php 為什麼讓 Apache 只能用 prefork、換成 PHP-FPM 之後能改用哪一種時">Apache MPM</a> — Apache 的並發模型，決定能不能搭配 mod_php</li>
<li><a href="/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM</a> — nginx 用 <code>fastcgi_pass</code> 轉請求給它的 PHP 程序管理器</li>
<li><a href="/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層</a> — nginx 作為單機 reverse proxy 在請求責任鏈的位置、以及跟雲端 ALB 的選擇條件</li>
</ul>
]]></content:encoded></item><item><title>MySQL</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/mysql/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/mysql/</guid><description>&lt;p>MySQL 是最廣泛使用的開源關聯式資料庫，多數 PHP 應用、WordPress、以及大量 web 服務的資料層都跑在 MySQL 上，維運上常搭配 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin&lt;/a> 或 CLI 工具管理。MariaDB 是 MySQL 被 Oracle 收購後社群分支出來的相容實作，多數 Linux 發行版已經把預設的 mysql 套件指向 MariaDB。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>MySQL 在 infra 裡是典型的 stateful 資源——資料不可重建、備份和刪除保護是 day-1 需求。接手維運時，MySQL 的版本、備份設定、認證方式是第一批要確認的項目。雲端環境裡 MySQL 常以 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/rds/" data-link-title="RDS" data-link-desc="AWS 的受管關聯式資料庫服務，代管備份、更新與 failover，讓使用者專注在 schema 和查詢">RDS&lt;/a> 形式運行（受管服務、代管備份與 failover）。&lt;/p>
&lt;h2 id="大版本升級的關鍵差異">大版本升級的關鍵差異&lt;/h2>
&lt;p>MySQL 5.7 → 8.0 的 breaking change 在接手和升級情境裡經常遇到：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>變更項&lt;/th>
 &lt;th>5.7 行為&lt;/th>
 &lt;th>8.0 行為&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>預設認證方式&lt;/td>
 &lt;td>&lt;code>mysql_native_password&lt;/code>&lt;/td>
 &lt;td>&lt;code>caching_sha2_password&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>GROUP BY&lt;/code> 隱式排序&lt;/td>
 &lt;td>有（按 group 欄位排）&lt;/td>
 &lt;td>無（需要明確 &lt;code>ORDER BY&lt;/code>）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>預設字元集&lt;/td>
 &lt;td>&lt;code>utf8&lt;/code>（3 byte）&lt;/td>
 &lt;td>&lt;code>utf8mb4&lt;/code>（4 byte、支援 emoji）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>GRANT&lt;/code> 同時建使用者&lt;/td>
 &lt;td>允許&lt;/td>
 &lt;td>必須先 &lt;code>CREATE USER&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>接手維運時的確認清單：&lt;code>SELECT VERSION();&lt;/code> 查版本、&lt;code>SHOW DATABASES;&lt;/code> 看有哪些資料庫、&lt;code>SHOW VARIABLES LIKE 'character_set%';&lt;/code> 確認字元集、&lt;code>SHOW VARIABLES LIKE 'max_connections';&lt;/code> 看連線上限。&lt;/p>
&lt;h2 id="cli-工具">CLI 工具&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>功能&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>mysql&lt;/code>&lt;/td>
 &lt;td>互動式 SQL 查詢&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>mysqldump&lt;/code>&lt;/td>
 &lt;td>匯出資料庫為 SQL 文字檔&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>mysqlcheck&lt;/code>&lt;/td>
 &lt;td>檢查、修復、優化資料表&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>mysqlimport&lt;/code>&lt;/td>
 &lt;td>匯入 CSV / TSV 資料&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;code>mysqldump&lt;/code> 是備份的核心工具——一行指令把整個資料庫匯出成可還原的 SQL。phpMyAdmin 的匯出功能底層也是類似的邏輯，但受 web server timeout 限制，大資料庫更適合用 CLI。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>MySQL 的資料遺失後只能從備份還原，infra 設計先保護資料、再處理連線與效能：備份頻率和保留天數（RDS 預設 7 天自動備份）、是否開 multi-AZ（failover 保護）、連線池設定（RDS Proxy 或應用層 pool）、慢查詢日誌是否開啟。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/rds/" data-link-title="RDS" data-link-desc="AWS 的受管關聯式資料庫服務，代管備份、更新與 failover，讓使用者專注在 schema 和查詢">RDS&lt;/a> — AWS 的受管 MySQL 服務&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin&lt;/a> — Web 介面的 MySQL 管理工具&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>MySQL 是最廣泛使用的開源關聯式資料庫，多數 PHP 應用、WordPress、以及大量 web 服務的資料層都跑在 MySQL 上，維運上常搭配 <a href="/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin</a> 或 CLI 工具管理。MariaDB 是 MySQL 被 Oracle 收購後社群分支出來的相容實作，多數 Linux 發行版已經把預設的 mysql 套件指向 MariaDB。</p>
<h2 id="概念位置">概念位置</h2>
<p>MySQL 在 infra 裡是典型的 stateful 資源——資料不可重建、備份和刪除保護是 day-1 需求。接手維運時，MySQL 的版本、備份設定、認證方式是第一批要確認的項目。雲端環境裡 MySQL 常以 <a href="/blog/devops/infra/knowledge-cards/rds/" data-link-title="RDS" data-link-desc="AWS 的受管關聯式資料庫服務，代管備份、更新與 failover，讓使用者專注在 schema 和查詢">RDS</a> 形式運行（受管服務、代管備份與 failover）。</p>
<h2 id="大版本升級的關鍵差異">大版本升級的關鍵差異</h2>
<p>MySQL 5.7 → 8.0 的 breaking change 在接手和升級情境裡經常遇到：</p>
<table>
  <thead>
      <tr>
          <th>變更項</th>
          <th>5.7 行為</th>
          <th>8.0 行為</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>預設認證方式</td>
          <td><code>mysql_native_password</code></td>
          <td><code>caching_sha2_password</code></td>
      </tr>
      <tr>
          <td><code>GROUP BY</code> 隱式排序</td>
          <td>有（按 group 欄位排）</td>
          <td>無（需要明確 <code>ORDER BY</code>）</td>
      </tr>
      <tr>
          <td>預設字元集</td>
          <td><code>utf8</code>（3 byte）</td>
          <td><code>utf8mb4</code>（4 byte、支援 emoji）</td>
      </tr>
      <tr>
          <td><code>GRANT</code> 同時建使用者</td>
          <td>允許</td>
          <td>必須先 <code>CREATE USER</code></td>
      </tr>
  </tbody>
</table>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>接手維運時的確認清單：<code>SELECT VERSION();</code> 查版本、<code>SHOW DATABASES;</code> 看有哪些資料庫、<code>SHOW VARIABLES LIKE 'character_set%';</code> 確認字元集、<code>SHOW VARIABLES LIKE 'max_connections';</code> 看連線上限。</p>
<h2 id="cli-工具">CLI 工具</h2>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>功能</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>mysql</code></td>
          <td>互動式 SQL 查詢</td>
      </tr>
      <tr>
          <td><code>mysqldump</code></td>
          <td>匯出資料庫為 SQL 文字檔</td>
      </tr>
      <tr>
          <td><code>mysqlcheck</code></td>
          <td>檢查、修復、優化資料表</td>
      </tr>
      <tr>
          <td><code>mysqlimport</code></td>
          <td>匯入 CSV / TSV 資料</td>
      </tr>
  </tbody>
</table>
<p><code>mysqldump</code> 是備份的核心工具——一行指令把整個資料庫匯出成可還原的 SQL。phpMyAdmin 的匯出功能底層也是類似的邏輯，但受 web server timeout 限制，大資料庫更適合用 CLI。</p>
<h2 id="設計責任">設計責任</h2>
<p>MySQL 的資料遺失後只能從備份還原，infra 設計先保護資料、再處理連線與效能：備份頻率和保留天數（RDS 預設 7 天自動備份）、是否開 multi-AZ（failover 保護）、連線池設定（RDS Proxy 或應用層 pool）、慢查詢日誌是否開啟。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/rds/" data-link-title="RDS" data-link-desc="AWS 的受管關聯式資料庫服務，代管備份、更新與 failover，讓使用者專注在 schema 和查詢">RDS</a> — AWS 的受管 MySQL 服務</li>
<li><a href="/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin</a> — Web 介面的 MySQL 管理工具</li>
</ul>
]]></content:encoded></item><item><title>RDS</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/rds/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/rds/</guid><description>&lt;p>RDS（Relational Database Service）是 AWS 提供的受管關聯式資料庫服務。它在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2&lt;/a> instance 上跑資料庫引擎（MySQL、PostgreSQL、MariaDB、Oracle、SQL Server），但把作業系統更新、自動備份、跨可用區 failover、磁碟擴容這些運維工作交給 AWS 代管。使用者操作的是資料庫層級的設定（schema、query、parameter group），不需要 SSH 進機器管 OS。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>RDS 是 infra 系列中 stateful 資源的代表。它持有不可重建的資料，所以它的 IaC 描述、備份策略、&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">刪除保護&lt;/a>、變更審查都比 stateless 資源（如 EC2 web server）嚴格。模組五（核心服務）和接手維運模組的資料庫相關段落都以 RDS 為主要範例。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要理解 RDS 的情境包括：接手一個已經在跑的 production 資料庫、評估要不要從自建 MySQL 遷移到 RDS、設定資料庫的備份和高可用、或在 IaC 裡描述資料庫資源。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>RDS 持有無法重建的資料，下表的設定設錯時代價依項目不同：backup retention 與 deletion protection 設錯，損失的是救不回來的資料；instance class、engine version 與部分 parameter group 參數事後調整，代價是重開機或一段停機窗口。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>設定&lt;/th>
 &lt;th>決定什麼&lt;/th>
 &lt;th>影響&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>instance class&lt;/td>
 &lt;td>CPU / 記憶體規格&lt;/td>
 &lt;td>效能與成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>multi-AZ&lt;/td>
 &lt;td>是否跨可用區部署 standby&lt;/td>
 &lt;td>可用性（failover 分鐘級）vs 成本（約 2 倍）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>backup retention&lt;/td>
 &lt;td>自動備份保留天數（1-35）&lt;/td>
 &lt;td>可回溯的時間窗口&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>deletion protection&lt;/td>
 &lt;td>是否允許刪除&lt;/td>
 &lt;td>防誤刪（production 必開）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>parameter group&lt;/td>
 &lt;td>資料庫引擎參數（max_connections 等）&lt;/td>
 &lt;td>效能調校&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>engine version&lt;/td>
 &lt;td>資料庫版本&lt;/td>
 &lt;td>功能與相容性&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>跟自建 MySQL on EC2 的取捨：RDS 省去 OS 層運維，但 parameter group 和 option group 的可調整範圍比直接操作 my.cnf 窄。需要完全控制 OS 層（如自訂 plugin、特殊檔案系統）時，自建較合理。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/mysql/" data-link-title="MySQL" data-link-desc="最廣泛使用的開源關聯式資料庫。MariaDB 是其社群分支。大版本升級（5.7→8.0）有認證方式和查詢行為的 breaking change">MySQL&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>RDS（Relational Database Service）是 AWS 提供的受管關聯式資料庫服務。它在 <a href="/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2</a> instance 上跑資料庫引擎（MySQL、PostgreSQL、MariaDB、Oracle、SQL Server），但把作業系統更新、自動備份、跨可用區 failover、磁碟擴容這些運維工作交給 AWS 代管。使用者操作的是資料庫層級的設定（schema、query、parameter group），不需要 SSH 進機器管 OS。</p>
<h2 id="概念位置">概念位置</h2>
<p>RDS 是 infra 系列中 stateful 資源的代表。它持有不可重建的資料，所以它的 IaC 描述、備份策略、<a href="/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">刪除保護</a>、變更審查都比 stateless 資源（如 EC2 web server）嚴格。模組五（核心服務）和接手維運模組的資料庫相關段落都以 RDS 為主要範例。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要理解 RDS 的情境包括：接手一個已經在跑的 production 資料庫、評估要不要從自建 MySQL 遷移到 RDS、設定資料庫的備份和高可用、或在 IaC 裡描述資料庫資源。</p>
<h2 id="設計責任">設計責任</h2>
<p>RDS 持有無法重建的資料，下表的設定設錯時代價依項目不同：backup retention 與 deletion protection 設錯，損失的是救不回來的資料；instance class、engine version 與部分 parameter group 參數事後調整，代價是重開機或一段停機窗口。</p>
<table>
  <thead>
      <tr>
          <th>設定</th>
          <th>決定什麼</th>
          <th>影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>instance class</td>
          <td>CPU / 記憶體規格</td>
          <td>效能與成本</td>
      </tr>
      <tr>
          <td>multi-AZ</td>
          <td>是否跨可用區部署 standby</td>
          <td>可用性（failover 分鐘級）vs 成本（約 2 倍）</td>
      </tr>
      <tr>
          <td>backup retention</td>
          <td>自動備份保留天數（1-35）</td>
          <td>可回溯的時間窗口</td>
      </tr>
      <tr>
          <td>deletion protection</td>
          <td>是否允許刪除</td>
          <td>防誤刪（production 必開）</td>
      </tr>
      <tr>
          <td>parameter group</td>
          <td>資料庫引擎參數（max_connections 等）</td>
          <td>效能調校</td>
      </tr>
      <tr>
          <td>engine version</td>
          <td>資料庫版本</td>
          <td>功能與相容性</td>
      </tr>
  </tbody>
</table>
<p>跟自建 MySQL on EC2 的取捨：RDS 省去 OS 層運維，但 parameter group 和 option group 的可調整範圍比直接操作 my.cnf 窄。需要完全控制 OS 層（如自訂 plugin、特殊檔案系統）時，自建較合理。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/mysql/" data-link-title="MySQL" data-link-desc="最廣泛使用的開源關聯式資料庫。MariaDB 是其社群分支。大版本升級（5.7→8.0）有認證方式和查詢行為的 breaking change">MySQL</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a></li>
</ul>
]]></content:encoded></item><item><title>S3</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/s3/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/s3/</guid><description>&lt;p>S3（Simple Storage Service）是 AWS 的物件儲存服務。每個檔案（object）放在一個 bucket 裡、用 key（路徑）定址。S3 的持久性設計為 99.999999999%（11 個 9），資料自動跨多個可用區複製，不需要手動備份 S3 本身——要保護的是「物件被覆寫或刪除」的風險，而非「S3 服務掛掉」的風險，這正是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection&lt;/a> 機制要解的問題。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>S3 在 infra 系列裡有三個角色：Terraform state 的存放處（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/remote-state-backend/" data-link-title="Remote State Backend" data-link-desc="把 Terraform state 從本地搬到團隊共享儲存的機制，同時滿足持久保存、並行鎖與敏感值保護">remote state backend&lt;/a>）、應用程式的靜態檔案儲存（上傳內容、備份歸檔）、以及 log 的長期保存目的地。模組一（state backend）、模組五（storage）、模組八（治理）都涉及 S3。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要理解 S3 的情境包括：設定 Terraform 的 remote state backend、管理使用者上傳的檔案、設計備份歸檔策略、或評估儲存成本。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>S3 的持久性設計處理的是硬體與可用區故障；物件被覆寫或刪除、資料被公開存取、儲存成本隨時間累積這三類風險要靠 bucket 設定處理，下表的設定大多對應其中一類：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>設定&lt;/th>
 &lt;th>決定什麼&lt;/th>
 &lt;th>影響&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>versioning&lt;/td>
 &lt;td>是否保留物件的歷史版本&lt;/td>
 &lt;td>覆寫或刪除後能回溯（state bucket 必開）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>encryption&lt;/td>
 &lt;td>靜態加密方式（SSE-S3 / SSE-KMS）&lt;/td>
 &lt;td>合規與金鑰管理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>public access block&lt;/td>
 &lt;td>是否封鎖公開存取&lt;/td>
 &lt;td>安全（預設全封鎖）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>lifecycle rules&lt;/td>
 &lt;td>自動移到低成本儲存類或過期刪除&lt;/td>
 &lt;td>成本控制（如 30 天後移到 Glacier）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>bucket policy&lt;/td>
 &lt;td>跨帳號或跨服務的存取規則&lt;/td>
 &lt;td>權限邊界&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>versioning 開啟後，刪除物件只是加一個 delete marker、實際資料還在。要真正刪除需要刪除 delete marker 和所有歷史版本。這是保護 state 檔的關鍵機制——誤寫 state 後可以回捲到上一個版本。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/remote-state-backend/" data-link-title="Remote State Backend" data-link-desc="把 Terraform state 從本地搬到團隊共享儲存的機制，同時滿足持久保存、並行鎖與敏感值保護">Remote State Backend&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>S3（Simple Storage Service）是 AWS 的物件儲存服務。每個檔案（object）放在一個 bucket 裡、用 key（路徑）定址。S3 的持久性設計為 99.999999999%（11 個 9），資料自動跨多個可用區複製，不需要手動備份 S3 本身——要保護的是「物件被覆寫或刪除」的風險，而非「S3 服務掛掉」的風險，這正是 <a href="/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection</a> 機制要解的問題。</p>
<h2 id="概念位置">概念位置</h2>
<p>S3 在 infra 系列裡有三個角色：Terraform state 的存放處（<a href="/blog/devops/infra/knowledge-cards/remote-state-backend/" data-link-title="Remote State Backend" data-link-desc="把 Terraform state 從本地搬到團隊共享儲存的機制，同時滿足持久保存、並行鎖與敏感值保護">remote state backend</a>）、應用程式的靜態檔案儲存（上傳內容、備份歸檔）、以及 log 的長期保存目的地。模組一（state backend）、模組五（storage）、模組八（治理）都涉及 S3。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要理解 S3 的情境包括：設定 Terraform 的 remote state backend、管理使用者上傳的檔案、設計備份歸檔策略、或評估儲存成本。</p>
<h2 id="設計責任">設計責任</h2>
<p>S3 的持久性設計處理的是硬體與可用區故障；物件被覆寫或刪除、資料被公開存取、儲存成本隨時間累積這三類風險要靠 bucket 設定處理，下表的設定大多對應其中一類：</p>
<table>
  <thead>
      <tr>
          <th>設定</th>
          <th>決定什麼</th>
          <th>影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>versioning</td>
          <td>是否保留物件的歷史版本</td>
          <td>覆寫或刪除後能回溯（state bucket 必開）</td>
      </tr>
      <tr>
          <td>encryption</td>
          <td>靜態加密方式（SSE-S3 / SSE-KMS）</td>
          <td>合規與金鑰管理</td>
      </tr>
      <tr>
          <td>public access block</td>
          <td>是否封鎖公開存取</td>
          <td>安全（預設全封鎖）</td>
      </tr>
      <tr>
          <td>lifecycle rules</td>
          <td>自動移到低成本儲存類或過期刪除</td>
          <td>成本控制（如 30 天後移到 Glacier）</td>
      </tr>
      <tr>
          <td>bucket policy</td>
          <td>跨帳號或跨服務的存取規則</td>
          <td>權限邊界</td>
      </tr>
  </tbody>
</table>
<p>versioning 開啟後，刪除物件只是加一個 delete marker、實際資料還在。要真正刪除需要刪除 delete marker 和所有歷史版本。這是保護 state 檔的關鍵機制——誤寫 state 後可以回捲到上一個版本。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/remote-state-backend/" data-link-title="Remote State Backend" data-link-desc="把 Terraform state 從本地搬到團隊共享儲存的機制，同時滿足持久保存、並行鎖與敏感值保護">Remote State Backend</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection</a></li>
</ul>
]]></content:encoded></item><item><title>EC2</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ec2/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ec2/</guid><description>&lt;p>EC2（Elastic Compute Cloud）是 AWS 提供的虛擬機器服務。每一台 EC2 instance 是一台完整的虛擬伺服器——有自己的 OS、CPU、記憶體、磁碟和網路介面。使用者可以 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 進去、安裝軟體、跑應用程式，跟操作一台實體伺服器的體驗相似。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>EC2 是 infra 系列中「運算」面向的基礎單位。容器服務（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS&lt;/a>、EKS）底層也跑在 EC2 上（除非用 Fargate）。模組五（核心服務）的運算段落、接手維運（雲端篇）的 VM 快照、升級模組的 OS 遷移都以 EC2 為操作對象。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要理解 EC2 的情境包括：接手一個跑在 VM 上的應用程式、評估容器化 vs VM 部署、設定 auto-scaling、或建立 AMI 快照作為備份。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>一台 EC2 instance 由五個組件構成：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>組件&lt;/th>
 &lt;th>角色&lt;/th>
 &lt;th>選型標準&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>AMI&lt;/td>
 &lt;td>作業系統映像（Ubuntu、Amazon Linux 等）&lt;/td>
 &lt;td>OS 偏好、軟體預裝需求&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Instance type&lt;/td>
 &lt;td>CPU / 記憶體規格（t3.micro、m6i.large 等）&lt;/td>
 &lt;td>工作負載的 CPU 和記憶體需求&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>EBS&lt;/td>
 &lt;td>持久化磁碟&lt;/td>
 &lt;td>容量、IOPS、是否需要加密&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Security group&lt;/td>
 &lt;td>網路防火牆規則&lt;/td>
 &lt;td>哪些 port 開放、來源限制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>IAM role&lt;/td>
 &lt;td>instance 的雲端權限&lt;/td>
 &lt;td>需要存取哪些 AWS 服務&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>跟容器（ECS / EKS）的差別：EC2 管整台 VM（含 OS 更新、安全性修補、磁碟管理），容器只管應用程式及其依賴。EC2 的運維負擔較高、但控制粒度也較高。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ami/" data-link-title="AMI" data-link-desc="EC2 instance 的作業系統映像快照，包含 OS、軟體、設定與磁碟內容，從 AMI 開出的 instance 跟原始狀態一樣">AMI&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/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/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/instance-metadata-service/" data-link-title="Instance Metadata Service（實例中繼資料服務）" data-link-desc="看到建立主機頁上的 IMDSv2、authorization header、metadata 選項，主機上的程式要取得自己的 ID、區域或身分憑證，或評估網頁應用程式的 SSRF 漏洞會不會讓攻擊者讀到主機的憑證時，查中繼資料服務提供什麼、第二版的存取驗證防的是什麼">Instance Metadata Service（實例中繼資料服務）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>EC2（Elastic Compute Cloud）是 AWS 提供的虛擬機器服務。每一台 EC2 instance 是一台完整的虛擬伺服器——有自己的 OS、CPU、記憶體、磁碟和網路介面。使用者可以 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 進去、安裝軟體、跑應用程式，跟操作一台實體伺服器的體驗相似。</p>
<h2 id="概念位置">概念位置</h2>
<p>EC2 是 infra 系列中「運算」面向的基礎單位。容器服務（<a href="/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS</a>、EKS）底層也跑在 EC2 上（除非用 Fargate）。模組五（核心服務）的運算段落、接手維運（雲端篇）的 VM 快照、升級模組的 OS 遷移都以 EC2 為操作對象。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要理解 EC2 的情境包括：接手一個跑在 VM 上的應用程式、評估容器化 vs VM 部署、設定 auto-scaling、或建立 AMI 快照作為備份。</p>
<h2 id="設計責任">設計責任</h2>
<p>一台 EC2 instance 由五個組件構成：</p>
<table>
  <thead>
      <tr>
          <th>組件</th>
          <th>角色</th>
          <th>選型標準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>AMI</td>
          <td>作業系統映像（Ubuntu、Amazon Linux 等）</td>
          <td>OS 偏好、軟體預裝需求</td>
      </tr>
      <tr>
          <td>Instance type</td>
          <td>CPU / 記憶體規格（t3.micro、m6i.large 等）</td>
          <td>工作負載的 CPU 和記憶體需求</td>
      </tr>
      <tr>
          <td>EBS</td>
          <td>持久化磁碟</td>
          <td>容量、IOPS、是否需要加密</td>
      </tr>
      <tr>
          <td>Security group</td>
          <td>網路防火牆規則</td>
          <td>哪些 port 開放、來源限制</td>
      </tr>
      <tr>
          <td>IAM role</td>
          <td>instance 的雲端權限</td>
          <td>需要存取哪些 AWS 服務</td>
      </tr>
  </tbody>
</table>
<p>跟容器（ECS / EKS）的差別：EC2 管整台 VM（含 OS 更新、安全性修補、磁碟管理），容器只管應用程式及其依賴。EC2 的運維負擔較高、但控制粒度也較高。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ami/" data-link-title="AMI" data-link-desc="EC2 instance 的作業系統映像快照，包含 OS、軟體、設定與磁碟內容，從 AMI 開出的 instance 跟原始狀態一樣">AMI</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/instance-metadata-service/" data-link-title="Instance Metadata Service（實例中繼資料服務）" data-link-desc="看到建立主機頁上的 IMDSv2、authorization header、metadata 選項，主機上的程式要取得自己的 ID、區域或身分憑證，或評估網頁應用程式的 SSRF 漏洞會不會讓攻擊者讀到主機的憑證時，查中繼資料服務提供什麼、第二版的存取驗證防的是什麼">Instance Metadata Service（實例中繼資料服務）</a></li>
</ul>
]]></content:encoded></item><item><title>EBS</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ebs/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ebs/</guid><description>&lt;p>EBS（Elastic Block Store）是 AWS 提供的區塊儲存服務——可以把它理解為掛在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2&lt;/a> instance 上的虛擬硬碟。EBS volume 跟 EC2 instance 的生命週期獨立：instance 停止時 EBS volume 上的資料保留；instance 終止時，建立 instance 時一起掛上的根磁碟預設跟著刪除（&lt;code>DeleteOnTermination&lt;/code> 預設為 true），之後另外掛上的磁碟預設保留，細節見下方〈設計責任〉。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>EBS 是 infra 系列中儲存面向的底層組件。RDS 的資料實際存在 EBS 上（由 AWS 代管）、EC2 的根磁碟和附加磁碟都是 EBS volume。接手維運時對 VM 做快照（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ami/" data-link-title="AMI" data-link-desc="EC2 instance 的作業系統映像快照，包含 OS、軟體、設定與磁碟內容，從 AMI 開出的 instance 跟原始狀態一樣">AMI&lt;/a>），背後就是在對 EBS volume 做 snapshot。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要理解 EBS 的情境包括：EC2 instance 的磁碟快滿了需要擴容、要對 VM 做快照備份、評估磁碟效能（IOPS）是否足夠、或清理不再掛載的孤立 volume（殭屍 volume 持續計費）。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>設定&lt;/th>
 &lt;th>決定什麼&lt;/th>
 &lt;th>影響&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Volume type&lt;/td>
 &lt;td>gp3（通用）/ io2（高 IOPS）/ st1（高吞吐）&lt;/td>
 &lt;td>效能與成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Size&lt;/td>
 &lt;td>磁碟容量（GB）&lt;/td>
 &lt;td>線上擴容可行、但縮小不行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Encryption&lt;/td>
 &lt;td>是否加密&lt;/td>
 &lt;td>合規（建立後不可更改，要加密只能建新的複製過去）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Snapshot&lt;/td>
 &lt;td>快照備份&lt;/td>
 &lt;td>EBS snapshot 是增量的（只存變更的區塊）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DeleteOnTermination&lt;/td>
 &lt;td>instance 終止時是否跟著刪除&lt;/td>
 &lt;td>根磁碟預設 true、附加磁碟預設 false&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>跟 instance store 的差別：instance store 是 EC2 實體主機上的臨時磁碟，效能高但 instance 停止資料就消失。EBS 是持久化儲存，instance 停止資料仍在。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>EBS（Elastic Block Store）是 AWS 提供的區塊儲存服務——可以把它理解為掛在 <a href="/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2</a> instance 上的虛擬硬碟。EBS volume 跟 EC2 instance 的生命週期獨立：instance 停止時 EBS volume 上的資料保留；instance 終止時，建立 instance 時一起掛上的根磁碟預設跟著刪除（<code>DeleteOnTermination</code> 預設為 true），之後另外掛上的磁碟預設保留，細節見下方〈設計責任〉。</p>
<h2 id="概念位置">概念位置</h2>
<p>EBS 是 infra 系列中儲存面向的底層組件。RDS 的資料實際存在 EBS 上（由 AWS 代管）、EC2 的根磁碟和附加磁碟都是 EBS volume。接手維運時對 VM 做快照（<a href="/blog/devops/infra/knowledge-cards/ami/" data-link-title="AMI" data-link-desc="EC2 instance 的作業系統映像快照，包含 OS、軟體、設定與磁碟內容，從 AMI 開出的 instance 跟原始狀態一樣">AMI</a>），背後就是在對 EBS volume 做 snapshot。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要理解 EBS 的情境包括：EC2 instance 的磁碟快滿了需要擴容、要對 VM 做快照備份、評估磁碟效能（IOPS）是否足夠、或清理不再掛載的孤立 volume（殭屍 volume 持續計費）。</p>
<h2 id="設計責任">設計責任</h2>
<table>
  <thead>
      <tr>
          <th>設定</th>
          <th>決定什麼</th>
          <th>影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Volume type</td>
          <td>gp3（通用）/ io2（高 IOPS）/ st1（高吞吐）</td>
          <td>效能與成本</td>
      </tr>
      <tr>
          <td>Size</td>
          <td>磁碟容量（GB）</td>
          <td>線上擴容可行、但縮小不行</td>
      </tr>
      <tr>
          <td>Encryption</td>
          <td>是否加密</td>
          <td>合規（建立後不可更改，要加密只能建新的複製過去）</td>
      </tr>
      <tr>
          <td>Snapshot</td>
          <td>快照備份</td>
          <td>EBS snapshot 是增量的（只存變更的區塊）</td>
      </tr>
      <tr>
          <td>DeleteOnTermination</td>
          <td>instance 終止時是否跟著刪除</td>
          <td>根磁碟預設 true、附加磁碟預設 false</td>
      </tr>
  </tbody>
</table>
<p>跟 instance store 的差別：instance store 是 EC2 實體主機上的臨時磁碟，效能高但 instance 停止資料就消失。EBS 是持久化儲存，instance 停止資料仍在。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection</a></li>
</ul>
]]></content:encoded></item><item><title>HCL</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/hcl/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/hcl/</guid><description>&lt;p>HCL（HashiCorp Configuration Language）是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/terraform-plan-apply/" data-link-title="terraform plan / apply" data-link-desc="IaC 的兩個核心操作：plan 只看不動（產出差異報告）、apply 真的動（執行差異）">Terraform&lt;/a> 和 OpenTofu 使用的設定語言。它用宣告式的 resource block 描述「環境應該長什麼樣」，由工具負責比對現況與描述、算出差異再套用。寫 HCL 的人描述目標狀態，不描述達到目標的步驟。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>HCL 是 infra 系列中 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> 程式碼的語言層。IaC 卡講的是「用程式碼管理基礎設施」的概念，HCL 是這個概念落地時最常用的語言。模組一到八的所有 HCL 範例都用這個語言寫成。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要理解 HCL 的情境包括：第一次打開一份 &lt;code>.tf&lt;/code> 檔案、要讀懂 Terraform 的 plan 輸出、要修改或新增一個 resource 定義、或要 review 別人的 infra PR。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>HCL 的基本結構：&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_s3_bucket&amp;#34; &amp;#34;example&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"> bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;my-bucket&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"> tags&lt;/span> &lt;span class="o">=&lt;/span>&lt;span class="n"> { env&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;prod&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>resource&lt;/code>：宣告一個雲端資源&lt;/li>
&lt;li>&lt;code>&amp;quot;aws_s3_bucket&amp;quot;&lt;/code>：資源類型（由 provider 決定）&lt;/li>
&lt;li>&lt;code>&amp;quot;example&amp;quot;&lt;/code>：這個資源在程式碼裡的名稱（用來引用）&lt;/li>
&lt;li>&lt;code>{}&lt;/code>：這個資源的屬性&lt;/li>
&lt;/ul>
&lt;p>跟其他格式的差別：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>格式&lt;/th>
 &lt;th>特性&lt;/th>
 &lt;th>適合場景&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>JSON / YAML&lt;/td>
 &lt;td>純資料格式、沒有邏輯&lt;/td>
 &lt;td>設定值、資料交換&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>HCL&lt;/td>
 &lt;td>支援變數、函式、條件、迴圈&lt;/td>
 &lt;td>基礎設施描述&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>TypeScript / Python&lt;/td>
 &lt;td>通用程式語言、完整邏輯&lt;/td>
 &lt;td>複雜的 infra 抽象（CDK / Pulumi）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>HCL 的定位在 JSON 和通用語言之間——比 JSON 有表達力（能做迴圈和條件）、比通用語言好 review（diff 直觀、不需要在腦中「執行」程式碼才知道結果）。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/toml/" data-link-title="TOML" data-link-desc="讀到 .toml 設定檔、要判斷一個值會被解析成什麼型別、要在 JSON、YAML、TOML 之間選設定檔格式，或解析器版本對不上而報錯時">TOML&lt;/a>：只描述資料、沒有變數與函式的設定檔格式&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>HCL（HashiCorp Configuration Language）是 <a href="/blog/devops/infra/knowledge-cards/terraform-plan-apply/" data-link-title="terraform plan / apply" data-link-desc="IaC 的兩個核心操作：plan 只看不動（產出差異報告）、apply 真的動（執行差異）">Terraform</a> 和 OpenTofu 使用的設定語言。它用宣告式的 resource block 描述「環境應該長什麼樣」，由工具負責比對現況與描述、算出差異再套用。寫 HCL 的人描述目標狀態，不描述達到目標的步驟。</p>
<h2 id="概念位置">概念位置</h2>
<p>HCL 是 infra 系列中 <a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> 程式碼的語言層。IaC 卡講的是「用程式碼管理基礎設施」的概念，HCL 是這個概念落地時最常用的語言。模組一到八的所有 HCL 範例都用這個語言寫成。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要理解 HCL 的情境包括：第一次打開一份 <code>.tf</code> 檔案、要讀懂 Terraform 的 plan 輸出、要修改或新增一個 resource 定義、或要 review 別人的 infra PR。</p>
<h2 id="設計責任">設計責任</h2>
<p>HCL 的基本結構：</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_s3_bucket&#34; &#34;example&#34;</span> {
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">  bucket</span> <span class="o">=</span> <span class="s2">&#34;my-bucket&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">  tags</span>   <span class="o">=</span><span class="n"> { env</span> <span class="o">=</span> <span class="s2">&#34;prod&#34;</span> }
</span></span><span class="line"><span class="ln">4</span><span class="cl">}</span></span></code></pre></div><ul>
<li><code>resource</code>：宣告一個雲端資源</li>
<li><code>&quot;aws_s3_bucket&quot;</code>：資源類型（由 provider 決定）</li>
<li><code>&quot;example&quot;</code>：這個資源在程式碼裡的名稱（用來引用）</li>
<li><code>{}</code>：這個資源的屬性</li>
</ul>
<p>跟其他格式的差別：</p>
<table>
  <thead>
      <tr>
          <th>格式</th>
          <th>特性</th>
          <th>適合場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>JSON / YAML</td>
          <td>純資料格式、沒有邏輯</td>
          <td>設定值、資料交換</td>
      </tr>
      <tr>
          <td>HCL</td>
          <td>支援變數、函式、條件、迴圈</td>
          <td>基礎設施描述</td>
      </tr>
      <tr>
          <td>TypeScript / Python</td>
          <td>通用程式語言、完整邏輯</td>
          <td>複雜的 infra 抽象（CDK / Pulumi）</td>
      </tr>
  </tbody>
</table>
<p>HCL 的定位在 JSON 和通用語言之間——比 JSON 有表達力（能做迴圈和條件）、比通用語言好 review（diff 直觀、不需要在腦中「執行」程式碼才知道結果）。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/toml/" data-link-title="TOML" data-link-desc="讀到 .toml 設定檔、要判斷一個值會被解析成什麼型別、要在 JSON、YAML、TOML 之間選設定檔格式，或解析器版本對不上而報錯時">TOML</a>：只描述資料、沒有變數與函式的設定檔格式</li>
</ul>
]]></content:encoded></item><item><title>terraform plan / apply</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/terraform-plan-apply/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/terraform-plan-apply/</guid><description>&lt;p>&lt;code>terraform plan&lt;/code> 和 &lt;code>terraform apply&lt;/code> 是 Terraform 操作基礎設施的兩個核心指令。plan 比對三方（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">state&lt;/a> 檔、雲端現況、HCL 描述）產出差異報告，告訴使用者「如果 apply 會發生什麼」，但不做任何改動。apply 執行 plan 算出的差異，在雲端建立、修改或刪除資源。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>plan/apply 的分離是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> 可審查性的基礎。模組七（PR 流程）的核心機制就是「PR 觸發 plan → plan 結果貼回 PR → reviewer 看 plan 再決定要不要 apply」。這個「先看再動」的流程跟手動操作（直接在 Console 改）的根本差別。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要理解 plan/apply 的情境包括：第一次跑 Terraform、review 別人的 infra PR（看 plan 輸出）、排查 drift（plan 在沒有 code 變更的情況下顯示差異）、或決定一次 apply 是否安全。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>plan 輸出的三種動作標記：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>標記&lt;/th>
 &lt;th>意義&lt;/th>
 &lt;th>風險&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>+&lt;/code>&lt;/td>
 &lt;td>新增資源&lt;/td>
 &lt;td>低（新建不影響現有）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>~&lt;/code>&lt;/td>
 &lt;td>修改資源（in-place update）&lt;/td>
 &lt;td>中（看改什麼，改 tag 低風險、改 instance type 可能重啟）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>-/+&lt;/code>&lt;/td>
 &lt;td>先刪後建（forces replacement）&lt;/td>
 &lt;td>高（stateful 資源如 RDS 代表資料遺失）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>-&lt;/code>&lt;/td>
 &lt;td>刪除資源&lt;/td>
 &lt;td>高（不可逆）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>review plan 時最需要警惕的是 &lt;code>-/+&lt;/code>（forces replacement）——看起來只是改一個屬性，但某些屬性的修改會觸發資源重建（例如 RDS 的 &lt;code>identifier&lt;/code> 改名）。&lt;code>local_file&lt;/code> 只改 &lt;code>file_permission&lt;/code> 也會印 &lt;code># forces replacement&lt;/code>，實跑的輸出在 &lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/hands-on/multi-host-manifest-walkthrough/" data-link-title="多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理" data-link-desc="要照著部署多台主機的維護清單並執行檢查腳本，或要讀跨主機執行時 ansible 的 exit code 與每台的 rc、門檻造成的 WARN、清單格式錯與連不上的主機各自印出什麼時，或要確認 Terraform 寫出的 inventory 的檔案權限、`--check` 預覽與實際部署結果不同的情形時，查在可拋棄的容器上實際跑過的指令、輸出與遇到的錯誤">多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理&lt;/a>〈由 Terraform 產生 inventory：init、plan、apply 與 local_file 的檔案權限〉。&lt;/p>
&lt;p>plan 與 apply 之間可能有時間差。如果 plan 之後、apply 之前有人手動改了雲端資源，apply 時的實際行為可能跟 plan 預期的不同。多數團隊在 apply 階段會重跑一次 plan 並要求結果一致。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p><code>terraform plan</code> 和 <code>terraform apply</code> 是 Terraform 操作基礎設施的兩個核心指令。plan 比對三方（<a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">state</a> 檔、雲端現況、HCL 描述）產出差異報告，告訴使用者「如果 apply 會發生什麼」，但不做任何改動。apply 執行 plan 算出的差異，在雲端建立、修改或刪除資源。</p>
<h2 id="概念位置">概念位置</h2>
<p>plan/apply 的分離是 <a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> 可審查性的基礎。模組七（PR 流程）的核心機制就是「PR 觸發 plan → plan 結果貼回 PR → reviewer 看 plan 再決定要不要 apply」。這個「先看再動」的流程跟手動操作（直接在 Console 改）的根本差別。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要理解 plan/apply 的情境包括：第一次跑 Terraform、review 別人的 infra PR（看 plan 輸出）、排查 drift（plan 在沒有 code 變更的情況下顯示差異）、或決定一次 apply 是否安全。</p>
<h2 id="設計責任">設計責任</h2>
<p>plan 輸出的三種動作標記：</p>
<table>
  <thead>
      <tr>
          <th>標記</th>
          <th>意義</th>
          <th>風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>+</code></td>
          <td>新增資源</td>
          <td>低（新建不影響現有）</td>
      </tr>
      <tr>
          <td><code>~</code></td>
          <td>修改資源（in-place update）</td>
          <td>中（看改什麼，改 tag 低風險、改 instance type 可能重啟）</td>
      </tr>
      <tr>
          <td><code>-/+</code></td>
          <td>先刪後建（forces replacement）</td>
          <td>高（stateful 資源如 RDS 代表資料遺失）</td>
      </tr>
      <tr>
          <td><code>-</code></td>
          <td>刪除資源</td>
          <td>高（不可逆）</td>
      </tr>
  </tbody>
</table>
<p>review plan 時最需要警惕的是 <code>-/+</code>（forces replacement）——看起來只是改一個屬性，但某些屬性的修改會觸發資源重建（例如 RDS 的 <code>identifier</code> 改名）。<code>local_file</code> 只改 <code>file_permission</code> 也會印 <code># forces replacement</code>，實跑的輸出在 <a href="/blog/devops/operations/09-host-lifecycle/hands-on/multi-host-manifest-walkthrough/" data-link-title="多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理" data-link-desc="要照著部署多台主機的維護清單並執行檢查腳本，或要讀跨主機執行時 ansible 的 exit code 與每台的 rc、門檻造成的 WARN、清單格式錯與連不上的主機各自印出什麼時，或要確認 Terraform 寫出的 inventory 的檔案權限、`--check` 預覽與實際部署結果不同的情形時，查在可拋棄的容器上實際跑過的指令、輸出與遇到的錯誤">多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理</a>〈由 Terraform 產生 inventory：init、plan、apply 與 local_file 的檔案權限〉。</p>
<p>plan 與 apply 之間可能有時間差。如果 plan 之後、apply 之前有人手動改了雲端資源，apply 時的實際行為可能跟 plan 預期的不同。多數團隊在 apply 階段會重跑一次 plan 並要求結果一致。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a></li>
</ul>
]]></content:encoded></item><item><title>AMI</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ami/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ami/</guid><description>&lt;p>AMI（Amazon Machine Image）是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2&lt;/a> instance 的完整映像快照。它包含作業系統、已安裝的軟體、設定檔、磁碟內容——從一個 AMI 啟動新的 instance，得到的是跟拍照時完全一樣的環境。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>AMI 在 infra 系列裡有兩個角色。第一個是接手維運時的保險——對 VM（即 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2&lt;/a> instance）建一個 AMI 等於把整台機器拍下來，做任何改動前都有一個可回退的基線。第二個是環境標準化——把裝好軟體的 instance 做成 AMI（golden image），之後開新機器都從這個 AMI 啟動，確保每台機器的基線一致。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要理解 AMI 的情境包括：接手一台不確定裡面裝了什麼的 EC2（先拍 AMI 再動）、要在另一個 region 或帳號複製一台同樣的機器、OS 升級時要保留舊環境作為 rollback、或設計 auto-scaling 的 launch template（需要指定 AMI）。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>操作&lt;/th>
 &lt;th>用途&lt;/th>
 &lt;th>注意事項&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>建立 AMI&lt;/td>
 &lt;td>對現有 instance 拍照&lt;/td>
 &lt;td>&lt;code>--no-reboot&lt;/code> 避免服務中斷，但檔案系統一致性略低&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>從 AMI 啟動 instance&lt;/td>
 &lt;td>複製環境&lt;/td>
 &lt;td>新 instance 有新的 IP、hostname、instance ID&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨 region 複製 AMI&lt;/td>
 &lt;td>災難復原或多 region 部署&lt;/td>
 &lt;td>複製是非同步的、完成後才能在目標 region 使用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>共享 AMI&lt;/td>
 &lt;td>跨帳號使用同一個映像&lt;/td>
 &lt;td>需要設定 AMI 的 launch permission&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>AMI 包含 EBS snapshot——AMI 的儲存成本就是底層 EBS snapshot 的成本（按儲存量計費）。不再使用的 AMI 要記得 deregister 並刪除對應的 snapshot，否則持續計費。&lt;/p>
&lt;p>跟 container image 的差別：AMI 是整台 VM 的映像（含 OS、kernel、系統套件），container image 只包含應用程式和它的依賴（共用 host OS 的 kernel）。AMI 以 GB 計（通常 8-50 GB），container image 以 MB 計（通常 50-500 MB）。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ebs/" data-link-title="EBS" data-link-desc="掛在 EC2 instance 上的持久化區塊儲存（虛擬磁碟），支援 snapshot 快照備份，跟 instance 獨立生命週期">EBS&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>AMI（Amazon Machine Image）是 <a href="/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2</a> instance 的完整映像快照。它包含作業系統、已安裝的軟體、設定檔、磁碟內容——從一個 AMI 啟動新的 instance，得到的是跟拍照時完全一樣的環境。</p>
<h2 id="概念位置">概念位置</h2>
<p>AMI 在 infra 系列裡有兩個角色。第一個是接手維運時的保險——對 VM（即 <a href="/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2</a> instance）建一個 AMI 等於把整台機器拍下來，做任何改動前都有一個可回退的基線。第二個是環境標準化——把裝好軟體的 instance 做成 AMI（golden image），之後開新機器都從這個 AMI 啟動，確保每台機器的基線一致。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要理解 AMI 的情境包括：接手一台不確定裡面裝了什麼的 EC2（先拍 AMI 再動）、要在另一個 region 或帳號複製一台同樣的機器、OS 升級時要保留舊環境作為 rollback、或設計 auto-scaling 的 launch template（需要指定 AMI）。</p>
<h2 id="設計責任">設計責任</h2>
<table>
  <thead>
      <tr>
          <th>操作</th>
          <th>用途</th>
          <th>注意事項</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>建立 AMI</td>
          <td>對現有 instance 拍照</td>
          <td><code>--no-reboot</code> 避免服務中斷，但檔案系統一致性略低</td>
      </tr>
      <tr>
          <td>從 AMI 啟動 instance</td>
          <td>複製環境</td>
          <td>新 instance 有新的 IP、hostname、instance ID</td>
      </tr>
      <tr>
          <td>跨 region 複製 AMI</td>
          <td>災難復原或多 region 部署</td>
          <td>複製是非同步的、完成後才能在目標 region 使用</td>
      </tr>
      <tr>
          <td>共享 AMI</td>
          <td>跨帳號使用同一個映像</td>
          <td>需要設定 AMI 的 launch permission</td>
      </tr>
  </tbody>
</table>
<p>AMI 包含 EBS snapshot——AMI 的儲存成本就是底層 EBS snapshot 的成本（按儲存量計費）。不再使用的 AMI 要記得 deregister 並刪除對應的 snapshot，否則持續計費。</p>
<p>跟 container image 的差別：AMI 是整台 VM 的映像（含 OS、kernel、系統套件），container image 只包含應用程式和它的依賴（共用 host OS 的 kernel）。AMI 以 GB 計（通常 8-50 GB），container image 以 MB 計（通常 50-500 MB）。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/ebs/" data-link-title="EBS" data-link-desc="掛在 EC2 instance 上的持久化區塊儲存（虛擬磁碟），支援 snapshot 快照備份，跟 instance 獨立生命週期">EBS</a></li>
</ul>
]]></content:encoded></item><item><title>Composer</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/composer/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/composer/</guid><description>&lt;p>Composer 是 PHP 的套件管理工具，角色等同於 Node.js 的 npm、Python 的 pip、Go 的 go mod。它負責宣告專案需要哪些第三方套件、鎖定每個套件的確切版本、以及把套件安裝到專案目錄裡；PHP 專案的設定值管理則交給 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dotenv/" data-link-title=".env" data-link-desc="存放環境變數的純文字檔案，把機密值從程式碼分離出來">.env&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>接手 PHP 專案時，Composer 是判斷「專案依賴了什麼、版本有沒有已知漏洞」的入口，執行環境仰賴 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini&lt;/a> 的 CLI 設定。專案根目錄通常有三個 Composer 相關的檔案：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>檔案&lt;/th>
 &lt;th>角色&lt;/th>
 &lt;th>進 Git？&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>composer.json&lt;/code>&lt;/td>
 &lt;td>宣告依賴（套件名稱 + 版本範圍）&lt;/td>
 &lt;td>是&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>composer.lock&lt;/code>&lt;/td>
 &lt;td>鎖定確切版本（含所有 transitive 依賴）&lt;/td>
 &lt;td>是&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>vendor/&lt;/code>&lt;/td>
 &lt;td>安裝的套件目錄&lt;/td>
 &lt;td>否（.gitignore 排除、由 &lt;code>composer install&lt;/code> 重建）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>接手專案時如果根目錄有 &lt;code>composer.json&lt;/code> 但沒有 &lt;code>vendor/&lt;/code>，代表需要先跑 &lt;code>composer install&lt;/code> 才能讓專案運作。如果連 &lt;code>composer.lock&lt;/code> 都沒有，代表套件版本沒有鎖定——每次安裝可能拿到不同版本。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>兩個常用指令的差別：&lt;/p>
&lt;ul>
&lt;li>&lt;code>composer install&lt;/code>：按 &lt;code>composer.lock&lt;/code> 安裝確切版本。用於部署和接手——確保每台機器安裝的版本一致。&lt;/li>
&lt;li>&lt;code>composer update&lt;/code>：重新解析 &lt;code>composer.json&lt;/code> 的版本範圍、更新到最新的符合版本、改寫 &lt;code>composer.lock&lt;/code>。用於主動升級依賴。&lt;/li>
&lt;/ul>
&lt;p>接手時的關鍵操作：&lt;/p>
&lt;ul>
&lt;li>&lt;code>composer audit&lt;/code>：掃描已安裝套件的已知安全漏洞&lt;/li>
&lt;li>&lt;code>composer outdated&lt;/code>：列出可更新的套件及其最新版本&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dotenv/" data-link-title=".env" data-link-desc="存放環境變數的純文字檔案，把機密值從程式碼分離出來">.env&lt;/a>：Composer 管套件、.env 管設定值，兩者都是 PHP 專案的基礎設施&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini&lt;/a>：Composer 需要 PHP CLI 執行，php.ini 的 memory_limit 和 max_execution_time 會影響 Composer 能不能跑完&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Composer 是 PHP 的套件管理工具，角色等同於 Node.js 的 npm、Python 的 pip、Go 的 go mod。它負責宣告專案需要哪些第三方套件、鎖定每個套件的確切版本、以及把套件安裝到專案目錄裡；PHP 專案的設定值管理則交給 <a href="/blog/devops/infra/knowledge-cards/dotenv/" data-link-title=".env" data-link-desc="存放環境變數的純文字檔案，把機密值從程式碼分離出來">.env</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>接手 PHP 專案時，Composer 是判斷「專案依賴了什麼、版本有沒有已知漏洞」的入口，執行環境仰賴 <a href="/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini</a> 的 CLI 設定。專案根目錄通常有三個 Composer 相關的檔案：</p>
<table>
  <thead>
      <tr>
          <th>檔案</th>
          <th>角色</th>
          <th>進 Git？</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>composer.json</code></td>
          <td>宣告依賴（套件名稱 + 版本範圍）</td>
          <td>是</td>
      </tr>
      <tr>
          <td><code>composer.lock</code></td>
          <td>鎖定確切版本（含所有 transitive 依賴）</td>
          <td>是</td>
      </tr>
      <tr>
          <td><code>vendor/</code></td>
          <td>安裝的套件目錄</td>
          <td>否（.gitignore 排除、由 <code>composer install</code> 重建）</td>
      </tr>
  </tbody>
</table>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>接手專案時如果根目錄有 <code>composer.json</code> 但沒有 <code>vendor/</code>，代表需要先跑 <code>composer install</code> 才能讓專案運作。如果連 <code>composer.lock</code> 都沒有，代表套件版本沒有鎖定——每次安裝可能拿到不同版本。</p>
<h2 id="設計責任">設計責任</h2>
<p>兩個常用指令的差別：</p>
<ul>
<li><code>composer install</code>：按 <code>composer.lock</code> 安裝確切版本。用於部署和接手——確保每台機器安裝的版本一致。</li>
<li><code>composer update</code>：重新解析 <code>composer.json</code> 的版本範圍、更新到最新的符合版本、改寫 <code>composer.lock</code>。用於主動升級依賴。</li>
</ul>
<p>接手時的關鍵操作：</p>
<ul>
<li><code>composer audit</code>：掃描已安裝套件的已知安全漏洞</li>
<li><code>composer outdated</code>：列出可更新的套件及其最新版本</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/dotenv/" data-link-title=".env" data-link-desc="存放環境變數的純文字檔案，把機密值從程式碼分離出來">.env</a>：Composer 管套件、.env 管設定值，兩者都是 PHP 專案的基礎設施</li>
<li><a href="/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini</a>：Composer 需要 PHP CLI 執行，php.ini 的 memory_limit 和 max_execution_time 會影響 Composer 能不能跑完</li>
</ul>
]]></content:encoded></item><item><title>mysqldump</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/mysqldump/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/mysqldump/</guid><description>&lt;p>mysqldump 是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/mysql/" data-link-title="MySQL" data-link-desc="最廣泛使用的開源關聯式資料庫。MariaDB 是其社群分支。大版本升級（5.7→8.0）有認證方式和查詢行為的 breaking change">MySQL&lt;/a> 和 MariaDB 內建的命令列備份工具，把整個資料庫（或指定的表）匯出成一份包含 CREATE TABLE 和 INSERT 語句的 SQL 純文字檔。還原時把這份檔案餵給 &lt;code>mysql&lt;/code> client 就能重建資料。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>mysqldump 是有 SSH 存取（或 remote MySQL 存取）時的主要備份手段。比 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin&lt;/a> 的匯出更可靠——不受 web server 的 timeout 和記憶體限制影響，可以處理數 GB 的資料庫。沒有 SSH 的環境只能退回 phpMyAdmin 匯出。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>接手時如果 server 上有 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cron/" data-link-title="cron" data-link-desc="Unix/Linux 的排程工作系統，按時間表自動執行指令。接手維運時要盤點所有 cron job">cron&lt;/a> job 在跑 mysqldump，代表前任有做自動備份——確認輸出的 dump 檔案存在哪、保留幾天、有沒有被驗證過能還原。如果沒有任何 mysqldump cron，代表備份可能只靠 phpMyAdmin 手動匯出或完全沒做。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>常用的 flag 組合：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">mysqldump -u user -p &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> --single-transaction &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> --routines &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> --triggers &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> dbname &amp;gt; dump-&lt;span class="k">$(&lt;/span>date +%Y%m%d&lt;span class="k">)&lt;/span>.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Flag&lt;/th>
 &lt;th>作用&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>--single-transaction&lt;/code>&lt;/td>
 &lt;td>InnoDB 表不鎖表匯出（用一致性快照），生產備份必備&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>--routines&lt;/code>&lt;/td>
 &lt;td>含 stored procedure 和 function&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>--triggers&lt;/code>&lt;/td>
 &lt;td>含 trigger&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>--quick&lt;/code>&lt;/td>
 &lt;td>逐行讀取、不把整個表載入記憶體，大表必備&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>還原指令：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">mysql -u user -p dbname &amp;lt; dump-20260626.sql&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>mysqldump 產出的是邏輯備份（SQL 語句），還原速度取決於資料量——幾百 MB 以內分鐘級，數 GB 可能要半小時以上。需要更快的備份/還原（物理備份），要用 Percona XtraBackup 或 MySQL Enterprise Backup。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin&lt;/a>：無 SSH 時的替代備份手段&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cron/" data-link-title="cron" data-link-desc="Unix/Linux 的排程工作系統，按時間表自動執行指令。接手維運時要盤點所有 cron job">cron&lt;/a>：搭配 cron 做定期自動備份&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>mysqldump 是 <a href="/blog/devops/infra/knowledge-cards/mysql/" data-link-title="MySQL" data-link-desc="最廣泛使用的開源關聯式資料庫。MariaDB 是其社群分支。大版本升級（5.7→8.0）有認證方式和查詢行為的 breaking change">MySQL</a> 和 MariaDB 內建的命令列備份工具，把整個資料庫（或指定的表）匯出成一份包含 CREATE TABLE 和 INSERT 語句的 SQL 純文字檔。還原時把這份檔案餵給 <code>mysql</code> client 就能重建資料。</p>
<h2 id="概念位置">概念位置</h2>
<p>mysqldump 是有 SSH 存取（或 remote MySQL 存取）時的主要備份手段。比 <a href="/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin</a> 的匯出更可靠——不受 web server 的 timeout 和記憶體限制影響，可以處理數 GB 的資料庫。沒有 SSH 的環境只能退回 phpMyAdmin 匯出。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>接手時如果 server 上有 <a href="/blog/devops/infra/knowledge-cards/cron/" data-link-title="cron" data-link-desc="Unix/Linux 的排程工作系統，按時間表自動執行指令。接手維運時要盤點所有 cron job">cron</a> job 在跑 mysqldump，代表前任有做自動備份——確認輸出的 dump 檔案存在哪、保留幾天、有沒有被驗證過能還原。如果沒有任何 mysqldump cron，代表備份可能只靠 phpMyAdmin 手動匯出或完全沒做。</p>
<h2 id="設計責任">設計責任</h2>
<p>常用的 flag 組合：</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">mysqldump -u user -p <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --single-transaction <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --routines <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --triggers <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  dbname &gt; dump-<span class="k">$(</span>date +%Y%m%d<span class="k">)</span>.sql</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>Flag</th>
          <th>作用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>--single-transaction</code></td>
          <td>InnoDB 表不鎖表匯出（用一致性快照），生產備份必備</td>
      </tr>
      <tr>
          <td><code>--routines</code></td>
          <td>含 stored procedure 和 function</td>
      </tr>
      <tr>
          <td><code>--triggers</code></td>
          <td>含 trigger</td>
      </tr>
      <tr>
          <td><code>--quick</code></td>
          <td>逐行讀取、不把整個表載入記憶體，大表必備</td>
      </tr>
  </tbody>
</table>
<p>還原指令：</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">mysql -u user -p dbname &lt; dump-20260626.sql</span></span></code></pre></div><p>mysqldump 產出的是邏輯備份（SQL 語句），還原速度取決於資料量——幾百 MB 以內分鐘級，數 GB 可能要半小時以上。需要更快的備份/還原（物理備份），要用 Percona XtraBackup 或 MySQL Enterprise Backup。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin</a>：無 SSH 時的替代備份手段</li>
<li><a href="/blog/devops/infra/knowledge-cards/cron/" data-link-title="cron" data-link-desc="Unix/Linux 的排程工作系統，按時間表自動執行指令。接手維運時要盤點所有 cron job">cron</a>：搭配 cron 做定期自動備份</li>
</ul>
]]></content:encoded></item><item><title>Reverse Proxy</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/reverse-proxy/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/reverse-proxy/</guid><description>&lt;p>Reverse proxy 是一個坐在後端服務前面、代替它接收外部請求的中介層。外部 client 連的是 reverse proxy 的位址，reverse proxy 根據規則把請求轉發到實際處理的內部服務，再把回應傳回給 client。Client 不知道（也不需要知道）後面有幾台服務、跑在哪裡。實務上最常見的實作是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx&lt;/a> 或 ALB。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>nginx 和 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB&lt;/a> 都扮演 reverse proxy 角色。差別在層級：nginx 通常部署在應用層（跟應用伺服器同一台或同一個 VPC 內），ALB 是雲端平台提供的受管服務。兩者的核心功能相同——接收外部流量、轉發到後端、回傳結果。&lt;/p>
&lt;p>跟 forward proxy 的方向相反：forward proxy 代替 client 發送請求（client 在內網、proxy 幫它出去）；reverse proxy 代替 server 接收請求（server 在內網、proxy 幫它面對外部）。reverse proxy 在流量入口責任鏈裡的位置、跟 DNS 與負載平衡層的交棒關係，見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層&lt;/a>。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>接手時如果 server 上跑著 nginx 但應用程式用的是 PHP-FPM 或 Node.js，nginx 多半扮演 reverse proxy——它接 HTTP/HTTPS 請求、轉發給後端的 application server。設定檔裡的 &lt;code>proxy_pass&lt;/code>（nginx）或 &lt;code>ProxyPass&lt;/code>（Apache）就是 reverse proxy 的轉發規則，用在講 HTTP 的後端（Node.js、Python 的 gunicorn 這類）。PHP-FPM 只講 FastCGI，nginx 轉給它用的是 &lt;code>fastcgi_pass&lt;/code>，Apache 用 &lt;code>mod_proxy_fcgi&lt;/code>；用 &lt;code>proxy_pass&lt;/code> 指向 PHP-FPM 的埠不會成功，見 &lt;a href="https://tarrragon.github.io/blog/programming/php/01-server-runtime/fastcgi/" data-link-title="FastCGI：讓 web 伺服器與常駐的 PHP 程序溝通的協定" data-link-desc="說明 FastCGI 協定怎麼在 CGI 的基礎上去掉每個請求重啟程序這一點：常駐的應用程式程序、web 伺服器與它之間的連線、一個請求在協定裡的樣子，以及 SCRIPT_FILENAME 由 web 伺服器決定會帶來的安全邊界（cgi.fix_pathinfo 與 try_files）">FastCGI&lt;/a>。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>reverse proxy 常承擔的功能：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>功能&lt;/th>
 &lt;th>說明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>TLS 終結&lt;/td>
 &lt;td>HTTPS 的加解密在 proxy 層處理，後端服務只收 HTTP&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>負載平衡&lt;/td>
 &lt;td>把請求分配到多台後端（round-robin、least-connection）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>路由分流&lt;/td>
 &lt;td>依 URL path 導到不同後端服務（/api → backend、/ → frontend）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>靜態檔案快取&lt;/td>
 &lt;td>圖片、CSS、JS 由 proxy 直接回應、不轉發到後端&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>安全過濾&lt;/td>
 &lt;td>擋掉異常請求、限制請求速率、加安全標頭&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB&lt;/a>：雲端的受管 reverse proxy + 負載平衡器&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx&lt;/a>：最常見的 reverse proxy 軟體&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM&lt;/a>：只講 FastCGI 的 PHP 後端，nginx 用 &lt;code>fastcgi_pass&lt;/code> 而非 &lt;code>proxy_pass&lt;/code> 轉給它&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Reverse proxy 是一個坐在後端服務前面、代替它接收外部請求的中介層。外部 client 連的是 reverse proxy 的位址，reverse proxy 根據規則把請求轉發到實際處理的內部服務，再把回應傳回給 client。Client 不知道（也不需要知道）後面有幾台服務、跑在哪裡。實務上最常見的實作是 <a href="/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx</a> 或 ALB。</p>
<h2 id="概念位置">概念位置</h2>
<p>nginx 和 <a href="/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB</a> 都扮演 reverse proxy 角色。差別在層級：nginx 通常部署在應用層（跟應用伺服器同一台或同一個 VPC 內），ALB 是雲端平台提供的受管服務。兩者的核心功能相同——接收外部流量、轉發到後端、回傳結果。</p>
<p>跟 forward proxy 的方向相反：forward proxy 代替 client 發送請求（client 在內網、proxy 幫它出去）；reverse proxy 代替 server 接收請求（server 在內網、proxy 幫它面對外部）。reverse proxy 在流量入口責任鏈裡的位置、跟 DNS 與負載平衡層的交棒關係，見<a href="/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層</a>。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>接手時如果 server 上跑著 nginx 但應用程式用的是 PHP-FPM 或 Node.js，nginx 多半扮演 reverse proxy——它接 HTTP/HTTPS 請求、轉發給後端的 application server。設定檔裡的 <code>proxy_pass</code>（nginx）或 <code>ProxyPass</code>（Apache）就是 reverse proxy 的轉發規則，用在講 HTTP 的後端（Node.js、Python 的 gunicorn 這類）。PHP-FPM 只講 FastCGI，nginx 轉給它用的是 <code>fastcgi_pass</code>，Apache 用 <code>mod_proxy_fcgi</code>；用 <code>proxy_pass</code> 指向 PHP-FPM 的埠不會成功，見 <a href="/blog/programming/php/01-server-runtime/fastcgi/" data-link-title="FastCGI：讓 web 伺服器與常駐的 PHP 程序溝通的協定" data-link-desc="說明 FastCGI 協定怎麼在 CGI 的基礎上去掉每個請求重啟程序這一點：常駐的應用程式程序、web 伺服器與它之間的連線、一個請求在協定裡的樣子，以及 SCRIPT_FILENAME 由 web 伺服器決定會帶來的安全邊界（cgi.fix_pathinfo 與 try_files）">FastCGI</a>。</p>
<h2 id="設計責任">設計責任</h2>
<p>reverse proxy 常承擔的功能：</p>
<table>
  <thead>
      <tr>
          <th>功能</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>TLS 終結</td>
          <td>HTTPS 的加解密在 proxy 層處理，後端服務只收 HTTP</td>
      </tr>
      <tr>
          <td>負載平衡</td>
          <td>把請求分配到多台後端（round-robin、least-connection）</td>
      </tr>
      <tr>
          <td>路由分流</td>
          <td>依 URL path 導到不同後端服務（/api → backend、/ → frontend）</td>
      </tr>
      <tr>
          <td>靜態檔案快取</td>
          <td>圖片、CSS、JS 由 proxy 直接回應、不轉發到後端</td>
      </tr>
      <tr>
          <td>安全過濾</td>
          <td>擋掉異常請求、限制請求速率、加安全標頭</td>
      </tr>
  </tbody>
</table>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB</a>：雲端的受管 reverse proxy + 負載平衡器</li>
<li><a href="/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx</a>：最常見的 reverse proxy 軟體</li>
<li><a href="/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM</a>：只講 FastCGI 的 PHP 後端，nginx 用 <code>fastcgi_pass</code> 而非 <code>proxy_pass</code> 轉給它</li>
</ul>
]]></content:encoded></item><item><title>Database Migration</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/database-migration/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/database-migration/</guid><description>&lt;p>Database migration 是用版本化的腳本管理資料庫 schema 變更的做法。每次 schema 變更（加欄位、改索引、拆表、改資料型別）寫成一份獨立的 migration 檔案，按順序套用。這讓 schema 的演進跟程式碼一樣有版本歷史、可追蹤、可在新環境重現。在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/rds/" data-link-title="RDS" data-link-desc="AWS 的受管關聯式資料庫服務，代管備份、更新與 failover，讓使用者專注在 schema 和查詢">RDS&lt;/a> 等受管資料庫上執行時，schema 變更仍需注意鎖表與停機風險。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>migration 解決的問題是「資料庫的 schema 怎麼從 A 狀態安全地變成 B 狀態」。沒有 migration 時，schema 變更靠在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin&lt;/a> 或 CLI 手動執行 SQL，改了什麼只存在操作者的記憶裡。有 migration 時，每次變更都是 repo 裡的一份檔案，跟程式碼一起 commit、一起 review。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>接手專案時，如果 repo 裡有 &lt;code>migrations/&lt;/code> 目錄（或框架特定的路徑如 Laravel 的 &lt;code>database/migrations/&lt;/code>、Rails 的 &lt;code>db/migrate/&lt;/code>），代表專案使用 migration。如果 repo 裡只有一份 &lt;code>schema.sql&lt;/code> 或完全沒有 schema 相關檔案，代表 schema 變更是手動的——這時候建立 migration 紀律是接手後的優先事項之一。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>每份 migration 檔案包含兩個方向：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>UP&lt;/strong>（套用）：執行 schema 變更的 SQL&lt;/li>
&lt;li>&lt;strong>DOWN&lt;/strong>（回退）：撤銷這次變更的 SQL（不是所有變更都能完美回退，如刪除欄位後資料就沒了）&lt;/li>
&lt;/ul>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">-- migrations/2026-06-26-001-add-users-email-verified.sql
&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="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- UP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ADD&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">COLUMN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">email_verified&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">BOOLEAN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DEFAULT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FALSE&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- DOWN
&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 class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DROP&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">COLUMN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">email_verified&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>常用的 migration 工具：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>語言 / 框架&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Laravel Migration&lt;/td>
 &lt;td>PHP / Laravel&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rails Migration&lt;/td>
 &lt;td>Ruby / Rails&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Flyway&lt;/td>
 &lt;td>Java / 跨語言（純 SQL）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Liquibase&lt;/td>
 &lt;td>Java / 跨語言（XML / YAML / SQL）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>golang-migrate&lt;/td>
 &lt;td>Go&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>手動 SQL 檔案&lt;/td>
 &lt;td>無框架時的最低限度方案&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>沒有框架時，用日期 + 序號命名 SQL 檔案（&lt;code>2026-06-26-001-描述.sql&lt;/code>），搭配一張 &lt;code>migration_log&lt;/code> 表記錄哪些已經套用過，就是最低限度的 migration 系統。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/rds/" data-link-title="RDS" data-link-desc="AWS 的受管關聯式資料庫服務，代管備份、更新與 failover，讓使用者專注在 schema 和查詢">RDS&lt;/a>：migration 在 production 資料庫上執行時要格外小心——大表的 ALTER TABLE 可能鎖表&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/mysqldump/" data-link-title="mysqldump" data-link-desc="MySQL / MariaDB 的 CLI 備份工具，把資料庫匯出成 SQL 語句的純文字檔">mysqldump&lt;/a>：執行 migration 前先做一次完整備份&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Database migration 是用版本化的腳本管理資料庫 schema 變更的做法。每次 schema 變更（加欄位、改索引、拆表、改資料型別）寫成一份獨立的 migration 檔案，按順序套用。這讓 schema 的演進跟程式碼一樣有版本歷史、可追蹤、可在新環境重現。在 <a href="/blog/devops/infra/knowledge-cards/rds/" data-link-title="RDS" data-link-desc="AWS 的受管關聯式資料庫服務，代管備份、更新與 failover，讓使用者專注在 schema 和查詢">RDS</a> 等受管資料庫上執行時，schema 變更仍需注意鎖表與停機風險。</p>
<h2 id="概念位置">概念位置</h2>
<p>migration 解決的問題是「資料庫的 schema 怎麼從 A 狀態安全地變成 B 狀態」。沒有 migration 時，schema 變更靠在 <a href="/blog/devops/infra/knowledge-cards/phpmyadmin/" data-link-title="phpMyAdmin" data-link-desc="Web 介面的 MySQL / MariaDB 管理工具，透過瀏覽器操作資料庫">phpMyAdmin</a> 或 CLI 手動執行 SQL，改了什麼只存在操作者的記憶裡。有 migration 時，每次變更都是 repo 裡的一份檔案，跟程式碼一起 commit、一起 review。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>接手專案時，如果 repo 裡有 <code>migrations/</code> 目錄（或框架特定的路徑如 Laravel 的 <code>database/migrations/</code>、Rails 的 <code>db/migrate/</code>），代表專案使用 migration。如果 repo 裡只有一份 <code>schema.sql</code> 或完全沒有 schema 相關檔案，代表 schema 變更是手動的——這時候建立 migration 紀律是接手後的優先事項之一。</p>
<h2 id="設計責任">設計責任</h2>
<p>每份 migration 檔案包含兩個方向：</p>
<ul>
<li><strong>UP</strong>（套用）：執行 schema 變更的 SQL</li>
<li><strong>DOWN</strong>（回退）：撤銷這次變更的 SQL（不是所有變更都能完美回退，如刪除欄位後資料就沒了）</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- migrations/2026-06-26-001-add-users-email-verified.sql
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="c1">-- UP
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">users</span><span class="w"> </span><span class="k">ADD</span><span class="w"> </span><span class="k">COLUMN</span><span class="w"> </span><span class="n">email_verified</span><span class="w"> </span><span class="nb">BOOLEAN</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="k">FALSE</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w"></span><span class="c1">-- DOWN
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">users</span><span class="w"> </span><span class="k">DROP</span><span class="w"> </span><span class="k">COLUMN</span><span class="w"> </span><span class="n">email_verified</span><span class="p">;</span></span></span></code></pre></div><p>常用的 migration 工具：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>語言 / 框架</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Laravel Migration</td>
          <td>PHP / Laravel</td>
      </tr>
      <tr>
          <td>Rails Migration</td>
          <td>Ruby / Rails</td>
      </tr>
      <tr>
          <td>Flyway</td>
          <td>Java / 跨語言（純 SQL）</td>
      </tr>
      <tr>
          <td>Liquibase</td>
          <td>Java / 跨語言（XML / YAML / SQL）</td>
      </tr>
      <tr>
          <td>golang-migrate</td>
          <td>Go</td>
      </tr>
      <tr>
          <td>手動 SQL 檔案</td>
          <td>無框架時的最低限度方案</td>
      </tr>
  </tbody>
</table>
<p>沒有框架時，用日期 + 序號命名 SQL 檔案（<code>2026-06-26-001-描述.sql</code>），搭配一張 <code>migration_log</code> 表記錄哪些已經套用過，就是最低限度的 migration 系統。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/rds/" data-link-title="RDS" data-link-desc="AWS 的受管關聯式資料庫服務，代管備份、更新與 failover，讓使用者專注在 schema 和查詢">RDS</a>：migration 在 production 資料庫上執行時要格外小心——大表的 ALTER TABLE 可能鎖表</li>
<li><a href="/blog/devops/infra/knowledge-cards/mysqldump/" data-link-title="mysqldump" data-link-desc="MySQL / MariaDB 的 CLI 備份工具，把資料庫匯出成 SQL 語句的純文字檔">mysqldump</a>：執行 migration 前先做一次完整備份</li>
</ul>
]]></content:encoded></item><item><title>Prometheus</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/prometheus/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/prometheus/</guid><description>&lt;p>Prometheus 是開源的 metrics 收集與告警系統。它用 pull 模式運作——定期從被監控的 target（應用程式、伺服器、資料庫）的 HTTP endpoint 拉取指標，存進本地的時序資料庫，之後交給 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/grafana/" data-link-title="Grafana" data-link-desc="開源的監控視覺化平台，從 Prometheus / Loki / Elasticsearch 等資料源建立 dashboard">Grafana&lt;/a> 做視覺化查詢。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Prometheus 在 infra 監控層負責「收集與儲存指標」。它搭配 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/grafana/" data-link-title="Grafana" data-link-desc="開源的監控視覺化平台，從 Prometheus / Loki / Elasticsearch 等資料源建立 dashboard">Grafana&lt;/a> 做視覺化（Prometheus 自己的 UI 只有基礎的 query 介面）、搭配 Alertmanager 做告警路由（Prometheus 偵測異常、Alertmanager 決定通知誰）。斷網環境裡它是取代 Datadog / New Relic 的預設方案——不需要連外、self-hosted、社群龐大。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>系統需要 Prometheus 的訊號是：需要追蹤隨時間變化的數值指標（CPU 使用率、request 延遲、佇列深度、錯誤率），且這些指標要能查詢歷史趨勢和設定告警閾值。如果只需要 log（文字紀錄），Loki 或 ELK 更適合；Prometheus 處理的是結構化的數值 metrics。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Prometheus 把每次拉取的指標寫進本地時序資料庫，target 數量、拉取頻率與保留天數一起決定磁碟用量，告警也建立在同一份資料上；要定的設定是：scrape interval（多久拉一次；程式預設 1 分鐘，官方範例設定寫 15 秒）、retention（資料保留多久、預設 15 天）、哪些 target 要監控（service discovery 或靜態設定）、告警規則的閾值和評估窗口。斷網環境的額外考量是 storage capacity——所有資料留在本地磁碟、沒有 cloud auto-scale。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/grafana/" data-link-title="Grafana" data-link-desc="開源的監控視覺化平台，從 Prometheus / Loki / Elasticsearch 等資料源建立 dashboard">Grafana&lt;/a>：視覺化 Prometheus 的指標&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Prometheus 是開源的 metrics 收集與告警系統。它用 pull 模式運作——定期從被監控的 target（應用程式、伺服器、資料庫）的 HTTP endpoint 拉取指標，存進本地的時序資料庫，之後交給 <a href="/blog/devops/infra/knowledge-cards/grafana/" data-link-title="Grafana" data-link-desc="開源的監控視覺化平台，從 Prometheus / Loki / Elasticsearch 等資料源建立 dashboard">Grafana</a> 做視覺化查詢。</p>
<h2 id="概念位置">概念位置</h2>
<p>Prometheus 在 infra 監控層負責「收集與儲存指標」。它搭配 <a href="/blog/devops/infra/knowledge-cards/grafana/" data-link-title="Grafana" data-link-desc="開源的監控視覺化平台，從 Prometheus / Loki / Elasticsearch 等資料源建立 dashboard">Grafana</a> 做視覺化（Prometheus 自己的 UI 只有基礎的 query 介面）、搭配 Alertmanager 做告警路由（Prometheus 偵測異常、Alertmanager 決定通知誰）。斷網環境裡它是取代 Datadog / New Relic 的預設方案——不需要連外、self-hosted、社群龐大。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>系統需要 Prometheus 的訊號是：需要追蹤隨時間變化的數值指標（CPU 使用率、request 延遲、佇列深度、錯誤率），且這些指標要能查詢歷史趨勢和設定告警閾值。如果只需要 log（文字紀錄），Loki 或 ELK 更適合；Prometheus 處理的是結構化的數值 metrics。</p>
<h2 id="設計責任">設計責任</h2>
<p>Prometheus 把每次拉取的指標寫進本地時序資料庫，target 數量、拉取頻率與保留天數一起決定磁碟用量，告警也建立在同一份資料上；要定的設定是：scrape interval（多久拉一次；程式預設 1 分鐘，官方範例設定寫 15 秒）、retention（資料保留多久、預設 15 天）、哪些 target 要監控（service discovery 或靜態設定）、告警規則的閾值和評估窗口。斷網環境的額外考量是 storage capacity——所有資料留在本地磁碟、沒有 cloud auto-scale。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/grafana/" data-link-title="Grafana" data-link-desc="開源的監控視覺化平台，從 Prometheus / Loki / Elasticsearch 等資料源建立 dashboard">Grafana</a>：視覺化 Prometheus 的指標</li>
</ul>
]]></content:encoded></item><item><title>Grafana</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/grafana/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/grafana/</guid><description>&lt;p>Grafana 是開源的監控視覺化平台。它本身不收集或儲存資料——它連接外部資料源（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/prometheus/" data-link-title="Prometheus" data-link-desc="開源的 metrics 收集與告警系統，用 pull 模式從 target 拉取指標，斷網環境的預設監控方案">Prometheus&lt;/a>、Loki、Elasticsearch、MySQL 等），提供查詢介面和可自訂的儀表板。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Grafana 在監控體系裡負責「讓指標和 log 變成人可以讀的畫面」。&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/prometheus/" data-link-title="Prometheus" data-link-desc="開源的 metrics 收集與告警系統，用 pull 模式從 target 拉取指標，斷網環境的預設監控方案">Prometheus&lt;/a> 收集指標、Loki 收集 log、Grafana 把兩者的資料用圖表、表格、熱力圖呈現。不同角色看不同 dashboard——DevOps 看資源健康、開發者看應用指標、管理層看 SLA 達成率。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>系統需要 Grafana 的訊號是：已經有 Prometheus 或其他資料源在收集指標，但需要一個視覺化介面來建 dashboard、設告警（Grafana 也有自己的告警功能）、分享給團隊。只需要臨時查詢時，Prometheus 內建的網頁介面就夠。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Grafana 本身不儲存資料，它的設計決定因此圍繞在怎麼連到外部資料源、畫面給誰看、出狀況時通知誰：dashboard 的組織（按服務、按環境、按角色）、資料源的連線設定、使用者權限（viewer / editor / admin）、告警通知管道（email / Slack / webhook）。告警規則放在 Grafana 還是留在 Prometheus 是一個取捨：放在 Grafana 時它停掉的期間沒有告警，換到的是能對 Prometheus 以外的資料來源告警、不寫 YAML 也能建規則，比較見 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/prometheus/visualization-tool-selection/" data-link-title="Prometheus 指標的視覺化工具：視覺化層的責任，與內建介面、Grafana、Perses、Kibana／OpenSearch Dashboards、託管服務的取捨" data-link-desc="自架 Prometheus 之後把指標畫成常駐畫面的那一層承擔哪些責任（查詢與呈現、儀表板定義的保存、存取控制、告警規則放在哪一層、多種資料來源、授權與資源），Grafana 逐項對照這些責任，內建介面、Perses、Kibana／OpenSearch Dashboards 與託管服務各自的定位與成立的前提，以及前提改變時改用哪一個">Prometheus 指標的視覺化工具&lt;/a>。斷網環境裡 Grafana 的 plugin 需要離線安裝（&lt;code>grafana cli --pluginUrl &amp;lt;檔案位置&amp;gt; plugins install &amp;lt;plugin 名稱&amp;gt;&lt;/code> 指向本地檔案）。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/prometheus/" data-link-title="Prometheus" data-link-desc="開源的 metrics 收集與告警系統，用 pull 模式從 target 拉取指標，斷網環境的預設監控方案">Prometheus&lt;/a>：Grafana 最常見的 metrics 資料源&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Grafana 是開源的監控視覺化平台。它本身不收集或儲存資料——它連接外部資料源（<a href="/blog/devops/infra/knowledge-cards/prometheus/" data-link-title="Prometheus" data-link-desc="開源的 metrics 收集與告警系統，用 pull 模式從 target 拉取指標，斷網環境的預設監控方案">Prometheus</a>、Loki、Elasticsearch、MySQL 等），提供查詢介面和可自訂的儀表板。</p>
<h2 id="概念位置">概念位置</h2>
<p>Grafana 在監控體系裡負責「讓指標和 log 變成人可以讀的畫面」。<a href="/blog/devops/infra/knowledge-cards/prometheus/" data-link-title="Prometheus" data-link-desc="開源的 metrics 收集與告警系統，用 pull 模式從 target 拉取指標，斷網環境的預設監控方案">Prometheus</a> 收集指標、Loki 收集 log、Grafana 把兩者的資料用圖表、表格、熱力圖呈現。不同角色看不同 dashboard——DevOps 看資源健康、開發者看應用指標、管理層看 SLA 達成率。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>系統需要 Grafana 的訊號是：已經有 Prometheus 或其他資料源在收集指標，但需要一個視覺化介面來建 dashboard、設告警（Grafana 也有自己的告警功能）、分享給團隊。只需要臨時查詢時，Prometheus 內建的網頁介面就夠。</p>
<h2 id="設計責任">設計責任</h2>
<p>Grafana 本身不儲存資料，它的設計決定因此圍繞在怎麼連到外部資料源、畫面給誰看、出狀況時通知誰：dashboard 的組織（按服務、按環境、按角色）、資料源的連線設定、使用者權限（viewer / editor / admin）、告警通知管道（email / Slack / webhook）。告警規則放在 Grafana 還是留在 Prometheus 是一個取捨：放在 Grafana 時它停掉的期間沒有告警，換到的是能對 Prometheus 以外的資料來源告警、不寫 YAML 也能建規則，比較見 <a href="/blog/backend/04-observability/vendors/prometheus/visualization-tool-selection/" data-link-title="Prometheus 指標的視覺化工具：視覺化層的責任，與內建介面、Grafana、Perses、Kibana／OpenSearch Dashboards、託管服務的取捨" data-link-desc="自架 Prometheus 之後把指標畫成常駐畫面的那一層承擔哪些責任（查詢與呈現、儀表板定義的保存、存取控制、告警規則放在哪一層、多種資料來源、授權與資源），Grafana 逐項對照這些責任，內建介面、Perses、Kibana／OpenSearch Dashboards 與託管服務各自的定位與成立的前提，以及前提改變時改用哪一個">Prometheus 指標的視覺化工具</a>。斷網環境裡 Grafana 的 plugin 需要離線安裝（<code>grafana cli --pluginUrl &lt;檔案位置&gt; plugins install &lt;plugin 名稱&gt;</code> 指向本地檔案）。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/prometheus/" data-link-title="Prometheus" data-link-desc="開源的 metrics 收集與告警系統，用 pull 模式從 target 拉取指標，斷網環境的預設監控方案">Prometheus</a>：Grafana 最常見的 metrics 資料源</li>
</ul>
]]></content:encoded></item><item><title>HashiCorp Vault</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vault/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vault/</guid><description>&lt;p>HashiCorp Vault 是機密管理系統，集中存放和控制對敏感資料（密碼、API key、&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssl-tls/" data-link-title="SSL / TLS" data-link-desc="加密 client 與 server 之間通訊的協定，讓 HTTPS 成為可能。TLS 是 SSL 的後繼者，但 SSL 憑證的稱呼仍廣泛使用">TLS&lt;/a> 私鑰、資料庫憑證）的存取。每一次讀取都有稽核紀錄、每一份機密都有存取政策、存取憑證可以設定自動輪替。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Vault 在 infra 裡負責「機密值的集中管理」。跟直接把密碼寫在環境變數或設定檔的差別是：Vault 提供存取控制（只有被授權的身分能讀特定 secret）、稽核軌跡（誰在什麼時候讀了什麼）、以及動態 secret（每次請求產生一組臨時憑證、用完即銷毀）。&lt;/p>
&lt;p>連網環境通常用雲端的 secret manager（AWS Secrets Manager、GCP Secret Manager）。斷網環境沒有雲端服務可用、Vault 是 self-hosted 的替代方案。Vault 的存取控制模型呼應 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> 的 policy 精神——差別在 Vault 管的是機密值、IAM 管的是雲端 API 操作權限。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>系統需要 Vault 的訊號是：多個服務共用同一組資料庫密碼且密碼寫在設定檔裡、沒有人知道上次輪替是什麼時候、或是稽核要求「列出誰能存取哪些機密」而答不出來。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Vault 重新啟動後處於 sealed 狀態，解封之前任何服務都讀不到機密，所以 unseal 方式與 storage backend 決定 Vault 停機後多快能恢復，認證方式與 secret engine 決定哪個身分能取得哪一種機密。要定的是：unseal 方式（連網用 cloud auto-unseal、斷網用 Shamir&amp;rsquo;s secret sharing——需要 N 把 key 中的 M 把才能解鎖）、storage backend（Consul、PostgreSQL、filesystem）、認證方式（人用 LDAP/OIDC、機器用 AppRole）、secret engine 的選擇（KV 存靜態值、PKI 簽發數位憑證、database 動態產生 DB 帳號、SSH 依登入身分簽發短期 SSH 憑證）。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a>：Vault 的存取政策跟 IAM 的 policy 概念類似&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssl-tls/" data-link-title="SSL / TLS" data-link-desc="加密 client 與 server 之間通訊的協定，讓 HTTPS 成為可能。TLS 是 SSL 的後繼者，但 SSL 憑證的稱呼仍廣泛使用">SSL/TLS&lt;/a>：Vault 的 PKI engine 可以當內部 CA 簽發數位憑證&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）&lt;/a>：Vault 的 SSH secrets engine 簽發的就是這種憑證；只用 OpenSSH 自己簽發的流程見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>HashiCorp Vault 是機密管理系統，集中存放和控制對敏感資料（密碼、API key、<a href="/blog/devops/infra/knowledge-cards/ssl-tls/" data-link-title="SSL / TLS" data-link-desc="加密 client 與 server 之間通訊的協定，讓 HTTPS 成為可能。TLS 是 SSL 的後繼者，但 SSL 憑證的稱呼仍廣泛使用">TLS</a> 私鑰、資料庫憑證）的存取。每一次讀取都有稽核紀錄、每一份機密都有存取政策、存取憑證可以設定自動輪替。</p>
<h2 id="概念位置">概念位置</h2>
<p>Vault 在 infra 裡負責「機密值的集中管理」。跟直接把密碼寫在環境變數或設定檔的差別是：Vault 提供存取控制（只有被授權的身分能讀特定 secret）、稽核軌跡（誰在什麼時候讀了什麼）、以及動態 secret（每次請求產生一組臨時憑證、用完即銷毀）。</p>
<p>連網環境通常用雲端的 secret manager（AWS Secrets Manager、GCP Secret Manager）。斷網環境沒有雲端服務可用、Vault 是 self-hosted 的替代方案。Vault 的存取控制模型呼應 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> 的 policy 精神——差別在 Vault 管的是機密值、IAM 管的是雲端 API 操作權限。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>系統需要 Vault 的訊號是：多個服務共用同一組資料庫密碼且密碼寫在設定檔裡、沒有人知道上次輪替是什麼時候、或是稽核要求「列出誰能存取哪些機密」而答不出來。</p>
<h2 id="設計責任">設計責任</h2>
<p>Vault 重新啟動後處於 sealed 狀態，解封之前任何服務都讀不到機密，所以 unseal 方式與 storage backend 決定 Vault 停機後多快能恢復，認證方式與 secret engine 決定哪個身分能取得哪一種機密。要定的是：unseal 方式（連網用 cloud auto-unseal、斷網用 Shamir&rsquo;s secret sharing——需要 N 把 key 中的 M 把才能解鎖）、storage backend（Consul、PostgreSQL、filesystem）、認證方式（人用 LDAP/OIDC、機器用 AppRole）、secret engine 的選擇（KV 存靜態值、PKI 簽發數位憑證、database 動態產生 DB 帳號、SSH 依登入身分簽發短期 SSH 憑證）。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a>：Vault 的存取政策跟 IAM 的 policy 概念類似</li>
<li><a href="/blog/devops/infra/knowledge-cards/ssl-tls/" data-link-title="SSL / TLS" data-link-desc="加密 client 與 server 之間通訊的協定，讓 HTTPS 成為可能。TLS 是 SSL 的後繼者，但 SSL 憑證的稱呼仍廣泛使用">SSL/TLS</a>：Vault 的 PKI engine 可以當內部 CA 簽發數位憑證</li>
<li><a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）</a>：Vault 的 SSH secrets engine 簽發的就是這種憑證；只用 OpenSSH 自己簽發的流程見 <a href="/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入</a></li>
</ul>
]]></content:encoded></item><item><title>Harbor</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/harbor/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/harbor/</guid><description>&lt;p>Harbor 是開源的 container image registry，由 CNCF 孵化。它在 Docker Registry 的基礎上加了企業級功能：Web UI、角色型存取控制（RBAC）、映像漏洞掃描（內建 Trivy）、映像簽章驗證、以及跨 registry 的映像複製。常見的映像消費端是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/fargate/" data-link-title="Fargate" data-link-desc="AWS ECS 的無伺服器執行模式，由 AWS 代管運算實例，不需要管 EC2 capacity 或 AMI 更新">Fargate&lt;/a> 這類容器 task。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Harbor 在容器生態裡負責「映像的儲存、分發和安全把關」。連網環境裡這個角色通常由 Docker Hub、AWS ECR 或 GCR 擔任。斷網環境沒有公開 registry 可用、Harbor 是 self-hosted 的替代——所有 base image 和應用 image 都推進 Harbor、所有 docker pull（例如 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS&lt;/a> task）都從 Harbor 拉。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>系統需要 Harbor 的訊號是：團隊開始用容器部署服務、且環境無法連到公開 registry（斷網或受限網路）、或需要在 pull 時自動掃描漏洞。如果只是幾個人在開發機上用 Docker、Docker Registry（無 UI、無掃描）就夠了。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>斷網環境裡每一次 image pull 都從 Harbor 拉，哪些 image 能被部署、誰能推送、舊 image 留多久，都取決於下面這些 Harbor 設定：project 的組織（按團隊、按環境、按產品線）、使用者認證（本地帳號 or LDAP 整合）、漏洞掃描政策（push 時自動掃、block 有 Critical CVE 的 image）、映像保留政策（保留最近 N 個 tag、自動清理舊 image）、以及 storage backend（本地磁碟或 NFS）。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS&lt;/a>：ECS task 從 registry 拉 image&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/fargate/" data-link-title="Fargate" data-link-desc="AWS ECS 的無伺服器執行模式，由 AWS 代管運算實例，不需要管 EC2 capacity 或 AMI 更新">Fargate&lt;/a>：Fargate task 同樣需要 registry&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Harbor 是開源的 container image registry，由 CNCF 孵化。它在 Docker Registry 的基礎上加了企業級功能：Web UI、角色型存取控制（RBAC）、映像漏洞掃描（內建 Trivy）、映像簽章驗證、以及跨 registry 的映像複製。常見的映像消費端是 <a href="/blog/devops/infra/knowledge-cards/fargate/" data-link-title="Fargate" data-link-desc="AWS ECS 的無伺服器執行模式，由 AWS 代管運算實例，不需要管 EC2 capacity 或 AMI 更新">Fargate</a> 這類容器 task。</p>
<h2 id="概念位置">概念位置</h2>
<p>Harbor 在容器生態裡負責「映像的儲存、分發和安全把關」。連網環境裡這個角色通常由 Docker Hub、AWS ECR 或 GCR 擔任。斷網環境沒有公開 registry 可用、Harbor 是 self-hosted 的替代——所有 base image 和應用 image 都推進 Harbor、所有 docker pull（例如 <a href="/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS</a> task）都從 Harbor 拉。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>系統需要 Harbor 的訊號是：團隊開始用容器部署服務、且環境無法連到公開 registry（斷網或受限網路）、或需要在 pull 時自動掃描漏洞。如果只是幾個人在開發機上用 Docker、Docker Registry（無 UI、無掃描）就夠了。</p>
<h2 id="設計責任">設計責任</h2>
<p>斷網環境裡每一次 image pull 都從 Harbor 拉，哪些 image 能被部署、誰能推送、舊 image 留多久，都取決於下面這些 Harbor 設定：project 的組織（按團隊、按環境、按產品線）、使用者認證（本地帳號 or LDAP 整合）、漏洞掃描政策（push 時自動掃、block 有 Critical CVE 的 image）、映像保留政策（保留最近 N 個 tag、自動清理舊 image）、以及 storage backend（本地磁碟或 NFS）。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS</a>：ECS task 從 registry 拉 image</li>
<li><a href="/blog/devops/infra/knowledge-cards/fargate/" data-link-title="Fargate" data-link-desc="AWS ECS 的無伺服器執行模式，由 AWS 代管運算實例，不需要管 EC2 capacity 或 AMI 更新">Fargate</a>：Fargate task 同樣需要 registry</li>
</ul>
]]></content:encoded></item><item><title>Helm</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/helm/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/helm/</guid><description>&lt;p>Helm 是 Kubernetes 的套件管理工具。它用 chart（一組模板檔案 + 預設值）把多個 K8s 資源（Deployment、Service、ConfigMap、Ingress 等）打包成一個可安裝、可升級、可回退的單位。跟非 K8s 路線的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS&lt;/a> 屬於不同容器編排選型。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Helm 在 K8s 生態裡的角色類似 apt 在 Linux、npm 在 Node.js——把「安裝一個應用」從「逐一 apply 多個 YAML」變成「一條 &lt;code>helm install&lt;/code> 指令」。chart 可以參數化（values.yaml），同一份 chart 在不同環境用不同參數部署。&lt;/p>
&lt;p>公開 chart 從 Artifact Hub 下載。斷網環境裡用 &lt;code>helm pull&lt;/code> 在外部下載 chart tarball、搬進內網、從本地檔案安裝，或用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/harbor/" data-link-title="Harbor" data-link-desc="開源的 container image registry，支援映像掃描、RBAC、複製，斷網環境取代 Docker Hub 的方案">Harbor&lt;/a> 的 OCI chart 支援當內部 chart registry。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>系統需要 Helm 的訊號是：用 K8s 部署的應用超過 3 個、每個應用由 5+ 個 K8s 資源組成、且需要在多個環境（dev/staging/prod）用不同參數部署同一套定義。如果只有 1-2 個簡單應用、直接 &lt;code>kubectl apply&lt;/code> 就好。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>chart 的粒度一旦定下，各環境的 values file 與版本號都依附在它上面，事後拆分或合併 chart 要連同這些一起改，所以 Helm 的設計從粒度開始：chart 的粒度（一個 chart = 一個微服務 or 一整個平台）、values 的組織（per-environment values file）、chart 版本管理（chart version vs app version）、以及升級策略（&lt;code>helm upgrade --atomic&lt;/code> 失敗自動回退）。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS&lt;/a>：ECS 是非 K8s 的容器編排替代&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Helm 是 Kubernetes 的套件管理工具。它用 chart（一組模板檔案 + 預設值）把多個 K8s 資源（Deployment、Service、ConfigMap、Ingress 等）打包成一個可安裝、可升級、可回退的單位。跟非 K8s 路線的 <a href="/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS</a> 屬於不同容器編排選型。</p>
<h2 id="概念位置">概念位置</h2>
<p>Helm 在 K8s 生態裡的角色類似 apt 在 Linux、npm 在 Node.js——把「安裝一個應用」從「逐一 apply 多個 YAML」變成「一條 <code>helm install</code> 指令」。chart 可以參數化（values.yaml），同一份 chart 在不同環境用不同參數部署。</p>
<p>公開 chart 從 Artifact Hub 下載。斷網環境裡用 <code>helm pull</code> 在外部下載 chart tarball、搬進內網、從本地檔案安裝，或用 <a href="/blog/devops/infra/knowledge-cards/harbor/" data-link-title="Harbor" data-link-desc="開源的 container image registry，支援映像掃描、RBAC、複製，斷網環境取代 Docker Hub 的方案">Harbor</a> 的 OCI chart 支援當內部 chart registry。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>系統需要 Helm 的訊號是：用 K8s 部署的應用超過 3 個、每個應用由 5+ 個 K8s 資源組成、且需要在多個環境（dev/staging/prod）用不同參數部署同一套定義。如果只有 1-2 個簡單應用、直接 <code>kubectl apply</code> 就好。</p>
<h2 id="設計責任">設計責任</h2>
<p>chart 的粒度一旦定下，各環境的 values file 與版本號都依附在它上面，事後拆分或合併 chart 要連同這些一起改，所以 Helm 的設計從粒度開始：chart 的粒度（一個 chart = 一個微服務 or 一整個平台）、values 的組織（per-environment values file）、chart 版本管理（chart version vs app version）、以及升級策略（<code>helm upgrade --atomic</code> 失敗自動回退）。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS</a>：ECS 是非 K8s 的容器編排替代</li>
</ul>
]]></content:encoded></item><item><title>動靜分離（Static / Dynamic Content Separation）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/static-dynamic-separation/</link><pubDate>Mon, 20 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/static-dynamic-separation/</guid><description>&lt;p>動靜分離指入口層把靜態資源（圖片、CSS、JS、字型）直接從檔案系統或快取回應、只把需要應用邏輯的動態請求轉發給應用伺服器的分流做法。它是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy&lt;/a> 這一層最具體的職責之一，目的是把應用伺服器的算力集中在真正需要它的請求上。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>動靜分離坐落在 reverse proxy 的路由職責裡，跟 TLS 終結、負載分散、健康檢查並列。它跟 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx&lt;/a> 的關係是實作面——nginx 用 &lt;code>try_files&lt;/code> 走檔案、&lt;code>proxy_pass&lt;/code> 轉後端，把兩類請求在設定裡分開。共享主機時代 Apache 加 mod_php 把動靜都吃在同一個 process、動靜分離是隱形預設；自管環境要明確劃出哪些路徑走檔案、哪些走後端。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>一個載入 50 個靜態資源、只有 1 個動態 API 呼叫的頁面，做了動靜分離後應用伺服器只需要處理那 1 個請求，其餘 50 個由 reverse proxy 直接回掉。判讀訊號：應用伺服器的 CPU 被大量靜態檔案請求佔用、或 access log 顯示 PHP / Node process 在回應 &lt;code>.css&lt;/code>、&lt;code>.js&lt;/code>——代表動靜沒有分離、算力花在不需要它的請求上。&lt;/p>
&lt;h2 id="判讀方式">判讀方式&lt;/h2>
&lt;p>動靜分離的邊界是「這個請求需不需要應用邏輯才能回應」。靜態資源在不同環境有不同的回應層：單機由 nginx 從檔案系統回、雲端可以再往外推到 CDN 在邊緣回。分離的層次越靠外、應用伺服器的負載越低，代價是快取失效的複雜度上升。它在入口責任鏈裡的定位、以及跟 CDN 邊緣回應的分層，在&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層&lt;/a>展開。&lt;/p></description><content:encoded><![CDATA[<p>動靜分離指入口層把靜態資源（圖片、CSS、JS、字型）直接從檔案系統或快取回應、只把需要應用邏輯的動態請求轉發給應用伺服器的分流做法。它是 <a href="/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy</a> 這一層最具體的職責之一，目的是把應用伺服器的算力集中在真正需要它的請求上。</p>
<h2 id="概念位置">概念位置</h2>
<p>動靜分離坐落在 reverse proxy 的路由職責裡，跟 TLS 終結、負載分散、健康檢查並列。它跟 <a href="/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx</a> 的關係是實作面——nginx 用 <code>try_files</code> 走檔案、<code>proxy_pass</code> 轉後端，把兩類請求在設定裡分開。共享主機時代 Apache 加 mod_php 把動靜都吃在同一個 process、動靜分離是隱形預設；自管環境要明確劃出哪些路徑走檔案、哪些走後端。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>一個載入 50 個靜態資源、只有 1 個動態 API 呼叫的頁面，做了動靜分離後應用伺服器只需要處理那 1 個請求，其餘 50 個由 reverse proxy 直接回掉。判讀訊號：應用伺服器的 CPU 被大量靜態檔案請求佔用、或 access log 顯示 PHP / Node process 在回應 <code>.css</code>、<code>.js</code>——代表動靜沒有分離、算力花在不需要它的請求上。</p>
<h2 id="判讀方式">判讀方式</h2>
<p>動靜分離的邊界是「這個請求需不需要應用邏輯才能回應」。靜態資源在不同環境有不同的回應層：單機由 nginx 從檔案系統回、雲端可以再往外推到 CDN 在邊緣回。分離的層次越靠外、應用伺服器的負載越低，代價是快取失效的複雜度上升。它在入口責任鏈裡的定位、以及跟 CDN 邊緣回應的分層，在<a href="/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層</a>展開。</p>
]]></content:encoded></item><item><title>CGI（Common Gateway Interface）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cgi/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cgi/</guid><description>&lt;p>CGI 是 web 伺服器呼叫外部程式來產生回應的介面，規格是 RFC 3875（CGI/1.1）：web 伺服器為每個請求啟動一個新程序，把請求資訊放進環境變數（RFC 稱為 meta-variables）、把請求內容寫進標準輸入，程式把回應寫到標準輸出後結束。它跟語言無關。可先對照 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/apache-mpm/" data-link-title="Apache MPM（Multi-Processing Module）" data-link-desc="在 Apache 設定或 httpd -V 看到 prefork、worker、event，或要判斷 mod_php 為什麼讓 Apache 只能用 prefork、換成 PHP-FPM 之後能改用哪一種時">Apache MPM&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>CGI 是「web 伺服器負責 HTTP、外部程式負責產生內容」這個分工的起點。後來的 FastCGI 保留了同一套參數名稱，只改掉程序壽命（程序常駐、處理很多請求），PHP 的實作是 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM&lt;/a>。&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/htaccess/" data-link-title=".htaccess" data-link-desc="Apache Web Server 的目錄層級設定檔，控制 URL rewrite、存取權限、PHP 設定覆寫與安全標頭">.htaccess&lt;/a> 時代的共享主機常用 CGI 加 suEXEC 讓每個站台以自己的帳號執行。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;code>$_SERVER&lt;/code> 的大寫鍵名&lt;/strong>：&lt;code>REQUEST_METHOD&lt;/code>、&lt;code>QUERY_STRING&lt;/code>、&lt;code>SCRIPT_FILENAME&lt;/code> 來自 CGI 的 meta-variables，FastCGI 沿用了它們。&lt;/li>
&lt;li>&lt;strong>每個請求一個新 PID&lt;/strong>：PHP 以 &lt;code>php-cgi&lt;/code> 被 CGI 呼叫時，每個請求的 &lt;code>getmypid()&lt;/code> 都不同，OPcache 永遠命中不了。&lt;/li>
&lt;li>&lt;strong>CVE-2012-1823&lt;/strong>：PHP 5.3.12 / 5.4.2 之前，以 CGI 方式執行的 &lt;code>php-cgi&lt;/code> 可能把查詢字串當成命令列選項，只影響這種部署。&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>接手 CGI 部署時，要知道它的每請求啟動成本與隔離方式；換成 PHP-FPM 時，用 pool 的執行帳號保留原本 suEXEC 提供的隔離。詳見 &lt;a href="https://tarrragon.github.io/blog/programming/php/01-server-runtime/cgi/" data-link-title="CGI：每個請求啟動一個 PHP 程序的執行模型" data-link-desc="說明 CGI 怎麼把一個 HTTP 請求交給外部程式：web 伺服器為每個請求啟動一個程序，用環境變數與標準輸入輸出傳遞請求和回應；PHP 以 php-cgi 執行時每個請求付出的啟動成本、OPcache 為什麼沒有作用、以每個站台的使用者身分執行帶來的隔離，以及請求資料與程式呼叫參數之間的邊界問題">CGI&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>CGI 是 web 伺服器呼叫外部程式來產生回應的介面，規格是 RFC 3875（CGI/1.1）：web 伺服器為每個請求啟動一個新程序，把請求資訊放進環境變數（RFC 稱為 meta-variables）、把請求內容寫進標準輸入，程式把回應寫到標準輸出後結束。它跟語言無關。可先對照 <a href="/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx</a> 與 <a href="/blog/devops/infra/knowledge-cards/apache-mpm/" data-link-title="Apache MPM（Multi-Processing Module）" data-link-desc="在 Apache 設定或 httpd -V 看到 prefork、worker、event，或要判斷 mod_php 為什麼讓 Apache 只能用 prefork、換成 PHP-FPM 之後能改用哪一種時">Apache MPM</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>CGI 是「web 伺服器負責 HTTP、外部程式負責產生內容」這個分工的起點。後來的 FastCGI 保留了同一套參數名稱，只改掉程序壽命（程序常駐、處理很多請求），PHP 的實作是 <a href="/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM</a>。<a href="/blog/devops/infra/knowledge-cards/htaccess/" data-link-title=".htaccess" data-link-desc="Apache Web Server 的目錄層級設定檔，控制 URL rewrite、存取權限、PHP 設定覆寫與安全標頭">.htaccess</a> 時代的共享主機常用 CGI 加 suEXEC 讓每個站台以自己的帳號執行。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong><code>$_SERVER</code> 的大寫鍵名</strong>：<code>REQUEST_METHOD</code>、<code>QUERY_STRING</code>、<code>SCRIPT_FILENAME</code> 來自 CGI 的 meta-variables，FastCGI 沿用了它們。</li>
<li><strong>每個請求一個新 PID</strong>：PHP 以 <code>php-cgi</code> 被 CGI 呼叫時，每個請求的 <code>getmypid()</code> 都不同，OPcache 永遠命中不了。</li>
<li><strong>CVE-2012-1823</strong>：PHP 5.3.12 / 5.4.2 之前，以 CGI 方式執行的 <code>php-cgi</code> 可能把查詢字串當成命令列選項，只影響這種部署。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>接手 CGI 部署時，要知道它的每請求啟動成本與隔離方式；換成 PHP-FPM 時，用 pool 的執行帳號保留原本 suEXEC 提供的隔離。詳見 <a href="/blog/programming/php/01-server-runtime/cgi/" data-link-title="CGI：每個請求啟動一個 PHP 程序的執行模型" data-link-desc="說明 CGI 怎麼把一個 HTTP 請求交給外部程式：web 伺服器為每個請求啟動一個程序，用環境變數與標準輸入輸出傳遞請求和回應；PHP 以 php-cgi 執行時每個請求付出的啟動成本、OPcache 為什麼沒有作用、以每個站台的使用者身分執行帶來的隔離，以及請求資料與程式呼叫參數之間的邊界問題">CGI</a>。</p>
]]></content:encoded></item><item><title>Apache MPM（Multi-Processing Module）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/apache-mpm/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/apache-mpm/</guid><description>&lt;p>Apache MPM 決定 Apache 用程序還是執行緒處理並發的連線：prefork 每個連線佔一個子程序、子程序裡沒有多執行緒；worker 每個子程序有多個執行緒，每個連線佔一個執行緒；event 跟 worker 一樣用執行緒，另外把閒置的 keep-alive 連線交給專門的執行緒看管。Apache 2.4 的官方映像檔預設是 event。可先對照 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cgi/" data-link-title="CGI（Common Gateway Interface）" data-link-desc="接手 cgi-bin 目錄或以 php-cgi 執行的舊站，或在 $_SERVER 看到 REQUEST_METHOD、QUERY_STRING 這類大寫鍵名，要知道它們從哪裡來時">CGI&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>MPM 是 Apache 的並發模型，跟 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx&lt;/a> 的事件驅動模型是同一個問題的不同答案；它也跟 PHP 的執行方式互相限制：&lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php&lt;/a> 把 PHP 放進 Apache 的程序裡，沒有以 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/zts/" data-link-title="ZTS（Zend Thread Safety）" data-link-desc="在 php -i 看到 Thread Safety 是 enabled 或 disabled，或要判斷某個 PHP 能不能跑在多執行緒的伺服器（Apache worker/event MPM、FrankenPHP）裡時">ZTS&lt;/a> 編譯時只能搭配 prefork；PHP 改由 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM&lt;/a> 在外面執行（Apache 用 &lt;code>mod_proxy_fcgi&lt;/code> 轉送）時，Apache 就能用 event。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;code>httpd -V&lt;/code> 的 &lt;code>Server MPM&lt;/code>&lt;/strong>：官方 &lt;code>httpd:2.4&lt;/code> 是 &lt;code>event&lt;/code>，&lt;code>php:8.5-apache&lt;/code> 是 &lt;code>prefork&lt;/code>（裝了 mod_php）。&lt;/li>
&lt;li>&lt;strong>&lt;code>apache2ctl -M&lt;/code> 列出 &lt;code>mpm_prefork_module&lt;/code>&lt;/strong>：同時列出 &lt;code>php_module&lt;/code> 時，就是 mod_php 加 prefork 的組合。&lt;/li>
&lt;li>&lt;strong>並發上限等於子程序上限&lt;/strong>：prefork 下 &lt;code>MaxRequestWorkers&lt;/code> 就是能同時處理的連線數，慢客戶端與閒置的 keep-alive 連線都佔著一個子程序。&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>從 mod_php 換到 PHP-FPM 時，可以順便把 MPM 換成 event，讓 Apache 用較少的資源處理大量連線。詳見 &lt;a href="https://tarrragon.github.io/blog/programming/php/01-server-runtime/mod-php/" data-link-title="mod_php：PHP 直譯器嵌進 Apache 程序的執行模型" data-link-desc="說明 mod_php 怎麼把 PHP 直譯器載入 Apache 的每個子程序：為什麼它要搭配 prefork MPM、每個子程序都帶著 PHP 對記憶體與並發的影響、所有站台共用 Apache 的執行帳號、php.ini 與 .htaccess 的設定方式，以及從 mod_php 搬到 PHP-FPM 時 .htaccess 的 php_value 與 .user.ini 的差異">mod_php&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Apache MPM 決定 Apache 用程序還是執行緒處理並發的連線：prefork 每個連線佔一個子程序、子程序裡沒有多執行緒；worker 每個子程序有多個執行緒，每個連線佔一個執行緒；event 跟 worker 一樣用執行緒，另外把閒置的 keep-alive 連線交給專門的執行緒看管。Apache 2.4 的官方映像檔預設是 event。可先對照 <a href="/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx</a> 與 <a href="/blog/devops/infra/knowledge-cards/cgi/" data-link-title="CGI（Common Gateway Interface）" data-link-desc="接手 cgi-bin 目錄或以 php-cgi 執行的舊站，或在 $_SERVER 看到 REQUEST_METHOD、QUERY_STRING 這類大寫鍵名，要知道它們從哪裡來時">CGI</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>MPM 是 Apache 的並發模型，跟 <a href="/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx</a> 的事件驅動模型是同一個問題的不同答案；它也跟 PHP 的執行方式互相限制：<a href="/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php</a> 把 PHP 放進 Apache 的程序裡，沒有以 <a href="/blog/programming/php/knowledge-cards/zts/" data-link-title="ZTS（Zend Thread Safety）" data-link-desc="在 php -i 看到 Thread Safety 是 enabled 或 disabled，或要判斷某個 PHP 能不能跑在多執行緒的伺服器（Apache worker/event MPM、FrankenPHP）裡時">ZTS</a> 編譯時只能搭配 prefork；PHP 改由 <a href="/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM</a> 在外面執行（Apache 用 <code>mod_proxy_fcgi</code> 轉送）時，Apache 就能用 event。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong><code>httpd -V</code> 的 <code>Server MPM</code></strong>：官方 <code>httpd:2.4</code> 是 <code>event</code>，<code>php:8.5-apache</code> 是 <code>prefork</code>（裝了 mod_php）。</li>
<li><strong><code>apache2ctl -M</code> 列出 <code>mpm_prefork_module</code></strong>：同時列出 <code>php_module</code> 時，就是 mod_php 加 prefork 的組合。</li>
<li><strong>並發上限等於子程序上限</strong>：prefork 下 <code>MaxRequestWorkers</code> 就是能同時處理的連線數，慢客戶端與閒置的 keep-alive 連線都佔著一個子程序。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>從 mod_php 換到 PHP-FPM 時，可以順便把 MPM 換成 event，讓 Apache 用較少的資源處理大量連線。詳見 <a href="/blog/programming/php/01-server-runtime/mod-php/" data-link-title="mod_php：PHP 直譯器嵌進 Apache 程序的執行模型" data-link-desc="說明 mod_php 怎麼把 PHP 直譯器載入 Apache 的每個子程序：為什麼它要搭配 prefork MPM、每個子程序都帶著 PHP 對記憶體與並發的影響、所有站台共用 Apache 的執行帳號、php.ini 與 .htaccess 的設定方式，以及從 mod_php 搬到 PHP-FPM 時 .htaccess 的 php_value 與 .user.ini 的差異">mod_php</a>。</p>
]]></content:encoded></item><item><title>Identity-Aware Proxy（身分感知代理）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/</guid><description>&lt;p>身分感知代理（identity-aware proxy）是一個放在網際網路與內部主機之間的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy&lt;/a>：它接收外部連線，先向組織的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider&lt;/a> 確認發起連線的帳號、再依授權規則決定是否替這條連線開到後端。通過的連線送到後端的方式有兩種：由代理從它自己的固定位址段連到主機的內部 IP（GCP 的 IAP），這時主機的防火牆只放行那段代理位址；或經由主機上的代理程式（connector，例如 &lt;code>cloudflared&lt;/code>）主動往外建立的反向連線送進去（Cloudflare Tunnel、Teleport agent），這時主機不開任何入站 port。兩種方式下主機都不必持有公開 IP。它是 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/zero-trust/" data-link-title="Zero Trust（零信任）" data-link-desc="判斷內網或 VPN 裡的服務能不能因為連線來自內部就省掉認證、或要評估一條連線該放行到什麼範圍時，查「每一次連線各自通過身分與授權」這個原則的內容與導入代價">Zero Trust（零信任）&lt;/a> 原則在存取入口上的一種實作：放行的依據是每一次連線的身分，網路位置不構成信任的理由。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>管理通道拆成網路可達性、通道授權、主機登入驗證三層來看時（模型見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>），身分感知代理同時承擔網路可達性與通道授權：sshd 只對代理可見，而能不能開通道由代理用 IdP 帳號判斷——這個判斷就是通道授權，指的是決定「這個人能不能打開通往主機的連線」，與登入主機時驗證 SSH 金鑰是兩回事。主機登入驗證在多數實作裡仍由主機上的 sshd 負責；Teleport 的節點這類實作用自己內建的 SSH 伺服器與代理簽發的憑證，主機登入驗證跟著換掉。由 sshd 負責時，金鑰的發放方式另見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）&lt;/a>。&lt;/p>
&lt;p>它與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）&lt;/a> 都讓內部主機不必各自對外，差別在對外接收連線的元件由誰維運、用什麼判斷身分：跳板機是自己維運的主機、用 SSH 金鑰判斷；身分感知代理是廠商提供或自己架設的代理服務、用 IdP 帳號判斷。它與一般反向代理的差別在轉送前多了一次身分驗證與授權。&lt;/p>
&lt;p>常見的實作有 GCP 的 IAP（Identity-Aware Proxy，對 HTTPS 應用與 TCP 連線各有一種模式，TCP forwarding 的來源位址段是 &lt;code>35.235.240.0/20&lt;/code>，到得了同一家雲端裡的主機，以及經 Cloud Interconnect 或 Cloud VPN 接進來、而且 Cloud Router 對地端通告了這段位址的地端主機）、Cloudflare Access、Teleport 的 proxy。Cloudflare Access 與 Teleport 用反向連線把流量送進主機，主機上的 connector 怎麼往外連、斷線時會怎樣見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/outbound-tunnel/" data-link-title="Outbound Tunnel" data-link-desc="反向隧道把出站連線轉成可達入口、與傳統 port-forward 的責任倒轉">Outbound Tunnel（出站隧道）&lt;/a>。這個模式的公開源頭是 Google 2014 年起發表的 BeyondCorp 系列論文。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>由代理連到內部 IP 的模式（GCP IAP）：&lt;/p>
&lt;ul>
&lt;li>主機的入站規則只放行某一段固定的代理位址，沒有任何來源是 &lt;code>0.0.0.0/0&lt;/code> 的管理 port 規則&lt;/li>
&lt;li>主機上的認證紀錄裡，SSH 連線的來源 IP 全部落在代理的位址段，看不出是哪個人——要查人得看代理或 IdP 的 audit log&lt;/li>
&lt;/ul>
&lt;p>經由代理程式反向連線的模式（Cloudflare Tunnel、Teleport agent）：&lt;/p>
&lt;ul>
&lt;li>主機上有一個常駐的代理程式（例如 &lt;code>cloudflared&lt;/code>、&lt;code>teleport&lt;/code>），主機的入站規則完全沒有管理 port&lt;/li>
&lt;li>代理程式把連線轉送到 sshd 的實作（&lt;code>cloudflared&lt;/code>），認證紀錄裡的 SSH 來源是 &lt;code>127.0.0.1&lt;/code> 或代理程式所在的本機位址；Teleport 節點用自己的 SSH 伺服器，sshd 的紀錄裡不會出現這些連線&lt;/li>
&lt;/ul>
&lt;p>兩種模式共同的樣子是主機沒有公開 IP，管理員仍然用 &lt;code>ssh&lt;/code> 或廠商 CLI 連得進去。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>導入身分感知代理之後，主機自己不再判斷連線者是誰，下面這些判斷移到代理與 IdP 上：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>IdP 帳號的安全強度&lt;/strong>：存取權限轉移到帳號上之後，帳號被釣魚等於通道被打開，所以 IdP 端要強制多因素驗證，管理員帳號優先用防釣魚的硬體金鑰或 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/passkey/" data-link-title="Passkey" data-link-desc="要用金鑰對取代密碼、或判斷已導入的 passkey 實際擋掉了什麼時，用來定位它的保護落在哪一段、上限由哪條路徑決定">Passkey&lt;/a>&lt;/li>
&lt;li>&lt;strong>授權範圍&lt;/strong>：授權綁給負責維運的群組，並限定可以連的 port 與主機，避免整個組織的帳號都拿得到通道&lt;/li>
&lt;li>&lt;strong>代理的 session 長度&lt;/strong>：IdP 停用一個帳號之後，代理已經簽給他的 session 或憑證要到期或被撤銷才失效（Cloudflare Access 的 session 最長可以設到一個月，&lt;code>gcloud&lt;/code> 在本機保存 refresh token），所以收回延遲的上限是代理端設定的 session 長度；處理離職或帳號外洩時另外在代理端撤銷 session。登入後留在本機的 token 檔可以被複製重放，不會再過一次多因素驗證，session 越短、或綁定裝置，被複製之後能用的時間越短&lt;/li>
&lt;li>&lt;strong>代理的可用性與延遲&lt;/strong>：所有位元組都經過代理轉送，代理服務中斷時管理通道一起中斷，大量傳檔也比直連慢；救援時的備用路徑見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）&lt;/a>&lt;/li>
&lt;li>&lt;strong>稽核紀錄的位置與開啟時機&lt;/strong>：主機端只看得到代理的位址，「誰在什麼時候連進哪台主機」要從代理或 IdP 的 audit log 取得。這份紀錄只記錄開啟之後的連線，事後要調查一次入侵時，開啟之前的連線查不回來，所以在切換到代理的同一天開啟，開啟之後實際連一次、查到這次連線的紀錄才算開對（見&lt;a href="https://tarrragon.github.io/blog/report/protection-proves-itself-by-firing/" data-link-title="平常不動作的防護機制的部署驗證：設定被接受、元件狀態正常、執行失敗被吞掉與測試從中途注入這些提早出現的成功訊號，驗證查詢與處置指令的假陰性，以及合成觸發本身有損害時的分段驗證" data-link-desc="部署停損開關、稽核紀錄、告警或自動處置這類平常不動作的防護機制之後，要判斷它算不算部署完成時讀；也用在驗收紀錄寫著「部署成功」「狀態 ACTIVE」「設定已套用」、或驗證查詢回傳零筆就被當成結論的時候">平常不動作的防護機制的部署驗證&lt;/a>）&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）&lt;/a> — 同樣收斂對外入口，由自己維運的主機轉接&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — 放行代理位址段的那一條入站規則所在的位置；AWS 叫 security group，GCP 對應的是 VPC 防火牆規則&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider&lt;/a> — 代理用來確認帳號的系統&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/gcp-iap-ssh-access/" data-link-title="GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding" data-link-desc="GCP 預設網路自帶的防火牆規則與它們留下的暴露面、IAP TCP forwarding 的連線路徑與日常連線指令、切換到 IAP 的順序為什麼這樣排、IAP 通道連不上時的依層判讀，以及 IAM 授權範圍、OS Login 的啟用條件與工作階段的結束、拿掉外部 IP 之後的出站路徑、Organization Policy、階層式防火牆、稽核紀錄與 Security Command Center 的 OPEN_SSH_PORT finding 在 GCP 上的處理">GCP VM 的 SSH 與 RDP 管理通道&lt;/a> — GCP IAP 的連線路徑、IAM 授權範圍與切換順序；實際指令在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/#%e7%b6%b2%e8%b7%af%e5%8f%af%e9%81%94%e6%80%a7%e4%b8%bb%e6%a9%9f%e5%88%86%e5%b8%83%e8%88%87%e4%bb%a3%e7%90%86%e6%8a%8a%e9%80%a3%e7%b7%9a%e9%80%81%e9%80%b2%e4%b8%bb%e6%a9%9f%e7%9a%84%e6%96%b9%e5%bc%8f" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a> — 從代理位址段連入與代理程式反向連線兩種送達方式，依主機分布的選擇條件&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>身分感知代理（identity-aware proxy）是一個放在網際網路與內部主機之間的 <a href="/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy</a>：它接收外部連線，先向組織的 <a href="/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider</a> 確認發起連線的帳號、再依授權規則決定是否替這條連線開到後端。通過的連線送到後端的方式有兩種：由代理從它自己的固定位址段連到主機的內部 IP（GCP 的 IAP），這時主機的防火牆只放行那段代理位址；或經由主機上的代理程式（connector，例如 <code>cloudflared</code>）主動往外建立的反向連線送進去（Cloudflare Tunnel、Teleport agent），這時主機不開任何入站 port。兩種方式下主機都不必持有公開 IP。它是 <a href="/blog/backend/knowledge-cards/zero-trust/" data-link-title="Zero Trust（零信任）" data-link-desc="判斷內網或 VPN 裡的服務能不能因為連線來自內部就省掉認證、或要評估一條連線該放行到什麼範圍時，查「每一次連線各自通過身分與授權」這個原則的內容與導入代價">Zero Trust（零信任）</a> 原則在存取入口上的一種實作：放行的依據是每一次連線的身分，網路位置不構成信任的理由。</p>
<h2 id="概念位置">概念位置</h2>
<p>管理通道拆成網路可達性、通道授權、主機登入驗證三層來看時（模型見<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>），身分感知代理同時承擔網路可達性與通道授權：sshd 只對代理可見，而能不能開通道由代理用 IdP 帳號判斷——這個判斷就是通道授權，指的是決定「這個人能不能打開通往主機的連線」，與登入主機時驗證 SSH 金鑰是兩回事。主機登入驗證在多數實作裡仍由主機上的 sshd 負責；Teleport 的節點這類實作用自己內建的 SSH 伺服器與代理簽發的憑證，主機登入驗證跟著換掉。由 sshd 負責時，金鑰的發放方式另見 <a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a> 與 <a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）</a>。</p>
<p>它與 <a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> 都讓內部主機不必各自對外，差別在對外接收連線的元件由誰維運、用什麼判斷身分：跳板機是自己維運的主機、用 SSH 金鑰判斷；身分感知代理是廠商提供或自己架設的代理服務、用 IdP 帳號判斷。它與一般反向代理的差別在轉送前多了一次身分驗證與授權。</p>
<p>常見的實作有 GCP 的 IAP（Identity-Aware Proxy，對 HTTPS 應用與 TCP 連線各有一種模式，TCP forwarding 的來源位址段是 <code>35.235.240.0/20</code>，到得了同一家雲端裡的主機，以及經 Cloud Interconnect 或 Cloud VPN 接進來、而且 Cloud Router 對地端通告了這段位址的地端主機）、Cloudflare Access、Teleport 的 proxy。Cloudflare Access 與 Teleport 用反向連線把流量送進主機，主機上的 connector 怎麼往外連、斷線時會怎樣見 <a href="/blog/backend/knowledge-cards/outbound-tunnel/" data-link-title="Outbound Tunnel" data-link-desc="反向隧道把出站連線轉成可達入口、與傳統 port-forward 的責任倒轉">Outbound Tunnel（出站隧道）</a>。這個模式的公開源頭是 Google 2014 年起發表的 BeyondCorp 系列論文。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>由代理連到內部 IP 的模式（GCP IAP）：</p>
<ul>
<li>主機的入站規則只放行某一段固定的代理位址，沒有任何來源是 <code>0.0.0.0/0</code> 的管理 port 規則</li>
<li>主機上的認證紀錄裡，SSH 連線的來源 IP 全部落在代理的位址段，看不出是哪個人——要查人得看代理或 IdP 的 audit log</li>
</ul>
<p>經由代理程式反向連線的模式（Cloudflare Tunnel、Teleport agent）：</p>
<ul>
<li>主機上有一個常駐的代理程式（例如 <code>cloudflared</code>、<code>teleport</code>），主機的入站規則完全沒有管理 port</li>
<li>代理程式把連線轉送到 sshd 的實作（<code>cloudflared</code>），認證紀錄裡的 SSH 來源是 <code>127.0.0.1</code> 或代理程式所在的本機位址；Teleport 節點用自己的 SSH 伺服器，sshd 的紀錄裡不會出現這些連線</li>
</ul>
<p>兩種模式共同的樣子是主機沒有公開 IP，管理員仍然用 <code>ssh</code> 或廠商 CLI 連得進去。</p>
<h2 id="設計責任">設計責任</h2>
<p>導入身分感知代理之後，主機自己不再判斷連線者是誰，下面這些判斷移到代理與 IdP 上：</p>
<ul>
<li><strong>IdP 帳號的安全強度</strong>：存取權限轉移到帳號上之後，帳號被釣魚等於通道被打開，所以 IdP 端要強制多因素驗證，管理員帳號優先用防釣魚的硬體金鑰或 <a href="/blog/backend/knowledge-cards/passkey/" data-link-title="Passkey" data-link-desc="要用金鑰對取代密碼、或判斷已導入的 passkey 實際擋掉了什麼時，用來定位它的保護落在哪一段、上限由哪條路徑決定">Passkey</a></li>
<li><strong>授權範圍</strong>：授權綁給負責維運的群組，並限定可以連的 port 與主機，避免整個組織的帳號都拿得到通道</li>
<li><strong>代理的 session 長度</strong>：IdP 停用一個帳號之後，代理已經簽給他的 session 或憑證要到期或被撤銷才失效（Cloudflare Access 的 session 最長可以設到一個月，<code>gcloud</code> 在本機保存 refresh token），所以收回延遲的上限是代理端設定的 session 長度；處理離職或帳號外洩時另外在代理端撤銷 session。登入後留在本機的 token 檔可以被複製重放，不會再過一次多因素驗證，session 越短、或綁定裝置，被複製之後能用的時間越短</li>
<li><strong>代理的可用性與延遲</strong>：所有位元組都經過代理轉送，代理服務中斷時管理通道一起中斷，大量傳檔也比直連慢；救援時的備用路徑見 <a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）</a></li>
<li><strong>稽核紀錄的位置與開啟時機</strong>：主機端只看得到代理的位址，「誰在什麼時候連進哪台主機」要從代理或 IdP 的 audit log 取得。這份紀錄只記錄開啟之後的連線，事後要調查一次入侵時，開啟之前的連線查不回來，所以在切換到代理的同一天開啟，開啟之後實際連一次、查到這次連線的紀錄才算開對（見<a href="/blog/report/protection-proves-itself-by-firing/" data-link-title="平常不動作的防護機制的部署驗證：設定被接受、元件狀態正常、執行失敗被吞掉與測試從中途注入這些提早出現的成功訊號，驗證查詢與處置指令的假陰性，以及合成觸發本身有損害時的分段驗證" data-link-desc="部署停損開關、稽核紀錄、告警或自動處置這類平常不動作的防護機制之後，要判斷它算不算部署完成時讀；也用在驗收紀錄寫著「部署成功」「狀態 ACTIVE」「設定已套用」、或驗證查詢回傳零筆就被當成結論的時候">平常不動作的防護機制的部署驗證</a>）</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> — 同樣收斂對外入口，由自己維運的主機轉接</li>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — 放行代理位址段的那一條入站規則所在的位置；AWS 叫 security group，GCP 對應的是 VPC 防火牆規則</li>
<li><a href="/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider</a> — 代理用來確認帳號的系統</li>
<li><a href="/blog/devops/infra/cloud-providers/gcp/gcp-iap-ssh-access/" data-link-title="GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding" data-link-desc="GCP 預設網路自帶的防火牆規則與它們留下的暴露面、IAP TCP forwarding 的連線路徑與日常連線指令、切換到 IAP 的順序為什麼這樣排、IAP 通道連不上時的依層判讀，以及 IAM 授權範圍、OS Login 的啟用條件與工作階段的結束、拿掉外部 IP 之後的出站路徑、Organization Policy、階層式防火牆、稽核紀錄與 Security Command Center 的 OPEN_SSH_PORT finding 在 GCP 上的處理">GCP VM 的 SSH 與 RDP 管理通道</a> — GCP IAP 的連線路徑、IAM 授權範圍與切換順序；實際指令在 <a href="/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄</a></li>
<li><a href="/blog/devops/infra/03-network-foundation/management-access-channel/#%e7%b6%b2%e8%b7%af%e5%8f%af%e9%81%94%e6%80%a7%e4%b8%bb%e6%a9%9f%e5%88%86%e5%b8%83%e8%88%87%e4%bb%a3%e7%90%86%e6%8a%8a%e9%80%a3%e7%b7%9a%e9%80%81%e9%80%b2%e4%b8%bb%e6%a9%9f%e7%9a%84%e6%96%b9%e5%bc%8f" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a> — 從代理位址段連入與代理程式反向連線兩種送達方式，依主機分布的選擇條件</li>
</ul>
]]></content:encoded></item><item><title>Bastion Host（跳板機）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/</guid><description>&lt;p>管理員要登入 private &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> 裡的主機時，跳板機（bastion host，也稱 jump host；其他中文資料常寫作「堡壘主機」）是常見的中繼點：一台放在公開網段、只負責轉接管理連線的主機。其他主機的防火牆只接受來自跳板機的 SSH，管理員先連上跳板機，再從那裡連到目的主機。OpenSSH 用 &lt;code>ssh -J &amp;lt;bastion-host&amp;gt; &amp;lt;target-host&amp;gt;&lt;/code>（設定檔裡的 &lt;code>ProxyJump&lt;/code>）一次完成兩段連線，私鑰留在管理員自己的電腦上，不必複製到跳板機。&lt;/p>
&lt;p>跳板機處理的是主機以 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 登入管理的情境：每台主機上跑著接受 SSH 連線的服務程式 sshd、預設監聽 22 port。只部署在 PaaS 或 serverless 平台、沒有任何可以 SSH 登入的主機時，平台自己的主控台與 CLI 就是管理入口，用不到跳板機。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>把管理通道拆成網路可達性、通道授權、主機登入驗證三層時（見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>），跳板機是網路可達性的做法之一：它把「每台主機都對外開 22 port」收斂成「只有一台對外開」。它同時兼任通道授權——決定誰能打開通往主機的連線——判斷依據是跳板機上的 SSH 金鑰，或跳板機信任的 SSH CA 所簽的憑證；跳板機與目的主機接受同一把金鑰時，一把私鑰外洩就同時通過通道授權與主機登入驗證。&lt;/p>
&lt;p>跳板機放在 public subnet，目的主機留在 private subnet，兩者之間的放行用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> 的來源引用表達：目的主機的入站規則把來源寫成「跳板機所屬的那個 security group」而不是一個 IP，跳板機換了 IP 或多加一台，規則都不必改。&lt;/p>
&lt;p>暴露面收斂成一台，但沒有消失：跳板機仍然對網際網路開著管理 port，它的 sshd 漏洞與金鑰管理就是整個環境的入口。不讓任何自管主機對外的做法是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a>，對外接收連線的那一層改由代理服務承擔。雲端廠商也提供受管的跳板服務（例如 Azure Bastion、Oracle Cloud 的 Bastion），主機的修補由廠商負責，連線方式與身分驗證依各家服務而定，位置介於自建跳板機與身分感知代理之間。Oracle Cloud 的 Bastion 建立時要指定一個目標 subnet（主機所在的 subnet，或主機所在 subnet 接受其流量的另一個 subnet），經由它登入的主機不需要公開 IP，誰能開工作階段由 IAM 政策決定，設定方式見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務&lt;/a> 的〈不放金鑰時的登入服務〉。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>private subnet 主機的 security group 只允許來自某一個 security group（或某一台主機）的 22 port&lt;/li>
&lt;li>管理員的 &lt;code>~/.ssh/config&lt;/code> 裡有 &lt;code>ProxyJump&lt;/code> 設定，或團隊文件寫著「先連 bastion」&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>自建跳板機時，整個環境對外的 SSH 暴露都集中在這一台上，所以下面這幾件事的標準比一般主機高：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>跳板機的來源範圍&lt;/strong>：管理員有固定的對外 IP 時，跳板機的 22 port 只放行那幾個 IP；遠端工作、IP 會變動時，白名單縮不下來，跳板機的 22 port 實質上對全網開放，認證紀錄裡會持續出現來自陌生 IP 的失敗登入，它的 sshd 修補速度就是整個環境的暴露期。只放行管理員 IP 時，ISP 重新分配了那個 IP，管理員就連不進跳板機，舊 IP 的新使用者反而在放行範圍裡；這一類會在主機外面改變的設定怎麼定期比對，見 &lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/long-running-host-maintenance/" data-link-title="長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算" data-link-desc="單台 Linux 主機（VPS、雲端主機）長期上線時，沒有人操作也會改變的狀態與維護對它們做的事：log 與資料的寫入者和清理機制（logrotate、journald、容器 log）、磁碟寫滿時各元件的反應、每份 log 要讀的欄位、入口在主機與雲端各層的盤點（ss）、各條更新管道的安全更新與重開機（dnf-automatic、unattended-upgrades、needs-restarting）、會到期與會改變的設定、檢查間隔的推算，以及接手一台已上線的主機時先處理哪幾項">長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算&lt;/a>〈會到期與會改變的外部設定〉&lt;/li>
&lt;li>&lt;strong>私鑰不落地&lt;/strong>：agent forwarding 是讓遠端主機借用管理員本機 ssh-agent 裡的私鑰去做下一段認證的功能（&lt;code>ssh -A&lt;/code> 或設定檔的 &lt;code>ForwardAgent yes&lt;/code>），開著它時，任何在跳板機上取得 root 的人都能借那把私鑰連到其他主機；把私鑰直接放上跳板機的後果相同。&lt;code>ProxyJump&lt;/code> 讓第二段認證也在管理員電腦上完成，不需要 agent forwarding。這種從一台主機往其他主機擴散的攻擊見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/lateral-movement/" data-link-title="Lateral Movement（橫向移動）" data-link-desc="評估一台主機被入侵之後攻擊者還連得到哪些機器、內網或 VPN 裡的可達範圍要收到多窄時，查攻擊者以已經拿下的機器為起點往內擴散的方式">Lateral Movement（橫向移動）&lt;/a>&lt;/li>
&lt;li>&lt;strong>金鑰不共用、不可複製&lt;/strong>：跳板機與目的主機各用不同的金鑰或憑證，讓一把私鑰外洩只通過一層；目的主機的金鑰發放可以改用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）&lt;/a> 或 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login&lt;/a>；跳板機與目的主機信任同一把 SSH CA 時，用不同的 principal 把兩層分開，做法見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入&lt;/a>。管理員的私鑰放在硬體安全金鑰裡（OpenSSH 8.2 起的 &lt;code>ed25519-sk&lt;/code>）時，私鑰檔無法被複製走&lt;/li>
&lt;li>&lt;strong>修補與監控&lt;/strong>：跳板機要比其他主機更快套用 sshd 的安全更新，並保留連線紀錄&lt;/li>
&lt;li>&lt;strong>操作紀錄&lt;/strong>：外包人員登入正式環境、或稽核要求逐條指令留紀錄時，需要集中錄製管理員在目的主機上執行了什麼；所有管理連線都經過跳板機，所以它是放錄製工具的位置。這個屬性與廠商無關，一套設定涵蓋多雲與地端混用的環境。組織有 IdP 時，讓跳板機只接受 IdP 簽發的短期 SSH 憑證，錄製與隨帳號收回兩者都保留&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> — 跳板機轉接的協定與金鑰認證&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a> — 改用代理服務承擔對外接收連線&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）&lt;/a> — 跳板機本身連不上時的備用登入路徑&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/#%e9%80%9a%e9%81%93%e6%8e%88%e6%ac%8a%e7%9a%84%e8%ba%ab%e5%88%86%e4%be%86%e6%ba%90%e5%ad%98%e5%8f%96%e8%80%85%e8%88%87%e4%b8%bb%e6%a9%9f%e6%93%81%e6%9c%89%e8%80%85%e7%9a%84%e9%97%9c%e4%bf%82%e8%ba%ab%e5%88%86%e6%89%80%e5%9c%a8%e7%9a%84%e5%b8%b3%e8%99%9f%e7%b3%bb%e7%b5%b1" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a> — 存取者是組織成員、外部合作方、個人或機器時，跳板機與其他做法的選擇條件&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>管理員要登入 private <a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> 裡的主機時，跳板機（bastion host，也稱 jump host；其他中文資料常寫作「堡壘主機」）是常見的中繼點：一台放在公開網段、只負責轉接管理連線的主機。其他主機的防火牆只接受來自跳板機的 SSH，管理員先連上跳板機，再從那裡連到目的主機。OpenSSH 用 <code>ssh -J &lt;bastion-host&gt; &lt;target-host&gt;</code>（設定檔裡的 <code>ProxyJump</code>）一次完成兩段連線，私鑰留在管理員自己的電腦上，不必複製到跳板機。</p>
<p>跳板機處理的是主機以 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 登入管理的情境：每台主機上跑著接受 SSH 連線的服務程式 sshd、預設監聽 22 port。只部署在 PaaS 或 serverless 平台、沒有任何可以 SSH 登入的主機時，平台自己的主控台與 CLI 就是管理入口，用不到跳板機。</p>
<h2 id="概念位置">概念位置</h2>
<p>把管理通道拆成網路可達性、通道授權、主機登入驗證三層時（見<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>），跳板機是網路可達性的做法之一：它把「每台主機都對外開 22 port」收斂成「只有一台對外開」。它同時兼任通道授權——決定誰能打開通往主機的連線——判斷依據是跳板機上的 SSH 金鑰，或跳板機信任的 SSH CA 所簽的憑證；跳板機與目的主機接受同一把金鑰時，一把私鑰外洩就同時通過通道授權與主機登入驗證。</p>
<p>跳板機放在 public subnet，目的主機留在 private subnet，兩者之間的放行用 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> 的來源引用表達：目的主機的入站規則把來源寫成「跳板機所屬的那個 security group」而不是一個 IP，跳板機換了 IP 或多加一台，規則都不必改。</p>
<p>暴露面收斂成一台，但沒有消失：跳板機仍然對網際網路開著管理 port，它的 sshd 漏洞與金鑰管理就是整個環境的入口。不讓任何自管主機對外的做法是 <a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a>，對外接收連線的那一層改由代理服務承擔。雲端廠商也提供受管的跳板服務（例如 Azure Bastion、Oracle Cloud 的 Bastion），主機的修補由廠商負責，連線方式與身分驗證依各家服務而定，位置介於自建跳板機與身分感知代理之間。Oracle Cloud 的 Bastion 建立時要指定一個目標 subnet（主機所在的 subnet，或主機所在 subnet 接受其流量的另一個 subnet），經由它登入的主機不需要公開 IP，誰能開工作階段由 IAM 政策決定，設定方式見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>private subnet 主機的 security group 只允許來自某一個 security group（或某一台主機）的 22 port</li>
<li>管理員的 <code>~/.ssh/config</code> 裡有 <code>ProxyJump</code> 設定，或團隊文件寫著「先連 bastion」</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>自建跳板機時，整個環境對外的 SSH 暴露都集中在這一台上，所以下面這幾件事的標準比一般主機高：</p>
<ul>
<li><strong>跳板機的來源範圍</strong>：管理員有固定的對外 IP 時，跳板機的 22 port 只放行那幾個 IP；遠端工作、IP 會變動時，白名單縮不下來，跳板機的 22 port 實質上對全網開放，認證紀錄裡會持續出現來自陌生 IP 的失敗登入，它的 sshd 修補速度就是整個環境的暴露期。只放行管理員 IP 時，ISP 重新分配了那個 IP，管理員就連不進跳板機，舊 IP 的新使用者反而在放行範圍裡；這一類會在主機外面改變的設定怎麼定期比對，見 <a href="/blog/devops/operations/09-host-lifecycle/long-running-host-maintenance/" data-link-title="長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算" data-link-desc="單台 Linux 主機（VPS、雲端主機）長期上線時，沒有人操作也會改變的狀態與維護對它們做的事：log 與資料的寫入者和清理機制（logrotate、journald、容器 log）、磁碟寫滿時各元件的反應、每份 log 要讀的欄位、入口在主機與雲端各層的盤點（ss）、各條更新管道的安全更新與重開機（dnf-automatic、unattended-upgrades、needs-restarting）、會到期與會改變的設定、檢查間隔的推算，以及接手一台已上線的主機時先處理哪幾項">長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算</a>〈會到期與會改變的外部設定〉</li>
<li><strong>私鑰不落地</strong>：agent forwarding 是讓遠端主機借用管理員本機 ssh-agent 裡的私鑰去做下一段認證的功能（<code>ssh -A</code> 或設定檔的 <code>ForwardAgent yes</code>），開著它時，任何在跳板機上取得 root 的人都能借那把私鑰連到其他主機；把私鑰直接放上跳板機的後果相同。<code>ProxyJump</code> 讓第二段認證也在管理員電腦上完成，不需要 agent forwarding。這種從一台主機往其他主機擴散的攻擊見 <a href="/blog/backend/knowledge-cards/lateral-movement/" data-link-title="Lateral Movement（橫向移動）" data-link-desc="評估一台主機被入侵之後攻擊者還連得到哪些機器、內網或 VPN 裡的可達範圍要收到多窄時，查攻擊者以已經拿下的機器為起點往內擴散的方式">Lateral Movement（橫向移動）</a></li>
<li><strong>金鑰不共用、不可複製</strong>：跳板機與目的主機各用不同的金鑰或憑證，讓一把私鑰外洩只通過一層；目的主機的金鑰發放可以改用 <a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）</a> 或 <a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a>；跳板機與目的主機信任同一把 SSH CA 時，用不同的 principal 把兩層分開，做法見 <a href="/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入</a>。管理員的私鑰放在硬體安全金鑰裡（OpenSSH 8.2 起的 <code>ed25519-sk</code>）時，私鑰檔無法被複製走</li>
<li><strong>修補與監控</strong>：跳板機要比其他主機更快套用 sshd 的安全更新，並保留連線紀錄</li>
<li><strong>操作紀錄</strong>：外包人員登入正式環境、或稽核要求逐條指令留紀錄時，需要集中錄製管理員在目的主機上執行了什麼；所有管理連線都經過跳板機，所以它是放錄製工具的位置。這個屬性與廠商無關，一套設定涵蓋多雲與地端混用的環境。組織有 IdP 時，讓跳板機只接受 IdP 簽發的短期 SSH 憑證，錄製與隨帳號收回兩者都保留</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> — 跳板機轉接的協定與金鑰認證</li>
<li><a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a> — 改用代理服務承擔對外接收連線</li>
<li><a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）</a> — 跳板機本身連不上時的備用登入路徑</li>
<li><a href="/blog/devops/infra/03-network-foundation/management-access-channel/#%e9%80%9a%e9%81%93%e6%8e%88%e6%ac%8a%e7%9a%84%e8%ba%ab%e5%88%86%e4%be%86%e6%ba%90%e5%ad%98%e5%8f%96%e8%80%85%e8%88%87%e4%b8%bb%e6%a9%9f%e6%93%81%e6%9c%89%e8%80%85%e7%9a%84%e9%97%9c%e4%bf%82%e8%ba%ab%e5%88%86%e6%89%80%e5%9c%a8%e7%9a%84%e5%b8%b3%e8%99%9f%e7%b3%bb%e7%b5%b1" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a> — 存取者是組織成員、外部合作方、個人或機器時，跳板機與其他做法的選擇條件</li>
</ul>
]]></content:encoded></item><item><title>SSH Certificate（SSH 憑證）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/</guid><description>&lt;p>SSH 憑證（SSH certificate）是由一把 SSH CA 私鑰簽過名的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 使用者公鑰，簽名裡帶著有效期間、允許登入的帳號名稱（principal）與識別字串。主機不再逐一記錄每個人的公鑰，只信任 CA 的公鑰：sshd 設定 &lt;code>TrustedUserCAKeys&lt;/code> 指向 CA 公鑰之後，任何帶著有效 CA 簽名、而且還在有效期內的憑證都能登入它列出的帳號。OpenSSH 從 5.4 版起內建這個機制，格式是 OpenSSH 自己的，與網站用的 X.509 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/certificate/" data-link-title="Certificate（數位憑證）" data-link-desc="文件裡的「憑證」要分辨指的是數位憑證還是存取憑證，或要判斷 TLS、mTLS、程式碼簽章用的憑證外洩之後要撤銷什麼時，查數位憑證由哪些部分組成、哪一部分是秘密">憑證&lt;/a>不相通。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>SSH 憑證處理的是登入主機用的金鑰怎麼發、怎麼失效——&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>把這一層叫主機登入驗證。它與另外兩種金鑰發放方式的差別在權限怎麼失效：&lt;/p>
&lt;ul>
&lt;li>靜態 &lt;code>authorized_keys&lt;/code>（見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a>）：沒有到期時間，收回要逐台刪除&lt;/li>
&lt;li>由中央服務決定可用的公鑰（見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login&lt;/a>）：帳號或角色移除，下一次登入就失敗&lt;/li>
&lt;li>SSH 憑證：憑證到期自動失效，離職或權限變更最晚在一個有效期之後生效&lt;/li>
&lt;/ul>
&lt;p>簽發服務通常接在 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider&lt;/a> 後面：管理員用 IdP 帳號登入簽發服務，換到一張短期憑證。管理通道前面那一層的通道授權（決定誰能打開通往主機的連線，例如身分感知代理檢查 IdP 帳號）若也用同一個 IdP，兩層就共用同一個身分來源，停用一個帳號同時擋下兩層。HashiCorp Vault 的 SSH secrets engine 與 Teleport 都以這種方式運作。三種做法收回的都是下一次登入，已經建立的工作階段要另外在主機上中斷。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>主機的 &lt;code>sshd_config&lt;/code> 有 &lt;code>TrustedUserCAKeys&lt;/code>，而 &lt;code>~/.ssh/authorized_keys&lt;/code> 是空的或不存在&lt;/li>
&lt;li>管理員的 &lt;code>~/.ssh/&lt;/code> 裡在金鑰對旁邊多了一個同名加 &lt;code>-cert.pub&lt;/code> 結尾的檔（例如 &lt;code>id_ed25519-cert.pub&lt;/code>），&lt;code>ssh&lt;/code> 連線時會自動帶上它；用 &lt;code>ssh-keygen -L -f &amp;lt;cert-file&amp;gt;&lt;/code> 讀得出它的有效期間與 principal&lt;/li>
&lt;li>每天第一次 SSH 之前要先跑一個登入或簽發指令&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>CA 私鑰取代了每台主機上的公鑰清單，所以導入 SSH 憑證時的決定都圍繞著 CA 與憑證內容：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>CA 私鑰的保管&lt;/strong>：CA 私鑰能簽出登入所有主機的憑證，它外洩的影響等於所有主機的所有金鑰一起外洩，所以放在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vault/" data-link-title="HashiCorp Vault" data-link-desc="機密管理系統，集中存放密碼、API key、TLS 私鑰，提供存取控制、稽核和自動輪替">HashiCorp Vault&lt;/a> 或硬體安全模組這類受控位置，不放在任何人的電腦上&lt;/li>
&lt;li>&lt;strong>有效期長度&lt;/strong>：有效期等於能接受的最晚收回延遲——離職或權限移除之後，那張憑證最多還能用一個有效期。延遲上限越短，有效期越短，管理員重新簽發也越頻繁；管理員手動簽發時常設成一個工作日左右，讓每天開工時簽一次；簽發由用戶端在連線時自動完成時（Teleport 的 &lt;code>tsh&lt;/code>、smallstep 的 &lt;code>step ssh&lt;/code>），有效期可以縮到幾小時以內，收回延遲跟著縮短&lt;/li>
&lt;li>&lt;strong>principal 的對應&lt;/strong>：憑證列出的帳號名稱與主機上的帳號怎麼對應，決定一張憑證能登入哪些主機的哪些帳號。主機上只有一個共用帳號（例如雲端映像預設的 &lt;code>ubuntu&lt;/code>）時，每張憑證的 principal 都是 &lt;code>ubuntu&lt;/code>，主機端分不出是誰登入，要靠憑證的識別字串（簽發時的 &lt;code>-I&lt;/code>）出現在 sshd 的認證紀錄裡來辨識人&lt;/li>
&lt;li>&lt;strong>提前撤銷&lt;/strong>：憑證還沒到期就要失效時（例如私鑰外洩），sshd 用 &lt;code>RevokedKeys&lt;/code> 指向撤銷清單，這份清單要能派送到所有主機&lt;/li>
&lt;li>&lt;strong>主機憑證&lt;/strong>：同一個機制也能反過來簽主機的金鑰（&lt;code>HostCertificate&lt;/code>），讓用戶端驗證主機身分。主機經常重建（autoscaling、重新部署）時，每次重建主機指紋都會變，用戶端會反覆看到指紋變更的警告而養成直接接受的習慣；改用主機憑證後用戶端只信任 CA，前提是用戶端的 &lt;code>known_hosts&lt;/code> 已加入 &lt;code>@cert-authority&lt;/code> 與 CA 公鑰&lt;/li>
&lt;/ul>
&lt;p>簽發的指令形式是 &lt;code>ssh-keygen -s &amp;lt;ca-private-key&amp;gt; -I &amp;lt;identity&amp;gt; -n &amp;lt;principals&amp;gt; -V &amp;lt;validity&amp;gt; &amp;lt;user-public-key&amp;gt;&lt;/code>：&lt;code>-I&lt;/code> 是寫進憑證、會出現在主機認證紀錄裡的識別字串（通常填 IdP 帳號），&lt;code>-n&lt;/code> 是允許登入的帳號名稱、多個用逗號分隔，&lt;code>-V&lt;/code> 是有效期間（&lt;code>+8h&lt;/code> 表示從現在起八小時）。實際運作時由簽發服務代為執行，簽出的憑證寫在使用者公鑰旁邊的 &lt;code>*-cert.pub&lt;/code>。&lt;/p></description><content:encoded><![CDATA[<p>SSH 憑證（SSH certificate）是由一把 SSH CA 私鑰簽過名的 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 使用者公鑰，簽名裡帶著有效期間、允許登入的帳號名稱（principal）與識別字串。主機不再逐一記錄每個人的公鑰，只信任 CA 的公鑰：sshd 設定 <code>TrustedUserCAKeys</code> 指向 CA 公鑰之後，任何帶著有效 CA 簽名、而且還在有效期內的憑證都能登入它列出的帳號。OpenSSH 從 5.4 版起內建這個機制，格式是 OpenSSH 自己的，與網站用的 X.509 <a href="/blog/backend/knowledge-cards/certificate/" data-link-title="Certificate（數位憑證）" data-link-desc="文件裡的「憑證」要分辨指的是數位憑證還是存取憑證，或要判斷 TLS、mTLS、程式碼簽章用的憑證外洩之後要撤銷什麼時，查數位憑證由哪些部分組成、哪一部分是秘密">憑證</a>不相通。</p>
<h2 id="概念位置">概念位置</h2>
<p>SSH 憑證處理的是登入主機用的金鑰怎麼發、怎麼失效——<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>把這一層叫主機登入驗證。它與另外兩種金鑰發放方式的差別在權限怎麼失效：</p>
<ul>
<li>靜態 <code>authorized_keys</code>（見 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a>）：沒有到期時間，收回要逐台刪除</li>
<li>由中央服務決定可用的公鑰（見 <a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a>）：帳號或角色移除，下一次登入就失敗</li>
<li>SSH 憑證：憑證到期自動失效，離職或權限變更最晚在一個有效期之後生效</li>
</ul>
<p>簽發服務通常接在 <a href="/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider</a> 後面：管理員用 IdP 帳號登入簽發服務，換到一張短期憑證。管理通道前面那一層的通道授權（決定誰能打開通往主機的連線，例如身分感知代理檢查 IdP 帳號）若也用同一個 IdP，兩層就共用同一個身分來源，停用一個帳號同時擋下兩層。HashiCorp Vault 的 SSH secrets engine 與 Teleport 都以這種方式運作。三種做法收回的都是下一次登入，已經建立的工作階段要另外在主機上中斷。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>主機的 <code>sshd_config</code> 有 <code>TrustedUserCAKeys</code>，而 <code>~/.ssh/authorized_keys</code> 是空的或不存在</li>
<li>管理員的 <code>~/.ssh/</code> 裡在金鑰對旁邊多了一個同名加 <code>-cert.pub</code> 結尾的檔（例如 <code>id_ed25519-cert.pub</code>），<code>ssh</code> 連線時會自動帶上它；用 <code>ssh-keygen -L -f &lt;cert-file&gt;</code> 讀得出它的有效期間與 principal</li>
<li>每天第一次 SSH 之前要先跑一個登入或簽發指令</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>CA 私鑰取代了每台主機上的公鑰清單，所以導入 SSH 憑證時的決定都圍繞著 CA 與憑證內容：</p>
<ul>
<li><strong>CA 私鑰的保管</strong>：CA 私鑰能簽出登入所有主機的憑證，它外洩的影響等於所有主機的所有金鑰一起外洩，所以放在 <a href="/blog/devops/infra/knowledge-cards/vault/" data-link-title="HashiCorp Vault" data-link-desc="機密管理系統，集中存放密碼、API key、TLS 私鑰，提供存取控制、稽核和自動輪替">HashiCorp Vault</a> 或硬體安全模組這類受控位置，不放在任何人的電腦上</li>
<li><strong>有效期長度</strong>：有效期等於能接受的最晚收回延遲——離職或權限移除之後，那張憑證最多還能用一個有效期。延遲上限越短，有效期越短，管理員重新簽發也越頻繁；管理員手動簽發時常設成一個工作日左右，讓每天開工時簽一次；簽發由用戶端在連線時自動完成時（Teleport 的 <code>tsh</code>、smallstep 的 <code>step ssh</code>），有效期可以縮到幾小時以內，收回延遲跟著縮短</li>
<li><strong>principal 的對應</strong>：憑證列出的帳號名稱與主機上的帳號怎麼對應，決定一張憑證能登入哪些主機的哪些帳號。主機上只有一個共用帳號（例如雲端映像預設的 <code>ubuntu</code>）時，每張憑證的 principal 都是 <code>ubuntu</code>，主機端分不出是誰登入，要靠憑證的識別字串（簽發時的 <code>-I</code>）出現在 sshd 的認證紀錄裡來辨識人</li>
<li><strong>提前撤銷</strong>：憑證還沒到期就要失效時（例如私鑰外洩），sshd 用 <code>RevokedKeys</code> 指向撤銷清單，這份清單要能派送到所有主機</li>
<li><strong>主機憑證</strong>：同一個機制也能反過來簽主機的金鑰（<code>HostCertificate</code>），讓用戶端驗證主機身分。主機經常重建（autoscaling、重新部署）時，每次重建主機指紋都會變，用戶端會反覆看到指紋變更的警告而養成直接接受的習慣；改用主機憑證後用戶端只信任 CA，前提是用戶端的 <code>known_hosts</code> 已加入 <code>@cert-authority</code> 與 CA 公鑰</li>
</ul>
<p>簽發的指令形式是 <code>ssh-keygen -s &lt;ca-private-key&gt; -I &lt;identity&gt; -n &lt;principals&gt; -V &lt;validity&gt; &lt;user-public-key&gt;</code>：<code>-I</code> 是寫進憑證、會出現在主機認證紀錄裡的識別字串（通常填 IdP 帳號），<code>-n</code> 是允許登入的帳號名稱、多個用逗號分隔，<code>-V</code> 是有效期間（<code>+8h</code> 表示從現在起八小時）。實際運作時由簽發服務代為執行，簽出的憑證寫在使用者公鑰旁邊的 <code>*-cert.pub</code>。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> — 靜態金鑰認證與 <code>authorized_keys</code></li>
<li><a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a> — 另一種讓登入權限隨帳號收回的做法</li>
<li><a href="/blog/devops/infra/knowledge-cards/ssh-host-key/" data-link-title="SSH Host Key 與 known_hosts（主機金鑰）" data-link-desc="ssh 第一次連線問要不要信任指紋、出現 REMOTE HOST IDENTIFICATION HAS CHANGED 警告，或主機重建之後連不上時，查 host key 怎麼證明主機身分、known_hosts 記了什麼，以及首次信任的風險">SSH Host Key 與 known_hosts（主機金鑰）</a> — 主機憑證取代的逐台指紋確認</li>
<li><a href="/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入</a> — 產生 CA、設定 sshd、簽發與撤銷的指令，以及 AuthorizedPrincipalsFile 讀取權限與 RevokedKeys 檔案不存在這兩個會讓所有人登不進去的設定</li>
<li><a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> — 跳板機與目的主機不共用金鑰時，目的主機這一段可以改用憑證</li>
<li><a href="/blog/devops/infra/03-network-foundation/management-access-channel/#%e4%b8%bb%e6%a9%9f%e7%99%bb%e5%85%a5%e9%a9%97%e8%ad%89%e7%99%bb%e5%85%a5%e6%ac%8a%e9%99%90%e7%9a%84%e6%94%b6%e5%9b%9e%e9%9d%a0%e4%ba%ba%e5%b7%a5%e5%88%aa%e9%99%a4%e5%b8%b3%e8%99%9f%e9%80%a3%e5%8b%95%e6%88%96%e5%88%b0%e6%9c%9f" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a> — 人員流動、主機數量與帳號系統不同時，三種金鑰發放方式的選擇條件</li>
</ul>
]]></content:encoded></item><item><title>OS Login</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/</guid><description>&lt;p>OS Login 是 GCP Compute Engine 管理 VM 登入權限的機制：VM 在有人登入時，向 Google 查詢這個 Google 帳號的 SSH 公鑰與它在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> 裡的角色，依角色決定是否允許登入、是否允許使用 sudo。持有 &lt;code>roles/compute.osLogin&lt;/code> 的帳號以一般使用者登入，持有 &lt;code>roles/compute.osAdminLogin&lt;/code> 的帳號另外拿得到 sudo。帳號停用或角色移除之後，下一次登入就會失敗，不必回到每台 VM 刪除公鑰；已經建立的工作階段不受影響，要另外在 VM 上結束（&lt;code>loginctl terminate-user &amp;lt;username&amp;gt;&lt;/code>），結束已建立工作階段的動作在應用程式的層次叫 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/session-invalidation/" data-link-title="Session Invalidation" data-link-desc="說明事件後如何讓既有會話失效，避免被重放或延續利用">session invalidation&lt;/a>。啟用方式是在專案或 VM 的 metadata 設定 &lt;code>enable-oslogin=TRUE&lt;/code>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>OS Login 出現之前，GCP 把 SSH 公鑰交給 VM 的方式是 metadata 金鑰：公鑰寫在專案或 VM 的 metadata 的 &lt;code>ssh-keys&lt;/code> 欄位，VM 上的 guest agent（GCP 映像預裝、負責把 metadata 套用到作業系統的常駐程式）讀出來寫進對應帳號的 &lt;code>authorized_keys&lt;/code>。這些公鑰和 Google 帳號沒有連動，帳號停用後公鑰仍然留在 metadata 裡，運作方式等同 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 卡描述的靜態公鑰。&lt;/p>
&lt;p>OS Login 改成每次登入時才向 Google 查詢，登入權限因此跟著帳號走。不分廠商來看，這是主機登入驗證（登入主機用的金鑰怎麼發、怎麼收回）裡「由中央服務決定可用的公鑰」的那一種，AWS 的 EC2 Instance Connect 屬於同一類，差別是它在連線前推送一把只在短時間內有效的公鑰；讓登入權限自動到期的另一種做法見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）&lt;/a>。&lt;/p>
&lt;p>OS Login 常與 GCP 的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a> 一起用。兩者都依 IAM 判斷，但檢查的是兩件事：IAP 的 IAM 權限決定是否允許開通道，OS Login 的 IAM 權限決定進入通道之後是否允許登入、是否允許使用 sudo。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>專案或 VM 的 metadata 有 &lt;code>enable-oslogin=TRUE&lt;/code>：專案層級用 &lt;code>gcloud compute project-info describe&lt;/code> 查，輸出的 &lt;code>commonInstanceMetadata.items&lt;/code> 裡列著專案層級的 metadata；VM 層級用 &lt;code>gcloud compute instances describe &amp;lt;instance-name&amp;gt; --zone=&amp;lt;zone&amp;gt;&lt;/code> 查，看輸出的 &lt;code>metadata.items&lt;/code>&lt;/li>
&lt;li>登入 VM 之後的使用者名稱是由 Google 帳號位址轉成的名稱（例如 &lt;code>alice_example_com&lt;/code>），不是管理員自己在 metadata 裡指定的名稱&lt;/li>
&lt;li>專案 metadata 的 &lt;code>ssh-keys&lt;/code> 欄位仍然有公鑰——啟用 OS Login 的 VM 不再讀它，但沒啟用的 VM 仍會讀，盤點時要一起清&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>OS Login 啟用後，VM 只認 IAM 角色，metadata 裡的公鑰立即失效，所以啟用前要先把角色與順序排好：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>啟用順序&lt;/strong>：先把登入角色授予每一位管理員與每一個要登入 VM 的&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/service-account/" data-link-title="Service Account（服務帳號）" data-link-desc="程式、排程或雲端服務要以自己的身分呼叫雲端 API、要決定給它哪些角色、遇到「狀態正常卻呼叫失敗」的權限問題、或看到 IAM 綁定裡的 serviceAccount: 成員時，查服務帳號是什麼、它的權限要從「它能做什麼」與「誰能以它的身分行事」兩邊設，以及它和人的帳號在管理上的差別">服務帳號&lt;/a>（CI、Ansible）、對一台 VM 啟用並連通，再做專案層級；順序反過來時，還沒拿到角色的管理員與自動化工具在啟用那一刻就登不進去&lt;/li>
&lt;li>&lt;strong>啟用範圍&lt;/strong>：專案層級啟用涵蓋專案內所有 VM，VM 層級的設定會蓋過專案層級；組織要新建的 VM 一律啟用時用 Organization Policy 的 &lt;code>compute.requireOsLogin&lt;/code>，既有 VM 要另外盤點&lt;/li>
&lt;li>&lt;strong>sudo 的授予&lt;/strong>：&lt;code>roles/compute.osAdminLogin&lt;/code> 只給確實需要 root 權限的群組，其餘給 &lt;code>roles/compute.osLogin&lt;/code>&lt;/li>
&lt;li>&lt;strong>服務帳號的附帶權限&lt;/strong>：VM 以服務帳號身分執行時，登入的使用者另外需要那個服務帳號的 &lt;code>roles/iam.serviceAccountUser&lt;/code>。從主控台用預設值建立的 VM 會掛上 Compute Engine default service account，所以多數 VM 都落在這個情況；VM 掛的是哪個服務帳號，看 &lt;code>gcloud compute instances describe&lt;/code> 輸出的 &lt;code>serviceAccounts[].email&lt;/code>&lt;/li>
&lt;li>&lt;strong>組織外的帳號&lt;/strong>：外包人員用組織以外的 Google 帳號時，要在組織層級另外授予 &lt;code>roles/compute.osLoginExternalUser&lt;/code> 才能登入&lt;/li>
&lt;li>&lt;strong>登入時的兩步驟驗證&lt;/strong>：metadata 設 &lt;code>enable-oslogin-2fa=TRUE&lt;/code> 後，每一次 SSH 登入都要再過一次 Google 帳號的第二因素；還沒在 Google 帳號上設定第二因素的管理員會在啟用那一刻被擋在外面&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> — OS Login 取代的是靜態 &lt;code>authorized_keys&lt;/code> 的金鑰發放方式&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> — OS Login 的登入權限由 IAM role 決定&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a> — 常一起使用的通道授權層&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/gcp-iap-ssh-access/#ssh-%e9%87%91%e9%91%b0%e5%9c%a8-gcp-%e4%b8%8a%e7%9a%84%e7%ae%a1%e7%90%86%e4%bd%8d%e7%bd%aemetadata-%e9%87%91%e9%91%b0%e8%88%87-os-login-%e7%9a%84%e5%95%9f%e7%94%a8%e6%a2%9d%e4%bb%b6" data-link-title="GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding" data-link-desc="GCP 預設網路自帶的防火牆規則與它們留下的暴露面、IAP TCP forwarding 的連線路徑與日常連線指令、切換到 IAP 的順序為什麼這樣排、IAP 通道連不上時的依層判讀，以及 IAM 授權範圍、OS Login 的啟用條件與工作階段的結束、拿掉外部 IP 之後的出站路徑、Organization Policy、階層式防火牆、稽核紀錄與 Security Command Center 的 OPEN_SSH_PORT finding 在 GCP 上的處理">GCP VM 的 SSH 與 RDP 管理通道&lt;/a> — OS Login 與 metadata 金鑰的差別，以及啟用的順序為什麼這樣排&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/#%e5%95%9f%e7%94%a8-os-login%e5%85%88%e6%8e%88%e8%a7%92%e8%89%b2%e5%96%ae%e5%8f%b0%e5%85%88%e8%a1%8c%e5%86%8d%e5%81%9a%e5%b0%88%e6%a1%88%e5%b1%a4%e7%b4%9a" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄&lt;/a> — 授予角色、單台先行啟用與還原的指令&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>OS Login 是 GCP Compute Engine 管理 VM 登入權限的機制：VM 在有人登入時，向 Google 查詢這個 Google 帳號的 SSH 公鑰與它在 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> 裡的角色，依角色決定是否允許登入、是否允許使用 sudo。持有 <code>roles/compute.osLogin</code> 的帳號以一般使用者登入，持有 <code>roles/compute.osAdminLogin</code> 的帳號另外拿得到 sudo。帳號停用或角色移除之後，下一次登入就會失敗，不必回到每台 VM 刪除公鑰；已經建立的工作階段不受影響，要另外在 VM 上結束（<code>loginctl terminate-user &lt;username&gt;</code>），結束已建立工作階段的動作在應用程式的層次叫 <a href="/blog/backend/knowledge-cards/session-invalidation/" data-link-title="Session Invalidation" data-link-desc="說明事件後如何讓既有會話失效，避免被重放或延續利用">session invalidation</a>。啟用方式是在專案或 VM 的 metadata 設定 <code>enable-oslogin=TRUE</code>。</p>
<h2 id="概念位置">概念位置</h2>
<p>OS Login 出現之前，GCP 把 SSH 公鑰交給 VM 的方式是 metadata 金鑰：公鑰寫在專案或 VM 的 metadata 的 <code>ssh-keys</code> 欄位，VM 上的 guest agent（GCP 映像預裝、負責把 metadata 套用到作業系統的常駐程式）讀出來寫進對應帳號的 <code>authorized_keys</code>。這些公鑰和 Google 帳號沒有連動，帳號停用後公鑰仍然留在 metadata 裡，運作方式等同 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 卡描述的靜態公鑰。</p>
<p>OS Login 改成每次登入時才向 Google 查詢，登入權限因此跟著帳號走。不分廠商來看，這是主機登入驗證（登入主機用的金鑰怎麼發、怎麼收回）裡「由中央服務決定可用的公鑰」的那一種，AWS 的 EC2 Instance Connect 屬於同一類，差別是它在連線前推送一把只在短時間內有效的公鑰；讓登入權限自動到期的另一種做法見 <a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）</a>。</p>
<p>OS Login 常與 GCP 的 <a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a> 一起用。兩者都依 IAM 判斷，但檢查的是兩件事：IAP 的 IAM 權限決定是否允許開通道，OS Login 的 IAM 權限決定進入通道之後是否允許登入、是否允許使用 sudo。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>專案或 VM 的 metadata 有 <code>enable-oslogin=TRUE</code>：專案層級用 <code>gcloud compute project-info describe</code> 查，輸出的 <code>commonInstanceMetadata.items</code> 裡列著專案層級的 metadata；VM 層級用 <code>gcloud compute instances describe &lt;instance-name&gt; --zone=&lt;zone&gt;</code> 查，看輸出的 <code>metadata.items</code></li>
<li>登入 VM 之後的使用者名稱是由 Google 帳號位址轉成的名稱（例如 <code>alice_example_com</code>），不是管理員自己在 metadata 裡指定的名稱</li>
<li>專案 metadata 的 <code>ssh-keys</code> 欄位仍然有公鑰——啟用 OS Login 的 VM 不再讀它，但沒啟用的 VM 仍會讀，盤點時要一起清</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>OS Login 啟用後，VM 只認 IAM 角色，metadata 裡的公鑰立即失效，所以啟用前要先把角色與順序排好：</p>
<ul>
<li><strong>啟用順序</strong>：先把登入角色授予每一位管理員與每一個要登入 VM 的<a href="/blog/devops/infra/knowledge-cards/service-account/" data-link-title="Service Account（服務帳號）" data-link-desc="程式、排程或雲端服務要以自己的身分呼叫雲端 API、要決定給它哪些角色、遇到「狀態正常卻呼叫失敗」的權限問題、或看到 IAM 綁定裡的 serviceAccount: 成員時，查服務帳號是什麼、它的權限要從「它能做什麼」與「誰能以它的身分行事」兩邊設，以及它和人的帳號在管理上的差別">服務帳號</a>（CI、Ansible）、對一台 VM 啟用並連通，再做專案層級；順序反過來時，還沒拿到角色的管理員與自動化工具在啟用那一刻就登不進去</li>
<li><strong>啟用範圍</strong>：專案層級啟用涵蓋專案內所有 VM，VM 層級的設定會蓋過專案層級；組織要新建的 VM 一律啟用時用 Organization Policy 的 <code>compute.requireOsLogin</code>，既有 VM 要另外盤點</li>
<li><strong>sudo 的授予</strong>：<code>roles/compute.osAdminLogin</code> 只給確實需要 root 權限的群組，其餘給 <code>roles/compute.osLogin</code></li>
<li><strong>服務帳號的附帶權限</strong>：VM 以服務帳號身分執行時，登入的使用者另外需要那個服務帳號的 <code>roles/iam.serviceAccountUser</code>。從主控台用預設值建立的 VM 會掛上 Compute Engine default service account，所以多數 VM 都落在這個情況；VM 掛的是哪個服務帳號，看 <code>gcloud compute instances describe</code> 輸出的 <code>serviceAccounts[].email</code></li>
<li><strong>組織外的帳號</strong>：外包人員用組織以外的 Google 帳號時，要在組織層級另外授予 <code>roles/compute.osLoginExternalUser</code> 才能登入</li>
<li><strong>登入時的兩步驟驗證</strong>：metadata 設 <code>enable-oslogin-2fa=TRUE</code> 後，每一次 SSH 登入都要再過一次 Google 帳號的第二因素；還沒在 Google 帳號上設定第二因素的管理員會在啟用那一刻被擋在外面</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> — OS Login 取代的是靜態 <code>authorized_keys</code> 的金鑰發放方式</li>
<li><a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> — OS Login 的登入權限由 IAM role 決定</li>
<li><a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a> — 常一起使用的通道授權層</li>
<li><a href="/blog/devops/infra/cloud-providers/gcp/gcp-iap-ssh-access/#ssh-%e9%87%91%e9%91%b0%e5%9c%a8-gcp-%e4%b8%8a%e7%9a%84%e7%ae%a1%e7%90%86%e4%bd%8d%e7%bd%aemetadata-%e9%87%91%e9%91%b0%e8%88%87-os-login-%e7%9a%84%e5%95%9f%e7%94%a8%e6%a2%9d%e4%bb%b6" data-link-title="GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding" data-link-desc="GCP 預設網路自帶的防火牆規則與它們留下的暴露面、IAP TCP forwarding 的連線路徑與日常連線指令、切換到 IAP 的順序為什麼這樣排、IAP 通道連不上時的依層判讀，以及 IAM 授權範圍、OS Login 的啟用條件與工作階段的結束、拿掉外部 IP 之後的出站路徑、Organization Policy、階層式防火牆、稽核紀錄與 Security Command Center 的 OPEN_SSH_PORT finding 在 GCP 上的處理">GCP VM 的 SSH 與 RDP 管理通道</a> — OS Login 與 metadata 金鑰的差別，以及啟用的順序為什麼這樣排</li>
<li><a href="/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/#%e5%95%9f%e7%94%a8-os-login%e5%85%88%e6%8e%88%e8%a7%92%e8%89%b2%e5%96%ae%e5%8f%b0%e5%85%88%e8%a1%8c%e5%86%8d%e5%81%9a%e5%b0%88%e6%a1%88%e5%b1%a4%e7%b4%9a" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄</a> — 授予角色、單台先行啟用與還原的指令</li>
</ul>
]]></content:encoded></item><item><title>Serial Console（序列主控台）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/serial-console/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/serial-console/</guid><description>&lt;p>序列主控台（serial console）承擔的是救援：當網路設定或 sshd 出錯而 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 連不進去時，它提供一條不經過主機作業系統的網路設定與 sshd 的登入路徑。實體伺服器上它是一條接到序列埠的線；雲端 VM 把它做成虛擬序列埠，由廠商的網頁管理介面或 CLI 轉送，例如 GCP 的 Serial Console（&lt;code>gcloud compute connect-to-serial-port&lt;/code>）、AWS 的 EC2 Serial Console，以及 Oracle Cloud 的 serial console connection（先替主機建一個序列主控台連線（instance console connection），再從自己的電腦用 SSH 連上，官方說明本機連線要用 RSA 金鑰）。連上之後看到的是作業系統的文字主控台；只看開機輸出不必登入，要在登入提示登進去時，用主機上設了密碼的本機帳號（Oracle Cloud 官方說明平台映像的 &lt;code>opc&lt;/code> 沒有預設密碼）。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>序列主控台與平時的管理通道設計方向相反：管理通道要隨時可用，救援入口平時關閉、只在需要時打開，因為它繞過了 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a> 或 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）&lt;/a> 這些平時的控制點。管理通道的網路可達性、通道授權或主機登入驗證任何一層設定錯了（這三層的分法見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>），序列主控台都還連得上，因為它不經過那三層的設定。它有自己的授權：雲端上是雲端帳號的 IAM 權限與開啟它的開關，登入時用主機上的本機帳號密碼，所以雲端 IAM 或開關設錯時，它一樣進不去。&lt;/p>
&lt;p>不同環境的救援入口形態不同。地端實體伺服器用的是頻外管理控制器（BMC，out-of-band management，其他中文資料常寫作「帶外管理」）：IPMI、Dell iDRAC、HPE iLO 這類嵌在主機板上的管理控制器，有自己的網路埠，在作業系統沒開機時也能看主控台、重開機、掛載安裝媒體。VPS 服務商多半在網頁後台提供一個在瀏覽器裡顯示主機畫面的主控台（常叫 VNC console），用法與雲端的序列主控台相同：不經過主機的網路設定，用本機帳號密碼登入。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>SSH 連線逾時或被拒，而雲端的網頁管理介面或監控顯示主機仍在執行——網路設定、防火牆或 sshd 出錯時的典型樣貌&lt;/li>
&lt;li>開機過程卡住（檔案系統檢查、&lt;code>/etc/fstab&lt;/code> 寫錯）時，序列主控台看得到開機訊息，SSH 在那個階段還沒有啟動&lt;/li>
&lt;li>雲端帳號或專案的設定裡，序列主控台存取是關閉的（GCP 預設關閉互動登入，用 metadata &lt;code>serial-port-enable=TRUE&lt;/code> 對單一 VM 開啟；AWS 要在帳號層級先允許）&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>救援入口在出事當下才用得到，而帳號、權限與演練在主機正常時準備最快，所以規劃時要先決定：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>救援帳號&lt;/strong>：序列主控台用本機帳號密碼登入，而用金鑰登入的主機通常沒有設密碼的帳號，所以救援帳號要在主機正常時就建好。密碼逐台隨機產生、存放在受控的密碼庫，sshd 維持只收金鑰；這組密碼不隨 IdP 收回，持有它的人離開時要逐台輪替。沒有事先建帳號時，雲端 VM 還有要重開機或停機、因此比較慢的路（GCP 的 startup-script 每次開機以 root 執行；AWS 的 user-data 預設只在第一次開機執行，要改成每次開機執行的 cloud-init 格式；GCP、AWS 與 Oracle Cloud 都能把開機磁碟掛到另一台 VM 修改；官方另為 Oracle Linux 附了經由序列主控台開進維護模式、重設 &lt;code>opc&lt;/code> 金鑰的範例），地端主機只能重開機進單人模式。不必重開機的路是業者在主機裡執行指令的服務：AWS 的 SSM Run Command 與 Oracle Cloud 的 Run Command，兩者都要主機上的代理程式在運作，代理程式停了或連不到業者的服務端點時用不上；Oracle Cloud 這幾個入口各自的前提見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務&lt;/a> 的〈建立之後加金鑰與金鑰遺失時的入口〉&lt;/li>
&lt;li>&lt;strong>演練&lt;/strong>：建好救援帳號之後實際用序列主控台登入一次、確認 sudo 可用，再關回去。沒演練過的救援入口在出事當下才發現密碼不對或權限不足；平時不存在、事故時才開的權限叫 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/break-glass-access/" data-link-title="Break-Glass Access" data-link-desc="說明緊急情況下臨時授予的高權限存取，如何用工單、時限與事後審查治理">Break-Glass Access&lt;/a>，事後回顧的做法在那張卡&lt;/li>
&lt;li>&lt;strong>開啟範圍與時機&lt;/strong>：平時保持關閉，救援時只對單一主機開啟，用完關回去；存取權限只授予持有救援責任的少數帳號（GCP 上開啟 metadata 與連線都需要 &lt;code>roles/compute.instanceAdmin.v1&lt;/code>，而這也是一般 VM 管理會用到的角色，光靠它收不窄）。GCP 用 Organization Policy 的 &lt;code>compute.disableSerialPortAccess&lt;/code> 在組織層級預設關閉時，只有能對專案解除這條政策的人開得了序列主控台，救援名單要包含這些人&lt;/li>
&lt;li>&lt;strong>頻外管理介面的網路&lt;/strong>：IPMI 這類控制器歷史上有多個認證弱點，它的網路埠接在獨立的管理網段，不接到一般網段或網際網路&lt;/li>
&lt;li>&lt;strong>操作紀錄&lt;/strong>：序列主控台的連線繞過了平時的稽核點與錄製位置，雲端廠商的 audit log 查得到誰在什麼時候開了主控台，查不到在裡面執行了什麼；有逐指令稽核要求時，救援時做了什麼要另外記錄&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> — 平時的管理通道，序列主控台在它斷掉時接手&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a> — 平時的通道控制點，救援入口繞過它&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）&lt;/a> — 另一種平時的通道控制點&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/#%e6%ba%96%e5%82%99%e6%95%91%e6%8f%b4%e5%85%a5%e5%8f%a3serial-console-%e8%88%87%e6%9c%ac%e6%a9%9f%e6%95%91%e6%8f%b4%e5%b8%b3%e8%99%9f" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄&lt;/a> — 在 GCP 上建救援帳號、開啟與關閉 Serial Console 的指令&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/" data-link-title="Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建" data-link-desc="要在 Oracle Cloud 沒有公開 IP 的私有子網路裡跑一台 Ubuntu 主機、讓它裝得到套件更新，或要經由序列主控台看主機的開機過程，或主機被閒置回收、停止或刪除之後要照原樣重建，或想知道 NAT gateway 要不要另外付費、開機磁碟扣掉多少區塊磁碟額度、合計超過 200 GB 時放不進額度的那一顆怎麼算、怎麼用 quota 擋下超額的建立、線上加大開機磁碟超過額度時怎麼算、空出額度之後付費磁碟多久轉成免費時，查實際跑過的指令、錯誤原文、重建腳本與每一步的讀法">Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建&lt;/a>〈序列主控台：RSA 金鑰、建立連線、ProxyJump 設定與看到的開機輸出〉 — 在 Oracle Cloud 上建序列主控台連線、把官方的連線字串改寫成 ProxyJump 設定，以及連上之後看到的開機輸出&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>序列主控台（serial console）承擔的是救援：當網路設定或 sshd 出錯而 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 連不進去時，它提供一條不經過主機作業系統的網路設定與 sshd 的登入路徑。實體伺服器上它是一條接到序列埠的線；雲端 VM 把它做成虛擬序列埠，由廠商的網頁管理介面或 CLI 轉送，例如 GCP 的 Serial Console（<code>gcloud compute connect-to-serial-port</code>）、AWS 的 EC2 Serial Console，以及 Oracle Cloud 的 serial console connection（先替主機建一個序列主控台連線（instance console connection），再從自己的電腦用 SSH 連上，官方說明本機連線要用 RSA 金鑰）。連上之後看到的是作業系統的文字主控台；只看開機輸出不必登入，要在登入提示登進去時，用主機上設了密碼的本機帳號（Oracle Cloud 官方說明平台映像的 <code>opc</code> 沒有預設密碼）。</p>
<h2 id="概念位置">概念位置</h2>
<p>序列主控台與平時的管理通道設計方向相反：管理通道要隨時可用，救援入口平時關閉、只在需要時打開，因為它繞過了 <a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a> 或 <a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> 這些平時的控制點。管理通道的網路可達性、通道授權或主機登入驗證任何一層設定錯了（這三層的分法見<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>），序列主控台都還連得上，因為它不經過那三層的設定。它有自己的授權：雲端上是雲端帳號的 IAM 權限與開啟它的開關，登入時用主機上的本機帳號密碼，所以雲端 IAM 或開關設錯時，它一樣進不去。</p>
<p>不同環境的救援入口形態不同。地端實體伺服器用的是頻外管理控制器（BMC，out-of-band management，其他中文資料常寫作「帶外管理」）：IPMI、Dell iDRAC、HPE iLO 這類嵌在主機板上的管理控制器，有自己的網路埠，在作業系統沒開機時也能看主控台、重開機、掛載安裝媒體。VPS 服務商多半在網頁後台提供一個在瀏覽器裡顯示主機畫面的主控台（常叫 VNC console），用法與雲端的序列主控台相同：不經過主機的網路設定，用本機帳號密碼登入。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>SSH 連線逾時或被拒，而雲端的網頁管理介面或監控顯示主機仍在執行——網路設定、防火牆或 sshd 出錯時的典型樣貌</li>
<li>開機過程卡住（檔案系統檢查、<code>/etc/fstab</code> 寫錯）時，序列主控台看得到開機訊息，SSH 在那個階段還沒有啟動</li>
<li>雲端帳號或專案的設定裡，序列主控台存取是關閉的（GCP 預設關閉互動登入，用 metadata <code>serial-port-enable=TRUE</code> 對單一 VM 開啟；AWS 要在帳號層級先允許）</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>救援入口在出事當下才用得到，而帳號、權限與演練在主機正常時準備最快，所以規劃時要先決定：</p>
<ul>
<li><strong>救援帳號</strong>：序列主控台用本機帳號密碼登入，而用金鑰登入的主機通常沒有設密碼的帳號，所以救援帳號要在主機正常時就建好。密碼逐台隨機產生、存放在受控的密碼庫，sshd 維持只收金鑰；這組密碼不隨 IdP 收回，持有它的人離開時要逐台輪替。沒有事先建帳號時，雲端 VM 還有要重開機或停機、因此比較慢的路（GCP 的 startup-script 每次開機以 root 執行；AWS 的 user-data 預設只在第一次開機執行，要改成每次開機執行的 cloud-init 格式；GCP、AWS 與 Oracle Cloud 都能把開機磁碟掛到另一台 VM 修改；官方另為 Oracle Linux 附了經由序列主控台開進維護模式、重設 <code>opc</code> 金鑰的範例），地端主機只能重開機進單人模式。不必重開機的路是業者在主機裡執行指令的服務：AWS 的 SSM Run Command 與 Oracle Cloud 的 Run Command，兩者都要主機上的代理程式在運作，代理程式停了或連不到業者的服務端點時用不上；Oracle Cloud 這幾個入口各自的前提見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈建立之後加金鑰與金鑰遺失時的入口〉</li>
<li><strong>演練</strong>：建好救援帳號之後實際用序列主控台登入一次、確認 sudo 可用，再關回去。沒演練過的救援入口在出事當下才發現密碼不對或權限不足；平時不存在、事故時才開的權限叫 <a href="/blog/backend/knowledge-cards/break-glass-access/" data-link-title="Break-Glass Access" data-link-desc="說明緊急情況下臨時授予的高權限存取，如何用工單、時限與事後審查治理">Break-Glass Access</a>，事後回顧的做法在那張卡</li>
<li><strong>開啟範圍與時機</strong>：平時保持關閉，救援時只對單一主機開啟，用完關回去；存取權限只授予持有救援責任的少數帳號（GCP 上開啟 metadata 與連線都需要 <code>roles/compute.instanceAdmin.v1</code>，而這也是一般 VM 管理會用到的角色，光靠它收不窄）。GCP 用 Organization Policy 的 <code>compute.disableSerialPortAccess</code> 在組織層級預設關閉時，只有能對專案解除這條政策的人開得了序列主控台，救援名單要包含這些人</li>
<li><strong>頻外管理介面的網路</strong>：IPMI 這類控制器歷史上有多個認證弱點，它的網路埠接在獨立的管理網段，不接到一般網段或網際網路</li>
<li><strong>操作紀錄</strong>：序列主控台的連線繞過了平時的稽核點與錄製位置，雲端廠商的 audit log 查得到誰在什麼時候開了主控台，查不到在裡面執行了什麼；有逐指令稽核要求時，救援時做了什麼要另外記錄</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> — 平時的管理通道，序列主控台在它斷掉時接手</li>
<li><a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a> — 平時的通道控制點，救援入口繞過它</li>
<li><a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> — 另一種平時的通道控制點</li>
<li><a href="/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/#%e6%ba%96%e5%82%99%e6%95%91%e6%8f%b4%e5%85%a5%e5%8f%a3serial-console-%e8%88%87%e6%9c%ac%e6%a9%9f%e6%95%91%e6%8f%b4%e5%b8%b3%e8%99%9f" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄</a> — 在 GCP 上建救援帳號、開啟與關閉 Serial Console 的指令</li>
<li><a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/" data-link-title="Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建" data-link-desc="要在 Oracle Cloud 沒有公開 IP 的私有子網路裡跑一台 Ubuntu 主機、讓它裝得到套件更新，或要經由序列主控台看主機的開機過程，或主機被閒置回收、停止或刪除之後要照原樣重建，或想知道 NAT gateway 要不要另外付費、開機磁碟扣掉多少區塊磁碟額度、合計超過 200 GB 時放不進額度的那一顆怎麼算、怎麼用 quota 擋下超額的建立、線上加大開機磁碟超過額度時怎麼算、空出額度之後付費磁碟多久轉成免費時，查實際跑過的指令、錯誤原文、重建腳本與每一步的讀法">Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建</a>〈序列主控台：RSA 金鑰、建立連線、ProxyJump 設定與看到的開機輸出〉 — 在 Oracle Cloud 上建序列主控台連線、把官方的連線字串改寫成 ProxyJump 設定，以及連上之後看到的開機輸出</li>
</ul>
]]></content:encoded></item><item><title>Managed Instance Group（MIG，代管執行個體群組）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/managed-instance-group/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/managed-instance-group/</guid><description>&lt;p>Managed Instance Group（MIG）是 GCP 依一份 instance template 自動維持一組相同 VM 的機制：它負責建出指定台數的 VM、在健康檢查失敗時重建、依負載增減台數，以及把整組換成新版 template。instance template 是建立 VM 的完整設定（機器類型、映像、磁碟、網路、&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/network-tag/" data-link-title="Network Tag（GCP 網路標籤）" data-link-desc="GCP 防火牆規則套不到某台 VM、新建的 VM 連不上，或要決定防火牆規則用 tag 還是服務帳號選 VM 時，查 network tag 怎麼讓防火牆規則選出要套用的 VM">Network Tag（網路標籤）&lt;/a>、metadata、服務帳號），建立之後不能修改。AWS 的對應機制是 Auto Scaling group 加 launch template。可先對照 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>MIG 把「一台 VM」換成「一份設定產生的一組 VM」：VM 隨時可能依 template 被重建或替換：&lt;code>recreate-instances&lt;/code> 沿用原本的名稱、換一顆新的開機磁碟（實測一次時執行個體 ID 也沒變，gcloud 說明寫 ID 可能改變）；滾動更新的預設替換方式 &lt;code>substitute&lt;/code> 建出新名稱、新 ID 的 VM；擴增則新建 VM。不論哪一種，只存在某一台開機磁碟上的修改都會在下一次重建時消失，要持久的設定一律寫進 template，改設定的方式是建一份新 template 再滾動更新。這是&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/immutable-image/" data-link-title="Immutable Image（不可變映像）" data-link-desc="想「修改 / 升級一個已經 build 好的 image」、或搞不懂為什麼同一個 tag 隔天內容變了時回來讀 — image 是不可變的 build 產物、改了就是新 image">不可變基礎設施&lt;/a>的做法：它適合 VM 本身沒有不能丟的狀態的工作負載；有狀態時用 stateful 設定保留指定的磁碟與 IP，或改成單獨管理的 VM。GKE 的 node pool 在底下也是 MIG，但那一組由 GKE 管理，設定改在 node pool 上。template 裡的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/network-tag/" data-link-title="Network Tag（GCP 網路標籤）" data-link-desc="GCP 防火牆規則套不到某台 VM、新建的 VM 連不上，或要決定防火牆規則用 tag 還是服務帳號選 VM 時，查 network tag 怎麼讓防火牆規則選出要套用的 VM">Network Tag（網路標籤）&lt;/a> 決定防火牆規則套不套得到新建的 VM。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>VM 的 metadata 有 &lt;code>created-by&lt;/code> 鍵，值是建立這台 VM 的 MIG 的資源路徑（VM 被移出 MIG 之後仍然留著，現在的成員以 &lt;code>gcloud compute instance-groups managed list-instances&lt;/code> 為準）；名稱是 &lt;code>gke-...-grp&lt;/code> 的 MIG 屬於 GKE 叢集&lt;/li>
&lt;li>半夜自動修復之後，某台 VM 上手動加的 tag、metadata 或帳號不見了&lt;/li>
&lt;li>&lt;code>gcloud compute instance-groups managed list-instances &amp;lt;mig-name&amp;gt;&lt;/code> 的 &lt;code>INSTANCE_TEMPLATE&lt;/code> 欄顯示每台 VM 用的是哪一份 template&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>管理通道、監控代理這類要在每台 VM 上都成立的設定寫進 template 或映像，不靠逐台指令。改 template 時先讀出現行 template 的完整設定逐項帶進新的一份，因為新 template 從預設值開始；先讓一台 VM 換新版驗證再整組更新，舊 template 留到新版驗證完。整組由 IaC 管理時，改動寫在程式碼裡，CLI 上的修改會在下一次套用時被改回。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/network-tag/" data-link-title="Network Tag（GCP 網路標籤）" data-link-desc="GCP 防火牆規則套不到某台 VM、新建的 VM 連不上，或要決定防火牆規則用 tag 還是服務帳號選 VM 時，查 network tag 怎麼讓防火牆規則選出要套用的 VM">Network Tag（網路標籤）&lt;/a> — template 裡決定防火牆規則套不套得到新 VM 的欄位&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）&lt;/a> — 單台 VM 的救援入口；MIG 裡多了依 template 重建這條路&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC&lt;/a> — MIG 與 template 多半由 IaC 管理&lt;/li>
&lt;li>管理通道的設定為什麼要寫在 template 見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/gke-mig-management-access/" data-link-title="GCP 上由 MIG 與 GKE 管理的 VM 的管理存取：instance template、node pool、kubectl debug 與控制平面存取" data-link-desc="VM 由 managed instance group 或 GKE node pool 管理時，管理通道的設定為什麼要寫在 instance template 與 node pool 而不是逐台修改、MIG 救援用重建、退回 template 與 Serial Console 各自適用的情形、GKE 的控制平面與節點兩個存取面、kubectl debug 取代節點 SSH 的權限邊界（RBAC 與 Pod Security Admission），以及 GKE 控制平面三種端點（authorized networks、DNS-based endpoint 與 private endpoint）的差別">GCP 上由 MIG 與 GKE 管理的 VM 的管理存取&lt;/a>，建 template、滾動更新與重建的指令見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/hands-on/mig-gke-access-walkthrough/" data-link-title="GCP 上 MIG 與 GKE 管理存取的實測紀錄：instance template 與 canary 滾動更新、重建與主機金鑰、kubectl debug 的 namespace、node pool 設定與控制平面端點的切換" data-link-desc="要把 IAP 的 tag、OS Login 與移除外部 IP 套到 MIG 或 GKE 上，想照一次實跑過的順序做、看每一步換一種做法的結果時讀；也用在 MIG 重建之後 SSH 出現 REMOTE HOST IDENTIFICATION HAS CHANGED、kubectl debug 停在等待，或關掉 GKE 控制平面的 IP 端點之前要確認每個人都改連的時候">GCP 上 MIG 與 GKE 管理存取的實測紀錄&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Managed Instance Group（MIG）是 GCP 依一份 instance template 自動維持一組相同 VM 的機制：它負責建出指定台數的 VM、在健康檢查失敗時重建、依負載增減台數，以及把整組換成新版 template。instance template 是建立 VM 的完整設定（機器類型、映像、磁碟、網路、<a href="/blog/devops/infra/knowledge-cards/network-tag/" data-link-title="Network Tag（GCP 網路標籤）" data-link-desc="GCP 防火牆規則套不到某台 VM、新建的 VM 連不上，或要決定防火牆規則用 tag 還是服務帳號選 VM 時，查 network tag 怎麼讓防火牆規則選出要套用的 VM">Network Tag（網路標籤）</a>、metadata、服務帳號），建立之後不能修改。AWS 的對應機制是 Auto Scaling group 加 launch template。可先對照 <a href="/blog/devops/infra/knowledge-cards/ec2/" data-link-title="EC2" data-link-desc="AWS 的虛擬機器服務，提供可隨時啟停的運算實例，組成包含 AMI、instance type、EBS、security group 與 IAM role">EC2</a> 與 <a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>MIG 把「一台 VM」換成「一份設定產生的一組 VM」：VM 隨時可能依 template 被重建或替換：<code>recreate-instances</code> 沿用原本的名稱、換一顆新的開機磁碟（實測一次時執行個體 ID 也沒變，gcloud 說明寫 ID 可能改變）；滾動更新的預設替換方式 <code>substitute</code> 建出新名稱、新 ID 的 VM；擴增則新建 VM。不論哪一種，只存在某一台開機磁碟上的修改都會在下一次重建時消失，要持久的設定一律寫進 template，改設定的方式是建一份新 template 再滾動更新。這是<a href="/blog/backend/knowledge-cards/immutable-image/" data-link-title="Immutable Image（不可變映像）" data-link-desc="想「修改 / 升級一個已經 build 好的 image」、或搞不懂為什麼同一個 tag 隔天內容變了時回來讀 — image 是不可變的 build 產物、改了就是新 image">不可變基礎設施</a>的做法：它適合 VM 本身沒有不能丟的狀態的工作負載；有狀態時用 stateful 設定保留指定的磁碟與 IP，或改成單獨管理的 VM。GKE 的 node pool 在底下也是 MIG，但那一組由 GKE 管理，設定改在 node pool 上。template 裡的 <a href="/blog/devops/infra/knowledge-cards/network-tag/" data-link-title="Network Tag（GCP 網路標籤）" data-link-desc="GCP 防火牆規則套不到某台 VM、新建的 VM 連不上，或要決定防火牆規則用 tag 還是服務帳號選 VM 時，查 network tag 怎麼讓防火牆規則選出要套用的 VM">Network Tag（網路標籤）</a> 決定防火牆規則套不套得到新建的 VM。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>VM 的 metadata 有 <code>created-by</code> 鍵，值是建立這台 VM 的 MIG 的資源路徑（VM 被移出 MIG 之後仍然留著，現在的成員以 <code>gcloud compute instance-groups managed list-instances</code> 為準）；名稱是 <code>gke-...-grp</code> 的 MIG 屬於 GKE 叢集</li>
<li>半夜自動修復之後，某台 VM 上手動加的 tag、metadata 或帳號不見了</li>
<li><code>gcloud compute instance-groups managed list-instances &lt;mig-name&gt;</code> 的 <code>INSTANCE_TEMPLATE</code> 欄顯示每台 VM 用的是哪一份 template</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>管理通道、監控代理這類要在每台 VM 上都成立的設定寫進 template 或映像，不靠逐台指令。改 template 時先讀出現行 template 的完整設定逐項帶進新的一份，因為新 template 從預設值開始；先讓一台 VM 換新版驗證再整組更新，舊 template 留到新版驗證完。整組由 IaC 管理時，改動寫在程式碼裡，CLI 上的修改會在下一次套用時被改回。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/network-tag/" data-link-title="Network Tag（GCP 網路標籤）" data-link-desc="GCP 防火牆規則套不到某台 VM、新建的 VM 連不上，或要決定防火牆規則用 tag 還是服務帳號選 VM 時，查 network tag 怎麼讓防火牆規則選出要套用的 VM">Network Tag（網路標籤）</a> — template 裡決定防火牆規則套不套得到新 VM 的欄位</li>
<li><a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）</a> — 單台 VM 的救援入口；MIG 裡多了依 template 重建這條路</li>
<li><a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> — MIG 與 template 多半由 IaC 管理</li>
<li>管理通道的設定為什麼要寫在 template 見 <a href="/blog/devops/infra/cloud-providers/gcp/gke-mig-management-access/" data-link-title="GCP 上由 MIG 與 GKE 管理的 VM 的管理存取：instance template、node pool、kubectl debug 與控制平面存取" data-link-desc="VM 由 managed instance group 或 GKE node pool 管理時，管理通道的設定為什麼要寫在 instance template 與 node pool 而不是逐台修改、MIG 救援用重建、退回 template 與 Serial Console 各自適用的情形、GKE 的控制平面與節點兩個存取面、kubectl debug 取代節點 SSH 的權限邊界（RBAC 與 Pod Security Admission），以及 GKE 控制平面三種端點（authorized networks、DNS-based endpoint 與 private endpoint）的差別">GCP 上由 MIG 與 GKE 管理的 VM 的管理存取</a>，建 template、滾動更新與重建的指令見 <a href="/blog/devops/infra/cloud-providers/gcp/hands-on/mig-gke-access-walkthrough/" data-link-title="GCP 上 MIG 與 GKE 管理存取的實測紀錄：instance template 與 canary 滾動更新、重建與主機金鑰、kubectl debug 的 namespace、node pool 設定與控制平面端點的切換" data-link-desc="要把 IAP 的 tag、OS Login 與移除外部 IP 套到 MIG 或 GKE 上，想照一次實跑過的順序做、看每一步換一種做法的結果時讀；也用在 MIG 重建之後 SSH 出現 REMOTE HOST IDENTIFICATION HAS CHANGED、kubectl debug 停在等待，或關掉 GKE 控制平面的 IP 端點之前要確認每個人都改連的時候">GCP 上 MIG 與 GKE 管理存取的實測紀錄</a></li>
</ul>
]]></content:encoded></item><item><title>Network Tag（GCP 網路標籤）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/network-tag/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/network-tag/</guid><description>&lt;p>Network tag 是貼在 GCP VM 上的字串標籤，防火牆規則用它選出要套用的 VM：規則的 &lt;code>--target-tags&lt;/code> 列出 tag，只有帶著其中一個 tag 的 VM 受這條規則影響（沒有 target 的規則套用到整個網路的 VM）。它是 VM 的屬性，&lt;code>gcloud compute instances add-tags&lt;/code> 逐台加，或寫進 instance template 讓建出來的 VM 都帶著。AWS 沒有對應的標籤機制，對應的做法是把 instance 掛進 security group。可先對照 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>tag 把「哪些 VM 要開這個 port」從防火牆規則裡拆出來，放到 VM 上：規則只寫一次，VM 加上 tag 就套用。代價是 tag 由誰都能改的 VM 屬性決定——有權限改 VM 的帳號加上一個 tag，那台 VM 就套用了那條規則；而一台 VM 被重建時，只有寫在 template 裡的 tag 會留下。這與 AWS 的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> 方向相反：Security Group 是把 instance 掛進規則集合。要讓授權更嚴格時，防火牆規則改用 &lt;code>--target-service-accounts&lt;/code> 依 VM 的服務帳號選，換服務帳號需要的權限比改 tag 高。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>&lt;code>gcloud compute instances describe &amp;lt;vm&amp;gt; --format=&amp;quot;value(tags.items)&amp;quot;&lt;/code> 列出這台 VM 的 tag&lt;/li>
&lt;li>只放行 IAP 來源的規則存在、而某台 VM 經 IAP 連不上：先查這台 VM 有沒有那條規則的 target tag&lt;/li>
&lt;li>由 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/managed-instance-group/" data-link-title="Managed Instance Group（MIG，代管執行個體群組）" data-link-desc="VM 被自動重建之後設定不見、要決定一組 VM 的設定該寫在哪裡，或要判斷一台 VM 能不能直接刪掉重建時，查 GCP 的 MIG 與 instance template 怎麼建立、修復與更新 VM">Managed Instance Group（MIG）&lt;/a> 自動重建或新建的 VM 少了手動加的 tag&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>管理通道用的 tag（例如放行 IAP 來源連 22 port 的那一個）寫進 instance template 或 GKE node pool 的設定，不靠逐台 &lt;code>add-tags&lt;/code>。改 template 或 node pool 的 tag 時，&lt;code>--tags&lt;/code> 是整組指定，既有的 tag 要一起列進去。能改 VM tag 的權限等於能改那台 VM 套用哪些防火牆規則，授權時與防火牆規則的管理權一起考慮。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — AWS 上用掛載而不是標籤決定規則套用範圍&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/managed-instance-group/" data-link-title="Managed Instance Group（MIG，代管執行個體群組）" data-link-desc="VM 被自動重建之後設定不見、要決定一組 VM 的設定該寫在哪裡，或要判斷一台 VM 能不能直接刪掉重建時，查 GCP 的 MIG 與 instance template 怎麼建立、修復與更新 VM">Managed Instance Group（MIG）&lt;/a> — tag 寫在 template 裡才會留到重建後&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a> — GCP 上放行 IAP 來源的防火牆規則用 tag 選 VM&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Network tag 是貼在 GCP VM 上的字串標籤，防火牆規則用它選出要套用的 VM：規則的 <code>--target-tags</code> 列出 tag，只有帶著其中一個 tag 的 VM 受這條規則影響（沒有 target 的規則套用到整個網路的 VM）。它是 VM 的屬性，<code>gcloud compute instances add-tags</code> 逐台加，或寫進 instance template 讓建出來的 VM 都帶著。AWS 沒有對應的標籤機制，對應的做法是把 instance 掛進 security group。可先對照 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>tag 把「哪些 VM 要開這個 port」從防火牆規則裡拆出來，放到 VM 上：規則只寫一次，VM 加上 tag 就套用。代價是 tag 由誰都能改的 VM 屬性決定——有權限改 VM 的帳號加上一個 tag，那台 VM 就套用了那條規則；而一台 VM 被重建時，只有寫在 template 裡的 tag 會留下。這與 AWS 的 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> 方向相反：Security Group 是把 instance 掛進規則集合。要讓授權更嚴格時，防火牆規則改用 <code>--target-service-accounts</code> 依 VM 的服務帳號選，換服務帳號需要的權限比改 tag 高。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li><code>gcloud compute instances describe &lt;vm&gt; --format=&quot;value(tags.items)&quot;</code> 列出這台 VM 的 tag</li>
<li>只放行 IAP 來源的規則存在、而某台 VM 經 IAP 連不上：先查這台 VM 有沒有那條規則的 target tag</li>
<li>由 <a href="/blog/devops/infra/knowledge-cards/managed-instance-group/" data-link-title="Managed Instance Group（MIG，代管執行個體群組）" data-link-desc="VM 被自動重建之後設定不見、要決定一組 VM 的設定該寫在哪裡，或要判斷一台 VM 能不能直接刪掉重建時，查 GCP 的 MIG 與 instance template 怎麼建立、修復與更新 VM">Managed Instance Group（MIG）</a> 自動重建或新建的 VM 少了手動加的 tag</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>管理通道用的 tag（例如放行 IAP 來源連 22 port 的那一個）寫進 instance template 或 GKE node pool 的設定，不靠逐台 <code>add-tags</code>。改 template 或 node pool 的 tag 時，<code>--tags</code> 是整組指定，既有的 tag 要一起列進去。能改 VM tag 的權限等於能改那台 VM 套用哪些防火牆規則，授權時與防火牆規則的管理權一起考慮。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — AWS 上用掛載而不是標籤決定規則套用範圍</li>
<li><a href="/blog/devops/infra/knowledge-cards/managed-instance-group/" data-link-title="Managed Instance Group（MIG，代管執行個體群組）" data-link-desc="VM 被自動重建之後設定不見、要決定一組 VM 的設定該寫在哪裡，或要判斷一台 VM 能不能直接刪掉重建時，查 GCP 的 MIG 與 instance template 怎麼建立、修復與更新 VM">Managed Instance Group（MIG）</a> — tag 寫在 template 裡才會留到重建後</li>
<li><a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a> — GCP 上放行 IAP 來源的防火牆規則用 tag 選 VM</li>
</ul>
]]></content:encoded></item><item><title>SSH Host Key 與 known_hosts（主機金鑰）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-host-key/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-host-key/</guid><description>&lt;p>SSH host key 是主機用來向用戶端證明自己身分的金鑰對，存在主機的 &lt;code>/etc/ssh/ssh_host_*_key&lt;/code>（私鑰）與同名 &lt;code>.pub&lt;/code>（公鑰）。用戶端第一次連線時看到的「指紋」就是這把公鑰的雜湊；用戶端確認之後把它記進 &lt;code>~/.ssh/known_hosts&lt;/code>（全機共用的是 &lt;code>/etc/ssh/ssh_known_hosts&lt;/code>），之後每次連線都比對主機出示的 host key 與記下的是否相同。可先對照 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>host key 擋的是中間人攻擊：有人攔截連線、冒充目標主機時，他出示的 host key 與 &lt;code>known_hosts&lt;/code> 記的不同，&lt;code>ssh&lt;/code> 會拒絕連線。這個保護的弱點在第一次連線：用戶端還沒有任何記錄，只能由使用者判斷指紋對不對，這種做法叫 TOFU（trust on first use，首次使用時信任），而多數人不會去核對指紋。主機重建時 host key 重新產生，&lt;code>ssh&lt;/code> 看到的就是一把不同的金鑰。&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）&lt;/a> 的主機憑證把逐台信任換成信任一把主機 CA，用戶端在 &lt;code>known_hosts&lt;/code> 用 &lt;code>@cert-authority&lt;/code> 一行信任它。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>第一次連線：&lt;code>The authenticity of host '...' can't be established. ED25519 key fingerprint is SHA256:...&lt;/code>&lt;/li>
&lt;li>host key 與記錄不同：&lt;code>WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!&lt;/code>，連線被拒，訊息會指出 &lt;code>known_hosts&lt;/code> 裡衝突的那一行&lt;/li>
&lt;li>&lt;code>ssh-keyscan &amp;lt;host&amp;gt;&lt;/code> 取得主機出示的公鑰；&lt;code>ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub&lt;/code> 在主機上印出指紋，可以與用戶端看到的對照&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>主機經常重建的環境裡，指紋變更警告會天天出現，管理員會被訓練成直接刪掉舊記錄、接受新的，而真正的中間人攻擊看起來一模一樣。這種環境改用主機憑證，或讓建立主機的流程把新的 host key 公鑰派送到用戶端的 &lt;code>/etc/ssh/ssh_known_hosts&lt;/code>；不把 &lt;code>StrictHostKeyChecking no&lt;/code> 當成長期做法。看到指紋變更而不確定主機是否重建過時，先從另一條路徑（雲端主控台、序列主控台）在主機上印出指紋核對，再更新記錄。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> — host key 是 SSH 連線建立時的主機驗證&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）&lt;/a> — 主機憑證取代逐台確認指紋&lt;/li>
&lt;li>實作見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>SSH host key 是主機用來向用戶端證明自己身分的金鑰對，存在主機的 <code>/etc/ssh/ssh_host_*_key</code>（私鑰）與同名 <code>.pub</code>（公鑰）。用戶端第一次連線時看到的「指紋」就是這把公鑰的雜湊；用戶端確認之後把它記進 <code>~/.ssh/known_hosts</code>（全機共用的是 <code>/etc/ssh/ssh_known_hosts</code>），之後每次連線都比對主機出示的 host key 與記下的是否相同。可先對照 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>host key 擋的是中間人攻擊：有人攔截連線、冒充目標主機時，他出示的 host key 與 <code>known_hosts</code> 記的不同，<code>ssh</code> 會拒絕連線。這個保護的弱點在第一次連線：用戶端還沒有任何記錄，只能由使用者判斷指紋對不對，這種做法叫 TOFU（trust on first use，首次使用時信任），而多數人不會去核對指紋。主機重建時 host key 重新產生，<code>ssh</code> 看到的就是一把不同的金鑰。<a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）</a> 的主機憑證把逐台信任換成信任一把主機 CA，用戶端在 <code>known_hosts</code> 用 <code>@cert-authority</code> 一行信任它。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>第一次連線：<code>The authenticity of host '...' can't be established. ED25519 key fingerprint is SHA256:...</code></li>
<li>host key 與記錄不同：<code>WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!</code>，連線被拒，訊息會指出 <code>known_hosts</code> 裡衝突的那一行</li>
<li><code>ssh-keyscan &lt;host&gt;</code> 取得主機出示的公鑰；<code>ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub</code> 在主機上印出指紋，可以與用戶端看到的對照</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>主機經常重建的環境裡，指紋變更警告會天天出現，管理員會被訓練成直接刪掉舊記錄、接受新的，而真正的中間人攻擊看起來一模一樣。這種環境改用主機憑證，或讓建立主機的流程把新的 host key 公鑰派送到用戶端的 <code>/etc/ssh/ssh_known_hosts</code>；不把 <code>StrictHostKeyChecking no</code> 當成長期做法。看到指紋變更而不確定主機是否重建過時，先從另一條路徑（雲端主控台、序列主控台）在主機上印出指紋核對，再更新記錄。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> — host key 是 SSH 連線建立時的主機驗證</li>
<li><a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）</a> — 主機憑證取代逐台確認指紋</li>
<li>實作見 <a href="/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入</a></li>
</ul>
]]></content:encoded></item><item><title>Service Account（服務帳號）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/service-account/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/service-account/</guid><description>&lt;p>服務帳號（service account）是給程式使用、而不是給人使用的身分。部署在雲端的程式要呼叫雲端的 API（讀寫儲存、發布訊息、修改帳單設定）時，以一個服務帳號的身分送出請求，&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> 依這個身分被授予的角色決定是否允許這個請求。GCP 的服務帳號以 email 形式的位址識別（&lt;code>&amp;lt;name&amp;gt;@&amp;lt;project-id&amp;gt;.iam.gserviceaccount.com&lt;/code>），在 IAM 綁定裡寫成 &lt;code>serviceAccount:&amp;lt;位址&amp;gt;&lt;/code>；AWS 對應的是 IAM role 被服務或程式承接（assume，AWS 中文文件寫作「擔任」）的用法。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>服務帳號在 IAM 裡同時是一個身分、也是一個資源，權限也因此分成兩邊來設：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>它是一個身分：它能做什麼&lt;/strong>。在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> 授予它角色，決定它能對哪些資源做什麼。例如一個&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/spend-protection-gcp-budget-killswitch/" data-link-title="GCP 上的雲端費用失控防護：預算警示、強制執行支出上限與自動停用帳單的作用範圍、反應時間與判斷" data-link-desc="GCP 上為費用失控設防的機制：預算警示寄出通知的時機、範圍、抵免與通知對象、強制執行支出上限涵蓋的服務與暫停期間，以預算通知觸發函式停止用量的兩種處置（解除帳單連結、只停止花錢的資源）該選哪一種，帳單資料延遲對這些機制反應時間的影響，停損開關放在另一個專案的理由，以及驗收為什麼分成真實通知與測試訊息兩段、停用後的恢復為什麼要先調預算">依預算通知停用帳單的函式&lt;/a>，它的服務帳號要有解除專案帳單連結的角色。&lt;/li>
&lt;li>&lt;strong>它是一個資源：誰能以它的身分行事&lt;/strong>。別的身分要「以它的身分行事」（把它掛到 VM 上、用它部署函式、用它的身分呼叫另一個服務），要對這個服務帳號本身持有角色。例如在啟用 OS Login 的 VM 上，登入一台掛著服務帳號的 VM 的人，要有那個服務帳號的 &lt;code>roles/iam.serviceAccountUser&lt;/code>。&lt;/li>
&lt;/ul>
&lt;p>兩邊漏設時的症狀不同。「誰能以它的身分行事」那一邊漏設時，部署或掛載的那一步就被拒絕；「它能做什麼」那一邊漏設時，部署與元件狀態都顯示正常，要到程式實際呼叫 API 的那一刻請求才被拒絕。&lt;/p>
&lt;p>程式取得服務帳號憑證的方式有兩種：在雲端裡執行的程式（VM、函式、容器）由平台自動提供短期憑證，不需要金鑰；在雲端外面的程式（CI、地端伺服器）可以下載一把長期金鑰，或改用 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/workload-identity/" data-link-title="Workload Identity" data-link-desc="用於機器工作負載的身份語意與授權邊界">workload identity&lt;/a> 以外部平台簽發的 token 換到短期憑證。長期金鑰外洩等於這個身分外洩，能用短期憑證時不發金鑰。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>IAM 綁定裡出現 &lt;code>serviceAccount:&lt;/code> 開頭的成員：&lt;code>gcloud projects get-iam-policy &amp;lt;project-id&amp;gt;&lt;/code> 列得出專案層級的綁定&lt;/li>
&lt;li>VM 掛著哪個服務帳號：&lt;code>gcloud compute instances describe &amp;lt;instance-name&amp;gt; --zone=&amp;lt;zone&amp;gt;&lt;/code> 輸出的 &lt;code>serviceAccounts[].email&lt;/code>；用主控台預設值建立的 VM 會掛上 Compute Engine 預設服務帳號（&lt;code>&amp;lt;project-number&amp;gt;-compute@developer.gserviceaccount.com&lt;/code>），instance template 的 &lt;code>describe&lt;/code> 對它顯示為 &lt;code>default&lt;/code>&lt;/li>
&lt;li>程式的錯誤訊息是 &lt;code>The caller does not have permission&lt;/code> 或 403，而部署與狀態都顯示正常：服務帳號缺少這個 API 呼叫需要的權限，用 &lt;code>gcloud iam roles describe &amp;lt;role&amp;gt;&lt;/code> 查它持有的角色裡有沒有那一項；錯誤訊息沒寫權限名稱時，API 參考文件裡那個方法的頁面列出它需要的權限（例如 &lt;code>projects.getBillingInfo&lt;/code> 需要 &lt;code>resourcemanager.projects.get&lt;/code>）&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>一個用途一個服務帳號&lt;/strong>：每個程式或每類工作用自己的服務帳號，角色只給它需要的那幾個（&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">least privilege&lt;/a>）；共用預設服務帳號時，任何一個程式被入侵都拿得到其他程式的權限&lt;/li>
&lt;li>&lt;strong>查角色實際包含哪些權限&lt;/strong>：角色名稱看起來管的那件事，角色本身不一定涵蓋其中的每一個動作，&lt;code>gcloud iam roles describe &amp;lt;role&amp;gt;&lt;/code> 列得出它的權限清單；例如 GCP 的 &lt;code>roles/billing.projectManager&lt;/code> 只能建立與刪除專案的帳單連結，讀取帳單狀態要另外的權限&lt;/li>
&lt;li>&lt;strong>呼叫鏈上的每一段都要授權&lt;/strong>：由事件觸發的程式（Pub/Sub 推送、Eventarc 觸發器）有「觸發器以哪個身分呼叫程式」與「程式以哪個身分呼叫 API」兩段，兩段各自要有角色&lt;/li>
&lt;li>&lt;strong>設定完以實際觸發驗收&lt;/strong>：「它能做什麼」那一邊的缺漏在部署時看不出來，所以設定完要實際觸發一次它要做的事、看結果，而不是看部署的回傳值，做法見&lt;a href="https://tarrragon.github.io/blog/report/protection-proves-itself-by-firing/" data-link-title="平常不動作的防護機制的部署驗證：設定被接受、元件狀態正常、執行失敗被吞掉與測試從中途注入這些提早出現的成功訊號，驗證查詢與處置指令的假陰性，以及合成觸發本身有損害時的分段驗證" data-link-desc="部署停損開關、稽核紀錄、告警或自動處置這類平常不動作的防護機制之後，要判斷它算不算部署完成時讀；也用在驗收紀錄寫著「部署成功」「狀態 ACTIVE」「設定已套用」、或驗證查詢回傳零筆就被當成結論的時候">平常不動作的防護機制的部署驗證&lt;/a>&lt;/li>
&lt;li>&lt;strong>「以它的身分行事」的權限也是權限&lt;/strong>：持有某個服務帳號的 &lt;code>roles/iam.serviceAccountUser&lt;/code> 或可以產生它的 token 的人，等同持有那個服務帳號的全部角色，盤點權限時要一起算&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> — 服務帳號的角色由 IAM 授予&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/workload-identity/" data-link-title="Workload Identity" data-link-desc="用於機器工作負載的身份語意與授權邊界">Workload Identity&lt;/a> — 雲端外的程式不發金鑰、以短期憑證取得服務帳號身分&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">Least Privilege（最小權限）&lt;/a> — 服務帳號的角色範圍原則&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login&lt;/a> — 登入掛著服務帳號的 VM 時另外需要 &lt;code>roles/iam.serviceAccountUser&lt;/code>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>服務帳號（service account）是給程式使用、而不是給人使用的身分。部署在雲端的程式要呼叫雲端的 API（讀寫儲存、發布訊息、修改帳單設定）時，以一個服務帳號的身分送出請求，<a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> 依這個身分被授予的角色決定是否允許這個請求。GCP 的服務帳號以 email 形式的位址識別（<code>&lt;name&gt;@&lt;project-id&gt;.iam.gserviceaccount.com</code>），在 IAM 綁定裡寫成 <code>serviceAccount:&lt;位址&gt;</code>；AWS 對應的是 IAM role 被服務或程式承接（assume，AWS 中文文件寫作「擔任」）的用法。</p>
<h2 id="概念位置">概念位置</h2>
<p>服務帳號在 IAM 裡同時是一個身分、也是一個資源，權限也因此分成兩邊來設：</p>
<ul>
<li><strong>它是一個身分：它能做什麼</strong>。在 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> 授予它角色，決定它能對哪些資源做什麼。例如一個<a href="/blog/devops/infra/cloud-providers/gcp/spend-protection-gcp-budget-killswitch/" data-link-title="GCP 上的雲端費用失控防護：預算警示、強制執行支出上限與自動停用帳單的作用範圍、反應時間與判斷" data-link-desc="GCP 上為費用失控設防的機制：預算警示寄出通知的時機、範圍、抵免與通知對象、強制執行支出上限涵蓋的服務與暫停期間，以預算通知觸發函式停止用量的兩種處置（解除帳單連結、只停止花錢的資源）該選哪一種，帳單資料延遲對這些機制反應時間的影響，停損開關放在另一個專案的理由，以及驗收為什麼分成真實通知與測試訊息兩段、停用後的恢復為什麼要先調預算">依預算通知停用帳單的函式</a>，它的服務帳號要有解除專案帳單連結的角色。</li>
<li><strong>它是一個資源：誰能以它的身分行事</strong>。別的身分要「以它的身分行事」（把它掛到 VM 上、用它部署函式、用它的身分呼叫另一個服務），要對這個服務帳號本身持有角色。例如在啟用 OS Login 的 VM 上，登入一台掛著服務帳號的 VM 的人，要有那個服務帳號的 <code>roles/iam.serviceAccountUser</code>。</li>
</ul>
<p>兩邊漏設時的症狀不同。「誰能以它的身分行事」那一邊漏設時，部署或掛載的那一步就被拒絕；「它能做什麼」那一邊漏設時，部署與元件狀態都顯示正常，要到程式實際呼叫 API 的那一刻請求才被拒絕。</p>
<p>程式取得服務帳號憑證的方式有兩種：在雲端裡執行的程式（VM、函式、容器）由平台自動提供短期憑證，不需要金鑰；在雲端外面的程式（CI、地端伺服器）可以下載一把長期金鑰，或改用 <a href="/blog/backend/knowledge-cards/workload-identity/" data-link-title="Workload Identity" data-link-desc="用於機器工作負載的身份語意與授權邊界">workload identity</a> 以外部平台簽發的 token 換到短期憑證。長期金鑰外洩等於這個身分外洩，能用短期憑證時不發金鑰。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>IAM 綁定裡出現 <code>serviceAccount:</code> 開頭的成員：<code>gcloud projects get-iam-policy &lt;project-id&gt;</code> 列得出專案層級的綁定</li>
<li>VM 掛著哪個服務帳號：<code>gcloud compute instances describe &lt;instance-name&gt; --zone=&lt;zone&gt;</code> 輸出的 <code>serviceAccounts[].email</code>；用主控台預設值建立的 VM 會掛上 Compute Engine 預設服務帳號（<code>&lt;project-number&gt;-compute@developer.gserviceaccount.com</code>），instance template 的 <code>describe</code> 對它顯示為 <code>default</code></li>
<li>程式的錯誤訊息是 <code>The caller does not have permission</code> 或 403，而部署與狀態都顯示正常：服務帳號缺少這個 API 呼叫需要的權限，用 <code>gcloud iam roles describe &lt;role&gt;</code> 查它持有的角色裡有沒有那一項；錯誤訊息沒寫權限名稱時，API 參考文件裡那個方法的頁面列出它需要的權限（例如 <code>projects.getBillingInfo</code> 需要 <code>resourcemanager.projects.get</code>）</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<ul>
<li><strong>一個用途一個服務帳號</strong>：每個程式或每類工作用自己的服務帳號，角色只給它需要的那幾個（<a href="/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">least privilege</a>）；共用預設服務帳號時，任何一個程式被入侵都拿得到其他程式的權限</li>
<li><strong>查角色實際包含哪些權限</strong>：角色名稱看起來管的那件事，角色本身不一定涵蓋其中的每一個動作，<code>gcloud iam roles describe &lt;role&gt;</code> 列得出它的權限清單；例如 GCP 的 <code>roles/billing.projectManager</code> 只能建立與刪除專案的帳單連結，讀取帳單狀態要另外的權限</li>
<li><strong>呼叫鏈上的每一段都要授權</strong>：由事件觸發的程式（Pub/Sub 推送、Eventarc 觸發器）有「觸發器以哪個身分呼叫程式」與「程式以哪個身分呼叫 API」兩段，兩段各自要有角色</li>
<li><strong>設定完以實際觸發驗收</strong>：「它能做什麼」那一邊的缺漏在部署時看不出來，所以設定完要實際觸發一次它要做的事、看結果，而不是看部署的回傳值，做法見<a href="/blog/report/protection-proves-itself-by-firing/" data-link-title="平常不動作的防護機制的部署驗證：設定被接受、元件狀態正常、執行失敗被吞掉與測試從中途注入這些提早出現的成功訊號，驗證查詢與處置指令的假陰性，以及合成觸發本身有損害時的分段驗證" data-link-desc="部署停損開關、稽核紀錄、告警或自動處置這類平常不動作的防護機制之後，要判斷它算不算部署完成時讀；也用在驗收紀錄寫著「部署成功」「狀態 ACTIVE」「設定已套用」、或驗證查詢回傳零筆就被當成結論的時候">平常不動作的防護機制的部署驗證</a></li>
<li><strong>「以它的身分行事」的權限也是權限</strong>：持有某個服務帳號的 <code>roles/iam.serviceAccountUser</code> 或可以產生它的 token 的人，等同持有那個服務帳號的全部角色，盤點權限時要一起算</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> — 服務帳號的角色由 IAM 授予</li>
<li><a href="/blog/backend/knowledge-cards/workload-identity/" data-link-title="Workload Identity" data-link-desc="用於機器工作負載的身份語意與授權邊界">Workload Identity</a> — 雲端外的程式不發金鑰、以短期憑證取得服務帳號身分</li>
<li><a href="/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">Least Privilege（最小權限）</a> — 服務帳號的角色範圍原則</li>
<li><a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a> — 登入掛著服務帳號的 VM 時另外需要 <code>roles/iam.serviceAccountUser</code></li>
</ul>
]]></content:encoded></item><item><title>Availability Zone（可用區）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/availability-zone/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/availability-zone/</guid><description>&lt;p>可用區（availability zone）是雲端區域（region）裡一個或一組彼此隔離的資料中心，各自有獨立的電力、冷卻與網路連線，設計上不會同時故障。同一個區域內的可用區之間以低延遲的專線相連。Google Cloud 叫 zone，AWS 叫 Availability Zone（AZ），Oracle Cloud 叫 availability domain（AD）。可用區之下還有一層更小的隔離單位，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/fault-domain/" data-link-title="Fault Domain（故障域）" data-link-desc="建立 Oracle Cloud 主機時看到 fault domain 欄位、要讓同一個服務的幾台主機不落在同一組實體硬體上，或所在區域只有一個可用區、要找可用區以外的隔離手段時，查故障域的範圍、它防的事件與各家對應的放置政策">Fault Domain（故障域）&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>雲端資源的位置由大到小是區域、可用區、故障域。區域決定的是地理位置帶來的性質（延遲、出口 IP 的國家、資料受哪個國家的法規管轄，以及可選的機型；各項的說明在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域&lt;/a> 的〈區域（region）：主機在哪個城市〉），可用區決定的是「同一個城市裡哪一棟」，作用在兩件事上：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>高可用&lt;/strong>：同一個服務的多台主機分散在不同可用區，一棟資料中心停電或斷線時其他棟的主機繼續服務。只放在一個可用區的服務，那個可用區故障時整個服務停擺。&lt;/li>
&lt;li>&lt;strong>資源的搭配限制&lt;/strong>：主機、開機磁碟、區塊磁碟這類資源屬於某一個可用區，主機只能掛同一個可用區的磁碟；&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> 在 AWS 上也綁在一個可用區，在 Google Cloud 與 Oracle Cloud 上可以跨可用區（Oracle Cloud 的 regional subnet）。&lt;/li>
&lt;/ul>
&lt;p>一個區域有幾個可用區依業者與區域而定。Oracle Cloud 的商用區域多數只有一個 AD，官方〈Regions and Availability Domains〉的區域表在 2026-10-24 列出有三個的是法蘭克福、倫敦、阿什本、芝加哥、鳳凰城，這份名單隨業者開新區域而變，選區時回到那張表看。只有一個可用區的區域做不到跨可用區的高可用，那時的做法見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/fault-domain/" data-link-title="Fault Domain（故障域）" data-link-desc="建立 Oracle Cloud 主機時看到 fault domain 欄位、要讓同一個服務的幾台主機不落在同一組實體硬體上，或所在區域只有一個可用區、要找可用區以外的隔離手段時，查故障域的範圍、它防的事件與各家對應的放置政策">Fault Domain（故障域）&lt;/a> 的〈設計責任〉。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>建立主機時錯誤訊息寫「in availability domain AD-1」「in zone asia-east1-b」這類位置：容量是按可用區分配的，換一個可用區可能就建得起來&lt;/li>
&lt;li>從保留下來的開機磁碟重建主機時，可選的可用區只剩那顆磁碟所在的那一個&lt;/li>
&lt;li>Oracle Cloud 的 AD 完整名稱帶有 tenancy 專屬的前綴（例如 &lt;code>XXXX:AP-BATAM-1-AD-1&lt;/code>），不同帳號看到的前綴不同，指令裡要用 &lt;code>oci iam availability-domain list&lt;/code> 查到的完整名稱&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>單一台主機&lt;/strong>：可用區只影響能否拿到容量，選建得起來的那一個即可&lt;/li>
&lt;li>&lt;strong>要做高可用的服務&lt;/strong>：至少兩個可用區各放一份，負載平衡器與資料庫的副本也要跨可用區；跨可用區的流量在 AWS 與 Google Cloud 另外計費，費率見各家價格頁的資料傳輸那一項&lt;/li>
&lt;li>&lt;strong>選區域時&lt;/strong>：服務需要跨可用區的高可用時，選有多個可用區的區域&lt;/li>
&lt;li>&lt;strong>判斷兩台主機會不會一起停&lt;/strong>：可用區是對整棟資料中心停電、斷線這一類失效分開的一層失效域；兩台主機在不同可用區，仍可能因為共用同一個區域、帳號或 DNS 而一起停，逐項列共用依賴的做法見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/failure-domain/" data-link-title="Failure Domain（失效域）" data-link-desc="判斷兩個元件會不會因為同一件事一起停掉、備援或監控是否獨立於被保護的對象時，查它們共用了哪些會失效的依賴">Failure Domain（失效域）&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/fault-domain/" data-link-title="Fault Domain（故障域）" data-link-desc="建立 Oracle Cloud 主機時看到 fault domain 欄位、要讓同一個服務的幾台主機不落在同一組實體硬體上，或所在區域只有一個可用區、要找可用區以外的隔離手段時，查故障域的範圍、它防的事件與各家對應的放置政策">Fault Domain（故障域）&lt;/a> — 可用區內部更小一層的硬體隔離&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC&lt;/a> — 虛擬網路跨可用區還是綁在單一可用區，依業者而定&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> — AWS 的 subnet 綁在一個可用區，決定主機落在哪一棟&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域&lt;/a> — 建立主機時這三層選項的取捨&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>可用區（availability zone）是雲端區域（region）裡一個或一組彼此隔離的資料中心，各自有獨立的電力、冷卻與網路連線，設計上不會同時故障。同一個區域內的可用區之間以低延遲的專線相連。Google Cloud 叫 zone，AWS 叫 Availability Zone（AZ），Oracle Cloud 叫 availability domain（AD）。可用區之下還有一層更小的隔離單位，見 <a href="/blog/devops/infra/knowledge-cards/fault-domain/" data-link-title="Fault Domain（故障域）" data-link-desc="建立 Oracle Cloud 主機時看到 fault domain 欄位、要讓同一個服務的幾台主機不落在同一組實體硬體上，或所在區域只有一個可用區、要找可用區以外的隔離手段時，查故障域的範圍、它防的事件與各家對應的放置政策">Fault Domain（故障域）</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>雲端資源的位置由大到小是區域、可用區、故障域。區域決定的是地理位置帶來的性質（延遲、出口 IP 的國家、資料受哪個國家的法規管轄，以及可選的機型；各項的說明在 <a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a> 的〈區域（region）：主機在哪個城市〉），可用區決定的是「同一個城市裡哪一棟」，作用在兩件事上：</p>
<ul>
<li><strong>高可用</strong>：同一個服務的多台主機分散在不同可用區，一棟資料中心停電或斷線時其他棟的主機繼續服務。只放在一個可用區的服務，那個可用區故障時整個服務停擺。</li>
<li><strong>資源的搭配限制</strong>：主機、開機磁碟、區塊磁碟這類資源屬於某一個可用區，主機只能掛同一個可用區的磁碟；<a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> 在 AWS 上也綁在一個可用區，在 Google Cloud 與 Oracle Cloud 上可以跨可用區（Oracle Cloud 的 regional subnet）。</li>
</ul>
<p>一個區域有幾個可用區依業者與區域而定。Oracle Cloud 的商用區域多數只有一個 AD，官方〈Regions and Availability Domains〉的區域表在 2026-10-24 列出有三個的是法蘭克福、倫敦、阿什本、芝加哥、鳳凰城，這份名單隨業者開新區域而變，選區時回到那張表看。只有一個可用區的區域做不到跨可用區的高可用，那時的做法見 <a href="/blog/devops/infra/knowledge-cards/fault-domain/" data-link-title="Fault Domain（故障域）" data-link-desc="建立 Oracle Cloud 主機時看到 fault domain 欄位、要讓同一個服務的幾台主機不落在同一組實體硬體上，或所在區域只有一個可用區、要找可用區以外的隔離手段時，查故障域的範圍、它防的事件與各家對應的放置政策">Fault Domain（故障域）</a> 的〈設計責任〉。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>建立主機時錯誤訊息寫「in availability domain AD-1」「in zone asia-east1-b」這類位置：容量是按可用區分配的，換一個可用區可能就建得起來</li>
<li>從保留下來的開機磁碟重建主機時，可選的可用區只剩那顆磁碟所在的那一個</li>
<li>Oracle Cloud 的 AD 完整名稱帶有 tenancy 專屬的前綴（例如 <code>XXXX:AP-BATAM-1-AD-1</code>），不同帳號看到的前綴不同，指令裡要用 <code>oci iam availability-domain list</code> 查到的完整名稱</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<ul>
<li><strong>單一台主機</strong>：可用區只影響能否拿到容量，選建得起來的那一個即可</li>
<li><strong>要做高可用的服務</strong>：至少兩個可用區各放一份，負載平衡器與資料庫的副本也要跨可用區；跨可用區的流量在 AWS 與 Google Cloud 另外計費，費率見各家價格頁的資料傳輸那一項</li>
<li><strong>選區域時</strong>：服務需要跨可用區的高可用時，選有多個可用區的區域</li>
<li><strong>判斷兩台主機會不會一起停</strong>：可用區是對整棟資料中心停電、斷線這一類失效分開的一層失效域；兩台主機在不同可用區，仍可能因為共用同一個區域、帳號或 DNS 而一起停，逐項列共用依賴的做法見 <a href="/blog/backend/knowledge-cards/failure-domain/" data-link-title="Failure Domain（失效域）" data-link-desc="判斷兩個元件會不會因為同一件事一起停掉、備援或監控是否獨立於被保護的對象時，查它們共用了哪些會失效的依賴">Failure Domain（失效域）</a></li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/fault-domain/" data-link-title="Fault Domain（故障域）" data-link-desc="建立 Oracle Cloud 主機時看到 fault domain 欄位、要讓同一個服務的幾台主機不落在同一組實體硬體上，或所在區域只有一個可用區、要找可用區以外的隔離手段時，查故障域的範圍、它防的事件與各家對應的放置政策">Fault Domain（故障域）</a> — 可用區內部更小一層的硬體隔離</li>
<li><a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> — 虛擬網路跨可用區還是綁在單一可用區，依業者而定</li>
<li><a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> — AWS 的 subnet 綁在一個可用區，決定主機落在哪一棟</li>
<li><a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a> — 建立主機時這三層選項的取捨</li>
</ul>
]]></content:encoded></item><item><title>Fault Domain（故障域）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/fault-domain/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/fault-domain/</guid><description>&lt;p>故障域（fault domain）在這張卡上指 Oracle Cloud 建立主機時選的那個隔離單位：一個 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）&lt;/a> 內部的一組實體硬體與基礎設施。它是 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/failure-domain/" data-link-title="Failure Domain（失效域）" data-link-desc="判斷兩個元件會不會因為同一件事一起停掉、備援或監控是否獨立於被保護的對象時，查它們共用了哪些會失效的依賴">Failure Domain（失效域）&lt;/a> 這個一般概念的一種業者實作：失效域是會因同一個原因一起停掉的元件集合，Oracle Cloud 把可用區內的硬體組直接命名為 fault domain。Oracle Cloud 的每個可用區有三個故障域，官方說明它防的是兩種事件：某一組硬體的非預期故障，以及業者對那一組硬體做的計畫性維護。一個故障域受影響時，放在其他故障域的主機不受影響。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>故障域提供的是反親和性（anti-affinity）：把幾台主機刻意放在不同的實體硬體上，讓一次硬體事件不會讓它們同時停擺。它比&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">可用區&lt;/a>小一層——可用區隔離的是整棟資料中心的電力與網路，故障域隔離的是同一棟裡的機櫃與實體主機。&lt;/p>
&lt;p>Oracle Cloud 以「故障域」這個名稱讓使用者在建立主機時直接選；AWS 與 Google Cloud 沒有對應的欄位名稱，同一個效果用放置政策取得：AWS 的 placement group（spread 策略）、Google Cloud 的 spread placement policy，作用都是讓一組主機分散到不同的實體硬體上。AWS 要先停止主機才能換進或移出 placement group。Google Cloud 的 spread placement policy 指定了多個 availability domain（官方說明是彼此隔離的一組硬體）時，可以不停機就套用到既有的主機，而套用時主機可能被搬到另一個 availability domain，搬的方式依主機的維護政策是即時遷移或停止主機。&lt;/p>
&lt;p>只有一台主機時，故障域沒有選擇的意義，那台主機總是只在一組硬體上。Oracle Cloud 建立頁的預設「讓 Oracle 選擇」會依當下容量挑一個，而手動指定會縮小可用的容量。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>建立主機時收到 &lt;code>Out of host capacity&lt;/code>：Oracle Cloud 的 Compute〈Known Issues〉頁列的處置之一是不指定 fault domain 再建一次，因為指定故障域等於只接受那一個故障域裡的硬體，可用容量跟著變小&lt;/li>
&lt;li>同一個服務的兩台主機在主機詳細資訊裡顯示同一個 &lt;code>FAULT-DOMAIN-&amp;lt;N&amp;gt;&lt;/code>：一次硬體維護可能同時重開兩台&lt;/li>
&lt;li>&lt;code>oci iam availability-domain list&lt;/code> 只回傳一筆：這個區域只有一個可用區，跨可用區做不到，故障域是同一個區域內剩下的硬體隔離手段&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>同一個服務兩台以上的主機&lt;/strong>：區域有多個可用區時先分到不同可用區，故障域用在同一個可用區之內；區域只有一個可用區時，分散到不同故障域。Oracle Cloud 允許建立之後再修改故障域&lt;/li>
&lt;li>&lt;strong>只有一個可用區的區域&lt;/strong>：高可用的設計從跨可用區改成跨故障域，並接受整個可用區故障時服務會停的風險，或另外跨區域&lt;/li>
&lt;li>&lt;strong>單一台主機&lt;/strong>：維持讓業者選&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）&lt;/a> — 故障域上面那一層的隔離單位&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/managed-instance-group/" data-link-title="Managed Instance Group（MIG，代管執行個體群組）" data-link-desc="VM 被自動重建之後設定不見、要決定一組 VM 的設定該寫在哪裡，或要判斷一台 VM 能不能直接刪掉重建時，查 GCP 的 MIG 與 instance template 怎麼建立、修復與更新 VM">Managed Instance Group（MIG，代管執行個體群組）&lt;/a> — 一組主機自動分散與修復的機制&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域&lt;/a> — 建立主機時這三層選項的取捨&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>故障域（fault domain）在這張卡上指 Oracle Cloud 建立主機時選的那個隔離單位：一個 <a href="/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）</a> 內部的一組實體硬體與基礎設施。它是 <a href="/blog/backend/knowledge-cards/failure-domain/" data-link-title="Failure Domain（失效域）" data-link-desc="判斷兩個元件會不會因為同一件事一起停掉、備援或監控是否獨立於被保護的對象時，查它們共用了哪些會失效的依賴">Failure Domain（失效域）</a> 這個一般概念的一種業者實作：失效域是會因同一個原因一起停掉的元件集合，Oracle Cloud 把可用區內的硬體組直接命名為 fault domain。Oracle Cloud 的每個可用區有三個故障域，官方說明它防的是兩種事件：某一組硬體的非預期故障，以及業者對那一組硬體做的計畫性維護。一個故障域受影響時，放在其他故障域的主機不受影響。</p>
<h2 id="概念位置">概念位置</h2>
<p>故障域提供的是反親和性（anti-affinity）：把幾台主機刻意放在不同的實體硬體上，讓一次硬體事件不會讓它們同時停擺。它比<a href="/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">可用區</a>小一層——可用區隔離的是整棟資料中心的電力與網路，故障域隔離的是同一棟裡的機櫃與實體主機。</p>
<p>Oracle Cloud 以「故障域」這個名稱讓使用者在建立主機時直接選；AWS 與 Google Cloud 沒有對應的欄位名稱，同一個效果用放置政策取得：AWS 的 placement group（spread 策略）、Google Cloud 的 spread placement policy，作用都是讓一組主機分散到不同的實體硬體上。AWS 要先停止主機才能換進或移出 placement group。Google Cloud 的 spread placement policy 指定了多個 availability domain（官方說明是彼此隔離的一組硬體）時，可以不停機就套用到既有的主機，而套用時主機可能被搬到另一個 availability domain，搬的方式依主機的維護政策是即時遷移或停止主機。</p>
<p>只有一台主機時，故障域沒有選擇的意義，那台主機總是只在一組硬體上。Oracle Cloud 建立頁的預設「讓 Oracle 選擇」會依當下容量挑一個，而手動指定會縮小可用的容量。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>建立主機時收到 <code>Out of host capacity</code>：Oracle Cloud 的 Compute〈Known Issues〉頁列的處置之一是不指定 fault domain 再建一次，因為指定故障域等於只接受那一個故障域裡的硬體，可用容量跟著變小</li>
<li>同一個服務的兩台主機在主機詳細資訊裡顯示同一個 <code>FAULT-DOMAIN-&lt;N&gt;</code>：一次硬體維護可能同時重開兩台</li>
<li><code>oci iam availability-domain list</code> 只回傳一筆：這個區域只有一個可用區，跨可用區做不到，故障域是同一個區域內剩下的硬體隔離手段</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<ul>
<li><strong>同一個服務兩台以上的主機</strong>：區域有多個可用區時先分到不同可用區，故障域用在同一個可用區之內；區域只有一個可用區時，分散到不同故障域。Oracle Cloud 允許建立之後再修改故障域</li>
<li><strong>只有一個可用區的區域</strong>：高可用的設計從跨可用區改成跨故障域，並接受整個可用區故障時服務會停的風險，或另外跨區域</li>
<li><strong>單一台主機</strong>：維持讓業者選</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）</a> — 故障域上面那一層的隔離單位</li>
<li><a href="/blog/devops/infra/knowledge-cards/managed-instance-group/" data-link-title="Managed Instance Group（MIG，代管執行個體群組）" data-link-desc="VM 被自動重建之後設定不見、要決定一組 VM 的設定該寫在哪裡，或要判斷一台 VM 能不能直接刪掉重建時，查 GCP 的 MIG 與 instance template 怎麼建立、修復與更新 VM">Managed Instance Group（MIG，代管執行個體群組）</a> — 一組主機自動分散與修復的機制</li>
<li><a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a> — 建立主機時這三層選項的取捨</li>
</ul>
]]></content:encoded></item><item><title>Instance Metadata Service（實例中繼資料服務）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/instance-metadata-service/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/instance-metadata-service/</guid><description>&lt;p>實例中繼資料服務（instance metadata service，IMDS）是雲端業者在每台主機上提供的一個 HTTP 端點，位址固定是 &lt;code>169.254.169.254&lt;/code>，只有主機自己連得到。主機上的程式向它查詢這台主機的資訊：主機 ID、所在區域與可用區、網路設定、建立時放進去的 SSH 公鑰與初始化腳本，以及業者發給這台主機的身分憑證（讓主機上的程式不必存放長期金鑰就能呼叫雲端 API，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/service-account/" data-link-title="Service Account（服務帳號）" data-link-desc="程式、排程或雲端服務要以自己的身分呼叫雲端 API、要決定給它哪些角色、遇到「狀態正常卻呼叫失敗」的權限問題、或看到 IAM 綁定裡的 serviceAccount: 成員時，查服務帳號是什麼、它的權限要從「它能做什麼」與「誰能以它的身分行事」兩邊設，以及它和人的帳號在管理上的差別">Service Account（服務帳號）&lt;/a>）。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>開機流程依賴它：cloud-init 在主機第一次開機時從中繼資料服務讀出 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 公鑰寫進 &lt;code>authorized_keys&lt;/code>、讀出初始化腳本並執行。業者的代理程式也從這裡讀主機的設定。&lt;/p>
&lt;p>同一個特性也是它的風險：主機上任何能發 HTTP 請求的程式都讀得到它。網頁應用程式有 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/ssrf/" data-link-title="SSRF" data-link-desc="說明伺服器端請求被濫用時如何存取內部網路或 metadata 服務">SSRF&lt;/a>（server-side request forgery，伺服器端請求偽造）漏洞時，攻擊者可以讓應用程式替他去讀 &lt;code>169.254.169.254&lt;/code>，取得主機的身分憑證，再拿那份憑證在雲端帳號裡以主機的權限行事。&lt;/p>
&lt;p>第二版的存取方式（AWS 與 Oracle Cloud 叫 IMDSv2）要求請求帶上證明：AWS 要先用 PUT 請求取得一個 token、之後每個請求在標頭帶著它，Oracle Cloud 要每個請求帶 &lt;code>Authorization: Bearer Oracle&lt;/code> 標頭並改打 &lt;code>/opc/v2&lt;/code> 路徑。兩版可以並存，主機的設定改成只允許第二版之後，不帶證明的第一版請求才會被拒絕。多數 SSRF 漏洞只讓攻擊者控制網址、控制不了標頭，所以這一個要求就擋掉了大部分的攻擊路徑。Google Cloud 的中繼資料伺服器固定要求 &lt;code>Metadata-Flavor: Google&lt;/code> 標頭，作用的方向相同：擋掉只能控制網址、帶不了標頭的請求；AWS 的 token 另外要先用 PUT 取得，擋的範圍更大一些。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>建立頁上有「Require an authorization header」（Oracle Cloud）、「Metadata version: V2 only (token required)」（AWS）這類選項&lt;/li>
&lt;li>主機上的舊程式或舊版工具讀不到中繼資料、請求回錯誤：它還在用第一版的請求方式&lt;/li>
&lt;li>安全掃描報告指出某台主機允許 IMDSv1：那台主機上的應用程式一旦有 SSRF 漏洞，身分憑證就讀得到&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>新主機&lt;/strong>：映像檔支援第二版時開啟強制要求。Oracle Cloud 官方列出支援的平台映像是 Oracle Linux 與 Oracle Autonomous Linux，其他平台映像（包括 Ubuntu）、多數自訂映像與多數市集映像不支援；自訂的 Linux 映像在 cloud-init 20.3 以上、Oracle Cloud Agent 0.0.19 以上時可能支援（官方給自訂映像的條件）。在不支援的映像上強制第二版，cloud-init 讀不到公鑰，主機會連不上。Oracle Cloud 上這個設定建立後可以在主機頁面改回兩版都允許、再重開，判斷與處置見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤&lt;/a> 的〈中繼資料服務與 Authorization header〉&lt;/li>
&lt;li>&lt;strong>既有主機改成只允許第二版&lt;/strong>：先確認主機上讀中繼資料的程式（代理程式、SDK、自己的腳本）都已支援第二版，再切換&lt;/li>
&lt;li>&lt;strong>主機的身分權限&lt;/strong>：主機被授權呼叫雲端 API 時（例如主機上的程式要寫物件儲存），業者經由中繼資料服務把代表主機身分的憑證交給主機上的程式——Oracle Cloud 叫 instance principal（權限由 dynamic group 加 policy 授予），AWS 是掛在主機上的 IAM role（經由 instance profile），Google Cloud 是掛在主機上的服務帳號。這份憑證帶著主機身分的全部權限，所以權限只給這台主機上的程式需要的那些&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/service-account/" data-link-title="Service Account（服務帳號）" data-link-desc="程式、排程或雲端服務要以自己的身分呼叫雲端 API、要決定給它哪些角色、遇到「狀態正常卻呼叫失敗」的權限問題、或看到 IAM 綁定裡的 serviceAccount: 成員時，查服務帳號是什麼、它的權限要從「它能做什麼」與「誰能以它的身分行事」兩邊設，以及它和人的帳號在管理上的差別">Service Account（服務帳號）&lt;/a> — 中繼資料服務發放的憑證代表的身分&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> — 決定那份憑證能做什麼&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤&lt;/a> — 建立主機時的中繼資料服務選項&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>實例中繼資料服務（instance metadata service，IMDS）是雲端業者在每台主機上提供的一個 HTTP 端點，位址固定是 <code>169.254.169.254</code>，只有主機自己連得到。主機上的程式向它查詢這台主機的資訊：主機 ID、所在區域與可用區、網路設定、建立時放進去的 SSH 公鑰與初始化腳本，以及業者發給這台主機的身分憑證（讓主機上的程式不必存放長期金鑰就能呼叫雲端 API，見 <a href="/blog/devops/infra/knowledge-cards/service-account/" data-link-title="Service Account（服務帳號）" data-link-desc="程式、排程或雲端服務要以自己的身分呼叫雲端 API、要決定給它哪些角色、遇到「狀態正常卻呼叫失敗」的權限問題、或看到 IAM 綁定裡的 serviceAccount: 成員時，查服務帳號是什麼、它的權限要從「它能做什麼」與「誰能以它的身分行事」兩邊設，以及它和人的帳號在管理上的差別">Service Account（服務帳號）</a>）。</p>
<h2 id="概念位置">概念位置</h2>
<p>開機流程依賴它：cloud-init 在主機第一次開機時從中繼資料服務讀出 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 公鑰寫進 <code>authorized_keys</code>、讀出初始化腳本並執行。業者的代理程式也從這裡讀主機的設定。</p>
<p>同一個特性也是它的風險：主機上任何能發 HTTP 請求的程式都讀得到它。網頁應用程式有 <a href="/blog/backend/knowledge-cards/ssrf/" data-link-title="SSRF" data-link-desc="說明伺服器端請求被濫用時如何存取內部網路或 metadata 服務">SSRF</a>（server-side request forgery，伺服器端請求偽造）漏洞時，攻擊者可以讓應用程式替他去讀 <code>169.254.169.254</code>，取得主機的身分憑證，再拿那份憑證在雲端帳號裡以主機的權限行事。</p>
<p>第二版的存取方式（AWS 與 Oracle Cloud 叫 IMDSv2）要求請求帶上證明：AWS 要先用 PUT 請求取得一個 token、之後每個請求在標頭帶著它，Oracle Cloud 要每個請求帶 <code>Authorization: Bearer Oracle</code> 標頭並改打 <code>/opc/v2</code> 路徑。兩版可以並存，主機的設定改成只允許第二版之後，不帶證明的第一版請求才會被拒絕。多數 SSRF 漏洞只讓攻擊者控制網址、控制不了標頭，所以這一個要求就擋掉了大部分的攻擊路徑。Google Cloud 的中繼資料伺服器固定要求 <code>Metadata-Flavor: Google</code> 標頭，作用的方向相同：擋掉只能控制網址、帶不了標頭的請求；AWS 的 token 另外要先用 PUT 取得，擋的範圍更大一些。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>建立頁上有「Require an authorization header」（Oracle Cloud）、「Metadata version: V2 only (token required)」（AWS）這類選項</li>
<li>主機上的舊程式或舊版工具讀不到中繼資料、請求回錯誤：它還在用第一版的請求方式</li>
<li>安全掃描報告指出某台主機允許 IMDSv1：那台主機上的應用程式一旦有 SSRF 漏洞，身分憑證就讀得到</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<ul>
<li><strong>新主機</strong>：映像檔支援第二版時開啟強制要求。Oracle Cloud 官方列出支援的平台映像是 Oracle Linux 與 Oracle Autonomous Linux，其他平台映像（包括 Ubuntu）、多數自訂映像與多數市集映像不支援；自訂的 Linux 映像在 cloud-init 20.3 以上、Oracle Cloud Agent 0.0.19 以上時可能支援（官方給自訂映像的條件）。在不支援的映像上強制第二版，cloud-init 讀不到公鑰，主機會連不上。Oracle Cloud 上這個設定建立後可以在主機頁面改回兩版都允許、再重開，判斷與處置見 <a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a> 的〈中繼資料服務與 Authorization header〉</li>
<li><strong>既有主機改成只允許第二版</strong>：先確認主機上讀中繼資料的程式（代理程式、SDK、自己的腳本）都已支援第二版，再切換</li>
<li><strong>主機的身分權限</strong>：主機被授權呼叫雲端 API 時（例如主機上的程式要寫物件儲存），業者經由中繼資料服務把代表主機身分的憑證交給主機上的程式——Oracle Cloud 叫 instance principal（權限由 dynamic group 加 policy 授予），AWS 是掛在主機上的 IAM role（經由 instance profile），Google Cloud 是掛在主機上的服務帳號。這份憑證帶著主機身分的全部權限，所以權限只給這台主機上的程式需要的那些</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/service-account/" data-link-title="Service Account（服務帳號）" data-link-desc="程式、排程或雲端服務要以自己的身分呼叫雲端 API、要決定給它哪些角色、遇到「狀態正常卻呼叫失敗」的權限問題、或看到 IAM 綁定裡的 serviceAccount: 成員時，查服務帳號是什麼、它的權限要從「它能做什麼」與「誰能以它的身分行事」兩邊設，以及它和人的帳號在管理上的差別">Service Account（服務帳號）</a> — 中繼資料服務發放的憑證代表的身分</li>
<li><a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> — 決定那份憑證能做什麼</li>
<li><a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a> — 建立主機時的中繼資料服務選項</li>
</ul>
]]></content:encoded></item><item><title>CDN（內容遞送網路）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cdn/</link><pubDate>Sun, 25 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cdn/</guid><description>&lt;p>CDN（content delivery network，內容遞送網路）是一組分散在各地機房的伺服器，代替網站的源站（實際跑網站程式的主機）接收使用者的連線：使用者連到離自己近的 CDN 機房，CDN 能用快取回應的就直接回，不能的再從自己的機房連回源站取回應，這個動作叫回源。決定使用者連到哪裡的是網域的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dns/" data-link-title="DNS" data-link-desc="Domain Name System — 把域名轉成 IP 位址的系統，以及 A record、CNAME、NS、TTL 的角色">DNS&lt;/a> 記錄：記錄設成經由 CDN 代理時，解析網域拿到的是 CDN 的位址。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>CDN 排在請求路徑的最前面，在源站的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy&lt;/a> 與應用之前，承擔的是邊緣的遞送、快取與過濾（&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/waf/" data-link-title="WAF" data-link-desc="說明 Web Application Firewall 如何在入口層過濾常見攻擊與濫用">WAF&lt;/a> 規則、速率限制、依國家封鎖）。這些過濾只對經過 CDN 的請求作用：源站的位址一旦被知道，請求可以跳過 CDN 直接連源站，所以 CDN 後面的源站要另外在雲端的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> 或主機防火牆上只放行 CDN 的位址。CDN 回源時用的是它自己機房的位址，經由 CDN 送來的請求，源站看到的連線來源因此是 CDN 的位址，使用者真正的位址放在 CDN 加上的 HTTP 標頭裡（Cloudflare 是 &lt;code>CF-Connecting-IP&lt;/code>）。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>回應標頭帶著 CDN 的標記&lt;/strong>：Cloudflare 代理的網站回應帶 &lt;code>cf-ray&lt;/code> 與 &lt;code>server: cloudflare&lt;/code>。看到它代表這一次請求經過了 CDN，不代表所有訪客都經過：其他訪客的 DNS 快取可能還指向源站的舊位址。&lt;/li>
&lt;li>&lt;strong>源站的存取記錄裡全是同幾段位址&lt;/strong>：那是 CDN 機房的位址；要記錄使用者的位址，反向代理要設定成只信任來自 CDN 位址段的 &lt;code>CF-Connecting-IP&lt;/code>（nginx 的 &lt;code>set_real_ip_from&lt;/code> 加 &lt;code>real_ip_header&lt;/code>）。這個設定擋得下不經 CDN 的直連偽造，擋不下從 CDN 位址送來的偽造：任何帳號的 Cloudflare Worker 都從 Cloudflare 的位址送出、標頭可以自己寫，實測見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/cloudflare-origin-pull-walkthrough/" data-link-title="Cloudflare 回源的實測紀錄：Oracle Cloud NSG 只放行 Cloudflare 位址與規則數上限、回源的來源位址、Authenticated Origin Pulls、Worker 偽造 CF-Connecting-IP 與 Tunnel 的出站連接埠" data-link-desc="源站要放在 Cloudflare 後面、想確認只放行 Cloudflare 位址的雲端防火牆放不放得下整份清單、回源實際用哪些位址、Authenticated Origin Pulls 怎麼驗證、Cloudflare Worker 能不能穿過這道防火牆並偽造 CF-Connecting-IP、或 Tunnel 要開哪些出站連接埠時，查 Oracle Cloud 與 Cloudflare 免費方案上實際跑過的指令與輸出">Cloudflare 回源的實測紀錄：Oracle Cloud NSG 只放行 Cloudflare 位址與規則數上限、回源的來源位址、Authenticated Origin Pulls、Worker 偽造 CF-Connecting-IP 與 Tunnel 的出站連接埠&lt;/a>。&lt;/li>
&lt;li>&lt;strong>CDN 公佈的位址清單&lt;/strong>：Cloudflare 在 &lt;code>https://www.cloudflare.com/ips-v4&lt;/code> 與 &lt;code>ips-v6&lt;/code> 公佈回源用的位址段，2026-10-25 是 15 段 IPv4 與 7 段 IPv6；源站的防火牆規則就是拿這份清單寫的。&lt;/li>
&lt;/ul>
&lt;h2 id="判讀方式">判讀方式&lt;/h2>
&lt;p>判斷一條請求經不經過 CDN，看的是使用者解析網域拿到的位址，而不是網站有沒有開 CDN：子網域沒有設成代理、或舊記錄還在快取裡時，那些請求直接到源站，CDN 上的規則管不到它們。CDN 的位址由所有客戶共用，只放行 CDN 位址擋得住不經過 CDN 的直連，擋不住別人用自己的 CDN 帳號指向這台源站；要分得出，要用 CDN 端的回源驗證（Cloudflare 的 Authenticated Origin Pulls 用自己上傳的憑證，或 Tunnel）。源站防火牆的位置、套用時機與 Docker 的陷阱見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/origin-host-firewall/" data-link-title="CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖" data-link-desc="網站放在 CDN（例如 Cloudflare）後面時，源站主機的入站與出站限制：請求避開 CDN 直連源站的成因、來源限制放在雲端防火牆、主機防火牆或 CDN 端驗證的差別、只放行 CDN 位址的套用時機、Docker 發佈的連接埠繞過 ufw 與 firewalld 的路徑與各種限制寫法、CDN 位址送來的請求為什麼不等於 CDN 替自己的網域轉送、出站 DNS 限制的效果與漏洞、地理封鎖放在 CDN 邊緣的理由">CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖&lt;/a>。「origin protection」在快取的語境裡指的是快取未命中時保護源站的負載，見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/origin-protection/" data-link-title="Origin Protection" data-link-desc="快取失效或冷啟動時、miss 流量集中打回正式來源會不會壓垮它——回源保護的手段組合與監控面">Origin Protection&lt;/a>，跟這裡的源站安全是兩件事。&lt;/p></description><content:encoded><![CDATA[<p>CDN（content delivery network，內容遞送網路）是一組分散在各地機房的伺服器，代替網站的源站（實際跑網站程式的主機）接收使用者的連線：使用者連到離自己近的 CDN 機房，CDN 能用快取回應的就直接回，不能的再從自己的機房連回源站取回應，這個動作叫回源。決定使用者連到哪裡的是網域的 <a href="/blog/devops/infra/knowledge-cards/dns/" data-link-title="DNS" data-link-desc="Domain Name System — 把域名轉成 IP 位址的系統，以及 A record、CNAME、NS、TTL 的角色">DNS</a> 記錄：記錄設成經由 CDN 代理時，解析網域拿到的是 CDN 的位址。</p>
<h2 id="概念位置">概念位置</h2>
<p>CDN 排在請求路徑的最前面，在源站的 <a href="/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy</a> 與應用之前，承擔的是邊緣的遞送、快取與過濾（<a href="/blog/backend/knowledge-cards/waf/" data-link-title="WAF" data-link-desc="說明 Web Application Firewall 如何在入口層過濾常見攻擊與濫用">WAF</a> 規則、速率限制、依國家封鎖）。這些過濾只對經過 CDN 的請求作用：源站的位址一旦被知道，請求可以跳過 CDN 直接連源站，所以 CDN 後面的源站要另外在雲端的 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> 或主機防火牆上只放行 CDN 的位址。CDN 回源時用的是它自己機房的位址，經由 CDN 送來的請求，源站看到的連線來源因此是 CDN 的位址，使用者真正的位址放在 CDN 加上的 HTTP 標頭裡（Cloudflare 是 <code>CF-Connecting-IP</code>）。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong>回應標頭帶著 CDN 的標記</strong>：Cloudflare 代理的網站回應帶 <code>cf-ray</code> 與 <code>server: cloudflare</code>。看到它代表這一次請求經過了 CDN，不代表所有訪客都經過：其他訪客的 DNS 快取可能還指向源站的舊位址。</li>
<li><strong>源站的存取記錄裡全是同幾段位址</strong>：那是 CDN 機房的位址；要記錄使用者的位址，反向代理要設定成只信任來自 CDN 位址段的 <code>CF-Connecting-IP</code>（nginx 的 <code>set_real_ip_from</code> 加 <code>real_ip_header</code>）。這個設定擋得下不經 CDN 的直連偽造，擋不下從 CDN 位址送來的偽造：任何帳號的 Cloudflare Worker 都從 Cloudflare 的位址送出、標頭可以自己寫，實測見 <a href="/blog/devops/infra/03-network-foundation/hands-on/cloudflare-origin-pull-walkthrough/" data-link-title="Cloudflare 回源的實測紀錄：Oracle Cloud NSG 只放行 Cloudflare 位址與規則數上限、回源的來源位址、Authenticated Origin Pulls、Worker 偽造 CF-Connecting-IP 與 Tunnel 的出站連接埠" data-link-desc="源站要放在 Cloudflare 後面、想確認只放行 Cloudflare 位址的雲端防火牆放不放得下整份清單、回源實際用哪些位址、Authenticated Origin Pulls 怎麼驗證、Cloudflare Worker 能不能穿過這道防火牆並偽造 CF-Connecting-IP、或 Tunnel 要開哪些出站連接埠時，查 Oracle Cloud 與 Cloudflare 免費方案上實際跑過的指令與輸出">Cloudflare 回源的實測紀錄：Oracle Cloud NSG 只放行 Cloudflare 位址與規則數上限、回源的來源位址、Authenticated Origin Pulls、Worker 偽造 CF-Connecting-IP 與 Tunnel 的出站連接埠</a>。</li>
<li><strong>CDN 公佈的位址清單</strong>：Cloudflare 在 <code>https://www.cloudflare.com/ips-v4</code> 與 <code>ips-v6</code> 公佈回源用的位址段，2026-10-25 是 15 段 IPv4 與 7 段 IPv6；源站的防火牆規則就是拿這份清單寫的。</li>
</ul>
<h2 id="判讀方式">判讀方式</h2>
<p>判斷一條請求經不經過 CDN，看的是使用者解析網域拿到的位址，而不是網站有沒有開 CDN：子網域沒有設成代理、或舊記錄還在快取裡時，那些請求直接到源站，CDN 上的規則管不到它們。CDN 的位址由所有客戶共用，只放行 CDN 位址擋得住不經過 CDN 的直連，擋不住別人用自己的 CDN 帳號指向這台源站；要分得出，要用 CDN 端的回源驗證（Cloudflare 的 Authenticated Origin Pulls 用自己上傳的憑證，或 Tunnel）。源站防火牆的位置、套用時機與 Docker 的陷阱見 <a href="/blog/devops/infra/03-network-foundation/origin-host-firewall/" data-link-title="CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖" data-link-desc="網站放在 CDN（例如 Cloudflare）後面時，源站主機的入站與出站限制：請求避開 CDN 直連源站的成因、來源限制放在雲端防火牆、主機防火牆或 CDN 端驗證的差別、只放行 CDN 位址的套用時機、Docker 發佈的連接埠繞過 ufw 與 firewalld 的路徑與各種限制寫法、CDN 位址送來的請求為什麼不等於 CDN 替自己的網域轉送、出站 DNS 限制的效果與漏洞、地理封鎖放在 CDN 邊緣的理由">CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖</a>。「origin protection」在快取的語境裡指的是快取未命中時保護源站的負載，見 <a href="/blog/backend/knowledge-cards/origin-protection/" data-link-title="Origin Protection" data-link-desc="快取失效或冷啟動時、miss 流量集中打回正式來源會不會壓垮它——回源保護的手段組合與監控面">Origin Protection</a>，跟這裡的源站安全是兩件事。</p>
]]></content:encoded></item><item><title>DNAT（目的位址轉換）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dnat/</link><pubDate>Sun, 25 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dnat/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT Gateway&lt;/a> 改的是出站封包的來源位址（SNAT），讓內部主機借一個公開位址出去；DNAT（destination NAT，目的位址轉換）改的是入站封包的目的地，讓外面的連線送到指定的那一台：封包原本送往「主機的 8081」，改寫之後送往「容器的 80」。回程的封包由 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/conntrack/" data-link-title="conntrack（連線追蹤）" data-link-desc="防火牆規則寫了 established、related 或 ctorigdstport、有狀態防火牆為什麼不必寫回程規則、改了規則卻像沒生效、或位址轉換之後要比對原本的位址時，查 Linux 核心記錄連線狀態的表與它在規則裡的用法">conntrack（連線追蹤）&lt;/a> 記下的對應自動改回來，外面的連線方看到的始終是主機的位址與 8081。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Linux 處理封包的框架叫 netfilter，它在封包流程上留了幾個固定位置（hook）讓規則掛上去。DNAT 掛在 prerouting 這個 hook：封包剛進主機、核心還沒決定路由之前就改寫（iptables 寫在 nat 表的 PREROUTING 鏈，nftables 寫在 &lt;code>type nat&lt;/code> 的鏈；主機本機發出的連線則在 OUTPUT 那一段改寫）。改寫之後，核心依新的目的地決定路由：目的地是容器或另一台主機時，封包走轉送（forward）路徑送給另一個介面，不經過管主機本身入站連線的 input 路徑；改寫成主機本機的位址或連接埠時（iptables 的 REDIRECT、firewalld 不帶目的位址的 forward-port），封包仍走 input 路徑。&lt;/p>
&lt;p>Docker 用 &lt;code>-p 8081:80&lt;/code> 發佈連接埠時，IPv4 那一份屬於目的地改成容器的那一種：寫在 input 路徑上的主機防火牆規則（ufw、firewalld 的 zone）管不到它（容器網路沒開 IPv6 時，IPv6 那一份不經 DNAT，由主機上的 docker-proxy 程式（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/userland-proxy/" data-link-title="Userland Proxy（使用者空間的連接埠轉送程式）" data-link-desc="ss 列出 docker-proxy、rootlesskit 或 rootlessport 聽著容器發佈的連接埠、主機防火牆的入站規則對容器的連接埠有效、或容器的存取紀錄只看得到閘道位址而沒有真實來源時，查這類轉送程式接收連線的路徑與它換掉來源位址的代價">Userland Proxy&lt;/a>）接收、走 input 路徑，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/ufw-docker-walkthrough/" data-link-title="ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑" data-link-desc="在 Ubuntu 的 ufw 主機上用 Docker 發佈連接埠、想只放行 Cloudflare 的位址、或想限制容器送出的 DNS 查詢時，查 ufw 規則對發佈連接埠的效果、DOCKER-USER 規則在 reload 與重開機之後還在不在、IPv6 那一份發佈由誰接收，以及 compose 容器的 DNS 封包從哪裡送出">ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑&lt;/a>），而轉送路徑上 Docker 與 firewalld 預設都放行經過位址轉換的連線（Docker 的 &lt;code>DOCKER-FORWARD&lt;/code> 鏈、firewalld 的 &lt;code>ct status dnat accept&lt;/code>），所以這些連接埠繞過了主機防火牆。雲端的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> 在封包到達主機、還沒被改寫之前就比對完，不受這個改寫影響。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;code>iptables -t nat -S DOCKER&lt;/code> 看得到規則&lt;/strong>：&lt;code>-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:80&lt;/code> 就是 Docker 發佈 8081 時寫的那一條。&lt;/li>
&lt;li>&lt;strong>防火牆清單看不到、外面卻連得到的連接埠&lt;/strong>：&lt;code>firewall-cmd --list-all&lt;/code> 與 &lt;code>ufw status&lt;/code> 都沒有列 8081，從外面連照樣拿到容器的回應；查發佈的連接埠要用 &lt;code>docker ps&lt;/code> 或 &lt;code>ss -ltnp&lt;/code>。&lt;/li>
&lt;li>&lt;strong>規則比對不到改寫前的連接埠&lt;/strong>：寫在 FORWARD 鏈的 &lt;code>--dport 8081&lt;/code> 比對不到任何封包，因為封包到那裡時目的連接埠已經是 80。&lt;/li>
&lt;/ul>
&lt;h2 id="判讀方式">判讀方式&lt;/h2>
&lt;p>限制規則寫在 DNAT 之前還是之後，決定它看得到哪一個連接埠：之前（prerouting、優先序比 dstnat 小）看得到原本的目的連接埠，直接用它比對；之後（FORWARD 鏈、Docker 的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/docker-user/" data-link-title="DOCKER-USER（Docker 留給使用者的轉送規則鏈）" data-link-desc="要在 Docker 發佈的連接埠上加來源限制、寫進 DOCKER-USER 的規則在防火牆重新載入或重開機之後不見、或規則擋到了容器自己往外的連線時，查這條鏈在轉送路徑上的位置、比對原始連接埠的寫法與規則的存續條件">&lt;code>DOCKER-USER&lt;/code>&lt;/a>）看到的是改寫後的容器位址，要改用連線追蹤記下的原始值比對（iptables 的 &lt;code>-m conntrack --ctorigdstport 8081&lt;/code>、nftables 的 &lt;code>ct original proto-dst 8081&lt;/code>）。主機防火牆沒有放行、外面卻連得到的連接埠，多半是經過 DNAT 送給了容器或另一台主機，這時原因在規則的位置，不在規則的內容。Kubernetes 的 NodePort 在 kube-proxy 的 iptables 模式下也用 DNAT 送往 Pod，IPVS 模式的掛法不同，兩者都沒有在這個站上實測。Docker 的實測與各種限制寫法的比較見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/origin-firewall-walkthrough/" data-link-title="源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS" data-link-desc="要在 Linux 主機上設定只放行 Cloudflare 位址的防火牆、確認 Docker 發佈的連接埠有沒有繞過 firewalld、在 DOCKER-USER、StrictForwardPorts 與 nftables 表之間選一種限制容器的來源、或限制出站 DNS 時，查 Oracle Linux 10 上實際跑過的指令、輸出，以及規則在重新載入、dockerd 重啟與重開機之後還在不在">源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT Gateway</a> 改的是出站封包的來源位址（SNAT），讓內部主機借一個公開位址出去；DNAT（destination NAT，目的位址轉換）改的是入站封包的目的地，讓外面的連線送到指定的那一台：封包原本送往「主機的 8081」，改寫之後送往「容器的 80」。回程的封包由 <a href="/blog/devops/infra/knowledge-cards/conntrack/" data-link-title="conntrack（連線追蹤）" data-link-desc="防火牆規則寫了 established、related 或 ctorigdstport、有狀態防火牆為什麼不必寫回程規則、改了規則卻像沒生效、或位址轉換之後要比對原本的位址時，查 Linux 核心記錄連線狀態的表與它在規則裡的用法">conntrack（連線追蹤）</a> 記下的對應自動改回來，外面的連線方看到的始終是主機的位址與 8081。</p>
<h2 id="概念位置">概念位置</h2>
<p>Linux 處理封包的框架叫 netfilter，它在封包流程上留了幾個固定位置（hook）讓規則掛上去。DNAT 掛在 prerouting 這個 hook：封包剛進主機、核心還沒決定路由之前就改寫（iptables 寫在 nat 表的 PREROUTING 鏈，nftables 寫在 <code>type nat</code> 的鏈；主機本機發出的連線則在 OUTPUT 那一段改寫）。改寫之後，核心依新的目的地決定路由：目的地是容器或另一台主機時，封包走轉送（forward）路徑送給另一個介面，不經過管主機本身入站連線的 input 路徑；改寫成主機本機的位址或連接埠時（iptables 的 REDIRECT、firewalld 不帶目的位址的 forward-port），封包仍走 input 路徑。</p>
<p>Docker 用 <code>-p 8081:80</code> 發佈連接埠時，IPv4 那一份屬於目的地改成容器的那一種：寫在 input 路徑上的主機防火牆規則（ufw、firewalld 的 zone）管不到它（容器網路沒開 IPv6 時，IPv6 那一份不經 DNAT，由主機上的 docker-proxy 程式（<a href="/blog/devops/infra/knowledge-cards/userland-proxy/" data-link-title="Userland Proxy（使用者空間的連接埠轉送程式）" data-link-desc="ss 列出 docker-proxy、rootlesskit 或 rootlessport 聽著容器發佈的連接埠、主機防火牆的入站規則對容器的連接埠有效、或容器的存取紀錄只看得到閘道位址而沒有真實來源時，查這類轉送程式接收連線的路徑與它換掉來源位址的代價">Userland Proxy</a>）接收、走 input 路徑，見 <a href="/blog/devops/infra/03-network-foundation/hands-on/ufw-docker-walkthrough/" data-link-title="ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑" data-link-desc="在 Ubuntu 的 ufw 主機上用 Docker 發佈連接埠、想只放行 Cloudflare 的位址、或想限制容器送出的 DNS 查詢時，查 ufw 規則對發佈連接埠的效果、DOCKER-USER 規則在 reload 與重開機之後還在不在、IPv6 那一份發佈由誰接收，以及 compose 容器的 DNS 封包從哪裡送出">ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑</a>），而轉送路徑上 Docker 與 firewalld 預設都放行經過位址轉換的連線（Docker 的 <code>DOCKER-FORWARD</code> 鏈、firewalld 的 <code>ct status dnat accept</code>），所以這些連接埠繞過了主機防火牆。雲端的 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> 在封包到達主機、還沒被改寫之前就比對完，不受這個改寫影響。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong><code>iptables -t nat -S DOCKER</code> 看得到規則</strong>：<code>-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:80</code> 就是 Docker 發佈 8081 時寫的那一條。</li>
<li><strong>防火牆清單看不到、外面卻連得到的連接埠</strong>：<code>firewall-cmd --list-all</code> 與 <code>ufw status</code> 都沒有列 8081，從外面連照樣拿到容器的回應；查發佈的連接埠要用 <code>docker ps</code> 或 <code>ss -ltnp</code>。</li>
<li><strong>規則比對不到改寫前的連接埠</strong>：寫在 FORWARD 鏈的 <code>--dport 8081</code> 比對不到任何封包，因為封包到那裡時目的連接埠已經是 80。</li>
</ul>
<h2 id="判讀方式">判讀方式</h2>
<p>限制規則寫在 DNAT 之前還是之後，決定它看得到哪一個連接埠：之前（prerouting、優先序比 dstnat 小）看得到原本的目的連接埠，直接用它比對；之後（FORWARD 鏈、Docker 的 <a href="/blog/devops/infra/knowledge-cards/docker-user/" data-link-title="DOCKER-USER（Docker 留給使用者的轉送規則鏈）" data-link-desc="要在 Docker 發佈的連接埠上加來源限制、寫進 DOCKER-USER 的規則在防火牆重新載入或重開機之後不見、或規則擋到了容器自己往外的連線時，查這條鏈在轉送路徑上的位置、比對原始連接埠的寫法與規則的存續條件"><code>DOCKER-USER</code></a>）看到的是改寫後的容器位址，要改用連線追蹤記下的原始值比對（iptables 的 <code>-m conntrack --ctorigdstport 8081</code>、nftables 的 <code>ct original proto-dst 8081</code>）。主機防火牆沒有放行、外面卻連得到的連接埠，多半是經過 DNAT 送給了容器或另一台主機，這時原因在規則的位置，不在規則的內容。Kubernetes 的 NodePort 在 kube-proxy 的 iptables 模式下也用 DNAT 送往 Pod，IPVS 模式的掛法不同，兩者都沒有在這個站上實測。Docker 的實測與各種限制寫法的比較見 <a href="/blog/devops/infra/03-network-foundation/hands-on/origin-firewall-walkthrough/" data-link-title="源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS" data-link-desc="要在 Linux 主機上設定只放行 Cloudflare 位址的防火牆、確認 Docker 發佈的連接埠有沒有繞過 firewalld、在 DOCKER-USER、StrictForwardPorts 與 nftables 表之間選一種限制容器的來源、或限制出站 DNS 時，查 Oracle Linux 10 上實際跑過的指令、輸出，以及規則在重新載入、dockerd 重啟與重開機之後還在不在">源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS</a>。</p>
]]></content:encoded></item><item><title>conntrack（連線追蹤）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/conntrack/</link><pubDate>Sun, 25 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/conntrack/</guid><description>&lt;p>雲端的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> 放行一條入站連線之後，回程的封包不必另寫規則，因為它是「有狀態」的防火牆；Linux 主機上讓防火牆有狀態的元件是 conntrack（connection tracking，連線追蹤）。它是核心裡記錄每一條連線的表：兩端的位址與連接埠、協定、TCP 的連線階段（SYN_SENT、ESTABLISHED、TIME_WAIT 這類），以及經過位址轉換時原本與改寫後的兩組位址。防火牆規則比對的連線狀態（NEW、ESTABLISHED、RELATED、INVALID）是核心依這張表替每個封包推出來的。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>iptables 的 &lt;code>-m conntrack&lt;/code>、&lt;code>-m state&lt;/code> 與 nftables 的 &lt;code>ct&lt;/code> 都在讀這張表。它也是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dnat/" data-link-title="DNAT（目的位址轉換）" data-link-desc="容器發佈的連接埠沒被主機防火牆擋下、port forwarding 的限制規則要寫在位址轉換之前還是之後、或要比對位址轉換之前的連接埠時，查 DNAT 改寫封包目的地的時間點與它對防火牆規則的影響">DNAT&lt;/a> 與 SNAT（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT Gateway&lt;/a> 做的那一種）能運作的前提：核心在連線的第一個封包改寫位址時，把原本與改寫後的對應記進表裡，同一條連線之後的封包與回程封包都照這個對應改寫。規則要比對位址轉換之前的值時，讀的就是表裡記下的「原始方向」（original）。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>防火牆規則開頭的 &lt;code>ct state { established, related } accept&lt;/code>&lt;/strong>：實測時 firewalld 的轉送鏈就以這一條起手，已經放行的連線之後的封包直接通過，規則只需要描述新連線。&lt;/li>
&lt;li>&lt;strong>firewalld 的 &lt;code>ct status dnat accept&lt;/code>&lt;/strong>：在 &lt;code>StrictForwardPorts=no&lt;/code>（預設值）下，所有經過目的位址轉換的連線在任何 policy 之前就被接受，Docker 發佈的連接埠因此不受 firewalld policy 約束。&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/docker-user/" data-link-title="DOCKER-USER（Docker 留給使用者的轉送規則鏈）" data-link-desc="要在 Docker 發佈的連接埠上加來源限制、寫進 DOCKER-USER 的規則在防火牆重新載入或重開機之後不見、或規則擋到了容器自己往外的連線時，查這條鏈在轉送路徑上的位置、比對原始連接埠的寫法與規則的存續條件">&lt;code>DOCKER-USER&lt;/code>&lt;/a> 裡的 &lt;code>-m conntrack --ctorigdstport 8081 --ctdir ORIGINAL&lt;/code>&lt;/strong>：封包的目的地已經改成容器的 80，用表裡記下的原始目的連接埠 8081 比對；&lt;code>--ctdir ORIGINAL&lt;/code> 只比對發起方向的封包，回程封包不進這條規則。&lt;/li>
&lt;li>&lt;strong>&lt;code>conntrack -L&lt;/code>&lt;/strong>（套件 conntrack-tools）或 &lt;code>/proc/net/nf_conntrack&lt;/code>：列出表裡每一條連線，一行裡有協定、TCP 階段、&lt;code>src=&lt;/code>/&lt;code>dst=&lt;/code>/&lt;code>sport=&lt;/code>/&lt;code>dport=&lt;/code> 兩組（第一組是原始方向，第二組是回程方向；兩組對不上就是經過了位址轉換），以及 &lt;code>ASSURED&lt;/code> 這類旗標。&lt;/li>
&lt;/ul>
&lt;h2 id="判讀方式">判讀方式&lt;/h2>
&lt;p>一條防火牆規則比對的是封包現在的欄位，或是表裡記下的原始值，兩者在位址轉換之後不同：寫在位址轉換之後的規則，要比對原本的位址或連接埠時要用 &lt;code>ctorig*&lt;/code> 這類條件，用 &lt;code>--dport&lt;/code> 只比對得到改寫後的值。規則改了卻像沒生效時，原因常在已經存在的連線：ESTABLISHED 的連線多半在規則前面就被放行，新規則只擋得到之後的新連線；要讓既有的連線也受新規則約束，用 &lt;code>conntrack -D&lt;/code>（可加 &lt;code>-p tcp --dport &amp;lt;連接埠&amp;gt;&lt;/code> 縮小範圍）刪掉表裡那幾條，連線方重新連線時才會被新規則比對。表的容量有上限（&lt;code>nf_conntrack_max&lt;/code>），連線數很大的主機表滿時會丟掉新連線，核心記錄裡會出現 &lt;code>nf_conntrack: table full, dropping packet&lt;/code>。Docker 發佈的連接埠怎麼用它限制來源，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/origin-host-firewall/" data-link-title="CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖" data-link-desc="網站放在 CDN（例如 Cloudflare）後面時，源站主機的入站與出站限制：請求避開 CDN 直連源站的成因、來源限制放在雲端防火牆、主機防火牆或 CDN 端驗證的差別、只放行 CDN 位址的套用時機、Docker 發佈的連接埠繞過 ufw 與 firewalld 的路徑與各種限制寫法、CDN 位址送來的請求為什麼不等於 CDN 替自己的網域轉送、出站 DNS 限制的效果與漏洞、地理封鎖放在 CDN 邊緣的理由">CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>雲端的 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> 放行一條入站連線之後，回程的封包不必另寫規則，因為它是「有狀態」的防火牆；Linux 主機上讓防火牆有狀態的元件是 conntrack（connection tracking，連線追蹤）。它是核心裡記錄每一條連線的表：兩端的位址與連接埠、協定、TCP 的連線階段（SYN_SENT、ESTABLISHED、TIME_WAIT 這類），以及經過位址轉換時原本與改寫後的兩組位址。防火牆規則比對的連線狀態（NEW、ESTABLISHED、RELATED、INVALID）是核心依這張表替每個封包推出來的。</p>
<h2 id="概念位置">概念位置</h2>
<p>iptables 的 <code>-m conntrack</code>、<code>-m state</code> 與 nftables 的 <code>ct</code> 都在讀這張表。它也是 <a href="/blog/devops/infra/knowledge-cards/dnat/" data-link-title="DNAT（目的位址轉換）" data-link-desc="容器發佈的連接埠沒被主機防火牆擋下、port forwarding 的限制規則要寫在位址轉換之前還是之後、或要比對位址轉換之前的連接埠時，查 DNAT 改寫封包目的地的時間點與它對防火牆規則的影響">DNAT</a> 與 SNAT（<a href="/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT Gateway</a> 做的那一種）能運作的前提：核心在連線的第一個封包改寫位址時，把原本與改寫後的對應記進表裡，同一條連線之後的封包與回程封包都照這個對應改寫。規則要比對位址轉換之前的值時，讀的就是表裡記下的「原始方向」（original）。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong>防火牆規則開頭的 <code>ct state { established, related } accept</code></strong>：實測時 firewalld 的轉送鏈就以這一條起手，已經放行的連線之後的封包直接通過，規則只需要描述新連線。</li>
<li><strong>firewalld 的 <code>ct status dnat accept</code></strong>：在 <code>StrictForwardPorts=no</code>（預設值）下，所有經過目的位址轉換的連線在任何 policy 之前就被接受，Docker 發佈的連接埠因此不受 firewalld policy 約束。</li>
<li><strong><a href="/blog/devops/infra/knowledge-cards/docker-user/" data-link-title="DOCKER-USER（Docker 留給使用者的轉送規則鏈）" data-link-desc="要在 Docker 發佈的連接埠上加來源限制、寫進 DOCKER-USER 的規則在防火牆重新載入或重開機之後不見、或規則擋到了容器自己往外的連線時，查這條鏈在轉送路徑上的位置、比對原始連接埠的寫法與規則的存續條件"><code>DOCKER-USER</code></a> 裡的 <code>-m conntrack --ctorigdstport 8081 --ctdir ORIGINAL</code></strong>：封包的目的地已經改成容器的 80，用表裡記下的原始目的連接埠 8081 比對；<code>--ctdir ORIGINAL</code> 只比對發起方向的封包，回程封包不進這條規則。</li>
<li><strong><code>conntrack -L</code></strong>（套件 conntrack-tools）或 <code>/proc/net/nf_conntrack</code>：列出表裡每一條連線，一行裡有協定、TCP 階段、<code>src=</code>/<code>dst=</code>/<code>sport=</code>/<code>dport=</code> 兩組（第一組是原始方向，第二組是回程方向；兩組對不上就是經過了位址轉換），以及 <code>ASSURED</code> 這類旗標。</li>
</ul>
<h2 id="判讀方式">判讀方式</h2>
<p>一條防火牆規則比對的是封包現在的欄位，或是表裡記下的原始值，兩者在位址轉換之後不同：寫在位址轉換之後的規則，要比對原本的位址或連接埠時要用 <code>ctorig*</code> 這類條件，用 <code>--dport</code> 只比對得到改寫後的值。規則改了卻像沒生效時，原因常在已經存在的連線：ESTABLISHED 的連線多半在規則前面就被放行，新規則只擋得到之後的新連線；要讓既有的連線也受新規則約束，用 <code>conntrack -D</code>（可加 <code>-p tcp --dport &lt;連接埠&gt;</code> 縮小範圍）刪掉表裡那幾條，連線方重新連線時才會被新規則比對。表的容量有上限（<code>nf_conntrack_max</code>），連線數很大的主機表滿時會丟掉新連線，核心記錄裡會出現 <code>nf_conntrack: table full, dropping packet</code>。Docker 發佈的連接埠怎麼用它限制來源，見 <a href="/blog/devops/infra/03-network-foundation/origin-host-firewall/" data-link-title="CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖" data-link-desc="網站放在 CDN（例如 Cloudflare）後面時，源站主機的入站與出站限制：請求避開 CDN 直連源站的成因、來源限制放在雲端防火牆、主機防火牆或 CDN 端驗證的差別、只放行 CDN 位址的套用時機、Docker 發佈的連接埠繞過 ufw 與 firewalld 的路徑與各種限制寫法、CDN 位址送來的請求為什麼不等於 CDN 替自己的網域轉送、出站 DNS 限制的效果與漏洞、地理封鎖放在 CDN 邊緣的理由">CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖</a>。</p>
]]></content:encoded></item><item><title>Network Namespace（網路命名空間）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/network-namespace/</link><pubDate>Sun, 25 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/network-namespace/</guid><description>&lt;p>要在一台主機上讓一個程序有自己的 IP、自己的網路介面與路由表，Linux 用的是 network namespace（網路命名空間）：核心切出的一套獨立網路環境，跑在裡面的程序只看得到那一套。它是 Linux namespace 的其中一種，同一族還有隔離程序編號的 pid、隔離掛載點的 mount 等，容器同時用好幾種。預設的 bridge 網路下，每個容器各有一個 network namespace，所以容器裡有自己的 IP 與 &lt;code>eth0&lt;/code>；發佈連接埠時，外面的連線靠主機上的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dnat/" data-link-title="DNAT（目的位址轉換）" data-link-desc="容器發佈的連接埠沒被主機防火牆擋下、port forwarding 的限制規則要寫在位址轉換之前還是之後、或要比對位址轉換之前的連接埠時，查 DNAT 改寫封包目的地的時間點與它對防火牆規則的影響">DNAT&lt;/a> 把目的地改寫成容器的位址，送進容器的 namespace。Kubernetes 也有叫 namespace 的概念，指的是叢集裡資源的命名與權限分組，見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/namespace/" data-link-title="Namespace（命名空間）" data-link-desc="看到 kubectl 的 -n、kube-system、default，或跨 namespace 的 Service 名稱連不到時：namespace 劃分物件名稱的範圍、預設用哪一個、對 DNS 名稱與權限的影響">Namespace&lt;/a>；這張卡講的是核心層的網路隔離。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>兩個 network namespace 之間預設不通，要用 veth（成對的虛擬網路介面，從一端送進去的封包從另一端出來）一端放在 namespace 裡、一端放在主機上，再接到主機上的 bridge（Docker 的 docker0）或直接設路由。封包從 namespace 經 veth 進到主機時，跟從實體網卡進來的封包一樣經過主機的防火牆與 DNAT、由 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/conntrack/" data-link-title="conntrack（連線追蹤）" data-link-desc="防火牆規則寫了 established、related 或 ctorigdstport、有狀態防火牆為什麼不必寫回程規則、改了規則卻像沒生效、或位址轉換之後要比對原本的位址時，查 Linux 核心記錄連線狀態的表與它在規則裡的用法">conntrack（連線追蹤）&lt;/a> 記錄。容器不一定各有一個：&lt;code>--network host&lt;/code> 的容器直接用主機的 namespace，&lt;code>--network container:&amp;lt;名稱&amp;gt;&lt;/code> 與 Kubernetes 同一個 Pod 裡的容器共用一個。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;code>ip netns list&lt;/code> 與 &lt;code>ip netns exec &amp;lt;名稱&amp;gt; &amp;lt;指令&amp;gt;&lt;/code>&lt;/strong>：手動建的 namespace 用 &lt;code>ip netns add&lt;/code> 建立，&lt;code>ip netns exec&lt;/code> 讓一條指令跑在那個 namespace 裡。Docker 建的 namespace 放在 &lt;code>/var/run/docker/netns&lt;/code>，不在 &lt;code>ip netns&lt;/code> 讀的 &lt;code>/run/netns&lt;/code>，所以列不出來；要進容器的 namespace，用 &lt;code>docker inspect -f '{{.State.Pid}}' &amp;lt;容器&amp;gt;&lt;/code> 取得容器程序的 PID，再用 &lt;code>nsenter -t &amp;lt;PID&amp;gt; -n &amp;lt;指令&amp;gt;&lt;/code> 執行。&lt;/li>
&lt;li>&lt;strong>主機上多出 &lt;code>veth&lt;/code> 開頭的介面&lt;/strong>：容器每接一個 bridge 網路就有一條，另一端在容器的 namespace 裡（第一條叫 &lt;code>eth0&lt;/code>，再接一個網路是 &lt;code>eth1&lt;/code>）。&lt;/li>
&lt;li>&lt;strong>一台主機上模擬兩個來源位址&lt;/strong>：建兩個 namespace，各給一個位址（例如一個落在 Cloudflare 的位址段、一個落在範圍外），從各自的 namespace 連主機，就能驗證「只放行某些來源」的防火牆規則，不需要第二台主機。這個做法的完整指令見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/origin-firewall-walkthrough/" data-link-title="源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS" data-link-desc="要在 Linux 主機上設定只放行 Cloudflare 位址的防火牆、確認 Docker 發佈的連接埠有沒有繞過 firewalld、在 DOCKER-USER、StrictForwardPorts 與 nftables 表之間選一種限制容器的來源、或限制出站 DNS 時，查 Oracle Linux 10 上實際跑過的指令、輸出，以及規則在重新載入、dockerd 重啟與重開機之後還在不在">源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS&lt;/a> 的〈用 network namespace 模擬 Cloudflare 與其他來源〉；ufw 主機上的版本，以及替兩個來源加 IPv6 位址的 &lt;code>ns_setup6&lt;/code>，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/ufw-docker-walkthrough/" data-link-title="ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑" data-link-desc="在 Ubuntu 的 ufw 主機上用 Docker 發佈連接埠、想只放行 Cloudflare 的位址、或想限制容器送出的 DNS 查詢時，查 ufw 規則對發佈連接埠的效果、DOCKER-USER 規則在 reload 與重開機之後還在不在、IPv6 那一份發佈由誰接收，以及 compose 容器的 DNS 封包從哪裡送出">ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑&lt;/a> 的〈共用的函式與兩個測試來源〉。&lt;/li>
&lt;/ul>
&lt;h2 id="判讀方式">判讀方式&lt;/h2>
&lt;p>一個程序連不到某個位址、或看到的介面跟預期不同時，原因常在它跑在哪個 network namespace：同一台主機上的 &lt;code>127.0.0.1&lt;/code> 在每個 namespace 裡各指自己，容器裡連 &lt;code>127.0.0.1&lt;/code> 連不到主機上的服務，容器裡的程式也綁不上主機的 LAN 位址（那個位址不在容器的 namespace 裡）。用 namespace 模擬來源做測試時，結果只驗證到防火牆依來源位址的判斷：封包從 veth 進來，firewalld 對沒有指派 zone 的 veth 套用預設 zone 的規則，主機的實體網卡在別的 zone 時，模擬的結果不代表真實流量；真實網路上的路由、雲端防火牆與上游服務（例如 CDN 實際用哪些位址回源）也不在測試範圍內。&lt;/p></description><content:encoded><![CDATA[<p>要在一台主機上讓一個程序有自己的 IP、自己的網路介面與路由表，Linux 用的是 network namespace（網路命名空間）：核心切出的一套獨立網路環境，跑在裡面的程序只看得到那一套。它是 Linux namespace 的其中一種，同一族還有隔離程序編號的 pid、隔離掛載點的 mount 等，容器同時用好幾種。預設的 bridge 網路下，每個容器各有一個 network namespace，所以容器裡有自己的 IP 與 <code>eth0</code>；發佈連接埠時，外面的連線靠主機上的 <a href="/blog/devops/infra/knowledge-cards/dnat/" data-link-title="DNAT（目的位址轉換）" data-link-desc="容器發佈的連接埠沒被主機防火牆擋下、port forwarding 的限制規則要寫在位址轉換之前還是之後、或要比對位址轉換之前的連接埠時，查 DNAT 改寫封包目的地的時間點與它對防火牆規則的影響">DNAT</a> 把目的地改寫成容器的位址，送進容器的 namespace。Kubernetes 也有叫 namespace 的概念，指的是叢集裡資源的命名與權限分組，見 <a href="/blog/backend/knowledge-cards/namespace/" data-link-title="Namespace（命名空間）" data-link-desc="看到 kubectl 的 -n、kube-system、default，或跨 namespace 的 Service 名稱連不到時：namespace 劃分物件名稱的範圍、預設用哪一個、對 DNS 名稱與權限的影響">Namespace</a>；這張卡講的是核心層的網路隔離。</p>
<h2 id="概念位置">概念位置</h2>
<p>兩個 network namespace 之間預設不通，要用 veth（成對的虛擬網路介面，從一端送進去的封包從另一端出來）一端放在 namespace 裡、一端放在主機上，再接到主機上的 bridge（Docker 的 docker0）或直接設路由。封包從 namespace 經 veth 進到主機時，跟從實體網卡進來的封包一樣經過主機的防火牆與 DNAT、由 <a href="/blog/devops/infra/knowledge-cards/conntrack/" data-link-title="conntrack（連線追蹤）" data-link-desc="防火牆規則寫了 established、related 或 ctorigdstport、有狀態防火牆為什麼不必寫回程規則、改了規則卻像沒生效、或位址轉換之後要比對原本的位址時，查 Linux 核心記錄連線狀態的表與它在規則裡的用法">conntrack（連線追蹤）</a> 記錄。容器不一定各有一個：<code>--network host</code> 的容器直接用主機的 namespace，<code>--network container:&lt;名稱&gt;</code> 與 Kubernetes 同一個 Pod 裡的容器共用一個。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong><code>ip netns list</code> 與 <code>ip netns exec &lt;名稱&gt; &lt;指令&gt;</code></strong>：手動建的 namespace 用 <code>ip netns add</code> 建立，<code>ip netns exec</code> 讓一條指令跑在那個 namespace 裡。Docker 建的 namespace 放在 <code>/var/run/docker/netns</code>，不在 <code>ip netns</code> 讀的 <code>/run/netns</code>，所以列不出來；要進容器的 namespace，用 <code>docker inspect -f '{{.State.Pid}}' &lt;容器&gt;</code> 取得容器程序的 PID，再用 <code>nsenter -t &lt;PID&gt; -n &lt;指令&gt;</code> 執行。</li>
<li><strong>主機上多出 <code>veth</code> 開頭的介面</strong>：容器每接一個 bridge 網路就有一條，另一端在容器的 namespace 裡（第一條叫 <code>eth0</code>，再接一個網路是 <code>eth1</code>）。</li>
<li><strong>一台主機上模擬兩個來源位址</strong>：建兩個 namespace，各給一個位址（例如一個落在 Cloudflare 的位址段、一個落在範圍外），從各自的 namespace 連主機，就能驗證「只放行某些來源」的防火牆規則，不需要第二台主機。這個做法的完整指令見 <a href="/blog/devops/infra/03-network-foundation/hands-on/origin-firewall-walkthrough/" data-link-title="源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS" data-link-desc="要在 Linux 主機上設定只放行 Cloudflare 位址的防火牆、確認 Docker 發佈的連接埠有沒有繞過 firewalld、在 DOCKER-USER、StrictForwardPorts 與 nftables 表之間選一種限制容器的來源、或限制出站 DNS 時，查 Oracle Linux 10 上實際跑過的指令、輸出，以及規則在重新載入、dockerd 重啟與重開機之後還在不在">源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS</a> 的〈用 network namespace 模擬 Cloudflare 與其他來源〉；ufw 主機上的版本，以及替兩個來源加 IPv6 位址的 <code>ns_setup6</code>，見 <a href="/blog/devops/infra/03-network-foundation/hands-on/ufw-docker-walkthrough/" data-link-title="ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑" data-link-desc="在 Ubuntu 的 ufw 主機上用 Docker 發佈連接埠、想只放行 Cloudflare 的位址、或想限制容器送出的 DNS 查詢時，查 ufw 規則對發佈連接埠的效果、DOCKER-USER 規則在 reload 與重開機之後還在不在、IPv6 那一份發佈由誰接收，以及 compose 容器的 DNS 封包從哪裡送出">ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑</a> 的〈共用的函式與兩個測試來源〉。</li>
</ul>
<h2 id="判讀方式">判讀方式</h2>
<p>一個程序連不到某個位址、或看到的介面跟預期不同時，原因常在它跑在哪個 network namespace：同一台主機上的 <code>127.0.0.1</code> 在每個 namespace 裡各指自己，容器裡連 <code>127.0.0.1</code> 連不到主機上的服務，容器裡的程式也綁不上主機的 LAN 位址（那個位址不在容器的 namespace 裡）。用 namespace 模擬來源做測試時，結果只驗證到防火牆依來源位址的判斷：封包從 veth 進來，firewalld 對沒有指派 zone 的 veth 套用預設 zone 的規則，主機的實體網卡在別的 zone 時，模擬的結果不代表真實流量；真實網路上的路由、雲端防火牆與上游服務（例如 CDN 實際用哪些位址回源）也不在測試範圍內。</p>
]]></content:encoded></item><item><title>DOCKER-USER（Docker 留給使用者的轉送規則鏈）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/docker-user/</link><pubDate>Mon, 26 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/docker-user/</guid><description>&lt;p>DOCKER-USER 是 Docker 在 iptables 的 filter 表裡建的一條自訂鏈：FORWARD 鏈的第一條規則無條件跳到它，Docker 自己不在裡面寫任何規則，啟動與重啟時也不清它的內容，讓使用者在 Docker 放行容器流量之前插入自己的限制。它管的是轉送路徑：Docker 發佈的連接埠經過 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dnat/" data-link-title="DNAT（目的位址轉換）" data-link-desc="容器發佈的連接埠沒被主機防火牆擋下、port forwarding 的限制規則要寫在位址轉換之前還是之後、或要比對位址轉換之前的連接埠時，查 DNAT 改寫封包目的地的時間點與它對防火牆規則的影響">DNAT（目的位址轉換）&lt;/a> 改寫成容器位址之後，封包由主機轉送給容器，寫在主機入站規則（ufw 的入站規則、firewalld 的 zone）上的限制碰不到它，DOCKER-USER 是這條路上第一個可以寫規則的位置。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>FORWARD 鏈的跳轉順序是 DOCKER-USER、Docker 自己的 &lt;code>DOCKER-FORWARD&lt;/code>、然後才是 ufw 的 &lt;code>ufw-*&lt;/code> 鏈；&lt;code>DOCKER-FORWARD&lt;/code> 接受送往發佈連接埠的連線，所以寫在 ufw route 規則裡的限制輪不到這些封包，寫在 DOCKER-USER 的輪得到。封包到這條鏈時目的地已經是容器的位址與連接埠，限制發佈的連接埠要用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/conntrack/" data-link-title="conntrack（連線追蹤）" data-link-desc="防火牆規則寫了 established、related 或 ctorigdstport、有狀態防火牆為什麼不必寫回程規則、改了規則卻像沒生效、或位址轉換之後要比對原本的位址時，查 Linux 核心記錄連線狀態的表與它在規則裡的用法">conntrack（連線追蹤）&lt;/a> 記下的原始目的連接埠比對：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">iptables -I DOCKER-USER -p tcp -m conntrack --ctstate DNAT --ctorigdstport &lt;span class="m">443&lt;/span> --ctdir ORIGINAL ! -s 173.245.48.0/20 -j DROP&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>三個 conntrack 條件各管一件事：&lt;code>--ctorigdstport 443&lt;/code> 比對位址轉換之前的連接埠；&lt;code>--ctdir ORIGINAL&lt;/code> 只比對發起方向的封包，容器回覆的封包不拿來比對來源；&lt;code>--ctstate DNAT&lt;/code> 只比對經過位址轉換的連線，也就是送往發佈連接埠的那些。少了最後一個時，容器自己往外連 443、同一台主機上容器之間連 80 的封包也進這條鏈、原始目的連接埠一樣是 443 或 80，一起被丟掉（2026-10-26 在 Ubuntu 24.04 與 Docker 29.9.2 上實測）。&lt;/p>
&lt;p>這條鏈只存在 iptables 後端：Docker 改用 nftables 後端時沒有它（Docker 的〈firewall-nftables〉文件）。它也只管 IPv4 那一份發佈：容器網路沒開 IPv6 時，IPv6 那一份由主機上的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/userland-proxy/" data-link-title="Userland Proxy（使用者空間的連接埠轉送程式）" data-link-desc="ss 列出 docker-proxy、rootlesskit 或 rootlessport 聽著容器發佈的連接埠、主機防火牆的入站規則對容器的連接埠有效、或容器的存取紀錄只看得到閘道位址而沒有真實來源時，查這類轉送程式接收連線的路徑與它換掉來源位址的代價">Userland Proxy（使用者空間的連接埠轉送程式）&lt;/a> 接收，不經過轉送路徑。主機上同時裝了 Docker 與以 root 執行的 Podman 時，Podman 的轉送封包也經過 DOCKER-USER（實測）。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;code>iptables -S FORWARD&lt;/code> 的前兩行&lt;/strong>：&lt;code>-A FORWARD -j DOCKER-USER&lt;/code>、&lt;code>-A FORWARD -j DOCKER-FORWARD&lt;/code>。主機也裝了 Podman 時，最前面還有一條 &lt;code>-j NETAVARK_FORWARD&lt;/code>。&lt;/li>
&lt;li>&lt;strong>規則加了、從外面量過擋得住，過一陣子又失效&lt;/strong>：防火牆重新載入或重開機清掉了它，&lt;code>iptables -S DOCKER-USER&lt;/code> 只剩 &lt;code>-N DOCKER-USER&lt;/code> 這一行。&lt;/li>
&lt;li>&lt;strong>加了限制之後容器呼叫外部 API 逾時&lt;/strong>：規則只比對原始目的連接埠，沒有 &lt;code>--ctstate DNAT&lt;/code>，容器的出站連線也被丟掉。&lt;/li>
&lt;/ul>
&lt;h2 id="判讀方式">判讀方式&lt;/h2>
&lt;p>DOCKER-USER 的弱點在規則的存續，用 iptables 指令加的規則只存在執行中的核心裡，各種情形下的結果不同（2026-10-25 到 26 日在 Ubuntu 24.04 的 ufw 0.36.2、Oracle Linux 10.2 的 firewalld 2.4.3 上實測）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>情形&lt;/th>
 &lt;th>規則&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>任何主機重開機&lt;/td>
 &lt;td>不在&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>systemctl restart docker&lt;/code>&lt;/td>
 &lt;td>還在&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ufw 的 &lt;code>ufw reload&lt;/code>，after.rules 沒有宣告這條鏈&lt;/td>
 &lt;td>還在&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ufw 的 after.rules 宣告這條鏈&lt;/td>
 &lt;td>每次 reload 先清空、再寫入檔案裡的規則；用 iptables 指令另外加的不在&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>firewalld 的 reload，nftables 後端、沒有 direct 設定&lt;/td>
 &lt;td>還在&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>firewalld 的 reload，nftables 後端、有 direct rule 或 passthrough&lt;/td>
 &lt;td>不在（Oracle Cloud 的 Oracle Linux 平台映像自帶 passthrough）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>firewalld 的 reload，iptables 後端&lt;/td>
 &lt;td>不在&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>要讓規則留過重開機，ufw 主機把它寫進 &lt;code>/etc/ufw/after.rules&lt;/code>，同一台主機上的 DOCKER-USER 規則都要寫在那裡；firewalld 主機上改用掛在位址轉換之前的自建 nftables 表，或把限制放在雲端防火牆。direct 設定是主機當下的狀態，之後裝的軟體可能再加進來，用&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">組態管理&lt;/a>工具定期 reload 的主機上，規則被清掉之後直連就重新開著而沒有告警。實測的指令、輸出與 after.rules 的整份清單寫法見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/ufw-docker-walkthrough/" data-link-title="ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑" data-link-desc="在 Ubuntu 的 ufw 主機上用 Docker 發佈連接埠、想只放行 Cloudflare 的位址、或想限制容器送出的 DNS 查詢時，查 ufw 規則對發佈連接埠的效果、DOCKER-USER 規則在 reload 與重開機之後還在不在、IPv6 那一份發佈由誰接收，以及 compose 容器的 DNS 封包從哪裡送出">ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/origin-firewall-walkthrough/" data-link-title="源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS" data-link-desc="要在 Linux 主機上設定只放行 Cloudflare 位址的防火牆、確認 Docker 發佈的連接埠有沒有繞過 firewalld、在 DOCKER-USER、StrictForwardPorts 與 nftables 表之間選一種限制容器的來源、或限制出站 DNS 時，查 Oracle Linux 10 上實際跑過的指令、輸出，以及規則在重新載入、dockerd 重啟與重開機之後還在不在">源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS&lt;/a>；各種限制寫法的取捨見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/origin-host-firewall/" data-link-title="CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖" data-link-desc="網站放在 CDN（例如 Cloudflare）後面時，源站主機的入站與出站限制：請求避開 CDN 直連源站的成因、來源限制放在雲端防火牆、主機防火牆或 CDN 端驗證的差別、只放行 CDN 位址的套用時機、Docker 發佈的連接埠繞過 ufw 與 firewalld 的路徑與各種限制寫法、CDN 位址送來的請求為什麼不等於 CDN 替自己的網域轉送、出站 DNS 限制的效果與漏洞、地理封鎖放在 CDN 邊緣的理由">CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖&lt;/a> 的〈Docker 發佈的連接埠：封包路徑與限制的寫法〉。&lt;/p></description><content:encoded><![CDATA[<p>DOCKER-USER 是 Docker 在 iptables 的 filter 表裡建的一條自訂鏈：FORWARD 鏈的第一條規則無條件跳到它，Docker 自己不在裡面寫任何規則，啟動與重啟時也不清它的內容，讓使用者在 Docker 放行容器流量之前插入自己的限制。它管的是轉送路徑：Docker 發佈的連接埠經過 <a href="/blog/devops/infra/knowledge-cards/dnat/" data-link-title="DNAT（目的位址轉換）" data-link-desc="容器發佈的連接埠沒被主機防火牆擋下、port forwarding 的限制規則要寫在位址轉換之前還是之後、或要比對位址轉換之前的連接埠時，查 DNAT 改寫封包目的地的時間點與它對防火牆規則的影響">DNAT（目的位址轉換）</a> 改寫成容器位址之後，封包由主機轉送給容器，寫在主機入站規則（ufw 的入站規則、firewalld 的 zone）上的限制碰不到它，DOCKER-USER 是這條路上第一個可以寫規則的位置。</p>
<h2 id="概念位置">概念位置</h2>
<p>FORWARD 鏈的跳轉順序是 DOCKER-USER、Docker 自己的 <code>DOCKER-FORWARD</code>、然後才是 ufw 的 <code>ufw-*</code> 鏈；<code>DOCKER-FORWARD</code> 接受送往發佈連接埠的連線，所以寫在 ufw route 規則裡的限制輪不到這些封包，寫在 DOCKER-USER 的輪得到。封包到這條鏈時目的地已經是容器的位址與連接埠，限制發佈的連接埠要用 <a href="/blog/devops/infra/knowledge-cards/conntrack/" data-link-title="conntrack（連線追蹤）" data-link-desc="防火牆規則寫了 established、related 或 ctorigdstport、有狀態防火牆為什麼不必寫回程規則、改了規則卻像沒生效、或位址轉換之後要比對原本的位址時，查 Linux 核心記錄連線狀態的表與它在規則裡的用法">conntrack（連線追蹤）</a> 記下的原始目的連接埠比對：</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">iptables -I DOCKER-USER -p tcp -m conntrack --ctstate DNAT --ctorigdstport <span class="m">443</span> --ctdir ORIGINAL ! -s 173.245.48.0/20 -j DROP</span></span></code></pre></div><p>三個 conntrack 條件各管一件事：<code>--ctorigdstport 443</code> 比對位址轉換之前的連接埠；<code>--ctdir ORIGINAL</code> 只比對發起方向的封包，容器回覆的封包不拿來比對來源；<code>--ctstate DNAT</code> 只比對經過位址轉換的連線，也就是送往發佈連接埠的那些。少了最後一個時，容器自己往外連 443、同一台主機上容器之間連 80 的封包也進這條鏈、原始目的連接埠一樣是 443 或 80，一起被丟掉（2026-10-26 在 Ubuntu 24.04 與 Docker 29.9.2 上實測）。</p>
<p>這條鏈只存在 iptables 後端：Docker 改用 nftables 後端時沒有它（Docker 的〈firewall-nftables〉文件）。它也只管 IPv4 那一份發佈：容器網路沒開 IPv6 時，IPv6 那一份由主機上的 <a href="/blog/devops/infra/knowledge-cards/userland-proxy/" data-link-title="Userland Proxy（使用者空間的連接埠轉送程式）" data-link-desc="ss 列出 docker-proxy、rootlesskit 或 rootlessport 聽著容器發佈的連接埠、主機防火牆的入站規則對容器的連接埠有效、或容器的存取紀錄只看得到閘道位址而沒有真實來源時，查這類轉送程式接收連線的路徑與它換掉來源位址的代價">Userland Proxy（使用者空間的連接埠轉送程式）</a> 接收，不經過轉送路徑。主機上同時裝了 Docker 與以 root 執行的 Podman 時，Podman 的轉送封包也經過 DOCKER-USER（實測）。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong><code>iptables -S FORWARD</code> 的前兩行</strong>：<code>-A FORWARD -j DOCKER-USER</code>、<code>-A FORWARD -j DOCKER-FORWARD</code>。主機也裝了 Podman 時，最前面還有一條 <code>-j NETAVARK_FORWARD</code>。</li>
<li><strong>規則加了、從外面量過擋得住，過一陣子又失效</strong>：防火牆重新載入或重開機清掉了它，<code>iptables -S DOCKER-USER</code> 只剩 <code>-N DOCKER-USER</code> 這一行。</li>
<li><strong>加了限制之後容器呼叫外部 API 逾時</strong>：規則只比對原始目的連接埠，沒有 <code>--ctstate DNAT</code>，容器的出站連線也被丟掉。</li>
</ul>
<h2 id="判讀方式">判讀方式</h2>
<p>DOCKER-USER 的弱點在規則的存續，用 iptables 指令加的規則只存在執行中的核心裡，各種情形下的結果不同（2026-10-25 到 26 日在 Ubuntu 24.04 的 ufw 0.36.2、Oracle Linux 10.2 的 firewalld 2.4.3 上實測）：</p>
<table>
  <thead>
      <tr>
          <th>情形</th>
          <th>規則</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>任何主機重開機</td>
          <td>不在</td>
      </tr>
      <tr>
          <td><code>systemctl restart docker</code></td>
          <td>還在</td>
      </tr>
      <tr>
          <td>ufw 的 <code>ufw reload</code>，after.rules 沒有宣告這條鏈</td>
          <td>還在</td>
      </tr>
      <tr>
          <td>ufw 的 after.rules 宣告這條鏈</td>
          <td>每次 reload 先清空、再寫入檔案裡的規則；用 iptables 指令另外加的不在</td>
      </tr>
      <tr>
          <td>firewalld 的 reload，nftables 後端、沒有 direct 設定</td>
          <td>還在</td>
      </tr>
      <tr>
          <td>firewalld 的 reload，nftables 後端、有 direct rule 或 passthrough</td>
          <td>不在（Oracle Cloud 的 Oracle Linux 平台映像自帶 passthrough）</td>
      </tr>
      <tr>
          <td>firewalld 的 reload，iptables 後端</td>
          <td>不在</td>
      </tr>
  </tbody>
</table>
<p>要讓規則留過重開機，ufw 主機把它寫進 <code>/etc/ufw/after.rules</code>，同一台主機上的 DOCKER-USER 規則都要寫在那裡；firewalld 主機上改用掛在位址轉換之前的自建 nftables 表，或把限制放在雲端防火牆。direct 設定是主機當下的狀態，之後裝的軟體可能再加進來，用<a href="/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">組態管理</a>工具定期 reload 的主機上，規則被清掉之後直連就重新開著而沒有告警。實測的指令、輸出與 after.rules 的整份清單寫法見 <a href="/blog/devops/infra/03-network-foundation/hands-on/ufw-docker-walkthrough/" data-link-title="ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑" data-link-desc="在 Ubuntu 的 ufw 主機上用 Docker 發佈連接埠、想只放行 Cloudflare 的位址、或想限制容器送出的 DNS 查詢時，查 ufw 規則對發佈連接埠的效果、DOCKER-USER 規則在 reload 與重開機之後還在不在、IPv6 那一份發佈由誰接收，以及 compose 容器的 DNS 封包從哪裡送出">ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑</a> 與 <a href="/blog/devops/infra/03-network-foundation/hands-on/origin-firewall-walkthrough/" data-link-title="源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS" data-link-desc="要在 Linux 主機上設定只放行 Cloudflare 位址的防火牆、確認 Docker 發佈的連接埠有沒有繞過 firewalld、在 DOCKER-USER、StrictForwardPorts 與 nftables 表之間選一種限制容器的來源、或限制出站 DNS 時，查 Oracle Linux 10 上實際跑過的指令、輸出，以及規則在重新載入、dockerd 重啟與重開機之後還在不在">源站防火牆只放行 Cloudflare 位址的實測紀錄：firewalld、Docker 發佈的連接埠、DOCKER-USER、nftables 表與出站 DNS</a>；各種限制寫法的取捨見 <a href="/blog/devops/infra/03-network-foundation/origin-host-firewall/" data-link-title="CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖" data-link-desc="網站放在 CDN（例如 Cloudflare）後面時，源站主機的入站與出站限制：請求避開 CDN 直連源站的成因、來源限制放在雲端防火牆、主機防火牆或 CDN 端驗證的差別、只放行 CDN 位址的套用時機、Docker 發佈的連接埠繞過 ufw 與 firewalld 的路徑與各種限制寫法、CDN 位址送來的請求為什麼不等於 CDN 替自己的網域轉送、出站 DNS 限制的效果與漏洞、地理封鎖放在 CDN 邊緣的理由">CDN 後面的源站主機防火牆：只放行 CDN 來源的位置與套用時機、Docker 發佈連接埠的封包路徑、出站 DNS 的限制與地理封鎖</a> 的〈Docker 發佈的連接埠：封包路徑與限制的寫法〉。</p>
]]></content:encoded></item><item><title>Userland Proxy（使用者空間的連接埠轉送程式）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/userland-proxy/</link><pubDate>Mon, 26 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/userland-proxy/</guid><description>&lt;p>把連線送進容器有兩種做法。&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dnat/" data-link-title="DNAT（目的位址轉換）" data-link-desc="容器發佈的連接埠沒被主機防火牆擋下、port forwarding 的限制規則要寫在位址轉換之前還是之後、或要比對位址轉換之前的連接埠時，查 DNAT 改寫封包目的地的時間點與它對防火牆規則的影響">DNAT（目的位址轉換）&lt;/a> 是核心在封包剛進主機時改寫目的地，封包本身被轉送給容器；userland proxy 是一個在主機上執行的使用者空間程式聽著那個連接埠，收下連線之後自己再開一條連線到容器，把兩條連線之間的資料來回搬。常見的三個是 Docker 的 docker-proxy、rootless Docker 的 rootlesskit，以及 rootless Podman 的 rootlessport。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>連線的終點是主機上的一個程式，所以封包走的是 input 路徑（目的地是主機本身程式的那一條），主機防火牆的入站規則（ufw 的入站規則、firewalld 的 zone）管得到它，跟 DNAT 那一類正好相反。代價在容器看到的來源：容器收到的連線是轉送程式開的，來源位址是轉送程式那一端的位址，不是外面的真實來源。這跟 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy（反向代理）&lt;/a> 換掉來源位址的道理相同，差別在反向代理常用 HTTP 標頭（&lt;code>X-Forwarded-For&lt;/code>）把原本的來源帶過去，這幾個轉送程式在 TCP 這一層搬資料，什麼都不帶。&lt;/p>
&lt;p>三個程式各在什麼時候出現：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>docker-proxy&lt;/strong>：以 root 執行的 Docker 用 &lt;code>-p&lt;/code> 發佈時，主機上 IPv4 與 IPv6 各有一份；IPv4 那一份從外面來的封包在 prerouting 就被 DNAT 改寫、到不了它，IPv6 那一份在容器網路沒開 IPv6 時由它接收。Docker 的 &lt;code>userland-proxy&lt;/code> 設成 &lt;code>false&lt;/code> 時它不啟動，IPv6 那一份也就沒有發佈（2026-10-26 在 Docker 29.9.2 上實測）。&lt;/li>
&lt;li>&lt;strong>rootlesskit&lt;/strong>：rootless Docker 的 dockerd 以一般使用者執行、不能改主機的 iptables，發佈的連接埠全部由它接聽。&lt;/li>
&lt;li>&lt;strong>rootlessport&lt;/strong>：rootless Podman 的對應程式，網路由 slirp4netns（Podman 4）或 pasta（Podman 5 的預設）提供。&lt;/li>
&lt;/ul>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;code>ss -ltnp&lt;/code> 列出這幾個程式聽著發佈的連接埠，nat 表裡沒有對應的 DNAT&lt;/strong>：&lt;code>users:((&amp;quot;rootlesskit&amp;quot;,pid=4085,fd=13))&lt;/code> 聽著 &lt;code>0.0.0.0:8083&lt;/code>，&lt;code>iptables -t nat -S | grep -c 8083&lt;/code> 回 0。&lt;/li>
&lt;li>&lt;strong>容器的存取紀錄只有閘道位址&lt;/strong>：2026-10-26 的實測裡，經由 docker-proxy 的 IPv6 連線與 rootless Docker 的連線，容器記到的來源都是 &lt;code>172.17.0.1&lt;/code>；rootless Podman 4.9 記到的是 slirp4netns 那張 tap 介面的 &lt;code>10.0.2.100&lt;/code>。&lt;/li>
&lt;li>&lt;strong>主機防火牆的入站規則對容器的連接埠有效&lt;/strong>：ufw 預設拒絕入站時兩個測試來源都連不到 rootless 容器的連接埠，加一條入站放行之後只有被放行的那個連得到。&lt;/li>
&lt;/ul>
&lt;h2 id="判讀方式">判讀方式&lt;/h2>
&lt;p>判斷一個發佈的連接埠屬於哪一類，先查有沒有規則把它的目的地改寫掉，再看誰在聽：&lt;code>nft list ruleset | grep 'dport &amp;lt;連接埠&amp;gt;'&lt;/code> 同時列得出經由 iptables-nft 寫的與原生 nftables 寫的改寫規則。查到改寫規則時是 DNAT 那一類，聽著連接埠的程式只是佔住它（Podman 的 &lt;code>conmon&lt;/code>、docker-proxy 的 IPv4 那一份），不算數；查不到時，聽著的轉送程式才是接收者。查表是預測，最終以從範圍外的來源實際連一次為準。屬於這一類時，限制寫在主機防火牆的入站規則上就有效，而容器裡依來源位址做的判斷（存取紀錄、速率限制、只信任來自 CDN 位址段的 &lt;code>CF-Connecting-IP&lt;/code>）都拿不到真正的來源；Docker 文件列的保留來源位址做法（RootlessKit v3 的 &lt;code>builtin&lt;/code> 加 &lt;code>userland-proxy: false&lt;/code>、slirp4netns 轉送方式、pasta 加 &lt;code>implicit&lt;/code>）這個站上沒有實跑。實測見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/podman-rootless-ports-walkthrough/" data-link-title="Podman 與 rootless Docker 發佈連接埠的實測紀錄：rootful Podman 在 ufw 與 firewalld 上的轉送路徑、rootless Docker 與 rootless Podman 由主機程式接收的連接埠與容器看到的來源位址" data-link-desc="用 Podman 或 rootless 模式的 Docker 發佈連接埠、想知道 ufw 或 firewalld 的規則管不管得到、或想在容器裡拿到連線的真實來源位址時，查 rootful Podman 在 ufw 與 firewalld 上的不同結果、rootless 模式接收連線的路徑，以及容器看到的來源位址">Podman 與 rootless Docker 發佈連接埠的實測紀錄：rootful Podman 在 ufw 與 firewalld 上的轉送路徑、rootless Docker 與 rootless Podman 由主機程式接收的連接埠與容器看到的來源位址&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/hands-on/ufw-docker-walkthrough/" data-link-title="ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑" data-link-desc="在 Ubuntu 的 ufw 主機上用 Docker 發佈連接埠、想只放行 Cloudflare 的位址、或想限制容器送出的 DNS 查詢時，查 ufw 規則對發佈連接埠的效果、DOCKER-USER 規則在 reload 與重開機之後還在不在、IPv6 那一份發佈由誰接收，以及 compose 容器的 DNS 封包從哪裡送出">ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑&lt;/a> 的〈IPv6 那一份發佈：docker-proxy 的接收路徑與 ufw 入站規則的效果〉。&lt;/p></description><content:encoded><![CDATA[<p>把連線送進容器有兩種做法。<a href="/blog/devops/infra/knowledge-cards/dnat/" data-link-title="DNAT（目的位址轉換）" data-link-desc="容器發佈的連接埠沒被主機防火牆擋下、port forwarding 的限制規則要寫在位址轉換之前還是之後、或要比對位址轉換之前的連接埠時，查 DNAT 改寫封包目的地的時間點與它對防火牆規則的影響">DNAT（目的位址轉換）</a> 是核心在封包剛進主機時改寫目的地，封包本身被轉送給容器；userland proxy 是一個在主機上執行的使用者空間程式聽著那個連接埠，收下連線之後自己再開一條連線到容器，把兩條連線之間的資料來回搬。常見的三個是 Docker 的 docker-proxy、rootless Docker 的 rootlesskit，以及 rootless Podman 的 rootlessport。</p>
<h2 id="概念位置">概念位置</h2>
<p>連線的終點是主機上的一個程式，所以封包走的是 input 路徑（目的地是主機本身程式的那一條），主機防火牆的入站規則（ufw 的入站規則、firewalld 的 zone）管得到它，跟 DNAT 那一類正好相反。代價在容器看到的來源：容器收到的連線是轉送程式開的，來源位址是轉送程式那一端的位址，不是外面的真實來源。這跟 <a href="/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy（反向代理）</a> 換掉來源位址的道理相同，差別在反向代理常用 HTTP 標頭（<code>X-Forwarded-For</code>）把原本的來源帶過去，這幾個轉送程式在 TCP 這一層搬資料，什麼都不帶。</p>
<p>三個程式各在什麼時候出現：</p>
<ul>
<li><strong>docker-proxy</strong>：以 root 執行的 Docker 用 <code>-p</code> 發佈時，主機上 IPv4 與 IPv6 各有一份；IPv4 那一份從外面來的封包在 prerouting 就被 DNAT 改寫、到不了它，IPv6 那一份在容器網路沒開 IPv6 時由它接收。Docker 的 <code>userland-proxy</code> 設成 <code>false</code> 時它不啟動，IPv6 那一份也就沒有發佈（2026-10-26 在 Docker 29.9.2 上實測）。</li>
<li><strong>rootlesskit</strong>：rootless Docker 的 dockerd 以一般使用者執行、不能改主機的 iptables，發佈的連接埠全部由它接聽。</li>
<li><strong>rootlessport</strong>：rootless Podman 的對應程式，網路由 slirp4netns（Podman 4）或 pasta（Podman 5 的預設）提供。</li>
</ul>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong><code>ss -ltnp</code> 列出這幾個程式聽著發佈的連接埠，nat 表裡沒有對應的 DNAT</strong>：<code>users:((&quot;rootlesskit&quot;,pid=4085,fd=13))</code> 聽著 <code>0.0.0.0:8083</code>，<code>iptables -t nat -S | grep -c 8083</code> 回 0。</li>
<li><strong>容器的存取紀錄只有閘道位址</strong>：2026-10-26 的實測裡，經由 docker-proxy 的 IPv6 連線與 rootless Docker 的連線，容器記到的來源都是 <code>172.17.0.1</code>；rootless Podman 4.9 記到的是 slirp4netns 那張 tap 介面的 <code>10.0.2.100</code>。</li>
<li><strong>主機防火牆的入站規則對容器的連接埠有效</strong>：ufw 預設拒絕入站時兩個測試來源都連不到 rootless 容器的連接埠，加一條入站放行之後只有被放行的那個連得到。</li>
</ul>
<h2 id="判讀方式">判讀方式</h2>
<p>判斷一個發佈的連接埠屬於哪一類，先查有沒有規則把它的目的地改寫掉，再看誰在聽：<code>nft list ruleset | grep 'dport &lt;連接埠&gt;'</code> 同時列得出經由 iptables-nft 寫的與原生 nftables 寫的改寫規則。查到改寫規則時是 DNAT 那一類，聽著連接埠的程式只是佔住它（Podman 的 <code>conmon</code>、docker-proxy 的 IPv4 那一份），不算數；查不到時，聽著的轉送程式才是接收者。查表是預測，最終以從範圍外的來源實際連一次為準。屬於這一類時，限制寫在主機防火牆的入站規則上就有效，而容器裡依來源位址做的判斷（存取紀錄、速率限制、只信任來自 CDN 位址段的 <code>CF-Connecting-IP</code>）都拿不到真正的來源；Docker 文件列的保留來源位址做法（RootlessKit v3 的 <code>builtin</code> 加 <code>userland-proxy: false</code>、slirp4netns 轉送方式、pasta 加 <code>implicit</code>）這個站上沒有實跑。實測見 <a href="/blog/devops/infra/03-network-foundation/hands-on/podman-rootless-ports-walkthrough/" data-link-title="Podman 與 rootless Docker 發佈連接埠的實測紀錄：rootful Podman 在 ufw 與 firewalld 上的轉送路徑、rootless Docker 與 rootless Podman 由主機程式接收的連接埠與容器看到的來源位址" data-link-desc="用 Podman 或 rootless 模式的 Docker 發佈連接埠、想知道 ufw 或 firewalld 的規則管不管得到、或想在容器裡拿到連線的真實來源位址時，查 rootful Podman 在 ufw 與 firewalld 上的不同結果、rootless 模式接收連線的路徑，以及容器看到的來源位址">Podman 與 rootless Docker 發佈連接埠的實測紀錄：rootful Podman 在 ufw 與 firewalld 上的轉送路徑、rootless Docker 與 rootless Podman 由主機程式接收的連接埠與容器看到的來源位址</a> 與 <a href="/blog/devops/infra/03-network-foundation/hands-on/ufw-docker-walkthrough/" data-link-title="ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑" data-link-desc="在 Ubuntu 的 ufw 主機上用 Docker 發佈連接埠、想只放行 Cloudflare 的位址、或想限制容器送出的 DNS 查詢時，查 ufw 規則對發佈連接埠的效果、DOCKER-USER 規則在 reload 與重開機之後還在不在、IPv6 那一份發佈由誰接收，以及 compose 容器的 DNS 封包從哪裡送出">ufw 主機上 Docker 發佈連接埠的實測紀錄：ufw 規則的效果、DOCKER-USER 規則的存續與 after.rules、IPv6 的 docker-proxy、compose 內建 DNS 的出站路徑</a> 的〈IPv6 那一份發佈：docker-proxy 的接收路徑與 ufw 入站規則的效果〉。</p>
]]></content:encoded></item><item><title>TOML</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/toml/</link><pubDate>Mon, 26 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/toml/</guid><description>&lt;p>TOML（Tom&amp;rsquo;s Obvious, Minimal Language）是一種給人讀寫的設定檔格式：每一行是 &lt;code>鍵 = 值&lt;/code>，值的型別由寫法本身決定（字串要加引號、數字與布林值不加），用方括號的表頭把鍵分組，可以寫 &lt;code>#&lt;/code> 開頭的註解，沒有用縮排表示結構。規格由 toml.io 維護，1.0.0 在 2021 年 1 月發佈，1.1.0 在 2025 年 12 月發佈；Python 的 &lt;code>pyproject.toml&lt;/code>、Rust 的 &lt;code>Cargo.toml&lt;/code> 都用這個格式，跟 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/dotenv/" data-link-title=".env" data-link-desc="存放環境變數的純文字檔案，把機密值從程式碼分離出來">.env&lt;/a> 同樣是描述設定的檔案：比起 HCL，TOML 只描述資料、沒有變數與函式；比起 .env，TOML 有型別、分組與巢狀結構。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>TOML 在設定檔格式裡的位置介於 JSON 與 YAML 之間：跟 JSON 一樣每個值的型別都由寫法明確決定，跟 YAML 一樣可以寫註解、適合人手動編輯。它只描述資料，需要變數、迴圈與條件來描述基礎設施時用的是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL&lt;/a> 這類設定語言。它在本站被用來寫主機的維護清單，選它的理由與實測見 &lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/host-manifest-in-toml/" data-link-title="主機維護清單寫成 TOML：JSON、YAML、TOML 的取捨、TOML 1.0 與 1.1 的版本差異、清單的欄位設計與只讀檢查腳本" data-link-desc="把一台主機的維護、暫停與恢復上線項目寫成一份資料檔：這份清單對格式的需求、JSON、YAML、TOML 逐項比對（YAML 把 no 讀成 false、010 讀成 8 的實測，以及 TOML 照樣把 1.10 讀成 1.1）、TOML 的表格與表格陣列寫法、1.0 與 1.1 的解析器差異（Python tomllib 的 TOMLDecodeError）、欄位怎麼依檢查腳本與人的需要設計，以及只讀檢查腳本的白名單條件、輸出與 exit code">主機維護清單寫成 TOML：JSON、YAML、TOML 的取捨、TOML 1.0 與 1.1 的版本差異、清單的欄位設計與只讀檢查腳本&lt;/a>；Python 打包設定裡的 TOML 用法見 &lt;a href="https://tarrragon.github.io/blog/programming/python/advanced/07-packaging/pyproject-toml/" data-link-title="6.1 pyproject.toml 完整指南" data-link-desc="理解現代 Python 套件的設定標準">6.1 pyproject.toml 完整指南&lt;/a>。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要讀懂 TOML 的情境包括：打開一個副檔名是 &lt;code>.toml&lt;/code> 的設定檔、要判斷某個值會被程式讀成字串還是數字、在 JSON、YAML、TOML 之間選設定檔格式，以及解析器回報 &lt;code>TOMLDecodeError&lt;/code> 這類錯誤、而同一份檔案在另一台機器上解析得過。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>TOML 的基本結構：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c"># 註解&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;web-demo&amp;#34;&lt;/span> &lt;span class="c"># 字串一定要加引號&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nx">port&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">443&lt;/span> &lt;span class="c"># 整數&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="nx">enabled&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="c"># 布林值只有 true 與 false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="nx">checked_at&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="ld">2026-10-26T06:21:00Z&lt;/span> &lt;span class="c"># 有時區的日期時間，解析成帶時區的時間物件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">host&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="c"># 表格（table）：底下的鍵屬於 host&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">provider&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;oracle-cloud&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 class="p">[[&lt;/span>&lt;span class="nx">entrypoint&lt;/span>&lt;span class="p">]]&lt;/span> &lt;span class="c"># 表格陣列（array of tables）：每出現一次就新增一筆&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nx">port&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">443&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="nx">exposure&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;cdn-only&amp;#34;&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;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="p">[[&lt;/span>&lt;span class="nx">entrypoint&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="nx">port&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">22&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="nx">exposure&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;admin-only&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;strong>&lt;code>[名稱]&lt;/code>&lt;/strong>：表格（table），指一組鍵值，等於程式裡的一個物件，跟試算表的格子無關；底下的鍵到下一個表頭為止都屬於它。&lt;/li>
&lt;li>&lt;strong>&lt;code>[[名稱]]&lt;/code>&lt;/strong>：表格陣列，每出現一次就在陣列裡加一筆，適合同一種項目要列很多筆的清單（入口、檢查項目、步驟）。&lt;/li>
&lt;li>&lt;strong>型別由寫法決定&lt;/strong>：&lt;code>&amp;quot;no&amp;quot;&lt;/code> 是字串，不加引號的 &lt;code>no&lt;/code> 不屬於任何型別，解析器報錯；YAML 1.1 的解析器（例如 PyYAML）把不加引號的 &lt;code>no&lt;/code> 讀成 &lt;code>false&lt;/code>（英文資料常稱為 Norway problem）、&lt;code>010&lt;/code> 讀成 8。寫法剛好屬於另一個型別時，TOML 一樣照那個型別讀：不加引號的 &lt;code>1.10&lt;/code> 是浮點數 1.1。所以版本號這類看起來像數字的字串要加引號。&lt;/li>
&lt;li>&lt;strong>日期與時間是原生型別&lt;/strong>：有時區的日期時間、沒有時區的日期時間、日期、時間各是一種型別，不必用字串存再自己解析。&lt;/li>
&lt;li>&lt;strong>巢狀的表格陣列&lt;/strong>：&lt;code>[[entrypoint.verify]]&lt;/code> 屬於它上面最近的那一筆 &lt;code>[[entrypoint]]&lt;/code>，讓每一筆項目各有自己的一組子項目。&lt;/li>
&lt;li>&lt;strong>寫在同一行的表格（inline table）&lt;/strong>：&lt;code>verify = { command = &amp;quot;...&amp;quot;, expect_stdout = &amp;quot;ok&amp;quot; }&lt;/code>，適合只有幾個欄位的子項目；TOML 1.0 要寫在一行內、結尾不能有逗號。&lt;/li>
&lt;li>&lt;strong>兩種字串&lt;/strong>：雙引號的基本字串處理反斜線跳脫（regex 的 &lt;code>\.&lt;/code> 寫成 &lt;code>\\.&lt;/code>），單引號的字面字串不處理任何跳脫（寫成 &lt;code>'\.'&lt;/code>）。&lt;/li>
&lt;li>&lt;strong>數字的分隔&lt;/strong>：整數可以用底線分隔位數，&lt;code>1_073_741_824&lt;/code> 等於 &lt;code>1073741824&lt;/code>。&lt;/li>
&lt;/ul>
&lt;p>跟其他格式的差別：&lt;/p></description><content:encoded><![CDATA[<p>TOML（Tom&rsquo;s Obvious, Minimal Language）是一種給人讀寫的設定檔格式：每一行是 <code>鍵 = 值</code>，值的型別由寫法本身決定（字串要加引號、數字與布林值不加），用方括號的表頭把鍵分組，可以寫 <code>#</code> 開頭的註解，沒有用縮排表示結構。規格由 toml.io 維護，1.0.0 在 2021 年 1 月發佈，1.1.0 在 2025 年 12 月發佈；Python 的 <code>pyproject.toml</code>、Rust 的 <code>Cargo.toml</code> 都用這個格式，跟 <a href="/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL</a> 與 <a href="/blog/devops/infra/knowledge-cards/dotenv/" data-link-title=".env" data-link-desc="存放環境變數的純文字檔案，把機密值從程式碼分離出來">.env</a> 同樣是描述設定的檔案：比起 HCL，TOML 只描述資料、沒有變數與函式；比起 .env，TOML 有型別、分組與巢狀結構。</p>
<h2 id="概念位置">概念位置</h2>
<p>TOML 在設定檔格式裡的位置介於 JSON 與 YAML 之間：跟 JSON 一樣每個值的型別都由寫法明確決定，跟 YAML 一樣可以寫註解、適合人手動編輯。它只描述資料，需要變數、迴圈與條件來描述基礎設施時用的是 <a href="/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL</a> 這類設定語言。它在本站被用來寫主機的維護清單，選它的理由與實測見 <a href="/blog/devops/operations/09-host-lifecycle/host-manifest-in-toml/" data-link-title="主機維護清單寫成 TOML：JSON、YAML、TOML 的取捨、TOML 1.0 與 1.1 的版本差異、清單的欄位設計與只讀檢查腳本" data-link-desc="把一台主機的維護、暫停與恢復上線項目寫成一份資料檔：這份清單對格式的需求、JSON、YAML、TOML 逐項比對（YAML 把 no 讀成 false、010 讀成 8 的實測，以及 TOML 照樣把 1.10 讀成 1.1）、TOML 的表格與表格陣列寫法、1.0 與 1.1 的解析器差異（Python tomllib 的 TOMLDecodeError）、欄位怎麼依檢查腳本與人的需要設計，以及只讀檢查腳本的白名單條件、輸出與 exit code">主機維護清單寫成 TOML：JSON、YAML、TOML 的取捨、TOML 1.0 與 1.1 的版本差異、清單的欄位設計與只讀檢查腳本</a>；Python 打包設定裡的 TOML 用法見 <a href="/blog/programming/python/advanced/07-packaging/pyproject-toml/" data-link-title="6.1 pyproject.toml 完整指南" data-link-desc="理解現代 Python 套件的設定標準">6.1 pyproject.toml 完整指南</a>。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要讀懂 TOML 的情境包括：打開一個副檔名是 <code>.toml</code> 的設定檔、要判斷某個值會被程式讀成字串還是數字、在 JSON、YAML、TOML 之間選設定檔格式，以及解析器回報 <code>TOMLDecodeError</code> 這類錯誤、而同一份檔案在另一台機器上解析得過。</p>
<h2 id="設計責任">設計責任</h2>
<p>TOML 的基本結構：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># 註解</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;web-demo&#34;</span>          <span class="c"># 字串一定要加引號</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">port</span> <span class="p">=</span> <span class="mi">443</span>                 <span class="c"># 整數</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">enabled</span> <span class="p">=</span> <span class="kc">true</span>             <span class="c"># 布林值只有 true 與 false</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nx">checked_at</span> <span class="p">=</span> <span class="ld">2026-10-26T06:21:00Z</span>   <span class="c"># 有時區的日期時間，解析成帶時區的時間物件</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="p">[</span><span class="nx">host</span><span class="p">]</span>                     <span class="c"># 表格（table）：底下的鍵屬於 host</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">provider</span> <span class="p">=</span> <span class="s2">&#34;oracle-cloud&#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 class="p">[[</span><span class="nx">entrypoint</span><span class="p">]]</span>             <span class="c"># 表格陣列（array of tables）：每出現一次就新增一筆</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">port</span> <span class="p">=</span> <span class="mi">443</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">exposure</span> <span class="p">=</span> <span class="s2">&#34;cdn-only&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">[[</span><span class="nx">entrypoint</span><span class="p">]]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nx">port</span> <span class="p">=</span> <span class="mi">22</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nx">exposure</span> <span class="p">=</span> <span class="s2">&#34;admin-only&#34;</span></span></span></code></pre></div><ul>
<li><strong><code>[名稱]</code></strong>：表格（table），指一組鍵值，等於程式裡的一個物件，跟試算表的格子無關；底下的鍵到下一個表頭為止都屬於它。</li>
<li><strong><code>[[名稱]]</code></strong>：表格陣列，每出現一次就在陣列裡加一筆，適合同一種項目要列很多筆的清單（入口、檢查項目、步驟）。</li>
<li><strong>型別由寫法決定</strong>：<code>&quot;no&quot;</code> 是字串，不加引號的 <code>no</code> 不屬於任何型別，解析器報錯；YAML 1.1 的解析器（例如 PyYAML）把不加引號的 <code>no</code> 讀成 <code>false</code>（英文資料常稱為 Norway problem）、<code>010</code> 讀成 8。寫法剛好屬於另一個型別時，TOML 一樣照那個型別讀：不加引號的 <code>1.10</code> 是浮點數 1.1。所以版本號這類看起來像數字的字串要加引號。</li>
<li><strong>日期與時間是原生型別</strong>：有時區的日期時間、沒有時區的日期時間、日期、時間各是一種型別，不必用字串存再自己解析。</li>
<li><strong>巢狀的表格陣列</strong>：<code>[[entrypoint.verify]]</code> 屬於它上面最近的那一筆 <code>[[entrypoint]]</code>，讓每一筆項目各有自己的一組子項目。</li>
<li><strong>寫在同一行的表格（inline table）</strong>：<code>verify = { command = &quot;...&quot;, expect_stdout = &quot;ok&quot; }</code>，適合只有幾個欄位的子項目；TOML 1.0 要寫在一行內、結尾不能有逗號。</li>
<li><strong>兩種字串</strong>：雙引號的基本字串處理反斜線跳脫（regex 的 <code>\.</code> 寫成 <code>\\.</code>），單引號的字面字串不處理任何跳脫（寫成 <code>'\.'</code>）。</li>
<li><strong>數字的分隔</strong>：整數可以用底線分隔位數，<code>1_073_741_824</code> 等於 <code>1073741824</code>。</li>
</ul>
<p>跟其他格式的差別：</p>
<table>
  <thead>
      <tr>
          <th>格式</th>
          <th>註解</th>
          <th>型別怎麼決定</th>
          <th>結構怎麼表示</th>
          <th>適合的場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>JSON</td>
          <td>不能</td>
          <td>寫法決定，規則嚴格</td>
          <td>大括號與中括號</td>
          <td>程式之間交換資料</td>
      </tr>
      <tr>
          <td>YAML</td>
          <td>能</td>
          <td>不加引號的值由解析器推測（版本之間規則不同）</td>
          <td>縮排</td>
          <td>巢狀很深的設定（Kubernetes、CI 的 workflow）</td>
      </tr>
      <tr>
          <td>TOML</td>
          <td>能</td>
          <td>寫法決定，不加引號又不是合法值時報錯</td>
          <td>表頭與表格陣列</td>
          <td>人手動維護、層數不深的設定與清單</td>
      </tr>
  </tbody>
</table>
<p><strong>版本對解析器的影響</strong>：1.1.0 新增的語法（寫在同一行的表格 inline table 可以跨行並在結尾加逗號、<code>\xHH</code> 與 <code>\e</code> 跳脫、時間可以省略秒）只有支援 1.1 的解析器讀得懂。Python 的標準函式庫 <code>tomllib</code> 在 3.11 加入時支援 1.0，3.15 起支援 1.1，3.10 以下沒有 <code>tomllib</code>、要另裝 <code>tomli</code>。同一份用了 1.1 語法的檔案，在 Python 3.15 解析得過，在 3.12 會報錯；版本對不上時的兩種結果與寫檔時的對策，見 <a href="/blog/devops/operations/09-host-lifecycle/host-manifest-in-toml/" data-link-title="主機維護清單寫成 TOML：JSON、YAML、TOML 的取捨、TOML 1.0 與 1.1 的版本差異、清單的欄位設計與只讀檢查腳本" data-link-desc="把一台主機的維護、暫停與恢復上線項目寫成一份資料檔：這份清單對格式的需求、JSON、YAML、TOML 逐項比對（YAML 把 no 讀成 false、010 讀成 8 的實測，以及 TOML 照樣把 1.10 讀成 1.1）、TOML 的表格與表格陣列寫法、1.0 與 1.1 的解析器差異（Python tomllib 的 TOMLDecodeError）、欄位怎麼依檢查腳本與人的需要設計，以及只讀檢查腳本的白名單條件、輸出與 exit code">主機維護清單寫成 TOML：JSON、YAML、TOML 的取捨、TOML 1.0 與 1.1 的版本差異、清單的欄位設計與只讀檢查腳本</a>〈TOML 1.0 與 1.1 的差異與解析器的版本〉。<code>tomllib</code> 只能讀不能寫，要由程式寫出 TOML 時需要第三方套件。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/yaml/" data-link-title="YAML" data-link-desc="讀到 .yaml 或 .yml 設定檔、一個沒加引號的值被程式讀成布林值或別的數字、同一份檔案換一個解析器結果不同，或要在 JSON、YAML、TOML 之間選設定檔格式時">YAML</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/dotenv/" data-link-title=".env" data-link-desc="存放環境變數的純文字檔案，把機密值從程式碼分離出來">.env</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini</a></li>
</ul>
]]></content:encoded></item><item><title>YAML</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/yaml/</link><pubDate>Mon, 26 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/yaml/</guid><description>&lt;p>YAML（YAML Ain&amp;rsquo;t Markup Language）是一種用縮排表示結構的資料格式：&lt;code>鍵: 值&lt;/code> 組成對應表，&lt;code>- &lt;/code> 開頭的行組成清單，子項目往右縮排一層，可以寫 &lt;code>#&lt;/code> 開頭的註解，不加引號的值由解析器依寫法推測型別。Kubernetes 的 manifest、GitHub Actions 的 workflow、Ansible 的 playbook 與 inventory、Docker Compose 的設定檔都用這個格式（Ansible 的變數檔一樣受下面的隱式型別影響，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">Configuration Management（組態管理）&lt;/a>）。同樣是給人手動編輯的設定檔格式，&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/toml/" data-link-title="TOML" data-link-desc="讀到 .toml 設定檔、要判斷一個值會被解析成什麼型別、要在 JSON、YAML、TOML 之間選設定檔格式，或解析器版本對不上而報錯時">TOML&lt;/a> 用表頭分組、型別完全由寫法決定；&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL&lt;/a> 另外有變數與函式，YAML 與 TOML 一樣只描述資料。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>YAML 在設定檔格式裡的位置是巢狀深、層數多的設定：縮排一層就是一層結構，寫到第五、六層仍然不必重複上層的名字，這是 Kubernetes 與 CI 工具選它的理由，&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/helm/" data-link-title="Helm" data-link-desc="Kubernetes 的套件管理工具，用 chart 打包一組 K8s 資源的部署定義">Helm&lt;/a> 的 chart 參數檔 &lt;code>values.yaml&lt;/code> 也是這個格式。付出的代價在型別：不加引號的值是什麼型別，由解析器實作的 YAML 版本決定，而一份檔案自己沒有辦法指定讀它的解析器。它跟 TOML、JSON 逐項比對的需求表與實測，見 &lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/host-manifest-in-toml/" data-link-title="主機維護清單寫成 TOML：JSON、YAML、TOML 的取捨、TOML 1.0 與 1.1 的版本差異、清單的欄位設計與只讀檢查腳本" data-link-desc="把一台主機的維護、暫停與恢復上線項目寫成一份資料檔：這份清單對格式的需求、JSON、YAML、TOML 逐項比對（YAML 把 no 讀成 false、010 讀成 8 的實測，以及 TOML 照樣把 1.10 讀成 1.1）、TOML 的表格與表格陣列寫法、1.0 與 1.1 的解析器差異（Python tomllib 的 TOMLDecodeError）、欄位怎麼依檢查腳本與人的需要設計，以及只讀檢查腳本的白名單條件、輸出與 exit code">主機維護清單寫成 TOML：JSON、YAML、TOML 的取捨、TOML 1.0 與 1.1 的版本差異、清單的欄位設計與只讀檢查腳本&lt;/a>〈主機維護清單對檔案格式的需求：JSON、YAML、TOML 的比對〉。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>需要讀懂 YAML 的情境包括：打開一個副檔名是 &lt;code>.yaml&lt;/code> 或 &lt;code>.yml&lt;/code> 的檔案、程式讀到的值跟檔案上寫的字不一樣（寫 &lt;code>no&lt;/code> 讀成 &lt;code>false&lt;/code>、寫 &lt;code>010&lt;/code> 讀成 8）、同一份檔案在兩個工具裡讀出不同的結果，以及解析器回報 &lt;code>ScannerError&lt;/code> 或 &lt;code>found character '\t' that cannot start any token&lt;/code> 這類縮排錯誤。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>YAML 的基本結構：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c"># 註解&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">web-demo &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 對應表的一個鍵；不加引號的 web-demo 讀成字串&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">443&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 讀成整數&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">entrypoints&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 值是一份清單&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">443&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 清單的每一項以 &amp;#34;- &amp;#34; 開頭，項目本身可以是對應表&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">exposure&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cdn-only&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">22&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">exposure&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin-only&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">note&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">| &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># | 保留換行的多行字串&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">第一行&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">第二行&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;strong>縮排就是結構&lt;/strong>：同一層的鍵左側對齊，子項目多縮排一層；縮排只能用空格，Tab 字元會讓解析器回報 &lt;code>ScannerError&lt;/code>。&lt;/li>
&lt;li>&lt;strong>不加引號的值由解析器推測型別&lt;/strong>：這是 YAML 最常讓設定出錯的位置，見下面的版本差異。字串加上引號（&lt;code>&amp;quot;no&amp;quot;&lt;/code>、&lt;code>'010'&lt;/code>）就不會被推測。&lt;/li>
&lt;li>&lt;strong>重複的鍵不報錯&lt;/strong>：PyYAML 6.0.3 解析 &lt;code>a: 1&lt;/code> 接著 &lt;code>a: 2&lt;/code>，得到 &lt;code>{'a': 2}&lt;/code>，前一個值被安靜地蓋掉；手動維護的長檔案裡重複一個鍵，前面那一筆設定就不生效。&lt;/li>
&lt;li>&lt;strong>錨點與別名&lt;/strong>：&lt;code>&amp;amp;名稱&lt;/code> 標記一個節點、&lt;code>*名稱&lt;/code> 引用它，用來在同一份檔案裡重用一段設定；讀的人要回頭找錨點才知道別名展開成什麼。&lt;/li>
&lt;li>&lt;strong>一個檔案可以有多份文件&lt;/strong>：&lt;code>---&lt;/code> 分隔多份文件，Kubernetes 常用這個寫法把多個資源放在同一個檔案裡。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>版本決定型別的推測規則&lt;/strong>：YAML 1.1（2005 年）與 1.2（2009 年）對不加引號的值規則不同。同樣這五行：&lt;/p></description><content:encoded><![CDATA[<p>YAML（YAML Ain&rsquo;t Markup Language）是一種用縮排表示結構的資料格式：<code>鍵: 值</code> 組成對應表，<code>- </code> 開頭的行組成清單，子項目往右縮排一層，可以寫 <code>#</code> 開頭的註解，不加引號的值由解析器依寫法推測型別。Kubernetes 的 manifest、GitHub Actions 的 workflow、Ansible 的 playbook 與 inventory、Docker Compose 的設定檔都用這個格式（Ansible 的變數檔一樣受下面的隱式型別影響，見 <a href="/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">Configuration Management（組態管理）</a>）。同樣是給人手動編輯的設定檔格式，<a href="/blog/devops/infra/knowledge-cards/toml/" data-link-title="TOML" data-link-desc="讀到 .toml 設定檔、要判斷一個值會被解析成什麼型別、要在 JSON、YAML、TOML 之間選設定檔格式，或解析器版本對不上而報錯時">TOML</a> 用表頭分組、型別完全由寫法決定；<a href="/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL</a> 另外有變數與函式，YAML 與 TOML 一樣只描述資料。</p>
<h2 id="概念位置">概念位置</h2>
<p>YAML 在設定檔格式裡的位置是巢狀深、層數多的設定：縮排一層就是一層結構，寫到第五、六層仍然不必重複上層的名字，這是 Kubernetes 與 CI 工具選它的理由，<a href="/blog/devops/infra/knowledge-cards/helm/" data-link-title="Helm" data-link-desc="Kubernetes 的套件管理工具，用 chart 打包一組 K8s 資源的部署定義">Helm</a> 的 chart 參數檔 <code>values.yaml</code> 也是這個格式。付出的代價在型別：不加引號的值是什麼型別，由解析器實作的 YAML 版本決定，而一份檔案自己沒有辦法指定讀它的解析器。它跟 TOML、JSON 逐項比對的需求表與實測，見 <a href="/blog/devops/operations/09-host-lifecycle/host-manifest-in-toml/" data-link-title="主機維護清單寫成 TOML：JSON、YAML、TOML 的取捨、TOML 1.0 與 1.1 的版本差異、清單的欄位設計與只讀檢查腳本" data-link-desc="把一台主機的維護、暫停與恢復上線項目寫成一份資料檔：這份清單對格式的需求、JSON、YAML、TOML 逐項比對（YAML 把 no 讀成 false、010 讀成 8 的實測，以及 TOML 照樣把 1.10 讀成 1.1）、TOML 的表格與表格陣列寫法、1.0 與 1.1 的解析器差異（Python tomllib 的 TOMLDecodeError）、欄位怎麼依檢查腳本與人的需要設計，以及只讀檢查腳本的白名單條件、輸出與 exit code">主機維護清單寫成 TOML：JSON、YAML、TOML 的取捨、TOML 1.0 與 1.1 的版本差異、清單的欄位設計與只讀檢查腳本</a>〈主機維護清單對檔案格式的需求：JSON、YAML、TOML 的比對〉。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>需要讀懂 YAML 的情境包括：打開一個副檔名是 <code>.yaml</code> 或 <code>.yml</code> 的檔案、程式讀到的值跟檔案上寫的字不一樣（寫 <code>no</code> 讀成 <code>false</code>、寫 <code>010</code> 讀成 8）、同一份檔案在兩個工具裡讀出不同的結果，以及解析器回報 <code>ScannerError</code> 或 <code>found character '\t' that cannot start any token</code> 這類縮排錯誤。</p>
<h2 id="設計責任">設計責任</h2>
<p>YAML 的基本結構：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># 註解</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">web-demo           </span><span class="w"> </span><span class="c"># 對應表的一個鍵；不加引號的 web-demo 讀成字串</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">443</span><span class="w">                 </span><span class="c"># 讀成整數</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="nt">entrypoints</span><span class="p">:</span><span class="w">              </span><span class="c"># 值是一份清單</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">  </span>- <span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">443</span><span class="w">             </span><span class="c"># 清單的每一項以 &#34;- &#34; 開頭，項目本身可以是對應表</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">exposure</span><span class="p">:</span><span class="w"> </span><span class="l">cdn-only</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">  </span>- <span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">22</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="nt">exposure</span><span class="p">:</span><span class="w"> </span><span class="l">admin-only</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w"></span><span class="nt">note</span><span class="p">:</span><span class="w"> </span><span class="l">|                  </span><span class="w"> </span><span class="c"># | 保留換行的多行字串</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">  </span><span class="l">第一行</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">  </span><span class="l">第二行</span></span></span></code></pre></div><ul>
<li><strong>縮排就是結構</strong>：同一層的鍵左側對齊，子項目多縮排一層；縮排只能用空格，Tab 字元會讓解析器回報 <code>ScannerError</code>。</li>
<li><strong>不加引號的值由解析器推測型別</strong>：這是 YAML 最常讓設定出錯的位置，見下面的版本差異。字串加上引號（<code>&quot;no&quot;</code>、<code>'010'</code>）就不會被推測。</li>
<li><strong>重複的鍵不報錯</strong>：PyYAML 6.0.3 解析 <code>a: 1</code> 接著 <code>a: 2</code>，得到 <code>{'a': 2}</code>，前一個值被安靜地蓋掉；手動維護的長檔案裡重複一個鍵，前面那一筆設定就不生效。</li>
<li><strong>錨點與別名</strong>：<code>&amp;名稱</code> 標記一個節點、<code>*名稱</code> 引用它，用來在同一份檔案裡重用一段設定；讀的人要回頭找錨點才知道別名展開成什麼。</li>
<li><strong>一個檔案可以有多份文件</strong>：<code>---</code> 分隔多份文件，Kubernetes 常用這個寫法把多個資源放在同一個檔案裡。</li>
</ul>
<p><strong>版本決定型別的推測規則</strong>：YAML 1.1（2005 年）與 1.2（2009 年）對不加引號的值規則不同。同樣這五行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">country</span><span class="p">:</span><span class="w"> </span><span class="kc">no</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="m">010</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="nt">port_map</span><span class="p">:</span><span class="w"> </span><span class="m">22</span><span class="p">:</span><span class="m">22</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">1.10</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div><p>交給兩個 Python 解析器，結果是：</p>
<table>
  <thead>
      <tr>
          <th>這一行</th>
          <th>PyYAML 6.0.3（實作 YAML 1.1）</th>
          <th>ruamel.yaml 0.19.1（實作 YAML 1.2）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>country: no</code></td>
          <td>布林值 <code>False</code></td>
          <td>字串 <code>'no'</code></td>
      </tr>
      <tr>
          <td><code>mode: 010</code></td>
          <td>八進位的 <code>8</code></td>
          <td>十進位的 <code>10</code></td>
      </tr>
      <tr>
          <td><code>port_map: 22:22</code></td>
          <td>六十進位的 <code>1342</code></td>
          <td>字串 <code>'22:22'</code></td>
      </tr>
      <tr>
          <td><code>version: 1.10</code></td>
          <td>浮點數 <code>1.1</code></td>
          <td>浮點數 <code>1.1</code></td>
      </tr>
      <tr>
          <td><code>on: true</code>（鍵名）</td>
          <td>鍵變成布林值 <code>True</code></td>
          <td>鍵是字串 <code>'on'</code></td>
      </tr>
  </tbody>
</table>
<p>兩個解析器都沒有報錯。<code>no</code> 被讀成 <code>false</code> 這一種，英文資料常稱為 Norway problem（挪威的國家代碼 <code>NO</code> 變成布林值）。YAML 1.2 把布林值限定成 <code>true</code> 與 <code>false</code> 及其大小寫變體，所以 1.1 的幾個陷阱消失；<code>1.10</code> 在兩個版本都是合法的浮點數，所以版本號不論哪一版都要加引號。一份 YAML 檔被讀成哪一版，由讀它的程式選的解析器決定：PyYAML 至今實作 1.1；Go 的 <code>gopkg.in/yaml.v3</code> 依它的 README 以 1.2 為主，而解碼到布林型別的欄位時仍接受 1.1 的 <code>yes</code>、<code>no</code>、<code>on</code>、<code>off</code>（這一點照文件，沒有實跑），所以寫檔的人無法只看檔案判斷結果，跨工具共用的值一律加引號。這張表在 <code>python:3.12-slim</code> 容器裡以兩個套件的 <code>safe_load</code> 實跑。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/toml/" data-link-title="TOML" data-link-desc="讀到 .toml 設定檔、要判斷一個值會被解析成什麼型別、要在 JSON、YAML、TOML 之間選設定檔格式，或解析器版本對不上而報錯時">TOML</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/hcl/" data-link-title="HCL" data-link-desc="Terraform 使用的宣告式設定語言，用 resource block 描述基礎設施的目標狀態，工具負責收斂差異">HCL</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/helm/" data-link-title="Helm" data-link-desc="Kubernetes 的套件管理工具，用 chart 打包一組 K8s 資源的部署定義">Helm</a></li>
</ul>
]]></content:encoded></item><item><title>Orphaned Resource（孤兒資源）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/orphaned-resource/</link><pubDate>Mon, 26 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/orphaned-resource/</guid><description>&lt;p>孤兒資源（orphaned resource）是雲端帳號裡，原本為某一台主機或某一項服務建立、而那台主機已經刪除或停用之後仍然存在的資源：沒有掛在任何主機上的磁碟、沒有附在任何主機上的保留 IP、主機刪除後留下的快照與備份、後面已經沒有主機的負載平衡器與 NAT 閘道。它們在對這類資源收費的業者帳單上各自計費、各自佔用免費額度，而主控台的主機列表裡看不到它們。跟它相關的是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection&lt;/a>（刪除保護）：刪除保護防的是該留的資源被誤刪，孤兒資源是該刪的資源沒有刪。&lt;/p>
&lt;p>同名而所指不同的兩個詞：Linux 的孤兒程序（orphan process）是父程序先結束、被 PID 1 收養的程序；套件管理器的孤兒套件是當初作為相依被裝進來、而依賴它的套件已經移除的套件（&lt;code>pacman -Qtd&lt;/code>、&lt;code>dnf autoremove&lt;/code> 處理的對象）。本卡講的是雲端帳號裡的資源。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>孤兒資源的成因是建立與刪除的範圍不對稱：建立主機的精靈一次建出主機、開機磁碟、公開 IP，有時連網路一起建；刪除時每一項的去留各有預設，業者為了避免誤刪資料，常把磁碟（AWS 的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ebs/" data-link-title="EBS" data-link-desc="掛在 EC2 instance 上的持久化區塊儲存（虛擬磁碟），支援 snapshot 快照備份，跟 instance 獨立生命週期">EBS&lt;/a> 這類區塊磁碟）的預設設成保留（Oracle Cloud 終止主機時開機磁碟預設保留）。各家的預設與欄位名稱見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留&lt;/a>〈刪除（終止）主機時開機磁碟的預設去留〉；暫停一台主機時逐項要決定保留或刪除的資源，見 &lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/suspend-and-resume-service/" data-link-title="服務暫停與恢復上線：只停服務、停止主機、終止主機到全部刪除各自保留的資源，暫停期間仍計費與仍被指向的資源，恢復前要查的漂移與恢復的順序" data-link-desc="一台主機上的服務要暫停一段時間再恢復：只停服務、停止主機、終止主機保留設定、全部刪除靠 repo 重建各自保留什麼與付出什麼，怎麼依保留的月費、重建的成本與漂移量選擇，暫停期間仍在計費的資源（含在作業系統裡 shutdown 不停止計費），仍被 DNS 與 CDN 指向的結果（Cloudflare 521、522，釋放 IP 之後的懸空 DNS），恢復前要重新確認的漂移，以及依元件依賴排出的恢復順序與驗證">服務暫停與恢復上線：只停服務、停止主機、終止主機到全部刪除各自保留的資源，暫停期間仍計費與仍被指向的資源，恢復前要查的漂移與恢復的順序&lt;/a>〈暫停期間仍在計費的資源〉。帳單層看到的「被遺忘的資源」是同一件事，見 &lt;a href="https://tarrragon.github.io/blog/devops/operations/08-cost-management/billing-models/" data-link-title="計費模式理解" data-link-desc="看懂雲端帳單怎麼生成時，先認清收費的維度、on-demand/reserved/savings plan/spot 的承諾差異、以及 egress 這類最常被忽略的隱藏成本">計費模式理解&lt;/a>〈隱藏成本：帳單上的意外〉。&lt;/p>
&lt;p>DNS 層有一個對應的情形：主機與 IP 都刪了，DNS 紀錄還指著那個位址，這是懸空 DNS 的成因，見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS&lt;/a>〈懸空 DNS 與子網域接管〉。孤兒資源的後果是多付錢與佔額度；懸空 DNS 的後果是業者把位址分給別的客戶之後，網域的流量送到別人的主機。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>主機都刪了，帳單上的區塊儲存、公開 IP 或快照費用沒有歸零。&lt;/li>
&lt;li>免費帳號建新主機時回報區塊儲存額度不足，而執行中的主機加起來沒有用到那麼多。Oracle Cloud 的付費帳戶在額度不足時，沒有設 compartment quota 的話照樣建好、不報錯（設了 quota 時超額的建立回 400），對應的訊號是區塊儲存的總用量（含付費磁碟的 &lt;code>total-storage-gb&lt;/code>）大於掛在執行中主機上的磁碟合計；只看免費額度的用量會漏掉，因為有付費磁碟時它比總用量小（建立不被擋下是 2026-10-27 實測，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/" data-link-title="Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建" data-link-desc="要在 Oracle Cloud 沒有公開 IP 的私有子網路裡跑一台 Ubuntu 主機、讓它裝得到套件更新，或要經由序列主控台看主機的開機過程，或主機被閒置回收、停止或刪除之後要照原樣重建，或想知道 NAT gateway 要不要另外付費、開機磁碟扣掉多少區塊磁碟額度、合計超過 200 GB 時放不進額度的那一顆怎麼算、怎麼用 quota 擋下超額的建立、線上加大開機磁碟超過額度時怎麼算、空出額度之後付費磁碟多久轉成免費時，查實際跑過的指令、錯誤原文、重建腳本與每一步的讀法">Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建&lt;/a>〈費用與額度：A1、開機磁碟與區塊儲存額度、出站流量與 NAT gateway〉）。&lt;/li>
&lt;li>主控台的磁碟列表裡有狀態是「未掛載」的磁碟（AWS 的 EBS 叫 &lt;code>available&lt;/code>），名稱裡帶著已經不存在的主機名。&lt;/li>
&lt;li>公開 IP 列表裡有沒有附在任何主機上的保留 IP。&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>孤兒資源的處理分在建立、刪除與盤點三個時間點：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>建立時記下歸屬&lt;/strong>：替主機與它的附屬資源加上同一組標籤（專案名、用途、建立者），刪除主機之後依標籤查得出還剩哪些資源；沒有標籤的磁碟只能靠名稱與建立時間猜，猜錯就刪到還在用的資料。&lt;/li>
&lt;li>&lt;strong>刪除時逐項決定&lt;/strong>：終止主機的對話框裡，開機磁碟要不要一起刪是一個選項，選了保留就代表之後要回來處理它；保留 IP 與快照不在那個對話框裡，要另外到各自的列表刪。&lt;/li>
&lt;li>&lt;strong>定期盤點&lt;/strong>：依資源類型列出沒有附在任何主機上的項目，跟標籤比對；確認不再需要之後才刪，磁碟與快照刪了就救不回來，所以刪之前先確認裡面的資料有沒有另外的備份。&lt;/li>
&lt;li>&lt;strong>用 IaC 管理時&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">Infrastructure as Code (IaC)&lt;/a> 的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State（IaC 狀態檔）&lt;/a> 記著它建過的資源，&lt;code>terraform destroy&lt;/code> 會連同附屬資源一起刪；在主控台手動建、沒有寫進狀態檔的資源不在它的範圍內，destroy 之後照樣留在帳號裡。主控台手動改過 IaC 建的資源，則是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift（設定漂移）&lt;/a>。&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ebs/" data-link-title="EBS" data-link-desc="掛在 EC2 instance 上的持久化區塊儲存（虛擬磁碟），支援 snapshot 快照備份，跟 instance 獨立生命週期">EBS&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift（設定漂移）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>孤兒資源（orphaned resource）是雲端帳號裡，原本為某一台主機或某一項服務建立、而那台主機已經刪除或停用之後仍然存在的資源：沒有掛在任何主機上的磁碟、沒有附在任何主機上的保留 IP、主機刪除後留下的快照與備份、後面已經沒有主機的負載平衡器與 NAT 閘道。它們在對這類資源收費的業者帳單上各自計費、各自佔用免費額度，而主控台的主機列表裡看不到它們。跟它相關的是 <a href="/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection</a>（刪除保護）：刪除保護防的是該留的資源被誤刪，孤兒資源是該刪的資源沒有刪。</p>
<p>同名而所指不同的兩個詞：Linux 的孤兒程序（orphan process）是父程序先結束、被 PID 1 收養的程序；套件管理器的孤兒套件是當初作為相依被裝進來、而依賴它的套件已經移除的套件（<code>pacman -Qtd</code>、<code>dnf autoremove</code> 處理的對象）。本卡講的是雲端帳號裡的資源。</p>
<h2 id="概念位置">概念位置</h2>
<p>孤兒資源的成因是建立與刪除的範圍不對稱：建立主機的精靈一次建出主機、開機磁碟、公開 IP，有時連網路一起建；刪除時每一項的去留各有預設，業者為了避免誤刪資料，常把磁碟（AWS 的 <a href="/blog/devops/infra/knowledge-cards/ebs/" data-link-title="EBS" data-link-desc="掛在 EC2 instance 上的持久化區塊儲存（虛擬磁碟），支援 snapshot 快照備份，跟 instance 獨立生命週期">EBS</a> 這類區塊磁碟）的預設設成保留（Oracle Cloud 終止主機時開機磁碟預設保留）。各家的預設與欄位名稱見 <a href="/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留</a>〈刪除（終止）主機時開機磁碟的預設去留〉；暫停一台主機時逐項要決定保留或刪除的資源，見 <a href="/blog/devops/operations/09-host-lifecycle/suspend-and-resume-service/" data-link-title="服務暫停與恢復上線：只停服務、停止主機、終止主機到全部刪除各自保留的資源，暫停期間仍計費與仍被指向的資源，恢復前要查的漂移與恢復的順序" data-link-desc="一台主機上的服務要暫停一段時間再恢復：只停服務、停止主機、終止主機保留設定、全部刪除靠 repo 重建各自保留什麼與付出什麼，怎麼依保留的月費、重建的成本與漂移量選擇，暫停期間仍在計費的資源（含在作業系統裡 shutdown 不停止計費），仍被 DNS 與 CDN 指向的結果（Cloudflare 521、522，釋放 IP 之後的懸空 DNS），恢復前要重新確認的漂移，以及依元件依賴排出的恢復順序與驗證">服務暫停與恢復上線：只停服務、停止主機、終止主機到全部刪除各自保留的資源，暫停期間仍計費與仍被指向的資源，恢復前要查的漂移與恢復的順序</a>〈暫停期間仍在計費的資源〉。帳單層看到的「被遺忘的資源」是同一件事，見 <a href="/blog/devops/operations/08-cost-management/billing-models/" data-link-title="計費模式理解" data-link-desc="看懂雲端帳單怎麼生成時，先認清收費的維度、on-demand/reserved/savings plan/spot 的承諾差異、以及 egress 這類最常被忽略的隱藏成本">計費模式理解</a>〈隱藏成本：帳單上的意外〉。</p>
<p>DNS 層有一個對應的情形：主機與 IP 都刪了，DNS 紀錄還指著那個位址，這是懸空 DNS 的成因，見 <a href="/blog/backend/10-system-evolution/namespace-and-domain-disposition/" data-link-title="10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS" data-link-desc="服務結束時網域與名稱的去向，以及它們決定外部引用是失效還是易手：網域的續約與出售、交給中立方的技術控制、套件與雲端資源名稱釋出後被重新申請與佔位、雲端資源刪除後的懸空 DNS 與子網域接管，以及綁在可過期網域上的帳號身分">10.6 命名空間與網域的處置：續約、名稱保留與回收、懸空 DNS</a>〈懸空 DNS 與子網域接管〉。孤兒資源的後果是多付錢與佔額度；懸空 DNS 的後果是業者把位址分給別的客戶之後，網域的流量送到別人的主機。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>主機都刪了，帳單上的區塊儲存、公開 IP 或快照費用沒有歸零。</li>
<li>免費帳號建新主機時回報區塊儲存額度不足，而執行中的主機加起來沒有用到那麼多。Oracle Cloud 的付費帳戶在額度不足時，沒有設 compartment quota 的話照樣建好、不報錯（設了 quota 時超額的建立回 400），對應的訊號是區塊儲存的總用量（含付費磁碟的 <code>total-storage-gb</code>）大於掛在執行中主機上的磁碟合計；只看免費額度的用量會漏掉，因為有付費磁碟時它比總用量小（建立不被擋下是 2026-10-27 實測，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/" data-link-title="Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建" data-link-desc="要在 Oracle Cloud 沒有公開 IP 的私有子網路裡跑一台 Ubuntu 主機、讓它裝得到套件更新，或要經由序列主控台看主機的開機過程，或主機被閒置回收、停止或刪除之後要照原樣重建，或想知道 NAT gateway 要不要另外付費、開機磁碟扣掉多少區塊磁碟額度、合計超過 200 GB 時放不進額度的那一顆怎麼算、怎麼用 quota 擋下超額的建立、線上加大開機磁碟超過額度時怎麼算、空出額度之後付費磁碟多久轉成免費時，查實際跑過的指令、錯誤原文、重建腳本與每一步的讀法">Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建</a>〈費用與額度：A1、開機磁碟與區塊儲存額度、出站流量與 NAT gateway〉）。</li>
<li>主控台的磁碟列表裡有狀態是「未掛載」的磁碟（AWS 的 EBS 叫 <code>available</code>），名稱裡帶著已經不存在的主機名。</li>
<li>公開 IP 列表裡有沒有附在任何主機上的保留 IP。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>孤兒資源的處理分在建立、刪除與盤點三個時間點：</p>
<ul>
<li><strong>建立時記下歸屬</strong>：替主機與它的附屬資源加上同一組標籤（專案名、用途、建立者），刪除主機之後依標籤查得出還剩哪些資源；沒有標籤的磁碟只能靠名稱與建立時間猜，猜錯就刪到還在用的資料。</li>
<li><strong>刪除時逐項決定</strong>：終止主機的對話框裡，開機磁碟要不要一起刪是一個選項，選了保留就代表之後要回來處理它；保留 IP 與快照不在那個對話框裡，要另外到各自的列表刪。</li>
<li><strong>定期盤點</strong>：依資源類型列出沒有附在任何主機上的項目，跟標籤比對；確認不再需要之後才刪，磁碟與快照刪了就救不回來，所以刪之前先確認裡面的資料有沒有另外的備份。</li>
<li><strong>用 IaC 管理時</strong>：<a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">Infrastructure as Code (IaC)</a> 的 <a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State（IaC 狀態檔）</a> 記著它建過的資源，<code>terraform destroy</code> 會連同附屬資源一起刪；在主控台手動建、沒有寫進狀態檔的資源不在它的範圍內，destroy 之後照樣留在帳號裡。主控台手動改過 IaC 建的資源，則是 <a href="/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift（設定漂移）</a>。</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/deletion-protection/" data-link-title="Deletion Protection" data-link-desc="雲端平台提供的防誤刪機制，開啟後刪除操作需要先顯式關閉保護才能執行">Deletion Protection</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/ebs/" data-link-title="EBS" data-link-desc="掛在 EC2 instance 上的持久化區塊儲存（虛擬磁碟），支援 snapshot 快照備份，跟 instance 獨立生命週期">EBS</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift（設定漂移）</a></li>
</ul>
]]></content:encoded></item><item><title>Configuration Management（組態管理）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/configuration-management/</link><pubDate>Mon, 26 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/configuration-management/</guid><description>&lt;p>組態管理（configuration management；其他中文資料也寫作設定管理）在主機維運的語境裡，指用工具把一群主機「內部」的狀態寫成宣告，再由工具把每台主機帶到那個狀態：要裝哪些套件、設定檔的內容、哪些服務要啟用、哪些使用者與權限要存在。Ansible、Puppet、Chef、Salt 是這一類工具。它跟 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">Infrastructure as Code (IaC)&lt;/a> 的分界在管理對象：IaC 管的是雲端業者那一端的資源（主機本身、網路、防火牆規則、DNS 紀錄），組態管理管的是主機開機之後作業系統裡面的套件、設定檔與服務；兩者常一起用，IaC 建出主機，組態管理把主機設定好。&lt;/p>
&lt;p>同名而所指不同的兩個詞：軟體工程裡的軟體組態管理（software configuration management，SCM）指原始碼的版本控制、基線與變更控制；中國大陸工業控制業界的「組態」（组态软件）指在 SCADA 或 HMI 軟體裡設定畫面與點位。本卡講的是主機設定的管理工具。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>組態管理工具都有兩個部分：主機清冊，與每台主機要套用的內容。Ansible 把主機清冊叫 inventory（列出受管主機與分組的檔案），跟商業領域的存貨（inventory）同字而無關；要套用的內容寫在 playbook（依序列出要對哪些主機套用哪些模組的 YAML 檔）與 role（把一類主機要用的任務、範本與變數收成一個可重用的目錄）裡，這個 playbook 跟事故處理的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/playbook/" data-link-title="Playbook" data-link-desc="說明場景化處置腳本如何降低事故處理不確定性">Playbook&lt;/a> 同名而所指不同。inventory 把主機分成群組（&lt;code>web&lt;/code>、&lt;code>db&lt;/code>），變數依層次放在 &lt;code>group_vars/&amp;lt;群組&amp;gt;.yml&lt;/code> 與 &lt;code>host_vars/&amp;lt;主機&amp;gt;.yml&lt;/code>，一台主機最後拿到的值是各層合併的結果，合併時越具體的層越優先：&lt;code>host_vars&lt;/code> 蓋過群組的值，子群組蓋過 &lt;code>all&lt;/code>。一台主機同時屬於兩個同層的群組、而兩個群組給了同一個變數不同的值時，依群組名稱的字母順序，排在後面的蓋過前面的；要指定誰優先，在 inventory 檔本身替群組設 &lt;code>ansible_group_priority&lt;/code>（預設 1，數字大的較晚合併而勝出），寫在 &lt;code>group_vars&lt;/code> 檔裡沒有效果；優先順序只會讓其中一組勝出，兼任兩種角色的主機需要兩組值都生效時，要讓兩個角色用不同的變數名、在範本裡取聯集。這幾條用 ansible-core 2.17 實測過，&lt;code>ansible-inventory --host &amp;lt;主機&amp;gt;&lt;/code> 印得出合併後的值，實測的檔案與輸出見 &lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/multi-host-manifest/" data-link-title="多台主機的維護清單：值的分層與合併的位置、用 Ansible 的 inventory 與範本產生每台的清單、由 Terraform 的輸出產生 inventory、跨主機執行的 exit code，以及單台清單的適用範圍" data-link-desc="把單台的主機維護清單擴充到多台主機：每台相同、依角色不同、每台不同的值怎麼分層，合併放在部署時產生每台一份、由檢查腳本讀多份、依角色過濾還是執行時覆寫，用 Ansible 的 inventory、群組變數與範本產生 TOML 清單時的變數合併、兼任角色、型別、語法撞名、跳脫與產生時的驗證，由 Terraform 的輸出產生 inventory、terraform plan 能取代哪些主控台上的確認，對多台執行檢查腳本時 exit code 的讀法與執行身分的代價，以及單台清單夠用的條件">多台主機的維護清單：值的分層與合併的位置、用 Ansible 的 inventory 與範本產生每台的清單、由 Terraform 的輸出產生 inventory、跨主機執行的 exit code，以及單台清單的適用範圍&lt;/a>。inventory 與 &lt;code>group_vars&lt;/code> 是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/yaml/" data-link-title="YAML" data-link-desc="讀到 .yaml 或 .yml 設定檔、一個沒加引號的值被程式讀成布林值或別的數字、同一份檔案換一個解析器結果不同，或要在 JSON、YAML、TOML 之間選設定檔格式時">YAML&lt;/a>，所以 YAML 1.1 的隱式型別照樣作用：&lt;code>country: no&lt;/code> 讀出來是布林值 &lt;code>false&lt;/code>。&lt;/p>
&lt;p>主機清冊可以手寫，也可以由 IaC 產生：Terraform 知道它建的每台主機的 IP 與角色，可以用 &lt;code>terraform output -json&lt;/code> 輸出給別的工具讀，或用 &lt;code>local_file&lt;/code> 這類資源直接寫出一份 inventory 檔。那份檔案是 Terraform 管理的一個資源，它的內容記在 Terraform 的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State（IaC 狀態檔）&lt;/a> 裡，手動改了它，下一次 &lt;code>terraform plan&lt;/code> 會要求重建、&lt;code>apply&lt;/code> 會蓋掉手改的內容。由 &lt;code>local_file&lt;/code> 寫出 inventory、經由 SSH 部署到兩台容器並執行檢查的實際操作，以及 &lt;code>file_permission&lt;/code> 的預設值扣掉 umask 之後的實際權限，在 &lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/hands-on/multi-host-manifest-walkthrough/" data-link-title="多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理" data-link-desc="要照著部署多台主機的維護清單並執行檢查腳本，或要讀跨主機執行時 ansible 的 exit code 與每台的 rc、門檻造成的 WARN、清單格式錯與連不上的主機各自印出什麼時，或要確認 Terraform 寫出的 inventory 的檔案權限、`--check` 預覽與實際部署結果不同的情形時，查在可拋棄的容器上實際跑過的指令、輸出與遇到的錯誤">多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理&lt;/a>〈由 Terraform 產生 inventory：init、plan、apply 與 local_file 的檔案權限〉。雲端資源的實際狀態跟 IaC 記錄的不一致叫 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift（設定漂移）&lt;/a>；主機裡的套件與設定檔跟組態管理的宣告不一致，有人同樣叫它 configuration drift，比對的兩邊換成了組態管理的宣告與主機內部的狀態；組態管理工具在下一次套用時把主機帶回宣告的狀態，兩次套用之間有人登入手改的部分，在那之前一直存在。&lt;/p>
&lt;p>Ansible 由執行指令的那台機器經由 SSH 連到每台主機，把要執行的模組送過去執行，主機上不必裝常駐的代理程式（agent）；Puppet 與 Chef 傳統上在每台主機跑一個代理程式，定期向伺服器拉取該套用的內容（Puppet 與 Chef 的運作方式取自兩者的文件，沒有實測）。Ansible 這種推送的做法由操作的人或排程決定什麼時候套用；代理程式拉取的做法讓主機自己定期回到宣告的狀態。&lt;/p></description><content:encoded><![CDATA[<p>組態管理（configuration management；其他中文資料也寫作設定管理）在主機維運的語境裡，指用工具把一群主機「內部」的狀態寫成宣告，再由工具把每台主機帶到那個狀態：要裝哪些套件、設定檔的內容、哪些服務要啟用、哪些使用者與權限要存在。Ansible、Puppet、Chef、Salt 是這一類工具。它跟 <a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">Infrastructure as Code (IaC)</a> 的分界在管理對象：IaC 管的是雲端業者那一端的資源（主機本身、網路、防火牆規則、DNS 紀錄），組態管理管的是主機開機之後作業系統裡面的套件、設定檔與服務；兩者常一起用，IaC 建出主機，組態管理把主機設定好。</p>
<p>同名而所指不同的兩個詞：軟體工程裡的軟體組態管理（software configuration management，SCM）指原始碼的版本控制、基線與變更控制；中國大陸工業控制業界的「組態」（组态软件）指在 SCADA 或 HMI 軟體裡設定畫面與點位。本卡講的是主機設定的管理工具。</p>
<h2 id="概念位置">概念位置</h2>
<p>組態管理工具都有兩個部分：主機清冊，與每台主機要套用的內容。Ansible 把主機清冊叫 inventory（列出受管主機與分組的檔案），跟商業領域的存貨（inventory）同字而無關；要套用的內容寫在 playbook（依序列出要對哪些主機套用哪些模組的 YAML 檔）與 role（把一類主機要用的任務、範本與變數收成一個可重用的目錄）裡，這個 playbook 跟事故處理的 <a href="/blog/backend/knowledge-cards/playbook/" data-link-title="Playbook" data-link-desc="說明場景化處置腳本如何降低事故處理不確定性">Playbook</a> 同名而所指不同。inventory 把主機分成群組（<code>web</code>、<code>db</code>），變數依層次放在 <code>group_vars/&lt;群組&gt;.yml</code> 與 <code>host_vars/&lt;主機&gt;.yml</code>，一台主機最後拿到的值是各層合併的結果，合併時越具體的層越優先：<code>host_vars</code> 蓋過群組的值，子群組蓋過 <code>all</code>。一台主機同時屬於兩個同層的群組、而兩個群組給了同一個變數不同的值時，依群組名稱的字母順序，排在後面的蓋過前面的；要指定誰優先，在 inventory 檔本身替群組設 <code>ansible_group_priority</code>（預設 1，數字大的較晚合併而勝出），寫在 <code>group_vars</code> 檔裡沒有效果；優先順序只會讓其中一組勝出，兼任兩種角色的主機需要兩組值都生效時，要讓兩個角色用不同的變數名、在範本裡取聯集。這幾條用 ansible-core 2.17 實測過，<code>ansible-inventory --host &lt;主機&gt;</code> 印得出合併後的值，實測的檔案與輸出見 <a href="/blog/devops/operations/09-host-lifecycle/multi-host-manifest/" data-link-title="多台主機的維護清單：值的分層與合併的位置、用 Ansible 的 inventory 與範本產生每台的清單、由 Terraform 的輸出產生 inventory、跨主機執行的 exit code，以及單台清單的適用範圍" data-link-desc="把單台的主機維護清單擴充到多台主機：每台相同、依角色不同、每台不同的值怎麼分層，合併放在部署時產生每台一份、由檢查腳本讀多份、依角色過濾還是執行時覆寫，用 Ansible 的 inventory、群組變數與範本產生 TOML 清單時的變數合併、兼任角色、型別、語法撞名、跳脫與產生時的驗證，由 Terraform 的輸出產生 inventory、terraform plan 能取代哪些主控台上的確認，對多台執行檢查腳本時 exit code 的讀法與執行身分的代價，以及單台清單夠用的條件">多台主機的維護清單：值的分層與合併的位置、用 Ansible 的 inventory 與範本產生每台的清單、由 Terraform 的輸出產生 inventory、跨主機執行的 exit code，以及單台清單的適用範圍</a>。inventory 與 <code>group_vars</code> 是 <a href="/blog/devops/infra/knowledge-cards/yaml/" data-link-title="YAML" data-link-desc="讀到 .yaml 或 .yml 設定檔、一個沒加引號的值被程式讀成布林值或別的數字、同一份檔案換一個解析器結果不同，或要在 JSON、YAML、TOML 之間選設定檔格式時">YAML</a>，所以 YAML 1.1 的隱式型別照樣作用：<code>country: no</code> 讀出來是布林值 <code>false</code>。</p>
<p>主機清冊可以手寫，也可以由 IaC 產生：Terraform 知道它建的每台主機的 IP 與角色，可以用 <code>terraform output -json</code> 輸出給別的工具讀，或用 <code>local_file</code> 這類資源直接寫出一份 inventory 檔。那份檔案是 Terraform 管理的一個資源，它的內容記在 Terraform 的 <a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State（IaC 狀態檔）</a> 裡，手動改了它，下一次 <code>terraform plan</code> 會要求重建、<code>apply</code> 會蓋掉手改的內容。由 <code>local_file</code> 寫出 inventory、經由 SSH 部署到兩台容器並執行檢查的實際操作，以及 <code>file_permission</code> 的預設值扣掉 umask 之後的實際權限，在 <a href="/blog/devops/operations/09-host-lifecycle/hands-on/multi-host-manifest-walkthrough/" data-link-title="多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理" data-link-desc="要照著部署多台主機的維護清單並執行檢查腳本，或要讀跨主機執行時 ansible 的 exit code 與每台的 rc、門檻造成的 WARN、清單格式錯與連不上的主機各自印出什麼時，或要確認 Terraform 寫出的 inventory 的檔案權限、`--check` 預覽與實際部署結果不同的情形時，查在可拋棄的容器上實際跑過的指令、輸出與遇到的錯誤">多台主機維護清單的實作紀錄：在容器上安裝 ansible-core 與 Terraform、由 Terraform 產生 inventory、用 Ansible 的範本部署每台的清單、以 sudo 與 systemd-run 執行檢查腳本，以及清理</a>〈由 Terraform 產生 inventory：init、plan、apply 與 local_file 的檔案權限〉。雲端資源的實際狀態跟 IaC 記錄的不一致叫 <a href="/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift（設定漂移）</a>；主機裡的套件與設定檔跟組態管理的宣告不一致，有人同樣叫它 configuration drift，比對的兩邊換成了組態管理的宣告與主機內部的狀態；組態管理工具在下一次套用時把主機帶回宣告的狀態，兩次套用之間有人登入手改的部分，在那之前一直存在。</p>
<p>Ansible 由執行指令的那台機器經由 SSH 連到每台主機，把要執行的模組送過去執行，主機上不必裝常駐的代理程式（agent）；Puppet 與 Chef 傳統上在每台主機跑一個代理程式，定期向伺服器拉取該套用的內容（Puppet 與 Chef 的運作方式取自兩者的文件，沒有實測）。Ansible 這種推送的做法由操作的人或排程決定什麼時候套用；代理程式拉取的做法讓主機自己定期回到宣告的狀態。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>repo 裡有 <code>inventory.yml</code>（或 <code>hosts.ini</code>）、<code>group_vars/</code>、<code>host_vars/</code>、<code>playbook.yml</code>、<code>roles/</code> 這類目錄。</li>
<li>維護的人改一台主機的設定時，改的是 repo 裡的變數或範本，再對那一組主機執行一次，不登入主機手改。</li>
<li>同一個設定檔在每台主機上的內容只有主機名、IP、角色相關的幾行不同。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<ul>
<li><strong>每個值放在哪一層</strong>：所有主機都相同的放 <code>all</code>，依角色不同的放群組，只有一台不同的放 <code>host_vars</code>。同一個值在兩層都寫了時，讀檔的人要知道合併規則才知道哪一個生效；對哪一個值生效有疑問時，用 <code>ansible-inventory --host</code> 查合併後的值。</li>
<li><strong>套用要能重複執行</strong>：每個步驟執行第二次時不改變結果（<a href="/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">Idempotency</a>），排程定期套用時才不會每次都重啟服務或重寫檔案。Ansible 的模組多半這樣設計，<code>command</code> 與 <code>shell</code> 模組例外：它們每次都執行、每次都回報 changed，要用 <code>creates</code>、<code>removes</code> 或 <code>changed_when</code> 寫出什麼情形下不必執行、什麼結果算有改變。</li>
<li><strong>範本的語法與產出的格式分開</strong>：Ansible 的範本是 Jinja2，<code>{{ }}</code> 是它的替換語法；產出的檔案自己也用 <code>{{ }}</code> 時（例如另一支程式的變數替換），那一段要包在 <code>{% raw %}</code> 裡，否則 Jinja2 會先替換它。值寫進 TOML、JSON 這類有引號規則的格式時，用 <code>to_json</code> 過濾器輸出（含非 ASCII 字元時加 <code>ensure_ascii=False</code>），引號與反斜線才會被正確跳脫；直接輸出時，Python 的布林值寫成 <code>False</code>，字串不帶引號，含反斜線的字串陣列被寫成兩個反斜線，TOML 解析失敗或讀到被改掉的值。</li>
<li><strong>主機清冊的唯一來源</strong>：主機由 IaC 建立時，inventory 由 IaC 的輸出產生，不另外手寫一份；兩份各自維護時，新增或刪除主機只改了其中一份，另一份就指著不存在的主機，或漏掉新的主機。</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">Infrastructure as Code (IaC)</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift（設定漂移）</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/yaml/" data-link-title="YAML" data-link-desc="讀到 .yaml 或 .yml 設定檔、一個沒加引號的值被程式讀成布林值或別的數字、同一份檔案換一個解析器結果不同，或要在 JSON、YAML、TOML 之間選設定檔格式時">YAML</a></li>
<li><a href="/blog/devops/infra/knowledge-cards/state/" data-link-title="State（IaC 狀態檔）" data-link-desc="IaC 工具用來記錄每個納管資源在雲端真實樣貌的快照，是比對差異與排定操作順序的依據">State（IaC 狀態檔）</a></li>
</ul>
]]></content:encoded></item><item><title>VNIC（虛擬網路卡）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vnic/</link><pubDate>Tue, 27 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vnic/</guid><description>&lt;p>VNIC（virtual network interface card，虛擬網路卡）是雲端主機接上虛擬網路的那一張網卡。主機在網路上的身分都掛在它上面：所在的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet（子網路）&lt;/a>、私有 IP、公開 IP（有配發時），以及網卡層的防火牆規則（Oracle Cloud 的 NSG、AWS 的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a>）。各家的名稱不同：Oracle Cloud 叫 VNIC，AWS 叫 ENI（Elastic Network Interface），Google Cloud 叫 network interface。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>主機與網路設定之間隔著 VNIC 這一層，所以「這台主機的私有 IP 是多少」「這台主機套用了哪些網卡層的防火牆規則（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> 這一類）」查的都是它的 VNIC，不是主機本身。Oracle Cloud 的 CLI 也是這樣分工：&lt;code>oci compute instance list-vnics&lt;/code> 列出主機的 VNIC 與私有 IP，掛上或拿掉 NSG 用的是 &lt;code>oci network vnic update --nsg-ids&lt;/code>，而不是主機的指令。&lt;/p>
&lt;p>建立主機時，業者同時替它建一張主要的 VNIC；主機被刪除時，這張 VNIC 也跟著刪除，上面的私有 IP 與防火牆規則的掛載關係一起消失。所以主機被刪除再重建時，新主機拿到的是一張新的 VNIC：私有 IP 多半換了一個，原本掛在舊 VNIC 上的 NSG 要重新掛上去；而 NSG 本身與它的規則屬於虛擬網路，不跟著主機消失。主機只是停止時，VNIC 與私有 IP 都留著。在 Oracle Cloud 上實際重建過一次的資源去留與重新掛 NSG 的指令，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/" data-link-title="Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建" data-link-desc="要在 Oracle Cloud 沒有公開 IP 的私有子網路裡跑一台 Ubuntu 主機、讓它裝得到套件更新，或要經由序列主控台看主機的開機過程，或主機被閒置回收、停止或刪除之後要照原樣重建，或想知道 NAT gateway 要不要另外付費、開機磁碟扣掉多少區塊磁碟額度、合計超過 200 GB 時放不進額度的那一顆怎麼算、怎麼用 quota 擋下超額的建立、線上加大開機磁碟超過額度時怎麼算、空出額度之後付費磁碟多久轉成免費時，查實際跑過的指令、錯誤原文、重建腳本與每一步的讀法">Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建&lt;/a>〈被回收之後的重建：怎麼發現、留下與消失的資源、重建腳本〉。&lt;/p>
&lt;p>一台主機可以另外掛第二張以上的 VNIC，接到另一個 subnet；那些額外的網卡依各家的規則可以單獨拿掉，主要的那一張跟主機一起建立、一起刪除。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>主機重建之後，原本連得到網際網路的套件更新又逾時、而沒有任何錯誤訊息：新的 VNIC 沒有掛上原本的出站規則&lt;/li>
&lt;li>剛建好的主機，列出 VNIC 的查詢一時回空值：主機還在建立，VNIC 的掛載紀錄還沒出現&lt;/li>
&lt;li>同一個 subnet 裡兩台主機的防火牆行為不同：subnet 層的規則相同，差在各自 VNIC 上掛的 NSG 或 security group&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>網卡層的規則只影響掛上它的那幾張 VNIC，subnet 層的規則影響整個 subnet，所以放規則的位置決定了影響範圍；而 VNIC 會隨主機重建而換新，所以：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>規則放在哪一層&lt;/strong>：只給一台主機的放行（例如只有它需要的出站）放在網卡層，掛在它的 VNIC 上；整個 subnet 都需要的放在 subnet 層。兩層怎麼合併見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則&lt;/a>〈防火牆規則的位置與合併方式〉&lt;/li>
&lt;li>&lt;strong>重建時的重新掛載&lt;/strong>：用腳本或 IaC 重建主機時，把「掛上網卡層規則」寫成重建流程的一步，或在建立主機時就指定（Oracle Cloud 的 &lt;code>instance launch --nsg-ids&lt;/code>），不靠人記得&lt;/li>
&lt;li>&lt;strong>整份替換&lt;/strong>：Oracle Cloud 的 &lt;code>vnic update --nsg-ids&lt;/code> 用給的清單取代整份 NSG 清單，加一個 NSG 時要把原本掛著的那幾個一起寫進去&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet（子網路）&lt;/a> — VNIC 接在哪一個 subnet，決定它的私有 IP 範圍與 subnet 層的規則&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — 掛在網卡層的有狀態防火牆，Oracle Cloud 的對應是 NSG&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT Gateway&lt;/a> — 沒有公開 IP 的 VNIC 往外連時經過的出口&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC（Virtual Private Cloud）&lt;/a> — VNIC 所在的虛擬網路&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>VNIC（virtual network interface card，虛擬網路卡）是雲端主機接上虛擬網路的那一張網卡。主機在網路上的身分都掛在它上面：所在的 <a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet（子網路）</a>、私有 IP、公開 IP（有配發時），以及網卡層的防火牆規則（Oracle Cloud 的 NSG、AWS 的 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a>）。各家的名稱不同：Oracle Cloud 叫 VNIC，AWS 叫 ENI（Elastic Network Interface），Google Cloud 叫 network interface。</p>
<h2 id="概念位置">概念位置</h2>
<p>主機與網路設定之間隔著 VNIC 這一層，所以「這台主機的私有 IP 是多少」「這台主機套用了哪些網卡層的防火牆規則（<a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> 這一類）」查的都是它的 VNIC，不是主機本身。Oracle Cloud 的 CLI 也是這樣分工：<code>oci compute instance list-vnics</code> 列出主機的 VNIC 與私有 IP，掛上或拿掉 NSG 用的是 <code>oci network vnic update --nsg-ids</code>，而不是主機的指令。</p>
<p>建立主機時，業者同時替它建一張主要的 VNIC；主機被刪除時，這張 VNIC 也跟著刪除，上面的私有 IP 與防火牆規則的掛載關係一起消失。所以主機被刪除再重建時，新主機拿到的是一張新的 VNIC：私有 IP 多半換了一個，原本掛在舊 VNIC 上的 NSG 要重新掛上去；而 NSG 本身與它的規則屬於虛擬網路，不跟著主機消失。主機只是停止時，VNIC 與私有 IP 都留著。在 Oracle Cloud 上實際重建過一次的資源去留與重新掛 NSG 的指令，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/" data-link-title="Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建" data-link-desc="要在 Oracle Cloud 沒有公開 IP 的私有子網路裡跑一台 Ubuntu 主機、讓它裝得到套件更新，或要經由序列主控台看主機的開機過程，或主機被閒置回收、停止或刪除之後要照原樣重建，或想知道 NAT gateway 要不要另外付費、開機磁碟扣掉多少區塊磁碟額度、合計超過 200 GB 時放不進額度的那一顆怎麼算、怎麼用 quota 擋下超額的建立、線上加大開機磁碟超過額度時怎麼算、空出額度之後付費磁碟多久轉成免費時，查實際跑過的指令、錯誤原文、重建腳本與每一步的讀法">Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建</a>〈被回收之後的重建：怎麼發現、留下與消失的資源、重建腳本〉。</p>
<p>一台主機可以另外掛第二張以上的 VNIC，接到另一個 subnet；那些額外的網卡依各家的規則可以單獨拿掉，主要的那一張跟主機一起建立、一起刪除。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>主機重建之後，原本連得到網際網路的套件更新又逾時、而沒有任何錯誤訊息：新的 VNIC 沒有掛上原本的出站規則</li>
<li>剛建好的主機，列出 VNIC 的查詢一時回空值：主機還在建立，VNIC 的掛載紀錄還沒出現</li>
<li>同一個 subnet 裡兩台主機的防火牆行為不同：subnet 層的規則相同，差在各自 VNIC 上掛的 NSG 或 security group</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>網卡層的規則只影響掛上它的那幾張 VNIC，subnet 層的規則影響整個 subnet，所以放規則的位置決定了影響範圍；而 VNIC 會隨主機重建而換新，所以：</p>
<ul>
<li><strong>規則放在哪一層</strong>：只給一台主機的放行（例如只有它需要的出站）放在網卡層，掛在它的 VNIC 上；整個 subnet 都需要的放在 subnet 層。兩層怎麼合併見 <a href="/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則</a>〈防火牆規則的位置與合併方式〉</li>
<li><strong>重建時的重新掛載</strong>：用腳本或 IaC 重建主機時，把「掛上網卡層規則」寫成重建流程的一步，或在建立主機時就指定（Oracle Cloud 的 <code>instance launch --nsg-ids</code>），不靠人記得</li>
<li><strong>整份替換</strong>：Oracle Cloud 的 <code>vnic update --nsg-ids</code> 用給的清單取代整份 NSG 清單，加一個 NSG 時要把原本掛著的那幾個一起寫進去</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet（子網路）</a> — VNIC 接在哪一個 subnet，決定它的私有 IP 範圍與 subnet 層的規則</li>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — 掛在網卡層的有狀態防火牆，Oracle Cloud 的對應是 NSG</li>
<li><a href="/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT Gateway</a> — 沒有公開 IP 的 VNIC 往外連時經過的出口</li>
<li><a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC（Virtual Private Cloud）</a> — VNIC 所在的虛擬網路</li>
</ul>
]]></content:encoded></item></channel></rss>