<?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>Cloud-Providers on Tarragon</title><link>https://tarrragon.github.io/blog/tags/cloud-providers/</link><description>Recent content in Cloud-Providers on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 27 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/cloud-providers/index.xml" rel="self" type="application/rss+xml"/><item><title>雲端業者的帳號申請與主機維運</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/</guid><description>&lt;p>這個模組收各家雲端業者自己的那一層：帳號與區域在申請當下就固定的設定、主控台與 CLI 的登入方式、免費額度的計算規則，以及在那一家實際建主機、走管理通道的紀錄。成熟度階梯（模組零到九）講的是 vendor 中立的地基，這裡放的是同一件事落到某一家時才會遇到的設定與陷阱。&lt;/p>
&lt;p>模組是橫切的：任何一階實作時都可能需要先在某一家開帳號。內容的歸屬規則是：成熟度階梯的模組放 vendor 中立的概念，正文的範例以 AWS 為參考實作，但不收某一家的完整實作篇；這個模組的根目錄放跨業者、在主控台與帳號層操作的內容（建立主機的選項、主控台的管理領域、免費額度比較）；某一家的實作與實測紀錄放在業者子資料夾，例如管理通道在 GCP 上的 IAP 實作、帳單停損在 GCP 上的建置。概念文章連到業者子資料夾，業者子資料夾的每一篇連回它實作的那個概念。&lt;/p>
&lt;h2 id="章節文章">章節文章&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;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;/td>
 &lt;td>計費單位、免費額度的區域限制、試用結束後的處置、出站流量&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/console-areas/" data-link-title="雲端主控台的管理領域：身分、資源分組、運算、網路、儲存、費用、監控、稽核、安全服務與配額" data-link-desc="雲端業者的主控台依管理需求分成幾個領域：每個領域管什麼、日常在那裡做哪些事、新帳號開好之後先去哪幾個領域看設定，以及 Oracle Cloud、Google Cloud、AWS 各自把它放在選單的哪個名稱底下">雲端主控台的管理領域&lt;/a>&lt;/td>
 &lt;td>身分、資源分組、運算、網路、儲存、費用、監控、稽核、安全服務與配額各管什麼，新帳號先看哪幾個，三家的選單名稱&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="子資料夾">子資料夾&lt;/h2>
&lt;p>依 weight 排列（list.html 先列子資料夾、再列文章，所以兩篇根目錄文章在列表頁排在子資料夾之後）：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/" data-link-title="建立雲端虛擬機的選項" data-link-desc="站在雲端主控台的建立虛擬機頁面、不確定某一個欄位會影響什麼時，依功能分類查位置、機型與映像檔、網路、登入、磁碟、代理程式與安全開機各自的選項、建立後能不能改與對費用的影響">建立雲端虛擬機的選項&lt;/a>：vendor 中立，依功能分類一類一篇（位置、機型與映像檔、網路、登入、磁碟、代理程式與維護、安全開機）&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/" data-link-title="Oracle Cloud（OCI）" data-link-desc="準備申請或已經申請了 Oracle Cloud 免費帳號（home region 要在送出申請之前選）、要在 Always Free 額度內建主機並從本機管理時，查帳號結構、CLI 登入與實測紀錄">Oracle Cloud&lt;/a>：主控台導覽、帳號結構與 home region、CLI 登入、建立主機的實測紀錄&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/" data-link-title="Google Cloud（GCP）" data-link-desc="在 GCP 專案上要把 VM 的 SSH 改走 IAP、管理 MIG 與 GKE 節點的登入，或建預算警示與自動停用帳單的停損開關時，查機制與判斷依據，以及子資料夾裡逐步的實測紀錄">Google Cloud&lt;/a>：IAP 管理通道、MIG 與 GKE 的管理存取、帳單停損開關，三個主題各有一篇概念文章與一篇實測紀錄&lt;/li>
&lt;/ul>
&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">rg -n &lt;span class="s2">&amp;#34;[一二三四五六七八九]家|其他兩家|另兩家|Google Cloud 與 AWS|AWS 與 Google Cloud|Oracle Cloud .*Google Cloud .*AWS&amp;#34;&lt;/span> &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> content/devops/infra/cloud-providers/ content/devops/infra/knowledge-cards/availability-zone.md &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> content/devops/infra/knowledge-cards/fault-domain.md content/devops/infra/knowledge-cards/instance-metadata-service.md&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/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="要規劃 VPC 與 subnet 分層、設計 security group 與 NAT、收斂對外開放的 SSH 與 RDP 管理 port、或決定管理員登入伺服器的通道時，從這裡找對應的文章">模組三：網路地基&lt;/a>：管理通道的三層模型，各家實作的實測對照它&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/devops/operations/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing 與成本監控告警；預算門檻、配額與停損層級的推算在本模組，各家的建置篇目前只有 GCP，放在雲端業者模組">模組八：成本管理&lt;/a>：成本監控與計費模型；不分業者的預算門檻、信用卡額度、配額當費用上限與停損層級的推算在 &lt;a href="https://tarrragon.github.io/blog/devops/operations/08-cost-management/spend-planning-before-renting/" data-link-title="租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層" data-link-desc="租用雲端主機或把試用帳號升級成付費帳戶之前，費用失控時從觸發到停止之間的金額怎麼從配額與訊號延遲推出來，再用它推扣款的信用卡額度、配額、預算警示與觸發處置的門檻；Oracle Cloud、Google Cloud 與 AWS 的扣款時點、預先授權、扣款失敗的後果、擋下建立與停止用量的機制各自涵蓋什麼，以及停損停到哪一層的判斷">租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層&lt;/a>；要不要建停損，依據是各家試用結束後會不會自動開始計費&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/devops/infra/takeover/" data-link-title="接手維運：別人建的環境怎麼接管" data-link-desc="接手前人的專案時，怎麼在不搞壞東西的前提下盤點現況、建立維運能力、逐步正規化 — 從無 SSH 的 FTP 環境到有半套 IaC 的雲端環境都適用">接手維運&lt;/a>：用主控台建好的資源要收進 IaC 時的匯入流程&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/devops/going-live/hosting-spectrum/" data-link-title="主機形態光譜" data-link-desc="第一次要選服務跑在哪、被 VPS / IaaS / PaaS / serverless 這些詞搞混、不知道差在哪時回來讀 — 主機形態是一道「你管多少」的光譜">主機形態光譜&lt;/a>：選 IaaS 之前先決定要管多少&lt;/li>
&lt;/ul>
&lt;h2 id="backlog">Backlog&lt;/h2>
&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>管理通道模型在 AWS 上的實作篇（SSM Session Manager、EC2 Instance Connect），產出放 &lt;code>cloud-providers/aws/&lt;/code>&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>有可建 EC2 與 IAM role 的 AWS 測試帳號，篇內指令實跑過才寫&lt;/td>
 &lt;td>1 篇（中）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AWS Systems Manager Session Manager 知識卡（代理程式反向連線的代表實作）&lt;/td>
 &lt;td>知識卡&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>1 張卡（小）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Home Region / OCPU 知識卡（OCPU 卡一併涵蓋 vCPU 與超執行緒的換算）&lt;/td>
 &lt;td>知識卡&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>2 張卡（小）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Tenancy、Compartment 與 OCID 知識卡（行內定義已在五篇以上重複）&lt;/td>
 &lt;td>知識卡&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>1–2 張卡（小）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>業者代理程式（guest agent）、cloud-init、Service Quota、Spot／Preemptible、業者私有連線（service gateway、VPC endpoint、Private Google Access）知識卡&lt;/td>
 &lt;td>知識卡&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>5 張卡（中）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>自架 VPN 的用途對照：出口 IP 的國家、閒置回收、流量額度、VPN port 在雲端與作業系統兩道防火牆都要放行，串起散在 pricing、account、networking 的判斷&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>1 篇（小）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>收費比較補上 AWS、GCP 以外業者（例如 Linode、Vultr）&lt;/td>
 &lt;td>vendor&lt;/td>
 &lt;td>實際申請過或查到官方規則&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Egress（出站流量）與 MFA（多因素驗證）知識卡：本模組的 pricing、account、console-areas 與 operations 的 billing-models 都用到，全站沒有卡；卡放 &lt;code>infra/knowledge-cards/&lt;/code> 或 &lt;code>backend/knowledge-cards/&lt;/code>，建好之後回填各篇第一次出現的位置&lt;/td>
 &lt;td>知識卡&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>2 張卡（小）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Terraform Provider 知識卡（Terraform 操作某一類資源的外掛、&lt;code>required_providers&lt;/code> 與 &lt;code>terraform init&lt;/code> 下載的關係）：全站約 25 篇把 Terraform 與 provider 寫在一起，沒有卡；卡放 &lt;code>infra/knowledge-cards/&lt;/code>，建好之後回填各篇第一次出現的位置（含 operations 模組九的多台主機實作紀錄）&lt;/td>
 &lt;td>知識卡&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>1 張卡（小）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>模組標題「雲端業者的帳號申請與主機維運」與內容對齊：模組實際收的是建立主機的欄位、主控台的管理領域、免費額度比較，以及各家的帳號結構、CLI 登入與建置實測，標題的「主機維運」讀起來涵蓋開機之後的日常維運；改標題時同一次改 &lt;code>infra/_index.md&lt;/code>、&lt;code>infra/03-network-foundation/_index.md&lt;/code> 與 &lt;code>operations/08-cost-management/_index.md&lt;/code> 的連結文字&lt;/td>
 &lt;td>跨模組&lt;/td>
 &lt;td>先定新標題&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>vm-options/shape-and-image〈停止主機時的計費〉：在作業系統裡下 &lt;code>shutdown&lt;/code> 之後 Oracle Cloud 主控台顯示的主機狀態，以及計費依據是業者那一端記錄的主機狀態這條機制，補成「習慣在主機裡關機 → 帳單照樣計費 → 改從主控台停止」的情境&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>讀到 Oracle Cloud 官方原文寫出 OS 內關機後的主機狀態與計費依據；讀不到就只補機制以外的部分&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>vm-options/placement〈故障域（fault domain）：同一棟裡的哪一組硬體〉與 Fault Domain 卡：兩台主機都用預設「讓業者選」時，Oracle Cloud 是否自動把它們放進不同的故障域；不保證的話寫明兩台要手動指定不同的故障域&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>讀到 Oracle Cloud 官方原文&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>這個模組收各家雲端業者自己的那一層：帳號與區域在申請當下就固定的設定、主控台與 CLI 的登入方式、免費額度的計算規則，以及在那一家實際建主機、走管理通道的紀錄。成熟度階梯（模組零到九）講的是 vendor 中立的地基，這裡放的是同一件事落到某一家時才會遇到的設定與陷阱。</p>
<p>模組是橫切的：任何一階實作時都可能需要先在某一家開帳號。內容的歸屬規則是：成熟度階梯的模組放 vendor 中立的概念，正文的範例以 AWS 為參考實作，但不收某一家的完整實作篇；這個模組的根目錄放跨業者、在主控台與帳號層操作的內容（建立主機的選項、主控台的管理領域、免費額度比較）；某一家的實作與實測紀錄放在業者子資料夾，例如管理通道在 GCP 上的 IAP 實作、帳單停損在 GCP 上的建置。概念文章連到業者子資料夾，業者子資料夾的每一篇連回它實作的那個概念。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較</a></td>
          <td>計費單位、免費額度的區域限制、試用結束後的處置、出站流量</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/console-areas/" data-link-title="雲端主控台的管理領域：身分、資源分組、運算、網路、儲存、費用、監控、稽核、安全服務與配額" data-link-desc="雲端業者的主控台依管理需求分成幾個領域：每個領域管什麼、日常在那裡做哪些事、新帳號開好之後先去哪幾個領域看設定，以及 Oracle Cloud、Google Cloud、AWS 各自把它放在選單的哪個名稱底下">雲端主控台的管理領域</a></td>
          <td>身分、資源分組、運算、網路、儲存、費用、監控、稽核、安全服務與配額各管什麼，新帳號先看哪幾個，三家的選單名稱</td>
      </tr>
  </tbody>
</table>
<h2 id="子資料夾">子資料夾</h2>
<p>依 weight 排列（list.html 先列子資料夾、再列文章，所以兩篇根目錄文章在列表頁排在子資料夾之後）：</p>
<ul>
<li><a href="/blog/devops/infra/cloud-providers/vm-options/" data-link-title="建立雲端虛擬機的選項" data-link-desc="站在雲端主控台的建立虛擬機頁面、不確定某一個欄位會影響什麼時，依功能分類查位置、機型與映像檔、網路、登入、磁碟、代理程式與安全開機各自的選項、建立後能不能改與對費用的影響">建立雲端虛擬機的選項</a>：vendor 中立，依功能分類一類一篇（位置、機型與映像檔、網路、登入、磁碟、代理程式與維護、安全開機）</li>
<li><a href="/blog/devops/infra/cloud-providers/oracle-cloud/" data-link-title="Oracle Cloud（OCI）" data-link-desc="準備申請或已經申請了 Oracle Cloud 免費帳號（home region 要在送出申請之前選）、要在 Always Free 額度內建主機並從本機管理時，查帳號結構、CLI 登入與實測紀錄">Oracle Cloud</a>：主控台導覽、帳號結構與 home region、CLI 登入、建立主機的實測紀錄</li>
<li><a href="/blog/devops/infra/cloud-providers/gcp/" data-link-title="Google Cloud（GCP）" data-link-desc="在 GCP 專案上要把 VM 的 SSH 改走 IAP、管理 MIG 與 GKE 節點的登入，或建預算警示與自動停用帳單的停損開關時，查機制與判斷依據，以及子資料夾裡逐步的實測紀錄">Google Cloud</a>：IAP 管理通道、MIG 與 GKE 的管理存取、帳單停損開關，三個主題各有一篇概念文章與一篇實測紀錄</li>
</ul>
<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">rg -n <span class="s2">&#34;[一二三四五六七八九]家|其他兩家|另兩家|Google Cloud 與 AWS|AWS 與 Google Cloud|Oracle Cloud .*Google Cloud .*AWS&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  content/devops/infra/cloud-providers/ content/devops/infra/knowledge-cards/availability-zone.md <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  content/devops/infra/knowledge-cards/fault-domain.md content/devops/infra/knowledge-cards/instance-metadata-service.md</span></span></code></pre></div><h2 id="跟其他模組的關係">跟其他模組的關係</h2>
<ul>
<li>→ <a href="/blog/devops/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="要規劃 VPC 與 subnet 分層、設計 security group 與 NAT、收斂對外開放的 SSH 與 RDP 管理 port、或決定管理員登入伺服器的通道時，從這裡找對應的文章">模組三：網路地基</a>：管理通道的三層模型，各家實作的實測對照它</li>
<li>→ <a href="/blog/devops/operations/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing 與成本監控告警；預算門檻、配額與停損層級的推算在本模組，各家的建置篇目前只有 GCP，放在雲端業者模組">模組八：成本管理</a>：成本監控與計費模型；不分業者的預算門檻、信用卡額度、配額當費用上限與停損層級的推算在 <a href="/blog/devops/operations/08-cost-management/spend-planning-before-renting/" data-link-title="租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層" data-link-desc="租用雲端主機或把試用帳號升級成付費帳戶之前，費用失控時從觸發到停止之間的金額怎麼從配額與訊號延遲推出來，再用它推扣款的信用卡額度、配額、預算警示與觸發處置的門檻；Oracle Cloud、Google Cloud 與 AWS 的扣款時點、預先授權、扣款失敗的後果、擋下建立與停止用量的機制各自涵蓋什麼，以及停損停到哪一層的判斷">租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層</a>；要不要建停損，依據是各家試用結束後會不會自動開始計費</li>
<li>→ <a href="/blog/devops/infra/takeover/" data-link-title="接手維運：別人建的環境怎麼接管" data-link-desc="接手前人的專案時，怎麼在不搞壞東西的前提下盤點現況、建立維運能力、逐步正規化 — 從無 SSH 的 FTP 環境到有半套 IaC 的雲端環境都適用">接手維運</a>：用主控台建好的資源要收進 IaC 時的匯入流程</li>
<li>→ <a href="/blog/devops/going-live/hosting-spectrum/" data-link-title="主機形態光譜" data-link-desc="第一次要選服務跑在哪、被 VPS / IaaS / PaaS / serverless 這些詞搞混、不知道差在哪時回來讀 — 主機形態是一道「你管多少」的光譜">主機形態光譜</a>：選 IaaS 之前先決定要管多少</li>
</ul>
<h2 id="backlog">Backlog</h2>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>類型</th>
          <th>前置條件</th>
          <th>規模</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>管理通道模型在 AWS 上的實作篇（SSM Session Manager、EC2 Instance Connect），產出放 <code>cloud-providers/aws/</code></td>
          <td>主章</td>
          <td>有可建 EC2 與 IAM role 的 AWS 測試帳號，篇內指令實跑過才寫</td>
          <td>1 篇（中）</td>
      </tr>
      <tr>
          <td>AWS Systems Manager Session Manager 知識卡（代理程式反向連線的代表實作）</td>
          <td>知識卡</td>
          <td>無</td>
          <td>1 張卡（小）</td>
      </tr>
      <tr>
          <td>Home Region / OCPU 知識卡（OCPU 卡一併涵蓋 vCPU 與超執行緒的換算）</td>
          <td>知識卡</td>
          <td>無</td>
          <td>2 張卡（小）</td>
      </tr>
      <tr>
          <td>Tenancy、Compartment 與 OCID 知識卡（行內定義已在五篇以上重複）</td>
          <td>知識卡</td>
          <td>無</td>
          <td>1–2 張卡（小）</td>
      </tr>
      <tr>
          <td>業者代理程式（guest agent）、cloud-init、Service Quota、Spot／Preemptible、業者私有連線（service gateway、VPC endpoint、Private Google Access）知識卡</td>
          <td>知識卡</td>
          <td>無</td>
          <td>5 張卡（中）</td>
      </tr>
      <tr>
          <td>自架 VPN 的用途對照：出口 IP 的國家、閒置回收、流量額度、VPN port 在雲端與作業系統兩道防火牆都要放行，串起散在 pricing、account、networking 的判斷</td>
          <td>主章</td>
          <td>無</td>
          <td>1 篇（小）</td>
      </tr>
      <tr>
          <td>收費比較補上 AWS、GCP 以外業者（例如 Linode、Vultr）</td>
          <td>vendor</td>
          <td>實際申請過或查到官方規則</td>
          <td>小</td>
      </tr>
      <tr>
          <td>Egress（出站流量）與 MFA（多因素驗證）知識卡：本模組的 pricing、account、console-areas 與 operations 的 billing-models 都用到，全站沒有卡；卡放 <code>infra/knowledge-cards/</code> 或 <code>backend/knowledge-cards/</code>，建好之後回填各篇第一次出現的位置</td>
          <td>知識卡</td>
          <td>無</td>
          <td>2 張卡（小）</td>
      </tr>
      <tr>
          <td>Terraform Provider 知識卡（Terraform 操作某一類資源的外掛、<code>required_providers</code> 與 <code>terraform init</code> 下載的關係）：全站約 25 篇把 Terraform 與 provider 寫在一起，沒有卡；卡放 <code>infra/knowledge-cards/</code>，建好之後回填各篇第一次出現的位置（含 operations 模組九的多台主機實作紀錄）</td>
          <td>知識卡</td>
          <td>無</td>
          <td>1 張卡（小）</td>
      </tr>
      <tr>
          <td>模組標題「雲端業者的帳號申請與主機維運」與內容對齊：模組實際收的是建立主機的欄位、主控台的管理領域、免費額度比較，以及各家的帳號結構、CLI 登入與建置實測，標題的「主機維運」讀起來涵蓋開機之後的日常維運；改標題時同一次改 <code>infra/_index.md</code>、<code>infra/03-network-foundation/_index.md</code> 與 <code>operations/08-cost-management/_index.md</code> 的連結文字</td>
          <td>跨模組</td>
          <td>先定新標題</td>
          <td>小</td>
      </tr>
      <tr>
          <td>vm-options/shape-and-image〈停止主機時的計費〉：在作業系統裡下 <code>shutdown</code> 之後 Oracle Cloud 主控台顯示的主機狀態，以及計費依據是業者那一端記錄的主機狀態這條機制，補成「習慣在主機裡關機 → 帳單照樣計費 → 改從主控台停止」的情境</td>
          <td>主章</td>
          <td>讀到 Oracle Cloud 官方原文寫出 OS 內關機後的主機狀態與計費依據；讀不到就只補機制以外的部分</td>
          <td>小</td>
      </tr>
      <tr>
          <td>vm-options/placement〈故障域（fault domain）：同一棟裡的哪一組硬體〉與 Fault Domain 卡：兩台主機都用預設「讓業者選」時，Oracle Cloud 是否自動把它們放進不同的故障域；不保證的話寫明兩台要手動指定不同的故障域</td>
          <td>主章</td>
          <td>讀到 Oracle Cloud 官方原文</td>
          <td>小</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>Oracle Cloud 主控台導覽：頂端列、導覽選單的分類、清單頁的 compartment 篩選器與常用頁面的路徑</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/console-navigation/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/console-navigation/</guid><description>&lt;p>這篇是 Oracle Cloud Infrastructure 主控台的導覽：哪個功能在選單的哪裡。選單內容取自 2026-10-24 一個 Free Tier 帳號的主控台英文介面，介面語言可以從人像選單切換，切成中文時選單名稱會跟著翻譯。每個管理領域在做什麼、各家的對照見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/console-areas/" data-link-title="雲端主控台的管理領域：身分、資源分組、運算、網路、儲存、費用、監控、稽核、安全服務與配額" data-link-desc="雲端業者的主控台依管理需求分成幾個領域：每個領域管什麼、日常在那裡做哪些事、新帳號開好之後先去哪幾個領域看設定，以及 Oracle Cloud、Google Cloud、AWS 各自把它放在選單的哪個名稱底下">雲端主控台的管理領域&lt;/a>。&lt;/p>
&lt;h2 id="頂端列">頂端列&lt;/h2>
&lt;p>登入後畫面最上面那一列，由左到右是：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>☰（導覽選單）&lt;/strong>：所有服務的入口，內容見〈導覽選單的分類〉。選單上方有一個搜尋框，打服務名稱就列出它所在的分類，例如打 &lt;code>Bastion&lt;/code> 會列出「Bastion · Identity &amp;amp; Security」。&lt;/li>
&lt;li>&lt;strong>搜尋（放大鏡）&lt;/strong>：搜尋資源與服務，也能用資源名稱找到某一台主機或某一個 VCN。&lt;/li>
&lt;li>&lt;strong>區域選擇器&lt;/strong>：目前選定的區域，例如「Indonesia North (Batam)」。清單頁只顯示這個區域的資源。&lt;/li>
&lt;li>&lt;strong>Developer tools&lt;/strong>：兩個項目，Cloud Shell（瀏覽器裡的終端機，已經裝好 OCI CLI 並以目前登入的身分驗證，不必在本機安裝 CLI 或設定金鑰）與 Code Editor。&lt;/li>
&lt;li>&lt;strong>通知（鈴鐺）&lt;/strong>：Oracle 的公告，包括排定的硬體維護。&lt;/li>
&lt;li>&lt;strong>說明（問號）&lt;/strong>：文件與支援的入口。&lt;/li>
&lt;li>&lt;strong>人像選單&lt;/strong>：從上到下是目前的使用者名稱、identity domain（tenancy 裡存放使用者與群組的分區，新帳號只有 &lt;code>Default&lt;/code> 一個）、tenancy 名稱（tenancy 是 Oracle Cloud 的帳號，一個客戶一個）、介面語言、User settings（個人的密碼、多因素驗證、API 簽章金鑰與其他憑證）、Console settings、Sign out。點 tenancy 名稱進入 Tenancy Details 頁，那裡看得到 home region 與 tenancy 的 OCID（Oracle Cloud ID：Oracle Cloud 給每個資源的唯一識別碼，以 &lt;code>ocid1.&lt;/code> 開頭，CLI 與 API 用它指定要操作的對象）。&lt;/li>
&lt;/ul>
&lt;p>Free Tier 帳號在試用期間，頂端列上方另有一條「Free Tier account」的橫幅，說明試用結束後只剩 Always Free 資源，並附 Upgrade 連結。&lt;/p>
&lt;h2 id="導覽選單的分類">導覽選單的分類&lt;/h2>
&lt;p>選單左欄是分類，點一個分類，右側展開它底下的服務與頁面。實測的分類依序是：Home、Compute、Storage、Networking、Oracle AI Database、Databases、Analytics &amp;amp; AI、Developer Services、Identity &amp;amp; Security、Observability &amp;amp; Management、Hybrid &amp;amp; Multicloud、Migration &amp;amp; Recovery、Billing &amp;amp; Cost Management、Governance &amp;amp; Administration、Marketplace。管理一台主機會用到的是下面這幾類：&lt;/p>
&lt;p>&lt;strong>Compute&lt;/strong>：Overview、Instances、Instance Maintenance、Dedicated Virtual Machine Hosts、Instance Configurations、Instance Pools、Cluster Networks、Autoscaling Configurations、Capacity Reservations、Custom Images。建立與管理主機都從 Instances 進去。&lt;/p>
&lt;p>&lt;strong>Storage&lt;/strong>：Block Storage（Block Volumes、Block Volume Backups、Block Volume Replicas、Volume Groups 與它們的備份與複本、Backup Policies）、File Storage（File Systems、Mount Targets）、Object Storage &amp;amp; Archive Storage（Buckets、Private Endpoints）。開機磁碟不列在導覽選單上：進入任一個 Block Storage 頁面（例如 Block Volumes）之後，頁面頂端的「Storage」那一列可以展開，子選單裡有 Boot Volumes、Boot Volume Backups 等項目，刪除主機後留下的開機磁碟從這裡找。進入服務頁面之後，頁面頂端都有這樣一列可以展開的服務子選單，在同一個服務的各頁面之間切換用它比回導覽選單快。&lt;/p>
&lt;p>&lt;strong>Networking&lt;/strong>：Overview、Virtual cloud networks、Load balancers（Load balancer、Network load balancer）、DNS management（Public zones、Private zones 等）、IP management（Reserved public IPs、BYOIP、Public IP pools 等）、Network Command Center（Network visualizer、Network Path Analyzer 等）。VCN、subnet、security list、NSG、閘道都從 Virtual cloud networks 進入某一個 VCN 之後設定。&lt;/p>
&lt;p>&lt;strong>Identity &amp;amp; Security&lt;/strong>：Identity（Overview、Domains、Network Sources、Policies、Compartments）、Cloud Guard、Security Zones、Security Advisor、Certificates、Scanning（Vulnerability Reports 等）、Key Management（Vault 等）、Secret Management、Zero Trust Packet Routing，以及 Bastion。使用者與群組在 Domains 裡的 &lt;code>Default&lt;/code> domain 底下管理。&lt;/p>
&lt;p>&lt;strong>Observability &amp;amp; Management&lt;/strong>：Application Performance Monitoring、Logging（Logs、Audit 等）、Monitoring（Metrics Explorer、Alarm Definitions 等）、Log Analytics、Events Service、Management Agent 等。主機的 CPU 與記憶體圖表在主機頁面上就看得到；跨主機的指標與告警從 Monitoring 進去。&lt;/p></description><content:encoded><![CDATA[<p>這篇是 Oracle Cloud Infrastructure 主控台的導覽：哪個功能在選單的哪裡。選單內容取自 2026-10-24 一個 Free Tier 帳號的主控台英文介面，介面語言可以從人像選單切換，切成中文時選單名稱會跟著翻譯。每個管理領域在做什麼、各家的對照見 <a href="/blog/devops/infra/cloud-providers/console-areas/" data-link-title="雲端主控台的管理領域：身分、資源分組、運算、網路、儲存、費用、監控、稽核、安全服務與配額" data-link-desc="雲端業者的主控台依管理需求分成幾個領域：每個領域管什麼、日常在那裡做哪些事、新帳號開好之後先去哪幾個領域看設定，以及 Oracle Cloud、Google Cloud、AWS 各自把它放在選單的哪個名稱底下">雲端主控台的管理領域</a>。</p>
<h2 id="頂端列">頂端列</h2>
<p>登入後畫面最上面那一列，由左到右是：</p>
<ul>
<li><strong>☰（導覽選單）</strong>：所有服務的入口，內容見〈導覽選單的分類〉。選單上方有一個搜尋框，打服務名稱就列出它所在的分類，例如打 <code>Bastion</code> 會列出「Bastion · Identity &amp; Security」。</li>
<li><strong>搜尋（放大鏡）</strong>：搜尋資源與服務，也能用資源名稱找到某一台主機或某一個 VCN。</li>
<li><strong>區域選擇器</strong>：目前選定的區域，例如「Indonesia North (Batam)」。清單頁只顯示這個區域的資源。</li>
<li><strong>Developer tools</strong>：兩個項目，Cloud Shell（瀏覽器裡的終端機，已經裝好 OCI CLI 並以目前登入的身分驗證，不必在本機安裝 CLI 或設定金鑰）與 Code Editor。</li>
<li><strong>通知（鈴鐺）</strong>：Oracle 的公告，包括排定的硬體維護。</li>
<li><strong>說明（問號）</strong>：文件與支援的入口。</li>
<li><strong>人像選單</strong>：從上到下是目前的使用者名稱、identity domain（tenancy 裡存放使用者與群組的分區，新帳號只有 <code>Default</code> 一個）、tenancy 名稱（tenancy 是 Oracle Cloud 的帳號，一個客戶一個）、介面語言、User settings（個人的密碼、多因素驗證、API 簽章金鑰與其他憑證）、Console settings、Sign out。點 tenancy 名稱進入 Tenancy Details 頁，那裡看得到 home region 與 tenancy 的 OCID（Oracle Cloud ID：Oracle Cloud 給每個資源的唯一識別碼，以 <code>ocid1.</code> 開頭，CLI 與 API 用它指定要操作的對象）。</li>
</ul>
<p>Free Tier 帳號在試用期間，頂端列上方另有一條「Free Tier account」的橫幅，說明試用結束後只剩 Always Free 資源，並附 Upgrade 連結。</p>
<h2 id="導覽選單的分類">導覽選單的分類</h2>
<p>選單左欄是分類，點一個分類，右側展開它底下的服務與頁面。實測的分類依序是：Home、Compute、Storage、Networking、Oracle AI Database、Databases、Analytics &amp; AI、Developer Services、Identity &amp; Security、Observability &amp; Management、Hybrid &amp; Multicloud、Migration &amp; Recovery、Billing &amp; Cost Management、Governance &amp; Administration、Marketplace。管理一台主機會用到的是下面這幾類：</p>
<p><strong>Compute</strong>：Overview、Instances、Instance Maintenance、Dedicated Virtual Machine Hosts、Instance Configurations、Instance Pools、Cluster Networks、Autoscaling Configurations、Capacity Reservations、Custom Images。建立與管理主機都從 Instances 進去。</p>
<p><strong>Storage</strong>：Block Storage（Block Volumes、Block Volume Backups、Block Volume Replicas、Volume Groups 與它們的備份與複本、Backup Policies）、File Storage（File Systems、Mount Targets）、Object Storage &amp; Archive Storage（Buckets、Private Endpoints）。開機磁碟不列在導覽選單上：進入任一個 Block Storage 頁面（例如 Block Volumes）之後，頁面頂端的「Storage」那一列可以展開，子選單裡有 Boot Volumes、Boot Volume Backups 等項目，刪除主機後留下的開機磁碟從這裡找。進入服務頁面之後，頁面頂端都有這樣一列可以展開的服務子選單，在同一個服務的各頁面之間切換用它比回導覽選單快。</p>
<p><strong>Networking</strong>：Overview、Virtual cloud networks、Load balancers（Load balancer、Network load balancer）、DNS management（Public zones、Private zones 等）、IP management（Reserved public IPs、BYOIP、Public IP pools 等）、Network Command Center（Network visualizer、Network Path Analyzer 等）。VCN、subnet、security list、NSG、閘道都從 Virtual cloud networks 進入某一個 VCN 之後設定。</p>
<p><strong>Identity &amp; Security</strong>：Identity（Overview、Domains、Network Sources、Policies、Compartments）、Cloud Guard、Security Zones、Security Advisor、Certificates、Scanning（Vulnerability Reports 等）、Key Management（Vault 等）、Secret Management、Zero Trust Packet Routing，以及 Bastion。使用者與群組在 Domains 裡的 <code>Default</code> domain 底下管理。</p>
<p><strong>Observability &amp; Management</strong>：Application Performance Monitoring、Logging（Logs、Audit 等）、Monitoring（Metrics Explorer、Alarm Definitions 等）、Log Analytics、Events Service、Management Agent 等。主機的 CPU 與記憶體圖表在主機頁面上就看得到；跨主機的指標與告警從 Monitoring 進去。</p>
<p><strong>Billing &amp; Cost Management</strong>：Billing（Subscriptions、Invoices、Usage Statements、Payment History、Upgrade and Manage Payment）、Cost Management（Overview、Cost Analysis、Cost and Usage Reports、Budgets、Scheduled Reports、Cost Anomaly Detection 等）。</p>
<p><strong>Governance &amp; Administration</strong>：Account Management（Tenancy Details、Announcements、Region Management）、Cloud Advisor、Resource Scheduler、Tenancy Management（Resource Explorer、Tenancy Explorer、Quota policies、Limits, quotas and usage、Tag Namespaces）、Organization Management。</p>
<p>右側每個項目滑過時會出現「Press P to pin link」，釘選的項目出現在 Home 分類與首頁的 Pinned 區，常用的頁面釘起來就不必每次翻選單。</p>
<h2 id="清單頁的-compartment-篩選器">清單頁的 compartment 篩選器</h2>
<p>Compartment 是 tenancy 底下用來分組資源與套用權限政策的容器，tenancy 本身是最上層的 root compartment，每個資源都建在某一個 compartment 裡。Instances、Virtual cloud networks、Block Volumes 這類清單頁的表格上方有一行「Applied filters」，其中的 Compartment 那一項篩選條件就是 compartment 篩選器，清單只列出選定 compartment 裡的資源。實測新帳號的預設是 root compartment，這一項篩選條件顯示為「&lt;tenancy 名稱&gt; (root)」。資源建在另一個 compartment 時，清單看起來是空的，要把篩選器切過去。建立頁上的下拉選單也一樣：VCN、subnet 的選單只列出「所在 compartment」那一欄選定的 compartment 裡的資源。</p>
<h2 id="常用頁面的路徑">常用頁面的路徑</h2>
<table>
  <thead>
      <tr>
          <th>要做的事</th>
          <th>選單路徑</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>建立、停止、刪除主機</td>
          <td>Compute → Instances</td>
      </tr>
      <tr>
          <td>建立 VCN、subnet，改防火牆規則</td>
          <td>Networking → Virtual cloud networks → 點進某個 VCN</td>
      </tr>
      <tr>
          <td>保留一個固定的公開 IP</td>
          <td>Networking → IP management → Reserved public IPs</td>
      </tr>
      <tr>
          <td>找刪除主機後留下的開機磁碟</td>
          <td>Storage → Block Storage → Block Volumes，再從頁面頂端的 Storage 子選單切到 Boot Volumes</td>
      </tr>
      <tr>
          <td>建立 Bastion、在它底下建立登入主機用的 SSH 工作階段（session）</td>
          <td>Identity &amp; Security → Bastion</td>
      </tr>
      <tr>
          <td>建立實驗用的 compartment</td>
          <td>Identity &amp; Security → Identity → Compartments</td>
      </tr>
      <tr>
          <td>加使用者、群組</td>
          <td>Identity &amp; Security → Identity → Domains → <code>Default</code></td>
      </tr>
      <tr>
          <td>寫權限政策</td>
          <td>Identity &amp; Security → Identity → Policies</td>
      </tr>
      <tr>
          <td>開多因素驗證、建 API 簽章金鑰</td>
          <td>人像選單 → User settings；API 簽章金鑰在 Token and keys 分頁，或頁面左下角 Resources 區段的 API Keys（官方文件兩種寫法都有）</td>
      </tr>
      <tr>
          <td>設預算與警示</td>
          <td>Billing &amp; Cost Management → Cost Management → Budgets</td>
      </tr>
      <tr>
          <td>看這個月花了多少</td>
          <td>Billing &amp; Cost Management → Cost Management → Cost Analysis</td>
      </tr>
      <tr>
          <td>查誰做了什麼操作</td>
          <td>Observability &amp; Management → Logging → Audit</td>
      </tr>
      <tr>
          <td>看主機指標、設告警</td>
          <td>Observability &amp; Management → Monitoring</td>
      </tr>
      <tr>
          <td>查某種資源的上限與用量</td>
          <td>Governance &amp; Administration → Tenancy Management → Limits, quotas and usage</td>
      </tr>
      <tr>
          <td>看 home region 與 tenancy OCID</td>
          <td>Governance &amp; Administration → Account Management → Tenancy Details</td>
      </tr>
      <tr>
          <td>訂閱其他區域（付費帳戶限定）</td>
          <td>Governance &amp; Administration → Account Management → Region Management</td>
      </tr>
  </tbody>
</table>
<p>試用與 Free Tier 帳號只能有一個訂閱區域（就是 home region），升級成 Pay As You Go 之後還要先在 Limits, quotas and usage 申請提高 Subscribed region count，才能在 Region Management 訂閱第二個區域。</p>
<p>選單路徑對不上時（改版、介面語言不同），在導覽選單的搜尋框打表格裡的頁面名稱。</p>
<h2 id="延伸閱讀各頁面背後的概念">延伸閱讀：各頁面背後的概念</h2>
<p>這些分類各自管什麼、Google Cloud 與 AWS 的對應名稱，見 <a href="/blog/devops/infra/cloud-providers/console-areas/" data-link-title="雲端主控台的管理領域：身分、資源分組、運算、網路、儲存、費用、監控、稽核、安全服務與配額" data-link-desc="雲端業者的主控台依管理需求分成幾個領域：每個領域管什麼、日常在那裡做哪些事、新帳號開好之後先去哪幾個領域看設定，以及 Oracle Cloud、Google Cloud、AWS 各自把它放在選單的哪個名稱底下">雲端主控台的管理領域</a>；從 Compute → Instances 進去之後建立頁每個分頁的取值，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a>；Tenancy Details 頁上 home region 的意義與它對 Always Free 額度的限制，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a>。</p>
]]></content:encoded></item><item><title>位置：區域、可用區與故障域</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/placement/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/placement/</guid><description>&lt;p>這篇說明建立虛擬機時決定主機實體位置的三層選項。三層由大到小是區域、可用區、故障域，越大的那一層越早定、越難改。範圍是單一台主機的放置；多台主機跨可用區做高可用的設計見 &lt;a href="https://tarrragon.github.io/blog/devops/operations/06-high-availability/redundancy-patterns/" data-link-title="冗餘設計模式" data-link-desc="在 active-passive、active-active、multi-region 之間選冗餘模式時，用資料同步方式與 standby 是否服務流量來區分，並知道冗餘不等於備份">冗餘設計模式&lt;/a>。&lt;/p>
&lt;h2 id="區域region主機在哪個城市">區域（region）：主機在哪個城市&lt;/h2>
&lt;p>區域是一個地理位置上的資料中心群。Oracle Cloud 的區域識別碼帶城市（&lt;code>ap-tokyo-1&lt;/code>），Google Cloud 與 AWS 用洲別、方位加編號（&lt;code>asia-east1&lt;/code> 在台灣、&lt;code>ap-northeast-1&lt;/code> 在東京），城市要查各家的區域對照表。區域決定主機的下面這幾種性質：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>延遲&lt;/strong>：從使用者所在地連到主機的延遲主要由距離決定，量級上同一洲之內是幾十毫秒、跨洲是一兩百毫秒；實際的值用各家提供的區域延遲測試頁量，或從自己的電腦對那一區的服務端點量往返時間。&lt;/li>
&lt;li>&lt;strong>出口 IP 屬於哪個國家&lt;/strong>：主機對外連線時，對方看到的來源 IP 登記在區域所在的國家。要讓連線看起來從某個國家發出（例如自架 VPN），只有那個國家的區域符合。&lt;/li>
&lt;li>&lt;strong>資料受哪個國家的法律管轄&lt;/strong>：主機與它的磁碟實體上放在區域所在的國家，裡面的資料受那個國家的法規管轄。服務有資料落地的要求（個人資料不出境、產業法規指定存放地）時，能選的區域只剩那個國家或那個法域裡的區域。&lt;/li>
&lt;li>&lt;strong>能選的機型與服務&lt;/strong>：區域之間提供的機型不同，新開的區域常常缺幾種舊機型。一個區域實際提供哪些機型，要在那一區的建立頁或 API 上查，不能從另一區推。&lt;/li>
&lt;/ul>
&lt;p>主機建好之後不能搬到另一個區域。要換區域，做法是在新區域建一台新主機、把資料搬過去，再刪掉舊的。&lt;/p>
&lt;p>免費額度常常綁在特定區域上：Oracle Cloud 的 Always Free 只能用在申請時選的 home region，Google Cloud 的 Free Tier 只適用於三個美國區域。Oracle Cloud 的 home region 在申請帳號時就定了、之後不能改，所以這一層在 Oracle Cloud 上要在申請帳號之前想清楚；Google Cloud 的區域在建立主機時才選，選在三個美國區域以外就不算免費。各家的區域限制見 &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;h2 id="可用區availability-zone--availability-domain區域裡的哪一棟">可用區（availability zone / availability domain）：區域裡的哪一棟&lt;/h2>
&lt;p>可用區是區域裡彼此隔離的資料中心，各自有獨立的電力、冷卻與網路，設計上不會同時故障。建立頁上的這個欄位決定主機落在哪一個可用區，知識卡見 &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;/p>
&lt;p>單一台主機選哪個可用區，影響的是兩件事：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>容量&lt;/strong>：免費機型與熱門機型的容量是按可用區分配的。一個可用區回報容量不足時，同一區域的另一個可用區可能還有。換可用區是容量不足時的退路之一，其他退路（等待重試、不指定故障域、改小規格、建立前查容量、換架構、可被收回的容量、預留容量、升級付費帳戶）在 Oracle Cloud 上的完整清單，連同哪幾項會計費，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region&lt;/a> 的〈A1 容量不足時的退路〉。只有一個可用區的區域少了換可用區這條退路——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> 卡；業者開新區域時這份名單會變，選區之前回到官方〈Regions and Availability Domains〉的區域表看一次。&lt;/li>
&lt;li>&lt;strong>磁碟與主機要在同一個可用區&lt;/strong>：開機磁碟與區塊磁碟屬於某一個可用區，主機只能掛同一個可用區的磁碟。從保留下來的開機磁碟重建主機時，新主機要建在那顆磁碟所在的可用區。&lt;/li>
&lt;/ul>
&lt;p>主機建好之後也不能換可用區，換的做法跟換區域相同。&lt;/p>
&lt;h2 id="故障域fault-domain同一棟裡的哪一組硬體">故障域（fault domain）：同一棟裡的哪一組硬體&lt;/h2>
&lt;p>故障域是可用區內部的一組硬體與基礎設施，Oracle Cloud 的每個可用區有三個。它防的是兩種事件：某一組實體主機的硬體故障，以及業者對那一組硬體做的計畫性維護。一個故障域出事時，放在其他故障域的主機不受影響。知識卡見 &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;p>只有一台主機時，故障域沒有選擇的意義：不論放在哪一個，那台主機都只在一組硬體上。建立頁的預設「讓業者選」就是對的選擇，Oracle Cloud 會依當下的容量挑一個（&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">實測紀錄&lt;/a>在只有一個可用區的 &lt;code>ap-batam-1&lt;/code> 就是這樣填的）。手動指定故障域還會縮小可用的容量：指定之後只接受那一個故障域裡的硬體。Oracle Cloud 的 Compute〈Known Issues〉頁對 &lt;code>Out of host capacity&lt;/code> 列的處置裡，就有一項是不指定 fault domain 再建一次。&lt;/p>
&lt;p>故障域的作用在兩台以上。同一個服務的兩台主機，區域有多個可用區時先分到不同可用區，因為可用區隔離的是整棟資料中心的電力與網路，範圍比故障域大（見 &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> 的〈設計責任〉）；區域只有一個可用區時，才靠故障域把兩台放在不同的硬體上，一組硬體故障時只停一台。跨可用區的代價是兩台之間的流量多一段距離：AWS 與 Google Cloud 對跨可用區的流量另外計費，兩台之間做同步複製時延遲也比同一個可用區裡高。&lt;/p></description><content:encoded><![CDATA[<p>這篇說明建立虛擬機時決定主機實體位置的三層選項。三層由大到小是區域、可用區、故障域，越大的那一層越早定、越難改。範圍是單一台主機的放置；多台主機跨可用區做高可用的設計見 <a href="/blog/devops/operations/06-high-availability/redundancy-patterns/" data-link-title="冗餘設計模式" data-link-desc="在 active-passive、active-active、multi-region 之間選冗餘模式時，用資料同步方式與 standby 是否服務流量來區分，並知道冗餘不等於備份">冗餘設計模式</a>。</p>
<h2 id="區域region主機在哪個城市">區域（region）：主機在哪個城市</h2>
<p>區域是一個地理位置上的資料中心群。Oracle Cloud 的區域識別碼帶城市（<code>ap-tokyo-1</code>），Google Cloud 與 AWS 用洲別、方位加編號（<code>asia-east1</code> 在台灣、<code>ap-northeast-1</code> 在東京），城市要查各家的區域對照表。區域決定主機的下面這幾種性質：</p>
<ul>
<li><strong>延遲</strong>：從使用者所在地連到主機的延遲主要由距離決定，量級上同一洲之內是幾十毫秒、跨洲是一兩百毫秒；實際的值用各家提供的區域延遲測試頁量，或從自己的電腦對那一區的服務端點量往返時間。</li>
<li><strong>出口 IP 屬於哪個國家</strong>：主機對外連線時，對方看到的來源 IP 登記在區域所在的國家。要讓連線看起來從某個國家發出（例如自架 VPN），只有那個國家的區域符合。</li>
<li><strong>資料受哪個國家的法律管轄</strong>：主機與它的磁碟實體上放在區域所在的國家，裡面的資料受那個國家的法規管轄。服務有資料落地的要求（個人資料不出境、產業法規指定存放地）時，能選的區域只剩那個國家或那個法域裡的區域。</li>
<li><strong>能選的機型與服務</strong>：區域之間提供的機型不同，新開的區域常常缺幾種舊機型。一個區域實際提供哪些機型，要在那一區的建立頁或 API 上查，不能從另一區推。</li>
</ul>
<p>主機建好之後不能搬到另一個區域。要換區域，做法是在新區域建一台新主機、把資料搬過去，再刪掉舊的。</p>
<p>免費額度常常綁在特定區域上：Oracle Cloud 的 Always Free 只能用在申請時選的 home region，Google Cloud 的 Free Tier 只適用於三個美國區域。Oracle Cloud 的 home region 在申請帳號時就定了、之後不能改，所以這一層在 Oracle Cloud 上要在申請帳號之前想清楚；Google Cloud 的區域在建立主機時才選，選在三個美國區域以外就不算免費。各家的區域限制見 <a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較</a>。</p>
<h2 id="可用區availability-zone--availability-domain區域裡的哪一棟">可用區（availability zone / availability domain）：區域裡的哪一棟</h2>
<p>可用區是區域裡彼此隔離的資料中心，各自有獨立的電力、冷卻與網路，設計上不會同時故障。建立頁上的這個欄位決定主機落在哪一個可用區，知識卡見 <a href="/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）</a>。</p>
<p>單一台主機選哪個可用區，影響的是兩件事：</p>
<ul>
<li><strong>容量</strong>：免費機型與熱門機型的容量是按可用區分配的。一個可用區回報容量不足時，同一區域的另一個可用區可能還有。換可用區是容量不足時的退路之一，其他退路（等待重試、不指定故障域、改小規格、建立前查容量、換架構、可被收回的容量、預留容量、升級付費帳戶）在 Oracle Cloud 上的完整清單，連同哪幾項會計費，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈A1 容量不足時的退路〉。只有一個可用區的區域少了換可用區這條退路——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> 卡；業者開新區域時這份名單會變，選區之前回到官方〈Regions and Availability Domains〉的區域表看一次。</li>
<li><strong>磁碟與主機要在同一個可用區</strong>：開機磁碟與區塊磁碟屬於某一個可用區，主機只能掛同一個可用區的磁碟。從保留下來的開機磁碟重建主機時，新主機要建在那顆磁碟所在的可用區。</li>
</ul>
<p>主機建好之後也不能換可用區，換的做法跟換區域相同。</p>
<h2 id="故障域fault-domain同一棟裡的哪一組硬體">故障域（fault domain）：同一棟裡的哪一組硬體</h2>
<p>故障域是可用區內部的一組硬體與基礎設施，Oracle Cloud 的每個可用區有三個。它防的是兩種事件：某一組實體主機的硬體故障，以及業者對那一組硬體做的計畫性維護。一個故障域出事時，放在其他故障域的主機不受影響。知識卡見 <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>
<p>只有一台主機時，故障域沒有選擇的意義：不論放在哪一個，那台主機都只在一組硬體上。建立頁的預設「讓業者選」就是對的選擇，Oracle Cloud 會依當下的容量挑一個（<a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">實測紀錄</a>在只有一個可用區的 <code>ap-batam-1</code> 就是這樣填的）。手動指定故障域還會縮小可用的容量：指定之後只接受那一個故障域裡的硬體。Oracle Cloud 的 Compute〈Known Issues〉頁對 <code>Out of host capacity</code> 列的處置裡，就有一項是不指定 fault domain 再建一次。</p>
<p>故障域的作用在兩台以上。同一個服務的兩台主機，區域有多個可用區時先分到不同可用區，因為可用區隔離的是整棟資料中心的電力與網路，範圍比故障域大（見 <a href="/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）</a> 的〈設計責任〉）；區域只有一個可用區時，才靠故障域把兩台放在不同的硬體上，一組硬體故障時只停一台。跨可用區的代價是兩台之間的流量多一段距離：AWS 與 Google Cloud 對跨可用區的流量另外計費，兩台之間做同步複製時延遲也比同一個可用區裡高。</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（官方的說明是彼此隔離、各自不同的一組硬體；它跟 Oracle Cloud 的 availability domain 同名、層級不同，Oracle Cloud 那一層對應的是可用區）時，官方說明可以不停機就套用到既有的主機；套用時 Compute Engine 可能要把主機搬到另一個 availability domain，搬的方式依主機的維護政策，是即時遷移或停止主機。</p>
<h2 id="各家的欄位名稱區域可用區與故障域">各家的欄位名稱：區域、可用區與故障域</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>區域</td>
          <td>Region</td>
          <td>Region</td>
          <td>Region</td>
      </tr>
      <tr>
          <td>可用區</td>
          <td>Availability domain（AD）</td>
          <td>Zone</td>
          <td>Availability Zone（AZ）</td>
      </tr>
      <tr>
          <td>故障域</td>
          <td>Fault domain</td>
          <td>Spread placement policy</td>
          <td>Placement group（spread 策略）</td>
      </tr>
  </tbody>
</table>
<p>Oracle Cloud 建立頁的 Placement 區塊裡另有一個 Capacity type 欄位，它決定的是計費與可被收回的條件，跟位置無關，說明在 <a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔</a>。</p>
<h2 id="延伸閱讀同一組選項的下一層">延伸閱讀：同一組選項的下一層</h2>
<ul>
<li><a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔</a>：那一區能選哪些機型，以及容量不足時改小規格、換架構或改用可被收回容量各自的代價</li>
<li><a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a>：免費額度綁在區域上時，申請帳號那一刻就決定了位置；配額表列出某個機型、而那一區沒有那種硬體時，建立會失敗而錯誤碼分不出原因</li>
</ul>
]]></content:encoded></item><item><title>雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/pricing-comparison/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/pricing-comparison/</guid><description>&lt;p>這篇整理比較 Oracle Cloud、Google Cloud 與 AWS 的免費方案與小型主機費用時要對齊的項目。範圍是一台個人或實驗用的小型虛擬機：能不能免費開著、放在哪裡、什麼時候會開始收錢；服務要不要跑在自己管的虛擬機上、還是交給 PaaS 或 serverless，是比這一篇更早的決定，見 &lt;a href="https://tarrragon.github.io/blog/devops/going-live/hosting-spectrum/" data-link-title="主機形態光譜" data-link-desc="第一次要選服務跑在哪、被 VPS / IaaS / PaaS / serverless 這些詞搞混、不知道差在哪時回來讀 — 主機形態是一道「你管多少」的光譜">主機形態光譜&lt;/a>。AWS 的部分指的是 2025-07-15 起開立、開帳號時選 Free account plan 的帳號。2025-07-15 之前開的帳號適用舊的 Free Tier：EC2 使用指南〈&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-free-tier-usage.html">Track your Free Tier usage for Amazon EC2&lt;/a>〉的〈Free Tier benefits before and after July 15, 2025〉對照表寫明，舊帳號的 Free Tier 從開戶日起算 12 個月，免費機型是 &lt;code>t2.micro&lt;/code> 與 &lt;code>t3.micro&lt;/code>，超出額度的用量照 pay-as-you-go 價格計費；這篇其餘寫到 AWS 試用金與 Free plan 的地方不適用於這種帳號。各家的規則取自官方頁面，查證日期寫在比較表上；價格與額度會改，拿來做決定之前回到表上的連結看一次最新版本。&lt;/p>
&lt;h2 id="免費方案的兩種額度有期限的試用金與沒有期限的固定額度">免費方案的兩種額度：有期限的試用金與沒有期限的固定額度&lt;/h2>
&lt;p>三家的免費方案都由兩種性質不同的額度組成：試用金與固定額度。&lt;/p>
&lt;p>&lt;strong>試用金&lt;/strong>（free trial credits）是一筆有期限的金額，可以花在大部分付費服務上。有了它，試用期限內可以試用較大的機型或付費服務；期限一到、或金額用完，額度就結束。&lt;/p>
&lt;p>&lt;strong>固定額度&lt;/strong>是每個月重置、沒有期限的用量上限，只適用於指定的機型與服務。一台小主機要長期免費開著，靠的是這一種額度。各家對固定額度的稱呼不同：Oracle Cloud 叫 Always Free，Google Cloud 叫 Free Tier，AWS 叫 Always free services。&lt;/p>
&lt;p>要長期跑一台小主機時，看的是固定額度；試用金只影響開頭那一段期限（Oracle Cloud 30 天、Google Cloud 90 天、AWS 6 個月，見比較表）。AWS 目前的 Free plan 沒有 EC2 的固定額度，長期跑主機要升級成付費帳戶，見下一節。&lt;/p>
&lt;h2 id="計費單位的換算ocpuvcpu-與機型配額">計費單位的換算：OCPU、vCPU 與機型配額&lt;/h2>
&lt;p>三家描述運算額度的單位不同。比較的基準是 vCPU：作業系統看到的一個虛擬處理器。多數 x86 機型的一顆實體核心跑兩個硬體執行緒（超執行緒），雲端業者把每個硬體執行緒算成一個 vCPU，這時一個 vCPU 是半顆核心。例外是每核只跑一個執行緒的 x86 機型：AWS 的 &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/cpu-options-supported-instances-values.html">CPU 選項對照表&lt;/a> 上，AMD 處理器的 M7a、C7a、R7a 與 M8a、C8a、R8a 等系列每核執行緒數只有 1，它們的一個 vCPU 就是一顆實體核心。ARM 處理器裡，Oracle Cloud 的 Ampere A1 與 AWS Graviton 每核一個執行緒，一顆核心就是一個 vCPU；Oracle Cloud 較新的 Arm 機型 A2 與 A4 的 OCPU 換算跟 A1 不同，見下方 Oracle Cloud 那一項。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Oracle Cloud 用 OCPU 計&lt;/strong>：官方〈&lt;a href="https://docs.oracle.com/en-us/iaas/Content/Compute/References/computeshapes.htm">Compute Shapes&lt;/a>〉的〈OCPUs and vCPUs〉一節把 OCPU 定義為實體核心，換算成 vCPU 的比例依處理器不同：x86（AMD、Intel）的 1 OCPU 等於 2 個 vCPU，Ampere A1 的 1 OCPU 等於 1 個 vCPU；同一節另列較新的 Arm 機型 A2 與 A4，它們的 1 OCPU 等於 2 顆核心、也就是 2 個 vCPU，不在 Always Free 額度內。Always Free 的 A1 額度以「OCPU 小時」與「GB 小時」計，每月 1,500 OCPU 小時、9,000 GB 小時，可以自由切成一台或兩台；整個月開著的 2 OCPU A1 主機，作業系統看到的是 2 個 vCPU。付費 tenancy 的 A1 免費量是每月 3,000 OCPU 小時、18,000 GB 小時（相當於 4 OCPU、24 GB 整個月開著；2026-10-26 讀到的 Oracle 的 Compute 價目頁，頁名這次沒有記下）。&lt;/li>
&lt;li>&lt;strong>Google Cloud 以機型與台數計&lt;/strong>：Free Tier 是每月一台 &lt;code>e2-micro&lt;/code>。&lt;code>e2-micro&lt;/code> 是共享核心機型，作業系統看到 2 個 vCPU，而持續使用量是每個 vCPU 12.5%、合計 25% 的 CPU 時間；每個 vCPU 可以短時間爆發到 100%，全速爆發約 30 秒。持續高負載時，它能用的運算量是 0.25 個 vCPU。&lt;/li>
&lt;li>&lt;strong>AWS 以帳號層級的試用金計&lt;/strong>：目前的 Free plan 不再列出每月固定的機型時數，EC2 的用量由試用金支付。&lt;/li>
&lt;/ul>
&lt;p>所以「OCI 給 2 OCPU、GCP 給 2 vCPU」這類比較不成立：OCI 的 2 OCPU A1 在持續負載下是 2 個完整的 vCPU，GCP 的 &lt;code>e2-micro&lt;/code> 是 0.25 個。要比的是在持續負載下能拿到多少運算量，以及免費的部分是以時數、台數還是金額計算。&lt;/p></description><content:encoded><![CDATA[<p>這篇整理比較 Oracle Cloud、Google Cloud 與 AWS 的免費方案與小型主機費用時要對齊的項目。範圍是一台個人或實驗用的小型虛擬機：能不能免費開著、放在哪裡、什麼時候會開始收錢；服務要不要跑在自己管的虛擬機上、還是交給 PaaS 或 serverless，是比這一篇更早的決定，見 <a href="/blog/devops/going-live/hosting-spectrum/" data-link-title="主機形態光譜" data-link-desc="第一次要選服務跑在哪、被 VPS / IaaS / PaaS / serverless 這些詞搞混、不知道差在哪時回來讀 — 主機形態是一道「你管多少」的光譜">主機形態光譜</a>。AWS 的部分指的是 2025-07-15 起開立、開帳號時選 Free account plan 的帳號。2025-07-15 之前開的帳號適用舊的 Free Tier：EC2 使用指南〈<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-free-tier-usage.html">Track your Free Tier usage for Amazon EC2</a>〉的〈Free Tier benefits before and after July 15, 2025〉對照表寫明，舊帳號的 Free Tier 從開戶日起算 12 個月，免費機型是 <code>t2.micro</code> 與 <code>t3.micro</code>，超出額度的用量照 pay-as-you-go 價格計費；這篇其餘寫到 AWS 試用金與 Free plan 的地方不適用於這種帳號。各家的規則取自官方頁面，查證日期寫在比較表上；價格與額度會改，拿來做決定之前回到表上的連結看一次最新版本。</p>
<h2 id="免費方案的兩種額度有期限的試用金與沒有期限的固定額度">免費方案的兩種額度：有期限的試用金與沒有期限的固定額度</h2>
<p>三家的免費方案都由兩種性質不同的額度組成：試用金與固定額度。</p>
<p><strong>試用金</strong>（free trial credits）是一筆有期限的金額，可以花在大部分付費服務上。有了它，試用期限內可以試用較大的機型或付費服務；期限一到、或金額用完，額度就結束。</p>
<p><strong>固定額度</strong>是每個月重置、沒有期限的用量上限，只適用於指定的機型與服務。一台小主機要長期免費開著，靠的是這一種額度。各家對固定額度的稱呼不同：Oracle Cloud 叫 Always Free，Google Cloud 叫 Free Tier，AWS 叫 Always free services。</p>
<p>要長期跑一台小主機時，看的是固定額度；試用金只影響開頭那一段期限（Oracle Cloud 30 天、Google Cloud 90 天、AWS 6 個月，見比較表）。AWS 目前的 Free plan 沒有 EC2 的固定額度，長期跑主機要升級成付費帳戶，見下一節。</p>
<h2 id="計費單位的換算ocpuvcpu-與機型配額">計費單位的換算：OCPU、vCPU 與機型配額</h2>
<p>三家描述運算額度的單位不同。比較的基準是 vCPU：作業系統看到的一個虛擬處理器。多數 x86 機型的一顆實體核心跑兩個硬體執行緒（超執行緒），雲端業者把每個硬體執行緒算成一個 vCPU，這時一個 vCPU 是半顆核心。例外是每核只跑一個執行緒的 x86 機型：AWS 的 <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/cpu-options-supported-instances-values.html">CPU 選項對照表</a> 上，AMD 處理器的 M7a、C7a、R7a 與 M8a、C8a、R8a 等系列每核執行緒數只有 1，它們的一個 vCPU 就是一顆實體核心。ARM 處理器裡，Oracle Cloud 的 Ampere A1 與 AWS Graviton 每核一個執行緒，一顆核心就是一個 vCPU；Oracle Cloud 較新的 Arm 機型 A2 與 A4 的 OCPU 換算跟 A1 不同，見下方 Oracle Cloud 那一項。</p>
<ul>
<li><strong>Oracle Cloud 用 OCPU 計</strong>：官方〈<a href="https://docs.oracle.com/en-us/iaas/Content/Compute/References/computeshapes.htm">Compute Shapes</a>〉的〈OCPUs and vCPUs〉一節把 OCPU 定義為實體核心，換算成 vCPU 的比例依處理器不同：x86（AMD、Intel）的 1 OCPU 等於 2 個 vCPU，Ampere A1 的 1 OCPU 等於 1 個 vCPU；同一節另列較新的 Arm 機型 A2 與 A4，它們的 1 OCPU 等於 2 顆核心、也就是 2 個 vCPU，不在 Always Free 額度內。Always Free 的 A1 額度以「OCPU 小時」與「GB 小時」計，每月 1,500 OCPU 小時、9,000 GB 小時，可以自由切成一台或兩台；整個月開著的 2 OCPU A1 主機，作業系統看到的是 2 個 vCPU。付費 tenancy 的 A1 免費量是每月 3,000 OCPU 小時、18,000 GB 小時（相當於 4 OCPU、24 GB 整個月開著；2026-10-26 讀到的 Oracle 的 Compute 價目頁，頁名這次沒有記下）。</li>
<li><strong>Google Cloud 以機型與台數計</strong>：Free Tier 是每月一台 <code>e2-micro</code>。<code>e2-micro</code> 是共享核心機型，作業系統看到 2 個 vCPU，而持續使用量是每個 vCPU 12.5%、合計 25% 的 CPU 時間；每個 vCPU 可以短時間爆發到 100%，全速爆發約 30 秒。持續高負載時，它能用的運算量是 0.25 個 vCPU。</li>
<li><strong>AWS 以帳號層級的試用金計</strong>：目前的 Free plan 不再列出每月固定的機型時數，EC2 的用量由試用金支付。</li>
</ul>
<p>所以「OCI 給 2 OCPU、GCP 給 2 vCPU」這類比較不成立：OCI 的 2 OCPU A1 在持續負載下是 2 個完整的 vCPU，GCP 的 <code>e2-micro</code> 是 0.25 個。要比的是在持續負載下能拿到多少運算量，以及免費的部分是以時數、台數還是金額計算。</p>
<h2 id="固定額度的區域限制">固定額度的區域限制</h2>
<p>固定額度通常只適用於部分區域，而區域決定主機的實體位置——從使用者所在地連過去的延遲，以及主機對外連線時出口 IP 屬於哪個國家。</p>
<ul>
<li><strong>Oracle Cloud</strong>：Always Free 的運算與區塊磁碟只能建在 home region，home region 在申請時選定、之後不能改（細節見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a>）。</li>
<li><strong>Google Cloud</strong>：Free Tier 的 <code>e2-micro</code> 只適用於美國的三個區域：<code>us-west1</code>（奧勒岡）、<code>us-central1</code>（愛荷華）、<code>us-east1</code>（南卡羅來納）。在亞洲區域建同一個機型照一般價格計費。</li>
<li><strong>AWS</strong>：〈<a href="https://aws.amazon.com/free/free-tier-faqs/">Free Tier FAQ</a>〉寫明試用金與方案選擇在中國的 ZHY、BJS 兩區不提供（原文「The AWS Free Tier credits and account plan selections are not available in the China (ZHY) and China (BJS) regions.」），GovCloud（US）區域不適用 Free Tier（Lambda 等少數服務例外）；其他區域 FAQ 沒有列出限制。</li>
</ul>
<p>用途需要特定國家的出口 IP 時（例如要讓連線看起來從日本發出），固定額度的區域限制是第一個篩選條件。Google Cloud 的固定額度只在美國；Oracle Cloud 的前提是申請時就把 home region 選在那個國家，而那個國家的區域多半只有一個可用區，A1 免費機型容量不足時沒有別的可用區可以換（容量不足時還有哪些退路，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈A1 容量不足時的退路〉）；AWS 的 Free Tier FAQ 排除的只有中國與 GovCloud 的區域，日本的區域不在排除之列，而 Free plan 帳號最晚 6 個月後關閉，長期使用要升級成付費帳戶。</p>
<h2 id="試用結束後帳號與資源的處置">試用結束後帳號與資源的處置</h2>
<p>試用期結束、而沒有升級成付費帳戶時，主機繼續免費、被停掉還是開始收費，各家不同。</p>
<ul>
<li><strong>Oracle Cloud</strong>：試用結束後帳號照常存在，Always Free 資源不中斷。除非升級成付費帳戶，用試用金建的付費資源會被回收；tenancy 裡全部 A1 主機的合計超過 2 OCPU、12 GB 時，全部 A1 主機會被停用、30 天後刪除。A1 合計的判斷方式與試用結束前怎麼避免，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈Always Free 的額度與它綁在 home region 上的範圍〉。</li>
<li><strong>Google Cloud</strong>：試用結束時如果沒有升級成付費帳戶，帳單帳戶會被停用、資源會被停止、帳號失去 Google Cloud 產品的存取權，有 30 天的寬限期可以升級回復。Free Tier 的固定額度要在付費帳戶上才能繼續用；升級之後超出額度的用量開始計費。</li>
<li><strong>AWS</strong>：Free plan 在開立 6 個月後或試用金用完時（先到者為準）結束，帳號隨即自動關閉、失去資源與資料的存取權；AWS 保留帳號內容 90 天，90 天內升級到 Paid plan 可以保住帳號的存取權，剩下的試用金會用在之後的帳單上。升級之後超出試用金的用量開始計費。</li>
</ul>
<p>三家在不升級時的處置不同：Oracle Cloud 不升級也能留著免費資源，Google Cloud 與 AWS 不升級的話資源會停掉或帳號會關閉，而升級之後這兩家超出額度的部分會直接計費。升級成付費帳戶之前先設好預算警示，讓超出額度的費用在發生時有人收到通知。預算門檻怎麼算、扣款的信用卡要留多少額度、配額怎麼當成費用的上限，在 <a href="/blog/devops/operations/08-cost-management/spend-planning-before-renting/" data-link-title="租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層" data-link-desc="租用雲端主機或把試用帳號升級成付費帳戶之前，費用失控時從觸發到停止之間的金額怎麼從配額與訊號延遲推出來，再用它推扣款的信用卡額度、配額、預算警示與觸發處置的門檻；Oracle Cloud、Google Cloud 與 AWS 的扣款時點、預先授權、扣款失敗的後果、擋下建立與停止用量的機制各自涵蓋什麼，以及停損停到哪一層的判斷">租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層</a>；Google Cloud 預算的實際花費與預測花費兩種依據在 <a href="/blog/devops/infra/cloud-providers/gcp/spend-protection-gcp-budget-killswitch/" data-link-title="GCP 上的雲端費用失控防護：預算警示、強制執行支出上限與自動停用帳單的作用範圍、反應時間與判斷" data-link-desc="GCP 上為費用失控設防的機制：預算警示寄出通知的時機、範圍、抵免與通知對象、強制執行支出上限涵蓋的服務與暫停期間，以預算通知觸發函式停止用量的兩種處置（解除帳單連結、只停止花錢的資源）該選哪一種，帳單資料延遲對這些機制反應時間的影響，停損開關放在另一個專案的理由，以及驗收為什麼分成真實通知與測試訊息兩段、停用後的恢復為什麼要先調預算">GCP 上的雲端費用失控防護</a>，門檻比例、建立指令與從預算通知觸發停損的建置在 <a href="/blog/devops/infra/cloud-providers/gcp/hands-on/budget-killswitch-walkthrough/" data-link-title="GCP 預算警示與停損開關的實測紀錄：建預算、Pub/Sub 主題、服務帳號、函式與觸發器、兩段驗收、心跳監看與停用後的恢復" data-link-desc="要在 GCP 專案上建預算警示，或建一個花費超過預算就解除帳單連結的停損開關，想照一次實跑過的順序做、看每一步漏掉或換一種做法的結果時讀；也用在函式部署成功、狀態 ACTIVE 卻從沒解除過帳單連結，或專案因超支被停用之後要恢復的時候">GCP 預算警示與停損開關的實測紀錄</a>；成本監控與異常告警的設計見 <a href="/blog/devops/operations/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing 與成本監控告警；預算門檻、配額與停損層級的推算在本模組，各家的建置篇目前只有 GCP，放在雲端業者模組">運行期維運的成本管理</a>。Oracle Cloud 不同帳號狀態下超出額度的結果，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果〉。</p>
<h2 id="出站流量與閒置回收">出站流量與閒置回收</h2>
<p>主機本身免費，費用仍可能來自出站流量（outbound data transfer，也常寫作 egress）——主機送到網際網路的資料量。跑 VPN、下載站這類用途，流量才是主要成本。出站流量在整份帳單裡跟其他隱藏成本（NAT 閘道的流量費、被遺忘的資源）放在一起看，見 <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>
<ul>
<li><strong>Oracle Cloud</strong>：每月 10 TB 出站流量免費。</li>
<li><strong>Google Cloud</strong>：Free Tier 每月 1 GB 從北美送往各地的出站流量（不含中國與澳洲）；超過的部分計費。</li>
<li><strong>AWS</strong>：Free plan 頁面沒有列流量額度，額度寫在 EC2 的價格頁：每月 100 GB 送往網際網路的出站流量免費，所有服務與區域合計（不含中國與 GovCloud 區域）。</li>
</ul>
<p>另一項只有 Oracle Cloud 明文寫出的規則是閒置回收：Always Free 主機在 7 天內的 CPU 使用率（取第 95 百分位）、網路使用率、記憶體使用率（只算 A1 機型）都低於 20% 時，Oracle 可能回收它（官方沒有寫網路使用率的 20% 是對什麼的比例）；官方頁用的詞是 reclaim，沒有寫回收之後主機是停止還是刪除。規則的出處見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a>。一台大部分時間沒有流量的 VPN 或實驗機會落在這個條件裡。主機會被回收、而回收是停止還是刪除不明時，維護與暫停期間要先做的準備（資料備份到主機以外、確認照 repo 重建得出來），見 <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>〈暫停時保留的範圍：只停服務、停止主機、終止主機、全部刪除〉。</p>
<h2 id="三家免費方案比較表查證日期-2026-10-24">三家免費方案比較表（查證日期 2026-10-24）</h2>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>試用金</td>
          <td>300 美元、30 天</td>
          <td>300 美元、90 天</td>
          <td>註冊後 100 美元，完成官方列出的入門活動（例如用 EC2 建一台主機、用 AWS Budgets 設預算）最多再加 100 美元；Free plan 最長 6 個月</td>
      </tr>
      <tr>
          <td>固定額度的運算</td>
          <td>A1 每月 1,500 OCPU 小時與 9,000 GB 小時（付費 tenancy 是 3,000 OCPU 小時與 18,000 GB 小時）；E2.1.Micro 兩台</td>
          <td>每月一台 <code>e2-micro</code></td>
          <td>頁面未列出 EC2 的固定時數</td>
      </tr>
      <tr>
          <td>固定額度的區域</td>
          <td>只限 home region</td>
          <td><code>us-west1</code>、<code>us-central1</code>、<code>us-east1</code></td>
          <td>試用金與方案選擇不含中國（ZHY、BJS），GovCloud 不適用（FAQ）</td>
      </tr>
      <tr>
          <td>固定額度的磁碟</td>
          <td>開機磁碟與區塊磁碟合計 200 GB（預設 50 GB 的開機磁碟實際扣 47 GB，實測推論；付費帳戶新建的磁碟超出時以整顆計，實測；見表下）</td>
          <td>30 GB-months 的 standard persistent disk（開機磁碟類型要選 standard 才算）</td>
          <td>頁面未列出</td>
      </tr>
      <tr>
          <td>試用結束、不升級時</td>
          <td>帳號保留、Always Free 資源繼續（A1 合計超額時全部 A1 停用）</td>
          <td>資源停止、帳號失去存取權</td>
          <td>帳號關閉</td>
      </tr>
      <tr>
          <td>出站流量</td>
          <td>每月 10 TB</td>
          <td>每月 1 GB（北美出發）</td>
          <td>每月 100 GB（所有服務合計，列在 EC2 價格頁）</td>
      </tr>
      <tr>
          <td>官方頁面</td>
          <td><a href="https://docs.oracle.com/en-us/iaas/Content/FreeTier/freetier.htm">Free Tier</a>、<a href="https://docs.oracle.com/en-us/iaas/Content/FreeTier/freetier_topic-Always_Free_Resources.htm">Always Free Resources</a></td>
          <td><a href="https://docs.cloud.google.com/free/docs/free-cloud-features">Free cloud features</a></td>
          <td><a href="https://aws.amazon.com/free/">AWS Free Tier</a>、<a href="https://aws.amazon.com/free/free-tier-faqs/">Free Tier FAQ</a>、<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-free-tier-usage.html">Track your Free Tier usage for Amazon EC2</a>、<a href="https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/free-tier-plans.html">Choosing a plan</a>、<a href="https://aws.amazon.com/ec2/pricing/on-demand/">EC2 On-Demand Pricing</a></td>
      </tr>
  </tbody>
</table>
<p>表裡 Oracle Cloud 磁碟那一格的 47 GB 不是來自官方頁面：2026-10-27 用 <code>oci limits resource-availability get</code> 查區塊磁碟額度的用量，三顆預設大小的開機磁碟合計扣 141，每顆 47，數值是 46.6 GiB 進位。合計超過 200 GB 時，判定免費與否的單位是整顆磁碟：同一天在付費 tenancy 建一顆 60 GB 的開機磁碟讓合計變成 201，免費額度的用量停在 141，那一顆整顆不算免費額度。查法與推論過程在 <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〉。</p>
<h2 id="查付費主機價格時要輸入的項目">查付費主機價格時要輸入的項目</h2>
<p>固定額度不夠用、或要把主機放在固定額度以外的區域時，價格要回到各家的價格頁與計算器查。這篇不列單價，理由是單價隨區域、機型世代與時間改變，寫在這裡的數字很快就會跟讀者看到的帳單對不上。查價時把這幾項一起輸入計算器，算出來的才是一台主機一個月的實際費用：</p>
<ul>
<li>運算：機型、核心數（注意 OCPU 與 vCPU 的換算）、記憶體、每月開機時數</li>
<li>開機磁碟的容量與類型</li>
<li>公開 IPv4 位址：AWS 對每個公開 IPv4 位址按小時另外計費，其他業者去各自價格頁的 IP 位址那一項查</li>
<li>出站流量的預估量</li>
<li>負載平衡器：服務對外、主機留在私有 subnet 時要用。Oracle Cloud 的 Always Free 含一個 10 Mbps 的負載平衡器，AWS 與 Google Cloud 另外計費</li>
<li>NAT 閘道：私有 subnet 的主機要下載套件更新時要用，AWS 的 NAT Gateway 與 Google Cloud 的 Cloud NAT 另外計費；Oracle Cloud 的價目表（2026-10-27 讀）沒有 NAT gateway 這一項。一個 NAT gateway 開了約 6 小時的那一天，帳務明細也沒有它的項目，經它出去的流量記在送出流量的主機的 VNIC 上、照出站流量計（從這一天的明細推論，沒有讀完一個帳單週期），實測與讀法見 <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>
</ul>
]]></content:encoded></item><item><title>Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/</guid><description>&lt;p>Oracle Cloud Infrastructure（OCI）帳號裡的 home region 在申請表送出的那一刻就固定，之後不能改，而 Always Free 的運算與區塊磁碟額度只能用在 home region，所以〈選 home region 時要先確認的事〉那一節要在送出申請表之前讀。這篇整理 tenancy、home region、compartment 與 availability domain 各自的範圍、它們怎麼限制 Always Free 額度能用在哪裡，以及在主控台登入時兩個欄位的填法；從本機用 CLI 操作見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/" data-link-title="從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰" data-link-desc="OCI CLI 的兩種身分驗證方式——瀏覽器登入換來的 session token 與上傳公鑰的 API 簽章金鑰——各自把什麼存在本機、有效多久、外洩時的影響範圍，以及登入時的互動提示與錯誤訊息怎麼讀">從本機用 OCI CLI 操作 Oracle Cloud&lt;/a>。文中的規則取自 OCI 官方文件，查證日期 2026-10-24；登入欄位的行為在一個 Free Tier 帳號上實際操作過。&lt;/p>
&lt;h2 id="tenancyhome-regioncompartment-與-availability-domain">Tenancy、home region、compartment 與 availability domain&lt;/h2>
&lt;p>OCI 的帳號叫 &lt;strong>tenancy&lt;/strong>（主控台中文介面寫作「租用戶」）。一個 tenancy 對應一個客戶，裡面所有的使用者、群組、政策與資源都屬於它；申請時填的 Cloud Account Name 就是 tenancy 的名稱。Tenancy 與它底下的每一個資源各有一個以 &lt;code>ocid1.&lt;/code> 開頭的唯一識別碼，叫 OCID（Oracle Cloud ID），CLI 與 API 用它指定要操作的對象；tenancy 的 OCID 在 Tenancy Details 頁，其他資源的在各自的詳細頁上。&lt;/p>
&lt;p>&lt;strong>Home region&lt;/strong> 是 tenancy 建立時所在的那一個區域。官方文件〈&lt;a href="https://docs.oracle.com/en-us/iaas/Content/Identity/regions/managingregions.htm">Managing Regions&lt;/a>〉說明它存放帳號資訊與身分資源（使用者、群組、政策），而且建立之後不能變更，FAQ 的原文是「No. Oracle assigns your home region and you can&amp;rsquo;t change it.」使用者、群組、政策與 compartment 的定義住在 home region、只能在那裡修改：用 API 改 IAM 資源時要打 home region 的端點，用主控台改時主控台會替使用者把請求送到 home region，改完之後由 IAM 自動傳播到 tenancy 訂閱的其他區域，官方提醒傳播可能要幾分鐘。&lt;/p>
&lt;p>Tenancy 可以另外訂閱其他區域（region subscription），訂閱之後能在那一區建資源，而帳號與身分資源仍然住在 home region。訂閱改變的是資源能放在哪裡，不改變 home region。訂閱的數量有上限：官方〈Regions and Availability Domains〉寫明試用、Free Tier 與 Pay As You Go 的 tenancy 都只能有一個訂閱區域，也就是 home region 本身；升級成付費帳戶（Pay As You Go）之後可以申請提高這個上限；試用與 Free Tier 的 tenancy 不能申請，訂閱區域就只有 home region 一個。&lt;/p>
&lt;p>&lt;strong>Compartment&lt;/strong> 是 tenancy 底下用來分組資源與套用政策的容器。Tenancy 本身就是最上層的 root compartment，新帳號一開始只有它。實驗用的資源另開一個 compartment 放，清理時刪那一個 compartment 底下的資源就好，不會碰到 root 裡的其他設定。&lt;/p>
&lt;p>&lt;strong>Availability domain&lt;/strong>（AD）是一個區域裡彼此隔離的資料中心，跨業者的概念見 &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>。區域之間的 AD 數量不同：有些區域有三個，有些只有一個。申請之前，各區域的 AD 數列在官方〈Regions and Availability Domains〉頁的區域表；申請之後，&lt;code>oci iam availability-domain list&lt;/code>（登入方式見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/" data-link-title="從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰" data-link-desc="OCI CLI 的兩種身分驗證方式——瀏覽器登入換來的 session token 與上傳公鑰的 API 簽章金鑰——各自把什麼存在本機、有效多久、外洩時的影響範圍，以及登入時的互動提示與錯誤訊息怎麼讀">從本機用 OCI CLI 操作 Oracle Cloud&lt;/a>）回傳的清單長度就是自己 home region 的 AD 數。AD 數會影響免費機型建不起來時的退路，見〈A1 容量不足時的退路〉。&lt;/p></description><content:encoded><![CDATA[<p>Oracle Cloud Infrastructure（OCI）帳號裡的 home region 在申請表送出的那一刻就固定，之後不能改，而 Always Free 的運算與區塊磁碟額度只能用在 home region，所以〈選 home region 時要先確認的事〉那一節要在送出申請表之前讀。這篇整理 tenancy、home region、compartment 與 availability domain 各自的範圍、它們怎麼限制 Always Free 額度能用在哪裡，以及在主控台登入時兩個欄位的填法；從本機用 CLI 操作見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/" data-link-title="從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰" data-link-desc="OCI CLI 的兩種身分驗證方式——瀏覽器登入換來的 session token 與上傳公鑰的 API 簽章金鑰——各自把什麼存在本機、有效多久、外洩時的影響範圍，以及登入時的互動提示與錯誤訊息怎麼讀">從本機用 OCI CLI 操作 Oracle Cloud</a>。文中的規則取自 OCI 官方文件，查證日期 2026-10-24；登入欄位的行為在一個 Free Tier 帳號上實際操作過。</p>
<h2 id="tenancyhome-regioncompartment-與-availability-domain">Tenancy、home region、compartment 與 availability domain</h2>
<p>OCI 的帳號叫 <strong>tenancy</strong>（主控台中文介面寫作「租用戶」）。一個 tenancy 對應一個客戶，裡面所有的使用者、群組、政策與資源都屬於它；申請時填的 Cloud Account Name 就是 tenancy 的名稱。Tenancy 與它底下的每一個資源各有一個以 <code>ocid1.</code> 開頭的唯一識別碼，叫 OCID（Oracle Cloud ID），CLI 與 API 用它指定要操作的對象；tenancy 的 OCID 在 Tenancy Details 頁，其他資源的在各自的詳細頁上。</p>
<p><strong>Home region</strong> 是 tenancy 建立時所在的那一個區域。官方文件〈<a href="https://docs.oracle.com/en-us/iaas/Content/Identity/regions/managingregions.htm">Managing Regions</a>〉說明它存放帳號資訊與身分資源（使用者、群組、政策），而且建立之後不能變更，FAQ 的原文是「No. Oracle assigns your home region and you can&rsquo;t change it.」使用者、群組、政策與 compartment 的定義住在 home region、只能在那裡修改：用 API 改 IAM 資源時要打 home region 的端點，用主控台改時主控台會替使用者把請求送到 home region，改完之後由 IAM 自動傳播到 tenancy 訂閱的其他區域，官方提醒傳播可能要幾分鐘。</p>
<p>Tenancy 可以另外訂閱其他區域（region subscription），訂閱之後能在那一區建資源，而帳號與身分資源仍然住在 home region。訂閱改變的是資源能放在哪裡，不改變 home region。訂閱的數量有上限：官方〈Regions and Availability Domains〉寫明試用、Free Tier 與 Pay As You Go 的 tenancy 都只能有一個訂閱區域，也就是 home region 本身；升級成付費帳戶（Pay As You Go）之後可以申請提高這個上限；試用與 Free Tier 的 tenancy 不能申請，訂閱區域就只有 home region 一個。</p>
<p><strong>Compartment</strong> 是 tenancy 底下用來分組資源與套用政策的容器。Tenancy 本身就是最上層的 root compartment，新帳號一開始只有它。實驗用的資源另開一個 compartment 放，清理時刪那一個 compartment 底下的資源就好，不會碰到 root 裡的其他設定。</p>
<p><strong>Availability domain</strong>（AD）是一個區域裡彼此隔離的資料中心，跨業者的概念見 <a href="/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）</a>。區域之間的 AD 數量不同：有些區域有三個，有些只有一個。申請之前，各區域的 AD 數列在官方〈Regions and Availability Domains〉頁的區域表；申請之後，<code>oci iam availability-domain list</code>（登入方式見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/" data-link-title="從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰" data-link-desc="OCI CLI 的兩種身分驗證方式——瀏覽器登入換來的 session token 與上傳公鑰的 API 簽章金鑰——各自把什麼存在本機、有效多久、外洩時的影響範圍，以及登入時的互動提示與錯誤訊息怎麼讀">從本機用 OCI CLI 操作 Oracle Cloud</a>）回傳的清單長度就是自己 home region 的 AD 數。AD 數會影響免費機型建不起來時的退路，見〈A1 容量不足時的退路〉。</p>
<h2 id="always-free-的額度與它綁在-home-region-上的範圍">Always Free 的額度與它綁在 home region 上的範圍</h2>
<p>OCI 的免費方案有兩個部分。試用金是 300 美元、30 天內有效，可以用在大部分付費服務上；Always Free 是沒有期限的固定額度，試用結束之後仍然繼續。官方 Free Tier 頁的原文是「After your trial ends, your account remains active. There is no interruption to the availability of the Always Free Resources you have provisioned.」沒有升級成付費帳戶的帳號，試用結束後不會開始扣款；試用期間用試用金建的付費資源，在試用結束時被回收，A1 主機另有一條依合計規格停用的規則，見下方〈試用結束時超出額度〉那一項。試用中、試用結束而沒有升級、付費帳戶這三種狀態下超出額度各自的結果，整理在〈超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果〉。</p>
<p>運算的 Always Free 額度有兩種機型：</p>
<ul>
<li><strong>Ampere A1（<code>VM.Standard.A1.Flex</code>，ARM）</strong>：每月 1,500 OCPU 小時與 9,000 GB 小時的記憶體，相當於 2 OCPU、12 GB 記憶體整個月開著。可以全部給一台，也可以拆成兩台各 1 OCPU。付費 tenancy 的免費量比這個大：2026-10-26 讀到的 Oracle 的 Compute 價目頁（頁名這次沒有記下）寫的是「Each paid tenancy gets the first 3,000 OCPU hours and 18,000 GB hours per month for free」，也就是每月 3,000 OCPU 小時、18,000 GB 小時，相當於 4 OCPU、24 GB 整個月開著。OCI 的 OCPU 是一顆實體核心，跟其他業者以 vCPU 計價時的單位不同，比較時見 <a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較</a>。</li>
<li><strong>AMD Micro（<code>VM.Standard.E2.1.Micro</code>，x86）</strong>：最多兩台，每台 1/8 OCPU、1 GB 記憶體。</li>
</ul>
<p>儲存與網路另有額度：開機磁碟與區塊磁碟合計 200 GB（每台主機的開機磁碟預設 50 GB，從同一份 200 GB 額度裡扣）、備份最多 5 份、出站流量每月 10 TB；試用結束後沒有升級的 Free Tier tenancy，VCN 最多 2 個。預設 50 GB 的開機磁碟實際扣 47 GB 額度，數值是 46.6 GiB 進位（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〉。</p>
<p><strong>運算與區塊磁碟的 Always Free 額度只能用在 home region。</strong> Free Tier 頁寫的是「You can provision Always Free Autonomous AI Databases and compute instances only in the home region」；Always Free Resources 頁對區塊磁碟的原文是「the volume must be created in the home region of the tenancy」。升級成付費帳戶並訂閱其他區域之後，建在 home region 以外那些訂閱區域的主機照一般價格計費。</p>
<p>免費主機只能建在 home region，主機的實體位置因此就是 home region。這個位置決定兩件事。一是出口 IP 的國家：業者配給一台主機的公開 IP 位址，屬於那個區域所在國家的位址段，主機對外連線時，對方看到的來源 IP 就登記在那個國家。二是延遲：從使用者所在地連到主機的延遲，由兩地的距離決定。</p>
<p>Always Free 的主機另有兩條回收規則：</p>
<ul>
<li><strong>閒置回收</strong>：連續 7 天內 CPU 使用率（第 95 百分位）、網路使用率、記憶體使用率（只算 A1）三項指標各自都低於 20%，Oracle 可能回收這台主機。官方頁用的詞是 reclaim，沒有寫回收之後主機是停止還是刪除。只在需要時才登入的實驗機會符合這個條件。</li>
<li><strong>試用結束時超出額度</strong>：Free Tier 頁判斷 A1 是否超額，看的是 tenancy 裡全部 A1 主機的合計（OCPU 加總、記憶體加總），不看單台主機的規格。合計超過 2 OCPU、12 GB 時，試用結束那一刻 tenancy 裡的 A1 主機全部被停用，30 天後刪除，除非升級成付費帳戶。被停用的是每一台 A1 主機，包括單台規格在額度內的那幾台。官方沒有寫已經被停用的主機在升級之後會不會恢復。試用期間超出的部分由試用金支付，帳單上看不到費用，所以 A1 合計超額的後果要到試用結束那一刻才浮現。這個合計要自己加總：到 Compute → Instances 清單逐台記下 A1 主機的 OCPU 與記憶體；清單一次只列一個 compartment，每個放過主機的 compartment 都要切過去看。要保留 A1 主機，試用結束前把合計壓回 2 OCPU、12 GB 以內：把其中幾台的 OCPU 或記憶體改小，或刪掉其中一台。</li>
</ul>
<p>官方對閒置回收只寫了三項指標都要低於 20%，沒有寫網路使用率的 20% 是對什麼的比例，也沒有寫升級成付費帳戶之後這條規則還適不適用；被回收之後主機的狀態與能不能恢復同樣沒寫。回收之後是停止或刪除兩種情形各自留下哪些資源、怎麼發現，以及照原樣重建一台 A1 主機的腳本，見 <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>
<h2 id="a1-容量不足時的退路">A1 容量不足時的退路</h2>
<p>A1 免費機型在某個 AD 暫時沒有容量時，建立主機的 API 回 <code>Out of host capacity</code>。可以做的事有下面這些，出處分別是官方 Always Free 頁與 Compute 的〈Known Issues〉頁；標明「會計費」的幾項不在 Always Free 額度內，結果依帳號狀態而定，見〈超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果〉：</p>
<ul>
<li><strong>換一個 AD</strong>（Always Free 頁）：home region 只有一個 AD 時，這一項不存在。</li>
<li><strong>等一段時間再試</strong>（Always Free 頁）：有人釋出資源時才會短暫空出位置。重送的間隔看回應碼調整：<code>429 TooManyRequests</code> 是請求太頻繁被限流（<a href="/blog/backend/knowledge-cards/rate-limit/" data-link-title="Rate Limit" data-link-desc="說明限流如何保護服務入口、下游依賴與租戶公平性">Rate Limit</a> 卡），收到就拉長間隔，做法見 <a href="/blog/backend/knowledge-cards/exponential-backoff/" data-link-title="Exponential Backoff" data-link-desc="說明重試間隔如何逐步拉長以降低下游壓力">Exponential Backoff</a> 卡；<code>Out of host capacity</code> 才是那個 AD 當下沒有硬體。限流的門檻依帳號與 API 而定。</li>
<li><strong>不指定 fault domain</strong>（Known Issues 頁）：建立頁的 Fault domain 欄選「Let Oracle choose the best fault domain」，不把主機限定在單一個 fault domain 的硬體上。</li>
<li><strong>改小規格</strong>（Known Issues 頁）：同一個機型把 OCPU 或記憶體改小，需要的硬體少，容量比較容易拿到，仍然在免費額度內。</li>
<li><strong>建立前查容量</strong>（Known Issues 頁）：用 <code>CreateComputeCapacityReport</code> 這個 API 查某個機型當下在某個 AD 有沒有容量，查完再送建立請求。</li>
<li><strong>換成 x86 機型</strong>（會計費）：x86 的 Flex 機型不在免費額度內；試用期間用試用金建，當天就有主機可用，代價是這台用試用金建的 x86 主機在試用結束時會被回收。換架構要重新確認軟體有沒有那個架構的版本，見 <a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔</a> 的〈CPU 架構：x86 與 ARM〉。免費的 x86 機型 E2.1.Micro 在一個區域裡只建得在一個 AD 上，2026-10-24 實測的 <code>ap-batam-1</code> 沒有這種機型，出處與實測見〈E2.1.Micro 在各區域的供應：服務配額表與 404 錯誤〉。</li>
<li><strong>改用可被收回的容量（preemptible）</strong>（官方沒有寫它算不算免費額度，這裡按會計費看待）：官方 Always Free 頁與 Free Tier 頁都沒有提到 preemptible。業者需要容量時會直接終止這種主機，價格與限制見 <a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔</a> 的〈計費模式：隨用隨付、可被收回、預留容量與專屬主機〉。</li>
<li><strong>預留容量（capacity reservation）</strong>（會計費）：先向業者保留某種機型的容量，保留期間的計費方式見 <a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔</a> 的〈計費模式：隨用隨付、可被收回、預留容量與專屬主機〉。</li>
<li><strong>升級成付費帳戶</strong>（Always Free 頁）：官方寫升級之後能用的運算資源種類變多（原文「gives you access to more types of Compute resources」）。對容量不足來說，升級的作用是讓上面那幾項會計費的選項（x86 的 Flex 機型、可被收回的容量、預留容量）在試用金用完或試用結束之後仍然建得起來。Always Free 的部分仍然不收費，額度以外建的資源照一般價格計費。</li>
</ul>
<p>Home region 只有一個 AD、而且只用免費額度時，能用的退路是等待重試、不指定 fault domain、改小規格與建立前查容量這四項；換 AD 在這種區域不存在；x86 的 Flex 機型、可被收回的容量與預留容量不在免費額度內，要用試用金或升級成付費帳戶才建得起來。在只有一個 AD 的 <code>ap-batam-1</code> 實際重送的紀錄、OCI CLI 在一條指令裡自己重送造成的限流，以及依回應碼調整間隔的重送腳本，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈重送建立請求：CLI 自己的重送、限流門檻與重送腳本〉。</p>
<h2 id="e21micro-在各區域的供應服務配額表與-404-錯誤">E2.1.Micro 在各區域的供應：服務配額表與 404 錯誤</h2>
<p>E2.1.Micro 是 Always Free 的另一種機型。官方 Always Free 頁對它的供應寫的是 AD 層級：「Instances using the VM.Standard.E2.1.Micro shape can only be created in one availability domain.」也就是一個區域裡只有一個 AD 建得了 Micro，而官方沒有列出是哪一個 AD、哪些區域完全沒有。2026-10-24 在 <code>ap-batam-1</code> 實測，這一區唯一的 AD 沒有 E2.1.Micro，所以在這一區，A1 建不起來時改用 Micro 也建不起來。</p>
<p>主控台的服務配額表（Limits, quotas and usage 頁）上的數字是這個帳號允許開的台數上限，它不反映那一區是否提供那種硬體。這次的帳號在配額表上的 <code>vm-standard-e2-1-micro-count</code> 是 2，而 <code>ap-batam-1</code> 一台 Micro 也建不起來。</p>
<p>在沒有這個機型的區域用 CLI 建立 Micro 主機，API 回的是 <code>404 NotAuthorizedOrNotFound</code>。這個錯誤碼同時涵蓋權限不足與資源不存在，從訊息本身分不出是哪一種。一個區域實際提供哪些機型，用 <code>oci compute shape list --compartment-id &lt;compartment 的 OCID&gt; --availability-domain &lt;AD 名稱&gt;</code> 查，回傳的清單才是那個 AD 能建的機型；AD 名稱要填 <code>oci iam availability-domain list</code> 回傳的完整名稱，它帶有 tenancy 專屬的前綴，只寫 <code>AD-1</code> 會失敗。這條指令要先有 tenancy 才能跑，所以申請之前沒有辦法確認某一區有沒有 E2.1.Micro；申請之後才發現缺機型時，能做的事見〈選錯 home region 之後的選項〉。</p>
<h2 id="超出-always-free-額度時試用中未升級與付費帳戶各自的結果">超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果</h2>
<p>多建一台 A1、加大一顆磁碟、改用不在免費額度內的 x86 機型，這類讓用量超出 Always Free 額度的操作，結果由帳號當下的狀態決定：</p>
<ul>
<li><strong>試用中</strong>：超出的部分由試用金支付，帳單上看不到費用。試用結束時，用試用金建的付費資源會被回收，A1 合計超額時全部 A1 主機會被停用。</li>
<li><strong>試用結束而沒有升級</strong>：官方 Always Free 頁寫明，用不在 Always Free 範圍內的映像建主機，帳號必須是付費帳戶、或是還有試用金餘額的 Free Trial 帳號（原文「you must have a paid account or a Free Trial account with available credits」）；試用結束而沒有升級的帳號兩者都不是，所以超出額度的運算資源建不了，運算以外的資源這一頁沒有逐字寫；Free Tier 頁另外寫明，不升級的話申請時填的信用卡不會被扣款。</li>
<li><strong>升級成付費帳戶（Pay As You Go）</strong>：Always Free 的部分仍然不收費，超出的部分照一般價格計費。區塊磁碟的「超出的部分」以整顆磁碟計：沒有用 compartment quota 另設上限時，合計超過 200 GB 的建立請求不會被擋下，新建而放不進剩餘額度的那一顆整顆不算免費額度（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〉）。A1 的免費量是每月 3,000 OCPU 小時、18,000 GB 小時（相當於 4 OCPU、24 GB 整個月開著），出處是 2026-10-26 讀到的 Oracle 的 Compute 價目頁，見〈Always Free 的額度與它綁在 home region 上的範圍〉的 A1 那一項。</li>
</ul>
<p>站內各篇寫到「超出免費額度」的地方，指的都是這三種結果。升級之前先設好預算警示，見 <a href="/blog/devops/infra/cloud-providers/console-areas/" data-link-title="雲端主控台的管理領域：身分、資源分組、運算、網路、儲存、費用、監控、稽核、安全服務與配額" data-link-desc="雲端業者的主控台依管理需求分成幾個領域：每個領域管什麼、日常在那裡做哪些事、新帳號開好之後先去哪幾個領域看設定，以及 Oracle Cloud、Google Cloud、AWS 各自把它放在選單的哪個名稱底下">雲端主控台的管理領域</a> 的〈帳單與成本：花了多少、會花多少〉；升級時的 100 美元預先授權、扣款的信用卡要留多少額度、預算門檻與配額怎麼從每月能接受的上限推出來，見 <a href="/blog/devops/operations/08-cost-management/spend-planning-before-renting/" data-link-title="租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層" data-link-desc="租用雲端主機或把試用帳號升級成付費帳戶之前，費用失控時從觸發到停止之間的金額怎麼從配額與訊號延遲推出來，再用它推扣款的信用卡額度、配額、預算警示與觸發處置的門檻；Oracle Cloud、Google Cloud 與 AWS 的扣款時點、預先授權、扣款失敗的後果、擋下建立與停止用量的機制各自涵蓋什麼，以及停損停到哪一層的判斷">租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層</a>。</p>
<h2 id="選-home-region-時要先確認的事">選 home region 時要先確認的事</h2>
<p>Home region 在申請表上是一個下拉選單，選定的區域同時決定了免費主機的實體位置（出口 IP 的國家、延遲）與容量不足時有沒有別的 AD 可以換。選之前先確認這台免費主機要做什麼：</p>
<ul>
<li><strong>用途需要特定國家的出口 IP</strong>（例如要讓連線看起來從日本發出）：只有那個國家的區域符合，其他區域的延遲再低都達不到目的。以日本為例，這個條件與下方「主要拿來實驗」那一條的「選 AD 多的區域」同時滿足不了：日本的兩個區域（東京 <code>ap-tokyo-1</code>、大阪 <code>ap-osaka-1</code>）都只有一個 AD，選了日本，A1 容量不足時就沒有別的 AD 可以換，而 E2.1.Micro 有沒有提供要申請之後才查得到。兩者只能取一個：日本的出口 IP 是硬條件時，選日本的區域，接受 A1 容量不足時無處可換；出口 IP 只是加分項時，選 AD 多的區域，放棄日本的出口 IP。東京與大阪之間，官方沒有公開各區的容量資訊，選哪一個只能看延遲。預算為零而容量一直不足時，能用的退路見〈A1 容量不足時的退路〉最後一段。</li>
<li><strong>用途是給特定地區的使用者連</strong>：選離使用者近的區域，延遲由距離決定。</li>
<li><strong>主要拿來實驗</strong>：優先選 AD 多的區域（官方區域表上 AD 數為 3 的那幾個，名單見 <a href="/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）</a> 卡），A1 容量不足時有其他 AD 可以換。AD 多代表的是多了可以換的地方，不代表那一區的 A1 空閒容量比較多。</li>
<li><strong>資料要留在特定國家</strong>（法規或合約要求）：只有那個國家的區域符合，跟出口 IP 那一條同樣是硬條件。</li>
</ul>
<h2 id="選錯-home-region-之後的選項">選錯 home region 之後的選項</h2>
<p>Home region 不能改；官方有刪除 tenancy 的流程（〈<a href="https://docs.oracle.com/en-us/iaas/Content/General/Tasks/deleting_tenancy_freetier.htm">Deleting a Free Tier Tenancy and Cloud Account</a>〉：管理員在 Tenancy Details 頁按「Request tenancy deletion」），刪除後資源先被暫停、30 天後永久刪除且無法復原，而官方文件沒有寫明刪除之後能不能用同一個 email 與付款方式重新申請。走這條路之前，先開 support ticket 問清楚重新申請的條件。不刪 tenancy 的做法是保留原帳號、在想要的區域另建付費主機：管理員先把帳號升級成 Pay As You Go，再到 Limits, quotas and usage 頁申請提高訂閱區域的上限（Subscribed region count）；核准之後訂閱想要的區域，在那一區建的主機照一般價格計費。Always Free 額度仍然只能用在原本的 home region。</p>
<h2 id="主控台登入租用戶與使用者名稱兩個欄位與使用者名稱或密碼無效">主控台登入：租用戶與使用者名稱兩個欄位，與「使用者名稱或密碼無效」</h2>
<p>OCI 的登入頁把登入拆成兩個欄位，依序出現：</p>
<ol>
<li><strong>租用戶（Cloud Account Name）</strong>：填 tenancy 的名稱，也就是申請時取的帳戶名稱。OCI 用它找到要登入的是哪一個 tenancy。已經登入的主控台上，右上角人像選單的「租用戶」或「Tenancy:」後面就是這個值。</li>
<li><strong>使用者名稱</strong>：填 tenancy 裡那個使用者的名稱。申請時建立的管理員帳號，使用者名稱是申請用的 email。頁面要求選 identity domain（tenancy 裡存放使用者與群組的分區）時選 <code>Default</code>，新帳號只有這一個。</li>
</ol>
<p>兩個欄位任一個填錯，登入頁顯示的都是同一句「使用者名稱或密碼無效」，不會指出是哪一欄錯。實際登入時，租用戶欄填對、使用者名稱欄填成 tenancy 名稱，得到的就是這句訊息，密碼本身沒有錯。從本機用 <code>oci session authenticate</code> 登入時開出的瀏覽器頁面是同一個登入頁，欄位與錯誤訊息相同。</p>
<h2 id="延伸閱讀cli-登入免費額度比較位置與管理通道">延伸閱讀：CLI 登入、免費額度比較、位置與管理通道</h2>
<p>同一個 tenancy 從終端機登入的做法，以及 session token 與 API 簽章金鑰各自的時效，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/" data-link-title="從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰" data-link-desc="OCI CLI 的兩種身分驗證方式——瀏覽器登入換來的 session token 與上傳公鑰的 API 簽章金鑰——各自把什麼存在本機、有效多久、外洩時的影響範圍，以及登入時的互動提示與錯誤訊息怎麼讀">從本機用 OCI CLI 操作 Oracle Cloud</a>；Always Free 的 home region 限制跟 Google Cloud、AWS 的區域限制並排，見 <a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較</a>；home region 這一層之下，可用區與故障域在建立主機時各決定什麼，見 <a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a>；compartment 這種資源分組在三家主控台上的對應，見 <a href="/blog/devops/infra/cloud-providers/console-areas/" data-link-title="雲端主控台的管理領域：身分、資源分組、運算、網路、儲存、費用、監控、稽核、安全服務與配額" data-link-desc="雲端業者的主控台依管理需求分成幾個領域：每個領域管什麼、日常在那裡做哪些事、新帳號開好之後先去哪幾個領域看設定，以及 Oracle Cloud、Google Cloud、AWS 各自把它放在選單的哪個名稱底下">雲端主控台的管理領域</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>。</p>
]]></content:encoded></item><item><title>建立雲端虛擬機的選項</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/</guid><description>&lt;p>建立虛擬機的頁面把三十幾個欄位排在一起，而它們分屬幾類互不相干的功能。這個資料夾一類功能一篇，每篇涵蓋的項目見〈各篇共用的寫法〉。&lt;/p>
&lt;h2 id="章節文章">章節文章&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;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域&lt;/a>&lt;/td>
 &lt;td>主機的實體位置：延遲、出口 IP 的國家、可用機型、硬體故障時的影響範圍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔&lt;/a>&lt;/td>
 &lt;td>CPU 架構與核心、記憶體大小，計費與可被收回的條件，作業系統與架構相容性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&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;/td>
 &lt;td>主機在網路上的位置，誰連得到它、它連得到誰&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&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;/td>
 &lt;td>建好之後管理員用什麼身分、什麼金鑰登入，沒有金鑰時還有哪些入口&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留&lt;/a>&lt;/td>
 &lt;td>資料放在哪、能放多少、多快，刪除主機時資料會不會跟著刪&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&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;/td>
 &lt;td>業者裝在主機裡的程式做什麼，主機怎麼取得自己的設定，硬體維護時會怎樣&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機：Secure Boot、Measured Boot 與 TPM&lt;/a>&lt;/td>
 &lt;td>開機過程有沒有被竄改的檢查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/pre-launch-checks/" data-link-title="建立主機之前的檢查：建好就改不了的設定、登入路徑上各服務的支援組合、兩條登入路徑各自依賴的元件，以及容量稀缺時的取捨" data-link-desc="在建立頁按下建立之前，把位置、機型與映像檔、網路、登入、安全開機各篇的選項串成幾項檢查：哪些設定建立之後只能重建、怎麼查，登入用的業者服務各自支援哪些機型與作業系統、元件狀態正常為什麼不代表支援，兩條登入路徑各自依賴哪些元件、哪些仍然共用，以及機型容量難取得時設定怎麼選；Oracle Cloud、Google Cloud 與 AWS 各自查這些事的位置">建立主機之前的檢查&lt;/a>&lt;/td>
 &lt;td>把各篇的「建立後能不能改」與登入條件串成按下建立之前的檢查&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="各篇共用的寫法">各篇共用的寫法&lt;/h2>
&lt;p>每一篇都寫這幾項：這一類選項是什麼、建立後能不能改（改不了的才需要在建立頁上想清楚）、對費用的影響、選錯時的徵兆、實驗用的小主機怎麼選，結尾是三家的欄位名稱對照。維護時照這一份清單檢查各篇。主要例子取自 Oracle Cloud（實測在那一家），寫通則句之前先確認另兩家的行為，只對一家成立的句子在句首寫明業者。各家的實測紀錄放在業者子資料夾底下的 &lt;code>hands-on/&lt;/code>，例如 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄&lt;/a>，那一篇照主控台的分頁順序，每一欄連回這裡對應的那一篇。&lt;/p>
&lt;h2 id="backlog">Backlog&lt;/h2>
&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;/td>
 &lt;td>案例&lt;/td>
 &lt;td>A1 主機建得起來&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>開機磁碟線上加大、在作業系統裡擴充分割區的實測&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>A1 主機建得起來&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Boot Volume、Instance Shape 知識卡&lt;/td>
 &lt;td>知識卡&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>2 張卡（小）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>〈建立主機之前的檢查〉的反向連結：shape-and-image 的架構不能換與容量不足、management 的初始化腳本（Oracle Cloud 的 user_data 建立後不能改）、management-access-channel 的切換順序與救援帳號、serial-console 卡的救援帳號要在建立時用初始化腳本建，各補一句連到那一篇&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>〈建立主機之前的檢查〉補建立之前查得到的支援來源：Oracle Cloud 依作業系統列出可用 plugin 的 CLI、Google Cloud〈Operating system details〉頁，實跑或讀過原文再寫&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>建立虛擬機的頁面把三十幾個欄位排在一起，而它們分屬幾類互不相干的功能。這個資料夾一類功能一篇，每篇涵蓋的項目見〈各篇共用的寫法〉。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>這一類選項決定什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a></td>
          <td>主機的實體位置：延遲、出口 IP 的國家、可用機型、硬體故障時的影響範圍</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔</a></td>
          <td>CPU 架構與核心、記憶體大小，計費與可被收回的條件，作業系統與架構相容性</td>
      </tr>
      <tr>
          <td><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></td>
          <td>主機在網路上的位置，誰連得到它、它連得到誰</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a></td>
          <td>建好之後管理員用什麼身分、什麼金鑰登入，沒有金鑰時還有哪些入口</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留</a></td>
          <td>資料放在哪、能放多少、多快，刪除主機時資料會不會跟著刪</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a></td>
          <td>業者裝在主機裡的程式做什麼，主機怎麼取得自己的設定，硬體維護時會怎樣</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機：Secure Boot、Measured Boot 與 TPM</a></td>
          <td>開機過程有沒有被竄改的檢查</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/vm-options/pre-launch-checks/" data-link-title="建立主機之前的檢查：建好就改不了的設定、登入路徑上各服務的支援組合、兩條登入路徑各自依賴的元件，以及容量稀缺時的取捨" data-link-desc="在建立頁按下建立之前，把位置、機型與映像檔、網路、登入、安全開機各篇的選項串成幾項檢查：哪些設定建立之後只能重建、怎麼查，登入用的業者服務各自支援哪些機型與作業系統、元件狀態正常為什麼不代表支援，兩條登入路徑各自依賴哪些元件、哪些仍然共用，以及機型容量難取得時設定怎麼選；Oracle Cloud、Google Cloud 與 AWS 各自查這些事的位置">建立主機之前的檢查</a></td>
          <td>把各篇的「建立後能不能改」與登入條件串成按下建立之前的檢查</td>
      </tr>
  </tbody>
</table>
<h2 id="各篇共用的寫法">各篇共用的寫法</h2>
<p>每一篇都寫這幾項：這一類選項是什麼、建立後能不能改（改不了的才需要在建立頁上想清楚）、對費用的影響、選錯時的徵兆、實驗用的小主機怎麼選，結尾是三家的欄位名稱對照。維護時照這一份清單檢查各篇。主要例子取自 Oracle Cloud（實測在那一家），寫通則句之前先確認另兩家的行為，只對一家成立的句子在句首寫明業者。各家的實測紀錄放在業者子資料夾底下的 <code>hands-on/</code>，例如 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a>，那一篇照主控台的分頁順序，每一欄連回這裡對應的那一篇。</p>
<h2 id="backlog">Backlog</h2>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>類型</th>
          <th>前置條件</th>
          <th>規模</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>機型調整（改核心數與記憶體）要不要重開機的實測</td>
          <td>案例</td>
          <td>A1 主機建得起來</td>
          <td>小</td>
      </tr>
      <tr>
          <td>開機磁碟線上加大、在作業系統裡擴充分割區的實測</td>
          <td>案例</td>
          <td>A1 主機建得起來</td>
          <td>小</td>
      </tr>
      <tr>
          <td>Boot Volume、Instance Shape 知識卡</td>
          <td>知識卡</td>
          <td>無</td>
          <td>2 張卡（小）</td>
      </tr>
      <tr>
          <td>〈建立主機之前的檢查〉的反向連結：shape-and-image 的架構不能換與容量不足、management 的初始化腳本（Oracle Cloud 的 user_data 建立後不能改）、management-access-channel 的切換順序與救援帳號、serial-console 卡的救援帳號要在建立時用初始化腳本建，各補一句連到那一篇</td>
          <td>主章</td>
          <td>無</td>
          <td>小</td>
      </tr>
      <tr>
          <td>〈建立主機之前的檢查〉補建立之前查得到的支援來源：Oracle Cloud 依作業系統列出可用 plugin 的 CLI、Google Cloud〈Operating system details〉頁，實跑或讀過原文再寫</td>
          <td>主章</td>
          <td>無</td>
          <td>小</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shape-and-image/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shape-and-image/</guid><description>&lt;p>映像檔的 CPU 架構必須跟機型一致，所以建立頁上決定主機「是什麼樣的電腦」的兩組選項要一起決定：機型（CPU 架構、核心數與記憶體），以及開機磁碟上預先裝好的作業系統，也就是映像檔。同一個機型取得容量的方式（計費模式）也在這裡選。文中各家機型的規格、限制與計費規則取自官方文件，查證日期 2026-10-24。&lt;/p>
&lt;h2 id="cpu-架構x86-與-arm">CPU 架構：x86 與 ARM&lt;/h2>
&lt;p>機型的第一個屬性是 CPU 架構，目前雲端上有兩種：x86（Intel、AMD）與 ARM（Oracle Cloud 的 Ampere A1、AWS 的 Graviton、Google Cloud 的 Axion 與 Tau T2A）。架構決定主機能執行哪些程式：&lt;/p>
&lt;ul>
&lt;li>作業系統與套件庫提供兩種架構的版本，套件管理員（&lt;code>apt&lt;/code>、&lt;code>dnf&lt;/code>）依主機的架構下載對應的套件。&lt;/li>
&lt;li>只發佈 x86 版本的程式（部分商用軟體、舊版工具、只建了 &lt;code>amd64&lt;/code> 映像的容器）在 ARM 主機上跑不起來，或要靠模擬執行而變慢。容器最常在這裡出事：在 x86 的筆電或 CI 上 build 出來的映像預設只有 &lt;code>amd64&lt;/code>，本機測試全部通過，拿到 A1 主機上啟動才出現 &lt;code>exec format error&lt;/code>。在 x86 機器上一次 build 出 &lt;code>amd64&lt;/code> 與 &lt;code>arm64&lt;/code> 兩種映像的做法見 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/vendors/docker/buildkit-cross-platform/" data-link-title="BuildKit 與跨平台 build" data-link-desc="docker build 每次重下載套件很慢、要同時出 amd64 與 arm64 image、或 build 時要用私有存取憑證卻不想烤進 image 時回來讀 — BuildKit 的 cache/secret mount 與 buildx 跨平台 build">BuildKit 與跨平台 build&lt;/a> 的〈跨平台 build：一次出多架構〉。&lt;/li>
&lt;/ul>
&lt;p>ARM 機型的同等規格通常比較便宜，Oracle Cloud 的 Always Free 額度大部分也在 ARM（A1）上。要跑的軟體確認有 ARM 版本時，ARM 是比較划算的選擇；不確定時，先查那個軟體的下載頁或容器映像的平台清單有沒有 &lt;code>arm64&lt;/code>。&lt;/p>
&lt;p>架構在建立之後不能換，因為開機磁碟上的作業系統是為那個架構編譯的，換架構只能用另一個架構的映像檔重建。用 IaC 管理主機時，改映像檔這一類屬性在 Terraform 的 plan 上顯示成先刪後建（forces replacement），主機與它的開機磁碟會被重建，見 &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 plan / apply&lt;/a>。&lt;/p>
&lt;h2 id="核心數與記憶體">核心數與記憶體&lt;/h2>
&lt;p>固定規格的機型名稱本身就決定了核心數與記憶體（AWS 的 &lt;code>t3.micro&lt;/code>、Google Cloud 的 &lt;code>e2-micro&lt;/code>）。彈性機型（Oracle Cloud 名稱以 &lt;code>.Flex&lt;/code> 結尾的 shape、Google Cloud 的自訂機型）讓使用者在範圍內自己填核心數與記憶體，而記憶體能填的範圍隨核心數變動。&lt;/p>
&lt;p>各家計算核心的單位不同。vCPU 是作業系統看到的一個虛擬處理器；Oracle Cloud 用 OCPU，官方把一個 OCPU 定義為一顆實體核心；Oracle Cloud 的 x86 機型（AMD、Intel）1 OCPU 等於 2 個 vCPU，因為多數 x86 處理器的一顆核心跑兩個硬體執行緒，Ampere A1 的 1 OCPU 等於 1 個 vCPU；其他兩家直接用 vCPU。x86 裡每核只跑一個執行緒的例外機型與 Oracle Cloud 較新的 Arm 機型怎麼換算，見 &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> 的〈計費單位的換算：OCPU、vCPU 與機型配額〉。共享核心機型（Google Cloud 的 &lt;code>e2-micro&lt;/code>）作業系統看得到 2 個 vCPU，持續使用量卻只有 0.25 個，三家規格放在一起比較時的換算也在同一節。&lt;/p>
&lt;p>核心數與記憶體建立之後可以調整，代價是一段停機。Oracle Cloud 的官方說明是主機在執行中時，改機型的過程會把它重開，私有 IP、公開 IP 與掛載的磁碟維持不變；改之前在作業系統裡先把應用程式正常關掉，避免寫到一半的資料損壞。官方的主控台步驟另外註明，主機掛了兩顆以上的區塊磁碟時（原文是 more than one block volume attached），要先把區塊磁碟卸下才能改機型。Google Cloud 與 AWS 要先把主機停止才能換機型，換完再啟動；AWS 停止再啟動時，自動指派的公開 IP 會換成新的。能調整的機型也有限：Oracle Cloud 的 &lt;code>VM.Standard.E2.1.Micro&lt;/code> 不能調整，開了安全開機（shielded instance）的主機也不能改機型，A1 Flex 可以在 Always Free 額度內調整核心數與記憶體。&lt;/p></description><content:encoded><![CDATA[<p>映像檔的 CPU 架構必須跟機型一致，所以建立頁上決定主機「是什麼樣的電腦」的兩組選項要一起決定：機型（CPU 架構、核心數與記憶體），以及開機磁碟上預先裝好的作業系統，也就是映像檔。同一個機型取得容量的方式（計費模式）也在這裡選。文中各家機型的規格、限制與計費規則取自官方文件，查證日期 2026-10-24。</p>
<h2 id="cpu-架構x86-與-arm">CPU 架構：x86 與 ARM</h2>
<p>機型的第一個屬性是 CPU 架構，目前雲端上有兩種：x86（Intel、AMD）與 ARM（Oracle Cloud 的 Ampere A1、AWS 的 Graviton、Google Cloud 的 Axion 與 Tau T2A）。架構決定主機能執行哪些程式：</p>
<ul>
<li>作業系統與套件庫提供兩種架構的版本，套件管理員（<code>apt</code>、<code>dnf</code>）依主機的架構下載對應的套件。</li>
<li>只發佈 x86 版本的程式（部分商用軟體、舊版工具、只建了 <code>amd64</code> 映像的容器）在 ARM 主機上跑不起來，或要靠模擬執行而變慢。容器最常在這裡出事：在 x86 的筆電或 CI 上 build 出來的映像預設只有 <code>amd64</code>，本機測試全部通過，拿到 A1 主機上啟動才出現 <code>exec format error</code>。在 x86 機器上一次 build 出 <code>amd64</code> 與 <code>arm64</code> 兩種映像的做法見 <a href="/blog/backend/05-deployment-platform/vendors/docker/buildkit-cross-platform/" data-link-title="BuildKit 與跨平台 build" data-link-desc="docker build 每次重下載套件很慢、要同時出 amd64 與 arm64 image、或 build 時要用私有存取憑證卻不想烤進 image 時回來讀 — BuildKit 的 cache/secret mount 與 buildx 跨平台 build">BuildKit 與跨平台 build</a> 的〈跨平台 build：一次出多架構〉。</li>
</ul>
<p>ARM 機型的同等規格通常比較便宜，Oracle Cloud 的 Always Free 額度大部分也在 ARM（A1）上。要跑的軟體確認有 ARM 版本時，ARM 是比較划算的選擇；不確定時，先查那個軟體的下載頁或容器映像的平台清單有沒有 <code>arm64</code>。</p>
<p>架構在建立之後不能換，因為開機磁碟上的作業系統是為那個架構編譯的，換架構只能用另一個架構的映像檔重建。用 IaC 管理主機時，改映像檔這一類屬性在 Terraform 的 plan 上顯示成先刪後建（forces replacement），主機與它的開機磁碟會被重建，見 <a href="/blog/devops/infra/knowledge-cards/terraform-plan-apply/" data-link-title="terraform plan / apply" data-link-desc="IaC 的兩個核心操作：plan 只看不動（產出差異報告）、apply 真的動（執行差異）">terraform plan / apply</a>。</p>
<h2 id="核心數與記憶體">核心數與記憶體</h2>
<p>固定規格的機型名稱本身就決定了核心數與記憶體（AWS 的 <code>t3.micro</code>、Google Cloud 的 <code>e2-micro</code>）。彈性機型（Oracle Cloud 名稱以 <code>.Flex</code> 結尾的 shape、Google Cloud 的自訂機型）讓使用者在範圍內自己填核心數與記憶體，而記憶體能填的範圍隨核心數變動。</p>
<p>各家計算核心的單位不同。vCPU 是作業系統看到的一個虛擬處理器；Oracle Cloud 用 OCPU，官方把一個 OCPU 定義為一顆實體核心；Oracle Cloud 的 x86 機型（AMD、Intel）1 OCPU 等於 2 個 vCPU，因為多數 x86 處理器的一顆核心跑兩個硬體執行緒，Ampere A1 的 1 OCPU 等於 1 個 vCPU；其他兩家直接用 vCPU。x86 裡每核只跑一個執行緒的例外機型與 Oracle Cloud 較新的 Arm 機型怎麼換算，見 <a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較</a> 的〈計費單位的換算：OCPU、vCPU 與機型配額〉。共享核心機型（Google Cloud 的 <code>e2-micro</code>）作業系統看得到 2 個 vCPU，持續使用量卻只有 0.25 個，三家規格放在一起比較時的換算也在同一節。</p>
<p>核心數與記憶體建立之後可以調整，代價是一段停機。Oracle Cloud 的官方說明是主機在執行中時，改機型的過程會把它重開，私有 IP、公開 IP 與掛載的磁碟維持不變；改之前在作業系統裡先把應用程式正常關掉，避免寫到一半的資料損壞。官方的主控台步驟另外註明，主機掛了兩顆以上的區塊磁碟時（原文是 more than one block volume attached），要先把區塊磁碟卸下才能改機型。Google Cloud 與 AWS 要先把主機停止才能換機型，換完再啟動；AWS 停止再啟動時，自動指派的公開 IP 會換成新的。能調整的機型也有限：Oracle Cloud 的 <code>VM.Standard.E2.1.Micro</code> 不能調整，開了安全開機（shielded instance）的主機也不能改機型，A1 Flex 可以在 Always Free 額度內調整核心數與記憶體。</p>
<p>既然改大只要一段停機、不必重建，實驗機先開小一點、不夠再加；前提是改大的時候那一區還有容量，A1 這種容量經常不足的機型，在建得起來的那一刻就要把規格想好；要在改機型之前確認新機型當下有沒有容量，Oracle Cloud 的改機型說明寫明用 <code>CreateComputeCapacityReport</code> 這個 API 查。付費機型開小直接省錢，Always Free 的 A1（每月額度相當於 2 OCPU、12 GB 整個月開著，見 <a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較</a>）則是把剩下的額度留給第二台主機。「不夠」的訊號在主機的指標上：CPU 使用率長時間接近滿載、或記憶體用完開始用 swap、程式被 OOM 終止。</p>
<p>某個機型在一個區域建不起來（容量不足）時，本篇處理的是改小規格、換架構與改用可被收回的容量，代價看選哪一種；其餘的退路（換可用區、等待重試、不指定故障域、建立前查容量、預留容量、升級付費帳戶）與這幾種排在一起的完整清單，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈A1 容量不足時的退路〉。在同一種機型裡改小規格，容量比較容易拿到，免費額度也不變；換到另一個架構（例如 A1 建不起來改用 x86 的 Flex 機型），要重新確認軟體有沒有那個架構的版本，而且 Oracle Cloud 上 x86 的 Flex 機型不在 Always Free 額度內，試用期間由試用金支付、當天就建得起來，試用結束之後的結果依帳號狀態而定，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果〉；改用可被收回的容量，價格低，但主機隨時可能被收回，而官方 Always Free 頁沒有寫這種容量是否算在免費額度內。換可用區的部分見 <a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a>。</p>
<h2 id="計費模式隨用隨付可被收回預留容量與專屬主機">計費模式：隨用隨付、可被收回、預留容量與專屬主機</h2>
<p>同一個機型可以用不同的方式取得容量，差別在價格與「業者能不能把這台主機收回去」。Oracle Cloud 建立頁的 Capacity type 欄位列出這幾種：</p>
<ul>
<li><strong>隨用隨付（on-demand）</strong>：預設值。在共享的實體主機上開一台虛擬機，開著的時間計費，業者不會因為別人要用而收回它。Oracle Cloud 的官方 Always Free 頁沒有提到其他計費模式，在 Always Free 額度內建主機時維持這個預設。</li>
<li><strong>可被收回（preemptible）</strong>：同樣的機型，價格較低，而業者需要容量時會直接終止它。Oracle Cloud 的 preemptible 是隨用隨付價格的一半，沒有保證的最短執行時間，終止前兩分鐘發出一個事件通知，建立後不能停止、啟動或重開，也不能轉成隨用隨付。Google Cloud 與 AWS 的同類機型叫 Spot，規則各自不同，例如 Google Cloud 的 Spot VM 被收回之後可以再啟動。適合跑得斷、可以重來的批次工作；要一直開著的服務與手動設定過的實驗機不適合，被收回時設定會跟著消失。</li>
<li><strong>預留容量（capacity reservation）</strong>：先向業者保留一定數量的某種機型容量，之後建主機時從保留的額度裡扣。它處理的是「要用的時候沒有容量」，用在必須在某個時間點拿到主機的情境：大促前要擴容、災難時要在另一區重建、熱門機型經常容量不足。保留期間不論有沒有主機在用都計費。大促前什麼情形才需要預留容量，以及預留期間的費用怎麼從建立預留那一刻算進活動預算，見 <a href="/blog/devops/operations/05-capacity-planning/planned-peak-sizing-and-budget/" data-link-title="預知的大流量要加多大、編多少預算：從業務預估到擴容規格與費用區間的換算鏈、給決策者與會計的數字，以及每次活動之後的校準" data-link-desc="行銷活動、檔期這類事先知道的大流量，容量規劃怎麼從業務預估換算成峰值請求量、各層要開幾台與擴容的計費時數，再換算成費用區間；三點估計怎麼帶著預估的誤差，給決策者的選項要附規格、費用、不足時的後果與回退方式，給會計的預算要對齊增量與付款時點，以及每次活動之後校準換算係數、沒有歷史資料時怎麼起步">預知的大流量要加多大、編多少預算：從業務預估到擴容規格與費用區間的換算鏈、給決策者與會計的數字，以及每次活動之後的校準</a>。</li>
<li><strong>專屬主機（dedicated host）</strong>：整台實體主機只給這個帳號的虛擬機用，不跟其他客戶共用硬體，用在合規要求或授權按實體主機計算的軟體。Oracle Cloud 只允許在建立時把主機放上專屬主機。</li>
</ul>
<p>Oracle Cloud 的 Capacity type 欄位一次只能選其中一種；AWS 的 capacity reservation 則是讓隨用隨付的主機去使用保留下來的容量，兩者同時成立。預留容量跟用一到三年的承諾換取折扣的 reserved instance 是兩件事：預留容量保證拿得到容量、不打折；AWS 的 reserved instance 打折，而保不保證容量看範圍，以區域為範圍的 regional reserved instance 不保留容量，指定可用區的 zonal reserved instance 另外保留那一區的容量。Reserved instance 與 savings plan 的承諾差異、什麼時候值得買，見 <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>
<h2 id="停止主機時的計費">停止主機時的計費</h2>
<p>停止（stop）跟終止（terminate）是兩件事：停止是關機，主機與磁碟都保留，之後可以再開；終止是刪除主機。Oracle Cloud 的標準機型在停止期間暫停計算資源的計費，但仍然佔用服務配額（2026-10-27 一台 A1 停止的那一小時，逐小時的帳務明細沒有 A1 的項目，而 A1 的核心數與記憶體用量照算，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/suspend-resume-drill/" data-link-title="Oracle Cloud 主機的暫停與恢復上線實測紀錄：暫停前的基準、收窄入口、停止主機、停止期間的計費與 Cloudflare 的回應、恢復上線的每一步與耗時，以及收尾時 DNS 紀錄與主機的刪除順序" data-link-desc="要讓 Oracle Cloud 上一台放在 Cloudflare 後面的主機停機一段時間再恢復上線、想知道停止之後公開 IP 留不留、停止期間 A1 與開機磁碟怎麼計費、Cloudflare 回什麼錯誤、恢復時每一步要多久，或服務要收掉時 DNS 紀錄與主機的刪除順序時，查實際跑過的指令、輸出與量到的時間">Oracle Cloud 主機的暫停與恢復上線實測紀錄：暫停前的基準、收窄入口、停止主機、停止期間的計費與 Cloudflare 的回應、恢復上線的每一步與耗時，以及收尾時 DNS 紀錄與主機的刪除順序</a>）；Dense I/O、HPC 與多數 GPU 機型停止時照樣計費，要終止才會停。官方另外提醒，在作業系統裡下 <code>shutdown</code> 不會停止計費，要從主控台或 API 停止主機。Google Cloud 與 AWS 的規則方向相同：停止期間不收 vCPU 與記憶體的費用，磁碟與保留的公開 IP 照樣計費。磁碟的計費跟主機是否開著無關，見 <a href="/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留</a>。服務要暫停一段時間時，在只停服務、停止主機、終止主機與全部刪除之間怎麼選，以及停止之後仍在計費、仍被 DNS 指向的資源，見 <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>。</p>
<h2 id="映像檔開機磁碟上的作業系統">映像檔：開機磁碟上的作業系統</h2>
<p>映像檔是建立主機時複製到開機磁碟上的作業系統內容。主控台的映像檔清單通常分成三種來源：</p>
<ul>
<li><strong>業者提供的平台映像（platform image）</strong>：Ubuntu、Oracle Linux、Debian、Windows 等，由業者維護並預先裝好它自己的代理程式（agent，見 <a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a>）。實驗與一般用途選這一種。</li>
<li><strong>市集映像（marketplace）</strong>：第三方預先裝好特定軟體的映像，部分要另外付授權費。</li>
<li><strong>自訂映像（custom image）</strong>：從自己的主機或開機磁碟做出來的映像，用來複製已經設定好的環境。</li>
</ul>
<p>同一個作業系統常有完整版與 minimal 版。Minimal 版只裝開機與連網需要的套件，開機磁碟用量小、要修補的套件少，代價是常用工具（文字編輯器、部分診斷工具）要自己裝。要登入主機手動操作、排錯的實驗機選完整版；只跑容器或由初始化腳本、<a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> 裝好全部軟體、平常不登入的主機選 minimal 版。</p>
<p>映像檔要跟機型的架構一致：Oracle Cloud 的映像清單上，Ubuntu 的 ARM 版名稱帶 <code>aarch64</code>、x86 版不帶；AWS 與 Google Cloud 的映像多半以 <code>arm64</code> 標示 ARM 版，名稱看不出時看映像詳細資料裡的架構欄位。Oracle Cloud 的主控台只列出這一區有的機型，並把跟已選映像檔不相容的標成不可選；用 CLI 或 API 建立時沒有這層畫面上的提示，映像檔要自己挑對架構。查詢映像檔的指令是 <code>oci compute image list --compartment-id &lt;tenancy 的 OCID&gt; --shape &lt;機型名稱&gt;</code>：Oracle Cloud 給每個資源一個唯一識別碼叫 OCID，平台映像列在 tenancy（帳號本身，也是最上層的 compartment）底下，所以 <code>--compartment-id</code> 填 tenancy 的 OCID；<code>--shape</code> 填機型名稱（例如 <code>VM.Standard.A1.Flex</code>），CLI 只回傳跟那個機型相容的映像檔。tenancy 的 OCID 在哪裡查見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a>。映像檔也決定了預設的登入使用者名稱，見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a>。</p>
<p>映像檔只在建立的那一刻複製一次。之後作業系統的更新由主機自己的套件管理員處理，跟映像檔清單上出現的新版本無關；換作業系統同樣要用新的映像檔重建。</p>
<h2 id="各家的欄位名稱機型可被收回預留容量與映像檔">各家的欄位名稱：機型、可被收回、預留容量與映像檔</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>機型</td>
          <td>Shape</td>
          <td>Machine type</td>
          <td>Instance type</td>
      </tr>
      <tr>
          <td>彈性規格</td>
          <td>Flex shape</td>
          <td>Custom machine type</td>
          <td>無（選固定規格）</td>
      </tr>
      <tr>
          <td>可被收回</td>
          <td>Preemptible capacity</td>
          <td>Spot VM</td>
          <td>Spot Instance</td>
      </tr>
      <tr>
          <td>預留容量</td>
          <td>Capacity reservation</td>
          <td>Reservation</td>
          <td>Capacity Reservation</td>
      </tr>
      <tr>
          <td>映像檔</td>
          <td>Image</td>
          <td>Image</td>
          <td><a href="/blog/devops/infra/knowledge-cards/ami/" data-link-title="AMI" data-link-desc="EC2 instance 的作業系統映像快照，包含 OS、軟體、設定與磁碟內容，從 AMI 開出的 instance 跟原始狀態一樣">AMI</a>（Amazon Machine Image）</td>
      </tr>
  </tbody>
</table>
<p>映像檔複製到的那顆開機磁碟能設定的大小、效能與加密，見 <a href="/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留</a>；一台 Always Free 的 A1 Ubuntu 主機實際選的規格與映像檔，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的 Basic information 段。</p>
]]></content:encoded></item><item><title>從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/</guid><description>&lt;p>這篇整理在 macOS 上安裝 OCI CLI 之後，讓它取得操作 tenancy（Oracle Cloud 的帳號）的身分的兩種做法。範圍從 &lt;code>oci&lt;/code> 指令裝好開始，到第一條查詢指令回傳資料為止；macOS 上的安裝是 &lt;code>brew install oci-cli&lt;/code>，裝好後 &lt;code>oci --version&lt;/code> 印出版本號，Linux 與 Windows 的安裝方式見官方〈Installing the CLI〉。不想在本機安裝的話，主控台頂端列的 Cloud Shell 是一個已經裝好 CLI、以目前登入身分驗證好的瀏覽器終端機，本篇的登入步驟在那裡都不需要。tenancy、home region 與主控台登入欄位見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region&lt;/a>。實測環境是 Homebrew 安裝的 OCI CLI 3.95.1，日期 2026-10-24。&lt;/p>
&lt;h2 id="cli-的兩種身分驗證session-token-與-api-簽章金鑰">CLI 的兩種身分驗證：session token 與 API 簽章金鑰&lt;/h2>
&lt;p>OCI 的 API 不收帳號密碼。CLI 送出的每一個請求都用一把私鑰簽章，OCI 用對應的公鑰驗證簽章、再查這把公鑰屬於哪個使用者，以那個使用者的權限執行請求。兩種做法的差別在這對金鑰從哪裡來、公鑰怎麼讓 OCI 認得：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Session token&lt;/strong>：CLI 在本機產生一對臨時金鑰，開瀏覽器讓使用者登入主控台；登入成功後 OCI 簽發一個綁定這把臨時公鑰的 token，token 過期之後這對金鑰就沒有用了。&lt;/li>
&lt;li>&lt;strong>API 簽章金鑰&lt;/strong>：使用者在主控台把一把公鑰登記到自己的帳號下，之後持有對應私鑰的程式都能以這個使用者的身分呼叫 API，直到公鑰被刪除為止。&lt;/li>
&lt;/ul>
&lt;p>兩者在本機都會留下一個私鑰檔，差別在私鑰檔外洩之後能用多久。Session token 的私鑰在 token 過期後失效；API 簽章金鑰的私鑰在使用者到主控台刪掉公鑰之前一直有效。實驗與互動式操作用 session token，私鑰外洩之後可被冒用的時間最長到登入後 24 小時（見〈Session token 的時效與 refresh〉）；排程腳本、CI，以及經由 SSH 連進去、沒有瀏覽器可開的遠端機器，這類沒有人能開瀏覽器登入的場合才需要 API 簽章金鑰。&lt;/p>
&lt;h2 id="用瀏覽器登入換-session-token">用瀏覽器登入換 session token&lt;/h2>





&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">oci session authenticate --region ap-batam-1 --profile-name DEFAULT&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>--region&lt;/code>：填 tenancy 的 home region 的識別碼，這次實測的 home region 是 &lt;code>ap-batam-1&lt;/code>，換成自己的。CLI 用它決定開哪一區的登入頁、並寫進設定檔當成之後指令的預設區域；試用與 Free Tier 的 tenancy 只訂閱 home region 一個區域，填它就對。識別碼與主控台上的顯示名稱（例如「Indonesia North (Batam)」）是兩種寫法，對照列在 Governance &amp;amp; Administration → Account Management → Region Management 頁；不給這個選項時，CLI 也會列出全部區域的識別碼讓使用者輸入編號。&lt;/li>
&lt;li>&lt;code>--profile-name&lt;/code>：設定檔 &lt;code>~/.oci/config&lt;/code> 裡這一組身分的名稱。&lt;code>DEFAULT&lt;/code> 是 CLI 在指令沒有指定 &lt;code>--profile&lt;/code> 時使用的那一組；同一台機器要切換多個 tenancy 時，每個 tenancy 用一個不同的 profile 名稱。&lt;/li>
&lt;/ul>
&lt;p>實測 Homebrew 版的 CLI 跑在 Python 3.15 上時，這條指令與之後每條指令的開頭都會印一行 &lt;code>SyntaxWarning&lt;/code>，來源是 CLI 自己的程式碼裡的正規表示式字串，不影響結果。要讓輸出乾淨，在指令前面加上 &lt;code>PYTHONWARNINGS=ignore&lt;/code>（例如 &lt;code>PYTHONWARNINGS=ignore oci session validate&lt;/code>）只對那一條生效；改用 &lt;code>export PYTHONWARNINGS=ignore&lt;/code> 的話，整個 shell 裡所有 Python 程式的警告都會被關掉。&lt;/p>
&lt;p>登入過程中 CLI 會問兩個問題：區域與 profile 名稱，兩個都給在選項裡就不必互動輸入。這一點在 CLI 不是跑在互動式終端機裡時有差別——例如透過另一個程式代為執行、標準輸入沒有接到鍵盤——CLI 讀不到輸入，印出 &lt;code>Abort&lt;/code> 後結束。實測少給 &lt;code>--region&lt;/code> 時就是在區域清單之後直接 &lt;code>Abort&lt;/code>。&lt;/p>
&lt;p>CLI 接著開瀏覽器到登入頁，並在本機的 &lt;code>localhost:8181&lt;/code> 等登入結果。瀏覽器的登入頁跟主控台是同一個，先填租用戶，頁面要求選 identity domain 時選 &lt;code>Default&lt;/code>，再填使用者名稱與密碼，各欄的填法見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region&lt;/a> 的〈主控台登入：租用戶與使用者名稱兩個欄位，與「使用者名稱或密碼無效」〉。登入成功後瀏覽器把 token 送回 &lt;code>localhost:8181&lt;/code>，終端機印出 &lt;code>Completed browser authentication process!&lt;/code> 與 &lt;code>Config written to: /Users/&amp;lt;user&amp;gt;/.oci/config&lt;/code>。瀏覽器頁面不小心關掉時，終端機上印著同一個登入網址，貼回瀏覽器就能繼續；網址裡帶著這次產生的臨時公鑰，所以只對正在等待的這一次登入有效。&lt;/p></description><content:encoded><![CDATA[<p>這篇整理在 macOS 上安裝 OCI CLI 之後，讓它取得操作 tenancy（Oracle Cloud 的帳號）的身分的兩種做法。範圍從 <code>oci</code> 指令裝好開始，到第一條查詢指令回傳資料為止；macOS 上的安裝是 <code>brew install oci-cli</code>，裝好後 <code>oci --version</code> 印出版本號，Linux 與 Windows 的安裝方式見官方〈Installing the CLI〉。不想在本機安裝的話，主控台頂端列的 Cloud Shell 是一個已經裝好 CLI、以目前登入身分驗證好的瀏覽器終端機，本篇的登入步驟在那裡都不需要。tenancy、home region 與主控台登入欄位見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a>。實測環境是 Homebrew 安裝的 OCI CLI 3.95.1，日期 2026-10-24。</p>
<h2 id="cli-的兩種身分驗證session-token-與-api-簽章金鑰">CLI 的兩種身分驗證：session token 與 API 簽章金鑰</h2>
<p>OCI 的 API 不收帳號密碼。CLI 送出的每一個請求都用一把私鑰簽章，OCI 用對應的公鑰驗證簽章、再查這把公鑰屬於哪個使用者，以那個使用者的權限執行請求。兩種做法的差別在這對金鑰從哪裡來、公鑰怎麼讓 OCI 認得：</p>
<ul>
<li><strong>Session token</strong>：CLI 在本機產生一對臨時金鑰，開瀏覽器讓使用者登入主控台；登入成功後 OCI 簽發一個綁定這把臨時公鑰的 token，token 過期之後這對金鑰就沒有用了。</li>
<li><strong>API 簽章金鑰</strong>：使用者在主控台把一把公鑰登記到自己的帳號下，之後持有對應私鑰的程式都能以這個使用者的身分呼叫 API，直到公鑰被刪除為止。</li>
</ul>
<p>兩者在本機都會留下一個私鑰檔，差別在私鑰檔外洩之後能用多久。Session token 的私鑰在 token 過期後失效；API 簽章金鑰的私鑰在使用者到主控台刪掉公鑰之前一直有效。實驗與互動式操作用 session token，私鑰外洩之後可被冒用的時間最長到登入後 24 小時（見〈Session token 的時效與 refresh〉）；排程腳本、CI，以及經由 SSH 連進去、沒有瀏覽器可開的遠端機器，這類沒有人能開瀏覽器登入的場合才需要 API 簽章金鑰。</p>
<h2 id="用瀏覽器登入換-session-token">用瀏覽器登入換 session token</h2>





<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">oci session authenticate --region ap-batam-1 --profile-name DEFAULT</span></span></code></pre></div><ul>
<li><code>--region</code>：填 tenancy 的 home region 的識別碼，這次實測的 home region 是 <code>ap-batam-1</code>，換成自己的。CLI 用它決定開哪一區的登入頁、並寫進設定檔當成之後指令的預設區域；試用與 Free Tier 的 tenancy 只訂閱 home region 一個區域，填它就對。識別碼與主控台上的顯示名稱（例如「Indonesia North (Batam)」）是兩種寫法，對照列在 Governance &amp; Administration → Account Management → Region Management 頁；不給這個選項時，CLI 也會列出全部區域的識別碼讓使用者輸入編號。</li>
<li><code>--profile-name</code>：設定檔 <code>~/.oci/config</code> 裡這一組身分的名稱。<code>DEFAULT</code> 是 CLI 在指令沒有指定 <code>--profile</code> 時使用的那一組；同一台機器要切換多個 tenancy 時，每個 tenancy 用一個不同的 profile 名稱。</li>
</ul>
<p>實測 Homebrew 版的 CLI 跑在 Python 3.15 上時，這條指令與之後每條指令的開頭都會印一行 <code>SyntaxWarning</code>，來源是 CLI 自己的程式碼裡的正規表示式字串，不影響結果。要讓輸出乾淨，在指令前面加上 <code>PYTHONWARNINGS=ignore</code>（例如 <code>PYTHONWARNINGS=ignore oci session validate</code>）只對那一條生效；改用 <code>export PYTHONWARNINGS=ignore</code> 的話，整個 shell 裡所有 Python 程式的警告都會被關掉。</p>
<p>登入過程中 CLI 會問兩個問題：區域與 profile 名稱，兩個都給在選項裡就不必互動輸入。這一點在 CLI 不是跑在互動式終端機裡時有差別——例如透過另一個程式代為執行、標準輸入沒有接到鍵盤——CLI 讀不到輸入，印出 <code>Abort</code> 後結束。實測少給 <code>--region</code> 時就是在區域清單之後直接 <code>Abort</code>。</p>
<p>CLI 接著開瀏覽器到登入頁，並在本機的 <code>localhost:8181</code> 等登入結果。瀏覽器的登入頁跟主控台是同一個，先填租用戶，頁面要求選 identity domain 時選 <code>Default</code>，再填使用者名稱與密碼，各欄的填法見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈主控台登入：租用戶與使用者名稱兩個欄位，與「使用者名稱或密碼無效」〉。登入成功後瀏覽器把 token 送回 <code>localhost:8181</code>，終端機印出 <code>Completed browser authentication process!</code> 與 <code>Config written to: /Users/&lt;user&gt;/.oci/config</code>。瀏覽器頁面不小心關掉時，終端機上印著同一個登入網址，貼回瀏覽器就能繼續；網址裡帶著這次產生的臨時公鑰，所以只對正在等待的這一次登入有效。</p>
<p>設定檔裡的這一組長這樣（值是佔位的）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">[DEFAULT]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="na">fingerprint</span><span class="o">=</span><span class="s">&lt;臨時公鑰的指紋&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">key_file</span><span class="o">=</span><span class="s">/Users/&lt;user&gt;/.oci/sessions/DEFAULT/oci_api_key.pem</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="na">tenancy</span><span class="o">=</span><span class="s">&lt;tenancy 的 OCID&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="na">region</span><span class="o">=</span><span class="s">&lt;home region 的識別碼&gt;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="na">security_token_file</span><span class="o">=</span><span class="s">/Users/&lt;user&gt;/.oci/sessions/DEFAULT/token</span></span></span></code></pre></div><p><code>tenancy</code> 是 tenancy 的 OCID：OCI 給每一個資源（tenancy、compartment、VCN、主機）一個以 <code>ocid1.&lt;資源類型&gt;.</code> 開頭的唯一識別碼，叫 OCID（Oracle Cloud ID），CLI 與 API 用它指定要操作哪一個資源，各資源的詳細頁上都有一個可以複製的 OCID 欄位。<code>key_file</code> 是 CLI 產生的臨時私鑰，<code>security_token_file</code> 是 OCI 簽發的 token，兩個檔都在 <code>~/.oci/sessions/&lt;profile 名稱&gt;/</code> 底下。</p>
<p>不再使用時跑 <code>oci session terminate --profile &lt;profile 名稱&gt;</code>。官方 CLI 參考對這條指令的說明是從設定檔移除這一組，並刪掉它參照的資源（例如金鑰檔），沒有寫 OCI 那一端會不會撤銷已經簽發的 token。所以 token 與私鑰檔在 terminate 之前被複製走時，要假設那份 token 仍然有效：拿到的人可以在它過期前跑 <code>oci session refresh</code>，按〈Session token 的時效與 refresh〉寫的上限，影響的期限最長到登入後 24 小時。</p>
<h2 id="使用-session-token-時每條指令要帶的設定config-file-is-invalid-與-user-missing">使用 session token 時每條指令要帶的設定：config file is invalid 與 user missing</h2>
<p>CLI 預設用 API 簽章金鑰的方式驗證身分，用 session token 時要明確告訴它。兩種寫法效果相同：</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"># 每條指令各自指定</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci iam region-subscription list --auth security_token
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 整個 shell 工作階段都用 session token</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nb">export</span> <span class="nv">OCI_CLI_AUTH</span><span class="o">=</span>security_token</span></span></code></pre></div><p>少了這個設定，CLI 照 API 簽章金鑰的流程檢查設定檔，而那個流程要求設定檔裡有 <code>user</code>（使用者的 OCID），session 登入寫出的設定檔沒有這一欄。實測 CLI 在送出請求之前就停下來，印出 <code>ERROR: The config file at ~/.oci/config is invalid</code>，表格裡列出 <code>user | missing</code>，提示去主控台找使用者的 OCID——照提示補上 <code>user</code> 是在改成 API 簽章金鑰的做法，對 session token 是錯的方向，該補的是 <code>--auth security_token</code>。</p>
<p>確認登入成功、同時查出 tenancy 的基本資訊，可以跑這幾條：</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">oci session validate --auth security_token          <span class="c1"># 印出 token 的到期時間</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci iam region-subscription list --auth security_token   <span class="c1"># 訂閱的區域，is-home-region 為 true 的是 home region</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">oci iam availability-domain list --auth security_token   <span class="c1"># home region 的 availability domain 清單</span></span></span></code></pre></div><h2 id="session-token-的時效與-refresh">Session token 的時效與 refresh</h2>
<p>Token 的預設效期是 1 小時：實測 17:44 登入，<code>oci session validate</code> 回報到期時間 18:44。過期之前可以延長：</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">oci session refresh --profile DEFAULT</span></span></code></pre></div><p><code>--profile</code> 填登入時取的 profile 名稱；登入那一條指令的選項叫 <code>--profile-name</code>，其他指令（refresh、terminate、一般查詢）都叫 <code>--profile</code>。profile 不是 <code>DEFAULT</code> 時，上一節那幾條查詢指令也要各加 <code>--profile &lt;名稱&gt;</code>。官方文件說明 refresh 要在 token 還有效時做，可以一路延長到登入後的 24 小時；超過 24 小時，或 token 已經過期，就要重新跑一次 <code>oci session authenticate</code>、在瀏覽器重新登入。</p>
<h2 id="api-簽章金鑰不需要每天登入的做法">API 簽章金鑰：不需要每天登入的做法</h2>
<p>API 簽章金鑰在主控台的人像選單 → User settings 頁建立。官方〈Required Keys and OCIDs〉對這一頁的寫法有兩種：一處寫進入「Token and keys」分頁，另一處寫左下角 Resources 區段裡的「API Keys」，畫面上找得到哪一個就用哪一個；兩者都從「Add API Key」按鈕開始。在 Add API Key 的對話框讓主控台產生金鑰對並下載私鑰（Download Private Key），按 Add 之後頁面顯示一段 <code>[DEFAULT]</code> 開頭的設定文字。官方寫明一個使用者最多三把 API 金鑰。</p>
<p>那段文字是 CLI 設定檔的一組 profile，其中 <code>key_file</code> 是一個佔位路徑，CLI 會照這一行去讀私鑰，所以貼進 <code>~/.oci/config</code> 之後要把它改成私鑰實際存放的路徑。同一台機器已經照上面的步驟用 session token 登入過時，設定檔裡已經有一組 <code>[DEFAULT]</code>，貼上的這一組要改成另一個名稱（例如 <code>[apikey]</code>），兩組同名時 CLI 只會讀其中一組。私鑰檔的權限設成只有自己能讀寫，因為持有它的人能以這個使用者的全部權限呼叫 API：</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">mv ~/Downloads/&lt;下載的私鑰檔名&gt;.pem ~/.oci/   <span class="c1"># 瀏覽器下載的位置換成自己的</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">chmod <span class="m">600</span> ~/.oci/&lt;下載的私鑰檔名&gt;.pem          <span class="c1"># 私鑰檔只允許擁有者讀寫</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nb">unset</span> OCI_CLI_AUTH                              <span class="c1"># 前面設過 session token 的 shell 要先取消，否則仍走 session</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">oci iam region-subscription list --profile apikey   <span class="c1"># 不帶 --auth，CLI 用 API 簽章金鑰</span></span></span></code></pre></div><p>這把金鑰沒有到期時間。不用的時候回到 User settings 頁的 API 金鑰清單刪掉它，持有私鑰的程式就無法再呼叫 API。私鑰外洩的影響範圍是這個使用者的全部權限，所以給排程與 CI 用的金鑰，建在一個只有必要權限的專用使用者底下，不建在申請時建立的管理員帳號底下。</p>
<h2 id="延伸閱讀帳號結構免費額度與憑證設計">延伸閱讀：帳號結構、免費額度與憑證設計</h2>
<p>登入頁的租用戶與使用者名稱欄位、home region 對 Always Free 的限制，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a>；登入之後要建主機時各家免費額度的計算方式，見 <a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較</a>；session token 與 API 簽章金鑰這組取捨背後的概念，見 <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 的環境隔離">身分與存取憑證地基 — IAM 模型、OIDC 短期憑證與權限邊界設計</a> 的〈長期 access key 的風險〉與〈OIDC：給 CI/CD 的短期憑證〉。</p>
]]></content:encoded></item><item><title>雲端主控台的管理領域：身分、資源分組、運算、網路、儲存、費用、監控、稽核、安全服務與配額</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/console-areas/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/console-areas/</guid><description>&lt;p>這篇整理 Oracle Cloud、Google Cloud 與 AWS 的主控台導覽選單背後共通的管理領域。各家的選單名稱與分類方式不同，而它們要滿足的管理需求是同一組：誰能做什麼、資源怎麼分組、主機與網路與磁碟、花了多少錢、系統現在的狀態、誰做過什麼、安全相關的服務、帳號能開多少資源。範圍是「在主控台上去哪裡做這件事」，每個領域的概念與設計連到站內對應的模組。&lt;/p>
&lt;h2 id="主控台的共通結構選單區域選擇器與資源分組篩選器">主控台的共通結構：選單、區域選擇器與資源分組篩選器&lt;/h2>
&lt;p>主控台上有三種元件第一次登入時要先找到，各家有沒有、放在哪裡不同：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>導覽選單&lt;/strong>：列出所有服務，依領域分類。Oracle Cloud 是左上角的 ☰，AWS 與 Google Cloud 也有同樣位置的選單，另外都有一個搜尋框，打服務名稱就能直接跳過去。不知道某個功能在哪一類底下時，搜尋框比翻選單快。&lt;/li>
&lt;li>&lt;strong>區域選擇器&lt;/strong>：大部分資源屬於某一個區域，主控台一次只顯示目前選定區域裡的資源。主機建在東京、選擇器停在新加坡時，主機清單是空的。Oracle Cloud 與 AWS 的選擇器在右上角；Google Cloud 的主控台不以區域切換，而是在清單上列出每個資源的區域。&lt;/li>
&lt;li>&lt;strong>資源分組篩選器&lt;/strong>：資源還歸屬於某一個分組（Oracle Cloud 的 compartment、Google Cloud 的 project、AWS 的帳號），清單頁只列出目前選定分組裡的資源。Oracle Cloud 的清單頁上有一個 compartment 篩選器，預設常停在 root，資源建在別的 compartment 時，要切過去才看得到；Google Cloud 在頂端列切換專案；AWS 的分組由目前登入的帳號決定，清單頁上沒有篩選器。&lt;/li>
&lt;/ul>
&lt;p>清單看起來是空的、或剛建的資源找不到時，先查區域與分組這兩個篩選條件，多數時候資源還在，只是篩掉了。&lt;/p>
&lt;h2 id="身分與權限誰能登入能做什麼">身分與權限：誰能登入、能做什麼&lt;/h2>
&lt;p>帳號的管理員在這個領域管理使用者、群組、角色與權限政策，以及登入方式（密碼政策、多因素驗證、外部身分提供者）。日常的工作是加人、移除人、調整誰能動哪些資源，以及替程式建立服務帳號或 API 金鑰。&lt;/p>
&lt;p>新帳號開好之後，這個領域裡要先做兩件事：替申請時建立的管理員帳號開啟多因素驗證，並另外建一個日常使用、權限較小的帳號（權限只給到工作需要的範圍，見 &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> 卡），管理員帳號只在必要時用。理由是管理員帳號能動整個帳號裡的一切，它的密碼外洩時沒有任何一層權限擋得住；日常操作用權限較小的帳號，外洩時影響範圍就限在那些權限裡。帳號不是自己申請的（公司指派、接手別人的帳號）時，先確認誰持有那個管理員帳號、它有沒有開多因素驗證。公司的帳號常屬於一個組織，組織的政策（AWS Organizations 的 SCP、Google Cloud 的組織政策）可能讓某些選項建不起來，建立時收到權限不足的錯誤而自己的權限看起來沒問題時，去問管理組織的人。權限的分級與日常帳號的設計見 &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;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;/p>
&lt;h2 id="資源分組與標籤資源屬於哪個專案誰付錢">資源分組與標籤：資源屬於哪個專案、誰付錢&lt;/h2>
&lt;p>管理員與使用者在這個領域把資源分組，讓權限、預算與清理以組為單位進行；標籤則在組之外再加一層分類，用來分攤費用、篩選資源。Oracle Cloud 的分組是 compartment（可以巢狀），Google Cloud 是 project（上面還有 folder 與 organization），AWS 是帳號本身（多個帳號由 Organizations 統一管理）。&lt;/p>
&lt;p>實驗用的資源放進一個專用的分組，清理時刪那一組的資源，不會碰到正式環境；權限政策也能限定只作用在那一組。用帳號或專案隔開環境的設計見 &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 設定連管理員都越不過的護欄、用帳號工廠讓每個新帳號自帶安全基線">跨帳號策略 — Organizations、SCP 與帳號工廠&lt;/a>。&lt;/p>
&lt;h2 id="運算主機與映像檔">運算：主機與映像檔&lt;/h2>
&lt;p>使用者在這個領域建立、停止、刪除主機，管理映像檔、主機範本與自動擴展的主機群組，建立主機時的每個選項見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/" data-link-title="建立雲端虛擬機的選項" data-link-desc="站在雲端主控台的建立虛擬機頁面、不確定某一個欄位會影響什麼時，依功能分類查位置、機型與映像檔、網路、登入、磁碟、代理程式與安全開機各自的選項、建立後能不能改與對費用的影響">建立雲端虛擬機的選項&lt;/a>。日常會來這裡做的事是看主機狀態、停機省錢、調整規格，以及刪除不用的主機。&lt;/p>
&lt;h2 id="網路虛擬網路負載平衡dns-與公開-ip">網路：虛擬網路、負載平衡、DNS 與公開 IP&lt;/h2>
&lt;p>管理虛擬網路與 subnet、路由與閘道、防火牆規則、負載平衡器、DNS 區域、保留的公開 IP。主機連不進去、或連不到其他服務時，原因多半在這個領域的路由與防火牆規則。subnet 怎麼切、路由與防火牆規則怎麼設計見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="要規劃 VPC 與 subnet 分層、設計 security group 與 NAT、收斂對外開放的 SSH 與 RDP 管理 port、或決定管理員登入伺服器的通道時，從這裡找對應的文章">模組三：網路地基&lt;/a>；建立主機時的網路欄位見 &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>三種儲存在選單上通常分開：區塊磁碟（主機的開機磁碟與資料磁碟、它們的備份）、物件儲存（以 bucket 存放檔案，經由 HTTP 存取）、共享檔案系統（多台主機同時掛載）。刪除主機之後留下的開機磁碟、沒有人記得的備份與 bucket 仍然是存在的資源，會繼續佔用免費額度，超出額度之後的結果依業者與帳號狀態而定（例如試用中由試用金支付、付費帳戶照一般價格計費），清理時要回來看。Oracle Cloud 三種帳號狀態各自的結果見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region&lt;/a> 的〈超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果〉，Google Cloud 與 AWS 的見 &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;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這篇整理 Oracle Cloud、Google Cloud 與 AWS 的主控台導覽選單背後共通的管理領域。各家的選單名稱與分類方式不同，而它們要滿足的管理需求是同一組：誰能做什麼、資源怎麼分組、主機與網路與磁碟、花了多少錢、系統現在的狀態、誰做過什麼、安全相關的服務、帳號能開多少資源。範圍是「在主控台上去哪裡做這件事」，每個領域的概念與設計連到站內對應的模組。</p>
<h2 id="主控台的共通結構選單區域選擇器與資源分組篩選器">主控台的共通結構：選單、區域選擇器與資源分組篩選器</h2>
<p>主控台上有三種元件第一次登入時要先找到，各家有沒有、放在哪裡不同：</p>
<ul>
<li><strong>導覽選單</strong>：列出所有服務，依領域分類。Oracle Cloud 是左上角的 ☰，AWS 與 Google Cloud 也有同樣位置的選單，另外都有一個搜尋框，打服務名稱就能直接跳過去。不知道某個功能在哪一類底下時，搜尋框比翻選單快。</li>
<li><strong>區域選擇器</strong>：大部分資源屬於某一個區域，主控台一次只顯示目前選定區域裡的資源。主機建在東京、選擇器停在新加坡時，主機清單是空的。Oracle Cloud 與 AWS 的選擇器在右上角；Google Cloud 的主控台不以區域切換，而是在清單上列出每個資源的區域。</li>
<li><strong>資源分組篩選器</strong>：資源還歸屬於某一個分組（Oracle Cloud 的 compartment、Google Cloud 的 project、AWS 的帳號），清單頁只列出目前選定分組裡的資源。Oracle Cloud 的清單頁上有一個 compartment 篩選器，預設常停在 root，資源建在別的 compartment 時，要切過去才看得到；Google Cloud 在頂端列切換專案；AWS 的分組由目前登入的帳號決定，清單頁上沒有篩選器。</li>
</ul>
<p>清單看起來是空的、或剛建的資源找不到時，先查區域與分組這兩個篩選條件，多數時候資源還在，只是篩掉了。</p>
<h2 id="身分與權限誰能登入能做什麼">身分與權限：誰能登入、能做什麼</h2>
<p>帳號的管理員在這個領域管理使用者、群組、角色與權限政策，以及登入方式（密碼政策、多因素驗證、外部身分提供者）。日常的工作是加人、移除人、調整誰能動哪些資源，以及替程式建立服務帳號或 API 金鑰。</p>
<p>新帳號開好之後，這個領域裡要先做兩件事：替申請時建立的管理員帳號開啟多因素驗證，並另外建一個日常使用、權限較小的帳號（權限只給到工作需要的範圍，見 <a href="/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">Least Privilege</a> 卡），管理員帳號只在必要時用。理由是管理員帳號能動整個帳號裡的一切，它的密碼外洩時沒有任何一層權限擋得住；日常操作用權限較小的帳號，外洩時影響範圍就限在那些權限裡。帳號不是自己申請的（公司指派、接手別人的帳號）時，先確認誰持有那個管理員帳號、它有沒有開多因素驗證。公司的帳號常屬於一個組織，組織的政策（AWS Organizations 的 SCP、Google Cloud 的組織政策）可能讓某些選項建不起來，建立時收到權限不足的錯誤而自己的權限看起來沒問題時，去問管理組織的人。權限的分級與日常帳號的設計見 <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>，概念見 <a href="/blog/devops/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> 卡。</p>
<h2 id="資源分組與標籤資源屬於哪個專案誰付錢">資源分組與標籤：資源屬於哪個專案、誰付錢</h2>
<p>管理員與使用者在這個領域把資源分組，讓權限、預算與清理以組為單位進行；標籤則在組之外再加一層分類，用來分攤費用、篩選資源。Oracle Cloud 的分組是 compartment（可以巢狀），Google Cloud 是 project（上面還有 folder 與 organization），AWS 是帳號本身（多個帳號由 Organizations 統一管理）。</p>
<p>實驗用的資源放進一個專用的分組，清理時刪那一組的資源，不會碰到正式環境；權限政策也能限定只作用在那一組。用帳號或專案隔開環境的設計見 <a href="/blog/devops/infra/02-identity-credentials/multi-account-strategy/" data-link-title="跨帳號策略 — Organizations、SCP 與帳號工廠" data-link-desc="用 AWS Organizations 把環境拆成獨立帳號、用 SCP 設定連管理員都越不過的護欄、用帳號工廠讓每個新帳號自帶安全基線">跨帳號策略 — Organizations、SCP 與帳號工廠</a>。</p>
<h2 id="運算主機與映像檔">運算：主機與映像檔</h2>
<p>使用者在這個領域建立、停止、刪除主機，管理映像檔、主機範本與自動擴展的主機群組，建立主機時的每個選項見 <a href="/blog/devops/infra/cloud-providers/vm-options/" data-link-title="建立雲端虛擬機的選項" data-link-desc="站在雲端主控台的建立虛擬機頁面、不確定某一個欄位會影響什麼時，依功能分類查位置、機型與映像檔、網路、登入、磁碟、代理程式與安全開機各自的選項、建立後能不能改與對費用的影響">建立雲端虛擬機的選項</a>。日常會來這裡做的事是看主機狀態、停機省錢、調整規格，以及刪除不用的主機。</p>
<h2 id="網路虛擬網路負載平衡dns-與公開-ip">網路：虛擬網路、負載平衡、DNS 與公開 IP</h2>
<p>管理虛擬網路與 subnet、路由與閘道、防火牆規則、負載平衡器、DNS 區域、保留的公開 IP。主機連不進去、或連不到其他服務時，原因多半在這個領域的路由與防火牆規則。subnet 怎麼切、路由與防火牆規則怎麼設計見 <a href="/blog/devops/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="要規劃 VPC 與 subnet 分層、設計 security group 與 NAT、收斂對外開放的 SSH 與 RDP 管理 port、或決定管理員登入伺服器的通道時，從這裡找對應的文章">模組三：網路地基</a>；建立主機時的網路欄位見 <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>三種儲存在選單上通常分開：區塊磁碟（主機的開機磁碟與資料磁碟、它們的備份）、物件儲存（以 bucket 存放檔案，經由 HTTP 存取）、共享檔案系統（多台主機同時掛載）。刪除主機之後留下的開機磁碟、沒有人記得的備份與 bucket 仍然是存在的資源，會繼續佔用免費額度，超出額度之後的結果依業者與帳號狀態而定（例如試用中由試用金支付、付費帳戶照一般價格計費），清理時要回來看。Oracle Cloud 三種帳號狀態各自的結果見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果〉，Google Cloud 與 AWS 的見 <a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較</a> 的〈試用結束後帳號與資源的處置〉。開機磁碟的選項見 <a href="/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留</a>。</p>
<h2 id="帳單與成本花了多少會花多少">帳單與成本：花了多少、會花多少</h2>
<p>負責付款的人在這個領域查看帳單與付款方式、依服務與分組分析費用、設定預算與警示。新帳號開好之後要來這裡設一個預算警示，讓費用超過預期時有人收到通知。帳號已經升級成付費帳戶時，超出免費額度或用到非免費機型的費用會直接扣款，所以這類帳號一定要設。</p>
<p>Oracle Cloud 沒有升級的帳號在試用結束後不會開始計費，預算警示在這種帳號上的用處是在試用期間提早看到哪些資源不在免費額度內。成本監控與異常告警見 <a href="/blog/devops/operations/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing 與成本監控告警；預算門檻、配額與停損層級的推算在本模組，各家的建置篇目前只有 GCP，放在雲端業者模組">運行期維運的成本管理</a>；預算門檻怎麼從每月能接受的上限與費用訊號的延遲推出來、扣款的信用卡要留多少額度，在 <a href="/blog/devops/operations/08-cost-management/spend-planning-before-renting/" data-link-title="租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層" data-link-desc="租用雲端主機或把試用帳號升級成付費帳戶之前，費用失控時從觸發到停止之間的金額怎麼從配額與訊號延遲推出來，再用它推扣款的信用卡額度、配額、預算警示與觸發處置的門檻；Oracle Cloud、Google Cloud 與 AWS 的扣款時點、預先授權、扣款失敗的後果、擋下建立與停止用量的機制各自涵蓋什麼，以及停損停到哪一層的判斷">租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層</a>；Google Cloud 各機制的範圍在 <a href="/blog/devops/infra/cloud-providers/gcp/spend-protection-gcp-budget-killswitch/" data-link-title="GCP 上的雲端費用失控防護：預算警示、強制執行支出上限與自動停用帳單的作用範圍、反應時間與判斷" data-link-desc="GCP 上為費用失控設防的機制：預算警示寄出通知的時機、範圍、抵免與通知對象、強制執行支出上限涵蓋的服務與暫停期間，以預算通知觸發函式停止用量的兩種處置（解除帳單連結、只停止花錢的資源）該選哪一種，帳單資料延遲對這些機制反應時間的影響，停損開關放在另一個專案的理由，以及驗收為什麼分成真實通知與測試訊息兩段、停用後的恢復為什麼要先調預算">GCP 上的雲端費用失控防護</a>，門檻比例、建立指令與從預算通知觸發自動停損的建置在 <a href="/blog/devops/infra/cloud-providers/gcp/hands-on/budget-killswitch-walkthrough/" data-link-title="GCP 預算警示與停損開關的實測紀錄：建預算、Pub/Sub 主題、服務帳號、函式與觸發器、兩段驗收、心跳監看與停用後的恢復" data-link-desc="要在 GCP 專案上建預算警示，或建一個花費超過預算就解除帳單連結的停損開關，想照一次實跑過的順序做、看每一步漏掉或換一種做法的結果時讀；也用在函式部署成功、狀態 ACTIVE 卻從沒解除過帳單連結，或專案因超支被停用之後要恢復的時候">GCP 預算警示與停損開關的實測紀錄</a>。帳單上已經出現費用、要找出是哪個資源在計費時，在成本分析頁依服務與資源拆開；免費額度的帳號最常見的來源是刪除主機後留下的開機磁碟（<a href="/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留</a> 的〈刪除（終止）主機時開機磁碟的預設去留〉）、選到不在免費額度內的機型，以及 AWS 對公開 IPv4 位址的計費。免費額度的規則見 <a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較</a>。</p>
<h2 id="監控日誌與告警系統現在的狀態">監控、日誌與告警：系統現在的狀態</h2>
<p>主機與服務的指標（CPU、記憶體、網路流量）、日誌的收集與搜尋、告警規則與通知管道。主機變慢、服務停掉時，先來這裡看指標與日誌。log group、metric 與 alarm 怎麼跟資源一起管理見 <a href="/blog/devops/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性與 log</a>。</p>
<h2 id="稽核紀錄誰在什麼時候做了什麼">稽核紀錄：誰在什麼時候做了什麼</h2>
<p>業者自動記錄對雲端資源的管理操作（建立、修改、刪除）：誰、什麼時間、從哪個 IP、做了什麼操作、對哪個資源。主控台上建立、修改、刪除資源的點擊最後也是 API 呼叫，同樣會留下紀錄。要查出某個資源是誰、在什麼時候改掉或刪掉的，看這裡的紀錄。讀取資料這一類操作不一定預設記錄：Google Cloud 的 Data Access 稽核紀錄除了 BigQuery 之外預設關閉，AWS CloudTrail 的 Event history 只列管理操作，兩者都要另外打開。稽核紀錄的保存期限各家不同，要長期保存時要另外匯出。AWS 的同一個功能見 <a href="/blog/devops/infra/knowledge-cards/cloudtrail/" data-link-title="CloudTrail" data-link-desc="AWS 的 API 層稽核日誌服務，記錄誰在什麼時候對什麼資源做了什麼操作">CloudTrail</a> 卡。</p>
<h2 id="安全服務管理通道金鑰秘密與弱點掃描">安全服務：管理通道、金鑰、秘密與弱點掃描</h2>
<p>業者提供的安全服務多半集中在一個領域：不經公開 IP 登入主機的服務（Oracle Cloud Bastion、Google Cloud IAP、AWS Systems Manager）、金鑰與秘密的保管（Oracle Cloud 的 Vault、Google Cloud 的 Secret Manager、AWS 的 Secrets Manager）、主機的弱點掃描、組態風險的偵測。管理通道的設計見 <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>。Oracle Cloud 的 Vault 是業者的金鑰管理服務，跟 HashiCorp 公司的同名自架產品是兩個不同的產品；不用業者服務、自己架秘密管理時的選項見 <a href="/blog/devops/infra/knowledge-cards/vault/" data-link-title="HashiCorp Vault" data-link-desc="機密管理系統，集中存放密碼、API key、TLS 私鑰，提供存取控制、稽核和自動輪替">HashiCorp Vault</a> 卡。</p>
<h2 id="配額與限制帳號最多能開多少">配額與限制：帳號最多能開多少</h2>
<p>每個帳號對每種資源都有數量上限（主機台數、核心數、公開 IP 數），超過時建立會失敗。這個領域列出每項上限與目前用量，要開更多時從這裡申請調高。上限是帳號允許的數量，不保證那一區有那麼多硬體——Oracle Cloud 的配額表會列出某一區根本沒有提供的機型，實例見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈E2.1.Micro 在各區域的供應：服務配額表與 404 錯誤〉。為一次預知的活動調高上限時，申請要排在活動前多久、被拒時怎麼辦，見 <a href="/blog/backend/09-performance-capacity/peak-event-readiness/" data-link-title="9.11 高峰事件準備" data-link-desc="可預期峰值從 T-90 到 T-0 的準備時程：容量計畫、quota 申請、壓測、game day 與 pre-scaling 各排在事件前幾天、排在那裡的理由與項目之間的依賴，pre-scaling 的做法、watch room、event tier 分級與準備時間不夠時被壓縮的部分，以及事後 retro">9.11 高峰事件準備</a>〈T-90 → T-0 準備時程〉的 T-30。</p>
<h2 id="新帳號開好之後先看的領域">新帳號開好之後先看的領域</h2>
<p>帳號的第一小時要處理的安全底線——管理員帳號的多因素驗證、確認自己的登入身分、檢查既存的存取金鑰、確認稽核紀錄有在記、設帳單警報——整理在 <a href="/blog/devops/infra/00-infra-mindset/first-day-with-cloud-account/" data-link-title="拿到雲端帳號的第一天" data-link-desc="被指派 infra 工作、拿到 AWS 或 GCP 帳號、不確定該先做什麼時讀 — 第一小時安全底線、帳號現況判讀、後續學習路線分流">拿到雲端帳號的第一天</a> 的〈第一小時：安全底線〉，那一篇的操作路徑以 AWS 為例，其他業者對應的選單見下方〈各家的選單名稱：各管理領域在三家主控台上的位置〉；接手已經有資源的帳號時，先看同一篇的〈帳號現況判讀：空帳號還是既有資源〉。在那幾項之外，要開始建資源之前再處理下面這幾件事：</p>
<ul>
<li>
<p><strong>身分與權限</strong>：另外建一個日常使用、權限較小的帳號，理由見〈身分與權限：誰能登入、能做什麼〉。</p>
</li>
<li>
<p><strong>資源分組</strong>：建一個實驗用的分組，之後的資源都建在裡面，清理時只看這一組。Oracle Cloud 建一個 compartment，compartment 要先清空裡面的所有資源才能刪除，刪除的順序見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈刪除這組資源的順序〉；Google Cloud 建一個 project，刪除 project 會連同裡面的資源一起刪；AWS 的分組單位是帳號本身，另開帳號要由組織的管理帳號在 AWS Organizations 裡做，被指派管理單一帳號的人在那個帳號裡改用標籤與 Resource Groups 分組。</p>
</li>
<li>
<p><strong>配額與限制</strong>：上限依帳號類型而不同（Oracle Cloud 的〈Service Limits〉在運算的上限表上分成 Universal Credits 一欄，與 Pay As You Go 和試用帳號共用的一欄（欄名「Pay As You Go or Trial」），兩欄的值不同），所以要用的機型在自己這個帳號、這個區域的上限是多少，要到這個領域的頁面查；上限是 0 時那種機型建不起來，要先申請調高。上限不是 0 也不代表那一區有那種硬體。</p>
</li>
</ul>
<h2 id="各家的選單名稱各管理領域在三家主控台上的位置">各家的選單名稱：各管理領域在三家主控台上的位置</h2>
<table>
  <thead>
      <tr>
          <th>管理領域</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>身分與權限</td>
          <td>Identity &amp; Security → Identity</td>
          <td>IAM &amp; Admin</td>
          <td>IAM、IAM Identity Center</td>
      </tr>
      <tr>
          <td>資源分組</td>
          <td>Identity &amp; Security → Identity → Compartments</td>
          <td>專案選擇器、IAM &amp; Admin → Manage resources</td>
          <td>AWS Organizations</td>
      </tr>
      <tr>
          <td>標籤</td>
          <td>Governance &amp; Administration → Tenancy Management → Tag Namespaces</td>
          <td>IAM &amp; Admin → Tags、Labels</td>
          <td>Resource Groups &amp; Tag Editor</td>
      </tr>
      <tr>
          <td>運算</td>
          <td>Compute</td>
          <td>Compute Engine</td>
          <td>EC2</td>
      </tr>
      <tr>
          <td>網路</td>
          <td>Networking</td>
          <td>VPC network、Network services</td>
          <td>VPC</td>
      </tr>
      <tr>
          <td>儲存</td>
          <td>Storage</td>
          <td>Cloud Storage、Compute Engine → Storage</td>
          <td>S3、EC2 → Elastic Block Store</td>
      </tr>
      <tr>
          <td>帳單與成本</td>
          <td>Billing &amp; Cost Management</td>
          <td>Billing</td>
          <td>Billing and Cost Management</td>
      </tr>
      <tr>
          <td>監控、日誌</td>
          <td>Observability &amp; Management</td>
          <td>Monitoring、Logging</td>
          <td>CloudWatch</td>
      </tr>
      <tr>
          <td>稽核紀錄</td>
          <td>Observability &amp; Management → Logging → Audit</td>
          <td>Logging（Cloud Audit Logs）</td>
          <td>CloudTrail</td>
      </tr>
      <tr>
          <td>安全服務</td>
          <td>Identity &amp; Security</td>
          <td>Security</td>
          <td>Security Hub、Secrets Manager、Systems Manager</td>
      </tr>
      <tr>
          <td>配額與限制</td>
          <td>Governance &amp; Administration → Tenancy Management → Limits, quotas and usage</td>
          <td>IAM &amp; Admin → Quotas &amp; System Limits</td>
          <td>Service Quotas</td>
      </tr>
  </tbody>
</table>
<p>AWS 的身分與權限有兩處：公司帳號用 SSO 登入時，人員與他們能進的帳號在 IAM Identity Center 管理；IAM 管的是帳號裡的角色與程式用的身分。Oracle Cloud 的選單取自 2026-10-24 的主控台英文介面；選單名稱各家會隨改版調整，對不上時用搜尋框找服務名稱。Oracle Cloud 主控台各分類的完整內容見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/console-navigation/" data-link-title="Oracle Cloud 主控台導覽：頂端列、導覽選單的分類、清單頁的 compartment 篩選器與常用頁面的路徑" data-link-desc="Oracle Cloud 主控台頂端列各個按鈕（導覽選單、搜尋、區域、Cloud Shell、人像選單）做什麼，導覽選單各分類底下有哪些項目，清單頁的 compartment 篩選器，以及建立主機、網路、Bastion、預算、稽核紀錄、配額這些常用頁面在選單上的實際路徑">Oracle Cloud 主控台導覽</a>。</p>
<h2 id="延伸閱讀服務層的對照">延伸閱讀：服務層的對照</h2>
<p>這篇對照的是三家主控台上的管理領域與建立主機的欄位；資料庫、訊息佇列、函式這類託管服務在 AWS、Google Cloud 與 Azure 之間怎麼對應、對應的服務在哪些地方不等價，見 <a href="/blog/backend/00-service-selection/cloud-vendor-capability-mapping/" data-link-title="0.19 雲端服務對照地圖（AWS / GCP / Azure）" data-link-desc="把後端能力分類對照到 AWS / GCP / Azure 的具體服務名稱、保留跨雲遷移與選型差異的判讀重點">雲端服務對照地圖（AWS / GCP / Azure）</a>。</p>
]]></content:encoded></item><item><title>網路：subnet、私有 IP、公開 IP 與防火牆規則</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/networking/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/networking/</guid><description>&lt;p>建立頁的網路區塊決定一台主機在虛擬網路裡的位置、網際網路上的人連不連得到它，以及哪些 port 對外開放。範圍是把一台主機放進網路的那些欄位；虛擬網路本身怎麼切 subnet、路由與 NAT 怎麼設計見 &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 的定位">VPC、subnet 與 security group&lt;/a>，管理員要怎麼登入沒有公開 IP 的主機見 &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;h2 id="虛擬網路與-subnet主機放在哪一段網路">虛擬網路與 subnet：主機放在哪一段網路&lt;/h2>
&lt;p>每台主機的網卡都接在一個虛擬網路裡的某一個 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> 上；虛擬網路在 Oracle Cloud 叫 VCN，在 Google Cloud 與 AWS 叫 VPC（見 &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。在 Oracle Cloud 與 AWS 上，選定的 subnet 的路由表決定網際網路來的連線能否送到這台主機、主機對外連線時走哪一條路；網卡能否拿到公開 IP，Oracle Cloud 由 subnet 的屬性決定，AWS 是另一個獨立的設定（見下文 AWS 的說明）。Google Cloud 的 subnet 沒有公開與私有之分，主機能否從網際網路直接連到、對外連線走哪一條路，由主機是否有外部 IP 與防火牆規則決定。&lt;/p>
&lt;p>概念上，subnet 分成公開與私有兩種：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>公開 subnet&lt;/strong>：&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）的路由，裡面有公開 IP 的主機可以被網際網路直接連到開放的 port，也經由這個閘道對外連線。&lt;/li>
&lt;li>&lt;strong>私有 subnet&lt;/strong>：沒有那一條路由，外面連不進來。主機要對外連線（下載套件更新）時經由 &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> 閘道（NAT gateway）出去；要連業者自己的服務（物件儲存、監控、管理通道服務）時，經由業者提供的私有連線，Oracle Cloud 叫服務閘道（service gateway）、AWS 叫 VPC endpoint、Google Cloud 叫 Private Google Access。&lt;/li>
&lt;/ul>
&lt;p>公開與私有這個區分記在哪裡，Oracle Cloud 與 AWS 的做法不同。Oracle Cloud 記在 subnet 自己的屬性上：建立 subnet 時有一個「禁止公開 IP」的屬性，打開的就是私有 subnet，裡面的網卡指派不了公開 IP。AWS 上公開 IP 與路由是兩個獨立的設定，官方〈Subnets for your VPC〉分別寫了兩者：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>網卡是否有公開 IP&lt;/strong>：由 subnet 的「自動指派公開 IPv4」屬性決定，建立主機時可以對那一台主機覆寫這個屬性。&lt;/li>
&lt;li>&lt;strong>網際網路來的連線能否送到主機&lt;/strong>：由 subnet 的路由表決定。官方的定義是，有直接通往 internet gateway 路由的是公開 subnet，沒有的是私有 subnet。&lt;/li>
&lt;/ul>
&lt;p>所以 AWS 私有 subnet 裡的主機可以有公開 IP，而那個公開 IP 收不到網際網路來的連線。Google Cloud 沒有公開與私有 subnet 之分，任何 subnet 裡的主機都可以有外部 IP。另外，AWS 的一個 subnet 只在一個可用區裡，兩台主機要分到不同可用區時，要放進兩個不同可用區的 subnet；Oracle Cloud 與 Google Cloud 的 subnet 可以跨同一區域內的可用區，見 &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;/p></description><content:encoded><![CDATA[<p>建立頁的網路區塊決定一台主機在虛擬網路裡的位置、網際網路上的人連不連得到它，以及哪些 port 對外開放。範圍是把一台主機放進網路的那些欄位；虛擬網路本身怎麼切 subnet、路由與 NAT 怎麼設計見 <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 的定位">VPC、subnet 與 security group</a>，管理員要怎麼登入沒有公開 IP 的主機見 <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>
<h2 id="虛擬網路與-subnet主機放在哪一段網路">虛擬網路與 subnet：主機放在哪一段網路</h2>
<p>每台主機的網卡都接在一個虛擬網路裡的某一個 <a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> 上；虛擬網路在 Oracle Cloud 叫 VCN，在 Google Cloud 與 AWS 叫 VPC（見 <a href="/blog/devops/infra/knowledge-cards/vpc/" data-link-title="VPC（Virtual Private Cloud）" data-link-desc="雲端帳號內的一塊邏輯隔離私有網段，是所有網路切分的起點與容器">VPC</a> 卡）。建立主機的人在建立頁要決定的是接在哪一個 subnet。在 Oracle Cloud 與 AWS 上，選定的 subnet 的路由表決定網際網路來的連線能否送到這台主機、主機對外連線時走哪一條路；網卡能否拿到公開 IP，Oracle Cloud 由 subnet 的屬性決定，AWS 是另一個獨立的設定（見下文 AWS 的說明）。Google Cloud 的 subnet 沒有公開與私有之分，主機能否從網際網路直接連到、對外連線走哪一條路，由主機是否有外部 IP 與防火牆規則決定。</p>
<p>概念上，subnet 分成公開與私有兩種：</p>
<ul>
<li><strong>公開 subnet</strong>：<a href="/blog/devops/infra/knowledge-cards/route-table/" data-link-title="Route Table" data-link-desc="掛在 subnet 上的流量轉送規則，決定封包離開 subnet 後往哪走">路由表（route table）</a>有一條通往網際網路閘道（internet gateway）的路由，裡面有公開 IP 的主機可以被網際網路直接連到開放的 port，也經由這個閘道對外連線。</li>
<li><strong>私有 subnet</strong>：沒有那一條路由，外面連不進來。主機要對外連線（下載套件更新）時經由 <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> 閘道（NAT gateway）出去；要連業者自己的服務（物件儲存、監控、管理通道服務）時，經由業者提供的私有連線，Oracle Cloud 叫服務閘道（service gateway）、AWS 叫 VPC endpoint、Google Cloud 叫 Private Google Access。</li>
</ul>
<p>公開與私有這個區分記在哪裡，Oracle Cloud 與 AWS 的做法不同。Oracle Cloud 記在 subnet 自己的屬性上：建立 subnet 時有一個「禁止公開 IP」的屬性，打開的就是私有 subnet，裡面的網卡指派不了公開 IP。AWS 上公開 IP 與路由是兩個獨立的設定，官方〈Subnets for your VPC〉分別寫了兩者：</p>
<ul>
<li><strong>網卡是否有公開 IP</strong>：由 subnet 的「自動指派公開 IPv4」屬性決定，建立主機時可以對那一台主機覆寫這個屬性。</li>
<li><strong>網際網路來的連線能否送到主機</strong>：由 subnet 的路由表決定。官方的定義是，有直接通往 internet gateway 路由的是公開 subnet，沒有的是私有 subnet。</li>
</ul>
<p>所以 AWS 私有 subnet 裡的主機可以有公開 IP，而那個公開 IP 收不到網際網路來的連線。Google Cloud 沒有公開與私有 subnet 之分，任何 subnet 裡的主機都可以有外部 IP。另外，AWS 的一個 subnet 只在一個可用區裡，兩台主機要分到不同可用區時，要放進兩個不同可用區的 subnet；Oracle Cloud 與 Google Cloud 的 subnet 可以跨同一區域內的可用區，見 <a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a>。</p>
<p>在 Oracle Cloud 上，公開 subnet 裡沒有公開 IP 的主機完全沒有出站路徑：官方〈Internet Gateway〉的說明是，要經由 internet gateway 連網際網路的資源必須在公開 subnet 裡、而且有公開 IP，只有私有 IP 的資源要改走 NAT 閘道。所以在 Oracle Cloud 上，放進公開 subnet 的主機要同時給公開 IP 才有出站路徑；不給公開 IP 的主機放私有 subnet，出站走 NAT 閘道。在私有 subnet 的 Ubuntu 主機上實際加過 NAT 閘道、路由規則與只掛在那台主機上的 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>〈加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG〉。</p>
<p>替單一台實驗機選 subnet 與公開 IP 時，要先確定三項需求：主機上是否有服務要讓網際網路的人連、管理員從哪裡登入、主機自己是否要對外連線（下載套件更新）。下面各條依對外服務與登入方式的組合寫出放法，出站路徑寫在各條裡：</p>
<ul>
<li><strong>沒有對外的服務、管理員經由業者的管理通道登入</strong>：放私有 subnet、不給公開 IP，外面連不進來。Oracle Cloud 的 Bastion 是業者託管的服務、不是自己架的 <a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">跳板機（bastion host）</a>，經由它登入的主機不需要公開 IP；它的網路條件、費用與配額見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉。主機要下載套件更新時，路由表要另外有一條經過 NAT 閘道的路由；AWS 的 NAT Gateway 與 Google Cloud 的 Cloud NAT 另外計費，費率見各家價格頁。</li>
<li><strong>沒有對外的服務、管理員直接用 SSH 登入</strong>：放公開 subnet、給公開 IP，設定最少，出站也經由網際網路閘道。代價是 SSH port 對網際網路可見，要把防火牆規則的來源收緊成自己的 IP，自己的 IP 會變時每次都要改規則。三家業者預先準備的網路規則（Oracle Cloud 主控台新建 VCN 時的 Default Security List、Google Cloud <code>default</code> 網路的 <code>default-allow-ssh</code>、AWS 啟動精靈新建的 security group）預設都放行 22 port；Google Cloud 那一條的來源是整個網際網路，Oracle Cloud 那一份在 2026-10-24 實測時也是，見〈各家預設網路規則對 SSH 的開放〉。</li>
<li><strong>有一個服務要讓網際網路的人直接連（例如自架 VPN）</strong>：放公開 subnet、給公開 IP，防火牆規則只放行那一個服務的 port。管理員的 SSH 不必跟著對外：來源收緊成自己的 IP，或先連上 VPN、再經由主機的私有 IP 登入。</li>
<li><strong>網頁這類 HTTP 服務</strong>：主機放私有 subnet，由放在公開 subnet 的負載平衡器承接網際網路來的連線。Oracle Cloud 的 Always Free 含一個 10 Mbps 的負載平衡器；AWS 與 Google Cloud 的負載平衡器另外計費，計費項目見各家價格頁。</li>
</ul>
<p>建立頁替主機準備網路的方式，各家預先準備的東西不一樣。Oracle Cloud 的建立頁提供「選既有的 VCN」與「建一個新的 VCN」兩條路，選「建新的」時，主控台會順手建一個 VCN、一個 subnet 與一份預設的防火牆規則；Google Cloud 的專案預先就有一個 <code>default</code> 網路，建立頁預設把主機放進去；AWS 的啟動精靈把主機放進帳號的預設 VPC，並順手建一個新的 security group。要把主機放進自己設計過的網路時，先在網路的頁面建好虛擬網路與 subnet，再回到建立頁選那個 subnet。Oracle Cloud 的網路下拉選單還受 compartment（tenancy 底下分組資源的容器）篩選，只列出選定的 compartment 裡的 VCN；篩選的規則見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/console-navigation/" data-link-title="Oracle Cloud 主控台導覽：頂端列、導覽選單的分類、清單頁的 compartment 篩選器與常用頁面的路徑" data-link-desc="Oracle Cloud 主控台頂端列各個按鈕（導覽選單、搜尋、區域、Cloud Shell、人像選單）做什麼，導覽選單各分類底下有哪些項目，清單頁的 compartment 篩選器，以及建立主機、網路、Bastion、預算、稽核紀錄、配額這些常用頁面在選單上的實際路徑">Oracle Cloud 主控台導覽</a> 的〈清單頁的 compartment 篩選器〉，實際卡住的情形見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的 Networking 段。</p>
<h2 id="私有-ip主機在虛擬網路裡的位址">私有 IP：主機在虛擬網路裡的位址</h2>
<p>每張網卡一定有一個私有 IP，從 subnet 的位址範圍裡分配。建立頁的選項是自動分配（預設）或手動指定一個位址。單一台主機選自動分配；要讓其他設定寫死這台主機的位址時（例如另一台主機的設定檔、防火牆規則只放行這一個位址），才手動指定。</p>
<p>主機停止與重開時，私有 IP 都不會變，終止主機時業者才把它釋放。同一個虛擬網路裡的其他主機用這個位址連它。</p>
<p>Oracle Cloud 另有一個 DNS record 選項：勾選之後，網卡會在 VCN 的內部 DNS 裡得到一個名稱，同一個 VCN 裡的主機可以用名稱代替 IP 連它。這個選項要 VCN 與 subnet 都設了 DNS label（建立 VCN 與 subnet 時填的名稱片段）才能用，名稱只在 VCN 內部有效，網際網路上查不到。同一個 VCN 裡有其他主機要連這一台時勾選，設定檔寫名稱比寫 IP 好維護；只有一台主機時勾不勾都沒有差別。</p>
<h2 id="公開-ip網際網路上的入口">公開 IP：網際網路上的入口</h2>
<p>公開 IP 讓網際網路上的人能直接連到這台主機。在 Oracle Cloud 上，它只能指派給公開 subnet 裡的網卡；主機放在私有 subnet 時，建立頁上的公開 IP 選項是灰的。</p>
<p>Oracle Cloud 的公開 IP 分兩種，性質在建立後不能互換：</p>
<ul>
<li><strong>臨時（ephemeral）</strong>：跟著主機的生命週期。主機停止時保留，終止時自動刪除（Oracle Cloud 上一台主機停止 2 小時再啟動，公開 IP 前後相同；收掉時先刪 DNS 紀錄再終止主機的實測，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/suspend-resume-drill/" data-link-title="Oracle Cloud 主機的暫停與恢復上線實測紀錄：暫停前的基準、收窄入口、停止主機、停止期間的計費與 Cloudflare 的回應、恢復上線的每一步與耗時，以及收尾時 DNS 紀錄與主機的刪除順序" data-link-desc="要讓 Oracle Cloud 上一台放在 Cloudflare 後面的主機停機一段時間再恢復上線、想知道停止之後公開 IP 留不留、停止期間 A1 與開機磁碟怎麼計費、Cloudflare 回什麼錯誤、恢復時每一步要多久，或服務要收掉時 DNS 紀錄與主機的刪除順序時，查實際跑過的指令、輸出與量到的時間">Oracle Cloud 主機的暫停與恢復上線實測紀錄：暫停前的基準、收窄入口、停止主機、停止期間的計費與 Cloudflare 的回應、恢復上線的每一步與耗時，以及收尾時 DNS 紀錄與主機的刪除順序</a>）。建立頁上勾選「自動指派公開 IPv4」拿到的是臨時公開 IP。DNS 記錄指向臨時 IP 時，主機終止、重建之後拿到的是另一個位址，DNS 記錄仍然指向那個已經刪除、不再屬於這個帳號的舊位址；DNS 查詢照常回應那個舊位址而不會報錯。業者會把收回的位址再分配給別的客戶，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 與子網域接管〉；暫停或重建主機時哪些情形會把 IP 還給業者，見 <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>〈暫停期間仍指向這台主機的外部設定〉。AWS 自動指派的公開 IPv4 與 Oracle Cloud 不同，主機停止時就收回、再啟動時換成新的位址（AWS 文件〈Amazon EC2 instance IP addressing〉）。</li>
<li><strong>保留（reserved）</strong>：獨立的資源，主機終止之後仍然存在，可以再指派給另一台主機。要讓 DNS 記錄或外部白名單長期指向同一個位址時用保留公開 IP。</li>
</ul>
<p>在 Oracle Cloud 上，公開 IP 可以在建立之後再加、也可以拿掉：公開 subnet 裡的網卡沒有公開 IP 時，可以另外建一個臨時公開 IP 指派上去。AWS 上建立時沒有自動指派的主機，之後要對外得另外掛一個 Elastic IP。</p>
<p>主機所在的 subnet 在建立之後能否更換，三家的官方說明不同。Oracle Cloud 與 AWS 的官方網卡說明都寫明主要網卡（建立主機時自動建的那一張）不能從主機拿掉，網卡的私有 IP 從它所在的 subnet 分配，兩家的文件都沒有寫主要網卡可以換到另一個 subnet；所以在這兩家，把 subnet 當成建立時就決定的選項，主機要不要對外在建立頁選 subnet 時就決定。</p>
<p>在 Oracle Cloud 上，放在私有 subnet 的主機之後要對外，可走的路是加一張接在公開 subnet 的次要網卡、改由放在公開 subnet 的負載平衡器承接，或重建主機。官方說明次要網卡可以接在同一個或另一個 VCN 的其他 subnet，條件是跟主機在同一個可用區。在 Oracle Cloud 上，放在公開 subnet 卻不給公開 IP 的主機沒有出站路徑（見〈虛擬網路與 subnet：主機放在哪一段網路〉）。</p>
<p>Google Cloud 的〈Migrating a VM between networks〉說明頁寫明，主機的網卡可以換到同一個 VPC 網路裡的另一個 subnet：主機要先停止，換完之後主機仍在原來的區域與可用區，網卡的 MAC 位址會改變，目標 subnet 的位址範圍與原本不同時，內部 IP 也會改變。</p>
<p>AWS 對每個公開 IPv4 位址按小時另外計費，見 <a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較</a>。</p>
<p>主機是否指派了公開 IP，決定它在網際網路上暴露的面積。管理用的 SSH port 經由公開 IP 對外開放之後，網際網路上的掃描程式會持續嘗試登入。</p>
<h2 id="防火牆規則的位置與合併方式">防火牆規則的位置與合併方式</h2>
<p>一個封包要到得了主機上的程式，要通過兩道關：雲端業者那一側的防火牆規則，以及主機作業系統裡的防火牆，兩道都放行才到得了。</p>
<p>雲端業者那一側，Oracle Cloud 與 AWS 有兩個掛規則的位置，Google Cloud 只有一個：</p>
<table>
  <thead>
      <tr>
          <th>規則的位置</th>
          <th>Oracle Cloud</th>
          <th>AWS</th>
          <th>作用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>subnet</td>
          <td>Security list</td>
          <td>Network ACL</td>
          <td>這個 subnet 裡的每一張網卡</td>
      </tr>
      <tr>
          <td>網卡</td>
          <td>Network security group（NSG）</td>
          <td><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security group</a></td>
          <td>加入這一組規則的那幾張網卡</td>
      </tr>
  </tbody>
</table>
<p>兩個位置的規則同時存在時，Oracle Cloud 取兩邊的聯集，AWS 要兩邊都放行，Google Cloud 的 VPC 防火牆規則只有一個位置而不必合併：</p>
<ul>
<li>
<p><strong>Oracle Cloud 取聯集</strong>：官方〈Security Rules〉的說明是，套用在一張網卡上的規則是 subnet 的 security list 與這張網卡加入的所有 NSG 的聯集。Security list 與 NSG 都只能寫放行的規則，沒有拒絕規則，所以任何一邊有一條規則放行，封包就通過雲端那一道關。以 22 port 為例：</p>
<table>
  <thead>
      <tr>
          <th>Security list 對 22 port</th>
          <th>NSG 對 22 port</th>
          <th>封包能不能通過雲端那一道關</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>有放行規則</td>
          <td>沒有規則</td>
          <td>能</td>
      </tr>
      <tr>
          <td>沒有規則</td>
          <td>有放行規則</td>
          <td>能</td>
      </tr>
      <tr>
          <td>沒有規則</td>
          <td>沒有規則</td>
          <td>不能</td>
      </tr>
  </tbody>
</table>
<p>任一邊有一條放行規則，封包就通過雲端那一道關，所以兩邊都沒有放行 22 port 的規則時，22 port 的連線才會被擋下。預設的 security list 放行 22 port 時，NSG 裡即使沒有 22 port 的規則，22 port 的連線照樣由 security list 放行通過。</p>
</li>
<li>
<p><strong>AWS 兩個位置都要放行</strong>：封包要先通過 subnet 的 Network ACL，再通過網卡的 security group。Network ACL 可以寫拒絕規則，官方建議把它當成第二道控制，用來擋掉特定的流量；security group 放行了，Network ACL 擋下來的封包仍然到不了。</p>
</li>
<li>
<p><strong>Google Cloud 的 VPC 防火牆規則只有一個位置</strong>：防火牆規則設在 VPC 上，再用 <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> 或 <a href="/blog/devops/infra/knowledge-cards/service-account/" data-link-title="Service Account（服務帳號）" data-link-desc="程式、排程或雲端服務要以自己的身分呼叫雲端 API、要決定給它哪些角色、遇到「狀態正常卻呼叫失敗」的權限問題、或看到 IAM 綁定裡的 serviceAccount: 成員時，查服務帳號是什麼、它的權限要從「它能做什麼」與「誰能以它的身分行事」兩邊設，以及它和人的帳號在管理上的差別">服務帳號（service account）</a> 選出規則要套用的主機，沒有 subnet 這一層的規則。單一專案、沒有另外建過防火牆政策時只會遇到 VPC 防火牆規則；Google Cloud 另有掛在 VPC 網路上的 network firewall policy 與組織層的 hierarchical firewall policy，建了之後會跟 VPC 的防火牆規則依官方說明的評估順序一起評估，那時套用到一台主機的規則就不只 VPC 防火牆規則這一個來源。</p>
</li>
</ul>
<p>Oracle Cloud 建立頁上能設定的只有 NSG：「Use network security groups to control traffic」這個選項把主機的網卡加入某幾個 NSG，只有選既有的 VCN 時才出現。Security list 要到 VCN 的頁面設。官方文件說明 NSG 讓 subnet 的切法與應用程式的安全需求分開管理：同一個 subnet 裡的資料庫主機與網頁主機可以開不同的 port，不必為了開不同的 port 拆成兩個 subnet。</p>
<p>作業系統的防火牆（<code>iptables</code>、<code>nftables</code>、<code>ufw</code>）由主機自己管，不在雲端的主控台上。雲端那一道關放行了某個 port、連線仍然被拒絕時，要進主機查作業系統這一道。Oracle Cloud 的平台映像出廠就帶著一組作業系統防火牆規則，其中有讓主機連到開機磁碟與區塊磁碟的 iSCSI 端點的必要規則，官方〈Images〉建議不要移除；官方〈Images〉那一頁另外寫明在 Ubuntu 映像上不要用 <code>ufw</code> 改規則，用它改可能讓主機開不了機。SSH 連不上時，先看症狀是連線逾時還是連線被拒：逾時代表封包沒有送到主機（雲端規則、路由或作業系統防火牆丟棄了封包），被拒代表封包到了主機而 22 port 沒有程式接受連線或防火牆明確回拒，分辨方法見 <a href="/blog/linux/dotfile/knowledge-cards/connection-refused-vs-timeout/" data-link-title="Connection Refused vs. Timeout（連線被拒與逾時）" data-link-desc="遠端連不上、要判斷是封包到不了還是服務拒絕、決定往網路層還是服務層除錯時回來讀">Connection Refused vs. Timeout（連線被拒與逾時）</a>。主機上跑 Docker 時，容器發佈的連接埠由 Docker 在作業系統防火牆之前轉給容器，<code>ufw</code> 與 <code>firewalld</code> 的一般規則管不到它；封包路徑與限制的寫法見 <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>
<h2 id="各家預設網路規則對-ssh-的開放">各家預設網路規則對 SSH 的開放</h2>
<p>三家業者替使用者預先建好或順手建的網路，預設的規則都放行 22 port 的 SSH 流量。Google Cloud 那一條規則的來源是整個網際網路，Oracle Cloud 那一份的來源在 2026-10-24 實測時也是整個網際網路：</p>
<ul>
<li><strong>Oracle Cloud</strong>：建立頁選「建一個新的 VCN」時，主控台一起建一份「Default Security List」。官方〈Security Lists〉描述的是這條規則的用途：放行來自「authorized source IP addresses」的 TCP 22，讓使用者建好新的 VCN 與公開 subnet 之後，不必自己寫規則就能用 SSH 連上新建的 Linux 主機；這段描述沒有寫來源欄位的預設值。2026-10-24 實測時，主控台建出來的這條規則來源是 <code>0.0.0.0/0</code>。兩者不衝突：官方寫的是規則的用途，實測讀到的是那一次建出來的預設值。官方沒有寫預設值，所以建好之後到 subnet 的 security list 看一次實際的來源。Subnet 沒有另外指定 security list 時套用的就是這一份，所以這條規則的來源是 <code>0.0.0.0/0</code> 時，在新建的公開 subnet 裡建一台有公開 IP 的主機，那台主機的 SSH port 一建好就對網際網路開放。這份規則的完整內容（實測值）見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的 Networking 段。</li>
<li><strong>Google Cloud</strong>：專案建立時就有的 <code>default</code> 網路帶一條 <code>default-allow-ssh</code> 規則，放行 <code>0.0.0.0/0</code> 的 TCP 22，套用在這個網路裡的每一台主機。換掉這條規則的順序見 <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> 的〈切換到 IAP 的順序〉，逐步的指令見 <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><strong>AWS</strong>：EC2 啟動精靈順手建的 security group 預設放行進來的 SSH 流量（官方〈Prerequisites for EC2 Instance Connect〉的說明），放行的來源要在精靈的網路設定那一步確認並收緊。</li>
</ul>
<p>收緊的做法是把來源改成管理員的 IP，或改用不需要對外開放 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>
<h2 id="其他網路欄位vnic-名稱ipv6-與-launch-options">其他網路欄位：VNIC 名稱、IPv6 與 Launch options</h2>
<ul>
<li><strong>網卡名稱（VNIC name）</strong>：顯示用的名稱，不必唯一、之後可以改，不影響連線。留空時業者自動產生。</li>
<li><strong>IPv6</strong>：只有 VCN 與 subnet 都設了 IPv6 位址段時才能勾。只用 IPv4 連線的實驗機不需要。</li>
<li><strong>Launch options（Oracle Cloud）</strong>：網卡的虛擬化方式，只對虛擬機有效。預設值由映像檔決定，是那個映像檔支援的方式；改成映像檔不支援的方式時，主機可能開機後沒有網路，所以維持預設。</li>
</ul>
<h2 id="各家的欄位名稱虛擬網路網卡防火牆規則與保留的公開-ip">各家的欄位名稱：虛擬網路、網卡、防火牆規則與保留的公開 IP</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>虛擬網路</td>
          <td>VCN</td>
          <td>VPC network</td>
          <td>VPC</td>
      </tr>
      <tr>
          <td>網卡</td>
          <td>VNIC</td>
          <td>Network interface</td>
          <td>ENI（Elastic Network Interface）</td>
      </tr>
      <tr>
          <td>subnet 層規則</td>
          <td>Security list</td>
          <td>無（規則設在 VPC 上）</td>
          <td>Network ACL</td>
      </tr>
      <tr>
          <td>網卡層規則</td>
          <td>Network security group</td>
          <td>無（Firewall rule 設在 VPC 上，以網路標籤或服務帳號選主機）</td>
          <td>Security group</td>
      </tr>
      <tr>
          <td>兩層的合併</td>
          <td>聯集</td>
          <td>只有一層</td>
          <td>兩層都要放行</td>
      </tr>
      <tr>
          <td>保留的公開 IP</td>
          <td>Reserved public IP</td>
          <td>Static external IP address</td>
          <td>Elastic IP</td>
      </tr>
  </tbody>
</table>
<p>Subnet 的切法、路由表與 NAT 的設計見 <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 的定位">VPC、subnet 與 security group</a>；網路通了之後，登入時用的金鑰與帳號見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a>。</p>
]]></content:encoded></item><item><title>Oracle Cloud（OCI）</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/</guid><description>&lt;p>Oracle Cloud Infrastructure（OCI）的免費方案分成 30 天的試用金與沒有期限的 Always Free 資源，Always Free 資源綁在申請時選的 home region。這個資料夾的文章都在同一個 tenancy 上實際操作過：以 Free Tier 註冊，2026-10-27 確認已經是付費帳戶；實測日期寫在各篇裡。&lt;/p>
&lt;h2 id="章節文章">章節文章&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;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/console-navigation/" data-link-title="Oracle Cloud 主控台導覽：頂端列、導覽選單的分類、清單頁的 compartment 篩選器與常用頁面的路徑" data-link-desc="Oracle Cloud 主控台頂端列各個按鈕（導覽選單、搜尋、區域、Cloud Shell、人像選單）做什麼，導覽選單各分類底下有哪些項目，清單頁的 compartment 篩選器，以及建立主機、網路、Bastion、預算、稽核紀錄、配額這些常用頁面在選單上的實際路徑">Oracle Cloud 主控台導覽&lt;/a>&lt;/td>
 &lt;td>頂端列、導覽選單、compartment 篩選器、常用頁面路徑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region&lt;/a>&lt;/td>
 &lt;td>tenancy 與 home region、Always Free 的範圍、主控台登入欄位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/" data-link-title="從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰" data-link-desc="OCI CLI 的兩種身分驗證方式——瀏覽器登入換來的 session token 與上傳公鑰的 API 簽章金鑰——各自把什麼存在本機、有效多久、外洩時的影響範圍，以及登入時的互動提示與錯誤訊息怎麼讀">從本機用 OCI CLI 操作 Oracle Cloud&lt;/a>&lt;/td>
 &lt;td>&lt;code>oci session authenticate&lt;/code>、token 時效、API 簽章金鑰&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/cloud-providers/oracle-cloud/hands-on/" data-link-title="Oracle Cloud 的實作紀錄" data-link-desc="要在 Oracle Cloud 照著建一台 Always Free 的 A1 主機、在容量不足時重送建立請求，排查 Bastion managed SSH session 建立逾時，改用 port forwarding session 登入，讓私有子網路的主機經由 NAT gateway 連到套件來源，連上序列主控台，主機被回收之後要重建，用 quota 擋下超出免費量的建立，或主機要停機一段時間再恢復上線、服務收掉時 DNS 紀錄與主機的刪除順序時，查實際跑過的指令、輸出與除錯過程">Oracle Cloud 的實作紀錄&lt;/a>：照著做就能重現的操作紀錄（建立 A1 主機與建立前的檢查、容量不足時的重送腳本、Bastion 工作階段逾時的排查、Run Command 補公鑰的嘗試、改用 Oracle Linux 並放公鑰重建後經由 port forwarding session 登入；私有子網路的 Ubuntu 主機加出站、序列主控台的連線、被閒置回收之後的重建腳本；放在 Cloudflare 後面的主機停機再恢復上線的演練與收尾時的刪除順序），概念寫在本層的文章，實作文章連回來&lt;/li>
&lt;/ul>
&lt;h2 id="backlog">Backlog&lt;/h2>
&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>Bastion 管理通道實作文章，放 &lt;code>hands-on/&lt;/code>（managed SSH session 在 A1 加 Oracle Linux 上走一次、建工作階段需要的 IAM 政策、稽核紀錄）&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>一台 A1 Oracle Linux 9 主機，建好先用 &lt;code>plugin list&lt;/code> 確認有 Bastion（&lt;code>available-plugins get&lt;/code> 查到 10 沒有、9 分不出）；建之前到 Compute → Instances 加總現有 A1 的 OCPU 與記憶體，對照付費 tenancy 每月 4 OCPU、24 GB 的免費量&lt;/td>
 &lt;td>1 篇（中）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>〈Oracle Cloud 建立主機的實測紀錄〉的刪除資源順序實際跑一次&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>&lt;code>verify-a1&lt;/code> 已終止；剩下的 &lt;code>verify-a1-ol&lt;/code> 與網路等使用者決定刪除&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Run Command 寫回 &lt;code>authorized_keys&lt;/code> 的實測：建立時用 cloud-init 放 &lt;code>ocarun&lt;/code> 的 sudoers、再用 Run Command 寫公鑰，以及一般使用者送指令要的政策&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>政策那一半可以在 &lt;code>verify-a1-ol&lt;/code> 上驗；sudoers 那一半要新建一台 A1（建之前加總現有 A1 的規格，對照付費 tenancy 每月 4 OCPU、24 GB 的免費量）&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>OCI 的最小權限：群組、限定 compartment 的 policy 範例（日常帳號、CI 專用使用者）；可併入 Bastion 那一篇&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>先查官方 policy 範例&lt;/td>
 &lt;td>中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>實際發生閒置回收或試用結束停用時的紀錄，補進〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉的重建那一節：主機變成停止還是刪除、開機磁碟的去留、有沒有收到通知；重建腳本已經寫在那一節&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>遇到一次實際回收或停用（這個 tenancy 已是付費帳戶，官方沒寫回收規則是否適用於付費帳戶）&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>用〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉費用一節那條 usage API 查詢重查 2026-10-26 的明細，確認補齊之後仍然沒有 NAT gateway 的項目；順便查 Oracle 的 NAT gateway 文件有沒有明文寫收費規則&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>2026-10-29 以後（讓那一天的帳務資料補齊）&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>NAT gateway 開滿一個帳單週期後讀 Cost Analysis，確認它本身沒有收費項目；結果補進〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉的費用一節與〈雲端免費額度與小型主機的收費比較〉&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>要另建一個 NAT gateway 並開滿一個帳單週期（2026-10-27 09:32（台北時間）重建的 &lt;code>verify-nat&lt;/code> 已在同一天 15:05 起整組收掉時刪除）&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/console-navigation/" data-link-title="Oracle Cloud 主控台導覽：頂端列、導覽選單的分類、清單頁的 compartment 篩選器與常用頁面的路徑" data-link-desc="Oracle Cloud 主控台頂端列各個按鈕（導覽選單、搜尋、區域、Cloud Shell、人像選單）做什麼，導覽選單各分類底下有哪些項目，清單頁的 compartment 篩選器，以及建立主機、網路、Bastion、預算、稽核紀錄、配額這些常用頁面在選單上的實際路徑">Oracle Cloud 主控台導覽：頂端列、導覽選單的分類、清單頁的 compartment 篩選器與常用頁面的路徑&lt;/a> 的 Limits, quotas and usage 那一列補讀法：篩選條件的 Scope 預設是區域，可用網域層的限制（含付費磁碟的 &lt;code>total-storage-gb&lt;/code>）要切換 Scope 才列得出來，連到〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉費用一節；同時檢查帳號篇讀這一頁的段落（E2.1.Micro 的台數上限那一段，先查那條 limit 的 scope-type）與〈Oracle Cloud 建立主機的實測紀錄〉開機磁碟大小那一步（補預設扣 47、超出時整顆判定的連結）&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>無（2026-10-27 已在主控台讀過）&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉的〈費用與額度：A1、開機磁碟與區塊儲存額度、出站流量與 NAT gateway〉拆成 &lt;code>hands-on/&lt;/code> 的一篇區塊儲存額度與費用的實測紀錄（A1 與開機磁碟的用量、整顆計費、compartment quota、加大與付費轉免費、NAT gateway 的收費項目）：那一節的操作對象是 tenancy 的額度，跟私有子網路與出站無關，而全篇已經超過 900 行；拆完之後站內用這個段名連進來的位置（storage、spend-planning、orphaned-resource、pricing-comparison、account-and-home-region、停機演練那篇與原篇自己）改連新篇&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>知識卡：Work Request（Oracle Cloud 記錄非同步操作進度的物件；&lt;code>instance terminate&lt;/code> 的 &lt;code>--wait-for-state&lt;/code> 收它的狀態，&lt;code>instance action&lt;/code> 收主機的狀態，四篇實作文章用到而沒有卡）；&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cdn/" data-link-title="CDN（內容遞送網路）" data-link-desc="網站要放到 Cloudflare 這類服務後面、看到回應標頭帶著 CDN 的標記、或要判斷源站看到的來源位址是誰時，查 CDN 代替源站接收連線的方式、回源與它對源站防火牆的影響">CDN&lt;/a> 卡的可觀察訊號補「源站連不上時 Cloudflare 回 521 或 522」，連暫停那篇與〈Oracle Cloud 主機的暫停與恢復上線實測紀錄〉&lt;/td>
 &lt;td>知識卡&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>區塊磁碟整顆判定還沒量的情形：兩顆付費磁碟都放得進空出的額度時先轉哪一顆、付費轉免費的延遲（2026-10-27 量到 26 到 31 分鐘）是否固定、跨可用網域的區域合計怎麼算；結果補進〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉費用一節&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>要另建付費磁碟與可刪的免費磁碟，合計會超過 200，測完要刪&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>在序列主控台登入作業系統：事先替救援帳號設密碼、確認 sshd 不接受密碼登入，再從序列主控台登入一次；設密碼與登入那兩步由使用者自己做&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>要另建一台 A1 Ubuntu 主機（&lt;code>verify-ubuntu&lt;/code> 已在 2026-10-27 終止）；建之前加總現有 A1 的規格，對照付費 tenancy 每月 4 OCPU、24 GB 的免費量&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>〈Oracle Cloud 主機的暫停與恢復上線實測紀錄〉還沒做的部分：照主機清單跑 host-check.py 的暫停前與恢復後兩次比對、DNS 紀錄改指到維護頁再改回、主機開著而程序沒有在聽時 Cloudflare 回不回 521、暫停期長到要重開機時的恢復耗時&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>一台放在 Cloudflare 後面的主機與它的主機清單（2026-10-27 用的 &lt;code>verify-origin&lt;/code> 已終止，要照回源那篇重建）&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2026-10-25 審查列出、還沒查證的登入替代做法：Cloud Shell 接私有網路直接 ssh、主控台建立頁依映像列出的可用 plugin、dynamic port forwarding（SOCKS5）session 的連線指令&lt;/td>
 &lt;td>案例&lt;/td>
 &lt;td>&lt;code>verify-a1-ol&lt;/code>；Cloud Shell 那一項要先查 Private Network 需要的政策&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉審查列出、還沒寫進〈建立雲端虛擬機的選項〉各篇的取捨：OS Management Hub、自訂映像、Resource Manager、instance pool 重建主機、出站只放行特定網段、IPv6&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>先查各項的官方文件；出站網段那一項先確認 Canonical 鏡像站有沒有公開的位址清單&lt;/td>
 &lt;td>1 篇（中）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>Oracle Cloud Infrastructure（OCI）的免費方案分成 30 天的試用金與沒有期限的 Always Free 資源，Always Free 資源綁在申請時選的 home region。這個資料夾的文章都在同一個 tenancy 上實際操作過：以 Free Tier 註冊，2026-10-27 確認已經是付費帳戶；實測日期寫在各篇裡。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/oracle-cloud/console-navigation/" data-link-title="Oracle Cloud 主控台導覽：頂端列、導覽選單的分類、清單頁的 compartment 篩選器與常用頁面的路徑" data-link-desc="Oracle Cloud 主控台頂端列各個按鈕（導覽選單、搜尋、區域、Cloud Shell、人像選單）做什麼，導覽選單各分類底下有哪些項目，清單頁的 compartment 篩選器，以及建立主機、網路、Bastion、預算、稽核紀錄、配額這些常用頁面在選單上的實際路徑">Oracle Cloud 主控台導覽</a></td>
          <td>頂端列、導覽選單、compartment 篩選器、常用頁面路徑</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a></td>
          <td>tenancy 與 home region、Always Free 的範圍、主控台登入欄位</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/" data-link-title="從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰" data-link-desc="OCI CLI 的兩種身分驗證方式——瀏覽器登入換來的 session token 與上傳公鑰的 API 簽章金鑰——各自把什麼存在本機、有效多久、外洩時的影響範圍，以及登入時的互動提示與錯誤訊息怎麼讀">從本機用 OCI CLI 操作 Oracle Cloud</a></td>
          <td><code>oci session authenticate</code>、token 時效、API 簽章金鑰</td>
      </tr>
  </tbody>
</table>
<h2 id="子資料夾">子資料夾</h2>
<ul>
<li><a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/" data-link-title="Oracle Cloud 的實作紀錄" data-link-desc="要在 Oracle Cloud 照著建一台 Always Free 的 A1 主機、在容量不足時重送建立請求，排查 Bastion managed SSH session 建立逾時，改用 port forwarding session 登入，讓私有子網路的主機經由 NAT gateway 連到套件來源，連上序列主控台，主機被回收之後要重建，用 quota 擋下超出免費量的建立，或主機要停機一段時間再恢復上線、服務收掉時 DNS 紀錄與主機的刪除順序時，查實際跑過的指令、輸出與除錯過程">Oracle Cloud 的實作紀錄</a>：照著做就能重現的操作紀錄（建立 A1 主機與建立前的檢查、容量不足時的重送腳本、Bastion 工作階段逾時的排查、Run Command 補公鑰的嘗試、改用 Oracle Linux 並放公鑰重建後經由 port forwarding session 登入；私有子網路的 Ubuntu 主機加出站、序列主控台的連線、被閒置回收之後的重建腳本；放在 Cloudflare 後面的主機停機再恢復上線的演練與收尾時的刪除順序），概念寫在本層的文章，實作文章連回來</li>
</ul>
<h2 id="backlog">Backlog</h2>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>類型</th>
          <th>前置條件</th>
          <th>規模</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bastion 管理通道實作文章，放 <code>hands-on/</code>（managed SSH session 在 A1 加 Oracle Linux 上走一次、建工作階段需要的 IAM 政策、稽核紀錄）</td>
          <td>主章</td>
          <td>一台 A1 Oracle Linux 9 主機，建好先用 <code>plugin list</code> 確認有 Bastion（<code>available-plugins get</code> 查到 10 沒有、9 分不出）；建之前到 Compute → Instances 加總現有 A1 的 OCPU 與記憶體，對照付費 tenancy 每月 4 OCPU、24 GB 的免費量</td>
          <td>1 篇（中）</td>
      </tr>
      <tr>
          <td>〈Oracle Cloud 建立主機的實測紀錄〉的刪除資源順序實際跑一次</td>
          <td>案例</td>
          <td><code>verify-a1</code> 已終止；剩下的 <code>verify-a1-ol</code> 與網路等使用者決定刪除</td>
          <td>小</td>
      </tr>
      <tr>
          <td>Run Command 寫回 <code>authorized_keys</code> 的實測：建立時用 cloud-init 放 <code>ocarun</code> 的 sudoers、再用 Run Command 寫公鑰，以及一般使用者送指令要的政策</td>
          <td>案例</td>
          <td>政策那一半可以在 <code>verify-a1-ol</code> 上驗；sudoers 那一半要新建一台 A1（建之前加總現有 A1 的規格，對照付費 tenancy 每月 4 OCPU、24 GB 的免費量）</td>
          <td>小</td>
      </tr>
      <tr>
          <td>OCI 的最小權限：群組、限定 compartment 的 policy 範例（日常帳號、CI 專用使用者）；可併入 Bastion 那一篇</td>
          <td>主章</td>
          <td>先查官方 policy 範例</td>
          <td>中</td>
      </tr>
      <tr>
          <td>實際發生閒置回收或試用結束停用時的紀錄，補進〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉的重建那一節：主機變成停止還是刪除、開機磁碟的去留、有沒有收到通知；重建腳本已經寫在那一節</td>
          <td>案例</td>
          <td>遇到一次實際回收或停用（這個 tenancy 已是付費帳戶，官方沒寫回收規則是否適用於付費帳戶）</td>
          <td>小</td>
      </tr>
      <tr>
          <td>用〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉費用一節那條 usage API 查詢重查 2026-10-26 的明細，確認補齊之後仍然沒有 NAT gateway 的項目；順便查 Oracle 的 NAT gateway 文件有沒有明文寫收費規則</td>
          <td>案例</td>
          <td>2026-10-29 以後（讓那一天的帳務資料補齊）</td>
          <td>小</td>
      </tr>
      <tr>
          <td>NAT gateway 開滿一個帳單週期後讀 Cost Analysis，確認它本身沒有收費項目；結果補進〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉的費用一節與〈雲端免費額度與小型主機的收費比較〉</td>
          <td>案例</td>
          <td>要另建一個 NAT gateway 並開滿一個帳單週期（2026-10-27 09:32（台北時間）重建的 <code>verify-nat</code> 已在同一天 15:05 起整組收掉時刪除）</td>
          <td>小</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/oracle-cloud/console-navigation/" data-link-title="Oracle Cloud 主控台導覽：頂端列、導覽選單的分類、清單頁的 compartment 篩選器與常用頁面的路徑" data-link-desc="Oracle Cloud 主控台頂端列各個按鈕（導覽選單、搜尋、區域、Cloud Shell、人像選單）做什麼，導覽選單各分類底下有哪些項目，清單頁的 compartment 篩選器，以及建立主機、網路、Bastion、預算、稽核紀錄、配額這些常用頁面在選單上的實際路徑">Oracle Cloud 主控台導覽：頂端列、導覽選單的分類、清單頁的 compartment 篩選器與常用頁面的路徑</a> 的 Limits, quotas and usage 那一列補讀法：篩選條件的 Scope 預設是區域，可用網域層的限制（含付費磁碟的 <code>total-storage-gb</code>）要切換 Scope 才列得出來，連到〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉費用一節；同時檢查帳號篇讀這一頁的段落（E2.1.Micro 的台數上限那一段，先查那條 limit 的 scope-type）與〈Oracle Cloud 建立主機的實測紀錄〉開機磁碟大小那一步（補預設扣 47、超出時整顆判定的連結）</td>
          <td>案例</td>
          <td>無（2026-10-27 已在主控台讀過）</td>
          <td>小</td>
      </tr>
      <tr>
          <td>〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉的〈費用與額度：A1、開機磁碟與區塊儲存額度、出站流量與 NAT gateway〉拆成 <code>hands-on/</code> 的一篇區塊儲存額度與費用的實測紀錄（A1 與開機磁碟的用量、整顆計費、compartment quota、加大與付費轉免費、NAT gateway 的收費項目）：那一節的操作對象是 tenancy 的額度，跟私有子網路與出站無關，而全篇已經超過 900 行；拆完之後站內用這個段名連進來的位置（storage、spend-planning、orphaned-resource、pricing-comparison、account-and-home-region、停機演練那篇與原篇自己）改連新篇</td>
          <td>案例</td>
          <td>無</td>
          <td>中</td>
      </tr>
      <tr>
          <td>知識卡：Work Request（Oracle Cloud 記錄非同步操作進度的物件；<code>instance terminate</code> 的 <code>--wait-for-state</code> 收它的狀態，<code>instance action</code> 收主機的狀態，四篇實作文章用到而沒有卡）；<a href="/blog/devops/infra/knowledge-cards/cdn/" data-link-title="CDN（內容遞送網路）" data-link-desc="網站要放到 Cloudflare 這類服務後面、看到回應標頭帶著 CDN 的標記、或要判斷源站看到的來源位址是誰時，查 CDN 代替源站接收連線的方式、回源與它對源站防火牆的影響">CDN</a> 卡的可觀察訊號補「源站連不上時 Cloudflare 回 521 或 522」，連暫停那篇與〈Oracle Cloud 主機的暫停與恢復上線實測紀錄〉</td>
          <td>知識卡</td>
          <td>無</td>
          <td>小</td>
      </tr>
      <tr>
          <td>區塊磁碟整顆判定還沒量的情形：兩顆付費磁碟都放得進空出的額度時先轉哪一顆、付費轉免費的延遲（2026-10-27 量到 26 到 31 分鐘）是否固定、跨可用網域的區域合計怎麼算；結果補進〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉費用一節</td>
          <td>案例</td>
          <td>要另建付費磁碟與可刪的免費磁碟，合計會超過 200，測完要刪</td>
          <td>小</td>
      </tr>
      <tr>
          <td>在序列主控台登入作業系統：事先替救援帳號設密碼、確認 sshd 不接受密碼登入，再從序列主控台登入一次；設密碼與登入那兩步由使用者自己做</td>
          <td>案例</td>
          <td>要另建一台 A1 Ubuntu 主機（<code>verify-ubuntu</code> 已在 2026-10-27 終止）；建之前加總現有 A1 的規格，對照付費 tenancy 每月 4 OCPU、24 GB 的免費量</td>
          <td>小</td>
      </tr>
      <tr>
          <td>〈Oracle Cloud 主機的暫停與恢復上線實測紀錄〉還沒做的部分：照主機清單跑 host-check.py 的暫停前與恢復後兩次比對、DNS 紀錄改指到維護頁再改回、主機開著而程序沒有在聽時 Cloudflare 回不回 521、暫停期長到要重開機時的恢復耗時</td>
          <td>案例</td>
          <td>一台放在 Cloudflare 後面的主機與它的主機清單（2026-10-27 用的 <code>verify-origin</code> 已終止，要照回源那篇重建）</td>
          <td>小</td>
      </tr>
      <tr>
          <td>2026-10-25 審查列出、還沒查證的登入替代做法：Cloud Shell 接私有網路直接 ssh、主控台建立頁依映像列出的可用 plugin、dynamic port forwarding（SOCKS5）session 的連線指令</td>
          <td>案例</td>
          <td><code>verify-a1-ol</code>；Cloud Shell 那一項要先查 Private Network 需要的政策</td>
          <td>小</td>
      </tr>
      <tr>
          <td>〈Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄〉審查列出、還沒寫進〈建立雲端虛擬機的選項〉各篇的取捨：OS Management Hub、自訂映像、Resource Manager、instance pool 重建主機、出站只放行特定網段、IPv6</td>
          <td>主章</td>
          <td>先查各項的官方文件；出站網段那一項先確認 Canonical 鏡像站有沒有公開的位址清單</td>
          <td>1 篇（中）</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/login/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/login/</guid><description>&lt;p>建立頁的 SSH 金鑰欄位決定主機第一次開機之後，誰能用哪一把金鑰、以哪一個帳號登入。範圍是第一次登入用的金鑰與帳號；長期給一個團隊用的登入設計（&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/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>。&lt;/p>
&lt;h2 id="ssh-金鑰欄位把公鑰放進主機的方式">SSH 金鑰欄位把公鑰放進主機的方式&lt;/h2>
&lt;p>Linux 主機用 &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>~/.ssh/authorized_keys&lt;/code>），持有清單上某一把公鑰對應私鑰的人才能登入。建立頁的 SSH 金鑰欄位把一把公鑰交給業者，業者把它存進主機的中繼資料（metadata）。Oracle Cloud 與 AWS 的 Linux 映像由 cloud-init（多數 Linux 雲端映像預先裝好、開機時讀中繼資料做初始設定的程式）在主機第一次開機時讀出公鑰、寫進預設使用者的 &lt;code>authorized_keys&lt;/code>；Google Cloud 的映像由業者的 guest agent 持續依中繼資料同步金鑰，不限第一次開機。&lt;/p>
&lt;p>Oracle Cloud 的建立頁提供四個選項：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>讓業者產生一對金鑰（Generate a key pair for me）&lt;/strong>：業者產生一對 RSA 金鑰，公鑰放進主機，私鑰讓使用者在這一頁下載。私鑰只能在這一刻下載，離開頁面之後就拿不到；官方提醒持有私鑰的人都能登入這台主機，要存在安全的位置。&lt;/li>
&lt;li>&lt;strong>上傳公鑰檔（Upload public key files）&lt;/strong>：上傳自己電腦上已經有的公鑰（&lt;code>.pub&lt;/code> 檔）。私鑰從頭到尾只在自己的電腦上。&lt;/li>
&lt;li>&lt;strong>貼上公鑰（Paste public keys）&lt;/strong>：同上，改成貼上公鑰的文字。誤貼私鑰時頁面會報錯。&lt;/li>
&lt;li>&lt;strong>不放金鑰（No SSH keys）&lt;/strong>：主機裡沒有任何允許登入的公鑰，而且官方說明選了之後就不能再提供公鑰。SSH 登入只能經由 Bastion 的 managed SSH session（見〈不放金鑰時的登入服務〉）；Bastion 的 port forwarding session 要用主機 &lt;code>authorized_keys&lt;/code> 裡事先放好的公鑰，選了 No SSH keys 的主機用不了它。所以 managed SSH session 不支援的主機（A1 機型加非 Oracle Linux，或可用的 plugin 裡沒有 Bastion 的作業系統版本，見〈不放金鑰時的登入服務〉）選了 No SSH keys，就沒有 SSH 登入的入口。其餘的入口是〈建立之後加金鑰與金鑰遺失時的入口〉列的救援入口。&lt;/li>
&lt;/ul>
&lt;p>自己已經在用 SSH 的話，上傳或貼上自己的公鑰比較好：私鑰不經過任何網頁，也不必管理另一把專門給這台主機的金鑰。本機還沒有金鑰時，用 &lt;code>ssh-keygen -t ed25519&lt;/code> 產生一對（ed25519 是新版 OpenSSH 的 &lt;code>ssh-keygen&lt;/code> 預設產生的型別），公鑰在 &lt;code>~/.ssh/id_ed25519.pub&lt;/code>；那個路徑已經有一把金鑰時，&lt;code>ssh-keygen&lt;/code> 會問要不要覆寫，回答 n，或用 &lt;code>-f &amp;lt;新的檔名&amp;gt;&lt;/code> 另存一把。&lt;/p>
&lt;h2 id="預設使用者名稱與映像檔的對應">預設使用者名稱與映像檔的對應&lt;/h2>
&lt;p>在 Oracle Cloud 與 AWS 上，公鑰寫進的是映像檔預先建立的那個使用者，名稱依作業系統而定。Oracle Cloud 的官方說明是 Ubuntu 平台映像的使用者名稱是 &lt;code>ubuntu&lt;/code>，Oracle Linux 與相容 RHEL 的平台映像是 &lt;code>opc&lt;/code>。登入指令因此是 &lt;code>ssh ubuntu@&amp;lt;主機的 IP&amp;gt;&lt;/code> 或 &lt;code>ssh opc@&amp;lt;主機的 IP&amp;gt;&lt;/code>，用錯名稱時 SSH 回的是 &lt;code>Permission denied (publickey)&lt;/code>，看起來跟金鑰錯了一樣。Google Cloud 的使用者名稱取自中繼資料裡每一把公鑰附帶的名稱，guest agent 依那個名稱在主機上建使用者。&lt;/p>
&lt;p>這個預設使用者有 &lt;code>sudo&lt;/code> 權限，而且只用金鑰登入、沒有設密碼。多人共用同一台主機時，各自建自己的使用者、各自放自己的公鑰，誰做了什麼才分得出來；共用一個帳號時，登入紀錄只顯示那個帳號，對不到是哪一個人，有人離職也無法只撤銷他一個人的存取，見 &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;h2 id="建立之後加金鑰與金鑰遺失時的入口">建立之後加金鑰與金鑰遺失時的入口&lt;/h2>
&lt;p>建立之後能不能再加金鑰，看業者用什麼把金鑰寫進主機。Oracle Cloud 的金鑰由 cloud-init 在第一次開機時寫進主機一次，之後要加金鑰，做法是先用現有的金鑰登入，再在主機裡把新的公鑰加進 &lt;code>authorized_keys&lt;/code>。Google Cloud 的 guest agent 持續同步中繼資料，主機建好之後把公鑰加進主機或專案的中繼資料，guest agent 就會把它寫進主機。AWS 的 Systems Manager Session Manager 不經過 SSH，主機的 &lt;code>authorized_keys&lt;/code> 裡沒有任何公鑰也能開工作階段，條件備齊時可以當成另一個入口。&lt;/p></description><content:encoded><![CDATA[<p>建立頁的 SSH 金鑰欄位決定主機第一次開機之後，誰能用哪一把金鑰、以哪一個帳號登入。範圍是第一次登入用的金鑰與帳號；長期給一個團隊用的登入設計（<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/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>。</p>
<h2 id="ssh-金鑰欄位把公鑰放進主機的方式">SSH 金鑰欄位把公鑰放進主機的方式</h2>
<p>Linux 主機用 <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>~/.ssh/authorized_keys</code>），持有清單上某一把公鑰對應私鑰的人才能登入。建立頁的 SSH 金鑰欄位把一把公鑰交給業者，業者把它存進主機的中繼資料（metadata）。Oracle Cloud 與 AWS 的 Linux 映像由 cloud-init（多數 Linux 雲端映像預先裝好、開機時讀中繼資料做初始設定的程式）在主機第一次開機時讀出公鑰、寫進預設使用者的 <code>authorized_keys</code>；Google Cloud 的映像由業者的 guest agent 持續依中繼資料同步金鑰，不限第一次開機。</p>
<p>Oracle Cloud 的建立頁提供四個選項：</p>
<ul>
<li><strong>讓業者產生一對金鑰（Generate a key pair for me）</strong>：業者產生一對 RSA 金鑰，公鑰放進主機，私鑰讓使用者在這一頁下載。私鑰只能在這一刻下載，離開頁面之後就拿不到；官方提醒持有私鑰的人都能登入這台主機，要存在安全的位置。</li>
<li><strong>上傳公鑰檔（Upload public key files）</strong>：上傳自己電腦上已經有的公鑰（<code>.pub</code> 檔）。私鑰從頭到尾只在自己的電腦上。</li>
<li><strong>貼上公鑰（Paste public keys）</strong>：同上，改成貼上公鑰的文字。誤貼私鑰時頁面會報錯。</li>
<li><strong>不放金鑰（No SSH keys）</strong>：主機裡沒有任何允許登入的公鑰，而且官方說明選了之後就不能再提供公鑰。SSH 登入只能經由 Bastion 的 managed SSH session（見〈不放金鑰時的登入服務〉）；Bastion 的 port forwarding session 要用主機 <code>authorized_keys</code> 裡事先放好的公鑰，選了 No SSH keys 的主機用不了它。所以 managed SSH session 不支援的主機（A1 機型加非 Oracle Linux，或可用的 plugin 裡沒有 Bastion 的作業系統版本，見〈不放金鑰時的登入服務〉）選了 No SSH keys，就沒有 SSH 登入的入口。其餘的入口是〈建立之後加金鑰與金鑰遺失時的入口〉列的救援入口。</li>
</ul>
<p>自己已經在用 SSH 的話，上傳或貼上自己的公鑰比較好：私鑰不經過任何網頁，也不必管理另一把專門給這台主機的金鑰。本機還沒有金鑰時，用 <code>ssh-keygen -t ed25519</code> 產生一對（ed25519 是新版 OpenSSH 的 <code>ssh-keygen</code> 預設產生的型別），公鑰在 <code>~/.ssh/id_ed25519.pub</code>；那個路徑已經有一把金鑰時，<code>ssh-keygen</code> 會問要不要覆寫，回答 n，或用 <code>-f &lt;新的檔名&gt;</code> 另存一把。</p>
<h2 id="預設使用者名稱與映像檔的對應">預設使用者名稱與映像檔的對應</h2>
<p>在 Oracle Cloud 與 AWS 上，公鑰寫進的是映像檔預先建立的那個使用者，名稱依作業系統而定。Oracle Cloud 的官方說明是 Ubuntu 平台映像的使用者名稱是 <code>ubuntu</code>，Oracle Linux 與相容 RHEL 的平台映像是 <code>opc</code>。登入指令因此是 <code>ssh ubuntu@&lt;主機的 IP&gt;</code> 或 <code>ssh opc@&lt;主機的 IP&gt;</code>，用錯名稱時 SSH 回的是 <code>Permission denied (publickey)</code>，看起來跟金鑰錯了一樣。Google Cloud 的使用者名稱取自中繼資料裡每一把公鑰附帶的名稱，guest agent 依那個名稱在主機上建使用者。</p>
<p>這個預設使用者有 <code>sudo</code> 權限，而且只用金鑰登入、沒有設密碼。多人共用同一台主機時，各自建自己的使用者、各自放自己的公鑰，誰做了什麼才分得出來；共用一個帳號時，登入紀錄只顯示那個帳號，對不到是哪一個人，有人離職也無法只撤銷他一個人的存取，見 <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>
<h2 id="建立之後加金鑰與金鑰遺失時的入口">建立之後加金鑰與金鑰遺失時的入口</h2>
<p>建立之後能不能再加金鑰，看業者用什麼把金鑰寫進主機。Oracle Cloud 的金鑰由 cloud-init 在第一次開機時寫進主機一次，之後要加金鑰，做法是先用現有的金鑰登入，再在主機裡把新的公鑰加進 <code>authorized_keys</code>。Google Cloud 的 guest agent 持續同步中繼資料，主機建好之後把公鑰加進主機或專案的中繼資料，guest agent 就會把它寫進主機。AWS 的 Systems Manager Session Manager 不經過 SSH，主機的 <code>authorized_keys</code> 裡沒有任何公鑰也能開工作階段，條件備齊時可以當成另一個入口。</p>
<p>所以在 Oracle Cloud 上，金鑰遺失或一開始沒放、又沒有開代理程式的 Bastion plugin（讓 Oracle Cloud 的 Bastion 服務在建立工作階段時，把一把公鑰臨時放進主機的功能，見〈不放金鑰時的登入服務〉）時，主機上沒有可以用 SSH 進去的入口。剩下的入口各有前提，而多數前提要在金鑰遺失之前就準備好：</p>
<ul>
<li><strong>序列主控台（serial console）</strong>：不經過網路與 sshd，在主控台上建一個主控台連線就能看到主機的開機畫面與登入提示。要在登入提示登進去，主機裡要事先有一個設了密碼的帳號，而平台映像的預設使用者只用金鑰、沒有密碼；沒有這種帳號時，只能照官方範例從開機選單進維護模式重設金鑰，那份範例只寫了 Oracle Linux 的 <code>opc</code>。救援帳號怎麼事先準備見 <a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）</a> 的〈設計責任〉；在 Oracle Cloud 的 Ubuntu 24.04 主機上建序列主控台連線、改寫成 ProxyJump 設定與連上之後看到的開機輸出，見 <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 設定與看到的開機輸出〉。</li>
<li><strong>Compute Instance Run Command</strong>：在主控台送一段腳本到主機裡執行。官方說明這個 plugin 以 <code>ocarun</code> 這個使用者執行，要寫另一個使用者家目錄裡的 <code>authorized_keys</code>，得事先在主機上替 <code>ocarun</code> 設好 sudo（官方列的做法是建立時用 cloud-init 設，或建好之後登入主機設；主機登不進去時只剩前一種）；2026-10-25 對一台沒設的 A1 Ubuntu 主機實測，<code>ocarun</code> 沒有 sudo，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈Run Command 補公鑰的嘗試：dynamic group、IAM 政策與 ocarun 沒有 sudo 的結果〉；送指令的人也要有對應的權限政策，主機上的代理程式要連得到 Oracle 的服務端點（主機在私有 subnet 時經由服務閘道或 NAT 閘道；官方只對「腳本或輸出放在 Object Storage」這種用法寫明出站條件，列的是 NAT 閘道、服務閘道或 internet gateway 其中一種）。</li>
<li><strong>把開機磁碟掛到另一台主機</strong>：停止這台主機、把它的開機磁碟卸下，當成資料磁碟掛到另一台主機上，直接修改裡面的 <code>authorized_keys</code>，再掛回去開機。這一條不需要事先準備，代價是一段停機。</li>
<li><strong>重建</strong>：終止這台主機，帶著新的公鑰重建。官方對金鑰遺失列的處置就是重建與序列主控台這兩種。</li>
</ul>
<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> 把它們叫做網路可達性（連線怎麼到得了主機）、通道授權（誰可以開這條連線）與主機登入驗證（主機是否接受登入者出示的金鑰）。業者的服務各自包辦其中幾關，所以三家對應的服務包辦的範圍不同：</p>
<ul>
<li>
<p><strong>Oracle Cloud 的 Bastion</strong>：Bastion 是業者託管的服務，不是自己架的 <a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">跳板機（bastion host）</a>。官方〈Bastion〉概觀頁的說明是 Bastion 位在公開 subnet（原文 reside in a public subnet），替使用者建好連到私有 subnet 裡目標資源所需的網路設施，所以經由它登入的主機不需要公開 IP；同一頁寫明 Bastion 不收費，但有服務配額（service limits）。誰能建工作階段由 Oracle Cloud 的權限政策決定。登入的流程是在主控台 Identity &amp; Security → Bastion 建一個 Bastion，選一個目標 subnet、設定允許連入的來源位址範圍，再建一個工作階段（session），主控台會給出一條經由 Bastion 轉接的 <code>ssh</code> 指令。官方建立 Bastion 的說明頁寫明，目標 subnet 要是主機所在的 subnet，或主機所在的 subnet 接受其流量的另一個 subnet。概觀頁列的工作階段有三種，每一種都只在建立時設定的存續時間內能用來連線：</p>
<ul>
<li><strong>managed SSH session</strong>：主機上代理程式的 Bastion plugin 把建立工作階段時指定的公鑰臨時放進主機。網路可達性、通道授權與主機登入驗證都由 Bastion 包辦，所以主機可以不放任何金鑰。官方〈Known Issues for Bastion〉寫明，用 Arm 架構的 Ampere A1 機型建立、作業系統不是 Oracle Linux（例如 Ubuntu）的主機不支援這種工作階段，要改用下一項的 port forwarding session；Always Free 的 A1 主機選 Ubuntu 時就落在這個組合，實測的經過見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈建立之後：Bastion managed SSH session 逾時的排查〉。Known Issues 只寫到作業系統，沒有寫版本，而 Bastion plugin 是否提供依作業系統的版本而定：同一份實測紀錄在 2026-10-25 用 <code>oci instance-agent available-plugins get</code> 查到 Oracle Linux 10 可用的 plugin 裡確定沒有 Bastion（9 與 8 的回應跟查不到時相同，這支工具分不出），查法與改用 Oracle Linux 並放公鑰重建的經過在〈重建：Oracle Linux 映像加建立時放公鑰，經由 port forwarding session 登入〉。</li>
<li><strong>SSH port forwarding session</strong>：只把連線轉到一台主機的 22 port，不經過代理程式，只包辦網路可達性與通道授權；主機登入驗證仍然是主機自己的 sshd 與 <code>authorized_keys</code> 裡事先放好的公鑰。建立工作階段、在本機開轉送與登入的指令，見同一份實測紀錄的〈重建〉那一節。</li>
<li><strong>dynamic port forwarding（SOCKS5）session</strong>：跟 SSH port forwarding session 一樣不經過代理程式，差別是轉接的目標不固定，可以動態連到私有 subnet 裡的任何資源；只登入一台主機時，用 managed SSH session 或 SSH port forwarding session 就夠。</li>
</ul>
</li>
<li>
<p><strong>Google Cloud 的 IAP 加 OS Login</strong>：兩個服務各管一部分。IAP TCP forwarding 把連線轉進沒有外部 IP 的主機，誰能開這條通道由 IAM 決定，包辦網路可達性與通道授權；OS Login 在登入當下由 Google 查這個 Google 帳號登記的公鑰與 IAM 角色，決定能不能登入、以主機上的哪個 Linux 帳號登入，只包辦主機登入驗證。只開 OS Login、不用 IAP 時，連線是一般的 SSH：從 VPC 外面登入的話，主機要有外部 IP，防火牆規則要放行登入者的位址連到 22 port。兩者的機制與 OS Login 的啟用條件見 <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> 與 <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> 卡，啟用 OS Login 的指令見 <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> 的〈啟用 OS Login：先授角色、單台先行、再做專案層級〉。</p>
</li>
<li>
<p><strong>AWS 的 Systems Manager Session Manager</strong>：經由主機上的 SSM Agent 開工作階段，不經過 SSH port，網路可達性、通道授權與主機登入驗證都由 Session Manager 包辦。<strong>EC2 Instance Connect</strong> 則只包辦主機登入驗證：它把一把公鑰推送到主機的中繼資料，官方說明這把公鑰在中繼資料裡保留 60 秒；連線本身仍然是一般的 SSH，主機的 security group 要放行 22 port。</p>
</li>
</ul>
<p>這幾種服務需要的條件，依各個服務包辦的是哪幾關而不同：</p>
<ul>
<li>
<p><strong>主機上的代理程式</strong>（agent）：Oracle Cloud 的 managed SSH session 要代理程式的 Bastion plugin，在建立頁或主機頁面的 Oracle Cloud Agent 設定裡打開。AWS 的 Session Manager 要主機上有 SSM Agent 在執行，多數 AMI 預先裝了它。兩家的代理程式見 <a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a> 的〈代理程式與 plugin：業者裝在主機裡的程式〉。Oracle Cloud 的 port forwarding session 與 Google Cloud 的 IAP 不需要代理程式，OS Login 由映像預先裝好的 guest environment 處理。</p>
</li>
<li>
<p><strong>主機自己的權限</strong>：AWS 的 Systems Manager 要管理一台主機有兩個條件：主機上有 SSM Agent 在執行，以及 Systems Manager 有操作這台主機的權限。Systems Manager 預設沒有這個權限，給法有兩種，擇一即可：替主機掛一個 instance profile（主機用的 IAM 角色），或在 Systems Manager 的 Fleet Manager 打開 Default Host Management Configuration。Default Host Management Configuration 以帳號加區域為範圍，替那一區的每一台主機套用一個預設的 IAM 角色；它對一台主機生效的條件是那台主機用 IMDSv2、SSM Agent 在 3.2.582.0 以上。</p>
</li>
<li>
<p><strong>登入者的權限</strong>：登入的人在雲端帳號裡要有建立工作階段、連到這台主機的權限，由管理員在身分與權限的政策裡授予。</p>
</li>
<li>
<p><strong>連線的路徑</strong>：Oracle Cloud 的 managed SSH session 與 AWS 的 Session Manager，是主機上的代理程式主動往外連到業者的服務端點；主機在私有 subnet 時，這條往外的路經由業者的私有連線或 NAT 閘道，私有連線與 NAT 閘道有一種就夠；主機在公開 subnet 而且有公開 IP 時，經由 internet gateway。私有連線在 Oracle Cloud 是服務閘道；在 AWS 是 interface VPC endpoint，官方〈Improve the security of EC2 instances by using VPC endpoints for Systems Manager〉列出要建的有 <code>ssm</code>、<code>ssmmessages</code>、<code>ec2messages</code> 三個，這三個 endpoint 都要建（同一頁註明 SSM Agent 3.3.40.0 起，有 <code>ssmmessages</code> 可用時改走它而不走 <code>ec2messages</code>；SSM Agent 自己更新時另外要 <code>s3</code> 的 endpoint）。</p>
<p>Oracle Cloud 對 managed SSH session 的閘道要求，官方兩頁的寫法不同：〈Bastion〉概觀頁寫的是 VCN 要有服務閘道、internet gateway、NAT 閘道三者之中的一個，以及通往那個閘道的路由規則；建立 Bastion 的說明頁只寫 VCN 要有服務閘道與通往它的路由規則。VCN 有服務閘道與對應的路由規則時，兩頁的要求都滿足。</p>
<p>Oracle Cloud 的 port forwarding session 與 Google Cloud 的 IAP 是服務往裡連到主機的 22 port，主機的防火牆規則要放行來自服務那一端的連線。Oracle Cloud 那一端是建立 Bastion 時選的目標 subnet，主機的 security list 要放行來自那個 subnet 的 22 port；Google Cloud 那一端是官方 IAP TCP forwarding 說明列的 IAP 來源位址範圍 <code>35.235.240.0/20</code>。</p>
</li>
</ul>
<p>上面這幾項條件少了任一項時，主機照樣建得起來，而登入會失敗；主機上又沒有放公鑰，能進去查原因的只剩〈建立之後加金鑰與金鑰遺失時的入口〉那一節列的序列主控台、Run Command、把開機磁碟掛到另一台主機修改，以及重建。在 Oracle Cloud 上第一次建主機、還不熟這些服務時，先放自己的公鑰比較保險：代理程式或 plugin 出問題、managed SSH session 建不起來時，port forwarding session 不經過代理程式，用自己的公鑰仍然登得進去。這條退路有兩個前提：主機讀得到中繼資料（cloud-init 要從那裡讀出公鑰寫進 <code>authorized_keys</code>，中繼資料的設定見 <a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a> 的〈中繼資料服務與 Authorization header〉），以及主機的 security list 放行來自 Bastion 目標 subnet 的 22 port。managed SSH session 走通之後，這把公鑰拿掉與否取決於還有沒有另一條不經過代理程式的入口：拿掉之後只剩代理程式那一條；先建好序列主控台的救援帳號並登入過，拿掉公鑰時才仍然有兩條入口，判斷依據見 <a href="/blog/devops/infra/cloud-providers/vm-options/pre-launch-checks/" data-link-title="建立主機之前的檢查：建好就改不了的設定、登入路徑上各服務的支援組合、兩條登入路徑各自依賴的元件，以及容量稀缺時的取捨" data-link-desc="在建立頁按下建立之前，把位置、機型與映像檔、網路、登入、安全開機各篇的選項串成幾項檢查：哪些設定建立之後只能重建、怎麼查，登入用的業者服務各自支援哪些機型與作業系統、元件狀態正常為什麼不代表支援，兩條登入路徑各自依賴哪些元件、哪些仍然共用，以及機型容量難取得時設定怎麼選；Oracle Cloud、Google Cloud 與 AWS 各自查這些事的位置">建立主機之前的檢查</a> 的〈兩條登入路徑：各自依賴的元件與仍然共用的部分〉。這個做法只適用於 Oracle Cloud：Google Cloud 開了 OS Login 之後，guest agent 會忽略中繼資料裡的 SSH 公鑰。</p>
<h2 id="各家的欄位名稱金鑰欄位預設使用者與不放金鑰時的登入服務">各家的欄位名稱：金鑰欄位、預設使用者與不放金鑰時的登入服務</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>建立時放公鑰</td>
          <td>Add SSH keys</td>
          <td>SSH keys（metadata <code>ssh-keys</code>）</td>
          <td>Key pair</td>
      </tr>
      <tr>
          <td>Ubuntu 預設使用者</td>
          <td><code>ubuntu</code></td>
          <td>依 metadata 裡公鑰附帶的使用者名稱</td>
          <td><code>ubuntu</code></td>
      </tr>
      <tr>
          <td>不放金鑰時的登入服務</td>
          <td>Bastion：登入單一主機用 managed SSH session 或 SSH port forwarding session，另有 SOCKS5 session</td>
          <td>IAP TCP forwarding（連線）加 OS Login（登入驗證）</td>
          <td>Systems Manager Session Manager；EC2 Instance Connect（只推送金鑰）</td>
      </tr>
  </tbody>
</table>
<p>金鑰對了之後，連線還要通過的網路與防火牆見 <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>，SSH 連線逾時與連線被拒兩種症狀各自指向哪一層見 <a href="/blog/linux/dotfile/knowledge-cards/connection-refused-vs-timeout/" data-link-title="Connection Refused vs. Timeout（連線被拒與逾時）" data-link-desc="遠端連不上、要判斷是封包到不了還是服務拒絕、決定往網路層還是服務層除錯時回來讀">Connection Refused vs. Timeout（連線被拒與逾時）</a>；選 No SSH keys、改走 Bastion 時建立頁上要一起改的設定，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的 SSH keys 與 Management 段。</p>
]]></content:encoded></item><item><title>Google Cloud（GCP）</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/</guid><description>&lt;p>這個資料夾放 vendor 中立的概念落到 Google Cloud 上的實作，每一篇都對應一篇概念文章：管理通道的三層模型在 &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>，預算警示與停損的概念目前還寫在 GCP 那一篇裡（反應時間、兩種處置的判斷），vendor 中立的版本要從它抽出來放回 &lt;a href="https://tarrragon.github.io/blog/devops/operations/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing 與成本監控告警；預算門檻、配額與停損層級的推算在本模組，各家的建置篇目前只有 GCP，放在雲端業者模組">運行期維運的成本管理&lt;/a>，記在那個模組的 Backlog。篇中用到的建立 VM 欄位（網路與防火牆規則、登入、guest agent 與中繼資料）的 vendor 中立說明在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/" data-link-title="建立雲端虛擬機的選項" data-link-desc="站在雲端主控台的建立虛擬機頁面、不確定某一個欄位會影響什麼時，依功能分類查位置、機型與映像檔、網路、登入、磁碟、代理程式與安全開機各自的選項、建立後能不能改與對費用的影響">建立雲端虛擬機的選項&lt;/a>，試用額度與試用結束後的處置在 &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;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/hands-on/" data-link-title="Google Cloud 的實作紀錄" data-link-desc="要在 GCP 專案上照著把 SSH 與 RDP 改走 IAP、把同樣的設定套到 MIG 與 GKE，或建預算警示與停損開關時，查實際跑過的指令、輸出，以及每一步換一種做法的結果">Google Cloud 的實作紀錄&lt;/a>，每一篇概念文章對應一篇實作文章；實跑日期與未實跑的段落寫在各篇裡。&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>&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 管理通道：從預設的全開規則改走 IAP TCP forwarding&lt;/a>&lt;/td>
 &lt;td>管理通道&lt;/td>
 &lt;td>default 網路自帶規則、IAP 的連線路徑與指令、切換的順序、連不上時的依層判讀、OS Login、稽核&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&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>&lt;/td>
 &lt;td>管理通道&lt;/td>
 &lt;td>instance template 與 node pool、MIG 用重建取代登入救援、kubectl debug node 的權限邊界、控制平面存取&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&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 上為費用失控設防的機制：預算警示寄出通知的時機、範圍、抵免與通知對象、強制執行支出上限涵蓋的服務與暫停期間，以預算通知觸發函式停止用量的兩種處置（解除帳單連結、只停止花錢的資源）該選哪一種，帳單資料延遲對這些機制反應時間的影響，停損開關放在另一個專案的理由，以及驗收為什麼分成真實通知與測試訊息兩段、停用後的恢復為什麼要先調預算">GCP 上的雲端費用失控防護&lt;/a>&lt;/td>
 &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/cloud-providers/gcp/hands-on/" data-link-title="Google Cloud 的實作紀錄" data-link-desc="要在 GCP 專案上照著把 SSH 與 RDP 改走 IAP、把同樣的設定套到 MIG 與 GKE，或建預算警示與停損開關時，查實際跑過的指令、輸出，以及每一步換一種做法的結果">Google Cloud 的實作紀錄&lt;/a>：2026-10-24 在測試專案上實跑的三篇操作紀錄（SSH 與 RDP 改走 IAP：盤點、救援入口、IAP 規則與 IAM、停用與刪除舊規則、OS Login、Cloud NAT 與移除外部 IP；MIG 與 GKE：新 template 與 canary、重建、退回 template、節點除錯、node pool、控制平面端點；預算與停損開關：預算、主題、服務帳號、函式與觸發器、兩段驗收、心跳監看、恢復），概念寫在本層的三篇文章，實作文章連回來&lt;/li>
&lt;/ul>
&lt;h2 id="backlog">Backlog&lt;/h2>
&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>IAP 與 MIG、GKE 兩篇在 2026-10-24 實跑時還沒驗到的段落：IAP 篇的組織層級護欄（Organization Policy 的 &lt;code>skipDefaultNetworkCreation&lt;/code>、&lt;code>vmExternalIpAccess&lt;/code>、&lt;code>requireOsLogin&lt;/code>、&lt;code>disableSerialPortAccess&lt;/code>，以及階層式防火牆政策的 &lt;code>goto_next&lt;/code> 與拒絕規則）、組織外帳號的 &lt;code>roles/compute.osLoginExternalUser&lt;/code>、Windows VM 的序列主控台（SAC）登入；GKE 篇從未設定 &lt;code>gcpPublicCidrsAccessEnabled&lt;/code> 的叢集是否放行 GCP 外部 IP&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>有一個 Organization 資源（Cloud Identity 加自有網域）與其下的 GCP 測試專案；SAC 與 GCP 外部 IP 兩項只需要測試專案&lt;/td>
 &lt;td>中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>〈GCP 上的雲端費用失控防護〉縮成 GCP 的實作：預算門檻的依據與停到哪一層的判斷已經寫成不分業者的〈租用雲端主機之前的費用規劃〉（運維模組八），這一篇改成連過去，只留 GCP 各機制的範圍、實測與 GCP 特有的處置&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>運維模組八 Backlog 那一列（費用失控的來源與反應時間搬進新篇）完成，新篇與 cost-monitoring 指向這一篇〈費用失控的來源與反應時間〉的連結已改指新篇&lt;/td>
 &lt;td>中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>〈GCP 上的雲端費用失控防護〉剩下的未實跑部分：組織政策限制 API 與機型（要有組織節點）、只停止花錢的資源對 MIG 與 GKE node pool 的處置與它們需要的角色、&lt;code>gcloud beta monitoring channels list&lt;/code>、帳單停用超過一分鐘之後同專案函式是否還執行&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>有組織節點的測試帳號；GKE 叢集&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>實跑之後把 IAP 概念篇裡剩下的操作指令搬進實作文章：組織政策與階層式防火牆的建立與關聯、結束工作階段（&lt;code>loginctl&lt;/code>、&lt;code>pgrep&lt;/code>、&lt;code>pkill&lt;/code>）&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>第一列的組織護欄實跑過&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>〈GCP 預算警示與停損開關的實測紀錄〉補三項：只停止花錢的資源那一種的函式本體（未寫）與實測描述（在概念篇）、心跳監看的日誌指標與告警政策的完整指令、從預算拿掉 Pub/Sub 主題的 PATCH 指令&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>測試專案；心跳監看與 PATCH 重跑一次才有完整指令&lt;/td>
 &lt;td>小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2026-10-25 第三輪審查列出、還沒查證也沒寫進文章的替代做法，查證或實跑之後寫進對應的「替代做法」與概念篇：IAP 篇用 network firewall policy 加 secure tags 選 VM、用 Firewall Insights 盤點規則的命中、&lt;code>gcloud compute ssh --troubleshoot&lt;/code>、Windows 的 IAP Desktop、用 Privileged Access Manager 代替手寫 &lt;code>request.time&lt;/code> 條件、不放常駐救援帳號而改用不含祕密的 startup-script 或卸下磁碟；MIG 與 GKE 篇的 Connect Gateway、Autopilot 適用的條件、authorized networks 在固定出口 IP 環境下適用的條件；停損篇不經 Eventarc 而用 Pub/Sub push subscription 加 dead-letter topic、對用量指標設告警、程式化停用 API 或撤銷 API 金鑰、在可拋棄的專案上用極小預算整條驗收&lt;/td>
 &lt;td>主章&lt;/td>
 &lt;td>測試專案；Privileged Access Manager 與 Connect Gateway 要先回官方文件確認適用範圍&lt;/td>
 &lt;td>中&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>這個資料夾放 vendor 中立的概念落到 Google Cloud 上的實作，每一篇都對應一篇概念文章：管理通道的三層模型在 <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>，預算警示與停損的概念目前還寫在 GCP 那一篇裡（反應時間、兩種處置的判斷），vendor 中立的版本要從它抽出來放回 <a href="/blog/devops/operations/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing 與成本監控告警；預算門檻、配額與停損層級的推算在本模組，各家的建置篇目前只有 GCP，放在雲端業者模組">運行期維運的成本管理</a>，記在那個模組的 Backlog。篇中用到的建立 VM 欄位（網路與防火牆規則、登入、guest agent 與中繼資料）的 vendor 中立說明在 <a href="/blog/devops/infra/cloud-providers/vm-options/" data-link-title="建立雲端虛擬機的選項" data-link-desc="站在雲端主控台的建立虛擬機頁面、不確定某一個欄位會影響什麼時，依功能分類查位置、機型與映像檔、網路、登入、磁碟、代理程式與安全開機各自的選項、建立後能不能改與對費用的影響">建立雲端虛擬機的選項</a>，試用額度與試用結束後的處置在 <a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較</a>。三篇概念文章寫機制與判斷依據，逐步的指令、實跑結果與每一步的替代做法放在 <a href="/blog/devops/infra/cloud-providers/gcp/hands-on/" data-link-title="Google Cloud 的實作紀錄" data-link-desc="要在 GCP 專案上照著把 SSH 與 RDP 改走 IAP、把同樣的設定套到 MIG 與 GKE，或建預算警示與停損開關時，查實際跑過的指令、輸出，以及每一步換一種做法的結果">Google Cloud 的實作紀錄</a>，每一篇概念文章對應一篇實作文章；實跑日期與未實跑的段落寫在各篇裡。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>對應的概念</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><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 管理通道：從預設的全開規則改走 IAP TCP forwarding</a></td>
          <td>管理通道</td>
          <td>default 網路自帶規則、IAP 的連線路徑與指令、切換的順序、連不上時的依層判讀、OS Login、稽核</td>
      </tr>
      <tr>
          <td><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></td>
          <td>管理通道</td>
          <td>instance template 與 node pool、MIG 用重建取代登入救援、kubectl debug node 的權限邊界、控制平面存取</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/gcp/spend-protection-gcp-budget-killswitch/" data-link-title="GCP 上的雲端費用失控防護：預算警示、強制執行支出上限與自動停用帳單的作用範圍、反應時間與判斷" data-link-desc="GCP 上為費用失控設防的機制：預算警示寄出通知的時機、範圍、抵免與通知對象、強制執行支出上限涵蓋的服務與暫停期間，以預算通知觸發函式停止用量的兩種處置（解除帳單連結、只停止花錢的資源）該選哪一種，帳單資料延遲對這些機制反應時間的影響，停損開關放在另一個專案的理由，以及驗收為什麼分成真實通知與測試訊息兩段、停用後的恢復為什麼要先調預算">GCP 上的雲端費用失控防護</a></td>
          <td>成本管理</td>
          <td>預算警示、強制執行支出上限、兩種停止用量的處置該選哪一種、驗收與恢復的順序</td>
      </tr>
  </tbody>
</table>
<h2 id="子資料夾">子資料夾</h2>
<ul>
<li><a href="/blog/devops/infra/cloud-providers/gcp/hands-on/" data-link-title="Google Cloud 的實作紀錄" data-link-desc="要在 GCP 專案上照著把 SSH 與 RDP 改走 IAP、把同樣的設定套到 MIG 與 GKE，或建預算警示與停損開關時，查實際跑過的指令、輸出，以及每一步換一種做法的結果">Google Cloud 的實作紀錄</a>：2026-10-24 在測試專案上實跑的三篇操作紀錄（SSH 與 RDP 改走 IAP：盤點、救援入口、IAP 規則與 IAM、停用與刪除舊規則、OS Login、Cloud NAT 與移除外部 IP；MIG 與 GKE：新 template 與 canary、重建、退回 template、節點除錯、node pool、控制平面端點；預算與停損開關：預算、主題、服務帳號、函式與觸發器、兩段驗收、心跳監看、恢復），概念寫在本層的三篇文章，實作文章連回來</li>
</ul>
<h2 id="backlog">Backlog</h2>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>類型</th>
          <th>前置條件</th>
          <th>規模</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>IAP 與 MIG、GKE 兩篇在 2026-10-24 實跑時還沒驗到的段落：IAP 篇的組織層級護欄（Organization Policy 的 <code>skipDefaultNetworkCreation</code>、<code>vmExternalIpAccess</code>、<code>requireOsLogin</code>、<code>disableSerialPortAccess</code>，以及階層式防火牆政策的 <code>goto_next</code> 與拒絕規則）、組織外帳號的 <code>roles/compute.osLoginExternalUser</code>、Windows VM 的序列主控台（SAC）登入；GKE 篇從未設定 <code>gcpPublicCidrsAccessEnabled</code> 的叢集是否放行 GCP 外部 IP</td>
          <td>主章</td>
          <td>有一個 Organization 資源（Cloud Identity 加自有網域）與其下的 GCP 測試專案；SAC 與 GCP 外部 IP 兩項只需要測試專案</td>
          <td>中</td>
      </tr>
      <tr>
          <td>〈GCP 上的雲端費用失控防護〉縮成 GCP 的實作：預算門檻的依據與停到哪一層的判斷已經寫成不分業者的〈租用雲端主機之前的費用規劃〉（運維模組八），這一篇改成連過去，只留 GCP 各機制的範圍、實測與 GCP 特有的處置</td>
          <td>主章</td>
          <td>運維模組八 Backlog 那一列（費用失控的來源與反應時間搬進新篇）完成，新篇與 cost-monitoring 指向這一篇〈費用失控的來源與反應時間〉的連結已改指新篇</td>
          <td>中</td>
      </tr>
      <tr>
          <td>〈GCP 上的雲端費用失控防護〉剩下的未實跑部分：組織政策限制 API 與機型（要有組織節點）、只停止花錢的資源對 MIG 與 GKE node pool 的處置與它們需要的角色、<code>gcloud beta monitoring channels list</code>、帳單停用超過一分鐘之後同專案函式是否還執行</td>
          <td>主章</td>
          <td>有組織節點的測試帳號；GKE 叢集</td>
          <td>小</td>
      </tr>
      <tr>
          <td>實跑之後把 IAP 概念篇裡剩下的操作指令搬進實作文章：組織政策與階層式防火牆的建立與關聯、結束工作階段（<code>loginctl</code>、<code>pgrep</code>、<code>pkill</code>）</td>
          <td>主章</td>
          <td>第一列的組織護欄實跑過</td>
          <td>小</td>
      </tr>
      <tr>
          <td>〈GCP 預算警示與停損開關的實測紀錄〉補三項：只停止花錢的資源那一種的函式本體（未寫）與實測描述（在概念篇）、心跳監看的日誌指標與告警政策的完整指令、從預算拿掉 Pub/Sub 主題的 PATCH 指令</td>
          <td>主章</td>
          <td>測試專案；心跳監看與 PATCH 重跑一次才有完整指令</td>
          <td>小</td>
      </tr>
      <tr>
          <td>2026-10-25 第三輪審查列出、還沒查證也沒寫進文章的替代做法，查證或實跑之後寫進對應的「替代做法」與概念篇：IAP 篇用 network firewall policy 加 secure tags 選 VM、用 Firewall Insights 盤點規則的命中、<code>gcloud compute ssh --troubleshoot</code>、Windows 的 IAP Desktop、用 Privileged Access Manager 代替手寫 <code>request.time</code> 條件、不放常駐救援帳號而改用不含祕密的 startup-script 或卸下磁碟；MIG 與 GKE 篇的 Connect Gateway、Autopilot 適用的條件、authorized networks 在固定出口 IP 環境下適用的條件；停損篇不經 Eventarc 而用 Pub/Sub push subscription 加 dead-letter topic、對用量指標設告警、程式化停用 API 或撤銷 API 金鑰、在可拋棄的專案上用極小預算整條驗收</td>
          <td>主章</td>
          <td>測試專案；Privileged Access Manager 與 Connect Gateway 要先回官方文件確認適用範圍</td>
          <td>中</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/</guid><description>&lt;p>實測日期是 2026-10-24 與 2026-10-25，區域 &lt;code>ap-batam-1&lt;/code>，帳號是 Oracle Cloud 的 Free Tier，在本機用 OCI CLI 與主控台建立一台 A1 Ubuntu 24.04 主機，再從本機經由 Bastion 建立登入的工作階段；2026-10-25 下午另外用 Run Command 對同一台主機送指令；同一天晚上另建一台 A1 Oracle Linux 10.2 主機，建立時放公鑰，經由 Bastion 的 port forwarding session 登入。&lt;/p>
&lt;p>這篇記錄在 Oracle Cloud 建立一台主機的實際操作，目標是 Always Free 額度內、沒有公開 IP、之後經由 Bastion 服務登入的 A1 Ubuntu 主機。Bastion 是 Oracle Cloud 代管的登入服務：建立 Bastion 時指定一個目標 subnet，它替那個 subnet 裡沒有公開 IP 的主機開有時效的 SSH 工作階段（session）；這次要用的 managed SSH session 這種工作階段，由主機上 Oracle Cloud Agent 的 Bastion plugin 在建立工作階段時把一把公鑰臨時放進主機。工作階段的種類與需要的條件見 &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> 的〈不放金鑰時的登入服務〉。內容依操作的順序是：建立之前的檢查（哪些設定建好就改不了、登入路徑上每個服務支援哪些機型與作業系統、登入路徑要有兩條、各自依賴哪些元件、容量稀缺時怎麼選設定）；前置的 compartment、VCN、service gateway 與 private subnet；主控台建立頁 Basic information、Security、Networking、SSH keys、Storage、Management 各分頁的取值與理由，含 Oracle Cloud Agent 各 plugin 的用途；等效的 CLI 指令；按下 Create 之後的容量不足，CLI 自己的重送、這個帳號量到的限流門檻與依回應碼調整間隔的重送腳本；主機建好之後建立 Bastion 與 managed SSH session、工作階段建立逾時的排查與官方列出的成因；用 Run Command 補公鑰時建立的 dynamic group 與 IAM 政策、送指令與讀結果的指令，以及 &lt;code>ocarun&lt;/code> 沒有 sudo 的結果；這台主機剩下的登入入口；改用 Oracle Linux 映像、建立時放公鑰重建一台：建立之前依作業系統版本查可用的 plugin、指定大版本查映像、Oracle Linux 10.2 主機上沒有 Bastion plugin 的結果，以及經由 port forwarding session 登入與比對主機金鑰的指令；以及刪除這組資源的順序。每一節除了這次的取值，也寫出那一步沒做時會看到什麼、有哪些替代做法與各自換到什麼。每個欄位背後的概念連到 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/" data-link-title="建立雲端虛擬機的選項" data-link-desc="站在雲端主控台的建立虛擬機頁面、不確定某一個欄位會影響什麼時，依功能分類查位置、機型與映像檔、網路、登入、磁碟、代理程式與安全開機各自的選項、建立後能不能改與對費用的影響">建立雲端虛擬機的選項&lt;/a> 底下對應的那一篇，這裡只寫 Oracle Cloud 的欄位、這次的取值與實際跑過的指令。&lt;/p>
&lt;p>這次的實測沒有在建立之前做那幾項檢查，結果是主機建好之後登不進去：managed SSH session 逾時三次，Run Command 執行得了指令卻沒有 sudo，主機又沒有公鑰。所以〈建立之前的檢查：套在這台主機上的查法與結果〉是事後補上的檢查，最後是照那幾項檢查重建一台才登入成功。&lt;/p>
&lt;h2 id="建立之前的檢查套在這台主機上的查法與結果">建立之前的檢查：套在這台主機上的查法與結果&lt;/h2>
&lt;p>這些檢查的判斷依據在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/pre-launch-checks/" data-link-title="建立主機之前的檢查：建好就改不了的設定、登入路徑上各服務的支援組合、兩條登入路徑各自依賴的元件，以及容量稀缺時的取捨" data-link-desc="在建立頁按下建立之前，把位置、機型與映像檔、網路、登入、安全開機各篇的選項串成幾項檢查：哪些設定建立之後只能重建、怎麼查，登入用的業者服務各自支援哪些機型與作業系統、元件狀態正常為什麼不代表支援，兩條登入路徑各自依賴哪些元件、哪些仍然共用，以及機型容量難取得時設定怎麼選；Oracle Cloud、Google Cloud 與 AWS 各自查這些事的位置">建立主機之前的檢查&lt;/a>，這一節是套在這台主機（A1、Ubuntu 24.04、private subnet、經由 Bastion 登入）上的查法與結果。這些檢查排在進建立頁之前，因為結果會改變建立頁上的選擇：映像檔、SSH keys 欄位與要打開的 plugin。&lt;/p>
&lt;p>&lt;strong>建好就改不了的設定&lt;/strong>。用 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">oci compute instance update --help &lt;span class="p">|&lt;/span> sed -n &lt;span class="s1">&amp;#39;/--metadata/,/--extended-metadata/p&amp;#39;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>說明裡寫明 &lt;code>user_data&lt;/code> 與 &lt;code>ssh_authorized_keys&lt;/code> 建立後不能改，送出修改的請求會被拒絕。這台的設定裡落在這一種的是 SSH keys（選了 No SSH keys）、初始化腳本（沒放）、映像檔（Ubuntu；替換開機磁碟只能換同一個發行版）與 Shielded instance（Secure Boot、Measured Boot、Trusted Platform Module 都關閉）。指令裡 &lt;code>sed -n&lt;/code> 的範圍從 &lt;code>--metadata&lt;/code> 印到下一個選項 &lt;code>--extended-metadata&lt;/code>，這是本機這一版 CLI 的選項順序；版本不同、順序不同時，把結束的 pattern 換成 &lt;code>--metadata&lt;/code> 之後的那一個選項。&lt;/p>
&lt;p>&lt;strong>登入路徑上各服務的支援組合&lt;/strong>。這台規劃的 managed SSH session、不經過 plugin 的 port forwarding session，與事後想用的 Run Command，在官方文件上的結果：&lt;/p></description><content:encoded><![CDATA[<p>實測日期是 2026-10-24 與 2026-10-25，區域 <code>ap-batam-1</code>，帳號是 Oracle Cloud 的 Free Tier，在本機用 OCI CLI 與主控台建立一台 A1 Ubuntu 24.04 主機，再從本機經由 Bastion 建立登入的工作階段；2026-10-25 下午另外用 Run Command 對同一台主機送指令；同一天晚上另建一台 A1 Oracle Linux 10.2 主機，建立時放公鑰，經由 Bastion 的 port forwarding session 登入。</p>
<p>這篇記錄在 Oracle Cloud 建立一台主機的實際操作，目標是 Always Free 額度內、沒有公開 IP、之後經由 Bastion 服務登入的 A1 Ubuntu 主機。Bastion 是 Oracle Cloud 代管的登入服務：建立 Bastion 時指定一個目標 subnet，它替那個 subnet 裡沒有公開 IP 的主機開有時效的 SSH 工作階段（session）；這次要用的 managed SSH session 這種工作階段，由主機上 Oracle Cloud Agent 的 Bastion plugin 在建立工作階段時把一把公鑰臨時放進主機。工作階段的種類與需要的條件見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉。內容依操作的順序是：建立之前的檢查（哪些設定建好就改不了、登入路徑上每個服務支援哪些機型與作業系統、登入路徑要有兩條、各自依賴哪些元件、容量稀缺時怎麼選設定）；前置的 compartment、VCN、service gateway 與 private subnet；主控台建立頁 Basic information、Security、Networking、SSH keys、Storage、Management 各分頁的取值與理由，含 Oracle Cloud Agent 各 plugin 的用途；等效的 CLI 指令；按下 Create 之後的容量不足，CLI 自己的重送、這個帳號量到的限流門檻與依回應碼調整間隔的重送腳本；主機建好之後建立 Bastion 與 managed SSH session、工作階段建立逾時的排查與官方列出的成因；用 Run Command 補公鑰時建立的 dynamic group 與 IAM 政策、送指令與讀結果的指令，以及 <code>ocarun</code> 沒有 sudo 的結果；這台主機剩下的登入入口；改用 Oracle Linux 映像、建立時放公鑰重建一台：建立之前依作業系統版本查可用的 plugin、指定大版本查映像、Oracle Linux 10.2 主機上沒有 Bastion plugin 的結果，以及經由 port forwarding session 登入與比對主機金鑰的指令；以及刪除這組資源的順序。每一節除了這次的取值，也寫出那一步沒做時會看到什麼、有哪些替代做法與各自換到什麼。每個欄位背後的概念連到 <a href="/blog/devops/infra/cloud-providers/vm-options/" data-link-title="建立雲端虛擬機的選項" data-link-desc="站在雲端主控台的建立虛擬機頁面、不確定某一個欄位會影響什麼時，依功能分類查位置、機型與映像檔、網路、登入、磁碟、代理程式與安全開機各自的選項、建立後能不能改與對費用的影響">建立雲端虛擬機的選項</a> 底下對應的那一篇，這裡只寫 Oracle Cloud 的欄位、這次的取值與實際跑過的指令。</p>
<p>這次的實測沒有在建立之前做那幾項檢查，結果是主機建好之後登不進去：managed SSH session 逾時三次，Run Command 執行得了指令卻沒有 sudo，主機又沒有公鑰。所以〈建立之前的檢查：套在這台主機上的查法與結果〉是事後補上的檢查，最後是照那幾項檢查重建一台才登入成功。</p>
<h2 id="建立之前的檢查套在這台主機上的查法與結果">建立之前的檢查：套在這台主機上的查法與結果</h2>
<p>這些檢查的判斷依據在 <a href="/blog/devops/infra/cloud-providers/vm-options/pre-launch-checks/" data-link-title="建立主機之前的檢查：建好就改不了的設定、登入路徑上各服務的支援組合、兩條登入路徑各自依賴的元件，以及容量稀缺時的取捨" data-link-desc="在建立頁按下建立之前，把位置、機型與映像檔、網路、登入、安全開機各篇的選項串成幾項檢查：哪些設定建立之後只能重建、怎麼查，登入用的業者服務各自支援哪些機型與作業系統、元件狀態正常為什麼不代表支援，兩條登入路徑各自依賴哪些元件、哪些仍然共用，以及機型容量難取得時設定怎麼選；Oracle Cloud、Google Cloud 與 AWS 各自查這些事的位置">建立主機之前的檢查</a>，這一節是套在這台主機（A1、Ubuntu 24.04、private subnet、經由 Bastion 登入）上的查法與結果。這些檢查排在進建立頁之前，因為結果會改變建立頁上的選擇：映像檔、SSH keys 欄位與要打開的 plugin。</p>
<p><strong>建好就改不了的設定</strong>。用 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">oci compute instance update --help <span class="p">|</span> sed -n <span class="s1">&#39;/--metadata/,/--extended-metadata/p&#39;</span></span></span></code></pre></div><p>說明裡寫明 <code>user_data</code> 與 <code>ssh_authorized_keys</code> 建立後不能改，送出修改的請求會被拒絕。這台的設定裡落在這一種的是 SSH keys（選了 No SSH keys）、初始化腳本（沒放）、映像檔（Ubuntu；替換開機磁碟只能換同一個發行版）與 Shielded instance（Secure Boot、Measured Boot、Trusted Platform Module 都關閉）。指令裡 <code>sed -n</code> 的範圍從 <code>--metadata</code> 印到下一個選項 <code>--extended-metadata</code>，這是本機這一版 CLI 的選項順序；版本不同、順序不同時，把結束的 pattern 換成 <code>--metadata</code> 之後的那一個選項。</p>
<p><strong>登入路徑上各服務的支援組合</strong>。這台規劃的 managed SSH session、不經過 plugin 的 port forwarding session，與事後想用的 Run Command，在官方文件上的結果：</p>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>要讀的文件或查詢</th>
          <th>A1 加 Ubuntu 的結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bastion managed SSH session</td>
          <td>〈Known Issues for Bastion〉第一條</td>
          <td>不支援</td>
      </tr>
      <tr>
          <td>Bastion plugin 在這個作業系統版本上可不可用</td>
          <td><code>oci instance-agent available-plugins get</code>（依版本查）</td>
          <td>2026-10-25 查 Ubuntu 24.04 有；這一層不分機型，A1 的限制看上一列</td>
      </tr>
      <tr>
          <td>Bastion SSH port forwarding session</td>
          <td>同 managed SSH session；它不經過 plugin</td>
          <td>支援，條件是主機裡有公鑰</td>
      </tr>
      <tr>
          <td>Compute Instance Run Command</td>
          <td>Run Command 說明頁的支援映像清單</td>
          <td>清單上沒有 Ubuntu；實測執行得了指令，而 <code>ocarun</code> 沒有 sudo</td>
      </tr>
  </tbody>
</table>
<p>這幾項在建立之前都查得到，Run Command 要事先替 <code>ocarun</code> 設 sudo、第一次建主機先放公鑰比較保險這兩點，<a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 也有寫。2026-10-25 的建立沿用 2026-10-24 寫好的重送腳本，腳本的參數寫在讀這些文件之前，重新啟動時沒有再對一次。Known Issues 只寫到作業系統，沒有寫版本，而同一個作業系統的不同版本可用的 plugin 不同：後來重建時，2026-10-25 查到 Oracle Linux 10 確定沒有 Bastion plugin，9 與 8 的結果跟查不到時的回應相同、分不出來；這份清單隨代理程式的版本更新，要在建立當下自己查一次，查法見〈建立之前查映像檔版本與它可用的 plugin〉。</p>
<p><strong>兩條登入路徑各自依賴的元件</strong>。這台只規劃了 managed SSH session 一條，它依賴 Bastion plugin 與支援範圍，而這次最沒有把握的正是這兩項。不經過 plugin 的 port forwarding session 要主機裡有公鑰，選了 No SSH keys 就沒有；兩種工作階段都經過 Bastion、security list 的 22 port 與主機的 sshd，所以有公鑰時，這一組擋得住 plugin 與支援範圍的問題，擋不住 sshd 改壞。不經過 sshd 的是序列主控台，它要一個設了密碼的帳號，Ubuntu 平台映像的 <code>ubuntu</code> 只用金鑰登入，要用初始化腳本在建立時建，而初始化腳本也是建立後不能改的欄位。所以這台建立的那一刻就只剩一條 SSH 路徑，而那條路徑是不支援的組合；剩下的入口是替換開機磁碟、把開機磁碟掛到另一台主機修改、序列主控台與重建，各自的條件見〈這台主機剩下的登入入口〉。</p>
<p><strong>容量稀缺時的取捨</strong>。A1 在這一區的容量要靠重送腳本搶（〈重送建立請求：CLI 自己的重送、限流門檻與重送腳本〉），選錯建立後不能改的設定，補救就是再搶一次。照這一項，這台的設定該選補救代價低的那一種：放自己的公鑰、或改用 Oracle Linux 映像（A1 加 Oracle Linux 是 managed SSH session 支援的組合，版本要選可用 plugin 裡有 Bastion 的那一個）。「完全不放金鑰」裡跟機型無關的部分（Bastion 與 plugin 的設定、security list）可以在容易重建的機型上驗，而 A1 加 Ubuntu 這個組合支不支援只能讀 Known Issues，演練驗不到。這一區的 Change shape 視窗沒有列出 E2.1.Micro（見〈Basic information：名稱、compartment、位置、映像檔與機型〉），所以這個帳號找不到容易重建的免費機型；這時的做法是建這台時就選 A1 加 Oracle Linux 這個支援的組合來驗「完全不放金鑰」；它同樣要搶 A1 的容量，差別在組合不支援的風險已經由 Known Issues 排除，失敗的代價同樣是再搶一次。</p>
<h2 id="進建立頁之前compartmentvcn閘道與-private-subnet">進建立頁之前：compartment、VCN、閘道與 private subnet</h2>
<p>建立頁上的網路欄位只能選已經存在的 VCN 與 subnet，或讓主控台建一個新的（新建的那個會帶一份對網際網路開放 22 port 的預設規則，見〈Networking：VCN、subnet 與公開 IP〉）。這次先用 OCI CLI 建好下面幾項，建立頁上一律選既有的。CLI 的登入方式見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/" data-link-title="從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰" data-link-desc="OCI CLI 的兩種身分驗證方式——瀏覽器登入換來的 session token 與上傳公鑰的 API 簽章金鑰——各自把什麼存在本機、有效多久、外洩時的影響範圍，以及登入時的互動提示與錯誤訊息怎麼讀">從本機用 OCI CLI 操作 Oracle Cloud</a>，主控台上的對應頁面是 Identity &amp; Security → Identity → Compartments 與 Networking → Virtual cloud networks。</p>
<ul>
<li><strong>compartment <code>blog-verify</code></strong>：tenancy 底下分組資源的容器，實驗用的資源都放在這裡，清理時不碰 root 裡的其他設定。</li>
<li><strong>VCN <code>verify-vcn</code></strong>（<code>10.0.0.0/16</code>，位址範圍的寫法見 <a href="/blog/devops/infra/knowledge-cards/cidr/" data-link-title="CIDR（Classless Inter-Domain Routing）" data-link-desc="用前綴長度表示 IP 地址範圍的表示法，決定 VPC 與 subnet 的地址空間大小">CIDR</a> 卡）：主機所在的虛擬網路。</li>
<li><strong>service gateway 與路由表 <code>private-rt</code></strong>：私有 subnet 沒有通往網際網路的路由，主機上的 Oracle Cloud Agent 要連到 Oracle 自己的服務端點，經由 service gateway 出去；路由表裡那一條規則把送往 Oracle 服務網段的流量導向 service gateway。managed SSH session 對閘道與路由的要求見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入</a> 的〈不放金鑰時的登入服務〉，這次在其中選了 service gateway。</li>
</ul>
<p>這份網路刻意沒有 NAT 閘道，主機只用來測 Bastion 登入；沒有 NAT 時，主機連不到 Ubuntu 的套件來源，<code>apt</code> 更新與安裝都會失敗。要當一般主機用，就另外建一個 NAT 閘道，在路由表加一條通往它的路由，並在 security list 的出站規則放行網際網路。這三項在 Ubuntu 24.04 主機上實際加過一次，出站規則改放在只掛在那台主機上的 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>〈加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG〉。</p>
<ul>
<li><strong>security list <code>private-sl</code></strong>：入站只放行來源是同一個 subnet（<code>10.0.1.0/24</code>）的 22 port，因為 Bastion 的目標 subnet 設成這個 subnet，經由 Bastion 的 SSH 連線從這個 subnet 進來；出站只放行送往 Oracle 服務網段與同一個 subnet 的流量。</li>
<li><strong>subnet <code>private-subnet</code></strong>（<code>10.0.1.0/24</code>）：建立時設定禁止公開 IP，並掛上上面的路由表與 security list。</li>
</ul>





<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"># 以下每條都以 --wait-for-state 等資源建好，再用 --query 取出新資源的 OCID 給下一條用</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci iam compartment create --compartment-id &lt;tenancy 的 OCID&gt; --name blog-verify --description <span class="s2">&#34;&lt;用途說明&gt;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">oci network vcn create -c &lt;compartment 的 OCID&gt; --cidr-blocks <span class="s1">&#39;[&#34;10.0.0.0/16&#34;]&#39;</span> --display-name verify-vcn --dns-label verifyvcn
</span></span><span class="line"><span class="ln">4</span><span class="cl">oci network service-gateway create -c &lt;compartment 的 OCID&gt; --vcn-id &lt;VCN 的 OCID&gt; --services <span class="s1">&#39;[{&#34;serviceId&#34;:&#34;&lt;All Services 的 service ID&gt;&#34;}]&#39;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">oci network route-table create -c &lt;compartment 的 OCID&gt; --vcn-id &lt;VCN 的 OCID&gt; --display-name private-rt <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --route-rules <span class="s1">&#39;[{&#34;destination&#34;:&#34;&lt;All Services 的網段&gt;&#34;,&#34;destinationType&#34;:&#34;SERVICE_CIDR_BLOCK&#34;,&#34;networkEntityId&#34;:&#34;&lt;service gateway 的 OCID&gt;&#34;}]&#39;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">oci network subnet create -c &lt;compartment 的 OCID&gt; --vcn-id &lt;VCN 的 OCID&gt; --cidr-block 10.0.1.0/24 --display-name private-subnet <span class="se">\
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="se"></span>  --dns-label priv --prohibit-public-ip-on-vnic <span class="nb">true</span> --route-table-id &lt;路由表的 OCID&gt; --security-list-ids <span class="s1">&#39;[&#34;&lt;security list 的 OCID&gt;&#34;]&#39;</span></span></span></code></pre></div><ul>
<li><code>&lt;All Services 的 service ID&gt;</code> 與 <code>&lt;All Services 的網段&gt;</code>：用 <code>oci network service list</code> 查，名稱帶「All」的那一筆就是 Oracle 全部服務的網段，service gateway 與路由規則都指向它。</li>
<li>security list 用 <code>oci network security-list create</code> 建，入站與出站規則以 JSON 給，內容照上面清單的那兩條。</li>
<li><code>--prohibit-public-ip-on-vnic true</code>：這個 subnet 裡的網卡不能指派公開 IP，也就是 private subnet。</li>
<li>網段 <code>10.0.0.0/16</code> 與 <code>10.0.1.0/24</code> 是這次選的私有位址，換成自己規劃的也可以，security list 的來源要跟著改成 subnet 的網段。</li>
</ul>
<p>這幾項各自沒做時的結果與替代做法：</p>
<ul>
<li><strong>不建 compartment、資源放在 root</strong>：主機照樣建立成功（照文件推出，這次沒有在 root 建）。清理時要在 root 的資源清單裡逐項挑出這次建的，權限政策也只能寫成整個 tenancy 的範圍；〈Run Command 補公鑰的嘗試：dynamic group、IAM 政策與 ocarun 沒有 sudo 的結果〉那條政策寫成 <code>in compartment blog-verify</code>，範圍就只到這組實驗資源。</li>
<li><strong>不用 private subnet、改用公開 subnet 加公開 IP</strong>：主機有公開 IP，security list 只放行自己的公開 IP 連 22 port，就能用 <code>ssh</code> 直接登入，不需要 Bastion（照文件推出，這次沒有建公開 subnet）；代價是主機的位址暴露在網際網路上，家用網路的公開 IP 改變時規則要跟著改。兩種做法對管理通道各層的影響見 <a href="/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路</a> 的〈公開 IP：網際網路上的入口〉與 <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><strong>不建 service gateway 與它的路由</strong>：主機上的 Oracle Cloud Agent 連不到 Oracle 的服務端點。官方〈Managed SSH Session Creation Failed〉寫明這時 Bastion plugin 的狀態是 <code>INVALID</code>、managed SSH session 建不起來；Run Command 的說明頁只對腳本或輸出放在 Object Storage 的用法寫了出站條件，純文字的用法同樣要讓 plugin 連得到服務端點才領得到指令，這一點是推論，這次沒有在缺閘道的情況下實跑。替代做法是 NAT 閘道：它同樣讓代理程式連得到服務端點，另外讓主機連得到網際網路上的套件來源。</li>
<li><strong>security list 的 22 port 放行 <code>0.0.0.0/0</code></strong>：這個 subnet 禁止公開 IP，網際網路上的來源本來就到不了主機，所以結果相同（從網路設定推出，這次沒有改成全開驗證）；收窄成同一個 subnet，是讓這份 security list 之後被掛到另一個允許公開 IP 的 subnet 時，那個 subnet 的 22 port 不會跟著對網際網路開放。</li>
</ul>
<h2 id="basic-information名稱compartment位置映像檔與機型">Basic information：名稱、compartment、位置、映像檔與機型</h2>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>這次的值</th>
          <th>理由與對應的概念篇</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Name</td>
          <td><code>verify-a1</code></td>
          <td>自動產生的 <code>instance-&lt;日期&gt;-&lt;時間&gt;</code> 在清單裡認不出用途</td>
      </tr>
      <tr>
          <td>Create in compartment</td>
          <td><code>blog-verify</code></td>
          <td>實驗資源集中在一個 compartment，清理時不碰 root 裡的設定</td>
      </tr>
      <tr>
          <td>Availability domain</td>
          <td>AD-1</td>
          <td>這一區只有一個 AD，見 <a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a></td>
      </tr>
      <tr>
          <td>Capacity type</td>
          <td>On-demand</td>
          <td>官方 Always Free 頁列額度時沒有提到 preemptible，選它時額度怎麼算查不到原文；preemptible 會被收回，見 <a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統</a></td>
      </tr>
      <tr>
          <td>Fault domain</td>
          <td>Let Oracle choose the best fault domain</td>
          <td>單一台主機指定故障域沒有好處，還會縮小可用容量</td>
      </tr>
      <tr>
          <td>Image</td>
          <td>Canonical Ubuntu 24.04（build 2026.09.18-0）</td>
          <td>Oracle 維護的平台映像，架構跟著機型選 aarch64</td>
      </tr>
      <tr>
          <td>Shape</td>
          <td><code>VM.Standard.A1.Flex</code>，1 OCPU、6 GB</td>
          <td>Always Free 的 A1 上限是 2 OCPU、12 GB，先用一半，另一半留給第二台主機；之後要加大不必重建，代價是重開機</td>
      </tr>
  </tbody>
</table>
<p>「Change shape」的視窗依處理器分欄。這一區的 Ampere 欄有 <code>VM.Standard.A1.Flex</code> 並標示 Always Free-eligible；AMD 欄只列出 <code>VM.Standard.E5.Flex</code>，Always Free 的 <code>VM.Standard.E2.1.Micro</code> 不在清單上。E5.Flex 不在免費額度內，選了之後的結果依帳號狀態而定（試用中由試用金支付、試用結束而沒有升級的帳號建不了、付費帳戶照一般價格計費），見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果〉；區域之間提供的機型不同，見同一篇的〈E2.1.Micro 在各區域的供應：服務配額表與 404 錯誤〉。</p>
<p>這一頁影響後面結果最大的是映像檔。這次選 Ubuntu，A1 加 Ubuntu 落在 managed SSH session 不支援的組合裡（〈建立之前的檢查：套在這台主機上的查法與結果〉）。替代做法是 Oracle Linux 的 aarch64 平台映像：A1 加 Oracle Linux 是 managed SSH session 支援的組合，條件是選的版本可用的 plugin 裡有 Bastion（2026-10-25 實測 10 確定沒有，9 與 8 用查詢工具分不出，查法與讀法見〈建立之前查映像檔版本與它可用的 plugin〉），Oracle Linux 也在 Run Command 的支援映像清單上；換到的代價是預設使用者改成 <code>opc</code>、套件管理改用 <code>dnf</code>，照 Ubuntu 寫的教學指令要換寫法。映像檔建立之後只能換成同一個發行版（〈Replacing a Boot Volume〉），所以這一欄選錯，補救就是重建。</p>
<h2 id="securityshielded-instance">Security：Shielded instance</h2>
<p>Secure Boot、Measured Boot、Trusted Platform Module 三項預設都是 Disabled，這次維持關閉。三項在建立之後不能改，要測試這個功能時另外建一台；判斷要不要開見 <a href="/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機：Secure Boot、Measured Boot 與 TPM</a>。</p>
<h2 id="networkingvcnsubnet-與公開-ip">Networking：VCN、subnet 與公開 IP</h2>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>這次的值</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Primary network</td>
          <td>Select existing virtual cloud network</td>
          <td>用前置步驟建好的 VCN，避開新建 VCN 帶的預設規則</td>
      </tr>
      <tr>
          <td>Virtual cloud network compartment</td>
          <td><code>blog-verify</code></td>
          <td>VCN 建在這個 compartment，選單只列出這一欄選定的 compartment 裡的 VCN</td>
      </tr>
      <tr>
          <td>Virtual cloud network</td>
          <td><code>verify-vcn</code></td>
          <td>前置步驟建的 VCN</td>
      </tr>
      <tr>
          <td>Subnet</td>
          <td>Select existing subnet → <code>private-subnet</code></td>
          <td>禁止公開 IP 的 subnet，主機不暴露在網際網路上</td>
      </tr>
      <tr>
          <td>Private IPv4 address</td>
          <td>Automatically assign</td>
          <td>單一台主機不需要固定位址</td>
      </tr>
      <tr>
          <td>Automatically assign public IPv4 address</td>
          <td>關閉</td>
          <td>private subnet 不允許公開 IP，開關是灰的</td>
      </tr>
      <tr>
          <td>VNIC name</td>
          <td>留空</td>
          <td>顯示用，之後可以改</td>
      </tr>
      <tr>
          <td>DNS record</td>
          <td>Yes</td>
          <td>同一個 VCN 裡的其他主機可以用名稱連它</td>
      </tr>
      <tr>
          <td>Use network security groups</td>
          <td>No</td>
          <td>入站規則只用 subnet 的 security list 管，這台主機不需要另外的網卡層規則</td>
      </tr>
  </tbody>
</table>
<p>這一頁實測卡住的地方是 VCN 下拉選單：它只列出「Virtual cloud network compartment」那一欄選定的 compartment 裡的 VCN，而那一欄預設停在 root compartment。<code>verify-vcn</code> 建在 <code>blog-verify</code>，所以要先把那一欄切過去，選單裡才會出現它。</p>
<p>選「Create new virtual cloud network」時，主控台會一起建一份「Default Security List for &lt;VCN 名稱&gt;」。當天實測一個新建的 VCN，這份預設規則的入站部分是：</p>
<ul>
<li>TCP 22（SSH），來源 <code>0.0.0.0/0</code>，也就是網際網路上的任何位址</li>
<li>ICMP type 3 code 4（Path MTU Discovery 需要的封包），來源 <code>0.0.0.0/0</code></li>
<li>ICMP type 3，來源是 VCN 自己的位址範圍</li>
</ul>
<p>出站是全部放行。Subnet 沒有另外指定 security list 時套用的就是這一份，所以在新建的公開 subnet 裡建一台有公開 IP 的主機，那台主機的 SSH port 一建好就對整個網際網路開放。官方文件把 TCP 22 那一條規則寫成允許「authorized source IP addresses」，而預設填的來源是 <code>0.0.0.0/0</code>，等於任何位址都算在內；官方建議把來源收緊。這份規則跟 NSG 怎麼合併，以及為什麼 NSG 裡不寫 22 port 的規則時，22 port 的連線照樣由這份 security list 放行通過，見 <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="ssh-keys">SSH keys</h2>
<p>這次選 No SSH keys，目的是驗證主機完全不放金鑰時，managed SSH session 這條路本身走不走得通；managed SSH session 在主機有放公鑰時照樣能用，只是那樣就驗不出這件事。選了之後主機裡不放任何長期有效的公鑰，登入改用 Bastion 的 managed SSH session，由主機上的 Bastion plugin 臨時放進一把公鑰。Oracle Cloud 對這個選項的說明是選了之後就不能再提供公鑰，所以這條路要求〈Management：Oracle Cloud Agent 與其他進階選項〉那一節的 Bastion plugin 一定打開。這次刻意不留退路，代價在〈建立之後：Bastion managed SSH session 逾時的排查〉與〈Run Command 補公鑰的嘗試：dynamic group、IAM 政策與 ocarun 沒有 sudo 的結果〉兩節出現：工作階段的建立逾時之後，主機上沒有任何 SSH 登入的入口，Run Command 又寫不了 <code>ubuntu</code> 的 <code>authorized_keys</code>。</p>
<p>放公鑰與不放公鑰，差在 managed SSH session 失敗之後還剩什麼：</p>
<ul>
<li><strong>放自己的公鑰</strong>（上傳或貼上）：managed SSH session 失敗時，改建不經過 plugin 的 SSH port forwarding session，用那把公鑰登入。主機在 private subnet、沒有公開 IP，這把公鑰只對經由 Bastion 進來的連線有用；代價是這把金鑰要自己保管。這次如果這樣選，cloud-init 在 Authorization header 開著時寫入公鑰、security list 放行 Bastion 目標 subnet 的 22 port 這兩項成立的話，三次逾時之後就能登入主機查 plugin 的日誌；這是推論，cloud-init 寫公鑰這一段沒有在這台實跑；後來重建的 Oracle Linux 10.2 主機在 Authorization header 開著時寫入了公鑰（〈重建：Oracle Linux 映像加建立時放公鑰，經由 port forwarding session 登入〉），2026-10-26 在 Ubuntu 24.04 平台映像上也驗過寫得進（<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>〈建 Ubuntu 24.04 主機與經由 port forwarding 登入〉）。</li>
<li><strong>No SSH keys</strong>：主機裡沒有長期有效的公鑰，登入完全依賴 Bastion plugin，而這個欄位建立後不能改（<code>ssh_authorized_keys</code> 不接受修改）。plugin 這條路不通時，剩下的入口見〈這台主機剩下的登入入口〉。</li>
</ul>
<p>第一次建主機、還沒走通 Bastion 時先放公鑰的做法與它的前提，見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉。</p>
<h2 id="storage">Storage</h2>
<p>開機磁碟維持映像檔的預設大小（50 GB）、效能等級 Balanced、Use in-transit encryption 為 Enabled、不指定自己的加密金鑰，不另外建立區塊磁碟。50 GB 在 Always Free 的 200 GB 額度內。要記住的是刪除這台主機時，終止對話框裡的「Permanently delete the attached boot volume」預設不勾，不勾的話開機磁碟會留下來繼續佔著額度，見 <a href="/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留</a>。</p>
<h2 id="managementoracle-cloud-agent-與其他進階選項">Management：Oracle Cloud Agent 與其他進階選項</h2>
<p>Oracle Cloud Agent 的每一項功能是一個 plugin。官方列出的 plugin 與這次建立頁上的預設值：</p>
<table>
  <thead>
      <tr>
          <th>Plugin</th>
          <th>做什麼</th>
          <th>這次的預設</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Compute Instance Monitoring</td>
          <td>回報主機的健康、容量與效能指標給 Monitoring 服務</td>
          <td>開啟</td>
      </tr>
      <tr>
          <td>Custom Logs Monitoring</td>
          <td>把主機上的自訂日誌送進 Logging 服務</td>
          <td>開啟</td>
      </tr>
      <tr>
          <td>Compute Instance Run Command</td>
          <td>從主控台或 API 在主機裡執行腳本，用來遠端設定與排錯</td>
          <td>開啟</td>
      </tr>
      <tr>
          <td>Cloud Guard Workload Protection</td>
          <td>官方說明只寫一種用途：自訂映像沒有啟用 Oracle Cloud Agent 時，讓使用者自己管理 Cloud Guard 的 Instance Security 代理程式的更新；平台映像上它做什麼，說明頁沒有寫</td>
          <td>開啟</td>
      </tr>
      <tr>
          <td>Bastion</td>
          <td>讓 Bastion 服務對沒有公開 IP 的主機建立 managed SSH session</td>
          <td>關閉</td>
      </tr>
      <tr>
          <td>Block Volume Management</td>
          <td>替主機設定區塊磁碟的連線</td>
          <td>關閉</td>
      </tr>
      <tr>
          <td>OS Management Hub Agent</td>
          <td>管理與監看作業系統的更新與修補</td>
          <td>關閉</td>
      </tr>
      <tr>
          <td>Vulnerability Scanning</td>
          <td>掃描主機上的弱點，例如需要更新的套件</td>
          <td>關閉</td>
      </tr>
      <tr>
          <td>Management Agent</td>
          <td>替有整合 Management Agent 的 OCI 服務收集作業系統、應用程式與基礎設施的資料（可觀測性、日誌、組態、容量與健康狀態）</td>
          <td>關閉</td>
      </tr>
      <tr>
          <td>High Performance Computing</td>
          <td>給 HPC 機型的運算叢集用</td>
          <td>關閉</td>
      </tr>
      <tr>
          <td>Fleet Application Management</td>
          <td>替 Fleet Application Management 服務執行並監看應用程式的盤點（discovery）與修補</td>
          <td>關閉</td>
      </tr>
      <tr>
          <td>Oracle Java Management Service</td>
          <td>監看 Java Management 服務所管理的主機上的 Java 部署</td>
          <td>關閉</td>
      </tr>
  </tbody>
</table>
<p>官方的 plugin 說明頁對 Custom Logs Monitoring 在 A1 上的支援前後不一致：頁面上的註記寫它不支援 A1 機型，同一頁的代理程式版本紀錄（1.29.0）寫已經支援 Arm 處理器。這次維持建立頁的預設（開啟）：這個 plugin 只負責把日誌送進 Logging，要在 Logging 服務裡設定收哪些日誌之後才開始收，費用依收進來的日誌量計算（官方〈Custom Logs〉）；沒有設定時它不影響主機建立與登入。</p>
<p>這次要改的只有一項：<strong>把 Bastion 打開</strong>。沒有放 SSH 金鑰的主機，SSH 登入只能經由 Bastion 的 managed SSH session，而這個 plugin 預設關閉；不經過 SSH 的其他入口見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈建立之後加金鑰與金鑰遺失時的入口〉。漏開時不必重建，主機建好之後可以在主機頁面的 Oracle Cloud Agent 分頁打開，官方說明最多 10 分鐘生效。</p>
<p>其餘維持預設，理由各自如下：</p>
<ul>
<li><strong>Authorization header：Enabled</strong>。這是建立頁對 Ubuntu 的預設，而官方列出支援中繼資料第二版的平台映像只有 Oracle Linux 系列，Ubuntu 不在清單上。2026-10-25 建好的主機開著這個選項，代理程式照常回報各 plugin 的狀態；managed SSH session 的建立逾時時，暫時改成允許第一版也一樣逾時，所以這個選項不是那次失敗的成因，見〈建立之後：Bastion managed SSH session 逾時的排查〉。cloud-init 從中繼資料讀出公鑰寫進 <code>authorized_keys</code> 這一段，這台選了 No SSH keys 驗不到；後來重建的 Oracle Linux 10.2 主機開著這個選項時寫入了，2026-10-26 另一台放了公鑰的 Ubuntu 24.04 主機開著這個選項時也寫入了（〈SSH keys〉那一節的「放自己的公鑰」那一項）。要怎麼確認見 <a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a> 的〈中繼資料服務與 Authorization header〉。</li>
<li><strong>Live migration：讓 Oracle 選擇</strong>、<strong>Restart after infrastructure maintenance：Enabled</strong>：實驗機能接受維護時被遷移或重開。</li>
<li><strong>不放初始化腳本、不加標籤</strong>：這台主機只用來測登入，沒有要預先裝的軟體。沒放初始化腳本的結果，到了要用 Run Command 救援時才出現：Run Command 以 <code>ocarun</code> 執行，沒有 sudo（〈Run Command 補公鑰的嘗試：dynamic group、IAM 政策與 ocarun 沒有 sudo 的結果〉），而替 <code>ocarun</code> 設 sudo 的 sudoers 檔，官方列的做法有兩種：建立時用 cloud-init 放，或建好之後登入主機設定；登不進去時只剩前一種。所以要把 Run Command 當成救援入口，替代做法是在這一欄放一段 cloud-init，建立時把官方說明頁列的那一行 sudoers（<code>ocarun ALL=(ALL) NOPASSWD:ALL</code>，或只列救援要用的指令）寫進 <code>/etc/sudoers.d/</code> 底下的一個檔；這個欄位也是建立後不能改的那一種。這段 cloud-init 的寫法這次沒有實跑，所以這裡不附，實跑過再補（記在 Oracle Cloud 目錄頁的 Backlog）。給 <code>ocarun</code> 不限指令的 sudo，等於讓有權限送 Run Command 的人取得主機的 root，所以送指令的權限政策要跟著收窄。</li>
</ul>
<h2 id="等效的-cli-指令">等效的 CLI 指令</h2>
<p>同一組設定用 OCI 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">oci compute instance launch <span class="se">\
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="se"></span>  --compartment-id &lt;compartment 的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="se"></span>  --availability-domain &lt;AD 名稱，例如 XXXX:AP-BATAM-1-AD-1&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="se"></span>  --display-name verify-a1 <span class="se">\
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="se"></span>  --shape VM.Standard.A1.Flex --shape-config <span class="s1">&#39;{&#34;ocpus&#34;:1,&#34;memoryInGBs&#34;:6}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="se"></span>  --image-id &lt;Ubuntu 24.04 aarch64 映像檔的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="se"></span>  --subnet-id &lt;private subnet 的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="se"></span>  --assign-public-ip <span class="nb">false</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="se"></span>  --agent-config <span class="s1">&#39;{&#34;pluginsConfig&#34;:[{&#34;name&#34;:&#34;Bastion&#34;,&#34;desiredState&#34;:&#34;ENABLED&#34;}]}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="se"></span>  --is-pv-encryption-in-transit-enabled <span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="se"></span>  --instance-options <span class="s1">&#39;{&#34;areLegacyImdsEndpointsDisabled&#34;:true}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="se"></span>  --auth security_token</span></span></code></pre></div><ul>
<li><code>--compartment-id</code>、<code>--subnet-id</code>：compartment 與 subnet 的 OCID，在主控台各自的詳細頁複製，或用 <code>oci iam compartment list</code> 與 <code>oci network subnet list --compartment-id &lt;compartment 的 OCID&gt;</code> 查。OCID 是 Oracle Cloud 給每個資源的唯一識別碼，說明見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a>。</li>
<li><code>--availability-domain</code>：AD 的完整名稱帶有 tenancy 專屬的前綴，用 <code>oci iam availability-domain list</code> 查，不能只寫 <code>AD-1</code>。</li>
<li><code>--shape-config</code>：<code>ocpus</code> 與 <code>memoryInGBs</code> 對應 Basic information 表裡 Shape 那一列的 1 OCPU、6 GB。</li>
<li><code>--image-id</code>：映像檔的 OCID 每個區域不同、每次更新都會換，用 <code>oci compute image list --compartment-id &lt;tenancy 的 OCID&gt; --operating-system &quot;Canonical Ubuntu&quot; --operating-system-version &quot;24.04&quot; --shape VM.Standard.A1.Flex</code> 查最新的那一個。這裡的 <code>--compartment-id</code> 填 tenancy 的 OCID 而不是實驗用的 compartment，因為平台映像列在 tenancy 的 root 層；指令加上 <code>--shape VM.Standard.A1.Flex</code> 之後，CLI 只回傳跟 A1 相容的 aarch64 映像檔。</li>
<li><code>--agent-config</code>：只列出要改的 plugin，其餘維持預設。這裡把 Bastion 設成 <code>ENABLED</code>。</li>
<li><code>--is-pv-encryption-in-transit-enabled true</code> 與 <code>--instance-options '{&quot;areLegacyImdsEndpointsDisabled&quot;:true}'</code>：前者對應 Storage 段的 in-transit encryption，後者對應 Management 段的 Authorization header（只允許中繼資料第二版）。這兩項在主控台是預設開啟，CLI 不給時，官方的 CLI 參考寫明 in-transit encryption 的預設值是 false，所以要跟主控台一致就要明確帶上。2026-10-24 背景重試用的指令一開始沒有這兩行，之後才補上。</li>
<li><code>--auth security_token</code>：用 session token 登入時每條指令都要帶，不帶會報設定檔缺 <code>user</code>；用 API 簽章金鑰時拿掉。在主控台的 Cloud Shell 裡跑時也拿掉，Cloud Shell 裡的 CLI 用 Cloud Shell 自己的驗證，沒有本機的 session 設定。</li>
<li>沒有給 <code>--ssh-authorized-keys-file</code>，等同主控台的 No SSH keys。</li>
</ul>
<p>主控台與 CLI 的差別在錯誤訊息的可讀性：主控台的 Change shape 視窗只列出這一區有的機型，並把跟所選映像檔架構不相容的機型標成不可選；這次用 CLI 送出這一區沒有的 E2.1.Micro，回的是 <code>404 NotAuthorizedOrNotFound</code>，這個錯誤碼怎麼讀與怎麼查一區有哪些機型，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈E2.1.Micro 在各區域的供應：服務配額表與 404 錯誤〉。</p>
<h2 id="按下-create-之後的結果容量不足">按下 Create 之後的結果：容量不足</h2>
<p>主控台回的訊息是：</p>





<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">Out of capacity for shape VM.Standard.A1.Flex in availability domain AD-1. Create the instance in a different availability domain or try again later.</span></span></code></pre></div><p>意思是這個 AD 裡跑 A1 的實體主機當下沒有空出這個規格需要的資源；同一段時間用 CLI 送出的請求回的是 <code>InternalError: Out of host capacity.</code>，偶爾是 <code>429 TooManyRequests</code>（請求太頻繁被限流）。訊息建議的換 AD 在這一區不存在（只有一個 AD），fault domain 這次已經是讓 Oracle 選，規格 1 OCPU、6 GB 只用了額度的一半，還能再改小。容量不足時全部的退路與哪幾項會計費，列在 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈A1 容量不足時的退路〉；這次的帳號只用免費額度，選的是隔一段時間再試。</p>
<p>2026-10-24 的重送紀錄：先以 90 秒的間隔送了九次，前三次收到 429、後六次是容量不足；之後改成每 5 分鐘一次，到當天晚上仍然是容量不足。兩種回應碼的讀法見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈A1 容量不足時的退路〉裡「等一段時間再試」那一項。</p>
<h2 id="重送建立請求cli-自己的重送限流門檻與重送腳本">重送建立請求：CLI 自己的重送、限流門檻與重送腳本</h2>
<p>2026-10-25 改用下面這支腳本重送，從 10:32 開始，第 16 次（11:00）建立成功。這一天量到的兩件事決定了腳本的間隔怎麼設：</p>
<ul>
<li><strong>OCI CLI 在一條指令裡會自己重送</strong>：<code>oci</code> 預設會在同一條指令裡重送失敗的請求，<code>oci --help</code> 對 <code>--max-retries</code> 的說明是多數指令最多送 7 次（哪些回應碼算可以重送，<code>--help</code> 沒有列）。所以外層迴圈每「試一次」，送到 API 的是一連串請求，前一天以 90 秒間隔送、前三次就收到 429，跟這一點對得上。指令加上全域選項 <code>--no-retry</code> 之後，一次嘗試只送一個請求，間隔才完全由外層迴圈決定。</li>
<li><strong>這個帳號的限流門檻</strong>：只送單一請求的那一輪共 16 次，收到 <code>429 Too many requests for the user</code> 的有 2 次，這兩次送出之前，腳本分別等了 75 秒與 67 秒；其餘 13 次送出之前等了 76 到 171 秒，回的都是容量不足。前一輪以 46 秒的等待送，第三次就收到 429。訊息裡的「for the user」代表限流按使用者計算，同一個使用者並行開幾個迴圈，只會更快被限流。門檻依帳號與 API 而定，這是這個帳號這一天的量測；腳本因此不寫死間隔，而是依回應碼調整。</li>
</ul>





<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="cp">#!/bin/bash
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="cp"></span><span class="c1"># 容量不足時重送建立主機的請求，建立成功或認證失效時結束</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nb">set</span> -uo pipefail
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nb">export</span> <span class="nv">OCI_CLI_AUTH</span><span class="o">=</span>security_token
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nv">C</span><span class="o">=</span><span class="s2">&#34;&lt;compartment 的 OCID&gt;&#34;</span><span class="p">;</span> <span class="nv">SN</span><span class="o">=</span><span class="s2">&#34;&lt;private subnet 的 OCID&gt;&#34;</span><span class="p">;</span> <span class="nv">IMG</span><span class="o">=</span><span class="s2">&#34;&lt;映像檔的 OCID&gt;&#34;</span><span class="p">;</span> <span class="nv">AD</span><span class="o">=</span><span class="s2">&#34;&lt;AD 的完整名稱&gt;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nv">back</span><span class="o">=</span><span class="m">120</span> <span class="nv">last_refresh</span><span class="o">=</span><span class="m">0</span>                               <span class="c1"># 0：開跑時先延長一次 token</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">while</span> :<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">if</span> <span class="o">((</span> <span class="k">$(</span>date +%s<span class="k">)</span> - last_refresh &gt;<span class="o">=</span> <span class="m">1800</span> <span class="o">))</span><span class="p">;</span> <span class="k">then</span>   <span class="c1"># session token 一小時過期，每 30 分鐘延長一次</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    oci session refresh --profile DEFAULT &gt;/dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="o">||</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s2">&#34;token 無法延長，重新登入&#34;</span><span class="p">;</span> <span class="nb">exit</span> 2<span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nv">last_refresh</span><span class="o">=</span><span class="k">$(</span>date +%s<span class="k">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="nv">out</span><span class="o">=</span><span class="k">$(</span>oci --no-retry compute instance launch -c <span class="s2">&#34;</span><span class="nv">$C</span><span class="s2">&#34;</span> --availability-domain <span class="s2">&#34;</span><span class="nv">$AD</span><span class="s2">&#34;</span> --display-name verify-a1 <span class="se">\
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="se"></span>    --shape VM.Standard.A1.Flex --shape-config <span class="s1">&#39;{&#34;ocpus&#34;:1,&#34;memoryInGBs&#34;:6}&#39;</span> --image-id <span class="s2">&#34;</span><span class="nv">$IMG</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="se"></span>    --subnet-id <span class="s2">&#34;</span><span class="nv">$SN</span><span class="s2">&#34;</span> --assign-public-ip <span class="nb">false</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="se"></span>    --agent-config <span class="s1">&#39;{&#34;pluginsConfig&#34;:[{&#34;name&#34;:&#34;Bastion&#34;,&#34;desiredState&#34;:&#34;ENABLED&#34;}]}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="se"></span>    --is-pv-encryption-in-transit-enabled <span class="nb">true</span> --instance-options <span class="s1">&#39;{&#34;areLegacyImdsEndpointsDisabled&#34;:true}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="se"></span>    --query data.id --raw-output 2&gt;<span class="p">&amp;</span>1<span class="k">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="nv">id</span><span class="o">=</span><span class="k">$(</span>grep -o <span class="s1">&#39;ocid1\.instance\.[^&#34; ]*&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span> <span class="p">|</span> head -1<span class="k">)</span>   <span class="c1"># 輸出前面可能夾著 CLI 的警告</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$id</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span> <span class="nb">echo</span> <span class="s2">&#34;建立成功：</span><span class="nv">$id</span><span class="s2">&#34;</span><span class="p">;</span> <span class="nb">exit</span> 0<span class="p">;</span> <span class="k">fi</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="nv">status</span><span class="o">=</span><span class="k">$(</span>grep -o <span class="s1">&#39;&#34;status&#34;: [0-9]*&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span> <span class="p">|</span> head -1 <span class="p">|</span> grep -o <span class="s1">&#39;[0-9]*$&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="k">case</span> <span class="s2">&#34;</span><span class="nv">$status</span><span class="s2">&#34;</span> in
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;</span><span class="o">)</span>  <span class="nb">echo</span> <span class="s2">&#34;讀不出回應碼，停下來看輸出：</span><span class="nv">$out</span><span class="s2">&#34;</span><span class="p">;</span> <span class="nb">exit</span> <span class="m">3</span> <span class="p">;;</span>   <span class="c1"># 設定檔錯誤、網路錯誤這類沒有回應碼的失敗</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    401<span class="o">)</span> <span class="nb">echo</span> <span class="s2">&#34;認證失效，重新登入&#34;</span><span class="p">;</span> <span class="nb">exit</span> <span class="m">2</span> <span class="p">;;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    429<span class="o">)</span> <span class="nv">back</span><span class="o">=</span><span class="k">$((</span> back <span class="o">*</span> <span class="m">2</span> &gt; <span class="m">600</span> ? <span class="m">600</span> : back <span class="o">*</span> <span class="m">2</span> <span class="k">))</span> <span class="p">;;</span>   <span class="c1"># 被限流：間隔加倍，上限 600 秒</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    500<span class="o">)</span> <span class="nv">back</span><span class="o">=</span><span class="k">$((</span> back <span class="o">-</span> <span class="m">10</span> &lt; <span class="m">60</span> ? <span class="m">60</span> : back <span class="o">-</span> <span class="m">10</span> <span class="k">))</span> <span class="p">;;</span>   <span class="c1"># 容量不足：每次縮 10 秒，下限 60 秒</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    *<span class="o">)</span>   <span class="nb">echo</span> <span class="s2">&#34;回應碼 </span><span class="si">${</span><span class="nv">status</span><span class="si">}</span><span class="s2">，重送也不會好，停下來看訊息：</span><span class="k">$(</span>grep -o <span class="s1">&#39;&#34;message&#34;: &#34;[^&#34;]*&#34;&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span> <span class="p">|</span> head -1<span class="k">)</span><span class="s2">&#34;</span><span class="p">;</span> <span class="nb">exit</span> <span class="m">4</span> <span class="p">;;</span>   <span class="c1"># 400 參數錯誤這類</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="k">esac</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="k">$(</span>date +%H:%M:%S<span class="k">)</span><span class="s2"> </span><span class="si">${</span><span class="nv">status</span><span class="k">:-</span><span class="p">?</span><span class="si">}</span><span class="s2">，約 </span><span class="si">${</span><span class="nv">back</span><span class="si">}</span><span class="s2"> 秒後再送&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  sleep <span class="k">$((</span> back <span class="o">-</span> <span class="m">15</span> <span class="o">+</span> RANDOM <span class="o">%</span> <span class="m">31</span> <span class="k">))</span>                   <span class="c1"># 加 ±15 秒的隨機抖動</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">done</span></span></span></code></pre></div><ul>
<li>四個佔位值的查法與〈等效的 CLI 指令〉那一節相同；<code>--display-name verify-a1</code> 是主機在清單上的名稱，換成自己要的名稱時，〈重複建立的風險〉那一項查同名主機的指令也跟著換；<code>--shape-config</code> 到 <code>--instance-options</code> 是那一節的同一組設定，<code>--no-retry</code> 是寫在子命令之前的全域選項。</li>
<li><strong>間隔的調整方式</strong>：收到 429 時間隔加倍，收到容量不足的 500 時每次縮 10 秒，所以間隔會在「剛好不被限流」的附近來回。被限流的期間就算剛好有容量空出來也送不進去，所以把頻率壓在門檻下、讓請求能長時間穩定地送，比把間隔縮到最短更有機會碰上容量釋出的那一刻。120、600、60 這三個值是起點、上限與下限，換帳號時從 120 起跑，讓腳本自己往門檻靠。</li>
<li><strong>抖動（jitter）</strong>：每次的等待多加 ±15 秒的隨機值，讓送出的時間點不落在固定的週期上。間隔加倍的退避做法見 <a href="/blog/backend/knowledge-cards/exponential-backoff/" data-link-title="Exponential Backoff" data-link-desc="說明重試間隔如何逐步拉長以降低下游壓力">Exponential Backoff</a> 卡，那張卡把 jitter 列為退避要一起搭配的一項；抖動對這次搶到容量有沒有影響，這次沒有量。</li>
<li><strong>驗證方式</strong>：<code>export OCI_CLI_AUTH=security_token</code> 等同每條指令都帶 <code>--auth security_token</code>。<code>--profile DEFAULT</code> 填 <code>oci session authenticate</code> 時取的 profile 名稱，沒有另外取名時就是 <code>DEFAULT</code>。</li>
<li><strong>判斷建立成功的方式</strong>：輸出裡找得到 <code>ocid1.instance.</code> 開頭的 OCID 就算成功。標準錯誤併進了 <code>$out</code>，CLI 在 OCID 之前印出警告時（例如設定檔權限的警告），整段輸出就不是以 OCID 開頭；只比對開頭的話，已經建成的這一次會被當成失敗、再送一次，建出第二台。</li>
<li><strong>結束條件</strong>：建立成功（exit 0）、session token 無法再延長或收到 401（exit 2）、輸出裡讀不出回應碼（exit 3，設定檔或網路錯誤這類，重送也不會好）、收到 500 與 429 以外的回應碼（exit 4）。只有 500（容量不足）與 429（限流）是等一段時間就可能變好的回應；400 這類回應代表請求本身有誤（例如 <code>&lt;AD 的完整名稱&gt;</code> 沒換掉或填錯），重送幾次都是同一個結果，所以腳本印出回應碼與 <code>message</code> 就結束，照訊息改好參數再跑。<code>last_refresh=0</code> 讓腳本開跑時先延長一次 token，開跑時 token 只剩不到 30 分鐘也不會在第一次延長之前過期。session token 一次登入最長延到 24 小時，超過就要重跑 <code>oci session authenticate</code>，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/" data-link-title="從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰" data-link-desc="OCI CLI 的兩種身分驗證方式——瀏覽器登入換來的 session token 與上傳公鑰的 API 簽章金鑰——各自把什麼存在本機、有效多久、外洩時的影響範圍，以及登入時的互動提示與錯誤訊息怎麼讀">從本機用 OCI CLI 操作 Oracle Cloud</a>；要讓腳本跑超過一天，改用 API 簽章金鑰，並拿掉 <code>OCI_CLI_AUTH</code> 與延長 token 的那一段。</li>
<li><strong>重複建立的風險</strong>：建立成功就結束，所以收得到回應時，同一支腳本不會建出第二台。收不到回應的情形擋不住：建立請求在 Oracle 那一端已經成功、回應卻因為網路中斷沒有回到本機，這一次會落進讀不出回應碼的分支而結束（exit 3）。所以腳本以 exit 3 結束、或要再次執行之前，先用 <code>oci compute instance list -c &lt;compartment 的 OCID&gt; --display-name verify-a1</code> 確認沒有還在的同名主機。</li>
</ul>
<p>2026-10-25 實際跑的版本另外在開跑前查一次有沒有同名主機、把每次的結果寫進紀錄檔，重送與間隔的邏輯跟上面相同，只是那時遇到 500 與 429 以外的回應碼也會重送；2026-10-25 那一輪回的都是 500 與 429，沒有走到這個分支。2026-10-27 補上 exit 4 那一個分支之後，上面這一份用一支假的 <code>oci</code> 指令跑過六組回應，確認每個分支的間隔與結束碼：依序回 500、429、500 與成功（間隔 110、220、210 秒，exit 0）、先在標準錯誤印警告再印 OCID（只送一次，exit 0）、回一段沒有回應碼的錯誤（exit 3）、回 401（exit 2）、回 400（只送一次，印出回應碼與 <code>message</code>，exit 4）、先回 500 再回 400（重送一次之後 exit 4）；六組都在第一次送出之前先延長了一次 token。400 那兩組的 <code>message</code> 是假的 <code>oci</code> 自己編的，Oracle 實際回的訊息這次沒有看過。</p>
<p>不用腳本時，替代做法是在主控台隔一段時間按一次 Create：每一次要有人在場，間隔不固定；2026-10-24 在主控台手動按 Create、加上背景以 90 秒與 5 分鐘間隔跑的 CLI 迴圈，整天的回應都是容量不足。不加 <code>--no-retry</code>、沿用 CLI 自己的重送時，外層的每一次嘗試送出好幾個請求，同樣的間隔下更早被限流（2026-10-24 以 90 秒送，前三次就收到 429）。換區域、換機型、改付費這些不在重送範圍內的退路，與各自是否計費，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈A1 容量不足時的退路〉。腳本送出的建立參數是寫腳本那一刻的設定，重新啟動腳本之前，參數要再拿〈建立之前的檢查：套在這台主機上的查法與結果〉對一次。</p>
<h2 id="建立之後bastion-managed-ssh-session-逾時的排查">建立之後：Bastion managed SSH session 逾時的排查</h2>
<p>這一節記錄主機建好之後建立 Bastion 與 managed SSH session、工作階段停在 <code>CREATING</code> 約 15 分鐘後轉成 <code>DELETED</code>、從 work request 讀到的兩則錯誤，以及對照官方 Known Issues 找到的成因。這一節的指令跟腳本一樣用 session token 驗證：先在 shell 裡 <code>export OCI_CLI_AUTH=security_token</code>，或每條指令加 <code>--auth security_token</code>，兩種寫法等效。</p>
<h3 id="建立-bastion-與-managed-ssh-session">建立 Bastion 與 managed SSH session</h3>
<p>Bastion 在 2026-10-24 主機還沒建成時就先建好，建立 Bastion 只需要目標 subnet 存在：</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">oci bastion bastion create -c &lt;compartment 的 OCID&gt; --bastion-type STANDARD --target-subnet-id &lt;private subnet 的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --name verifybastion --client-cidr-list <span class="s1">&#39;[&#34;&lt;本機的公開 IP&gt;/32&#34;]&#39;</span> --max-session-ttl <span class="m">3600</span> --wait-for-state SUCCEEDED</span></span></code></pre></div><ul>
<li><code>--target-subnet-id</code>：Bastion 替哪一個 subnet 裡的主機開工作階段。Bastion 在這個 subnet 裡放一個私有端點，從那裡連主機的 22 port，所以〈進建立頁之前〉的 security list 放行來源是同一個 subnet 的 22 port。</li>
<li><code>--client-cidr-list</code>：允許從哪些位址連到 Bastion，填本機目前的公開 IP 加 <code>/32</code>。這個參數整份取代既有的清單；本機的 IP 變了（家用網路重新撥號之後常見）就用 <code>oci bastion bastion update --bastion-id &lt;Bastion 的 OCID&gt; --client-cidr-list '[&quot;&lt;新的公開 IP&gt;/32&quot;]'</code> 換掉，2026-10-25 就換過一次。這一項影響的是連線，不影響工作階段的建立。</li>
<li><code>--max-session-ttl 3600</code>：這個 Bastion 上工作階段的最長時效（秒），建工作階段時的 <code>--session-ttl</code> 不能超過它。</li>
<li>這次用 tenancy 的管理員帳號操作，沒有另外建 IAM 政策；一般使用者建 Bastion 與工作階段需要的政策，這篇沒有寫。</li>
</ul>
<p>主機在 2026-10-25 11:00 建立，11:04 主機頁面的 Oracle Cloud Agent 回報 Bastion plugin 是 <code>RUNNING</code>。代理程式回報得了 plugin 狀態，代表代理程式本身在只允許中繼資料第二版（Authorization header 開著）的設定下照常運作。接著建 managed SSH session：</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">oci bastion session create-managed-ssh --bastion-id &lt;Bastion 的 OCID&gt; --target-resource-id &lt;主機的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --target-os-username ubuntu --target-private-ip &lt;主機的私有 IP&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --ssh-public-key-file &lt;這次工作階段用的公鑰&gt; --session-ttl <span class="m">3600</span></span></span></code></pre></div><ul>
<li><code>--target-os-username ubuntu</code>：Ubuntu 平台映像的預設使用者，Oracle Linux 是 <code>opc</code>。</li>
<li><code>--target-private-ip</code>：用 <code>oci compute instance list-vnics --instance-id &lt;主機的 OCID&gt; --query 'data[0].&quot;private-ip&quot;' --raw-output</code> 查。</li>
<li><code>--ssh-public-key-file</code>：只給這一次工作階段用的公鑰（例如用 <code>ssh-keygen -t rsa -b 4096 -f &lt;檔名&gt;</code> 另產一組），plugin 在工作階段期間把它放進主機，連線時用對應的私鑰。</li>
<li><code>--session-ttl 3600</code>：工作階段的時效（秒），上限是 Bastion 的 <code>--max-session-ttl</code>。</li>
</ul>
<h3 id="結果停在-creating-約-15-分鐘後轉成-deleted">結果：停在 CREATING 約 15 分鐘後轉成 DELETED</h3>
<p>三次建立的工作階段都停在 <code>CREATING</code> 約 15 分鐘，接著轉成 <code>DELETED</code>，<code>lifecycle-details</code> 是空的。失敗的原因記在建立工作階段的 work request 上；work request 是 Oracle Cloud 記錄一次非同步操作的進度與錯誤的物件：</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"># 取這個 compartment 裡最近一次建立工作階段的 work request，再讀它的錯誤</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci bastion work-request list -c &lt;compartment 的 OCID&gt; --all <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data[?&#34;operation-type&#34;==`CREATE_SESSION`] | sort_by(@, &amp;&#34;time-accepted&#34;)[-1].id&#39;</span> --raw-output
</span></span><span class="line"><span class="ln">4</span><span class="cl">oci bastion work-request-error list --work-request-id &lt;上一條印出的 work request OCID&gt;</span></span></code></pre></div><ul>
<li><code>--query</code> 只留操作類型是 <code>CREATE_SESSION</code> 的 work request，依受理時間排序後取最後一筆。同一個 compartment 裡還有建立 Bastion、更新允許清單、刪除工作階段的 work request，只取清單第一筆的話可能取到那些。</li>
</ul>
<p>三次讀到的都是同兩則訊息：<code>End host agent didn't respond back on session status in time</code> 與 <code>End host agent hasn't responded back within max allowed time</code>，也就是 Bastion 服務等不到主機上的 plugin 回報工作階段的狀態。三次之間各改了一個條件，排除了兩個成因：</p>
<table>
  <thead>
      <tr>
          <th>次數</th>
          <th>建立時間</th>
          <th>跟前一次的差別</th>
          <th>結果</th>
          <th>排除的成因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>11:04</td>
          <td>plugin 剛轉成 <code>RUNNING</code> 就建</td>
          <td>約 15 分鐘逾時</td>
          <td>無</td>
      </tr>
      <tr>
          <td>2</td>
          <td>11:21</td>
          <td>plugin 已經跑了 17 分鐘</td>
          <td>約 15 分鐘逾時</td>
          <td>plugin 剛啟動、還沒同步</td>
      </tr>
      <tr>
          <td>3</td>
          <td>11:52</td>
          <td>暫時打開中繼資料第一版（<code>areLegacyImdsEndpointsDisabled:false</code>）再建</td>
          <td>約 15 分鐘逾時</td>
          <td>plugin 只能讀中繼資料第一版</td>
      </tr>
  </tbody>
</table>
<p>第三次之後把設定改回只允許第二版（<code>oci compute instance update --instance-id &lt;主機的 OCID&gt; --instance-options '{&quot;areLegacyImdsEndpointsDisabled&quot;:true}'</code>，不必重開機）。</p>
<p>主機開機時的序列主控台輸出（console history：開機輸出的唯讀快照，不是 <a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">序列主控台</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">oci compute console-history capture --instance-id &lt;主機的 OCID&gt; --wait-for-state SUCCEEDED --query data.id --raw-output
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci compute console-history get-content --instance-console-history-id &lt;上一條印出的 OCID&gt; --length <span class="m">10000000</span> --file &lt;存檔的路徑&gt;</span></span></code></pre></div><ul>
<li><code>--wait-for-state SUCCEEDED</code>：擷取是非同步的作業，等它完成再讀內容；不等的話，緊接著的 <code>get-content</code> 可能讀到還沒擷取完的內容。</li>
<li><code>--length 10000000</code>：要讀的位元組數。官方說明一次擷取最多 1 MB，填一個比 1 MB 大的數字就是讀全部。</li>
</ul>
<p>這次的輸出（1576 行）裡，cloud-init 在開機 63 秒時完成，<code>oracle-cloud-agent</code>（Ubuntu 上是 snap 套件）有啟動，<code>ssh.socket</code> 在監聽；<code>no authorized SSH keys fingerprints found for user ubuntu</code> 是選了 No SSH keys 的預期結果。序列主控台輸出不含 plugin 的日誌。</p>
<h3 id="成因a1-加-ubuntu-是官方列出的不支援組合">成因：A1 加 Ubuntu 是官方列出的不支援組合</h3>
<p>Oracle 官方〈Known Issues for Bastion〉的第一條寫明，用 Arm 架構的 Ampere A1 機型建立、而且作業系統不是 Oracle Linux（例如 Ubuntu）的主機，不支援 managed SSH session；原文的理由是這類主機上的 Bastion plugin 沒有正確啟用（「isn&rsquo;t properly enabled on some Arm-based instances」），所以建立工作階段會失敗。這台正是 A1 加 Ubuntu 24.04。主控台上的 plugin 狀態是 <code>RUNNING</code>，跟原文說的「沒有正確啟用」在主控台上看不出差別，所以 plugin 的狀態不能當成這條路走得通的證據。官方給的替代做法是改用 SSH port forwarding session。同一頁的另一條是 Ubuntu 上的 Oracle Cloud Agent 低於 1.11 版時 managed SSH session 也會失敗（處置是在主機上跑 <code>sudo snap refresh oracle-cloud-agent</code>）。這台主機的代理程式版本，後來用 Run Command 讀到是 1.61.0，這一條可以排除，見〈用 Run Command 讀代理程式的版本與日誌〉。</p>
<p>上面那張表的三次是在查到這一條之前做的。官方〈Managed SSH Session Creation Failed〉列的成因，對這台的狀況是：代理程式與 Bastion plugin 都在跑；VCN 有 service gateway 與路由，缺 gateway 時 plugin 的狀態會是 <code>INVALID</code>，這台不是；<code>ubuntu</code> 是存在的使用者；sshd 設定那一項，這台沒有 SSH 登入的入口可以查。建 managed SSH session 之前先讀 Known Issues 那一頁，確認機型與作業系統的組合有支援，三次各 15 分鐘的等待就可以省掉。</p>
<p>Port forwarding session 不經過 plugin，主機的登入驗證仍然是主機上的 sshd 與 <code>authorized_keys</code>；這台建立時選了 No SSH keys，主機裡沒有任何公鑰，所以 port forwarding session 建得起來也登不進去。下一節試的是先用 Run Command 把公鑰寫進主機。</p>
<h2 id="run-command-補公鑰的嘗試dynamic-groupiam-政策與-ocarun-沒有-sudo-的結果">Run Command 補公鑰的嘗試：dynamic group、IAM 政策與 ocarun 沒有 sudo 的結果</h2>
<p>Run Command 從 API 把一段腳本送進主機執行，由主機上的 Run Command plugin 定期向服務領取。要用它寫公鑰，有三件事要同時成立：主機有權限領取指令、plugin 在這個作業系統上執行得了腳本、執行腳本的使用者寫得了 <code>ubuntu</code> 家目錄裡的 <code>authorized_keys</code>。後兩件在官方文件上都不利：Run Command 說明頁的支援映像清單沒有 Ubuntu，而 plugin 以 <code>ocarun</code> 這個使用者執行，要以管理員權限執行得先在主機上加 sudoers 檔。主機頁面上這個 plugin 的狀態是 <code>RUNNING</code>，而〈成因：A1 加 Ubuntu 是官方列出的不支援組合〉已經看過 plugin 的狀態不能當成支援的證據，所以第一次只送唯讀的探測腳本，先查 <code>ocarun</code> 能不能 sudo，再決定要不要寫公鑰。</p>
<p>plugin 的狀態用這條指令列：</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">oci instance-agent plugin list --instanceagent-id &lt;主機的 OCID&gt; --compartment-id &lt;compartment 的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data[].{name:name,status:status}&#39;</span> --output table</span></span></code></pre></div><p>這台列出 Compute Instance Run Command、Bastion、Custom Logs Monitoring、Compute Instance Monitoring、Cloud Guard Workload Protection 是 <code>RUNNING</code>，其餘是 <code>STOPPED</code> 或 <code>NOT_SUPPORTED</code>。</p>
<h3 id="讓主機領得到指令dynamic-group-與-iam-政策">讓主機領得到指令：dynamic group 與 IAM 政策</h3>
<p>主機向 Run Command 服務領取指令，用的是主機自己的身分，所以要先把主機放進一個 dynamic group，再寫政策允許這個群組領取與回報指令：</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">oci iam dynamic-group create --compartment-id &lt;tenancy 的 OCID&gt; --name verify-a1-runcommand <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --description <span class="s2">&#34;&lt;用途說明&gt;&#34;</span> --matching-rule <span class="s2">&#34;ALL {instance.id = &#39;&lt;主機的 OCID&gt;&#39;}&#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">oci iam policy create --compartment-id &lt;compartment 的 OCID&gt; --name verify-a1-runcommand --description <span class="s2">&#34;&lt;用途說明&gt;&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --statements <span class="s1">&#39;[&#34;Allow dynamic-group verify-a1-runcommand to use instance-agent-command-execution-family in compartment blog-verify&#34;]&#39;</span></span></span></code></pre></div><ul>
<li><strong>dynamic group</strong>：依比對規則把資源本身當成 IAM 主體的群組。它建在 tenancy 層（<code>--compartment-id</code> 填 tenancy 的 OCID）；這個帳號只有 Default 這一個 identity domain（Oracle Cloud 存放使用者、群組與 dynamic group 的身分網域，<code>oci iam domain list --compartment-id &lt;tenancy 的 OCID&gt;</code> 列得出來），<code>oci iam dynamic-group create</code> 直接建在它底下；有多個 identity domain 的帳號，dynamic group 建在哪一個 domain 要另外確認，這次沒有實跑。<code>--matching-rule</code> 寫成 <code>instance.id = '&lt;主機的 OCID&gt;'</code>，群組裡只有這一台；改寫成 <code>instance.compartment.id = '&lt;compartment 的 OCID&gt;'</code> 會納入整個 compartment 的主機，之後新建的主機也自動有領取指令的身分（這時政策要加下一項說的 <code>where</code> 條件）。</li>
<li><strong>政策</strong>：建在 <code>blog-verify</code> 這個 compartment，<code>instance-agent-command-execution-family</code> 是領取與回報指令的權限，對象只有這個 dynamic group。官方〈Required IAM Policy for Working with Instances〉的範例在句尾多了 <code>where request.instance.id=target.instance.id</code>，限制每台主機只領取送給自己的指令；這次群組裡只有一台主機，加不加結果相同，比對規則改成整個 compartment 時要加上，否則群組裡的每一台主機都領得到送給其他主機的指令。政策裡的 <code>blog-verify</code> 與 <code>verify-a1-runcommand</code> 換成自己的 compartment 與群組名稱。</li>
<li><strong>送指令的人</strong>：這次用 tenancy 的管理員帳號送，沒有另外寫送指令的政策；一般使用者要送指令，另外需要送指令的權限政策，Run Command 說明頁指向官方〈Required IAM Policy for Working with Instances〉，這次沒有實跑。</li>
</ul>
<p>沒有這組政策時，官方的疑難排解寫明 plugin 的日誌（<code>/var/log/oracle-cloud-agent/plugins/runcommand/runcommand.log</code>）會出現 <code>NotAuthorizedOrNotFound</code> 的 404，plugin 領不到指令。官方寫的等待時間依建立的先後而定：先建 dynamic group、再建主機時，主機一建好就開始領取；主機建好之後才加進 dynamic group 時，最多要 30 分鐘。這次屬於後一種，政策在 13:03:06（台北時間）建好，13:03:26 送出指令，13:10:27 執行完，從送出到執行完約 7 分鐘。</p>
<h3 id="送出唯讀的探測腳本">送出唯讀的探測腳本</h3>
<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"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="cp"></span><span class="c1"># 第一次 Run Command：只讀，確認 ocarun 的身分與能不能 sudo</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">id
</span></span><span class="line"><span class="ln">4</span><span class="cl">. /etc/os-release <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$PRETTY_NAME</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">if</span> sudo -n <span class="nb">true</span> 2&gt;/dev/null<span class="p">;</span> <span class="k">then</span> <span class="nb">echo</span> <span class="s2">&#34;SUDO=yes&#34;</span><span class="p">;</span> <span class="k">else</span> <span class="nb">echo</span> <span class="s2">&#34;SUDO=no&#34;</span><span class="p">;</span> <span class="k">fi</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">ls -l /etc/sudoers.d/ 2&gt;<span class="p">&amp;</span><span class="m">1</span></span></span></code></pre></div><p><code>sudo -n</code> 在需要輸入密碼時直接失敗、不等待輸入，Run Command 不支援會要求輸入的腳本。腳本以文字的形式放進指令內容的 JSON，再送出：</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">python3 -c <span class="s1">&#39;import json;print(json.dumps({&#34;source&#34;:{&#34;sourceType&#34;:&#34;TEXT&#34;,&#34;text&#34;:open(&#34;runcmd-probe.sh&#34;).read()},&#34;output&#34;:{&#34;outputType&#34;:&#34;TEXT&#34;}}))&#39;</span> &gt; probe-content.json
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">oci instance-agent <span class="nb">command</span> create --compartment-id &lt;compartment 的 OCID&gt; --display-name verify-probe <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --timeout-in-seconds <span class="m">600</span> --target <span class="s1">&#39;{&#34;instanceId&#34;:&#34;&lt;主機的 OCID&gt;&#34;}&#39;</span> --content file://probe-content.json</span></span></code></pre></div><ul>
<li><code>runcmd-probe.sh</code> 是上面那支腳本存成的檔名。用 <code>json.dumps</code> 產生 JSON，腳本裡的換行與引號會自動跳脫；以文字送出的腳本上限 4 KB。</li>
<li><code>&quot;outputType&quot;:&quot;TEXT&quot;</code>：輸出以文字保存在指令的執行紀錄裡，只保留最後 1 KB；要完整的輸出改存到 Object Storage。</li>
<li><code>--timeout-in-seconds 600</code>：腳本執行的逾時。這個選項的名稱是 <code>--timeout-in-seconds</code>，寫成 API 欄位名稱的 <code>--execution-time-out-in-seconds</code> 時，CLI 回 <code>No such option</code>。</li>
</ul>
<h3 id="讀結果ocarun-沒有-sudo">讀結果：ocarun 沒有 sudo</h3>
<p>送出之後指令先停在 <code>ACCEPTED</code>，plugin 領取並執行完才改變狀態。狀態與輸出各用一條指令讀：</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">oci instance-agent command-execution get --command-id &lt;指令的 OCID&gt; --instance-id &lt;主機的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data.&#34;lifecycle-state&#34;&#39;</span> --raw-output
</span></span><span class="line"><span class="ln">3</span><span class="cl">oci instance-agent command-execution get --command-id &lt;指令的 OCID&gt; --instance-id &lt;主機的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data.content.text&#39;</span> --raw-output</span></span></code></pre></div><p><code>&lt;指令的 OCID&gt;</code> 是 <code>command create</code> 輸出裡的 <code>id</code>。這次每 30 秒查一次，前 405 秒都是 <code>ACCEPTED</code>，之後是 <code>FAILED</code>，輸出是：</p>





<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">uid=999(ocarun) gid=584788(snap_daemon) groups=584788(snap_daemon),4(adm)
</span></span><span class="line"><span class="ln">2</span><span class="cl">Ubuntu 24.04.5 LTS
</span></span><span class="line"><span class="ln">3</span><span class="cl">SUDO=no
</span></span><span class="line"><span class="ln">4</span><span class="cl">ls: cannot open directory &#39;/etc/sudoers.d/&#39;: Permission denied</span></span></code></pre></div><ul>
<li><strong><code>FAILED</code> 的意思</strong>：執行紀錄的 <code>exit-code</code> 是 2，來自腳本最後一行 <code>ls</code> 讀不了 <code>/etc/sudoers.d/</code>；同一筆紀錄的 <code>delivery-state</code> 是 <code>ACKED</code>，指令有送達並執行完。所以這裡的 <code>FAILED</code> 是腳本的結束碼，不是 plugin 沒有執行。</li>
<li><strong>plugin 在 A1 Ubuntu 上執行得了腳本</strong>：Ubuntu 不在支援映像清單上，而腳本確實執行了。Ubuntu 上的 Oracle Cloud Agent 是 snap 套件，<code>ocarun</code> 的群組是 <code>snap_daemon</code> 與 <code>adm</code>（<code>adm</code> 讀得到 <code>/var/log</code> 底下的系統日誌）。</li>
<li><strong><code>SUDO=no</code></strong>：<code>ocarun</code> 沒有 sudo，寫不了 <code>/home/ubuntu/.ssh/authorized_keys</code>，也加不了 sudoers 檔。要讓 <code>ocarun</code> 有 sudo，得先登入主機加 sudoers 檔，而這台正是登不進去才要用 Run Command，所以 Run Command 在這台主機上寫不了公鑰。建立時用初始化腳本放 sudoers 檔的做法，見〈Management：Oracle Cloud Agent 與其他進階選項〉。</li>
</ul>
<h3 id="用-run-command-讀代理程式的版本與日誌">用 Run Command 讀代理程式的版本與日誌</h3>
<p><code>ocarun</code> 寫不了其他使用者的檔案，讀得到的範圍則比較寬：它屬於 <code>adm</code> 群組，<code>/var/log</code> 底下開放給這個群組或所有人讀的檔案都讀得到。所以 Run Command 雖然救不回這台主機，仍然能在不登入的情況下查 managed SSH session 失敗時代理程式的狀態。2026-10-25 下午再送了兩次唯讀的腳本，送法同〈送出唯讀的探測腳本〉，只換腳本內容；兩次的內容合起來是：</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="cp">#!/bin/bash
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="cp"></span><span class="c1"># 只讀：代理程式的版本、日誌目錄、代理程式主日誌裡 Bastion plugin 的紀錄</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">snap list oracle-cloud-agent 2&gt;<span class="p">&amp;</span><span class="m">1</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">ls -l /var/log/oracle-cloud-agent/plugins/ 2&gt;<span class="p">&amp;</span><span class="m">1</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">ls /var/log/oracle-cloud-agent/plugins/bastions/ 2&gt;<span class="p">&amp;</span><span class="m">1</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">grep -ih bastion /var/log/oracle-cloud-agent/*.log 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> tail -n <span class="m">20</span></span></span></code></pre></div><p>第二次從送出到執行完約 2 分半鐘（06:19:43Z 到 06:22:10Z；主機已經在 dynamic group 裡，不再等第一次探測的 7 分鐘）。兩次讀到的是：</p>
<ul>
<li><strong>代理程式的版本</strong>：<code>snap list</code> 印出 <code>oracle-cloud-agent 1.61.0-6</code>，Notes 欄是 <code>classic,held</code>。Known Issues 第二條的 1.11 版以下不成立，這台的逾時只剩 A1 加 Ubuntu 那一條成因。</li>
<li><strong>Bastion plugin 的日誌讀不到</strong>：plugin 的目錄叫 <code>bastions</code>（不是 <code>bastion</code>），裡面有 <code>bastions.log</code>，而目錄的權限是 <code>drwxr--r--</code>、擁有者與群組都是 <code>snap_daemon</code>；<code>ocarun</code> 的主要群組雖然是 <code>snap_daemon</code>，群組那一欄是 <code>r--</code>、沒有 <code>x</code>，列得出檔名、進不了目錄，讀不了裡面的檔案（其他使用者那一欄同樣是 <code>r--</code>，不在這個群組也是一樣）。<code>runcommand</code> 目錄的擁有者是 <code>ocarun</code>、群組 <code>adm</code>。</li>
<li><strong>代理程式的主日誌讀得到</strong>：<code>/var/log/oracle-cloud-agent/agent.log</code> 開放所有人讀。它每 10 分鐘對 Bastion plugin 做一次健康檢查，紀錄是 <code>health check of plugin:[bastions], desiredState:[Enable], currentState:[running], status:[running] version:[1.61.0-6] err:[&lt;nil&gt;]</code>。讀到的是 05:51 與 06:01（UTC）這兩筆，都是 running、沒有錯誤；managed SSH session 逾時的那幾個時段（台北時間 11:04 到 12:07）的紀錄不在讀到的範圍裡。所以這次能確定的是：工作階段已經逾時三次之後，代理程式的健康檢查仍然回報 running，跟主控台上的 <code>RUNNING</code> 一樣看不出這個組合不支援。</li>
</ul>
<p>以文字保存的輸出只留最後 1 KB，<code>grep</code> 的結果被截斷；要讀完整的日誌，輸出改存到 Object Storage（<code>&quot;outputType&quot;</code> 改成 Object Storage 的那一種，這次沒有實跑）。</p>
<h2 id="這台主機剩下的登入入口">這台主機剩下的登入入口</h2>
<p>Run Command 寫不了公鑰之後，這台主機剩下的入口與各自的條件：</p>
<ul>
<li><strong>替換開機磁碟</strong>：官方〈Replacing a Boot Volume〉限定只能換成同一個 Linux 發行版，換不成 Oracle Linux。換成新的 Ubuntu 開機磁碟時，cloud-init 讀的仍是建立時的中繼資料，而 <code>ssh_authorized_keys</code> 是空的，所以新磁碟上也沒有公鑰；這是從兩份說明推出的結果，這次沒有實跑。換成自己做的 Ubuntu aarch64 自訂映像時，公鑰或設了密碼的救援帳號可以直接做進映像，不靠 cloud-init 從中繼資料讀；這條路不重建主機、不放掉 A1 的容量，代價是要自己做映像、匯入，而〈Replacing a Boot Volume〉沒有寫自訂映像在 A1 上能不能用，這次沒有實跑。</li>
<li><strong>把開機磁碟掛到另一台主機修改</strong>：停止這台、卸下開機磁碟、掛到同一個可用區的另一台主機，在那裡把公鑰寫進 <code>authorized_keys</code> 再掛回來。另一台主機的架構不必相同：開機磁碟當成資料磁碟掛上去，讀寫的是上面的檔案系統，跟 CPU 架構無關（推論）。這個帳號在這一區能建的免費機型只有 A1，用 A1 要再搶一次容量；改用付費的 x86 機型（例如 <code>VM.Standard.E5.Flex</code> 的 1 OCPU）開幾十分鐘、改完就刪，付費 x86 機型通常不像 A1 那樣容量不足（推論，這一區沒有實測），費用依帳號狀態而定，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果〉。這次兩種都沒有實跑。</li>
<li><strong>序列主控台</strong>：主機裡沒有設密碼的帳號，登入提示這一步登不進去；官方從開機選單進維護模式重設金鑰的範例只寫了 Oracle Linux，同樣的做法在這台 Ubuntu 上能不能用，官方沒有寫，這次也沒有實跑。</li>
<li><strong>重建主機</strong>：重建時放自己的公鑰（不論映像），讓 port forwarding session 登得進去；要用 managed SSH session 時，另外選可用 plugin 裡有 Bastion 的 Oracle Linux 版本（10 確定沒有；9、8、7.9 查詢工具分不出，要建起來才確認）。只換成 Oracle Linux 而不放公鑰，選到沒有 Bastion plugin 的版本時一樣登不進去。重建要重新搶 A1 的容量；2026-10-25 照這一項重建了一台，見〈重建：Oracle Linux 映像加建立時放公鑰，經由 port forwarding session 登入〉。</li>
</ul>
<p>截至 2026-10-25，這台主機還沒有登入過。登入是在另外重建的一台上完成的，見〈重建：Oracle Linux 映像加建立時放公鑰，經由 port forwarding session 登入〉。</p>
<h2 id="重建oracle-linux-映像加建立時放公鑰經由-port-forwarding-session-登入">重建：Oracle Linux 映像加建立時放公鑰，經由 port forwarding session 登入</h2>
<p>2026-10-25 晚上另外建了一台 A1 主機 <code>verify-a1-ol</code>，Ubuntu 那台 <code>verify-a1</code> 留著不動。Free Tier 與試用中的 tenancy，Always Free 的 A1 額度合計 2 OCPU、12 GB（官方〈Always Free Resources〉；付費 tenancy 的免費量比這個大，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位</a>〈Always Free 的額度與它綁在 home region 上的範圍〉），兩台各 1 OCPU、6 GB 剛好用滿；要再建一台 A1 而不超出額度，得先刪掉其中一台（同一天稍晚終止了 <code>verify-a1</code>，見〈刪除這組資源的順序〉）。超出時的結果依帳號狀態而定（試用中照樣建得起來，試用結束那一刻合計超額的 A1 會全部停用），見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region</a> 的〈Always Free 的額度與它綁在 home region 上的範圍〉。<code>verify-a1-ol</code> 改了兩個建立時才能決定的設定：映像檔換成 Oracle Linux，避開〈Known Issues for Bastion〉列為不支援 managed SSH session 的 A1 加非 Oracle Linux；建立時放自己的公鑰，讓 port forwarding session 這條不經過 Bastion plugin 的路徑也登得進去。其餘的設定（compartment、private subnet、不配公開 IP、只允許中繼資料第二版、Bastion plugin 開啟）跟 <code>verify-a1</code> 相同。這一節的指令跟〈建立之後〉那一節一樣，先在 shell 裡 <code>export OCI_CLI_AUTH=security_token</code>。</p>
<h3 id="建立之前查映像檔版本與它可用的-plugin">建立之前查映像檔版本與它可用的 plugin</h3>
<p>managed SSH session 要主機上有 Bastion plugin，而哪些 plugin 可用是依作業系統與版本決定的。官方〈Managing Plugins with Oracle Cloud Agent〉寫明可以在建立之前用 ListInstanceagentAvailablePlugins 查，對應的 CLI 是 <code>available-plugins get</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 依作業系統名稱與版本，列出代理程式在這種映像上可用的 plugin</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci instance-agent available-plugins get -c &lt;compartment 的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --os-name <span class="s2">&#34;Oracle Linux&#34;</span> --os-version &lt;作業系統版本&gt; --all --query <span class="s1">&#39;data[].name&#39;</span></span></span></code></pre></div><ul>
<li><code>--os-name</code>、<code>--os-version</code>：照抄 <code>oci compute image list</code> 輸出的 <code>operating-system</code> 與 <code>operating-system-version</code> 欄。這一欄是大版本：映像名稱是 <code>Oracle-Linux-10.2-aarch64-…</code> 的那一個，這一欄是 <code>10</code>；相容 A1 的 Oracle Linux 映像在這一欄只有 <code>10</code>、<code>9</code>、<code>8</code>、<code>7.9</code> 四種值。</li>
<li>先建一份對照：填一個不存在的版本（例如 <code>99</code>）查一次，記下回傳的清單。這支工具查不到對應時不報錯，回傳不分版本的全部 plugin（官方 CLI 參考寫明；實測填不存在的版本、或不存在的作業系統名稱，回傳的都是同一份 15 項的清單，裡面有 <code>Bastion</code>）。</li>
<li>讀法：查到的清單跟對照不同，才算查到這個版本的資料，這時清單裡有 <code>Bastion</code> 才建得了 managed SSH session；跟對照相同時，分不出是這個版本提供全部 plugin，還是工具沒有它的資料。</li>
<li>2026-10-25 的結果：Oracle Linux <code>10</code>（填 <code>10.2</code> 也一樣）回傳 8 項、沒有 <code>Bastion</code>，跟對照不同，確定沒有；Ubuntu <code>24.04</code> 回傳 12 項、有 <code>Bastion</code>，跟對照不同，確定有，但它在 A1 上受〈Known Issues for Bastion〉第一條限制；Oracle Linux <code>9</code>、<code>8</code>、<code>7.9</code> 與 Ubuntu <code>26.04</code> 回傳的清單跟對照完全相同，這支工具分不出它們有沒有 <code>Bastion</code>。要確認 Oracle Linux 9，剩下的做法是建一台再用 <code>plugin list</code> 看，或找官方逐版本列出 plugin 的文件（這次沒有找到）。</li>
</ul>
<p><code>verify-a1-ol</code> 建立時沒有做這一步，選的是建立時間最新的 Oracle Linux 10.2，結果見〈結果：這台 Oracle Linux 10.2 主機上沒有 Bastion plugin〉。</p>
<h3 id="建主機oracle-linux-映像加-ssh-authorized-keys-file">建主機：Oracle Linux 映像加 &ndash;ssh-authorized-keys-file</h3>
<p>建立的指令跟〈等效的 CLI 指令〉那一條相同，改了映像檔、公鑰與名稱三處；<code>--no-retry</code> 是重送腳本裡的寫法（理由見〈重送建立請求〉），單跑一次時可以拿掉。容量不足時，把同樣的三處改進重送腳本裡的 launch 指令：</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"># 查相容 A1 機型、指定大版本的 Oracle Linux 映像，取這個版本最新的建置</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">oci compute image list -c &lt;tenancy 的 OCID&gt; --operating-system <span class="s2">&#34;Oracle Linux&#34;</span> --operating-system-version &lt;大版本&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="se"></span>  --shape VM.Standard.A1.Flex --sort-by TIMECREATED --sort-order DESC --query <span class="s1">&#39;data[0].id&#39;</span> --raw-output
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 建主機：換掉 --image-id 與 --display-name，加上 --ssh-authorized-keys-file</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">oci --no-retry compute instance launch -c &lt;compartment 的 OCID&gt; --availability-domain &lt;可用區名稱&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="se"></span>  --display-name verify-a1-ol --shape VM.Standard.A1.Flex --shape-config <span class="s1">&#39;{&#34;ocpus&#34;:1,&#34;memoryInGBs&#34;:6}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="se"></span>  --image-id &lt;上一條印出的映像 OCID&gt; --subnet-id &lt;private subnet 的 OCID&gt; --assign-public-ip <span class="nb">false</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="se"></span>  --ssh-authorized-keys-file &lt;登入主機用的公鑰檔&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="se"></span>  --agent-config <span class="s1">&#39;{&#34;pluginsConfig&#34;:[{&#34;name&#34;:&#34;Bastion&#34;,&#34;desiredState&#34;:&#34;ENABLED&#34;}]}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="se"></span>  --is-pv-encryption-in-transit-enabled <span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="se"></span>  --instance-options <span class="s1">&#39;{&#34;areLegacyImdsEndpointsDisabled&#34;:true}&#39;</span></span></span></code></pre></div><ul>
<li><code>-c &lt;tenancy 的 OCID&gt;</code>：理由同〈等效的 CLI 指令〉的 <code>--image-id</code> 那一項；這次填實驗用的 compartment 也列得出同樣的平台映像。</li>
<li><code>--operating-system-version &lt;大版本&gt;</code>：決定取哪一個大版本，例如 <code>9</code>。不給時，<code>--sort-by TIMECREATED</code> 取到的是所有版本裡建立時間最新的那一個建置，這次取到 <code>Oracle-Linux-10.2-aarch64-2026.10.16-0</code>，也就是沒有 Bastion plugin 的版本。要用 managed SSH session，填〈建立之前查映像檔版本與它可用的 plugin〉確定有 <code>Bastion</code> 的版本；Oracle Linux 這次沒有一個版本查得到確定有，<code>9</code>、<code>8</code>、<code>7.9</code> 的查詢結果一樣，要建起來用 <code>plugin list</code> 才知道。這篇的 Backlog 選 <code>9</code> 去驗，理由是它在沒有被確定排除的版本裡最新，不是因為查詢結果比 <code>8</code> 好。</li>
<li><code>--ssh-authorized-keys-file</code>：公鑰檔的路徑，內容寫進主機的中繼資料 <code>ssh_authorized_keys</code>，開機時由 cloud-init 寫進預設使用者 <code>opc</code> 的 <code>authorized_keys</code>。這一項建立之後不能改（〈建立之前的檢查〉），所以要在這裡放。這次用自己另產的一組金鑰，跟 Bastion 工作階段用的那一組分開。用同一組也連得上，指令還少一把 <code>-i</code>；工作階段的私鑰外洩時，拿到的人還要一個有 IAM 權限的帳號替他建工作階段、來源 IP 也要在 Bastion 的允許清單裡，所以分開換到的主要是另一個方向：登入主機的私鑰外洩時，主機那一把要換（建立時放的這一把要另外改主機上的 <code>authorized_keys</code>），工作階段那一把不受影響（推論，這次沒有實跑）。</li>
</ul>
<p>這次第一次送出就建成，沒有遇到容量不足：台北時間 19:06:13 送出，19:07 查詢時已經是 <code>RUNNING</code>。容量是否不足依區域與時段而變，<code>verify-a1</code> 在同一區重送到第 16 次才建成（〈重送建立請求〉），所以重送腳本仍然要準備好。</p>
<h3 id="結果這台-oracle-linux-102-主機上沒有-bastion-plugin">結果：這台 Oracle Linux 10.2 主機上沒有 Bastion plugin</h3>
<p>主機開機約 30 分鐘後，<code>oci instance-agent plugin list --instanceagent-id &lt;主機的 OCID&gt; -c &lt;compartment 的 OCID&gt;</code> 列出的 plugin 裡沒有 Bastion：Compute Instance Run Command、Compute Instance Monitoring、Custom Logs Monitoring 是 <code>RUNNING</code>，其餘是 <code>STOPPED</code> 或 <code>NOT_SUPPORTED</code>，Bastion 這一項整個不在清單上。主機的 <code>agent-config</code> 仍然寫著 Bastion 是 <code>ENABLED</code>，同一時間查 <code>verify-a1</code> 的 Bastion plugin 是 <code>RUNNING</code>。登入之後在主機上查，代理程式是 <code>oracle-cloud-agent-1.64.0-16.el10.aarch64</code>，<code>/var/log/oracle-cloud-agent/plugins/</code> 底下沒有 <code>bastions</code>（<code>verify-a1</code> 上 plugin 目錄的名字）也沒有 <code>bastion</code>，<code>grep -i bastion</code> 代理程式的日誌也沒有任何一行。plugin 清單、plugin 目錄與日誌這三處都沒有 Bastion，跟〈建立之前查映像檔版本與它可用的 plugin〉查到的結果一致：Oracle Linux 10 可用的 plugin 清單裡沒有 Bastion，所以 managed SSH session 在這台建不起來。〈Known Issues for Bastion〉只寫 Arm 主機要跑 Oracle Linux，沒有寫版本，所以只讀 Known Issues 看不出這一點。</p>
<p>這台建立時放了公鑰，port forwarding session 這條路徑還在，所以不必重建。</p>
<h3 id="建-port-forwarding-session-並經由本機的轉送登入">建 port forwarding session 並經由本機的轉送登入</h3>
<p>port forwarding session 只替一個私有 IP 的一個 port 開 TCP 轉送。官方〈Bastion Overview〉寫明它不需要目標主機上執行 Oracle Cloud Agent，〈Connecting to a Port Forwarding Session〉也寫明不符合 managed SSH session 條件的主機可以用它連。主機那一端的登入驗證是 sshd 與 <code>authorized_keys</code>。Bastion 另有一種 dynamic port forwarding（SOCKS5）工作階段，一個工作階段連得到 subnet 裡的任何主機，同樣不經過代理程式，代價是本機要走 SOCKS 代理、能連到的範圍也變寬（〈Bastion Overview〉，這次沒有實跑）；這次用的是只轉到一台主機的 port forwarding：</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"># 建一個轉到主機 22 port 的 port forwarding session</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">oci bastion session create-port-forwarding --bastion-id &lt;Bastion 的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="se"></span>  --target-private-ip &lt;主機的私有 IP&gt; --target-port <span class="m">22</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="se"></span>  --ssh-public-key-file &lt;這次工作階段用的公鑰&gt; --session-ttl <span class="m">3600</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="se"></span>  --wait-for-state SUCCEEDED --wait-for-state FAILED --query <span class="s1">&#39;data.resources[0].identifier&#39;</span> --raw-output
</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="c1"># 在本機開轉送：本機的 2222 port 經由 Bastion 轉到主機的 22 port（這條指令會一直佔著終端機）</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">ssh -i &lt;這次工作階段用的私鑰&gt; -N -L 2222:&lt;主機的私有 IP&gt;:22 -p <span class="m">22</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="se"></span>  &lt;上一條印出的工作階段 OCID&gt;@host.bastion.&lt;區域&gt;.oci.oraclecloud.com
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 另開一個終端機，經由本機的 2222 port 登入主機</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">ssh -i &lt;登入主機用的私鑰&gt; -p <span class="m">2222</span> opc@localhost</span></span></code></pre></div><ul>
<li><code>--target-private-ip</code>、<code>--target-port 22</code>：轉送的終點。port forwarding session 不必指定主機的 OCID，也不經過主機上的代理程式。</li>
<li><code>--session-ttl 3600</code>：工作階段的時效（秒），上限是 Bastion 的 <code>--max-session-ttl</code>（<code>oci bastion bastion get --bastion-id &lt;Bastion 的 OCID&gt;</code> 的 <code>max-session-ttl-in-seconds</code>）；到期之後轉送中斷，要再建一個工作階段。</li>
<li><code>--wait-for-state SUCCEEDED --wait-for-state FAILED</code>：等建立的 work request 到其中一個狀態再回傳；<code>--query</code> 取出的是新工作階段的 OCID。work request 是 <code>FAILED</code> 時這一條一樣會印出 OCID，所以用 <code>oci bastion session get --session-id &lt;印出的 OCID&gt; --query 'data.&quot;lifecycle-state&quot;'</code> 確認工作階段是 <code>ACTIVE</code>，不是的話照〈結果：停在 CREATING 約 15 分鐘後轉成 DELETED〉讀 work request（記錄這次非同步建立的進度與錯誤的物件）的錯誤。這次 11 秒就是 <code>ACTIVE</code>，<code>verify-a1</code> 的 managed SSH session 是等 15 分鐘之後逾時。</li>
<li>本機轉送那一條的 <code>&lt;這次工作階段用的私鑰&gt;</code>：對應 <code>--ssh-public-key-file</code> 的那一把，用來通過 Bastion 的驗證；<code>-N</code> 只開轉送、不執行遠端指令；<code>-L 2222:&lt;主機的私有 IP&gt;:22</code> 把本機的 2222 port 轉到主機的 22 port，本機 port 可以選任何沒在用的號碼（官方原文是 any available local port），選 1024 以上的不需要 root；<code>-p 22</code> 是 Bastion 端的 SSH port，也是 ssh 的預設值，照官方的範例寫出來；使用者名稱是工作階段的 OCID，<code>&lt;區域&gt;</code> 是 <code>ap-batam-1</code> 這種區域識別碼。</li>
<li>登入主機那一條的 <code>&lt;登入主機用的私鑰&gt;</code>：對應建立主機時 <code>--ssh-authorized-keys-file</code> 的那一把，用來通過主機上 sshd 的驗證。同一次登入用到兩把金鑰，各自通過一道驗證。</li>
<li>本機轉送那一條的完整寫法不必自己組：<code>oci bastion session get --session-id &lt;工作階段的 OCID&gt; --query 'data.&quot;ssh-metadata&quot;.command' --raw-output</code> 印出同一條指令，主機名稱、使用者名稱與私有 IP 已經填好，只留 <code>&lt;privateKey&gt;</code> 與 <code>&lt;localPort&gt;</code> 兩個位置要換（實測）。主控台的工作階段清單裡，那個工作階段的選單也有 Copy SSH command（〈Connecting to a Port Forwarding Session〉）。</li>
</ul>
<p><strong>替代做法：用 <code>-J</code> 一條指令完成轉送與登入</strong>。ssh 的 ProxyJump 經由 Bastion 直接連到主機，不必另開一個終端機、也不必挑本機 port。兩把私鑰要各自交代給 ssh，所以寫成一個設定檔，再用 <code>ssh -F &lt;設定檔&gt; &lt;主機的別名&gt;</code> 連：</p>





<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"># Bastion 這一段：使用者名稱是工作階段的 OCID，用工作階段的私鑰
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">Host &lt;Bastion 的別名&gt;
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  HostName host.bastion.&lt;區域&gt;.oci.oraclecloud.com
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  User &lt;工作階段的 OCID&gt;
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  IdentityFile &lt;這次工作階段用的私鑰&gt;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  IdentitiesOnly yes
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"># 主機這一段：經由上面那一段跳過去，用登入主機的私鑰
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Host &lt;主機的別名&gt;
</span></span><span class="line"><span class="ln">10</span><span class="cl">  HostName &lt;主機的私有 IP&gt;
</span></span><span class="line"><span class="ln">11</span><span class="cl">  User opc
</span></span><span class="line"><span class="ln">12</span><span class="cl">  IdentityFile &lt;登入主機用的私鑰&gt;
</span></span><span class="line"><span class="ln">13</span><span class="cl">  IdentitiesOnly yes
</span></span><span class="line"><span class="ln">14</span><span class="cl">  ProxyJump &lt;Bastion 的別名&gt;</span></span></code></pre></div><ul>
<li><code>IdentitiesOnly yes</code>：只用 <code>IdentityFile</code> 指定的那一把，不把 ssh-agent 裡的其他金鑰一把把試過去；試的次數太多時伺服器會先斷線。</li>
<li>2026-10-25 用同一個 port forwarding session 實跑，登入成功（<code>opc@verify-a1-ol</code>）。這條路的終點跟 <code>-L</code> 一樣是工作階段指定的私有 IP 與 port，所以不必另建工作階段；主機金鑰記在 known_hosts 的私有 IP 底下，而 <code>-L</code> 的寫法記在 <code>[localhost]:2222</code> 底下，之後用同一個本機 port 轉到另一台主機時，ssh 會報 <code>REMOTE HOST IDENTIFICATION HAS CHANGED</code>。</li>
</ul>
<p>第一次跑本機轉送那一條時，ssh 先要求確認的是 Bastion 自己（<code>host.bastion.&lt;區域&gt;.oci.oraclecloud.com</code>）的主機金鑰，這一把跟要登入的主機無關，官方文件沒有列出它的指紋。接著第一次連 <code>localhost:2222</code> 時，ssh 要求確認的才是主機的金鑰（機制與 known_hosts 見 <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>）。這台主機的金鑰在開機時由 cloud-init 產生，指紋印在序列主控台輸出裡：用〈結果：停在 CREATING 約 15 分鐘後轉成 DELETED〉那兩條 <code>console-history</code> 指令讀出輸出，找 <code>BEGIN SSH HOST KEY FINGERPRINTS</code> 那一段的 ED25519 指紋，跟 ssh 顯示的指紋相同才接受。這次是先接受、登入之後才讀序列主控台輸出比對，兩邊的 ED25519 指紋完全相同；照上面的順序先比對再接受，才擋得住連到別台主機的情形。這台的 Run Command plugin 是 <code>RUNNING</code>，所以另一個取得指紋的方式是用 Run Command 送 <code>ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub</code>，主機金鑰的公鑰檔所有人都讀得到，<code>ocarun</code> 沒有 sudo 也讀得了；要先有〈讓主機領得到指令：dynamic group 與 IAM 政策〉那一組設定（推論，這次沒有實跑）。</p>
<p>台北時間 19:37:55 登入成功：<code>whoami</code> 印出 <code>opc</code>，<code>/etc/os-release</code> 是 Oracle Linux Server 10.2，<code>uname -m</code> 是 <code>aarch64</code>，<code>sudo -n true</code> 成功（<code>opc</code> 有免密碼的 sudo）。從送出建立到登入約 32 分鐘，大部分時間花在等 Bastion plugin，而它最後沒有出現。序列主控台輸出裡，cloud-init 在開機約 38 秒時印出 <code>opc</code> 的 <code>authorized_keys</code> 並完成（送出建立之後約 73 秒），所以直接走 port forwarding 的話，主機 <code>RUNNING</code> 之後一兩分鐘就可以連，不必等 plugin。建立時放了公鑰的主機，port forwarding 本身就是完整的登入路徑；managed SSH session 多給的是登入的公鑰只在工作階段期間放進主機、主機上不留常駐的金鑰，這台已經放了常駐的公鑰，這一點就沒有了。</p>
<ul>
<li><strong>沒做時</strong>（建立時沒放公鑰）：主機的 <code>authorized_keys</code> 是空的，而這台的 managed SSH session 又建不起來，結果跟 <code>verify-a1</code> 相同：兩種工作階段都登不進去，剩下的入口見〈這台主機剩下的登入入口〉。</li>
<li><strong>替代做法</strong>：
<ul>
<li>映像檔選 Ubuntu 加建立時放公鑰：port forwarding 同樣登得進去，managed SSH session 仍然受〈Known Issues for Bastion〉第一條限制（照 Known Issues 推出，這次沒有實跑）。</li>
<li>映像檔選 Oracle Linux 9 加建立時放公鑰：<code>available-plugins get</code> 對 9 的回應跟查不到時相同，有沒有 Bastion 要建起來才知道；有的話 managed SSH session 與 port forwarding 兩種工作階段都可用，沒有的話跟這次一樣靠 port forwarding（推論，這次沒有實跑）。有 Bastion 時換到的是 managed SSH session 這一條不依賴常駐公鑰的路徑，付出的是用比較舊的大版本。日常登入想讓主機上不留常駐金鑰、只在工作階段期間放臨時公鑰時，才值得為此選 9；只要登得進去，10.2 加公鑰加 port forwarding 已經夠用。</li>
<li>選有 Bastion plugin 的版本、不放公鑰、只靠 managed SSH session：plugin 一旦沒有運作（像 <code>verify-a1</code> 那樣狀態是 <code>RUNNING</code> 卻建不起工作階段），就沒有任何 SSH 的入口。建立時放公鑰，換到的是一條不依賴 plugin 的路徑。兩種工作階段最後都經過主機上的 sshd，所以 sshd 本身壞掉時兩種都連不上（從兩種工作階段的機制推論）；那時剩下序列主控台與重建，序列主控台在 Oracle Linux 上有官方從開機選單進維護模式的範例（〈這台主機剩下的登入入口〉），這次沒有實跑。</li>
</ul>
</li>
</ul>
<h2 id="刪除這組資源的順序">刪除這組資源的順序</h2>
<p>實驗結束要清掉時，順序跟建立時相反，因為每一項資源被後面建的資源依賴著：</p>
<ol>
<li>終止兩台主機（<code>verify-a1</code> 與 <code>verify-a1-ol</code>），在終止對話框勾選「Permanently delete the attached boot volume」，開機磁碟才會一起刪掉。CLI 是 <code>oci compute instance terminate --instance-id &lt;主機的 OCID&gt; --preserve-boot-volume false --force</code>：<code>--preserve-boot-volume false</code> 對應那個勾選，<code>--force</code> 跳過確認提示。2026-10-25 用這一條終止了 <code>verify-a1</code>，約 67 秒後主機與它的開機磁碟都是 <code>TERMINATED</code>；用 <code>oci bv boot-volume list -c &lt;compartment 的 OCID&gt; --availability-domain &lt;可用區名稱&gt;</code> 確認開機磁碟的狀態。本機的 port forwarding 轉送用 Ctrl-C 結束，工作階段到了 <code>--session-ttl</code> 自己失效。</li>
<li>刪除 Run Command 用的政策與 dynamic group（<code>oci iam policy delete --policy-id &lt;政策的 OCID&gt;</code>、<code>oci iam dynamic-group delete --dynamic-group-id &lt;dynamic group 的 OCID&gt;</code>）。政策建在 compartment 上，compartment 刪除之前要先刪；dynamic group 在 tenancy 層，不會隨 compartment 刪除，不刪的話它的比對規則仍然指著已經終止的主機 OCID。2026-10-25 終止 <code>verify-a1</code> 之後照這個順序刪了兩者，兩條都加 <code>--force</code> 跳過確認提示；刪完之後再 <code>oci iam policy get</code> 與 <code>oci iam dynamic-group get</code>，兩者都回 <code>404 NotAuthorizedOrNotFound</code>，代表已經不存在。</li>
<li>VCN 裡另外建了 NAT gateway 與 NSG 時，先刪它們：NSG 要先從網卡拿掉才刪得掉，NAT gateway 要先從路由表拿掉那一條路由（2026-10-27 實跑：順序反了時 NSG 回 <code>412 PreconditionFailed</code>，NAT gateway 回外層 <code>400 InvalidParameter</code>、訊息裡包著 <code>409, Conflict</code>），順序與指令見 <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>〈刪除這組資源的順序〉。</li>
<li>刪除 Bastion（<code>oci bastion bastion delete --bastion-id &lt;Bastion 的 OCID&gt;</code>）。Bastion 的私有端點在目標 subnet 裡，Bastion 還在時 subnet 刪不掉；它的工作階段隨 Bastion 一起失效。</li>
<li>刪除 subnet。subnet 裡還有主機或網卡時刪不掉。</li>
<li>刪除 subnet 用過的路由表與 security list。</li>
<li>刪除 service gateway。</li>
<li>刪除 VCN。</li>
<li>刪除 compartment。官方〈Deleting a Compartment〉寫明 compartment 要先清空所有資源（包括掛在它上面的權限政策）才能刪除，刪除是非同步的工作，狀態會先變成 Deleting。</li>
</ol>
<p>這個順序實際跑過的是終止 <code>verify-a1</code>、刪除政策和 dynamic group，以及 2026-10-27 照私有子網路那篇刪掉 NAT gateway 與 NSG 的第 3 步；其餘各步還沒有實際跑過（截至 2026-10-27 <code>verify-a1-ol</code> 與網路都還在），各項刪不掉時，看錯誤訊息指出的那一項依賴、先刪它。</p>
<p>要讓這組資源可以重建、清掉也不心疼，改用 IaC 描述它們，見 <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 工具選型與 state 地基</a>；已經用主控台或 CLI 建好的資源要收進 IaC，做法見 <a href="/blog/devops/infra/takeover/partial-iac-bulk-import/" data-link-title="Unmanaged Resource 批次 Import 工作流" data-link-desc="把 Terraform state 外的雲端資源有系統地納入 IaC 管理：優先序判斷、import block 語法、generated HCL 的 review 要點、批次策略與常見失敗處理">Unmanaged Resource 批次 Import 工作流</a>。</p>
]]></content:encoded></item><item><title>磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/storage/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/storage/</guid><description>&lt;p>建立頁的儲存區塊決定主機的資料放在哪裡、能放多少、讀寫多快，以及刪除主機時資料是否跟著刪除。範圍是掛在單一台主機上的磁碟；備份、快照與跨區域複製的設計另見各家的區塊儲存文件。文中的大小上限、效能等級與預設值取自各家官方文件，查證日期 2026-10-24。&lt;/p>
&lt;h2 id="開機磁碟與區塊磁碟">開機磁碟與區塊磁碟&lt;/h2>
&lt;p>雲端主機的磁碟跟主機本身是兩個獨立的資源。&lt;strong>開機磁碟&lt;/strong>（Oracle Cloud 叫 boot volume）在建立主機時從映像檔複製出來，裝著作業系統；&lt;strong>區塊磁碟&lt;/strong>（block volume）是另外建立、掛上去的資料磁碟，作業系統看到的是一顆新的空磁碟，要自己分割與格式化。兩種都是經由網路掛到主機上的虛擬磁碟，主機停止時資料保留，主機終止之後磁碟可以單獨存在，見 &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> 卡裡同一個概念在 AWS 上的說明。&lt;/p>
&lt;p>實驗用的小主機只用開機磁碟就夠。資料要跟作業系統分開管理時（重裝系統而保留資料、把資料搬到另一台主機），資料放在區塊磁碟上。Oracle Cloud 的建立頁最多可以同時建立並掛載 10 顆區塊磁碟，也可以之後再掛。&lt;/p>
&lt;h2 id="開機磁碟的大小">開機磁碟的大小&lt;/h2>
&lt;p>開機磁碟的預設大小由映像檔決定，Oracle Cloud 的 Linux 平台映像預設是 50 GB。建立頁可以勾選自訂大小，範圍是 50 GB 到 32 TB，且不能小於映像檔的預設值。免費額度按磁碟容量計算時，加大開機磁碟會直接吃掉額度：Oracle Cloud 的 Always Free（不會到期的免費額度）給的是開機磁碟與區塊磁碟合計 200 GB，一台主機的開機磁碟加大到 200 GB，第二台主機的開機磁碟就超出額度。Oracle Cloud 在付費帳戶新建一顆開機磁碟時，判定免費與否的單位是整顆磁碟：放不進剩餘額度的那一顆整顆不算免費，不是只有超出的那幾 GB；把既有的免費開機磁碟線上加大到超過剩餘額度、或另建一顆超過額度的資料磁碟，結果相同：加大的那一顆整顆移出免費額度，另建的那一顆整顆在額度外。之後刪掉別的磁碟空出額度、剩餘額度放得下付費的那一顆時，它會自動轉成免費，而轉換不是當下發生：線上加大而移出額度的那一顆開機磁碟，一次實測等了 26 到 31 分鐘，這段時間照付費計；建立時就在額度外的磁碟會自動轉換是 Always Free 頁寫的行為，延遲沒有量（以上都是 2026-10-27 實測，預設效能等級、home region。見 &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;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位&lt;/a> 的〈超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果〉；手上已經用掉多少，到區塊儲存服務的開機磁碟與區塊磁碟清單把每顆的大小加起來。&lt;/p>
&lt;p>磁碟的大小&lt;strong>只能加大、不能縮小&lt;/strong>。Oracle Cloud 支援在主機開著的時候線上加大，加大之後要在作業系統裡把分割區與檔案系統延伸到新的大小，作業系統才用得到新增的空間：一般 Linux 用 &lt;code>growpart&lt;/code> 延伸分割區，再依檔案系統用 &lt;code>resize2fs&lt;/code>（ext4）或 &lt;code>xfs_growfs&lt;/code>（XFS）延伸檔案系統，Oracle Linux 的平台映像另附一支 &lt;code>oci-growfs&lt;/code> 一次做完。所以建立時先用預設大小，空間不夠再加；一開始開得太大，之後想縮回去只能建新磁碟、把資料搬過去。&lt;/p>
&lt;h2 id="效能等級">效能等級&lt;/h2>
&lt;p>磁碟的讀寫速度有兩種決定方式：獨立於容量另外設定，或依容量換算。Oracle Cloud 與 AWS 的 gp3 讓效能獨立於容量另外設定；Google Cloud 的 Persistent Disk 依容量換算，磁碟類型決定每 GB 的效能，容量越大效能越高。Oracle Cloud 用 VPU（volume performance unit，每 GB 配幾個效能單位）分級：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>等級&lt;/th>
 &lt;th>每 GB 的 VPU&lt;/th>
 &lt;th>用途&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Lower Cost&lt;/td>
 &lt;td>0&lt;/td>
 &lt;td>大量循序讀寫（串流、日誌處理），只能用在區塊磁碟&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Balanced&lt;/td>
 &lt;td>10&lt;/td>
 &lt;td>開機磁碟與一般用途的預設值&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Higher Performance&lt;/td>
 &lt;td>20&lt;/td>
 &lt;td>資料庫這類讀寫頻繁的負載&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ultra High Performance&lt;/td>
 &lt;td>30 到 120&lt;/td>
 &lt;td>需要高 IOPS（每秒讀寫次數）與低延遲的負載&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>VPU 另外計費：Lower Cost 只收儲存費，其餘等級在儲存費之外加收效能費。開機磁碟不能選 Lower Cost，預設是 Balanced。官方的 Always Free 頁只寫了 200 GB 的容量，沒有寫這 200 GB 涵蓋哪個效能等級；建立頁在 Always Free 主機上預設給的是 Balanced，實驗機維持預設，建好之後到帳單的成本分析頁看區塊儲存有沒有出現費用，就能確認自己的帳號怎麼算。Oracle Cloud 的效能等級在磁碟建立之後可以修改，不必重建磁碟；主機上的磁碟延遲或 iowait 長時間偏高、資料庫查詢因為讀寫變慢時，就是該往上調的訊號。&lt;/p></description><content:encoded><![CDATA[<p>建立頁的儲存區塊決定主機的資料放在哪裡、能放多少、讀寫多快，以及刪除主機時資料是否跟著刪除。範圍是掛在單一台主機上的磁碟；備份、快照與跨區域複製的設計另見各家的區塊儲存文件。文中的大小上限、效能等級與預設值取自各家官方文件，查證日期 2026-10-24。</p>
<h2 id="開機磁碟與區塊磁碟">開機磁碟與區塊磁碟</h2>
<p>雲端主機的磁碟跟主機本身是兩個獨立的資源。<strong>開機磁碟</strong>（Oracle Cloud 叫 boot volume）在建立主機時從映像檔複製出來，裝著作業系統；<strong>區塊磁碟</strong>（block volume）是另外建立、掛上去的資料磁碟，作業系統看到的是一顆新的空磁碟，要自己分割與格式化。兩種都是經由網路掛到主機上的虛擬磁碟，主機停止時資料保留，主機終止之後磁碟可以單獨存在，見 <a href="/blog/devops/infra/knowledge-cards/ebs/" data-link-title="EBS" data-link-desc="掛在 EC2 instance 上的持久化區塊儲存（虛擬磁碟），支援 snapshot 快照備份，跟 instance 獨立生命週期">EBS</a> 卡裡同一個概念在 AWS 上的說明。</p>
<p>實驗用的小主機只用開機磁碟就夠。資料要跟作業系統分開管理時（重裝系統而保留資料、把資料搬到另一台主機），資料放在區塊磁碟上。Oracle Cloud 的建立頁最多可以同時建立並掛載 10 顆區塊磁碟，也可以之後再掛。</p>
<h2 id="開機磁碟的大小">開機磁碟的大小</h2>
<p>開機磁碟的預設大小由映像檔決定，Oracle Cloud 的 Linux 平台映像預設是 50 GB。建立頁可以勾選自訂大小，範圍是 50 GB 到 32 TB，且不能小於映像檔的預設值。免費額度按磁碟容量計算時，加大開機磁碟會直接吃掉額度：Oracle Cloud 的 Always Free（不會到期的免費額度）給的是開機磁碟與區塊磁碟合計 200 GB，一台主機的開機磁碟加大到 200 GB，第二台主機的開機磁碟就超出額度。Oracle Cloud 在付費帳戶新建一顆開機磁碟時，判定免費與否的單位是整顆磁碟：放不進剩餘額度的那一顆整顆不算免費，不是只有超出的那幾 GB；把既有的免費開機磁碟線上加大到超過剩餘額度、或另建一顆超過額度的資料磁碟，結果相同：加大的那一顆整顆移出免費額度，另建的那一顆整顆在額度外。之後刪掉別的磁碟空出額度、剩餘額度放得下付費的那一顆時，它會自動轉成免費，而轉換不是當下發生：線上加大而移出額度的那一顆開機磁碟，一次實測等了 26 到 31 分鐘，這段時間照付費計；建立時就在額度外的磁碟會自動轉換是 Always Free 頁寫的行為，延遲沒有量（以上都是 2026-10-27 實測，預設效能等級、home region。見 <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〉）。超出之後的結果依帳號狀態而定，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位</a> 的〈超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果〉；手上已經用掉多少，到區塊儲存服務的開機磁碟與區塊磁碟清單把每顆的大小加起來。</p>
<p>磁碟的大小<strong>只能加大、不能縮小</strong>。Oracle Cloud 支援在主機開著的時候線上加大，加大之後要在作業系統裡把分割區與檔案系統延伸到新的大小，作業系統才用得到新增的空間：一般 Linux 用 <code>growpart</code> 延伸分割區，再依檔案系統用 <code>resize2fs</code>（ext4）或 <code>xfs_growfs</code>（XFS）延伸檔案系統，Oracle Linux 的平台映像另附一支 <code>oci-growfs</code> 一次做完。所以建立時先用預設大小，空間不夠再加；一開始開得太大，之後想縮回去只能建新磁碟、把資料搬過去。</p>
<h2 id="效能等級">效能等級</h2>
<p>磁碟的讀寫速度有兩種決定方式：獨立於容量另外設定，或依容量換算。Oracle Cloud 與 AWS 的 gp3 讓效能獨立於容量另外設定；Google Cloud 的 Persistent Disk 依容量換算，磁碟類型決定每 GB 的效能，容量越大效能越高。Oracle Cloud 用 VPU（volume performance unit，每 GB 配幾個效能單位）分級：</p>
<table>
  <thead>
      <tr>
          <th>等級</th>
          <th>每 GB 的 VPU</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Lower Cost</td>
          <td>0</td>
          <td>大量循序讀寫（串流、日誌處理），只能用在區塊磁碟</td>
      </tr>
      <tr>
          <td>Balanced</td>
          <td>10</td>
          <td>開機磁碟與一般用途的預設值</td>
      </tr>
      <tr>
          <td>Higher Performance</td>
          <td>20</td>
          <td>資料庫這類讀寫頻繁的負載</td>
      </tr>
      <tr>
          <td>Ultra High Performance</td>
          <td>30 到 120</td>
          <td>需要高 IOPS（每秒讀寫次數）與低延遲的負載</td>
      </tr>
  </tbody>
</table>
<p>VPU 另外計費：Lower Cost 只收儲存費，其餘等級在儲存費之外加收效能費。開機磁碟不能選 Lower Cost，預設是 Balanced。官方的 Always Free 頁只寫了 200 GB 的容量，沒有寫這 200 GB 涵蓋哪個效能等級；建立頁在 Always Free 主機上預設給的是 Balanced，實驗機維持預設，建好之後到帳單的成本分析頁看區塊儲存有沒有出現費用，就能確認自己的帳號怎麼算。Oracle Cloud 的效能等級在磁碟建立之後可以修改，不必重建磁碟；主機上的磁碟延遲或 iowait 長時間偏高、資料庫查詢因為讀寫變慢時，就是該往上調的訊號。</p>
<h2 id="加密">加密</h2>
<ul>
<li><strong>靜態加密（encryption at rest）</strong>：Oracle Cloud 與 Google Cloud 的磁碟預設就是加密的，金鑰由業者管理，不必另外設定；AWS 的 EBS 要在每個區域打開 EBS encryption by default，新磁碟才會預設加密。要自己掌握金鑰時（合規要求、要能撤銷金鑰讓資料失效），可以改用自己在金鑰管理服務（Oracle Cloud 的 Vault）建立的金鑰。Oracle Cloud 的區塊儲存只支援 AES 金鑰，不支援 RSA 金鑰。</li>
<li><strong>傳輸中加密（in-transit encryption）</strong>：主機與磁碟之間經由網路傳送的資料也加密。Oracle Cloud 的虛擬機可以選擇開或不開，建立頁的預設是開啟（<a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">實測紀錄</a>的 Storage 段，2026-10-24 建立 A1 Ubuntu 主機時）；支援的裸機主機則固定開啟。用自己的 Vault 金鑰時，同一把金鑰也用在傳輸中加密。</li>
</ul>
<p>在 Oracle Cloud 上兩項都維持預設不會降低安全性，也不影響費用；在 AWS 上要先確認那個區域的預設加密有打開。</p>
<h2 id="刪除終止主機時開機磁碟的預設去留">刪除（終止）主機時開機磁碟的預設去留</h2>
<p>這一項三家的預設不同，而 Oracle Cloud 的預設是保留，刪了主機之後磁碟還在：</p>
<ul>
<li><strong>Oracle Cloud</strong>：終止主機時，開機磁碟<strong>預設保留</strong>。主控台的終止對話框裡有一個「Permanently delete the attached boot volume」的勾選框，不勾就保留。跟主機一起建立的區塊磁碟同樣預設保留，另有一個勾選框控制。保留下來的開機磁碟可以拿來重建主機，或當成資料磁碟掛到另一台主機上。</li>
<li><strong>Google Cloud</strong>：開機磁碟預設跟著主機一起刪除，建立頁開機磁碟的 Deletion rule 預設是 Delete disk。</li>
<li><strong>AWS</strong>：建立主機時一起掛上的根磁碟預設跟著主機一起刪除（<code>DeleteOnTermination</code> 預設為 true，這個值來自 AMI 的設定）；之後另外掛上的資料磁碟預設保留。</li>
</ul>
<p>在 Oracle Cloud 上刪除實驗機時，要嘛勾選刪除開機磁碟，要嘛在之後到區塊儲存服務的開機磁碟清單確認有沒有留下不要的磁碟。保留的磁碟是一個仍然存在的資源。官方的終止說明沒有寫它是否計費，而 Always Free 頁寫明 200 GB 算的是開機磁碟與區塊磁碟的合計，額度用完時要刪掉主機與它的開機磁碟才能空出空間；所以留下來的開機磁碟繼續佔著額度，之後再建的磁碟放不進剩下的額度時就整顆超出額度（判定以整顆磁碟為單位，實測連結在〈開機磁碟的大小〉）。</p>
<p>這類主機刪了、自己卻還存在而沒有人記得的資源，叫孤兒資源（orphaned resource），各家的帳單上都會出現，見 <a href="/blog/devops/operations/08-cost-management/billing-models/" data-link-title="計費模式理解" data-link-desc="看懂雲端帳單怎麼生成時，先認清收費的維度、on-demand/reserved/savings plan/spot 的承諾差異、以及 egress 這類最常被忽略的隱藏成本">計費模式理解</a> 的〈隱藏成本：帳單上的意外〉。Oracle Cloud 的這個預設最容易在反覆建刪實驗機時累積起來。用過 Google Cloud、AWS 的人，習慣刪了主機就等於刪了開機磁碟；在 Oracle Cloud 上每刪一台都留下一顆 50 GB 的開機磁碟，而主機清單上已經看不到它，它列在另一個選單（區塊儲存服務的開機磁碟清單），額度還沒用完時帳單上也不會出現任何費用。額度扣的是每顆磁碟以 GiB 進位的大小，預設的 50 GB 開機磁碟扣 47 GB（46.6 GiB 進位，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〉），建刪四輪之後這些磁碟佔掉 188 GB，第五台主機的開機磁碟就超出額度，這時才要回頭逐顆辨認哪些磁碟可以刪。刻意終止主機、保留磁碟以便之後重建的情形，什麼時候比停止主機或全部刪除划算，見 <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>〈暫停時保留的範圍：只停服務、停止主機、終止主機、全部刪除〉。</p>
<h2 id="各家的欄位名稱開機磁碟資料磁碟效能等級與終止時的預設">各家的欄位名稱：開機磁碟、資料磁碟、效能等級與終止時的預設</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>開機磁碟</td>
          <td>Boot volume</td>
          <td>Boot disk</td>
          <td>Root volume（EBS）</td>
      </tr>
      <tr>
          <td>資料磁碟</td>
          <td>Block volume</td>
          <td>Persistent Disk / Hyperdisk</td>
          <td>EBS volume</td>
      </tr>
      <tr>
          <td>效能等級</td>
          <td>VPU（Volume performance units）</td>
          <td>Disk type（pd-standard 等）</td>
          <td>Volume type（gp3、io2 等）</td>
      </tr>
      <tr>
          <td>終止時刪除</td>
          <td>預設保留</td>
          <td>預設刪除</td>
          <td>預設刪除</td>
      </tr>
  </tbody>
</table>
<h2 id="延伸閱讀磁碟之外的建立選項">延伸閱讀：磁碟之外的建立選項</h2>
<ul>
<li><a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔</a>：開機磁碟的內容來自哪一個映像檔、停止主機時哪些資源還在計費</li>
<li><a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的 Management 段：Oracle Cloud 代理程式的 Block Volume Management plugin 與其他 plugin 的用途</li>
</ul>
]]></content:encoded></item><item><title>Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/</link><pubDate>Mon, 26 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>事件說明&lt;/strong>：2026-10-26（本篇沒有標 UTC 的日期與時刻都是台北時間），在 Oracle Cloud 的 &lt;code>ap-batam-1&lt;/code> 區域，從一台 macOS 開發機用 OCI CLI 操作。tenancy 以 Free Tier 註冊，2026-10-27 確認它已經是付費帳戶（升級的日期這次沒有記錄）。主機是 A1 機型（&lt;code>VM.Standard.A1.Flex&lt;/code>，1 OCPU、6 GB），映像檔是 &lt;code>Canonical-Ubuntu-24.04-aarch64-2026.09.18-0&lt;/code>，放在既有的私有子網路裡、沒有公開 IP，經由 Bastion 的 port forwarding session 登入。當時在做的操作是：在雲端主機上確認 Ubuntu 的自動更新預設與核心更新前後的重開機判斷，並實際連一次序列主控台。2026-10-27 凌晨另外跑了重建腳本，並終止了重建腳本建出的 &lt;code>verify-ubuntu-rebuild&lt;/code>；接著把 &lt;code>verify-ubuntu&lt;/code> 停止、啟動，在三種狀態下各跑一次重建腳本，用 &lt;code>oci limits&lt;/code> 與 usage API 查區塊磁碟額度與帳務，凌晨最後照刪除順序收掉 &lt;code>verify-ubuntu&lt;/code> 的 NAT gateway 與 NSG。2026-10-27 上午又做了三件事：對 &lt;code>verify-ubuntu&lt;/code> 重跑重建腳本，讓它重建 NAT gateway 與 NSG；用重建腳本新建一台開機磁碟 60 GB 的 &lt;code>verify-ubuntu-60g&lt;/code>，建立期間對同一個名稱再跑一份腳本；以及在網頁主控台的 Limits, quotas and usage 頁讀區塊磁碟的用量；中午前用 usage API 查到那一台開機磁碟的計費項目之後，終止了 &lt;code>verify-ubuntu-60g&lt;/code>。2026-10-27 下午（13 時到 15 時）又做了五件事：不給 &lt;code>BOOT_GB&lt;/code> 用重建腳本新建 &lt;code>verify-ubuntu-nb&lt;/code>、設 compartment quota 試超額的建立、線上加大 &lt;code>verify-ubuntu&lt;/code> 的開機磁碟並另建一顆資料磁碟、終止 &lt;code>verify-ubuntu-nb&lt;/code> 之後觀察付費磁碟的額度用量與帳務明細怎麼變，最後照刪除順序把 &lt;code>verify-ubuntu&lt;/code> 與它的出站設定整組收掉。&lt;/p>&lt;/blockquote>
&lt;p>這篇記錄在 Oracle Cloud 的私有子網路裡建一台 Ubuntu 24.04 主機之後，讓它連得到套件來源、連上序列主控台，以及主機不見之後怎麼照原樣重建。各節依序是：&lt;/p>
&lt;ul>
&lt;li>前置條件：沿用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入&lt;/a>（下文稱「建主機那篇」）建好的 compartment、VCN、私有子網路、service gateway、Bastion 與金鑰。&lt;/li>
&lt;li>建 Ubuntu 24.04 主機，經由 port forwarding session 登入。&lt;/li>
&lt;li>只接了 service gateway 的私有子網路裡，apt 連不到套件來源的錯誤原文與成因。&lt;/li>
&lt;li>加上 NAT gateway、路由規則與只掛在這台主機上的出站 NSG（network security group：套在個別網卡上的一組防火牆規則）之後，&lt;code>apt-get update&lt;/code> 成功。&lt;/li>
&lt;li>序列主控台：產生 RSA 金鑰、建立序列主控台連線（instance console connection）、把官方的連線字串改寫成 ssh 的 ProxyJump 設定，以及看到的開機輸出。&lt;/li>
&lt;li>主機被閒置回收、停止或刪除之後：怎麼發現、哪些資源會留下、主機與序列主控台連線的重建腳本（含用 &lt;code>BOOT_GB&lt;/code> 指定開機磁碟大小）、每一步的讀法、同一個名稱同時跑兩份腳本的結果與會建出第二台主機的空窗、重建之後要重做的事與耗時。&lt;/li>
&lt;li>費用與額度（A1 的免費量、開機磁碟從區塊磁碟額度扣掉多少與用量的查法、合計超過 200 GB 時放不進剩餘額度的那一顆磁碟整顆不算免費額度、用 compartment quota 擋下超額的建立與被擋下時的回應、線上加大開機磁碟與另建資料磁碟超過額度時的結果、空出額度之後付費磁碟自動轉成 Always Free 要等多久與帳務明細怎麼記、網頁主控台 Limits, quotas and usage 頁與 CLI 的對照、在價目表與帳務明細裡找 NAT gateway 收費項目的結果），以及刪除這組資源的順序與實際刪除時的錯誤訊息（NSG 還掛在網卡上時的 412，NAT gateway 還被路由表指著時外層 400、內含 409）與整組收掉的實跑。&lt;/li>
&lt;/ul>
&lt;p>主機上量到的自動更新預設、核心更新前後的 &lt;code>needrestart -b&lt;/code> 與 &lt;code>reboot-required&lt;/code>，寫在 &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>（下文稱「維護篇」）〈會老化的軟體：各條更新管道與更新後的重開機〉，這裡只寫在 Oracle Cloud 上建立與連線的操作。本篇自己的 OCID 與 IP 一律是佔位值：OCID 用 &lt;code>oci ... list&lt;/code> 查，私有 IP 用 &lt;code>oci compute instance list-vnics&lt;/code> 查；錯誤原文裡的位址是 Canonical 鏡像站的。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p><strong>事件說明</strong>：2026-10-26（本篇沒有標 UTC 的日期與時刻都是台北時間），在 Oracle Cloud 的 <code>ap-batam-1</code> 區域，從一台 macOS 開發機用 OCI CLI 操作。tenancy 以 Free Tier 註冊，2026-10-27 確認它已經是付費帳戶（升級的日期這次沒有記錄）。主機是 A1 機型（<code>VM.Standard.A1.Flex</code>，1 OCPU、6 GB），映像檔是 <code>Canonical-Ubuntu-24.04-aarch64-2026.09.18-0</code>，放在既有的私有子網路裡、沒有公開 IP，經由 Bastion 的 port forwarding session 登入。當時在做的操作是：在雲端主機上確認 Ubuntu 的自動更新預設與核心更新前後的重開機判斷，並實際連一次序列主控台。2026-10-27 凌晨另外跑了重建腳本，並終止了重建腳本建出的 <code>verify-ubuntu-rebuild</code>；接著把 <code>verify-ubuntu</code> 停止、啟動，在三種狀態下各跑一次重建腳本，用 <code>oci limits</code> 與 usage API 查區塊磁碟額度與帳務，凌晨最後照刪除順序收掉 <code>verify-ubuntu</code> 的 NAT gateway 與 NSG。2026-10-27 上午又做了三件事：對 <code>verify-ubuntu</code> 重跑重建腳本，讓它重建 NAT gateway 與 NSG；用重建腳本新建一台開機磁碟 60 GB 的 <code>verify-ubuntu-60g</code>，建立期間對同一個名稱再跑一份腳本；以及在網頁主控台的 Limits, quotas and usage 頁讀區塊磁碟的用量；中午前用 usage API 查到那一台開機磁碟的計費項目之後，終止了 <code>verify-ubuntu-60g</code>。2026-10-27 下午（13 時到 15 時）又做了五件事：不給 <code>BOOT_GB</code> 用重建腳本新建 <code>verify-ubuntu-nb</code>、設 compartment quota 試超額的建立、線上加大 <code>verify-ubuntu</code> 的開機磁碟並另建一顆資料磁碟、終止 <code>verify-ubuntu-nb</code> 之後觀察付費磁碟的額度用量與帳務明細怎麼變，最後照刪除順序把 <code>verify-ubuntu</code> 與它的出站設定整組收掉。</p></blockquote>
<p>這篇記錄在 Oracle Cloud 的私有子網路裡建一台 Ubuntu 24.04 主機之後，讓它連得到套件來源、連上序列主控台，以及主機不見之後怎麼照原樣重建。各節依序是：</p>
<ul>
<li>前置條件：沿用 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入</a>（下文稱「建主機那篇」）建好的 compartment、VCN、私有子網路、service gateway、Bastion 與金鑰。</li>
<li>建 Ubuntu 24.04 主機，經由 port forwarding session 登入。</li>
<li>只接了 service gateway 的私有子網路裡，apt 連不到套件來源的錯誤原文與成因。</li>
<li>加上 NAT gateway、路由規則與只掛在這台主機上的出站 NSG（network security group：套在個別網卡上的一組防火牆規則）之後，<code>apt-get update</code> 成功。</li>
<li>序列主控台：產生 RSA 金鑰、建立序列主控台連線（instance console connection）、把官方的連線字串改寫成 ssh 的 ProxyJump 設定，以及看到的開機輸出。</li>
<li>主機被閒置回收、停止或刪除之後：怎麼發現、哪些資源會留下、主機與序列主控台連線的重建腳本（含用 <code>BOOT_GB</code> 指定開機磁碟大小）、每一步的讀法、同一個名稱同時跑兩份腳本的結果與會建出第二台主機的空窗、重建之後要重做的事與耗時。</li>
<li>費用與額度（A1 的免費量、開機磁碟從區塊磁碟額度扣掉多少與用量的查法、合計超過 200 GB 時放不進剩餘額度的那一顆磁碟整顆不算免費額度、用 compartment quota 擋下超額的建立與被擋下時的回應、線上加大開機磁碟與另建資料磁碟超過額度時的結果、空出額度之後付費磁碟自動轉成 Always Free 要等多久與帳務明細怎麼記、網頁主控台 Limits, quotas and usage 頁與 CLI 的對照、在價目表與帳務明細裡找 NAT gateway 收費項目的結果），以及刪除這組資源的順序與實際刪除時的錯誤訊息（NSG 還掛在網卡上時的 412，NAT gateway 還被路由表指著時外層 400、內含 409）與整組收掉的實跑。</li>
</ul>
<p>主機上量到的自動更新預設、核心更新前後的 <code>needrestart -b</code> 與 <code>reboot-required</code>，寫在 <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>（下文稱「維護篇」）〈會老化的軟體：各條更新管道與更新後的重開機〉，這裡只寫在 Oracle Cloud 上建立與連線的操作。本篇自己的 OCID 與 IP 一律是佔位值：OCID 用 <code>oci ... list</code> 查，私有 IP 用 <code>oci compute instance list-vnics</code> 查；錯誤原文裡的位址是 Canonical 鏡像站的。</p>
<h2 id="前置條件沿用-oracle-cloud-建立主機實測紀錄的網路bastion-與金鑰">前置條件：沿用 Oracle Cloud 建立主機實測紀錄的網路、Bastion 與金鑰</h2>
<p>這次沿用建主機那篇〈進建立頁之前：compartment、VCN、閘道與 private subnet〉建好的資源，沒有另建網路：</p>
<ul>
<li><strong>compartment 與 VCN</strong>：放實驗資源的 compartment，以及裡面的 VCN。</li>
<li><strong>私有子網路</strong>：這台主機要放的子網路。它的路由表一開始只有一條規則，目的地是 Oracle 服務網路，出口是 service gateway；security list 的出站規則也只放行 Oracle 服務網路與子網路內部。路由規則裡的目的地是一個名稱而不是位址範圍，這個名稱依區域而不同（這次的值是 <code>all-hsg-services-in-oracle-services-network</code>），用 <code>oci network service list</code> 查，名稱帶「All」的那一筆的 <code>cidr-block</code> 欄就是它。</li>
<li><strong>Bastion</strong>：目標子網路是上面那個私有子網路，client CIDR 是開發機對外的 IP（/32）。開發機的對外 IP 換了之後，來源不在 client CIDR 裡，經由 Bastion 的 ssh 連線建立不起來，要先改 client CIDR（從 client CIDR 的作用推論；這次開發機的 IP 沒有變過，沒有看到實際的錯誤訊息）。</li>
<li><strong>兩把金鑰</strong>：登入主機用的金鑰（建立主機時放進去，重建腳本裡的檔名是 <code>verify-a1-key</code>），以及建 Bastion session 用的金鑰（檔名 <code>session_key</code>）。兩把都是建主機那篇建的：session 用的那一把照〈建立 Bastion 與 managed SSH session〉用 <code>ssh-keygen</code> 另產，登入主機的那一把是〈建主機：Oracle Linux 映像加 &ndash;ssh-authorized-keys-file〉另產、跟 session 那一把分開的一組，兩把分開的理由寫在那一節。</li>
<li><strong>OCI CLI 的登入</strong>：session token 登入，有效期約 1 小時，到期時重跑 <code>oci session authenticate --region &lt;區域&gt; --profile-name DEFAULT</code>，做法見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/cli-session-login/" data-link-title="從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰" data-link-desc="OCI CLI 的兩種身分驗證方式——瀏覽器登入換來的 session token 與上傳公鑰的 API 簽章金鑰——各自把什麼存在本機、有效多久、外洩時的影響範圍，以及登入時的互動提示與錯誤訊息怎麼讀">從本機用 OCI CLI 操作 Oracle Cloud：session token 登入、時效與 API 簽章金鑰</a>。<code>&lt;區域&gt;</code> 填區域的識別碼（這次是 <code>ap-batam-1</code>），<code>oci iam region-subscription list</code> 列得出 tenancy 訂閱的區域（這一條沒有實跑）。</li>
</ul>
<p>下文的 CLI 指令裡，<code>&lt;compartment 的 OCID&gt;</code> 這類佔位值換成自己的。這次把重建腳本要用的值存在一個 <code>ids.env</code> 裡，腳本開頭 <code>source</code> 一次；每個鍵與查法如下（值是建立資源時從回傳記下的，查法的子命令只確認過 <code>--help</code> 存在，沒有逐一實跑）：</p>
<table>
  <thead>
      <tr>
          <th>鍵</th>
          <th>內容</th>
          <th>查法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>C</code></td>
          <td>compartment 的 OCID</td>
          <td><code>oci iam compartment list</code></td>
      </tr>
      <tr>
          <td><code>V</code></td>
          <td>VCN 的 OCID</td>
          <td><code>oci network vcn list -c &lt;compartment 的 OCID&gt;</code></td>
      </tr>
      <tr>
          <td><code>SG</code></td>
          <td>service gateway 的 OCID</td>
          <td><code>oci network service-gateway list -c &lt;compartment 的 OCID&gt;</code></td>
      </tr>
      <tr>
          <td><code>SGCIDR</code></td>
          <td>service gateway 路由規則的目的地名稱</td>
          <td><code>oci network service list</code>，名稱帶「All」那一筆的 <code>cidr-block</code></td>
      </tr>
      <tr>
          <td><code>RT</code></td>
          <td>私有子網路的路由表的 OCID</td>
          <td><code>oci network route-table list -c &lt;compartment 的 OCID&gt; --vcn-id &lt;VCN&gt;</code></td>
      </tr>
      <tr>
          <td><code>SN</code></td>
          <td>私有子網路的 OCID</td>
          <td><code>oci network subnet list -c &lt;compartment 的 OCID&gt; --vcn-id &lt;VCN&gt;</code></td>
      </tr>
      <tr>
          <td><code>B</code></td>
          <td>Bastion 的 OCID</td>
          <td><code>oci bastion bastion list -c &lt;compartment 的 OCID&gt;</code></td>
      </tr>
  </tbody>
</table>
<h2 id="建-ubuntu-2404-主機與經由-port-forwarding-登入">建 Ubuntu 24.04 主機與經由 port forwarding 登入</h2>
<p>建立指令跟建主機那篇〈等效的 CLI 指令〉相同，換成 Ubuntu 24.04 的映像檔，建立時放登入用的公鑰：</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"># 查 Ubuntu 24.04、相容 A1 的映像檔，取建立時間最新的那一個（實跑）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">oci compute image list -c &lt;compartment 的 OCID&gt; --operating-system <span class="s2">&#34;Canonical Ubuntu&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="se"></span>  --operating-system-version <span class="s2">&#34;24.04&#34;</span> --shape VM.Standard.A1.Flex <span class="se">\
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="se"></span>  --sort-by TIMECREATED --sort-order DESC --query <span class="s1">&#39;data[0].id&#39;</span> --raw-output
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 建主機（實跑；這次第一次送出就成功，容量不足時用建主機那篇的重送腳本）</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">oci compute instance launch -c &lt;compartment 的 OCID&gt; --availability-domain &lt;可用網域&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="se"></span>  --display-name verify-ubuntu --shape VM.Standard.A1.Flex --shape-config <span class="s1">&#39;{&#34;ocpus&#34;:1,&#34;memoryInGBs&#34;:6}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="se"></span>  --image-id &lt;映像檔的 OCID&gt; --subnet-id &lt;私有子網路的 OCID&gt; --assign-public-ip <span class="nb">false</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="se"></span>  --ssh-authorized-keys-file &lt;登入用公鑰的路徑&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="se"></span>  --agent-config <span class="s1">&#39;{&#34;pluginsConfig&#34;:[{&#34;name&#34;:&#34;Bastion&#34;,&#34;desiredState&#34;:&#34;ENABLED&#34;}]}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="se"></span>  --is-pv-encryption-in-transit-enabled <span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="se"></span>  --instance-options <span class="s1">&#39;{&#34;areLegacyImdsEndpointsDisabled&#34;:true}&#39;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 查主機的狀態（重建腳本裡同一條查詢實跑過）：印 PROVISIONING 時隔一陣子再查，印 RUNNING 才往下做</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">oci compute instance get --instance-id &lt;launch 印出的主機 OCID&gt; --query <span class="s1">&#39;data.&#34;lifecycle-state&#34;&#39;</span> --raw-output</span></span></code></pre></div><p>這次的映像檔是 <code>Canonical-Ubuntu-24.04-aarch64-2026.09.18-0</code>。建立請求在送出之後約 4 秒回傳主機的 OCID，下一次查狀態時（約 20 秒後）已經是 <code>RUNNING</code>。建立指令裡各參數這次的取值與換掉之後的結果：</p>
<ul>
<li><code>--availability-domain</code>：可用網域的完整名稱帶有 tenancy 專屬的前綴，用 <code>oci iam availability-domain list</code> 查；<code>ap-batam-1</code> 只有一個可用網域，沒有別的值可選。</li>
<li><code>--shape-config</code>：1 OCPU、6 GB。改大會多用 A1 的運算額度，合計超出免費量之後的結果依帳號狀態而定（〈費用與額度：A1、開機磁碟與區塊儲存額度、出站流量與 NAT gateway〉）；這次沒有比較其他規格。</li>
<li><code>--assign-public-ip false</code>：主機只有私有 IP，所以登入一定要經過 Bastion；換成公開 IP 的做法在下面的〈建主機與登入的其他做法：映像檔、公開 IP、VPN 與 managed SSH session〉。</li>
<li><code>--agent-config</code> 打開 Bastion plugin：plugin 是 managed SSH session 的前提，而這台只用 port forwarding session 登入，port forwarding 不經過 plugin，所以關掉它時登入照樣走得通（從兩種 session 的機制推論，這次沒有關掉 plugin 實跑）；打開它留著改用 managed SSH session 的可能，這台的組合為什麼用不了 managed SSH session 見下面的其他做法。</li>
<li><code>--is-pv-encryption-in-transit-enabled true</code>：開機磁碟在主機與儲存之間的傳輸加密。用這個值，沒有比較其他值；CLI 不給時預設是 false，跟網頁主控台的預設不同（建主機那篇〈等效的 CLI 指令〉）。</li>
<li><code>--instance-options</code> 的 <code>areLegacyImdsEndpointsDisabled</code>：主機只能用第二版的中繼資料服務（IMDSv2；中繼資料服務是什麼見 <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>，Oracle Cloud 的這個選項與第二版的差別見 <a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a>〈中繼資料服務與 Authorization header〉）。用這個值，沒有比較其他值；這台主機的 cloud-init 在這個設定下照樣讀到了公鑰（下面登入那一段的 <code>status: done</code>）。</li>
</ul>
<p>主機的私有 IP 由子網路配發，建立之後才知道。私有 IP 掛在主機的 <a href="/blog/devops/infra/knowledge-cards/vnic/" data-link-title="VNIC（虛擬網路卡）" data-link-desc="雲端主機的私有 IP、公開 IP 或網卡層的防火牆規則要查、要改，或主機重建之後私有 IP 與防火牆規則跟著不見時，查 VNIC 掛著哪些設定、它跟主機一起建立與刪除的關係，以及各家的名稱">VNIC（虛擬網路卡）</a>（virtual network interface card：主機的虛擬網路卡，私有 IP 與後面〈加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG〉的 NSG 都掛在它上面）上，所以用列出 VNIC 的子命令查：</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">oci compute instance list-vnics --instance-id &lt;主機的 OCID&gt; --query <span class="s1">&#39;data[0].&#34;private-ip&#34;&#39;</span> --raw-output</span></span></code></pre></div><p>接著建一個指向這個 IP、22 port 的 port forwarding session（實跑；建立 session 的 work request 約 1 分鐘後是 <code>SUCCEEDED</code>）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">oci bastion session create-port-forwarding --bastion-id &lt;Bastion 的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --target-private-ip &lt;主機的私有 IP&gt; --target-port <span class="m">22</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --ssh-public-key-file &lt;session 用公鑰的路徑&gt; --session-ttl <span class="m">3600</span> --display-name verify-ubuntu-pf <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --wait-for-state SUCCEEDED --wait-for-state FAILED --max-wait-seconds <span class="m">600</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data.resources[0].identifier&#39;</span> --raw-output</span></span></code></pre></div><p>這個 Bastion 建立時把 session 的最長時效（<code>max-session-ttl</code>）設成 3600 秒，<code>--session-ttl</code> 填更大的值時以 <code>InvalidParameter</code> 失敗，所以 session 最長 1 小時，過了要重建。自己的 Bastion 設了多少，用 <code>oci bastion bastion get --bastion-id &lt;Bastion 的 OCID&gt; --query 'data.&quot;max-session-ttl-in-seconds&quot;'</code> 查（這一條沒有實跑）。上限可以用 <code>oci bastion bastion update --bastion-id &lt;Bastion 的 OCID&gt; --max-session-ttl &lt;秒數&gt;</code> 調高（<code>--help</code> 列得出這個選項，沒有實跑；可以調到多少這次沒有查證）：換到的是 session 少重建幾次，付出的是 session 用的私鑰外洩時，拿到的人能用比較久。登入用一份 ssh 設定檔，把 Bastion 當跳板（<code>ProxyJump</code>）：</p>





<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"># ssh-ubuntu.conf：&lt;session 的 OCID&gt; 是上一條印出的值，&lt;…路徑&gt; 換成自己的
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">Host verify-bastion-u
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  HostName host.bastion.&lt;區域&gt;.oci.oraclecloud.com
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  User &lt;session 的 OCID&gt;
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  IdentityFile &lt;session 用私鑰的路徑&gt;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  IdentitiesOnly yes
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Host verify-ubuntu
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  HostName &lt;主機的私有 IP&gt;
</span></span><span class="line"><span class="ln">10</span><span class="cl">  User ubuntu
</span></span><span class="line"><span class="ln">11</span><span class="cl">  IdentityFile &lt;登入用私鑰的路徑&gt;
</span></span><span class="line"><span class="ln">12</span><span class="cl">  IdentitiesOnly yes
</span></span><span class="line"><span class="ln">13</span><span class="cl">  ProxyJump verify-bastion-u
</span></span><span class="line"><span class="ln">14</span><span class="cl">  UserKnownHostsFile &lt;這台專用的 known_hosts 路徑&gt;
</span></span><span class="line"><span class="ln">15</span><span class="cl">  StrictHostKeyChecking accept-new</span></span></code></pre></div>




<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">ssh -F ssh-ubuntu.conf verify-ubuntu <span class="s1">&#39;cloud-init status --wait; cloud-init status; uptime&#39;</span></span></span></code></pre></div><p>這次第一次連線印出 <code>status: done</code> 與 <code>up 1 min</code>：主機開機約 1 分鐘之後，cloud-init 的首次開機設定已經跑完，預設帳號 <code>ubuntu</code> 也有了公鑰。Ubuntu 映像檔的預設帳號是 <code>ubuntu</code>，Oracle Linux 是 <code>opc</code>，<code>User</code> 那一行要跟著映像檔改。<code>UserKnownHostsFile</code> 指向這台專用的檔案，主機重建之後 host key 會換，清掉這個檔就好，不必動 <code>~/.ssh/known_hosts</code>。</p>
<p><code>StrictHostKeyChecking accept-new</code> 讓 ssh 第一次連線時自動記下主機的 host key，之後 key 變了就拒絕連線。換到的是腳本裡的 ssh 不會停在「要不要信任這把 key」的提問；代價是第一次連線沒有比對指紋，那一刻連到的若不是這台主機，它的 key 也會被記下。要比對指紋時，照建主機那篇〈結果：停在 CREATING 約 15 分鐘後轉成 DELETED〉的做法用 console history 讀開機輸出，cloud-init 產生 host key 時把指紋印在裡面。預設值 <code>ask</code> 每台新主機第一次連線都要人回答，無人值守的腳本會停住（依 <code>ssh_config</code> 的說明，沒有實跑）。</p>
<p>設定檔的 <code>ProxyJump</code> 寫法以外，也可以在本機開一個轉送的 port 再連 <code>localhost</code>，兩種寫法的指令都在建主機那篇〈建 port forwarding session 並經由本機的轉送登入〉。</p>
<h3 id="建主機與登入的其他做法映像檔公開-ipvpn-與-managed-ssh-session">建主機與登入的其他做法：映像檔、公開 IP、VPN 與 managed SSH session</h3>
<ul>
<li><strong>改用 Oracle Linux 映像檔</strong>：Oracle Linux 的套件庫在 Oracle 的服務網路裡，只接 service gateway 的私有子網路就更新得到（從映像檔的套件庫設定與這個子網路的路由推論，這次沒有在這個子網路的 Oracle Linux 主機上跑 dnf 對照），〈加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG〉那些設定就不需要。代價是 Oracle Linux 10 的代理程式沒有 Bastion plugin，managed SSH session 用不了，只能用 port forwarding session（建主機那篇〈重建：Oracle Linux 映像加建立時放公鑰，經由 port forwarding session 登入〉）。</li>
<li><strong>給主機公開 IP</strong>：<code>--assign-public-ip true</code> 並把主機放進有 internet gateway 的公開子網路，主機自己就連得到套件來源，也可以直接 SSH，不需要 Bastion 與 NAT。代價是 22 port 對外暴露，要靠 security list 或 NSG 收窄來源（概念見 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a>）；這次沒有實跑這種做法。只開給開發機的公開 IP（/32）也能把來源收窄到一個位址，Bastion 比它多給了三件事：誰能開 session 由 IAM 政策決定，不只看來源 IP；主機在網際網路上沒有位址可以連；session 到時效就自己失效。公開 IP 這條路另外有一個重建時的代價：主機被刪除重建之後，臨時配發的公開 IP 會換一個，ssh 設定與允許清單都要跟著改（依公開 IP 的配發方式推論，這次沒有實跑）。三層各自的做法與取捨見 <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>〈網路可達性的做法：管理 port 對誰開放〉。</li>
<li><strong>改用 VPN 或 Tailscale 這類 overlay 網路登入</strong>：主機上裝 VPN 的用戶端，從開發機經由 VPN 連進來，不需要 Bastion session 的 1 小時時效。這台主機的 NSG 只開出站 TCP 80 與 443，Tailscale 這類需要 UDP 直連的工具，推論會退到經由 443 的中繼伺服器轉送，延遲比直連高；這一點沒有查證，也沒有實跑。網路可達性那一層的比較在同一篇〈網路可達性的做法：管理 port 對誰開放〉。</li>
<li><strong>managed SSH session 代替 port forwarding</strong>：Ubuntu 24.04 的代理程式有 Bastion plugin，但官方〈Known Issues for Bastion〉寫明，Arm 架構的 A1 機型上、作業系統不是 Oracle Linux 的主機不支援 managed SSH session（理由是這類主機上的 Bastion plugin 沒有正確啟用）。建主機那篇〈建立之後：Bastion managed SSH session 逾時的排查〉在 A1 加 Ubuntu 上實測三次都逾時，所以這次只有 port forwarding 行得通。</li>
</ul>
<h2 id="只接-service-gateway-的私有子網路apt-連到套件來源時的錯誤原文與成因">只接 service gateway 的私有子網路：apt 連到套件來源時的錯誤原文與成因</h2>
<p>主機建好之後，apt 在這個子網路裡連不到套件來源。這次 <code>apt-get update</code> 失敗時的輸出沒有留下，下面附的是同一台主機上 <code>apt-get install</code> 下載套件時的錯誤原文（實跑；IPv6 位址那幾行省略）：</p>





<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">E: Failed to fetch http://ap-batam-1-ad-1.clouds.ports.ubuntu.com/ubuntu-ports/pool/main/... Cannot initiate the connection to ap-batam-1-ad-1.clouds.ports.ubuntu.com:80 (2620:2d:4002:1::10a). - connect (101: Network is unreachable) ... Could not connect to ap-batam-1-ad-1.clouds.ports.ubuntu.com:80 (91.189.91.103), connection timed out
</span></span><span class="line"><span class="ln">2</span><span class="cl">E: Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?</span></span></code></pre></div><p>錯誤原文裡的兩種錯誤分別出自 IPv6 與 IPv4 兩條連線：</p>
<ul>
<li><strong><code>Network is unreachable</code>（IPv6）</strong>：主機的核心沒有任何通往網際網路的 IPv6 路由，<code>connect()</code> 立刻回錯誤碼 101（<code>ENETUNREACH</code>），封包沒有離開主機。序列主控台記下的開機輸出裡，cloud-init 印出的 IPv6 路由表只有 <code>fe80::/64</code>、<code>local</code> 與 <code>multicast</code> 三條，都只到這張網卡本身。</li>
<li><strong><code>connection timed out</code>（IPv4）</strong>：封包送得出主機，回應沒有回來。這個子網路的路由表沒有任何一條規則把網際網路的位址送到出口，security list 的出站規則也沒有放行網際網路；兩項都沒有放行，是哪一項先擋下封包，這次沒有分開實測（推論）。</li>
</ul>
<p>錯誤原文裡的主機名 <code>ap-batam-1-ad-1.clouds.ports.ubuntu.com</code> 是 Canonical 替雲端區域設的鏡像站；加上出站之後成功的輸出裡，<code>-security</code> 那幾行的來源是 <code>ports.ubuntu.com</code>。所以這個映像檔把一般的套件來源設成區域鏡像、把安全更新設成 <code>ports.ubuntu.com</code>（從兩份輸出與 Ubuntu 雲端映像檔的預設來源設定推論，這次沒有讀主機上的來源設定檔）。兩個主機都在 Oracle 的服務網路之外，而 service gateway 只轉送目的地是 Oracle 服務網路的封包，所以這台主機兩個都連不到。</p>
<p>這個失敗本身沒有訊號：<code>apt-get update</code> 的錯誤在背景排程裡只寫進 log，自動更新的 timer 照常觸發，什麼都裝不到。把 <code>apt-get update</code> 的輸出丟掉、只看 <code>apt list --upgradable</code> 時，連不到套件來源的主機會列出零個待裝的套件，看起來跟已經是最新的主機一樣；這次第一次探測就是這樣，加上出站之後同一台主機有 60 個安全更新待裝。所以私有子網路的主機上，讀自動更新的結果要讀它的 log（<code>/var/log/unattended-upgrades/</code>），不能只看有沒有待裝的套件。</p>
<h2 id="加出站nat-gateway路由規則與只掛在這台主機上的-nsg">加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG</h2>
<p>要讓這台主機連得到網際網路上的套件來源而不給它公開 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 Gateway</a>：主機送出的封包由 NAT gateway 換成它自己的公開 IP，回應的封包再換回來，網際網路上的主機連不進來。拿掉公開 IP 之後的出站需求怎麼處理（NAT，以及存取同一家業者的服務時不經過 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 之後的出站路徑〉；這一節是其中 NAT 那一條在 Oracle Cloud 上的實作。加出站要動三項設定：NAT gateway、路由表與 NSG。路由規則要填 NAT gateway 的 OCID，所以 NAT gateway 先建、路由表後改；NSG 跟前兩項沒有依賴，放在哪個位置做都可以。</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"># 1. 建 NAT gateway（實跑）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">oci network nat-gateway create -c &lt;compartment 的 OCID&gt; --vcn-id &lt;VCN 的 OCID&gt; --display-name verify-nat <span class="se">\
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="se"></span>  --wait-for-state AVAILABLE --query data.id --raw-output
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 2. 路由表加 0.0.0.0/0 → NAT（實跑）；update 是整份替換，原本那條 service gateway 的規則要一起寫</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">oci network route-table update --rt-id &lt;私有子網路路由表的 OCID&gt; --force --wait-for-state AVAILABLE --route-rules <span class="s1">&#39;[
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s1"> {&#34;destination&#34;:&#34;&lt;Oracle 服務網路的目的地名稱&gt;&#34;,&#34;destinationType&#34;:&#34;SERVICE_CIDR_BLOCK&#34;,&#34;networkEntityId&#34;:&#34;&lt;service gateway 的 OCID&gt;&#34;},
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s1"> {&#34;destination&#34;:&#34;0.0.0.0/0&#34;,&#34;destinationType&#34;:&#34;CIDR_BLOCK&#34;,&#34;networkEntityId&#34;:&#34;&lt;NAT gateway 的 OCID&gt;&#34;}]&#39;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 確認路由（沒有實跑）：該看到兩條，0.0.0.0/0 指向 NAT gateway 的 OCID，service gateway 那一條也還在</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">oci network route-table get --rt-id &lt;私有子網路路由表的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data.&#34;route-rules&#34;[].{destination:destination,target:&#34;network-entity-id&#34;}&#39;</span> --output table
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 3. 建只有出站 TCP 80、443 的 NSG，掛到這台主機的 VNIC（實跑）</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">oci network nsg create -c &lt;compartment 的 OCID&gt; --vcn-id &lt;VCN 的 OCID&gt; --display-name verify-ubuntu-egress <span class="se">\
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="se"></span>  --wait-for-state AVAILABLE --query data.id --raw-output
</span></span><span class="line"><span class="ln">14</span><span class="cl">oci network nsg rules add --nsg-id &lt;NSG 的 OCID&gt; --security-rules <span class="s1">&#39;[
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s1"> {&#34;direction&#34;:&#34;EGRESS&#34;,&#34;protocol&#34;:&#34;6&#34;,&#34;destination&#34;:&#34;0.0.0.0/0&#34;,&#34;destinationType&#34;:&#34;CIDR_BLOCK&#34;,&#34;isStateless&#34;:false,&#34;tcpOptions&#34;:{&#34;destinationPortRange&#34;:{&#34;min&#34;:80,&#34;max&#34;:80}}},
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s1"> {&#34;direction&#34;:&#34;EGRESS&#34;,&#34;protocol&#34;:&#34;6&#34;,&#34;destination&#34;:&#34;0.0.0.0/0&#34;,&#34;destinationType&#34;:&#34;CIDR_BLOCK&#34;,&#34;isStateless&#34;:false,&#34;tcpOptions&#34;:{&#34;destinationPortRange&#34;:{&#34;min&#34;:443,&#34;max&#34;:443}}}]&#39;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 確認規則（沒有實跑）：該看到兩列 EGRESS，port 分別是 80 與 443</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">oci network nsg rules list --nsg-id &lt;NSG 的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data[].{direction:direction,port:&#34;tcp-options&#34;.&#34;destination-port-range&#34;.min}&#39;</span> --output table
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># 查這台主機的 VNIC 的 OCID（實跑），再把 NSG 掛上去</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">oci compute instance list-vnics --instance-id &lt;主機的 OCID&gt; --query <span class="s1">&#39;data[0].id&#39;</span> --raw-output
</span></span><span class="line"><span class="ln">22</span><span class="cl">oci network vnic update --vnic-id &lt;主機的 VNIC 的 OCID&gt; --nsg-ids <span class="s1">&#39;[&#34;&lt;NSG 的 OCID&gt;&#34;]&#39;</span> --force
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"># 確認掛上（沒有實跑）：印出的 OCID 清單裡有 &lt;NSG 的 OCID&gt;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">oci network vnic get --vnic-id &lt;主機的 VNIC 的 OCID&gt; --query <span class="s1">&#39;data.&#34;nsg-ids&#34;&#39;</span></span></span></code></pre></div><ul>
<li><strong>路由表</strong>：決定封包往哪個出口送。規則依目的地比對，Oracle 服務網路的位址仍走 service gateway，其餘的（<code>0.0.0.0/0</code>）走 NAT gateway。<code>&lt;Oracle 服務網路的目的地名稱&gt;</code> 是原本那條規則的目的地，查法在〈前置條件：沿用 Oracle Cloud 建立主機實測紀錄的網路、Bastion 與金鑰〉的私有子網路那一項。<code>route-table update --route-rules</code> 會用給的清單取代整份規則，<code>--force</code> 略過取代之前的確認提示。只寫 NAT 那一條的話，service gateway 那一條就被刪掉，主機連 Oracle 服務網路的流量（Oracle Cloud Agent 的回報這類）改走 NAT gateway 到公開端點（從路由的比對方式推論，沒有實跑只寫一條的結果）。</li>
<li><strong>NSG</strong>：決定這張網卡（VNIC）放行哪些封包，概念見 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a>。這個子網路的 security list 只放行 Oracle 服務網路與子網路內部的出站，有路由也送不出去，所以要再放行一條。這一條放在 NSG：security list 套在整個子網路上，同一個子網路裡的其他主機（這次有一台 Oracle Linux 主機）會一起多出網際網路的出站。NSG 只套在掛上它的 VNIC，影響範圍只有這一台。Oracle Cloud 對一張網卡取 security list 與 NSG 兩邊規則的聯集，任何一邊放行就放行（<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>〈防火牆規則的位置與合併方式〉），所以「只開 80 與 443」成立的前提是這個子網路的 security list 一直不放行網際網路的出站；有人之後在 security list 加了全開的出站規則，這台主機的出站也跟著全開。規則裡的 <code>&quot;protocol&quot;:&quot;6&quot;</code> 是 TCP 在 IANA 協定號碼表上的號碼；規則是有狀態的（<code>isStateless: false</code>），回應的封包自動放行，不必另寫入站規則。</li>
<li><strong>只開 80 與 443</strong>：<code>apt-get update</code> 的輸出顯示 Ubuntu 的套件來源是 <code>http://</code>（80），套件的完整性由 apt 另外驗簽章；443 留給用 HTTPS 的來源（snap 與第三方套件庫這類）。只開這兩個 port 時 <code>apt-get update</code> 成功，名稱解析沒有另外放行；名稱解析走 VCN 內建的 DNS，在 VCN 裡面，不經過這兩條出站規則（從 VCN 的 DNS 設計推論，這次沒有讀主機上的解析設定）。</li>
</ul>
<p><code>vnic update --nsg-ids</code> 同樣是整份取代，<code>--force</code> 同樣略過確認提示：VNIC 原本掛著別的 NSG 時，要把原本的那幾個一起寫進清單。三樣都做完之後，同一條指令成功（實跑）：</p>





<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">Get:55 http://ports.ubuntu.com/ubuntu-ports noble-security/multiverse arm64 c-n-f Metadata [456 B]
</span></span><span class="line"><span class="ln">2</span><span class="cl">Fetched 39.9 MB in 9s (4371 kB/s)</span></span></code></pre></div><p>這一次下載 39.9 MB 花了 9 秒，速率 4371 kB/s。<code>apt list --upgradable</code> 列出 79 行，其中 60 個來自 <code>-security</code>，包括新的核心。這個映像檔建立之後公開的漏洞，在主機第一次開機時都還沒修補，所以新建的主機先手動跑一次更新並重開機：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">sudo unattended-upgrade   <span class="c1"># 裝 -security 的更新（實跑，402 秒）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">sudo systemctl reboot     <span class="c1"># 換上新核心（實跑）</span></span></span></code></pre></div><p><code>unattended-upgrade</code> 跑完升級了 64 個套件，比 <code>-security</code> 的 60 個多 4 個。這 4 個的來源這次沒有查；一個可能是新核心以新的套件名稱安裝，新安裝的套件不會列在 <code>apt list --upgradable</code> 裡（推論）。待裝的數量依映像檔建立的日期到建主機那一天之間公開了多少更新而變，換一個映像檔或換一天建，數字就不同。核心更新前後的重開機判斷在維護篇〈會老化的軟體：各條更新管道與更新後的重開機〉。</p>
<h3 id="出站的其他做法security-list建主機時就掛-nsg限縮出站的目的地與不加出站">出站的其他做法：security list、建主機時就掛 NSG、限縮出站的目的地與不加出站</h3>
<ul>
<li><strong>把出站規則加在 security list</strong>：效果是整個子網路的主機都能出站。同一個子網路只有這一台主機、或每一台本來就要更新時，這樣設定比較少；代價是之後放進這個子網路的主機也自動有網際網路出站（從 security list 套用的範圍推論，沒有實跑）。</li>
<li><strong>建主機時就掛上 NSG</strong>：<code>oci compute instance launch</code> 有 <code>--nsg-ids</code>（<code>--help</code> 確認過，沒有實跑），建立時就把出站 NSG 掛在主機的 VNIC 上。換到的是主機首次開機時就有出站，重建腳本裡掛 NSG 那一步（第 6 步）也不必另外做；付出的是 NAT gateway、路由與 NSG 都要在建主機之前做好，順序從「先建主機、再加出站」變成「先加出站、再建主機」。</li>
<li><strong>限縮出站的目的地</strong>：三種做法都把出站從「任何位址的 80 與 443」收窄。NSG 的規則只收 CIDR，可以只放行 Canonical 鏡像站的網段，代價是鏡像站的位址會變，位址換了之後更新又會逾時（推論，沒有查證）；在有出站的子網路自架一台 forward proxy，依網域篩選，私有子網路的主機只連它；或在 VCN 裡自建一台 apt 的快取代理（例如 apt-cacher-ng），私有子網路的主機只連那一台，另外還能控制更新的內容。後兩種適合主機多的環境，代價是多一台要維護的主機（推論，沒有實跑）。</li>
<li><strong>不加出站</strong>：主機永遠裝不到套件更新，自動更新的 timer 照常觸發、什麼都裝不到；只適合用完即刪、不需要更新的實驗。</li>
</ul>
<h2 id="序列主控台rsa-金鑰建立連線proxyjump-設定與看到的開機輸出">序列主控台：RSA 金鑰、建立連線、ProxyJump 設定與看到的開機輸出</h2>
<p>序列主控台（<a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）</a>）讀的是主機的虛擬序列埠，不經過主機的網路與 sshd，sshd 設定改壞、防火牆擋住、新核心開不了機時，仍看得到開機輸出。Oracle Cloud 的做法是替主機建一個序列主控台連線（instance console connection），它只收 RSA 公鑰：</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"># 產生 RSA 金鑰；序列主控台連線不收 ed25519（依 Oracle Cloud 文件〈Instance Console Connections〉，沒有拿 ed25519 實試）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh-keygen -q -t rsa -b <span class="m">3072</span> -N <span class="s1">&#39;&#39;</span> -C verify-console -f &lt;序列主控台私鑰的路徑&gt;
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 建序列主控台連線（實跑，回傳時已是 ACTIVE）</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">oci compute instance-console-connection create --instance-id &lt;主機的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --ssh-public-key-file &lt;序列主控台私鑰的路徑&gt;.pub --wait-for-state ACTIVE
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 取得官方的連線字串</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">oci compute instance-console-connection list -c &lt;compartment 的 OCID&gt; --instance-id &lt;主機的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data[0].&#34;connection-string&#34;&#39;</span> --raw-output</span></span></code></pre></div><p>這次的序列主控台連線用完就刪，平時不留。序列主控台是救援入口，繞過平時登入主機的控制點，所以照 <a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）</a>〈設計責任〉的「平時保持關閉、救援時才開啟、用完關回去」：要排查時跑〈被回收之後的重建：怎麼發現、留下與消失的資源、重建腳本〉裡序列主控台連線的重建腳本建一條（這次實跑約 15 秒），排查完用下面這條刪掉（腳本結尾會印出代入了連線 OCID 的同一條）：</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"># 刪掉序列主控台連線（實跑）：--wait-for-state 等的是連線本身的狀態</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci compute instance-console-connection delete --instance-console-connection-id &lt;連線的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --force --wait-for-state DELETED</span></span></code></pre></div><p>2026-10-27 對 <code>verify-ubuntu</code> 那條連線實跑這一條：CLI 印出 <code>Action completed. Waiting until the resource has entered state: ('DELETED',)</code>，exit 0，10 秒；之後 <code>instance-console-connection list</code> 只列出這一條，狀態是 <code>DELETED</code>，主機本身仍是 <code>RUNNING</code>。</p>
<p><code>ssh-keygen</code> 的 <code>-N ''</code> 讓私鑰不設保護密碼（passphrase），重建腳本可以不經互動使用它；代價是拿到這個私鑰檔的人就連得上這台主機的序列主控台。這個代價跟上面「連線只在排查時存在」一起看：連線平時不存在，私鑰外洩時，拿到的人還要等下一次有人建連線、而且要在刪掉之前用它，能用的時間窗只剩排查那一段；連線一直留著的話，同一把沒有保護密碼的私鑰隨時都連得上。檔案仍然只給自己讀（<code>ssh-keygen</code> 產生的私鑰權限是 600）。設了保護密碼的話，每次連線要輸入它，或先把私鑰交給 <code>ssh-agent</code>，這次沒有比較這種做法。</p>
<p>官方的連線字串是一條 ssh 指令，外面再包一層 ssh 當跳板，形狀是 <code>ssh -o ProxyCommand='ssh -W %h:%p -p 443 &lt;連線的 OCID&gt;@instance-console.&lt;區域&gt;.oci.oraclecloud.com' &lt;主機的 OCID&gt;</code>。<code>ProxyCommand</code> 讓 ssh 在本機執行一條任意的指令，把它的輸入輸出當成連線；<code>ProxyJump</code> 由 ssh 自己經由跳板轉送連線，不在本機執行別的指令，效果相同。<code>ProxyJump</code> 這一欄只接受跳板的使用者、主機與 port，有人改寫這一欄時，改得到的只有跳板的位址；<code>ProxyCommand</code> 那一欄被改寫時，換上的是會在本機執行的指令。改寫成 <code>ProxyJump</code> 時：跳板主機是 <code>instance-console.&lt;區域&gt;.oci.oraclecloud.com</code> 的 443 port，使用者名稱是連線的 OCID；目的地的「主機名稱」是主機的 OCID，使用者名稱同樣是連線的 OCID。</p>





<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"># ssh-console.conf
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">Host verify-console-gw
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  HostName instance-console.&lt;區域&gt;.oci.oraclecloud.com
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  Port 443
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  User &lt;連線的 OCID&gt;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  IdentityFile &lt;序列主控台私鑰的路徑&gt;
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  IdentitiesOnly yes
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  UserKnownHostsFile &lt;序列主控台專用的 known_hosts 路徑&gt;
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  StrictHostKeyChecking accept-new
</span></span><span class="line"><span class="ln">10</span><span class="cl">  HostKeyAlgorithms +ssh-rsa
</span></span><span class="line"><span class="ln">11</span><span class="cl">  PubkeyAcceptedAlgorithms +ssh-rsa
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">Host verify-console
</span></span><span class="line"><span class="ln">14</span><span class="cl">  HostName &lt;主機的 OCID&gt;
</span></span><span class="line"><span class="ln">15</span><span class="cl">  User &lt;連線的 OCID&gt;
</span></span><span class="line"><span class="ln">16</span><span class="cl">  ProxyJump verify-console-gw
</span></span><span class="line"><span class="ln">17</span><span class="cl">  IdentityFile &lt;序列主控台私鑰的路徑&gt;
</span></span><span class="line"><span class="ln">18</span><span class="cl">  IdentitiesOnly yes
</span></span><span class="line"><span class="ln">19</span><span class="cl">  UserKnownHostsFile &lt;序列主控台專用的 known_hosts 路徑&gt;
</span></span><span class="line"><span class="ln">20</span><span class="cl">  StrictHostKeyChecking accept-new
</span></span><span class="line"><span class="ln">21</span><span class="cl">  HostKeyAlgorithms +ssh-rsa
</span></span><span class="line"><span class="ln">22</span><span class="cl">  PubkeyAcceptedAlgorithms +ssh-rsa</span></span></code></pre></div>




<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"># -tt 讓 ssh 配一個終端機，序列埠的輸出才會照原樣顯示；離開時先按 Enter，再輸入 ~.</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh -tt -F ssh-console.conf verify-console</span></span></code></pre></div><p><code>HostKeyAlgorithms +ssh-rsa</code> 與 <code>PubkeyAcceptedAlgorithms +ssh-rsa</code> 兩行是預防性的：連線用 RSA 金鑰，新版 OpenSSH 預設不接受 <code>ssh-rsa</code> 簽章，這次沒有比較拿掉這兩行的結果。</p>
<p>連上之後先印出一段 Oracle Cloud 的說明，接著是主機目前的序列埠輸出。這次連著序列主控台讓主機重開機（在另一個終端機經由 Bastion 執行 <code>sudo systemctl reboot</code>），依序看到關機時 systemd 停止各個 unit、<code>reboot: Restarting system</code>、韌體的 <code>Oracle AAVMF Version: 20260902</code> 與載入的 UEFI 模組、核心的 <code>Linux version 6.17.0-…-oracle</code>，核心開始開機之後約 27 秒在 <code>ttyAMA0</code> 印出 <code>Ubuntu 24.04.5 LTS verify-ubuntu ttyAMA0</code> 與登入提示。只看開機卡在哪一步（核心、檔案系統、cloud-init）時，不必登入就看得到。</p>
<p>登入作業系統要一個設了密碼的使用者。這台主機的 <code>ubuntu</code> 帳號，<code>passwd -S ubuntu</code> 印出的狀態是 <code>L</code>（密碼鎖定），這次也沒有替任何使用者設密碼，所以登入那一步沒有做。</p>
<h3 id="序列主控台的其他做法在序列主控台登入作業系統console-historycloud-shell-與-vnc">序列主控台的其他做法：在序列主控台登入作業系統、console history、Cloud Shell 與 VNC</h3>
<ul>
<li><strong>事先設一個救援帳號的密碼</strong>：序列主控台出現登入提示時就能登入，sshd 壞掉時從這裡修。代價是主機多了一個密碼登入的帳號，要同時確認 sshd 不接受密碼登入（<code>sshd -T</code> 印 <code>passwordauthentication no</code>），密碼只在序列主控台上有效。GCP 上照這個做法實際登入過，步驟在 <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 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP</a>〈準備救援入口：Serial Console 與本機救援帳號〉；三家業者的條件比較在維護篇〈會老化的軟體：各條更新管道與更新後的重開機〉。</li>
<li><strong>只讀開機輸出的快照（console history）</strong>：<code>oci compute console-history capture</code> 擷取一份開機輸出的唯讀快照，不必建連線、不必產生 RSA 金鑰，也不能輸入；只要知道開機停在哪一步、或讀 host key 的指紋時夠用。建主機那篇〈結果：停在 CREATING 約 15 分鐘後轉成 DELETED〉用過這兩條指令。</li>
<li><strong>從網頁主控台的 Cloud Shell 連</strong>：網頁主控台的主機頁面有「Launch Cloud Shell connection」，在瀏覽器裡開連線，不必在本機產生金鑰與設定檔（依 Oracle Cloud 文件〈Instance Console Connections〉，沒有實跑）。</li>
<li><strong>VNC 連線</strong>：同一個序列主控台連線另有 VNC 的連線字串，看的是圖形畫面，Linux 伺服器映像檔沒有圖形介面時跟序列埠看到的相同（依同一份文件，沒有實跑）。</li>
</ul>
<h2 id="被回收之後的重建怎麼發現留下與消失的資源重建腳本">被回收之後的重建：怎麼發現、留下與消失的資源、重建腳本</h2>
<p>Always Free 的主機在連續 7 天內 CPU 使用率（取第 95 百分位）、網路使用率、記憶體使用率（只算 A1）三項都低於 20% 時，Oracle 可能回收它，規則與官方沒寫的部分在 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位</a>（下文稱「帳號篇」）〈Always Free 的額度與它綁在 home region 上的範圍〉。這台主機平常沒有負載，三項都落在條件裡；官方沒有寫這條規則對付費帳戶適不適用，這個 tenancy 已經是付費帳戶，所以無法確定這台是否會被回收。官方也沒有寫回收之後主機是停止還是刪除、會不會通知，所以重建的做法要兩種情形都涵蓋。</p>
<p>這次沒有做回收發生之前的對策，只準備了回收之後的重建。事前的對策有三種，這次都沒有實作：</p>
<ul>
<li><strong>維持負載</strong>：讓三項使用率之一一直高於 20%，主機就不落在回收條件裡。官方條款怎麼看待專門為了避開回收而製造的負載，這次沒有查證。</li>
<li><strong>用 Events 或 Monitoring 告警</strong>：主機狀態改變時通知自己，回收發生的當下就知道。回收是否發出可以訂閱的事件，這次沒有查證。</li>
<li><strong>定期備份開機磁碟</strong>：回收之後可以從備份還原，裝過的更新與設定都在。暫停、停止、終止主機各自會留下哪些資源，比較見 <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>〈暫停時保留的範圍：只停服務、停止主機、終止主機、全部刪除〉；那一篇的〈恢復上線前要重新確認的漂移〉另外寫了主機不是照計畫暫停時（被閒置回收這類）要拿什麼當比對的基準。</li>
</ul>
<p><strong>發現主機不見了</strong>：經由 Bastion 登入失敗時（port forwarding session 建不出來、或 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">oci compute instance get --instance-id &lt;主機的 OCID&gt; --query <span class="s1">&#39;data.&#34;lifecycle-state&#34;&#39;</span> --raw-output
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci compute instance list -c &lt;compartment 的 OCID&gt; --display-name verify-ubuntu <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data[].{id:id,state:&#34;lifecycle-state&#34;}&#39;</span></span></span></code></pre></div><ul>
<li>印 <code>RUNNING</code>：主機還在，問題在 Bastion、session 過期或開發機的 IP 換了（Bastion 的 client CIDR）。</li>
<li>印 <code>STOPPED</code>：主機被停止，開機磁碟、VNIC 與私有 IP 都還在，啟動之後回到 <code>RUNNING</code>；啟動也可能失敗，例如 A1 容量不足時（推論，這次沒有遇到）。</li>
<li>印 <code>TERMINATED</code>、或 <code>get</code> 回 404、<code>list</code> 找不到：主機被刪除，要重建。開機磁碟是否一起刪掉，官方沒有寫，到 Block Storage → Boot Volumes 查。舊磁碟還在時有兩種處理：拿它重建主機（<code>oci compute instance launch</code> 以 <code>--source-boot-volume-id &lt;開機磁碟的 OCID&gt;</code> 代替 <code>--image-id</code>，沒有實跑），裝過的更新與設定都還在；或刪掉它，釋放它佔的區塊磁碟額度。主機終止時開機磁碟的預設去留見 <a href="/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留</a>〈刪除（終止）主機時開機磁碟的預設去留〉。</li>
</ul>
<p><strong>兩種情形各自留下與消失的資源</strong>。格內寫「實跑」的是主動操作時看到的，不是回收：停止那一欄的是 2026-10-27 主動停止 <code>verify-ubuntu</code> 再啟動時看到的，刪除那一欄的是主動終止 <code>verify-ubuntu-rebuild</code> 時看到的。格內沒寫「實跑」的，停止那一欄依停止主機的機制推論，刪除那一欄依終止主機的機制推論。回收實際做的是停止還是刪除，這次沒有遇到：</p>
<table>
  <thead>
      <tr>
          <th>資源</th>
          <th>主機被停止</th>
          <th>主機被刪除</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>compartment、VCN、私有子網路、service gateway、Bastion</td>
          <td>留下</td>
          <td>留下</td>
      </tr>
      <tr>
          <td>NAT gateway、路由規則、NSG 與它的規則</td>
          <td>留下（主動停止時實跑）</td>
          <td>留下</td>
      </tr>
      <tr>
          <td>主機、它的 VNIC 與私有 IP</td>
          <td>留下，啟動之後私有 IP 不變（主動停止時實跑）</td>
          <td>消失；重建的主機拿到新的私有 IP</td>
      </tr>
      <tr>
          <td>NSG 掛在 VNIC 上的關係</td>
          <td>留下（主動停止時實跑）</td>
          <td>跟著 VNIC 消失，要重新掛</td>
      </tr>
      <tr>
          <td>開機磁碟與上面裝過的更新</td>
          <td>留下（主動停止時實跑）</td>
          <td>指定刪除時一起刪掉（主動終止時實跑，不是回收）；回收時的去留官方沒寫</td>
      </tr>
      <tr>
          <td>序列主控台連線</td>
          <td>平時沒有；排查時建的那一條用完就刪</td>
          <td>變成 <code>DELETED</code>（主動終止時實跑，不是回收）；平時本來就沒有</td>
      </tr>
      <tr>
          <td>port forwarding session</td>
          <td>1 小時後本來就會失效</td>
          <td>同左</td>
      </tr>
      <tr>
          <td>本機的 ssh 設定與 known_hosts</td>
          <td>不用改；例外是 <code>host-&lt;名稱&gt;.env</code> 還沒有記下這台主機時（主機不是重建腳本建的，例如照〈建 Ubuntu 24.04 主機與經由 port forwarding 登入〉手動建的），重建腳本第一次跑會清空 known_hosts 一次，下一次登入自動重新記下（見下面重建腳本的實跑結果）</td>
          <td>IP 與 host key 都換了，要重寫</td>
      </tr>
  </tbody>
</table>
<p><strong>重建腳本</strong>：下面這支腳本把「停止就啟動、不見就重建」與之後要重新接上的每一項資源與設定寫在一起。每一步先查後建，重跑不會重複建立；例外是 port forwarding session，它最長 1 小時就失效，所以每次都新建一個。每一步至少印一行「已存在」「新建」「啟動」「重送」或「改寫」；任何一條 <code>oci</code> 指令失敗，腳本就停下來，印出那條指令寫在 stderr 的內容，不會把失敗當成「查無結果」往下走。它的每一個建立指令都是〈建 Ubuntu 24.04 主機與經由 port forwarding 登入〉與〈加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG〉兩節實跑過的那一條，先查後建與錯誤處理的部分是新加的。</p>
<p>這支腳本改過三個版本，雲端實跑與測試各自用的是其中一版：</p>
<ul>
<li><strong>沒有等待迴圈的版本</strong>：沒有 <code>wait_state</code>，也沒有等待 VNIC 的迴圈。</li>
<li><strong>加上等待迴圈的版本</strong>：加上 <code>wait_state</code> 與等待 VNIC 的迴圈。</li>
<li><strong>現在這一版</strong>：下面這一版。在加上等待迴圈的版本之外，加了依主機狀態分支、重送迴圈的改寫（只重送 500 與 429）、依主機是否為同一台決定 known_hosts 的去留、路由目的地改讀 <code>ids.env</code> 的 <code>SGCIDR</code>，以及每一條 <code>oci</code> 失敗就停下來的錯誤處理（含路由表裡有其他規則時停下來、NAT gateway 與 NSG 已存在時也記進 <code>ids.env</code>）。2026-10-27 上午另外加了指定開機磁碟大小的 <code>BOOT_GB</code>，只動到建立請求那一行（加上 <code>BOOT_ARGS</code> 的展開）、開頭變數區組 <code>BOOT_ARGS</code> 的一行，以及檔頭的用法註解（改寫一行、加兩行；單位的說明是實跑之後才補上的，執行的程式碼沒有變）；當天凌晨 <code>STOPPED</code>、<code>STOPPING</code>、<code>STARTING</code> 那幾次跑的是加 <code>BOOT_GB</code> 之前的版本，那幾條路徑不經過建立請求，下表一律算在現在這一版。</li>
</ul>
<p>雲端實跑是 2026-10-27 在同一個 tenancy 上跑的；假的 <code>oci</code> 是一支依劇本回傳主機狀態與建立請求回應、也能讓單一種呼叫失敗的指令，搭配一支不等待、只記下秒數的假 <code>sleep</code>。兩者的結果都在腳本之後。序列主控台連線的重建腳本也列在同一張表；它在雲端跑的是訊息還寫「主控台連線」、還沒有錯誤處理與換金鑰處理的前一版：</p>
<table>
  <thead>
      <tr>
          <th>路徑</th>
          <th>在雲端實跑過</th>
          <th>用假的 <code>oci</code> 測過</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>主機已經是 <code>RUNNING</code></td>
          <td>現在這一版（重建 NAT gateway 與 NSG 那一次）；較早還跑過沒有等待迴圈的版本</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td>找不到同名主機，新建</td>
          <td>現在這一版從送出建立請求跑到結束（<code>BOOT_GB=60</code> 與不給 <code>BOOT_GB</code> 各一次）</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td>主機 <code>STOPPED</code>、<code>STOPPING</code>、<code>STARTING</code></td>
          <td>現在這一版</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td>主機 <code>PROVISIONING</code></td>
          <td>現在這一版（另一份腳本正在建同名主機時）</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td>主機停在其他狀態（<code>TERMINATING</code>）、或狀態讀不出</td>
          <td>沒有</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td>建立請求回 500 或 429 後重送、回 400 或 401、讀不出回應碼、OCID 前有警告、查不到映像檔</td>
          <td>沒有（雲端那一次第一次送出就成功）</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td>NAT gateway 與 NSG 新建、路由表少了 0.0.0.0/0 那一條</td>
          <td>現在這一版</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td>VNIC 已經掛著其他 NSG</td>
          <td>沒有</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td>路由表裡有其他規則</td>
          <td>沒有</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td>主機跟上次記下的不是同一台</td>
          <td>現在這一版，<code>host-&lt;名稱&gt;.env</code> 還沒有記下主機的情形（<code>STOPPED</code> 那一次）</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td>查詢或寫入的 <code>oci</code> 失敗（查主機、啟動、查 NAT、讀與改路由表、讀與加 NSG 規則、讀與改 VNIC）</td>
          <td>沒有</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td><code>instance action</code>、<code>wait_state</code> 實際等待的時間</td>
          <td><code>instance action</code> 等到 <code>RUNNING</code>：現在這一版，<code>STOPPED</code>、<code>STOPPING</code> 兩次，從腳本開始到印出「啟動」各約 9 秒；<code>wait_state</code> 的 10 秒輪詢：新建主機時跑到（路徑跑到是實跑；輪詢的次數從時間戳推論），印出「新建」主機（送出建立請求之前）到印出「已 RUNNING」約 50 秒，同一段時間碰到 <code>PROVISIONING</code> 的另一份腳本也在輪詢；900 秒逾時：沒有跑到</td>
          <td>不適用（假的 <code>sleep</code> 不等待）</td>
      </tr>
      <tr>
          <td>序列主控台連線：沒有 <code>ACTIVE</code> 的連線就建</td>
          <td>前一版</td>
          <td>現在這一版</td>
      </tr>
      <tr>
          <td>序列主控台連線：已有連線就沿用、新產生金鑰時換掉舊連線、找不到主機、<code>oci</code> 失敗</td>
          <td>沒有</td>
          <td>現在這一版</td>
      </tr>
  </tbody>
</table>





<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="cp">#!/bin/bash
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="cp"></span><span class="c1"># 主機被回收、停止或刪除之後，把 verify-ubuntu 這一組恢復成可以登入、連得到套件來源的狀態：</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="c1">#   主機（不存在就建、停止就啟動）→ 私有 IP 與 VNIC → NAT gateway → 私有子網路路由的 0.0.0.0/0 規則</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="c1">#   → 出站 NSG 與它的兩條規則 → NSG 掛到這台的 VNIC → port forwarding session → ssh 設定與 known_hosts。</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="c1"># 每一步先查後建，重跑不會重複建立（例外是 port forwarding session，每次都新建一個）；</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="c1">#   每一步至少印一行「已存在」「新建」「啟動」「重送」或「改寫」。任何一條 oci 失敗就停下來，印出它的 stderr。</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="c1"># 用法：./rebuild-ubuntu.sh [主機名稱]（預設 verify-ubuntu）。新建主機時，環境變數 BOOT_GB 指定開機磁碟大小，</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="c1">#   單位跟 boot-volume list 的 size-in-gbs 相同（60 建出 60 GiB）；不給就用映像檔的預設大小</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="c1">#   （Ubuntu 24.04 的 size-in-gbs 是 47）。需要 ids.env 的 C（compartment）、V（VCN）、</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="c1">#   SG（service gateway）、SGCIDR（service gateway 路由規則的目的地名稱）、RT（私有子網路的路由表）、</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="c1">#   SN（私有子網路）、B（Bastion）。</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="c1"># 整個 VCN 共用的 NAT gateway 與 NSG 記在 ids.env；只屬於這一台的值（主機、session）記在 host-&lt;名稱&gt;.env，</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="c1">#   ssh 設定與 known_hosts 也依名稱各一份，所以給不同名稱重跑不會蓋掉另一台的紀錄。</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="c1"># 結束碼：0 完成；2 登入過期、查不到映像檔、主機停在無法處理的狀態或其他無法繼續的錯誤；</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="c1">#   3 建立主機的回應讀不出回應碼（請求可能已經成功），重跑之前先用 instance list 確認沒有同名主機；</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="c1">#   4 建立主機的回應碼不是 500、429、401（400 參數錯誤這類），照印出的訊息改好再跑。</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="nb">set</span> -uo pipefail
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="c1"># OCI CLI 是 Python 程式；PYTHONWARNINGS=ignore 關掉 Python 直譯器印的警告，不讓警告行混進要解析的輸出</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="nb">export</span> <span class="nv">OCI_CLI_AUTH</span><span class="o">=</span>security_token <span class="nv">PYTHONWARNINGS</span><span class="o">=</span>ignore
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="nv">D</span><span class="o">=</span><span class="k">$(</span><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="nv">$0</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="k">)</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="nb">source</span> <span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/ids.env&#34;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="nv">NAME</span><span class="o">=</span><span class="si">${</span><span class="nv">1</span><span class="k">:-</span><span class="nv">verify</span><span class="p">-ubuntu</span><span class="si">}</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="nv">AD</span><span class="o">=</span><span class="s2">&#34;&lt;可用網域&gt;&#34;</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="nv">KEY</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/verify-a1-key.pub&#34;</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="nv">NAT_NAME</span><span class="o">=</span>verify-nat
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="nv">NSG_NAME</span><span class="o">=</span>verify-ubuntu-egress
</span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="nv">HOSTENV</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/host-</span><span class="si">${</span><span class="nv">NAME</span><span class="si">}</span><span class="s2">.env&#34;</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="nv">CONF</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/ssh-</span><span class="si">${</span><span class="nv">NAME</span><span class="si">}</span><span class="s2">.conf&#34;</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="nv">KNOWN</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/known_hosts_</span><span class="si">${</span><span class="nv">NAME</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="nv">BOOT_ARGS</span><span class="o">=()</span><span class="p">;</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BOOT_GB</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nv">BOOT_ARGS</span><span class="o">=(</span>--boot-volume-size-in-gbs <span class="s2">&#34;</span><span class="nv">$BOOT_GB</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="nv">NEW</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="nv">ERR</span><span class="o">=</span><span class="k">$(</span>mktemp <span class="s2">&#34;</span><span class="si">${</span><span class="nv">TMPDIR</span><span class="k">:-</span><span class="p">/tmp</span><span class="si">}</span><span class="s2">/rebuild-ubuntu.XXXXXX&#34;</span><span class="k">)</span><span class="p">;</span> <span class="nb">trap</span> <span class="s1">&#39;rm -f &#34;$ERR&#34;&#39;</span> EXIT
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">ts<span class="o">()</span> <span class="o">{</span> date +%m-%d<span class="se">\ </span>%H:%M:%S<span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">say<span class="o">()</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="k">$(</span>ts<span class="k">)</span><span class="s2"> [</span><span class="nv">$1</span><span class="s2">] </span><span class="nv">$2</span><span class="s2">&#34;</span><span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">die<span class="o">()</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="k">$(</span>ts<span class="k">)</span><span class="s2"> [錯誤] </span><span class="nv">$1</span><span class="s2">&#34;</span> &gt;<span class="p">&amp;</span>2<span class="p">;</span> <span class="nb">exit</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">2</span><span class="k">:-</span><span class="nv">2</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="c1"># 查詢與寫入用的包裝：stderr 存進 $ERR 那個暫存檔，濾掉兩種不是結果的提示行（Python 的 SyntaxWarning，</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="c1">#   與 CLI 建議改用 `oci -i` 的 Interactive mode 那一行），結束碼照 oci 自己的回傳。</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="c1">#   CLI 的 --query 結果是空的時候 stdout 什麼都不印，所以空字串要搭配結束碼 0 才代表查無結果</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">q<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">  <span class="nb">local</span> out rc
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">  <span class="nv">out</span><span class="o">=</span><span class="k">$(</span>oci <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span> 2&gt;<span class="s2">&#34;</span><span class="nv">$ERR</span><span class="s2">&#34;</span><span class="k">)</span><span class="p">;</span> <span class="nv">rc</span><span class="o">=</span><span class="nv">$?</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">  <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> grep -v -e SyntaxWarning -e <span class="s1">&#39;Interactive mode&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">  <span class="k">return</span> <span class="nv">$rc</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="c1"># q 失敗時停下來，印出 oci 寫在 stderr 的內容</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">fail<span class="o">()</span> <span class="o">{</span> die <span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="si">}</span><span class="s2">失敗：</span><span class="k">$(</span>tr <span class="s1">&#39;\n&#39;</span> <span class="s1">&#39; &#39;</span> &lt; <span class="s2">&#34;</span><span class="nv">$ERR</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span><span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="c1"># 把「鍵=值」記進 ids.env；已經有同樣的一行就不重複寫</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">record<span class="o">()</span> <span class="o">{</span> grep -qx <span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">=</span><span class="nv">$2</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/ids.env&#34;</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">=</span><span class="nv">$2</span><span class="s2">&#34;</span> &gt;&gt; <span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/ids.env&#34;</span><span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="c1"># instance get 沒有 --wait-for-state，所以自己每 10 秒查一次狀態，最多等 900 秒</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">wait_state<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">  <span class="nb">local</span> <span class="nv">want</span><span class="o">=</span><span class="nv">$1</span> i cur
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">  <span class="k">for</span> <span class="o">((</span><span class="nv">i</span> <span class="o">=</span> 0<span class="p">;</span> i &lt; 90<span class="p">;</span> i++<span class="o">))</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="nv">cur</span><span class="o">=</span><span class="k">$(</span>q compute instance get --instance-id <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> --query <span class="s1">&#39;data.&#34;lifecycle-state&#34;&#39;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;查主機狀態&#34;</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$cur</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;</span><span class="nv">$want</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="k">return</span> <span class="m">0</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    sleep <span class="m">10</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">  <span class="k">done</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">  die <span class="s2">&#34;主機等了 900 秒仍是 </span><span class="si">${</span><span class="nv">cur</span><span class="k">:-</span><span class="nv">查不到</span><span class="si">}</span><span class="s2">，沒有變成 </span><span class="nv">$want</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="c1"># 0. 登入：session token 過期時後面每一條都會失敗，先擋下來</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">oci session validate --profile DEFAULT &gt;/dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="se"></span>  <span class="o">||</span> die <span class="s2">&#34;CLI session 過期，先跑 oci session authenticate --region &lt;區域&gt; --profile-name DEFAULT&#34;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="c1"># 1. 主機：依名稱找還沒終止的那一台，依狀態決定跳過、啟動或建立</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="nv">LAST_IID</span><span class="o">=</span><span class="k">$(</span>grep <span class="s1">&#39;^IID=&#39;</span> <span class="s2">&#34;</span><span class="nv">$HOSTENV</span><span class="s2">&#34;</span> 2&gt;/dev/null <span class="p">|</span> tail -1 <span class="p">|</span> cut -d<span class="o">=</span> -f2<span class="k">)</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="nv">IID</span><span class="o">=</span><span class="k">$(</span>q compute instance list -c <span class="s2">&#34;</span><span class="nv">$C</span><span class="s2">&#34;</span> --display-name <span class="s2">&#34;</span><span class="nv">$NAME</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="se"></span>  --query <span class="s2">&#34;data[?\&#34;lifecycle-state\&#34;!=&#39;TERMINATED&#39;] | [0].id&#34;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;查同名主機&#34;</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="nv">STATE</span><span class="o">=</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="c1"># 查詢成功而且結果是空的，才代表沒有同名主機；查得到主機而狀態讀不出來時，不當成「不存在」去建第二台</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">  <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.instance* <span class="o">]]</span> <span class="o">||</span> die <span class="s2">&#34;查同名主機回了不是主機 OCID 的值：</span><span class="nv">$IID</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">  <span class="nv">STATE</span><span class="o">=</span><span class="k">$(</span>q compute instance get --instance-id <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> --query <span class="s1">&#39;data.&#34;lifecycle-state&#34;&#39;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;查主機狀態&#34;</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">  <span class="o">[[</span> -z <span class="s2">&#34;</span><span class="nv">$STATE</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> die <span class="s2">&#34;查得到主機 </span><span class="si">${</span><span class="nv">IID</span><span class="si">}</span><span class="s2">，卻讀不出它的狀態&#34;</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="k">case</span> <span class="s2">&#34;</span><span class="nv">$STATE</span><span class="s2">&#34;</span> in
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">  RUNNING<span class="o">)</span> say 已存在 <span class="s2">&#34;主機 </span><span class="si">${</span><span class="nv">NAME</span><span class="si">}</span><span class="s2">（RUNNING）&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">  STOPPED<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    q compute instance action --instance-id <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> --action START --wait-for-state RUNNING --max-wait-seconds <span class="m">900</span> &gt;/dev/null <span class="se">\
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="se"></span>      <span class="o">||</span> fail <span class="s2">&#34;啟動主機（STOPPED → RUNNING）&#34;</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    say 啟動 <span class="s2">&#34;主機 </span><span class="si">${</span><span class="nv">NAME</span><span class="si">}</span><span class="s2">（STOPPED → RUNNING）&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">  STARTING<span class="p">|</span>PROVISIONING<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    wait_state RUNNING
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    say 已存在 <span class="s2">&#34;主機 </span><span class="si">${</span><span class="nv">NAME</span><span class="si">}</span><span class="s2">（等到 RUNNING）&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">  STOPPING<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    wait_state STOPPED
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    q compute instance action --instance-id <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> --action START --wait-for-state RUNNING --max-wait-seconds <span class="m">900</span> &gt;/dev/null <span class="se">\
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="se"></span>      <span class="o">||</span> fail <span class="s2">&#34;啟動主機（STOPPING → RUNNING）&#34;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    say 啟動 <span class="s2">&#34;主機 </span><span class="si">${</span><span class="nv">NAME</span><span class="si">}</span><span class="s2">（STOPPING → RUNNING）&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">  <span class="s2">&#34;&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="c1"># 找不到還沒終止的同名主機：只有這種情形才建立</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="nv">IMG</span><span class="o">=</span><span class="k">$(</span>q compute image list -c <span class="s2">&#34;</span><span class="nv">$C</span><span class="s2">&#34;</span> --operating-system <span class="s2">&#34;Canonical Ubuntu&#34;</span> --operating-system-version <span class="s2">&#34;24.04&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="se"></span>      --shape VM.Standard.A1.Flex --sort-by TIMECREATED --sort-order DESC --query <span class="s1">&#39;data[0].id&#39;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;查映像檔&#34;</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$IMG</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.image* <span class="o">]]</span> <span class="o">||</span> die <span class="s2">&#34;查不到 Ubuntu 24.04 的 A1 映像檔：</span><span class="si">${</span><span class="nv">IMG</span><span class="k">:-</span><span class="nv">查詢結果是空的</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="nv">IMGNAME</span><span class="o">=</span><span class="k">$(</span>q compute image get --image-id <span class="s2">&#34;</span><span class="nv">$IMG</span><span class="s2">&#34;</span> --query <span class="s1">&#39;data.&#34;display-name&#34;&#39;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;查映像檔名稱&#34;</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    say 新建 <span class="s2">&#34;主機 </span><span class="si">${</span><span class="nv">NAME</span><span class="si">}</span><span class="s2">，映像檔 </span><span class="nv">$IMGNAME</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="c1"># 容量不足時重送：間隔從 120 秒起，429 加倍（上限 600），其他回應每次縮 10 秒、最低 60 秒，±15 秒抖動</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="nv">back</span><span class="o">=</span><span class="m">120</span> <span class="nv">n</span><span class="o">=</span><span class="m">0</span> <span class="nv">last_refresh</span><span class="o">=</span><span class="m">0</span>                     <span class="c1"># 0：開跑時先延長一次 token</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">    <span class="k">while</span> :<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">      <span class="nv">n</span><span class="o">=</span><span class="k">$((</span>n+1<span class="k">))</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">      <span class="c1"># session token 一小時過期，每 30 分鐘（1800 秒）延長一次，重送跑得再久也不會在中途過期</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">      <span class="k">if</span> <span class="o">((</span> <span class="k">$(</span>date +%s<span class="k">)</span> - last_refresh &gt;<span class="o">=</span> <span class="m">1800</span> <span class="o">))</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        oci session refresh --profile DEFAULT &gt;/dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="o">||</span> die <span class="s2">&#34;token refresh 失敗，重跑 oci session authenticate&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="nv">last_refresh</span><span class="o">=</span><span class="k">$(</span>date +%s<span class="k">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">      <span class="k">fi</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">      <span class="nv">out</span><span class="o">=</span><span class="k">$(</span>oci --no-retry compute instance launch -c <span class="s2">&#34;</span><span class="nv">$C</span><span class="s2">&#34;</span> --availability-domain <span class="s2">&#34;</span><span class="nv">$AD</span><span class="s2">&#34;</span> --display-name <span class="s2">&#34;</span><span class="nv">$NAME</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="se"></span>        --shape VM.Standard.A1.Flex --shape-config <span class="s1">&#39;{&#34;ocpus&#34;:1,&#34;memoryInGBs&#34;:6}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">107</span><span class="cl"><span class="se"></span>        --image-id <span class="s2">&#34;</span><span class="nv">$IMG</span><span class="s2">&#34;</span> --subnet-id <span class="s2">&#34;</span><span class="nv">$SN</span><span class="s2">&#34;</span> --assign-public-ip <span class="nb">false</span> <span class="si">${</span><span class="nv">BOOT_ARGS</span><span class="p">[@]+</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">BOOT_ARGS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">108</span><span class="cl"><span class="se"></span>        --ssh-authorized-keys-file <span class="s2">&#34;</span><span class="nv">$KEY</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">109</span><span class="cl"><span class="se"></span>        --agent-config <span class="s1">&#39;{&#34;pluginsConfig&#34;:[{&#34;name&#34;:&#34;Bastion&#34;,&#34;desiredState&#34;:&#34;ENABLED&#34;}]}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="se"></span>        --is-pv-encryption-in-transit-enabled <span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="se"></span>        --instance-options <span class="s1">&#39;{&#34;areLegacyImdsEndpointsDisabled&#34;:true}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="se"></span>        --query data.id --raw-output 2&gt;<span class="p">&amp;</span>1<span class="k">)</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">      <span class="c1"># 輸出前面可能夾著 CLI 的警告，所以在整段輸出裡找 OCID，不只比對開頭</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">      <span class="nv">id</span><span class="o">=</span><span class="k">$(</span>grep -o <span class="s1">&#39;ocid1\.instance\.[^&#34; ]*&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span> <span class="p">|</span> head -1<span class="k">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">      <span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$id</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span> <span class="nv">IID</span><span class="o">=</span><span class="nv">$id</span><span class="p">;</span> break<span class="p">;</span> <span class="k">fi</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">      <span class="nv">status</span><span class="o">=</span><span class="k">$(</span>grep -o <span class="s1">&#39;&#34;status&#34;: [0-9]*&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span> <span class="p">|</span> head -1 <span class="p">|</span> grep -o <span class="s1">&#39;[0-9]*$&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">      <span class="c1"># 設定檔錯誤、網路中斷這類失敗沒有回應碼；請求也可能已經在 Oracle 那一端成功，所以不重送</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">      <span class="o">[[</span> -z <span class="s2">&#34;</span><span class="nv">$status</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> die <span class="s2">&#34;讀不出回應碼，先用 oci compute instance list -c &lt;compartment&gt; --display-name </span><span class="nv">$NAME</span><span class="s2"> 確認沒有同名主機再重跑：</span><span class="nv">$out</span><span class="s2">&#34;</span> <span class="m">3</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">      <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$status</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="m">401</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> die <span class="s2">&#34;401，重跑 oci session authenticate&#34;</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">      <span class="c1"># 只有 500（容量不足）與 429（限流）等一段時間可能變好；400 這類代表請求本身有誤，重送也是同一個結果</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">      <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$status</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="m">500</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34;</span><span class="nv">$status</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="m">429</span> <span class="o">]]</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">122</span><span class="cl"><span class="se"></span>        <span class="o">&amp;&amp;</span> die <span class="s2">&#34;建立請求回 </span><span class="si">${</span><span class="nv">status</span><span class="si">}</span><span class="s2">，不重送：</span><span class="k">$(</span>grep -o <span class="s1">&#39;&#34;message&#34;: &#34;[^&#34;]*&#34;&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span> <span class="p">|</span> head -1<span class="k">)</span><span class="s2">&#34;</span> <span class="m">4</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">      <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$status</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="m">429</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span> <span class="nv">back</span><span class="o">=</span><span class="k">$((</span> back <span class="o">*</span> <span class="m">2</span> &gt; <span class="m">600</span> ? <span class="m">600</span> : back <span class="o">*</span> <span class="m">2</span> <span class="k">))</span><span class="p">;</span> <span class="k">else</span> <span class="nv">back</span><span class="o">=</span><span class="k">$((</span> back <span class="o">-</span> <span class="m">10</span> &lt; <span class="m">60</span> ? <span class="m">60</span> : back <span class="o">-</span> <span class="m">10</span> <span class="k">))</span><span class="p">;</span> <span class="k">fi</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">      <span class="nv">wait</span><span class="o">=</span><span class="k">$((</span> back <span class="o">-</span> <span class="m">15</span> <span class="o">+</span> RANDOM <span class="o">%</span> <span class="m">31</span> <span class="k">))</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">      say 重送 <span class="s2">&#34;第 </span><span class="nv">$n</span><span class="s2"> 次 </span><span class="si">${</span><span class="nv">status</span><span class="si">}</span><span class="s2">：</span><span class="k">$(</span>grep -o <span class="s1">&#39;&#34;message&#34;: &#34;[^&#34;]*&#34;&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span> <span class="p">|</span> head -1<span class="k">)</span><span class="s2">，</span><span class="si">${</span><span class="nv">wait</span><span class="si">}</span><span class="s2"> 秒後再送&#34;</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">      sleep <span class="s2">&#34;</span><span class="nv">$wait</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="k">done</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;IID=</span><span class="nv">$IID</span><span class="s2">&#34;</span> &gt;&gt; <span class="s2">&#34;</span><span class="nv">$HOSTENV</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="c1"># 新主機的 host key 是新的，舊紀錄留著的話 ssh 會因為 host key 不符而拒絕連線</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">    : &gt; <span class="s2">&#34;</span><span class="nv">$KNOWN</span><span class="s2">&#34;</span><span class="p">;</span> <span class="nv">NEW</span><span class="o">=</span><span class="m">1</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">    wait_state RUNNING
</span></span><span class="line"><span class="ln">132</span><span class="cl">    say 新建 <span class="s2">&#34;主機 </span><span class="nv">$NAME</span><span class="s2"> 已 RUNNING（第 </span><span class="nv">$n</span><span class="s2"> 次送出成功）&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">  *<span class="o">)</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">    <span class="c1"># TERMINATING、MOVING、CREATING_IMAGE 這類狀態，或上面沒有列出的狀態：不猜下一步，停下來</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">    die <span class="s2">&#34;主機 </span><span class="si">${</span><span class="nv">NAME</span><span class="si">}</span><span class="s2"> 的狀態是 </span><span class="si">${</span><span class="nv">STATE</span><span class="si">}</span><span class="s2">，這支腳本不處理這個狀態；等它變成 RUNNING、STOPPED 或 TERMINATED 再重跑&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl"><span class="k">esac</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl"><span class="c1"># 查到的主機跟 host-&lt;名稱&gt;.env 最後記下的不是同一台時（例如在別處重建過），舊的 host key 紀錄對不上，也清掉</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$NEW</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="m">1</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;</span><span class="nv">$LAST_IID</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;IID=</span><span class="nv">$IID</span><span class="s2">&#34;</span> &gt;&gt; <span class="s2">&#34;</span><span class="nv">$HOSTENV</span><span class="s2">&#34;</span><span class="p">;</span> : &gt; <span class="s2">&#34;</span><span class="nv">$KNOWN</span><span class="s2">&#34;</span><span class="p">;</span> <span class="nv">NEW</span><span class="o">=</span><span class="m">1</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl"><span class="c1"># 2. 私有 IP 與 VNIC：重建之後兩者都會換</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="c1"># 剛 RUNNING 的主機，VNIC 的掛載紀錄可能還沒出現，最多等 120 秒</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="k">for</span> <span class="o">((</span><span class="nv">i</span> <span class="o">=</span> 0<span class="p">;</span> i &lt; 12<span class="p">;</span> i++<span class="o">))</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">  <span class="nv">VNIC</span><span class="o">=</span><span class="k">$(</span>q compute instance list-vnics --instance-id <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> --query <span class="s1">&#39;data[0].id&#39;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;查 VNIC&#34;</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">  <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$VNIC</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.vnic* <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">break</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">  sleep <span class="m">10</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl"><span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$VNIC</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.vnic* <span class="o">]]</span> <span class="o">||</span> die <span class="s2">&#34;等了 120 秒仍查不到主機的 VNIC：</span><span class="nv">$VNIC</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl"><span class="nv">IP</span><span class="o">=</span><span class="k">$(</span>q compute instance list-vnics --instance-id <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> --query <span class="s1">&#39;data[0].&#34;private-ip&#34;&#39;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;查私有 IP&#34;</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">say 已存在 <span class="s2">&#34;VNIC 與私有 IP </span><span class="nv">$IP</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">
</span></span><span class="line"><span class="ln">153</span><span class="cl"><span class="c1"># 3. NAT gateway：VCN 裡同名而 AVAILABLE 的那一個；已存在或新建都記進 ids.env</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl"><span class="nv">NAT</span><span class="o">=</span><span class="k">$(</span>q network nat-gateway list -c <span class="s2">&#34;</span><span class="nv">$C</span><span class="s2">&#34;</span> --vcn-id <span class="s2">&#34;</span><span class="nv">$V</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">155</span><span class="cl"><span class="se"></span>  --query <span class="s2">&#34;data[?\&#34;display-name\&#34;==&#39;</span><span class="nv">$NAT_NAME</span><span class="s2">&#39; &amp;&amp; \&#34;lifecycle-state\&#34;==&#39;AVAILABLE&#39;] | [0].id&#34;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;查 NAT gateway&#34;</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$NAT</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.natgateway* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span> say 已存在 <span class="s2">&#34;NAT gateway </span><span class="nv">$NAT_NAME</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">  <span class="nv">NAT</span><span class="o">=</span><span class="k">$(</span>q network nat-gateway create -c <span class="s2">&#34;</span><span class="nv">$C</span><span class="s2">&#34;</span> --vcn-id <span class="s2">&#34;</span><span class="nv">$V</span><span class="s2">&#34;</span> --display-name <span class="s2">&#34;</span><span class="nv">$NAT_NAME</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">159</span><span class="cl"><span class="se"></span>    --wait-for-state AVAILABLE --query data.id --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;建 NAT gateway&#34;</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">  <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$NAT</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.natgateway* <span class="o">]]</span> <span class="o">||</span> die <span class="s2">&#34;建不出 NAT gateway：</span><span class="nv">$NAT</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">  say 新建 <span class="s2">&#34;NAT gateway </span><span class="nv">$NAT_NAME</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">record NAT <span class="s2">&#34;</span><span class="nv">$NAT</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">
</span></span><span class="line"><span class="ln">165</span><span class="cl"><span class="c1"># 4. 路由：私有子網路的路由表要有 service gateway 那一條與 0.0.0.0/0 → NAT；update 是整份替換，所以兩條一起寫。</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl"><span class="c1">#   路由表裡還有這兩條以外的規則時，改寫會把它們刪掉，所以先數，有就停下來</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl"><span class="nv">OTHER</span><span class="o">=</span><span class="k">$(</span>q network route-table get --rt-id <span class="s2">&#34;</span><span class="nv">$RT</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">168</span><span class="cl"><span class="se"></span>  --query <span class="s2">&#34;data.\&#34;route-rules\&#34;[?destination!=&#39;0.0.0.0/0&#39; &amp;&amp; destination!=&#39;</span><span class="nv">$SGCIDR</span><span class="s2">&#39;].destination&#34;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;讀路由表&#34;</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl"><span class="nv">OTHER</span><span class="o">=</span><span class="k">$(</span>tr -d <span class="s1">&#39;[]&#34; \n&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$OTHER</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl"><span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$OTHER</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> die <span class="s2">&#34;路由表裡有 0.0.0.0/0 與 service gateway 以外的規則（目的地 </span><span class="si">${</span><span class="nv">OTHER</span><span class="si">}</span><span class="s2">），先把它們加進這一步的 --route-rules 再跑&#34;</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl"><span class="nv">HAS</span><span class="o">=</span><span class="k">$(</span>q network route-table get --rt-id <span class="s2">&#34;</span><span class="nv">$RT</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">172</span><span class="cl"><span class="se"></span>  --query <span class="s2">&#34;data.\&#34;route-rules\&#34;[?destination==&#39;0.0.0.0/0&#39; &amp;&amp; \&#34;network-entity-id\&#34;==&#39;</span><span class="nv">$NAT</span><span class="s2">&#39;] | length(@)&#34;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;讀路由表&#34;</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$HAS</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="m">1</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span> say 已存在 <span class="s2">&#34;路由 0.0.0.0/0 → </span><span class="nv">$NAT_NAME</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">  q network route-table update --rt-id <span class="s2">&#34;</span><span class="nv">$RT</span><span class="s2">&#34;</span> --force --wait-for-state AVAILABLE --route-rules <span class="s2">&#34;[
</span></span></span><span class="line"><span class="ln">176</span><span class="cl"><span class="s2"> {\&#34;destination\&#34;:\&#34;</span><span class="nv">$SGCIDR</span><span class="s2">\&#34;,\&#34;destinationType\&#34;:\&#34;SERVICE_CIDR_BLOCK\&#34;,\&#34;networkEntityId\&#34;:\&#34;</span><span class="nv">$SG</span><span class="s2">\&#34;},
</span></span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="s2"> {\&#34;destination\&#34;:\&#34;0.0.0.0/0\&#34;,\&#34;destinationType\&#34;:\&#34;CIDR_BLOCK\&#34;,\&#34;networkEntityId\&#34;:\&#34;</span><span class="nv">$NAT</span><span class="s2">\&#34;}]&#34;</span> &gt;/dev/null <span class="se">\
</span></span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="se"></span>    <span class="o">||</span> fail <span class="s2">&#34;改寫路由表&#34;</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">  say 改寫 <span class="s2">&#34;路由表加上 0.0.0.0/0 → </span><span class="si">${</span><span class="nv">NAT_NAME</span><span class="si">}</span><span class="s2">（service gateway 那一條保留）&#34;</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">
</span></span><span class="line"><span class="ln">182</span><span class="cl"><span class="c1"># 5. 出站 NSG：只有出站 TCP 80、443 兩條規則；已存在或新建都記進 ids.env</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl"><span class="nv">NSGU</span><span class="o">=</span><span class="k">$(</span>q network nsg list -c <span class="s2">&#34;</span><span class="nv">$C</span><span class="s2">&#34;</span> --vcn-id <span class="s2">&#34;</span><span class="nv">$V</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">184</span><span class="cl"><span class="se"></span>  --query <span class="s2">&#34;data[?\&#34;display-name\&#34;==&#39;</span><span class="nv">$NSG_NAME</span><span class="s2">&#39; &amp;&amp; \&#34;lifecycle-state\&#34;==&#39;AVAILABLE&#39;] | [0].id&#34;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;查 NSG&#34;</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$NSGU</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.networksecuritygroup* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span> say 已存在 <span class="s2">&#34;NSG </span><span class="nv">$NSG_NAME</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">  <span class="nv">NSGU</span><span class="o">=</span><span class="k">$(</span>q network nsg create -c <span class="s2">&#34;</span><span class="nv">$C</span><span class="s2">&#34;</span> --vcn-id <span class="s2">&#34;</span><span class="nv">$V</span><span class="s2">&#34;</span> --display-name <span class="s2">&#34;</span><span class="nv">$NSG_NAME</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">188</span><span class="cl"><span class="se"></span>    --wait-for-state AVAILABLE --query data.id --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;建 NSG&#34;</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">  <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$NSGU</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.networksecuritygroup* <span class="o">]]</span> <span class="o">||</span> die <span class="s2">&#34;建不出 NSG：</span><span class="nv">$NSGU</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">  say 新建 <span class="s2">&#34;NSG </span><span class="nv">$NSG_NAME</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">record NSGU <span class="s2">&#34;</span><span class="nv">$NSGU</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl"><span class="k">for</span> port in <span class="m">80</span> 443<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">  <span class="nv">N</span><span class="o">=</span><span class="k">$(</span>q network nsg rules list --nsg-id <span class="s2">&#34;</span><span class="nv">$NSGU</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">195</span><span class="cl"><span class="se"></span>    --query <span class="s2">&#34;data[?direction==&#39;EGRESS&#39; &amp;&amp; \&#34;tcp-options\&#34;.\&#34;destination-port-range\&#34;.min==\`</span><span class="nv">$port</span><span class="s2">\`] | length(@)&#34;</span> --raw-output<span class="k">)</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">196</span><span class="cl"><span class="se"></span>    <span class="o">||</span> fail <span class="s2">&#34;讀 NSG 的規則&#34;</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$N</span><span class="s2">&#34;</span> -ge <span class="m">1</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span> say 已存在 <span class="s2">&#34;NSG 出站 TCP </span><span class="nv">$port</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">    q network nsg rules add --nsg-id <span class="s2">&#34;</span><span class="nv">$NSGU</span><span class="s2">&#34;</span> --security-rules <span class="s2">&#34;[{\&#34;direction\&#34;:\&#34;EGRESS\&#34;,\&#34;protocol\&#34;:\&#34;6\&#34;,
</span></span></span><span class="line"><span class="ln">200</span><span class="cl"><span class="s2">\&#34;destination\&#34;:\&#34;0.0.0.0/0\&#34;,\&#34;destinationType\&#34;:\&#34;CIDR_BLOCK\&#34;,\&#34;isStateless\&#34;:false,
</span></span></span><span class="line"><span class="ln">201</span><span class="cl"><span class="s2">\&#34;tcpOptions\&#34;:{\&#34;destinationPortRange\&#34;:{\&#34;min\&#34;:</span><span class="nv">$port</span><span class="s2">,\&#34;max\&#34;:</span><span class="nv">$port</span><span class="s2">}}}]&#34;</span> &gt;/dev/null <span class="se">\
</span></span></span><span class="line"><span class="ln">202</span><span class="cl"><span class="se"></span>      <span class="o">||</span> fail <span class="s2">&#34;加 NSG 出站 TCP </span><span class="nv">$port</span><span class="s2"> 的規則&#34;</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">    say 新建 <span class="s2">&#34;NSG 出站 TCP </span><span class="nv">$port</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">
</span></span><span class="line"><span class="ln">207</span><span class="cl"><span class="c1"># 6. 把 NSG 掛到這台主機目前的 VNIC；--nsg-ids 是整份替換，所以保留 VNIC 原本掛著的其他 NSG</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl"><span class="nv">CUR</span><span class="o">=</span><span class="k">$(</span>q network vnic get --vnic-id <span class="s2">&#34;</span><span class="nv">$VNIC</span><span class="s2">&#34;</span> --query <span class="s1">&#39;data.&#34;nsg-ids&#34;&#39;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;讀 VNIC 的 NSG 清單&#34;</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl"><span class="nv">CUR</span><span class="o">=</span><span class="k">$(</span>tr -d <span class="s1">&#39;[]&#34; \n&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$CUR</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;,</span><span class="nv">$CUR</span><span class="s2">,&#34;</span> <span class="o">==</span> *<span class="s2">&#34;,</span><span class="nv">$NSGU</span><span class="s2">,&#34;</span>* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span> say 已存在 <span class="s2">&#34;NSG 掛在 VNIC 上&#34;</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">  <span class="nv">LIST</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$CUR</span><span class="s2">,</span><span class="nv">$NSGU</span><span class="s2">&#34;</span> <span class="p">|</span> sed <span class="s1">&#39;s/^,//&#39;</span> <span class="p">|</span> awk -F, <span class="s1">&#39;{for(i=1;i&lt;=NF;i++) printf &#34;%s\&#34;%s\&#34;&#34;, (i&gt;1?&#34;,&#34;:&#34;&#34;), $i}&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">  q network vnic update --vnic-id <span class="s2">&#34;</span><span class="nv">$VNIC</span><span class="s2">&#34;</span> --nsg-ids <span class="s2">&#34;[</span><span class="nv">$LIST</span><span class="s2">]&#34;</span> --force &gt;/dev/null <span class="o">||</span> fail <span class="s2">&#34;改寫 VNIC 的 NSG 清單&#34;</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">  say 改寫 <span class="s2">&#34;VNIC 的 NSG 清單加上 </span><span class="nv">$NSG_NAME</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">
</span></span><span class="line"><span class="ln">217</span><span class="cl"><span class="c1"># 7. port forwarding session：指向這台現在的私有 IP 與 22 port；session 最長 1 小時，每次重跑都開新的</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl"><span class="nv">SPFU</span><span class="o">=</span><span class="k">$(</span>q bastion session create-port-forwarding --bastion-id <span class="s2">&#34;</span><span class="nv">$B</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">219</span><span class="cl"><span class="se"></span>  --target-private-ip <span class="s2">&#34;</span><span class="nv">$IP</span><span class="s2">&#34;</span> --target-port <span class="m">22</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">220</span><span class="cl"><span class="se"></span>  --ssh-public-key-file <span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/session_key.pub&#34;</span> --session-ttl <span class="m">3600</span> --display-name <span class="s2">&#34;</span><span class="nv">$NAME</span><span class="s2">-pf&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">221</span><span class="cl"><span class="se"></span>  --wait-for-state SUCCEEDED --wait-for-state FAILED --max-wait-seconds <span class="m">600</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">222</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data.resources[0].identifier&#39;</span> --raw-output <span class="p">|</span> tail -1<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;建 port forwarding session&#34;</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl"><span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$SPFU</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.bastionsession* <span class="o">]]</span> <span class="o">||</span> die <span class="s2">&#34;建不出 port forwarding session：</span><span class="nv">$SPFU</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;SPF=</span><span class="nv">$SPFU</span><span class="s2">&#34;</span> &gt;&gt; <span class="s2">&#34;</span><span class="nv">$HOSTENV</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">say 新建 <span class="s2">&#34;port forwarding session（1 小時後失效）&#34;</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">
</span></span><span class="line"><span class="ln">227</span><span class="cl"><span class="c1"># 8. ssh 設定：整份由範本產生；known_hosts 只在主機那一步（第 1 步）換了一台主機時清空（新建，</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl"><span class="c1">#   或查到的主機跟 host-&lt;名稱&gt;.env 最後記下的不同），同一台主機還在時保留</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">cat &gt; <span class="s2">&#34;</span><span class="nv">$CONF</span><span class="s2">&#34;</span> <span class="s">&lt;&lt;EOF
</span></span></span><span class="line"><span class="ln">230</span><span class="cl"><span class="s"># 由 rebuild-ubuntu.sh 產生：經 Bastion 的 port forwarding session 登入 $NAME
</span></span></span><span class="line"><span class="ln">231</span><span class="cl"><span class="s">Host ${NAME}-bastion
</span></span></span><span class="line"><span class="ln">232</span><span class="cl"><span class="s">  HostName host.bastion.&lt;區域&gt;.oci.oraclecloud.com
</span></span></span><span class="line"><span class="ln">233</span><span class="cl"><span class="s">  User $SPFU
</span></span></span><span class="line"><span class="ln">234</span><span class="cl"><span class="s">  IdentityFile $D/session_key
</span></span></span><span class="line"><span class="ln">235</span><span class="cl"><span class="s">  IdentitiesOnly yes
</span></span></span><span class="line"><span class="ln">236</span><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="ln">237</span><span class="cl"><span class="s">Host $NAME
</span></span></span><span class="line"><span class="ln">238</span><span class="cl"><span class="s">  HostName $IP
</span></span></span><span class="line"><span class="ln">239</span><span class="cl"><span class="s">  User ubuntu
</span></span></span><span class="line"><span class="ln">240</span><span class="cl"><span class="s">  IdentityFile $D/verify-a1-key
</span></span></span><span class="line"><span class="ln">241</span><span class="cl"><span class="s">  IdentitiesOnly yes
</span></span></span><span class="line"><span class="ln">242</span><span class="cl"><span class="s">  ProxyJump ${NAME}-bastion
</span></span></span><span class="line"><span class="ln">243</span><span class="cl"><span class="s">  UserKnownHostsFile $KNOWN
</span></span></span><span class="line"><span class="ln">244</span><span class="cl"><span class="s">  StrictHostKeyChecking accept-new
</span></span></span><span class="line"><span class="ln">245</span><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$NEW</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="m">1</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span> say 改寫 <span class="s2">&#34;ssh 設定 </span><span class="si">${</span><span class="nv">CONF</span><span class="si">}</span><span class="s2">；known_hosts 已清空（換了一台主機）&#34;</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl"><span class="k">else</span> say 改寫 <span class="s2">&#34;ssh 設定 </span><span class="si">${</span><span class="nv">CONF</span><span class="si">}</span><span class="s2">；known_hosts 保留&#34;</span><span class="p">;</span> <span class="k">fi</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;下一步：ssh -F </span><span class="nv">$CONF</span><span class="s2"> </span><span class="nv">$NAME</span><span class="s2"> &#39;cloud-init status --wait; uptime&#39;&#34;</span></span></span></code></pre></div><p>腳本放在跟 <code>ids.env</code>、金鑰同一個目錄執行。要依環境換或確認的位置：</p>
<ul>
<li><strong><code>&lt;可用網域&gt;</code></strong>（<code>oci iam availability-domain list</code> 查）與兩處 <strong><code>&lt;區域&gt;</code></strong>（查法在〈前置條件：沿用 Oracle Cloud 建立主機實測紀錄的網路、Bastion 與金鑰〉的 CLI 登入那一項）。</li>
<li><strong><code>--profile DEFAULT</code></strong>：<code>oci session authenticate</code> 時取的 profile 名稱，另外取了名字就兩處都換。</li>
<li><strong><code>NAT_NAME</code> 與 <code>NSG_NAME</code></strong>：要跟〈加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG〉建 NAT gateway 與 NSG 時用的名稱相同。名稱不同的話，腳本依名稱找不到既有的那一份，會再建一份 NAT gateway 與 NSG。</li>
<li><strong>規格與映像檔</strong>：<code>--shape-config</code> 的 1 OCPU、6 GB，以及查映像檔的 <code>&quot;Canonical Ubuntu&quot;</code>、<code>&quot;24.04&quot;</code>、<code>VM.Standard.A1.Flex</code> 三個篩選字串；換成別的作業系統時，產生 ssh 設定那一步的 <code>User ubuntu</code> 也要跟著換（Oracle Linux 是 <code>opc</code>）。</li>
<li><strong><code>BOOT_GB</code></strong>：只在新建主機時有作用，寫在指令前面（<code>BOOT_GB=60 ./rebuild-ubuntu.sh &lt;主機名稱&gt;</code>）。不給時開機磁碟是映像檔的預設大小，Ubuntu 24.04 扣區塊磁碟額度 47 GB；給了之後新主機的開機磁碟就是這個大小，<code>60</code> 實際建出 60 GiB（<code>lsblk -b</code> 讀到 64424509440 位元組，實跑）。<code>--boot-volume-size-in-gbs</code>、<code>size-in-gbs</code> 與額度用量寫的 GB，數值上都是 GiB（帳務明細與價目表的「GB」是哪一種沒有查）；映像檔預設的「50 GB」是十進位的 50.0 GB，也就是 46.6 GiB、<code>size-in-gbs</code> 47，所以 <code>BOOT_GB=50</code> 已經比預設大、扣 50（從 60 的實跑推論，50 沒有試）。主控台建立頁自訂大小的下限是 50 GB，出處見 <a href="/blog/devops/infra/cloud-providers/vm-options/storage/" data-link-title="磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留" data-link-desc="雲端主機的磁碟選項：開機磁碟的大小與只能加大的限制、效能等級與費用的關係、靜態加密與傳輸中加密、另外掛載的區塊磁碟，以及刪除主機時開機磁碟是否預設一起刪除">磁碟：開機磁碟與區塊磁碟的大小、效能、加密與去留</a>〈開機磁碟的大小〉；給小於 50 的值時 CLI 回什麼，這次沒有試。加大之後，Ubuntu 的根目錄在開機時自動延伸到整顆磁碟（<code>df -h /</code> 讀到 58G）；換成別的映像檔時，檔案系統可能要另外延伸（Oracle Linux 的 <code>oci-growfs</code> 這類工具），否則多出的容量用不到（沒有實跑）。加大之前先讀免費額度還剩多少（<code>total-free-storage-gb-regional</code> 的 <code>available</code>，額度算的是開機磁碟與區塊磁碟的合計，指令在〈費用與額度：A1、開機磁碟與區塊儲存額度、出站流量與 NAT gateway〉）：新磁碟比 <code>available</code> 大時，這一顆放不進剩餘額度，整顆不算免費額度。</li>
<li><strong><code>--session-ttl 3600</code></strong>：不能大於 Bastion 的 session 上限，查法在〈建 Ubuntu 24.04 主機與經由 port forwarding 登入〉。</li>
<li><strong>金鑰的檔名</strong>：<code>verify-a1-key</code>（登入主機）與 <code>session_key</code>（Bastion session）；序列主控台連線的重建腳本另外用 <code>console_rsa</code>，不存在時以 RSA 3072 位元（<code>-b 3072</code>）產生。</li>
<li><strong><code>ids.env</code> 裡七個鍵的值</strong>。</li>
</ul>
<p><code>ids.env</code> 的鍵與查法在〈前置條件：沿用 Oracle Cloud 建立主機實測紀錄的網路、Bastion 與金鑰〉的表，格式是一行一個 <code>鍵=值</code>（例如 <code>C=&lt;compartment 的 OCID&gt;</code>），腳本開頭 <code>source</code> 它。NAT gateway 與 NSG 不論是已存在還是腳本新建的，腳本都把它們的 OCID 以 <code>NAT=</code> 與 <code>NSGU=</code> 記進檔尾，已經有同樣的一行就不重複寫，所以跑過一次之後，〈刪除這組資源的順序〉要填的值都在這個檔裡。<code>host-&lt;名稱&gt;.env</code> 是這一台主機的紀錄：腳本每次新建主機或 session 就在檔尾多加一行 <code>IID=</code> 或 <code>SPF=</code>，同一個鍵出現好幾次時，最後一行才是現在有效的值。腳本只讀它最後一行的 <code>IID=</code>，拿來跟這次查到的主機比對：不是同一台時（新建，或主機在別處重建過），舊的 host key 紀錄對不上，腳本就清空 known_hosts 並補記這次的 <code>IID=</code>。檔案裡還沒有任何一行 <code>IID=</code> 時（主機不是重建腳本建的，例如照〈建 Ubuntu 24.04 主機與經由 port forwarding 登入〉手動建的，或是腳本較早的版本建的），腳本拿空字串比對，同樣判成不是同一台、清空一次。</p>
<p>兩支腳本都假設 CLI 用 session token 登入（〈前置條件：沿用 Oracle Cloud 建立主機實測紀錄的網路、Bastion 與金鑰〉的 CLI 登入那一項）。改用 API 簽章金鑰的話，拿掉三處：<code>export</code> 那一行的 <code>OCI_CLI_AUTH=security_token</code>、開頭的 <code>oci session validate</code> 檢查，以及重建腳本重送迴圈裡每 1800 秒跑一次的 <code>oci session refresh</code>；拿掉之後 CLI 改用設定檔裡的 API 簽章金鑰（沒有實跑）。</p>
<p>重建腳本改寫路由表時，<code>--route-rules</code> 只寫 service gateway 與 NAT gateway 兩條，前提是這張路由表原本只有 service gateway 那一條（加上之前跑腳本時加進去的 0.0.0.0/0 那一條）。改寫之前，腳本先列出這兩條以外的規則，列得出任何一條就停下來，印出那幾條的目的地：<code>route-table update</code> 是整份替換，沒寫進 <code>--route-rules</code> 的規則會被刪掉。這時把那幾條規則加進腳本裡的 <code>--route-rules</code>，再重跑。</p>
<p><strong>輸出的讀法</strong>：主機那一步（第 1 步）依查到的狀態印不同的行：</p>
<ul>
<li><strong>主機已經是 <code>RUNNING</code></strong>：印「已存在」。</li>
<li><strong>主機被停止（<code>STOPPED</code>）</strong>：印「啟動」（<code>STOPPED → RUNNING</code>）。<code>STOPPING</code> 時先等到 <code>STOPPED</code> 再啟動，<code>STARTING</code>、<code>PROVISIONING</code> 時等到 <code>RUNNING</code> 再印「已存在」。啟動失敗時（例如容量不足，推論）印出 <code>instance action</code> 的錯誤並以 2 結束。<code>STOPPED</code>、<code>STOPPING</code>、<code>STARTING</code>、<code>PROVISIONING</code> 四條在雲端跑過（結果在下面「在雲端實跑的結果」的「名稱不存在、同時另一份腳本碰到 <code>PROVISIONING</code>」與「主機 <code>STOPPED</code>、<code>STOPPING</code>、<code>STARTING</code>」兩項）；啟動失敗只用假的 <code>oci</code> 測過。</li>
<li><strong>找不到還沒終止的同名主機</strong>：印「新建」與映像檔名稱，容量不足（500）或被限流（429）時中間會有幾行「重送」（例：<code>[重送] 第 1 次 500：&quot;message&quot;: &quot;Out of host capacity.&quot;，112 秒後再送</code>，這一行出自假的 <code>oci</code>）。建立請求回 500、429、401 以外的回應碼時（例如 <code>&lt;可用網域&gt;</code> 沒換掉而回 400），腳本印出回應碼與 <code>message</code> 就以 4 結束，不重送：這類回應代表請求本身有誤，重送幾次都是同一個結果，照訊息改好參數再跑。</li>
<li><strong>主機停在其他狀態</strong>（<code>TERMINATING</code>、<code>MOVING</code>、<code>CREATING_IMAGE</code> 這類）：腳本不猜下一步，印出狀態並以 2 結束，等它變成 <code>RUNNING</code>、<code>STOPPED</code> 或 <code>TERMINATED</code> 再重跑。</li>
</ul>
<p>之後的各步，「已存在」代表那一項不必動，「新建」與「改寫」代表腳本這一次在雲端建了或改了資源：</p>
<ul>
<li><strong>VNIC 與私有 IP</strong>（第 2 步）：一律印「已存在」與私有 IP；主機重建過時這個 IP 是新的。</li>
<li><strong>NAT gateway</strong>（第 3 步）與 <strong>NSG 和它的兩條規則</strong>（第 5 步）：VCN 裡已經有同名的就印「已存在」；只有第一次跑、或有人刪掉了它們時才印「新建」。</li>
<li><strong>路由</strong>（第 4 步）：已經有 0.0.0.0/0 指向 NAT gateway 那一條就印「已存在」；印「改寫」代表路由表被整份替換成 service gateway 與 NAT gateway 兩條，也就是先前少了 0.0.0.0/0 那一條，或它指向別的出口。</li>
<li><strong>NSG 掛到 VNIC</strong>（第 6 步）：印「改寫」代表 VNIC 原本沒有掛這個 NSG，主機重建之後的新 VNIC 一定走這一條。</li>
<li><strong>port forwarding session</strong>（第 7 步）：每次都印「新建」。</li>
<li><strong>ssh 設定</strong>（第 8 步）：每次都印「改寫」，並註明 known_hosts 是保留還是清空；清空代表這次的主機跟 <code>host-&lt;名稱&gt;.env</code> 最後記下的不是同一台，或那個檔案還沒有記下任何一台（主機不是重建腳本建的；實跑結果在下面的 <code>STOPPED</code> 那一項）。</li>
</ul>
<p>任何一步印「錯誤」時腳本以 2 結束（建立請求被拒的 4 在上一項），錯誤那一行寫出是哪一步；<code>oci</code> 失敗時，冒號後面接的是那條指令寫在 stderr 的內容。修好之後重跑整支腳本即可，前面已經完成的步驟會印「已存在」。例外是建立請求的回應讀不出回應碼（exit 3）：設定檔錯誤、網路中斷都會走到這裡，而網路中斷時請求可能已經在 Oracle 那一端成功，所以重跑之前先用 <code>oci compute instance list -c &lt;compartment 的 OCID&gt; --display-name &lt;主機名稱&gt;</code> 確認沒有還在的同名主機。</p>
<p><strong>在雲端實跑的結果</strong>（「主機已經是 <code>RUNNING</code>，沒有等待迴圈的版本」與「名稱不存在，較早的兩個版本」兩項跑的是上面三個版本裡較早的兩個，其餘都是現在這一版；各項用的版本寫在項目裡）：</p>
<ul>
<li>
<p><strong>NAT gateway 與 NSG 都不存在</strong>（現在這一版，2026-10-27 09:32，跑的是 <code>./rebuild-ubuntu.sh</code>，不給名稱就是 <code>verify-ubuntu</code>；它的 NAT gateway 與 NSG 已在凌晨照〈刪除這組資源的順序〉刪掉）。exit 0，全程 32 秒。主機那一步印「已存在」（<code>RUNNING</code>），NAT gateway 印「新建」，路由印「改寫」（加上 0.0.0.0/0 → NAT gateway <code>verify-nat</code>，名稱是腳本的 <code>NAT_NAME</code>；保留 service gateway 那一條），NSG 與它的出站 TCP 80、443 兩條規則都印「新建」，掛 NSG 那一步印「改寫」；新的 NAT gateway 與 NSG 的 OCID 以新的一行接在 <code>ids.env</code> 檔尾。登入之後 <code>apt-get update</code> 下載 3982 kB，<code>curl https://archive.ubuntu.com/ubuntu/</code> 回 200，出站恢復。從 NAT gateway 那一行到掛 NSG 那一行共 14 秒，NAT gateway 與 NSG 的 <code>create</code> 帶的 <code>--wait-for-state AVAILABLE</code> 各約 3 秒（從 log 的時間戳推論）。</p>
</li>
<li>
<p><strong>名稱不存在、同時另一份腳本碰到 <code>PROVISIONING</code></strong>（現在這一版，2026-10-27 09:33，接在上一項之後，所以 NAT gateway 與 NSG 已經存在）。先在背景跑一份新建主機的腳本（下面稱「新建的那一份」），本機另開一個 shell 迴圈查同名主機的狀態，查到 <code>PROVISIONING</code> 就對同一個名稱再跑一份（下面稱「建立中才開跑的那一份」）。這個迴圈是測試用的，不屬於重建腳本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 迴圈裡的 oci 跟重建腳本一樣用 session token 登入</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nb">export</span> <span class="nv">OCI_CLI_AUTH</span><span class="o">=</span>security_token
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 新建的那一份：放到背景跑，開機磁碟 60 GB；APID 記下它的程序編號</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nv">BOOT_GB</span><span class="o">=</span><span class="m">60</span> ./rebuild-ubuntu.sh verify-ubuntu-60g &gt; new.log 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">&amp;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nv">APID</span><span class="o">=</span><span class="nv">$!</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 新建的那一份還在跑時，每次查完等 3 秒再查（這次實際約 4 秒一次）；</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 查到 PROVISIONING 就跑建立中才開跑的那一份。新建的那一份提早結束（例如建立請求回 400）時迴圈跟著結束</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">while</span> <span class="nb">kill</span> -0 <span class="s2">&#34;</span><span class="nv">$APID</span><span class="s2">&#34;</span> 2&gt;/dev/null<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="nv">st</span><span class="o">=</span><span class="k">$(</span>oci compute instance list -c &lt;compartment 的 OCID&gt; --display-name verify-ubuntu-60g <span class="se">\
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="se"></span>    --query <span class="s1">&#39;data[?&#34;lifecycle-state&#34;!=`TERMINATED`] | [0].&#34;lifecycle-state&#34;&#39;</span> --raw-output 2&gt;/dev/null<span class="k">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$st</span><span class="s2">&#34;</span> <span class="o">==</span> PROVISIONING <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="o">{</span> ./rebuild-ubuntu.sh verify-ubuntu-60g &gt; provisioning.log 2&gt;<span class="p">&amp;</span>1<span class="p">;</span> break<span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  sleep <span class="m">3</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nb">wait</span> <span class="s2">&#34;</span><span class="nv">$APID</span><span class="s2">&#34;</span></span></span></code></pre></div><p>新建的那一份開跑後第 6 秒，迴圈還查不到主機；第 10 秒查到 <code>PROVISIONING</code>，建立中才開跑的那一份隨即開跑。這一台讓區塊磁碟的合計變成 201，60 GB 那一顆整顆不算免費額度（〈費用與額度：A1、開機磁碟與區塊儲存額度、出站流量與 NAT gateway〉的「合計超過 200 GB 時」），所以照做這個測試會多出一顆不在免費額度裡的磁碟；這台在帳務明細出現它的計費項目之後，於 2026-10-27 11:58 終止，開機磁碟一起刪除（work request 65 秒 <code>SUCCEEDED</code>），區塊磁碟的兩條用量都回到 141；帳務的結果在〈費用與額度：A1、開機磁碟與區塊儲存額度、出站流量與 NAT gateway〉的「合計超過 200 GB 時」。</p>
<ul>
<li><strong>新建的那一份</strong>：建立請求第一次送出就成功，exit 0，全程 87 秒。從印出「新建」主機（送出建立請求之前）到印出「已 RUNNING」約 50 秒。建立請求不帶 <code>--wait-for-state</code>，這段時間由 <code>wait_state</code> 每 10 秒查一次狀態，所以它輪詢了好幾次，建立中才開跑的那一份的 <code>wait_state</code> 在同一段時間也一樣（從時間戳與腳本推論，腳本不記每一次查詢）。這兩份腳本的 <code>wait_state</code> 是它的輪詢第一次在雲端實際等待，先前幾次都在第一次查詢就拿到目標狀態。網路那幾步都印「已存在」，掛 NSG 那一步印「改寫」（新主機的 VNIC），port forwarding session 從上一行到印出「新建」17 秒，ssh 設定印「known_hosts 已清空（換了一台主機）」。</li>
<li><strong>建立中才開跑的那一份</strong>：查到主機是 <code>PROVISIONING</code>，走進 <code>STARTING|PROVISIONING</code> 那一條等到 <code>RUNNING</code>；它的 log 第一行（主機那一步）在新建的那一份印出「已 RUNNING」之後 4 秒印出「已存在」（等到 <code>RUNNING</code>），沒有再送建立請求，tenancy 裡只有一台 <code>verify-ubuntu-60g</code>。之後各步都印「已存在」，連掛 NSG 那一步也是（新建的那一份在 3 秒前掛上了），exit 0，全程 80 秒。建立中才開跑的那一份開跑時，新建的那一份已經把新主機的 <code>IID=</code> 寫進 <code>host-verify-ubuntu-60g.env</code>，所以建立中才開跑的那一份判成同一台，印「known_hosts 保留」（從它印「保留」、這個檔只有一行 <code>IID=</code>，以及腳本在開頭讀上一次的 <code>IID</code> 推得）。兩份腳本各建了一個 port forwarding session，<code>host-verify-ubuntu-60g.env</code> 因此有兩行 <code>SPF=</code>，ssh 設定是後寫的這一份。</li>
<li><strong>登入之後</strong>：照產生的 ssh 設定登入，<code>cloud-init status --wait</code> 正常結束（輸出沒有留下），<code>apt-get update</code> 照常連到套件來源；<code>df -h /</code> 印出根目錄 58G。</li>
<li><strong>兩份同時跑的邊界</strong>：這次只驗了後一份在 <code>PROVISIONING</code> 出現之後才開跑的情形。新建的那一份查到沒有同名主機之後、新主機在 <code>instance list</code> 查得到之前有一段空窗（這次迴圈在新建的那一份開跑後第 6 秒還查不到，空窗本身多長沒有量），後一份的查詢落在這段空窗時，結果是空的，會再送一次建立請求，建出第二台同名主機，A1 用量與開機磁碟額度都多扣一台主機的量。NAT gateway 與 NSG 是整個 VCN 共用、同樣依名稱先查後建：兩份腳本（名稱不同也一樣）在它們還不存在時同時跑，會各建一份，<code>ids.env</code> 記下兩行、<code>source</code> 時取後一行，路由表的「改寫」互相覆蓋，路由可能指向沒有記下的那一份。這兩種都是依腳本邏輯推論，沒有實跑；這次實跑時 NAT gateway 與 NSG 已經存在。所以同一個 VCN 一次只跑一份重建腳本。</li>
<li><strong>建立中的主機沒有變成 <code>RUNNING</code></strong>：<code>wait_state</code> 只比對 <code>RUNNING</code>，建立失敗、主機從 <code>PROVISIONING</code> 變成 <code>TERMINATED</code> 時，兩份腳本都會等滿 900 秒，再以「沒有變成 RUNNING」結束，訊息裡的狀態是最後一次查到的值（依腳本邏輯推論，沒有實跑）。</li>
<li><strong>不給 <code>BOOT_GB</code> 的新建</strong>（現在這一版，2026-10-27 13:04 實跑）：不給 <code>BOOT_GB</code> 時 <code>BOOT_ARGS</code> 是空陣列，建立請求那一行展開空陣列的寫法先前只用假的 <code>oci</code> 與本機的 bash 3.2 測過（寫法的理由在下面「腳本裡幾處寫法的理由」的 <code>BOOT_ARGS</code> 那一項）。在 tenancy 裡已有三台 A1、免費區塊儲存扣了 141 的狀態下，用 <code>env -u BOOT_GB /bin/bash ./rebuild-ubuntu.sh verify-ubuntu-nb</code> 跑（<code>env -u</code> 確保 shell 裡沒有殘留的 <code>BOOT_GB</code>）：建立請求第一次送出就成功，從印出「新建」到「已 RUNNING」37 秒，exit 0，log 第一行到最後一行 68 秒；網路那幾步都印「已存在」，掛 NSG 那一步印「改寫」。登入之後 <code>cloud-init status --wait</code> 回 <code>done</code>，<code>df -h /</code> 的根目錄是 45G；<code>oci bv boot-volume list</code> 列出這顆開機磁碟的 <code>size-in-gbs</code> 是 47，免費區塊儲存的用量從 141 變成 188，跟手動照預設大小建的主機相同。這台主機之後拿來測付費磁碟的自動轉換（〈費用與額度：A1、開機磁碟與區塊儲存額度、出站流量與 NAT gateway〉），測完已終止。</li>
</ul>
</li>
<li>
<p><strong>主機 <code>STOPPED</code>、<code>STOPPING</code>、<code>STARTING</code></strong>（現在這一版，2026-10-27 對 <code>verify-ubuntu</code> 連跑四次，<code>STARTING</code> 試了兩次；每次之前用 <code>oci compute instance action --instance-id &lt;主機的 OCID&gt; --action STOP</code> 或 <code>--action START</code> 讓主機落在那個狀態）。四次都是 exit 0，網路設定那幾步都印「已存在」，port forwarding session 每次都印「新建」。各次全程的秒數不同，差在 API 回應的快慢：port forwarding session 那一行以外，log 各行的時間戳之間都不到 10 秒，沒有任何一步走進 <code>sleep</code>，而 port forwarding session 那一行就從 8 秒到 22 秒不等。下面的「從腳本開始到這一行」是用 log 最後一行的時間減去全程秒數推算的，時間戳只到秒，誤差約 1 秒：</p>
<ul>
<li><strong><code>STOPPED</code></strong>：先帶 <code>--wait-for-state STOPPED</code> 停機，6 秒回 <code>STOPPED</code>。腳本全程 28 秒，第一行印「啟動」（<code>STOPPED → RUNNING</code>），從腳本開始到這一行約 9 秒，<code>instance action</code> 的 START 等到 <code>RUNNING</code> 包含在內。登入時 <code>uptime</code> 是 <code>up 0 min</code>，<code>cloud-init status --wait</code> 回 <code>done</code>，<code>apt-get update</code> 照常連到套件來源。</li>
<li><strong><code>STOPPING</code></strong>：送出 STOP 不帶 <code>--wait-for-state</code>，CLI 回傳時是 <code>STOPPING</code>，再查一次仍是 <code>STOPPING</code>，接著跑腳本。腳本查到 <code>STOPPING</code>，全程 35 秒，第一行印「啟動」（<code>STOPPING → RUNNING</code>），從腳本開始到這一行約 9 秒。這 9 秒裡有登入檢查、查主機、查狀態、<code>wait_state</code> 的查詢與 START 共五次 API 呼叫，<code>wait_state</code> 要是睡過一次 10 秒就不可能少於 10 秒，所以它第一次查時主機已經是 <code>STOPPED</code>，沒有進 <code>sleep</code>（從時間戳推論）。</li>
<li><strong><code>STARTING</code></strong>：試了兩次，第二次才走到這條分支。兩次都是主機停好之後送出 START 不帶 <code>--wait-for-state</code>。第一次在送出 START 與跑腳本之間多查了一次狀態（回 <code>STARTING</code>），腳本開始時主機已經是 <code>RUNNING</code>，第一行印「已存在」（<code>RUNNING</code>），全程 21 秒，走的是主機已經是 <code>RUNNING</code> 的那一條；從腳本開始到第一行只有約 2 秒，所以 <code>STARTING</code> 在多查的那一次之後幾秒內就變成 <code>RUNNING</code>（推論，沒有量 <code>STARTING</code> 維持多久）。第二次送出 START 之後立刻跑腳本：腳本查到 <code>STARTING</code>，進 <code>wait_state</code> 之後第一次查就已經是 <code>RUNNING</code>，沒有進到兩次查詢之間的 10 秒 <code>sleep</code>（從腳本開始到第一行只有約 4 秒推論）；全程 55 秒，第一行印「已存在」（等到 <code>RUNNING</code>）。</li>
<li><strong>VNIC 的等待迴圈</strong>：四次的 VNIC 那一行都在主機那一行之後 3 到 9 秒內印出，主機沒有換過、VNIC 一直掛著，迴圈第一次查就拿到，沒有進 <code>sleep</code>（從時間戳推論）。</li>
<li><strong>known_hosts</strong>：<code>STOPPED</code> 那一次印「known_hosts 已清空（換了一台主機）」，而主機是同一台。<code>verify-ubuntu</code> 是手動建的，之後只跑過腳本較早的版本（那一版只在新建主機時記 <code>IID</code>），<code>host-verify-ubuntu.env</code> 裡只有 session 的紀錄、沒有 <code>IID</code> 那一行，腳本拿空字串跟查到的 OCID 比，判成不同，同時寫進這一行；之後三次都印「保留」。下面假的 <code>oci</code> 測試表裡「主機 <code>STOPPED</code>」那一列印「保留」，是因為那一組測試的 <code>host-&lt;名稱&gt;.env</code> 已經記著同一台的 <code>IID</code>。清空之後，ssh 設定的 <code>StrictHostKeyChecking accept-new</code> 讓下一次登入重新記下這台的 host key（<code>Permanently added '&lt;私有 IP&gt;' (ED25519)</code>），登入沒有受影響；主機沒有重裝，host key 應該沒換，但清空之前的紀錄已經是空的，沒有舊的 key 可以比對。</li>
</ul>
</li>
<li>
<p><strong>主機已經是 <code>RUNNING</code>，沒有等待迴圈的版本</strong>：exit 0，全程 34 秒；主機那一步到掛 NSG 那一步都印「已存在」，port forwarding session 的建立佔了其中約 17 秒，最後兩行是 session 的「新建」與 ssh 設定的「改寫」。照產生的 <code>ssh-verify-ubuntu.conf</code> 登入之後，<code>apt-get update</code> 照常連到 Ubuntu 的套件來源。</p>
</li>
<li>
<p><strong>名稱不存在，較早的兩個版本</strong>（2026-10-27 凌晨建 <code>verify-ubuntu-rebuild</code>）：建立請求第一次送出就成功。沒有等待迴圈的版本在印出「已 RUNNING」之後，接著以「查不到主機的 VNIC」結束（exit 2）；加上等待迴圈的版本從主機已經是 <code>RUNNING</code> 的狀態接著跑，exit 0，80 秒：掛 NSG 那一步印「改寫」，替新主機的 VNIC 掛上出站 NSG；port forwarding session 這一次要 64 秒才變成 <code>SUCCEEDED</code>，比既有主機的那一次長。登入時主機已開機 3 分鐘，<code>cloud-init status --wait</code> 立刻回 <code>done</code>，<code>apt-get update</code> 9 秒下載 39.9 MB，速率 4468 kB/s。</p>
</li>
</ul>
<p><strong>用假的 <code>oci</code> 測的結果</strong>（現在這一版；假的 <code>sleep</code> 只記秒數，所以秒數是腳本算出來要等的值。重送前的等待依回應碼調整，再加減 15 秒的隨機抖動，每次跑的值不同）。主機那一步與建立請求的情形：</p>
<table>
  <thead>
      <tr>
          <th>情形</th>
          <th>結束碼</th>
          <th>建立請求送出次數</th>
          <th>記下的等待</th>
          <th>known_hosts</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>名稱不存在，新主機先回 <code>PROVISIONING</code></td>
          <td>0</td>
          <td>1</td>
          <td><code>wait_state</code> 等 10 秒一次</td>
          <td>清空</td>
      </tr>
      <tr>
          <td>名稱不存在，加 <code>BOOT_GB=60</code></td>
          <td>0</td>
          <td>1</td>
          <td><code>wait_state</code> 等 10 秒一次</td>
          <td>清空</td>
      </tr>
      <tr>
          <td>主機已經是 <code>RUNNING</code></td>
          <td>0</td>
          <td>0</td>
          <td>無</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>主機 <code>STOPPED</code></td>
          <td>0</td>
          <td>0</td>
          <td>無（<code>instance action</code> 啟動）</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>主機 <code>TERMINATING</code></td>
          <td>2</td>
          <td>0</td>
          <td>無</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>建立請求先回 500、第二次成功</td>
          <td>0</td>
          <td>2</td>
          <td>重送前 95–125 秒，之後 10 秒</td>
          <td>清空</td>
      </tr>
      <tr>
          <td>建立請求的輸出在 OCID 前面多一行警告</td>
          <td>0</td>
          <td>1</td>
          <td><code>wait_state</code> 等 10 秒一次</td>
          <td>清空</td>
      </tr>
      <tr>
          <td>建立請求的輸出讀不出回應碼</td>
          <td>3</td>
          <td>1</td>
          <td>無</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>主機 <code>PROVISIONING</code>、再查一次變 <code>RUNNING</code></td>
          <td>0</td>
          <td>0</td>
          <td><code>wait_state</code> 等 10 秒一次</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>主機 <code>STOPPING</code>、再查一次變 <code>STOPPED</code></td>
          <td>0</td>
          <td>0</td>
          <td>無（<code>instance action</code> 啟動）</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>建立請求回 401</td>
          <td>2</td>
          <td>1</td>
          <td>無</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>建立請求回 400</td>
          <td>4</td>
          <td>1</td>
          <td>無</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>建立請求先回 500、第二次回 400</td>
          <td>4</td>
          <td>2</td>
          <td>重送前 95–125 秒</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>主機 <code>STARTING</code>、再查一次變 <code>RUNNING</code></td>
          <td>0</td>
          <td>0</td>
          <td>無</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>主機查得到、狀態讀不出</td>
          <td>2</td>
          <td>0</td>
          <td>無</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>建立請求連回兩次 429、第三次成功</td>
          <td>0</td>
          <td>3</td>
          <td>重送前約 240 與 480 秒</td>
          <td>清空</td>
      </tr>
      <tr>
          <td>查不到映像檔</td>
          <td>2</td>
          <td>0</td>
          <td>無</td>
          <td>保留</td>
      </tr>
      <tr>
          <td>主機 <code>RUNNING</code>，但跟上次記下的不是同一台</td>
          <td>0</td>
          <td>0</td>
          <td>無</td>
          <td>清空</td>
      </tr>
  </tbody>
</table>
<p>加 <code>BOOT_GB=60</code> 的那一列，假的 <code>oci</code> 記下的建立請求帶著 <code>--boot-volume-size-in-gbs 60</code>；沒有給 <code>BOOT_GB</code> 的那幾列，建立請求裡沒有這個選項。每一種會送出建立請求的情形，第一次送出之前都先延長了一次 token（<code>last_refresh=0</code>）；回 400 的兩種情形印出的 <code>message</code> 是假的 <code>oci</code> 自己編的，Oracle 實際回的訊息這次沒有看過。查不到映像檔時，錯誤那一行是「查不到 Ubuntu 24.04 的 A1 映像檔：查詢結果是空的」。</p>
<p>網路設定那幾步的情形，以及讓單一種 <code>oci</code> 呼叫失敗的情形（主機都已經是 <code>RUNNING</code>）：</p>
<table>
  <thead>
      <tr>
          <th>情形</th>
          <th>結束碼</th>
          <th>結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>NAT gateway 與 NSG 都不存在、路由表少一條</td>
          <td>0</td>
          <td>新建 NAT gateway、NSG 與兩條規則，改寫路由表與 VNIC；兩者的 OCID 記進 <code>ids.env</code></td>
      </tr>
      <tr>
          <td>路由表少了 0.0.0.0/0 那一條</td>
          <td>0</td>
          <td>改寫路由表，寫進去的 service gateway 目的地是 <code>ids.env</code> 裡 <code>SGCIDR</code> 的值</td>
      </tr>
      <tr>
          <td>路由表裡有 0.0.0.0/0 與 service gateway 以外的規則</td>
          <td>2</td>
          <td>不改寫，印出那條規則的目的地</td>
      </tr>
      <tr>
          <td>VNIC 已經掛著另外兩個 NSG</td>
          <td>0</td>
          <td><code>vnic update</code> 的 <code>--nsg-ids</code> 是原本那兩個加上出站 NSG</td>
      </tr>
      <tr>
          <td>查同名主機失敗</td>
          <td>2</td>
          <td>不建第二台；印出假的 <code>oci</code> 寫在 stderr 的錯誤</td>
      </tr>
      <tr>
          <td>啟動主機（<code>STOPPED</code> 時）失敗</td>
          <td>2</td>
          <td>不印「啟動」，印出錯誤</td>
      </tr>
      <tr>
          <td>查 NAT gateway 失敗</td>
          <td>2</td>
          <td>不建第二個 NAT gateway</td>
      </tr>
      <tr>
          <td>讀路由表失敗、改寫路由表失敗</td>
          <td>2</td>
          <td>讀取失敗時不改寫；改寫失敗時不印「改寫」</td>
      </tr>
      <tr>
          <td>讀 NSG 的規則失敗、加 NSG 規則失敗</td>
          <td>2</td>
          <td>讀取失敗時不加規則；加規則失敗時不印「新建」</td>
      </tr>
      <tr>
          <td>讀 VNIC 的 NSG 清單失敗、改寫 VNIC 失敗</td>
          <td>2</td>
          <td>讀取失敗時不改寫，原本掛著的 NSG 不會被替換掉；改寫失敗時不印「改寫」</td>
      </tr>
  </tbody>
</table>
<p>這幾種失敗，腳本在加上錯誤處理之前都照樣往下走：查同名主機失敗時當成找不到主機，再建一台；啟動或寫入失敗時照樣印「啟動」「改寫」「新建」；讀路由表或 VNIC 失敗時照樣整份替換，刪掉原本的規則或 NSG（審查時用同一支假的 <code>oci</code> 重現過）。假的 <code>oci</code> 測的是腳本的分支與結束碼，測不到雲端的回應格式與等待時間。</p>
<p>序列主控台連線的重建腳本（下面那一支）用同一支假的 <code>oci</code> 測了這幾種情形：主機已有 <code>ACTIVE</code> 的連線時印「已存在」、不呼叫 <code>create</code>，設定檔填的是那條既有連線的 OCID（exit 0）；沒有時呼叫一次 <code>create</code>、印「新建」（exit 0）；金鑰不存在、而主機已有 <code>ACTIVE</code> 的連線時，產生新金鑰、刪掉舊連線、再建一條（exit 0）；以上三種結束時印出的「用完就刪」那條 <code>delete</code>，代入的都是這次設定檔裡用的那條連線的 OCID；找不到 <code>RUNNING</code> 的同名主機時印「找不到 RUNNING 的 verify-ubuntu，先跑 rebuild-ubuntu.sh」並以 2 結束；查主機、查連線、建連線任一條失敗時印出錯誤並以 2 結束。</p>
<p>腳本裡幾處寫法的理由：</p>
<ul>
<li><strong><code>q</code> 保留結束碼與 stderr</strong>：OCI CLI 3.95.1 在 <code>--query</code> 的結果是空的時候，stdout 沒有輸出、結束碼是 0，只在 stderr 印 <code>Query returned empty result, no output to show.</code>（讀 CLI 套件的 <code>cli_util.py</code> 確認的）；查詢失敗時 stdout 同樣是空的。所以空字串本身分不出「查無結果」與「查詢失敗」，要看結束碼：<code>q</code> 把 stderr 存進暫存檔、照原樣回傳 <code>oci</code> 的結束碼，呼叫的地方失敗就以 <code>fail</code> 停下來並印出 stderr。主機那一步只有在查詢成功、結果又是空的時候，才進建立分支。</li>
<li><strong>主機的狀態由 <code>wait_state</code> 自己查</strong>：<code>oci compute instance get</code> 沒有 <code>--wait-for-state</code>，帶了這個選項時 CLI 印出 <code>Error: No such option '--wait-for-state'.</code> 就結束；沒有等待迴圈的版本把 stderr 丟掉，這個錯誤沒有印出來，腳本在主機還沒 <code>RUNNING</code> 時就往下走。哪些子命令有這個選項用 <code>oci &lt;子命令&gt; --help</code> 查，這次查到有的是 <code>compute instance launch</code>、<code>compute instance action</code>、<code>compute instance terminate</code>、<code>network nat-gateway create</code>、<code>network route-table update</code>、<code>network nsg create</code>、<code>network vnic update</code>、<code>bastion session create-port-forwarding</code> 與 <code>compute instance-console-connection create</code>；沒有的是 <code>compute instance get</code>、<code>compute instance list-vnics</code>、<code>network nsg rules add</code> 與 <code>network nsg rules list</code>。VNIC 另外等最多 120 秒：剛送出建立請求約 5 秒的主機，<code>list-vnics</code> 在雲端實跑時回過空值（那一版的等待沒有作用，當時主機可能還沒到 <code>RUNNING</code>）。</li>
<li><strong>變數後面直接接中文的位置寫成 <code>${NAME}</code></strong>：macOS 的 <code>/bin/bash</code>（3.2.57）把變數名後面緊接的全形字元讀成變數名的一部分，<code>&quot;主機 $NAME（RUNNING）&quot;</code> 在 <code>set -u</code> 之下以 <code>NAME�: unbound variable</code> 結束。訊息裡的 <code>�</code> 不是文章的亂碼：bash 把全形括號「（」的 UTF-8 編碼（三個位元組）的第一個位元組當成變數名的一部分，照原樣印出那一個位元組，終端機顯示不出單獨一個位元組，就印成 <code>�</code>。用 <code>LC_ALL=C grep -n '\$[A-Za-z_][A-Za-z0-9_]*[^ -~]' &lt;腳本&gt;</code> 找得出這類位置，這一版跑這條是 0 行。</li>
<li><strong><code>BOOT_ARGS</code> 展開成 <code>${BOOT_ARGS[@]+&quot;${BOOT_ARGS[@]}&quot;}</code></strong>：同一個 bash 3.2.57 在 <code>set -u</code> 之下展開空陣列 <code>&quot;${BOOT_ARGS[@]}&quot;</code> 時，以 <code>BOOT_ARGS[@]: unbound variable</code> 結束（實跑）。沒有給 <code>BOOT_GB</code> 時陣列是空的。展開寫在 <code>out=$(oci …)</code> 的命令替換裡，照一般寫法，命令替換印出 <code>BOOT_ARGS[@]: unbound variable</code> 之後回傳空字串，腳本照樣往下走、讀不出回應碼，以 3 結束，訊息要讀者去查同名主機，看起來像網路中斷（依腳本結構與本機 bash 3.2 的最小重現推得，整支腳本沒有用一般寫法跑過）。重建既有主機的路徑走不到那一行，只測既有主機時看不出來。<code>${A[@]+…}</code> 在陣列有元素時才展開後面那一段，空陣列就什麼都不印。</li>
</ul>
<p><strong>序列主控台連線的重建腳本</strong>：主機沒有 <code>ACTIVE</code> 的序列主控台連線才建，並產生跟〈序列主控台：RSA 金鑰、建立連線、ProxyJump 設定與看到的開機輸出〉相同寫法的 <code>ssh-console.conf</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="cp"></span><span class="c1"># 替主機建序列主控台連線（instance console connection），並產生 ssh-console.conf。</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 主機已經有 ACTIVE 的連線就沿用；沒有就用 console_rsa.pub 建一條（金鑰不存在時先產生 RSA 3072，</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">#   這時既有的連線登記的是舊的公鑰，先刪掉再建）。</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 官方給的連線字串在 ssh 外面再包一層 ssh 當跳板；這裡寫成等價的 ProxyJump 設定，不在本機執行任意命令。</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 連線只在排查時存在：要排查時跑這支建一條，用完以結尾印出的 delete 指令刪掉。</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 用法：./rebuild-console.sh [主機名稱]（預設 verify-ubuntu）。連線：ssh -tt -F ssh-console.conf verify-console</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 結束碼：0 完成；2 登入過期、找不到主機、oci 失敗（印出它的 stderr）或建不出連線。</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nb">set</span> -uo pipefail
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># OCI CLI 是 Python 程式；PYTHONWARNINGS=ignore 關掉 Python 直譯器印的警告，不讓警告行混進要解析的輸出</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nb">export</span> <span class="nv">OCI_CLI_AUTH</span><span class="o">=</span>security_token <span class="nv">PYTHONWARNINGS</span><span class="o">=</span>ignore
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nv">D</span><span class="o">=</span><span class="k">$(</span><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="nv">$0</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="k">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">source</span> <span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/ids.env&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nv">NAME</span><span class="o">=</span><span class="si">${</span><span class="nv">1</span><span class="k">:-</span><span class="nv">verify</span><span class="p">-ubuntu</span><span class="si">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nv">REGION</span><span class="o">=</span>&lt;區域&gt;
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nv">CONF</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/ssh-console.conf&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nv">KNOWN</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/known_hosts_console&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="nv">ERR</span><span class="o">=</span><span class="k">$(</span>mktemp <span class="s2">&#34;</span><span class="si">${</span><span class="nv">TMPDIR</span><span class="k">:-</span><span class="p">/tmp</span><span class="si">}</span><span class="s2">/rebuild-console.XXXXXX&#34;</span><span class="k">)</span><span class="p">;</span> <span class="nb">trap</span> <span class="s1">&#39;rm -f &#34;$ERR&#34;&#39;</span> EXIT
</span></span><span class="line"><span class="ln">19</span><span class="cl">ts<span class="o">()</span> <span class="o">{</span> date +%m-%d<span class="se">\ </span>%H:%M:%S<span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">say<span class="o">()</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="k">$(</span>ts<span class="k">)</span><span class="s2"> [</span><span class="nv">$1</span><span class="s2">] </span><span class="nv">$2</span><span class="s2">&#34;</span><span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">die<span class="o">()</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="k">$(</span>ts<span class="k">)</span><span class="s2"> [錯誤] </span><span class="nv">$1</span><span class="s2">&#34;</span> &gt;<span class="p">&amp;</span>2<span class="p">;</span> <span class="nb">exit</span> 2<span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># 查詢與寫入用的包裝：stderr 存進 $ERR 那個暫存檔，濾掉兩種不是結果的提示行（Python 的 SyntaxWarning，</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1">#   與 CLI 建議改用 `oci -i` 的 Interactive mode 那一行），結束碼照 oci 自己的回傳。</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1">#   CLI 的 --query 結果是空的時候 stdout 什麼都不印，所以空字串要搭配結束碼 0 才代表查無結果</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">q<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">  <span class="nb">local</span> out rc
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="nv">out</span><span class="o">=</span><span class="k">$(</span>oci <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span> 2&gt;<span class="s2">&#34;</span><span class="nv">$ERR</span><span class="s2">&#34;</span><span class="k">)</span><span class="p">;</span> <span class="nv">rc</span><span class="o">=</span><span class="nv">$?</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> grep -v -e SyntaxWarning -e <span class="s1">&#39;Interactive mode&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="k">return</span> <span class="nv">$rc</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c1"># q 失敗時停下來，印出 oci 寫在 stderr 的內容</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">fail<span class="o">()</span> <span class="o">{</span> die <span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="si">}</span><span class="s2">失敗：</span><span class="k">$(</span>tr <span class="s1">&#39;\n&#39;</span> <span class="s1">&#39; &#39;</span> &lt; <span class="s2">&#34;</span><span class="nv">$ERR</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span><span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">oci session validate --profile DEFAULT &gt;/dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="se"></span>  <span class="o">||</span> die <span class="s2">&#34;CLI session 過期，先跑 oci session authenticate --region </span><span class="nv">$REGION</span><span class="s2"> --profile-name DEFAULT&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="nv">IID</span><span class="o">=</span><span class="k">$(</span>q compute instance list -c <span class="s2">&#34;</span><span class="nv">$C</span><span class="s2">&#34;</span> --display-name <span class="s2">&#34;</span><span class="nv">$NAME</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="se"></span>  --query <span class="s2">&#34;data[?\&#34;lifecycle-state\&#34;==&#39;RUNNING&#39;] | [0].id&#34;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;查 RUNNING 的主機&#34;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.instance* <span class="o">]]</span> <span class="o">||</span> die <span class="s2">&#34;找不到 RUNNING 的 </span><span class="si">${</span><span class="nv">NAME</span><span class="si">}</span><span class="s2">，先跑 rebuild-ubuntu.sh&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1"># 序列主控台連線只收 RSA 公鑰</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="nv">NEWKEY</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="k">if</span> <span class="o">[[</span> ! -f <span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/console_rsa&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">  ssh-keygen -q -t rsa -b <span class="m">3072</span> -N <span class="s1">&#39;&#39;</span> -C verify-console -f <span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/console_rsa&#34;</span> <span class="o">||</span> die <span class="s2">&#34;產生 RSA 金鑰失敗&#34;</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">  <span class="nv">NEWKEY</span><span class="o">=</span>1<span class="p">;</span> say 新建 <span class="s2">&#34;RSA 金鑰 console_rsa&#34;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="k">else</span> say 已存在 <span class="s2">&#34;RSA 金鑰 console_rsa&#34;</span><span class="p">;</span> <span class="k">fi</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="nv">CID</span><span class="o">=</span><span class="k">$(</span>q compute instance-console-connection list -c <span class="s2">&#34;</span><span class="nv">$C</span><span class="s2">&#34;</span> --instance-id <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="se"></span>  --query <span class="s2">&#34;data[?\&#34;lifecycle-state\&#34;==&#39;ACTIVE&#39;] | [0].id&#34;</span> --raw-output<span class="k">)</span> <span class="o">||</span> fail <span class="s2">&#34;查序列主控台連線&#34;</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="c1"># 剛產生新金鑰時，既有的連線登記的是舊的公鑰，用新私鑰連不上：先刪掉它，再用新的公鑰建一條</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$NEWKEY</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="m">1</span> <span class="o">&amp;&amp;</span> <span class="s2">&#34;</span><span class="nv">$CID</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.instanceconsoleconnection* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">  q compute instance-console-connection delete --instance-console-connection-id <span class="s2">&#34;</span><span class="nv">$CID</span><span class="s2">&#34;</span> --force <span class="se">\
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="se"></span>    --wait-for-state DELETED &gt;/dev/null <span class="o">||</span> fail <span class="s2">&#34;刪掉用舊公鑰建的序列主控台連線&#34;</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">  say 改寫 <span class="s2">&#34;刪掉用舊公鑰建的序列主控台連線&#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">  <span class="nv">CID</span><span class="o">=</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$CID</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.instanceconsoleconnection* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span> say 已存在 <span class="s2">&#34;序列主控台連線&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">  <span class="nv">CID</span><span class="o">=</span><span class="k">$(</span>q compute instance-console-connection create --instance-id <span class="s2">&#34;</span><span class="nv">$IID</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="se"></span>    --ssh-public-key-file <span class="s2">&#34;</span><span class="nv">$D</span><span class="s2">/console_rsa.pub&#34;</span> --wait-for-state ACTIVE --query data.id --raw-output <span class="p">|</span> tail -1<span class="k">)</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="se"></span>    <span class="o">||</span> fail <span class="s2">&#34;建序列主控台連線&#34;</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">  <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$CID</span><span class="s2">&#34;</span> <span class="o">==</span> ocid1.instanceconsoleconnection* <span class="o">]]</span> <span class="o">||</span> die <span class="s2">&#34;建不出序列主控台連線：</span><span class="nv">$CID</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">  say 新建 <span class="s2">&#34;序列主控台連線&#34;</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">
</span></span><span class="line"><span class="ln">66</span><span class="cl">cat &gt; <span class="s2">&#34;</span><span class="nv">$CONF</span><span class="s2">&#34;</span> <span class="s">&lt;&lt;EOF
</span></span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="s"># 由 rebuild-console.sh 產生：$NAME 的序列主控台。官方連線字串的跳板改寫成 ProxyJump。
</span></span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="s">Host verify-console-gw
</span></span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="s">  HostName instance-console.$REGION.oci.oraclecloud.com
</span></span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="s">  Port 443
</span></span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="s">  User $CID
</span></span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="s">  IdentityFile $D/console_rsa
</span></span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="s">  IdentitiesOnly yes
</span></span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="s">  UserKnownHostsFile $KNOWN
</span></span></span><span class="line"><span class="ln">75</span><span class="cl"><span class="s">  StrictHostKeyChecking accept-new
</span></span></span><span class="line"><span class="ln">76</span><span class="cl"><span class="s">  HostKeyAlgorithms +ssh-rsa
</span></span></span><span class="line"><span class="ln">77</span><span class="cl"><span class="s">  PubkeyAcceptedAlgorithms +ssh-rsa
</span></span></span><span class="line"><span class="ln">78</span><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="ln">79</span><span class="cl"><span class="s">Host verify-console
</span></span></span><span class="line"><span class="ln">80</span><span class="cl"><span class="s">  HostName $IID
</span></span></span><span class="line"><span class="ln">81</span><span class="cl"><span class="s">  User $CID
</span></span></span><span class="line"><span class="ln">82</span><span class="cl"><span class="s">  ProxyJump verify-console-gw
</span></span></span><span class="line"><span class="ln">83</span><span class="cl"><span class="s">  IdentityFile $D/console_rsa
</span></span></span><span class="line"><span class="ln">84</span><span class="cl"><span class="s">  IdentitiesOnly yes
</span></span></span><span class="line"><span class="ln">85</span><span class="cl"><span class="s">  UserKnownHostsFile $KNOWN
</span></span></span><span class="line"><span class="ln">86</span><span class="cl"><span class="s">  StrictHostKeyChecking accept-new
</span></span></span><span class="line"><span class="ln">87</span><span class="cl"><span class="s">  HostKeyAlgorithms +ssh-rsa
</span></span></span><span class="line"><span class="ln">88</span><span class="cl"><span class="s">  PubkeyAcceptedAlgorithms +ssh-rsa
</span></span></span><span class="line"><span class="ln">89</span><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="ln">90</span><span class="cl">: &gt; <span class="s2">&#34;</span><span class="nv">$KNOWN</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">91</span><span class="cl">say 改寫 <span class="s2">&#34;</span><span class="nv">$CONF</span><span class="s2"> 與 known_hosts_console&#34;</span>
</span></span><span class="line"><span class="ln">92</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;連線：ssh -tt -F </span><span class="nv">$CONF</span><span class="s2"> verify-console（離開：先按 Enter，再輸入 ~.）&#34;</span>
</span></span><span class="line"><span class="ln">93</span><span class="cl"><span class="c1"># 序列主控台連線是救援入口，平時不留：排查完就刪</span>
</span></span><span class="line"><span class="ln">94</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;用完就刪：oci compute instance-console-connection delete --instance-console-connection-id </span><span class="nv">$CID</span><span class="s2"> --force --wait-for-state DELETED&#34;</span></span></span></code></pre></div><p><code>&lt;區域&gt;</code> 換成自己的區域識別碼；<code>ids.env</code> 只用到 <code>C</code>。這支腳本每次都清空 <code>known_hosts_console</code>，下一次連線時由 <code>StrictHostKeyChecking accept-new</code> 重新記下跳板與目的地的 host key；代價跟〈建 Ubuntu 24.04 主機與經由 port forwarding 登入〉那一段寫的相同，第一次連線沒有比對指紋。這份檔案不依主機名稱分檔，給不同主機跑時共用同一份。<code>console_rsa</code> 不存在而這次新產生時，主機上既有的 <code>ACTIVE</code> 連線登記的是舊的公鑰，新私鑰連不上，所以腳本先刪掉那條連線再用新公鑰建一條。對新建的 <code>verify-ubuntu-rebuild</code> 實跑的是前一版：訊息還寫「主控台連線」、還沒有錯誤處理與換金鑰處理，建連線的步驟跟現在相同。結果是 exit 0，15 秒，印「已存在」（RSA 金鑰）、「新建」（序列主控台連線）與「改寫」（設定檔）。照設定檔連上之後印出 Oracle 對序列主控台連線的說明文字；本機用 <code>alarm</code> 讓 ssh 在 25 秒後結束，這段時間裡沒有按 Enter，所以登入提示沒有出現。</p>
<p><strong>重建之後要重做的事</strong>，依順序：</p>
<ol>
<li><code>ssh -F ssh-verify-ubuntu.conf verify-ubuntu 'cloud-init status --wait; uptime'</code>（設定檔名依主機名稱）：等首次開機設定跑完（這次約 1 分鐘）。</li>
<li>主機被刪除重建時，映像檔又是建立那一天的版本：照〈加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG〉最後那兩條，<code>sudo unattended-upgrade</code> 之後 <code>sudo systemctl reboot</code>（這次 402 秒、升級 64 個套件），才把映像檔之後的安全更新補上。只是被停止再啟動時，更新還在，不必重做。</li>
<li>序列主控台連線平時不留，重建之後不必另外建。要排查時才跑序列主控台連線的重建腳本，再 <code>ssh -tt -F ssh-console.conf verify-console</code>；排查完用腳本結尾印出的那條 <code>delete</code> 刪掉。</li>
</ol>
<p><strong>耗時</strong>：這次量到的是單步的時間，建立請求送出到回傳約 4 秒，約 20 秒後的下一次查詢時主機已經是 <code>RUNNING</code>，cloud-init 約 1 分鐘，port forwarding session 約 1 分鐘，<code>unattended-upgrade</code> 402 秒，核心開始開機到登入提示約 27 秒。重建腳本在主機還在時 34 秒跑完，主機停止、停止中、啟動中三種狀態各 28、35、55 秒（差在各步 API 的回應與 port forwarding session 的快慢）；名稱不存在的那一次，那一版沒有等待主機的狀態，建立請求到 <code>RUNNING</code> 的確切時間沒有量到（2 分 30 秒後重跑時已經是 <code>RUNNING</code>），之後接上網路與登入的部分 80 秒，其中 port forwarding session 佔 64 秒；這一版從建主機那一步一次跑到底的時間沒有量。A1 容量不足時，建立那一步要重送，時間取決於區域當時的容量，重送的間隔與限流門檻在建主機那篇〈重送建立請求：CLI 自己的重送、限流門檻與重送腳本〉。</p>
<h3 id="重建的其他做法用-iac-描述這組資源建立時交給-cloud-init-升級只靠手動">重建的其他做法：用 IaC 描述這組資源、建立時交給 cloud-init 升級、只靠手動</h3>
<ul>
<li><strong>用 Terraform 描述主機與網路</strong>：主機被刪除之後 <code>terraform apply</code> 就重建，<code>plan</code> 也會列出被刪掉的資源；回收這種狀況剛好是 <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/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">Drift（設定漂移）</a>：宣告的狀態與實際不一致。代價是要先把已經用 CLI 建好的資源匯入 state（做法見 <a href="/blog/devops/infra/takeover/partial-iac-bulk-import/" data-link-title="Unmanaged Resource 批次 Import 工作流" data-link-desc="把 Terraform state 外的雲端資源有系統地納入 IaC 管理：優先序判斷、import block 語法、generated HCL 的 review 要點、批次策略與常見失敗處理">Unmanaged Resource 批次 Import 工作流</a>）；這次沒有做。</li>
<li><strong>建立時交給 cloud-init 升級套件</strong>：<code>oci compute instance launch</code> 的 <code>--user-data-file</code> 放一份 cloud-config，讓 cloud-init 在首次開機時就跑套件升級（cloud-config 的 <code>package_upgrade: true</code>），重建之後不必再手動跑 <code>unattended-upgrade</code>。前提是主機首次開機時已經有出站，所以要搭配上面〈出站的其他做法：security list、建主機時就掛 NSG、限縮出站的目的地與不加出站〉的「建主機時就掛上 NSG」。官方提醒初始化腳本裡不要放會觸發重開機的指令，否則可能讓主機建立失敗（<a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a>〈初始化腳本〉），所以 cloud-config 只升級、不重開機，換上新核心的那一次重開機留到登入之後手動做，或由重建腳本在主機變成 <code>RUNNING</code>、cloud-init 跑完之後再觸發。這一項沒有實跑。</li>
<li><strong>只靠手動照〈建 Ubuntu 24.04 主機與經由 port forwarding 登入〉與〈加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG〉兩節重做</strong>：步驟都在本篇，不需要腳本；代價是每一步的 OCID 要重查，漏掉 NSG 掛到新 VNIC 這一步時，主機看起來建好了，<code>apt-get update</code> 卻又回到逾時，而且沒有任何錯誤訊息指出原因。</li>
</ul>
<h2 id="費用與額度a1開機磁碟與區塊儲存額度出站流量與-nat-gateway">費用與額度：A1、開機磁碟與區塊儲存額度、出站流量與 NAT gateway</h2>
<ul>
<li>
<p><strong>A1</strong>：帳號篇記錄的 Always Free 額度是每月 1,500 OCPU 小時與 9,000 GB 小時（相當於 2 OCPU、12 GB 整月開著）。2026-10-26 讀到的 Oracle 的 Compute 價目頁（頁名這次沒有記下）寫的是「Each paid tenancy gets the first 3,000 OCPU hours and 18,000 GB hours per month for free」，對象是付費 tenancy，相當於 4 OCPU、24 GB 整月開著。這個 tenancy 是付費帳戶：重建腳本實跑之前，同一個 tenancy 已經有三台各 1 OCPU、6 GB 的 A1 主機在跑（2026-10-27 用 <code>oci compute instance list</code> 查到 <code>verify-a1-ol</code>、<code>verify-origin</code>、<code>verify-ubuntu</code> 三台是 <code>RUNNING</code>），腳本建出的第四台讓合計變成 4 OCPU、24 GB，剛好等於付費 tenancy 每月的免費量，建立沒有被擋下。試用結束那一刻、A1 合計超過 2 OCPU、12 GB 就全部停用的規則，只管沒有升級的帳號（帳號篇〈Always Free 的額度與它綁在 home region 上的範圍〉）；付費帳戶超出免費量的部分照一般價格計費（帳號篇〈超出 Always Free 額度時：試用中、未升級與付費帳戶各自的結果〉）。免費量是帳務的規則，能建出幾個 OCPU 由 compute 的 service limit 決定，兩者是分開的：查法跟下面開機磁碟那一項同一組，A1 的 OCPU 與記憶體的限制名稱是 <code>standard-a1-core-count</code> 與 <code>standard-a1-memory-count</code>，用 <code>oci limits resource-availability get -c &lt;tenancy 的 OCID&gt; --service-name compute --limit-name &lt;名稱&gt; --availability-domain &lt;可用網域&gt;</code> 查（2026-10-27 實跑，寫法見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/suspend-resume-drill/" data-link-title="Oracle Cloud 主機的暫停與恢復上線實測紀錄：暫停前的基準、收窄入口、停止主機、停止期間的計費與 Cloudflare 的回應、恢復上線的每一步與耗時，以及收尾時 DNS 紀錄與主機的刪除順序" data-link-desc="要讓 Oracle Cloud 上一台放在 Cloudflare 後面的主機停機一段時間再恢復上線、想知道停止之後公開 IP 留不留、停止期間 A1 與開機磁碟怎麼計費、Cloudflare 回什麼錯誤、恢復時每一步要多久，或服務要收掉時 DNS 紀錄與主機的刪除順序時，查實際跑過的指令、輸出與量到的時間">Oracle Cloud 主機的暫停與恢復上線實測紀錄：暫停前的基準、收窄入口、停止主機、停止期間的計費與 Cloudflare 的回應、恢復上線的每一步與耗時，以及收尾時 DNS 紀錄與主機的刪除順序</a>〈暫停前記下的基準：接聽的程式、服務、待裝的更新、憑證效期與公開 IP〉）；其他限制的名稱用 <code>oci limits definition list -c &lt;tenancy 的 OCID&gt; --service-name compute</code> 列（沒有實跑）。超出免費量時，停止主機對 OCPU 與記憶體費用的影響見 <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>〈暫停期間仍在計費的資源〉；停止的主機的開機磁碟照樣佔著區塊磁碟額度。</p>
</li>
<li>
<p><strong>開機磁碟</strong>：每台的開機磁碟從 Always Free 的 200 GB 區塊磁碟額度裡扣；放不進剩餘額度的那一顆整顆在額度外（下面「合計超過 200 GB 時」）。這顆磁碟有三種寫法：序列主控台記下的開機輸出寫 <code>[sda] 97677312 512-byte logical blocks: (50.0 GB/46.6 GiB)</code>；<code>oci bv boot-volume list</code> 對 <code>verify-ubuntu</code> 回 <code>size-in-mbs</code> 47694，正好是 97677312 × 512 位元組換成 MiB；同一條回的 <code>size-in-gbs</code> 是 47，是 46.6 GiB 進位。另外兩台 Oracle Linux 主機的 <code>size-in-mbs</code> 是 47719，換成 GiB 同樣是 46.6，<code>size-in-gbs</code> 同樣是 47。每顆磁碟的大小與額度的用量用下面三條查（2026-10-27 實跑）。<code>&lt;tenancy 的 OCID&gt;</code> 是最上層 compartment（root）的 OCID，<code>~/.oci/config</code> 裡 <code>tenancy=</code> 那一行就是它。這次的 compartment 直接建在 root 底下，所以 <code>oci iam compartment get --compartment-id &lt;compartment 的 OCID&gt; --query 'data.&quot;compartment-id&quot;' --raw-output</code> 印出的上一層也是 tenancy 的 OCID（實跑，跟 <code>tenancy=</code> 的值相同）；compartment 是巢狀的時候，這條印出的是另一個 compartment，要讀 <code>~/.oci/config</code>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 列出每顆還沒終止的開機磁碟與它的大小；&lt;可用網域&gt; 是建主機時用的那一個</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">oci bv boot-volume list -c &lt;compartment 的 OCID&gt; --availability-domain &lt;可用網域&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data[?&#34;lifecycle-state&#34;!=`TERMINATED`].{name:&#34;display-name&#34;,gb:&#34;size-in-gbs&#34;,mb:&#34;size-in-mbs&#34;}&#39;</span> --output table
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 列出區塊儲存的限制名稱；區域層的免費額度叫 total-free-storage-gb-regional，</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 可用網域層另有一個 total-free-storage-gb，這個 tenancy 兩者的上限都是 200。</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 付費 tenancy 合計超過 200 時兩者都沒有擋下建立，放不進的那顆磁碟不算進兩者的 used（下面「合計超過 200 GB 時」）</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">oci limits definition list -c &lt;tenancy 的 OCID&gt; --service-name block-storage --all <span class="se">\
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data[].{name:name,desc:description,scope:&#34;scope-type&#34;}&#39;</span> --output table
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 查用量：used 是已經扣掉的量，available 是還剩的量。</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 查可用網域層那一個時，--limit-name 換成 total-free-storage-gb，並加 --availability-domain &lt;可用網域&gt;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">oci limits resource-availability get -c &lt;tenancy 的 OCID&gt; --service-name block-storage <span class="se">\
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="se"></span>  --limit-name total-free-storage-gb-regional</span></span></code></pre></div><p>2026-10-27 終止 <code>verify-ubuntu-rebuild</code> 之後查，剩三台主機時回 <code>&quot;used&quot;: 141</code>、<code>&quot;available&quot;: 59</code>，可用網域層那一個查出來的數字相同。這個 tenancy 裡只有這一個 compartment（<code>oci iam compartment list -c &lt;tenancy 的 OCID&gt; --compartment-id-in-subtree true --all</code> 只列出它），它與 root 底下都沒有開機磁碟以外的區塊磁碟（兩處的 <code>oci bv volume list -c &lt;…的 OCID&gt;</code> 都列不出任何一顆，root 底下也沒有開機磁碟；都是實跑），而 141 正好是三顆 <code>size-in-gbs</code> 47 的合計；三顆的 MiB 加起來是 139.8 GiB，總和進位會是 140，所以進位是每一顆各自做的。由此推論額度扣的數字就是每顆磁碟的 <code>size-in-gbs</code>：照預設大小建的主機，每台扣 47（額度以 GB 標示，數值是 46.6 GiB 進位），不是 50。照這個推算，終止 <code>verify-ubuntu-rebuild</code> 之前四台同時存在時合計扣 188，額度還剩 12（四台同時存在時沒有查用量，那台照同一個映像檔與預設大小算 47）。</p>
<p><strong>合計超過 200 GB 時</strong>（2026-10-27 09:33 實跑）：在扣了 141 的狀態下，用重建腳本以 <code>BOOT_GB=60</code> 建 <code>verify-ubuntu-60g</code>（建出 60 GiB，<code>size-in-gbs</code> 60；這裡的 GB 數值上都是 GiB，見〈被回收之後的重建：怎麼發現、留下與消失的資源、重建腳本〉的 <code>BOOT_GB</code> 那一項），三顆 47 加一顆 60 合計 201。建立請求沒有被擋下，主機照常建好（當時這個 tenancy 沒有設 compartment quota；設了 quota 之後超額的建立被擋下，見下面「用 compartment quota 擋下超額的建立」）。之後免費額度的用量與全部區塊磁碟的合計（<code>total-storage-gb</code>，上面的指令沒有查的第三條限制）不再相同：</p>
<table>
  <thead>
      <tr>
          <th>限制</th>
          <th>範圍</th>
          <th>上限</th>
          <th>used</th>
          <th>代表的量</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>total-free-storage-gb-regional</code></td>
          <td>區域</td>
          <td>200</td>
          <td>141</td>
          <td>算進 Always Free 額度的磁碟合計</td>
      </tr>
      <tr>
          <td><code>total-free-storage-gb</code></td>
          <td>可用網域</td>
          <td>200</td>
          <td>141</td>
          <td>同上，以可用網域計</td>
      </tr>
      <tr>
          <td><code>total-storage-gb</code></td>
          <td>可用網域</td>
          <td>61,440</td>
          <td>201</td>
          <td>全部區塊磁碟的合計，含付費的磁碟</td>
      </tr>
  </tbody>
</table>
<p>表裡的可用網域是 availability domain，區域裡的一個資料中心（跨業者的概念見 <a href="/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone（可用區）</a> 卡）；主控台的 Scope 選單在這個區域只列出一個可用網域，兩條免費額度的數字因此相同（推論）。免費額度的 used 停在 141：60 GB 那一顆整顆算在免費額度之外。假如只有超出的 1 GB 在額度外，used 會是 200。主機 <code>RUNNING</code> 之後 4 分鐘起，每 2 分鐘查一次、共 6 次（約 10 分鐘），免費額度每次都是 141，而同一批查詢裡的 <code>total-storage-gb</code> 每次都是 201、已經算進這一顆，所以 141 不是還沒更新的值（推論）。Oracle 的 <a href="https://docs.oracle.com/en-us/iaas/Content/FreeTier/freetier_topic-Always_Free_Resources.htm">Always Free Resources</a> 頁（2026-10-27 讀）對區塊磁碟的寫法跟這個結果一致：它舉的例子是四顆 50 GB 的 Always Free 磁碟加一顆 50 GB 的付費磁碟，刪掉其中一顆 Always Free 磁碟、空出額度之後，服務會自動把那顆付費磁碟轉成 Always Free（原文「When you have available Always Free capacity, Block Volume automatically tries to transition an existing paid resource to an Always Free resource」）。原文描述的轉換對象是整顆資源（paid resource），所以免費與否以整顆判定（從這一句推得，頁面沒有逐字寫；例子裡的剩餘額度是 0，單看例子分不出整顆與超出量兩種算法）。照這一句，這次的狀態下刪掉一顆 47 的免費磁碟，剩餘額度從 59 變成 106，放得下 60，這顆就可能轉成免費；同一天下午實測了這個轉換：轉成免費的是線上加大到 60 GB 而移出額度的那一顆開機磁碟，轉換發生在空出額度之後 26 到 31 分鐘；同時另建、一開始就在額度外的 50 GB 資料磁碟放不進剩下的額度，沒有轉換，所以建立時就在額度外的磁碟要等多久沒有量到（下面「付費磁碟自動轉成 Always Free」）。這次被判在額度外的是建立時放不進的那一顆；之後刪掉磁碟、重新分配時會選哪一顆轉成免費，頁面沒有寫。同一頁也寫了「Although it is possible to mix paid and Always Free resources, Oracle does not recommend this」，所以合計快到 200 時，先刪掉不用的舊磁碟再建，比讓一顆磁碟落到額度外單純。帳務明細也是整顆計費（2026-10-27 實跑）。用 usage API 依 <code>skuName</code>、<code>skuPartNumber</code>、<code>resourceId</code> 分組、逐小時（<code>--granularity HOURLY</code>）讀，這顆磁碟在 UTC 02 時那一小時記成兩個付費的項目：<code>Block Volume - Storage</code>（數量 0.0806）與 <code>Block Volume - Performance Units</code>（數量 0.806）。0.0806 正好是 60 GB 乘上 1 小時、除以一個月 744 小時，代表整顆 60 GB 都照價計費，不是只有超出的 1 GB；0.806 是同一顆磁碟的 60 GB 乘上 Balanced 等級的每 GB 10 個效能單位（VPU）再除以 744。同一小時其他三顆免費的開機磁碟記在 <code>Block Volume - Free</code>，數量各 0.0632（47 ÷ 744），金額 0，而且沒有效能單位的項目。兩個付費項目一小時合計約 0.0047，幣別是這個帳戶的 SGD（新加坡幣）。所以合計快到 200 時，下一顆放不進剩餘額度的磁碟是整顆照價計費：剩 59 時建一顆 60 GB，付費的是 60 GB 不是 1 GB。</p>
<p>這筆項目出現的時間也量到了：磁碟在 UTC 01:33 建立，01 時那一小時的明細裡沒有它（那一小時只存在 27 分鐘；之後有沒有補上，這次沒有再查）；02 時那一小時的明細，UTC 03:27 查還沒有，03:57 查已經有了，也就是那一小時結束後 1 到 2 小時之間出現。每 30 分鐘查一次時，查到的最新一筆依序是 00、01、01、02 時那一小時，逐小時明細大約落後 2 小時。</p>
<p><strong>用 compartment quota 擋下超額的建立</strong>（2026-10-27 13:06 實跑）：付費帳戶合計超過 200 GB 時建立請求照常通過，要讓超額的建立被擋下，可以在 compartment 上設 quota。quota 是 policy，建在 tenancy（root）上、指名要限制的 compartment；這次限制的是區塊儲存的合計與 A1 的核心數，兩者都以可用網域為單位，所以用 <code>where request.ad</code> 指定可用網域：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># &lt;compartment 的名稱&gt; 是名稱不是 OCID；&lt;可用網域&gt; 是建主機時用的那一個，這個區域只有一個</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci limits quota create --compartment-id &lt;tenancy 的 OCID&gt; --name verify-quota <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --description <span class="s2">&#34;cap block storage and A1 cores&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --statements <span class="s2">&#34;[\&#34;set block-storage quota total-storage-gb to 200 in compartment &lt;compartment 的名稱&gt; where request.ad = &#39;&lt;可用網域&gt;&#39;\&#34;, \&#34;set compute-core quota standard-a1-core-count to 4 in compartment &lt;compartment 的名稱&gt; where request.ad = &#39;&lt;可用網域&gt;&#39;\&#34;]&#34;</span></span></span></code></pre></div><p>回應的 <code>lifecycle-state</code> 是 <code>ACTIVE</code>。當時免費區塊儲存扣了 188（四顆 47）、A1 用了 4 OCPU（四台各 1），A1 已經到 4 的上限，區塊儲存剩 12、放不進 50 GB：</p>
<ul>
<li><strong>區塊儲存</strong>：建立後約 35 秒，<code>oci bv volume create -c &lt;compartment 的 OCID&gt; --availability-domain &lt;可用網域&gt; --display-name verify-data-50 --size-in-gbs 50</code> 建一顆 50 GB 的資料磁碟（合計會變成 238），回 <code>400</code>，<code>code</code> 是 <code>LimitExceeded</code>，訊息是 <code>You have reached your service limit in this Availability Domain for volumes. …</code>。Oracle 的 <a href="https://docs.oracle.com/en-us/iaas/Content/API/References/apierrors.htm">API Errors</a> 頁（2026-10-27 讀）把兩者分開：超過管理員設定的 compartment quota 是 <code>400 QuotaExceeded</code>，超過 Oracle 設定的 tenancy 上限是 <code>400 LimitExceeded</code>。這次區塊儲存那一條實際回的是 <code>LimitExceeded</code>，訊息也寫成 service limit，沒有提到 quota；<code>total-storage-gb</code> 的 service limit 是 61,440，用量只有 188，所以擋下它的是 quota。拿掉 quota 之後同一條指令建立成功（下一項），也對得上這個判讀。只看回應碼與訊息的排查會往 service limit 去查，查到的是用量遠低於上限。</li>
<li><strong>A1 核心數</strong>：重建腳本建第五台（<code>verify-ubuntu-q</code>），建立請求回 <code>400</code>，訊息直接寫出被超過的 quota：<code>The following compartment quotas were exceeded: standard-a1-core-count in policy '&lt;quota 的 OCID&gt;' by 1.</code>。重建腳本只重送 500 與 429，回 400 就不重送、以 4 結束，<code>instance list</code> 查不到這台主機。</li>
</ul>
<p>quota 用 <code>oci limits quota delete --quota-id &lt;quota 的 OCID&gt; --force</code> 刪掉（<code>&lt;quota 的 OCID&gt;</code> 是 <code>quota create</code> 回應裡的 <code>data.id</code>，或用 <code>oci limits quota list -c &lt;tenancy 的 OCID&gt;</code> 查），<code>quota get</code> 回 <code>NotAuthorizedOrNotFound</code>。這兩條 quota 擋得住的範圍比免費量窄，有四個缺口：</p>
<ul>
<li><strong>記憶體</strong>：A1 的免費量是 4 OCPU 與 24 GB 兩項，這次只限了核心數，沒有限 <code>standard-a1-memory-count</code>；1 OCPU 配 32 GB 的 A1 會通過 quota 而超出記憶體的免費量。要擋，再加一條 <code>set compute-memory quota standard-a1-memory-count to 24 …</code>（家族名稱與寫法照 Quota Policy Syntax 推，沒有實跑）。</li>
<li><strong>其他 compartment</strong>：quota 寫的是 <code>in compartment &lt;名稱&gt;</code>，只管那一個 compartment；免費量是整個 tenancy 一份，root 或別的 compartment 裡建的磁碟與主機不受這兩條限制。<a href="https://docs.oracle.com/en-us/iaas/Content/Quotas/Concepts/quota_policy_syntax.htm">Quota Policy Syntax</a> 的範例有 <code>in tenancy</code> 的寫法（2026-10-27 讀，沒有實跑）。</li>
<li><strong>多個可用網域</strong>：同一頁寫明可用網域層的 quota「is allocated to each AD」，每個可用網域各有一份 200；這個區域只有一個可用網域，所以這次的 200 就是全區的 200。可用網域有好幾個的區域，每一個都要用 <code>where request.ad</code> 各設一條，加起來不超過 200。</li>
<li><strong>加大磁碟</strong>：下一段線上加大是在拿掉 quota 之後做的，quota 擋不擋 <code>boot-volume update</code> 這次沒有測。</li>
</ul>
<p>在這幾個範圍裡，建立請求在超出免費量的那一刻就被拒，不會像上面那顆 60 GB 一樣整顆照價計費；代價是要建更大的磁碟時得先改 quota。quota 剛建好之後多久生效沒有量，這次建立後約 35 秒送出的請求已經被擋下。quota 的上限怎麼依最大花費速度訂，在 <a href="/blog/devops/operations/08-cost-management/spend-planning-before-renting/" data-link-title="租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層" data-link-desc="租用雲端主機或把試用帳號升級成付費帳戶之前，費用失控時從觸發到停止之間的金額怎麼從配額與訊號延遲推出來，再用它推扣款的信用卡額度、配額、預算警示與觸發處置的門檻；Oracle Cloud、Google Cloud 與 AWS 的扣款時點、預先授權、扣款失敗的後果、擋下建立與停止用量的機制各自涵蓋什麼，以及停損停到哪一層的判斷">租用雲端主機之前的費用規劃：費用訊號的延遲、停止之前的最大超支、扣款的信用卡額度、配額與預算門檻的計算，以及停損停到哪一層</a>〈平台上的上限：擋下建立、停止用量與只發通知〉。</p>
<p><strong>線上加大開機磁碟與另建資料磁碟，合計超過 200 GB 時</strong>（2026-10-27 13:08 實跑）：拿掉 quota 之後，在免費區塊儲存扣了 188 的狀態下做了兩件事。磁碟只能加大、不能縮小（Oracle 的 <a href="https://docs.oracle.com/en-us/iaas/Content/Block/Tasks/resizingavolume.htm">Resizing a Volume</a> 頁：「You can only increase the size of the volume. You cannot decrease the size.」，2026-10-27 讀），所以合計接近 200 時加大的那一顆移出免費額度之後，要回到免費只能等別處空出放得下它的額度，或另建一顆小的磁碟搬資料：</p>
<table>
  <thead>
      <tr>
          <th>動作</th>
          <th>免費額度的 used（兩條相同）</th>
          <th><code>total-storage-gb</code> 的 used</th>
          <th>讀法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>建 50 GB 的資料磁碟（<code>oci bv volume create … --size-in-gbs 50</code>）</td>
          <td>188</td>
          <td>238</td>
          <td>50 GB 整顆在免費額度外</td>
      </tr>
      <tr>
          <td><code>verify-ubuntu</code> 的開機磁碟從 47 線上加大到 60</td>
          <td>141</td>
          <td>251</td>
          <td>加大的那一顆整顆移出免費額度，不是只有超出的 13 GB</td>
      </tr>
  </tbody>
</table>
<p>開機磁碟的 OCID 用 <code>oci compute boot-volume-attachment list -c &lt;compartment 的 OCID&gt; --availability-domain &lt;可用網域&gt; --instance-id &lt;主機的 OCID&gt; --query 'data[0].&quot;boot-volume-id&quot;' --raw-output</code> 查。加大用 <code>oci bv boot-volume update --boot-volume-id &lt;開機磁碟的 OCID&gt; --size-in-gbs 60 --wait-for-state AVAILABLE</code>，主機開著時 34 秒回 <code>AVAILABLE</code>。另一種做法是先停止主機再加大（離線加大）：不必 rescan，啟動時作業系統直接讀到新的大小，代價是停機（依 Oracle 的磁碟加大文件推論，沒有實跑，也沒有比較兩種做法對免費額度的判定）。加大之前那顆 47 是免費的，加大到 60 之後，另外三顆 47（141）加上這顆 60，合計 201、超過 200，結果是那一顆整顆從免費額度裡拿出來（188 減 47），跟建立時放不進剩餘額度的情形相同。主機裡的作業系統要 rescan 才看到新的大小：加大之後 <code>lsblk -b</code> 讀到的仍是 50010783744 位元組，<code>echo 1 | sudo tee /sys/class/block/sda/device/rescan</code>（<code>sda</code> 是這台主機的開機磁碟名稱，自己的主機用 <code>lsblk</code> 確認）之後變成 64424509440（60 GiB），<code>dmesg</code> 印 <code>sda: detected capacity change from 97677312 to 125829120</code>；分割區與檔案系統的擴大這次沒有做（根目錄仍是 45G）。</p>
<p><strong>付費磁碟自動轉成 Always Free</strong>（2026-10-27 13:11 實跑）：上表之後，終止 <code>verify-ubuntu-nb</code>、刪掉它的 47 GB 開機磁碟（work request 65 秒 <code>SUCCEEDED</code>），免費額度從 141 降到 94，<code>total-storage-gb</code> 從 251 降到 204。之後每 5 分鐘讀一次兩個用量，共 90 分鐘：</p>
<table>
  <thead>
      <tr>
          <th>時刻（UTC）</th>
          <th>免費額度的 used</th>
          <th><code>total-storage-gb</code> 的 used</th>
          <th>讀法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>05:12:55</td>
          <td>94</td>
          <td>204</td>
          <td>終止之後立刻讀；60 GB 與 50 GB 兩顆都還在免費額度外</td>
      </tr>
      <tr>
          <td>05:38:45</td>
          <td>94</td>
          <td>204</td>
          <td>終止之後 26 分鐘，還沒轉換</td>
      </tr>
      <tr>
          <td>05:43:50</td>
          <td>154</td>
          <td>204</td>
          <td>終止之後 31 分鐘，多出的 60 是加大的那顆開機磁碟</td>
      </tr>
      <tr>
          <td>06:39:40</td>
          <td>154</td>
          <td>204</td>
          <td>之後每次都相同；50 GB 那顆放不進（154 加 50 是 204）</td>
      </tr>
  </tbody>
</table>
<p>帳務明細對得上這個轉換。查法是下面 NAT gateway 那一項的 <code>usage-api</code> 指令改成逐小時、看用量，並依資源分組（2026-10-27 實跑）：</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"># 起訖填要看的 UTC 整點；res 欄對回磁碟的 OCID</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci usage-api usage-summary request-summarized-usages --tenant-id &lt;tenancy 的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --time-usage-started 2026-10-27T04:00:00Z --time-usage-ended 2026-10-27T09:00:00Z <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --granularity HOURLY --query-type USAGE --group-by <span class="s1">&#39;[&#34;skuName&#34;,&#34;skuPartNumber&#34;,&#34;resourceId&#34;]&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data.items[].{hour:&#34;time-usage-started&#34;,sku:&#34;sku-name&#34;,res:&#34;resource-id&#34;,qty:&#34;computed-quantity&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --output table</span></span></code></pre></div><p>加大的那顆開機磁碟在 UTC 05 時那一小時分成兩段記：<code>Block Volume - Storage</code> 0.0394 與 <code>Block Volume - Performance Units</code> 0.3944（60 GB 付費約 29 分鐘），加上 <code>Block Volume - Free</code> 0.0384（加大之前的 47 GB 約 10 分鐘，加上轉換之後的 60 GB 約 21 分鐘）；換算回去，轉換大約在 05:39，跟用量在 05:38 到 05:43 之間跳動一致。06 時整小時記成 <code>Block Volume - Free</code> 0.0806（60 ÷ 744），沒有付費項目。50 GB 的資料磁碟 05 時記 <code>Block Volume - Storage</code> 0.0572 與 <code>Performance Units</code> 0.5725（建立之後約 51 分鐘）、06 時 0.0672 與 0.672，一直是付費。</p>
<p>所以空出額度之後，付費磁碟會自動轉成免費，但不是當下：這次等了 26 到 31 分鐘，這段時間照付費計。剩餘額度放不下的那一顆不轉換。轉換沒有任何通知，要自己讀免費額度的 used 確認；哪一顆還在額度外，用上面那條逐小時的查詢看那顆磁碟記在 <code>Block Volume - Storage</code> 還是 <code>Block Volume - Free</code>。兩顆都放得下時選哪一顆先轉、轉換的延遲是否固定，這次沒有量。</p>
<p><code>total-storage-gb</code> 要帶 <code>--availability-domain</code> 查，寫法跟上面查可用網域層免費額度那一條相同，只換 <code>--limit-name</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 全部區塊磁碟的合計（含付費的磁碟）；跟免費額度的 used 相減，就是不在免費額度裡的 GB 數</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci limits resource-availability get -c &lt;tenancy 的 OCID&gt; --service-name block-storage <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --limit-name total-storage-gb --availability-domain &lt;可用網域&gt;</span></span></code></pre></div><p><strong>網頁主控台的同一組數字</strong>：Governance &amp; Administration → Tenancy Management → Limits, quotas and usage 頁（路徑在 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/console-navigation/" data-link-title="Oracle Cloud 主控台導覽：頂端列、導覽選單的分類、清單頁的 compartment 篩選器與常用頁面的路徑" data-link-desc="Oracle Cloud 主控台頂端列各個按鈕（導覽選單、搜尋、區域、Cloud Shell、人像選單）做什麼，導覽選單各分類底下有哪些項目，清單頁的 compartment 篩選器，以及建立主機、網路、Bastion、預算、稽核紀錄、配額這些常用頁面在選單上的實際路徑">Oracle Cloud 主控台導覽：頂端列、導覽選單的分類、清單頁的 compartment 篩選器與常用頁面的路徑</a>），篩選條件的 Service 選 Block Volume 之後，2026-10-27 10 時前後讀到的數字跟 CLI 相同。這次開啟時篩選條件的 Scope 是區域（<code>ap-batam-1</code>，沒有試過換瀏覽器或換使用者時是否相同），這時只列出 <code>total-free-storage-gb-regional</code>（頁上的描述是 Free Tier Volume Quota (GB) in the Region）與兩項備份數量的限制（<code>backup-count</code>、<code>free-backup-count</code>），前者的 Service Limit、Usage、Available 三欄分別顯示 200、141、59；全部區塊磁碟的合計要把 Scope 換成可用網域才列出來，<code>total-free-storage-gb</code>（Free Volume Size (GB)）顯示 200、141、59，<code>total-storage-gb</code>（Volume Size (GB)）顯示 61,440、201、61,239。只看區域範圍時，看不到不在免費額度裡的那 60 GB。</p>
</li>
<li>
<p><strong>出站流量</strong>：每月前 10 TB 免費（Oracle〈Virtual Cloud Network Pricing〉），這台只拿來下載套件。</p>
</li>
<li>
<p><strong>NAT gateway</strong>：Oracle 的 <a href="https://www.oracle.com/cloud/price-list/">Cloud Price List</a>（2026-10-27 讀）在 Networking 那一節只列入站流量與出站流量，沒有 NAT gateway 這一項；<a href="https://www.oracle.com/cloud/networking/virtual-cloud-network/pricing/">Virtual Cloud Network Pricing</a> 頁同樣只寫區域內的流量、對外的出站流量、Dedicated connectivity 與 site-to-site VPN。實際的帳務用 usage API 依服務與 SKU 分組讀（2026-10-27 實跑）。usage API 讀的是網頁主控台 Cost Analysis 頁（Billing &amp; Cost Management → Cost Management → Cost Analysis）背後同一份用量資料，CLI 的服務名是 <code>usage-api</code>；SKU 是帳務明細上的計費品項，例如 <code>Standard - A1</code>。<code>--query</code> 收的是 JMESPath 運算式，上一項 <code>boot-volume list</code> 的篩選也是：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># COST 換成 USAGE 時印的是用量。要看每一項記在哪個資源上，--group-by 加 &#34;resourceId&#34;，</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># --query 的投影也要加 res:&#34;resource-id&#34;，否則表格裡不會多出資源那一欄（兩處都改過的版本實跑過）</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 起訖填要看的那幾天（UTC），這次只用過日界的整點</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># tenancy 的 OCID 跟上一項 oci limits 用的是同一個。這次 CLI 設定的區域就是 home region，</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 沒有試從其他區域呼叫 usage API 的結果</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">oci usage-api usage-summary request-summarized-usages --tenant-id &lt;tenancy 的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="se"></span>  --time-usage-started 2026-10-24T00:00:00Z --time-usage-ended 2026-10-27T00:00:00Z <span class="se">\
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="se"></span>  --granularity DAILY --query-type COST --group-by <span class="s1">&#39;[&#34;service&#34;,&#34;skuName&#34;]&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data.items[].{day:&#34;time-usage-started&#34;,service:service,sku:&#34;sku-name&#34;,cost:&#34;computed-amount&#34;,qty:&#34;computed-quantity&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="se"></span>  --output table</span></span></code></pre></div><p><code>verify-nat</code> 在 2026-10-26 13:18（UTC）建立，同一天 19:11（UTC，台北時間 10-27 03:11）前後刪除，存在約 6 小時，整段都落在明細的 2026-10-26 那一天裡（區間延長到 10-28 重查，明細也只到 10-26）。那一天的明細只有四項：Compute 的 <code>Standard - A1</code> 與 <code>Standard - A1 - Memory</code>、Block Storage 的 <code>Block Volume - Free</code>、Virtual Cloud Network 的 <code>Outbound Data Transfer Zone 2</code>，金額都是 0，沒有 NAT gateway 的項目。出站流量那一項以 <code>resourceId</code> 分組時有四筆，都記在 VNIC 上；用 <code>oci network vnic get</code> 對回去，其中一筆是 <code>verify-ubuntu</code> 的 VNIC（它只經 NAT gateway 出站），一筆是有公開 IP 的 <code>verify-origin</code>，另外兩筆的 VNIC 已經隨主機刪掉、查不到。所以經 NAT gateway 出去的流量記在送出流量的那台主機的 VNIC 上、照出站流量計，算進每月 10 TB 的免費量（從這一天的明細推論）。<code>verify-ubuntu</code> 那一筆是一整天的合計，NAT gateway 建立之前它也經 service gateway 送封包到 Oracle 服務網路，這次沒有把兩段分開，所以沒有確認這一筆裡經 NAT 的流量佔多少，也沒有確認 service gateway 那段是否計入出站流量。這只涵蓋 NAT gateway 存在的那約 6 小時；查詢是在 2026-10-26 19:3x（UTC）做的，那一天還沒結束；逐小時的明細大約落後 2 小時（上面開機磁碟那一項 2026-10-27 量到的），所以那一天最後幾小時的明細當時還沒出來，可能還不完整。<code>verify-nat</code> 已經刪除（〈刪除這組資源的順序〉），沒有讀完一個帳單週期的 Cost Analysis；隔幾天用同一條查詢、起訖涵蓋 10-26 重查一次，就能確認那一天的明細補齊之後有沒有多出 NAT gateway 的項目。<code>Outbound Data Transfer Zone 2</code> 的 Zone 是帳務替出站流量分的區；價目表依流量的出發地分成北美歐洲英國、亞太日本南美、中東非洲三組，三組的前 10 TB 都免費，Zone 2 對應哪一組這次沒有查。</p>
<p>所以就兩份價目頁與建立當天的明細來看，NAT gateway 本身沒有收費項目，開一個 NAT gateway 多出的費用只有經它出去的流量，而流量照一般的出站流量計、先扣每月 10 TB 的免費量。這是推論：證據只涵蓋約 6 小時、而且那一天的明細可能還不完整，也沒有讀完一個帳單週期；這次也沒有查 Oracle 的 NAT gateway 文件是否明文寫了收費規則。</p>
</li>
</ul>
<h2 id="刪除這組資源的順序">刪除這組資源的順序</h2>
<p>順序跟建立相反，依賴有兩組，兩組之間互不依賴：NSG 要先從 VNIC 拿掉才刪得掉，NAT gateway 要先不再被路由表指著才刪得掉；終止主機時 VNIC 與序列主控台連線跟著消失。指令裡要填的值的來源：<code>&lt;NAT gateway 的 OCID&gt;</code> 與 <code>&lt;NSG 的 OCID&gt;</code> 是 <code>ids.env</code> 的 <code>NAT</code> 與 <code>NSGU</code>，同一個鍵有好幾行時取最後一行，<code>source ids.env</code> 之後用 <code>$NAT</code>、<code>$NSGU</code> 拿到的就是最後一行的值（重建腳本跑過一次就會記進去；沒跑過的話，用 <code>oci network nat-gateway list -c &lt;compartment 的 OCID&gt; --vcn-id &lt;VCN 的 OCID&gt; --display-name verify-nat --query 'data[0].id' --raw-output</code> 與同樣寫法的 <code>oci network nsg list … --display-name verify-ubuntu-egress</code> 查，兩條沒有實跑），<code>&lt;Oracle 服務網路的目的地名稱&gt;</code> 是 <code>SGCIDR</code>，<code>&lt;路由表的 OCID&gt;</code> 與 <code>&lt;service gateway 的 OCID&gt;</code> 是 <code>RT</code> 與 <code>SG</code>；<code>&lt;VNIC 的 OCID&gt;</code> 用 <code>oci compute instance list-vnics --instance-id &lt;主機的 OCID&gt; --query 'data[0].id' --raw-output</code> 查，<code>&lt;主機的 OCID&gt;</code> 是 <code>host-&lt;名稱&gt;.env</code> 最後一行的 <code>IID</code>（沒有這個檔時，用〈被回收之後的重建：怎麼發現、留下與消失的資源、重建腳本〉的 <code>instance list</code> 查），<code>&lt;連線的 OCID&gt;</code> 用〈序列主控台：RSA 金鑰、建立連線、ProxyJump 設定與看到的開機輸出〉的 <code>instance-console-connection list</code> 查（把 <code>--query</code> 換成 <code>'data[0].id'</code>）。</p>
<p>主機也要一起刪時，先做終止主機那一步（第 6 步），再刪 NSG、改路由表、刪 NAT gateway：VNIC 隨主機一起消失，序列主控台連線變成 <code>DELETED</code>，所以從 VNIC 拿掉 NSG 那一步（第 1 步）與刪序列主控台連線那一步（第 5 步）可以省略。留著主機、只收掉出站設定時，做從 VNIC 拿掉 NSG 到刪 NAT gateway 那四步（第 1 到第 4 步）；排查到一半、序列主控台連線還在時，再加刪連線那一步（第 5 步）。第 1 到第 4 步帶的 <code>--wait-for-state</code> 讓 CLI 等到這一步的資源回到 <code>AVAILABLE</code> 或變成 <code>TERMINATED</code> 才回傳，同一組依賴裡，後一步的刪除依賴前一步已經生效（NSG 要先從 VNIC 拿掉才刪得掉，路由表要先不再指著 NAT gateway 它才刪得掉）；2026-10-26 那一次刪除沒有帶，這次帶了，兩種寫法在同一組資源上的差別沒有比較：</p>
<ol>
<li>把 NSG 從主機的 VNIC 拿掉：<code>oci network vnic update --vnic-id &lt;VNIC 的 OCID&gt; --nsg-ids '[]' --force --wait-for-state AVAILABLE</code>。<code>--nsg-ids</code> 是整份替換，先用 <code>oci network vnic get --vnic-id &lt;VNIC 的 OCID&gt; --query 'data.&quot;nsg-ids&quot;'</code> 列出 VNIC 現在掛著的 NSG（2026-10-27 實跑，只列出 <code>verify-ubuntu-egress</code> 一個）；清單裡還有別的 NSG 時，<code>'[]'</code> 換成去掉 <code>&lt;NSG 的 OCID&gt;</code> 之後的清單，否則別的 NSG 會一起被拿掉。NSG 還掛在 VNIC 上時刪不掉：2026-10-27 先試刪 <code>verify-ubuntu-egress</code>，<code>nsg delete</code> 回 <code>412 PreconditionFailed</code>，訊息是 <code>cannot be deleted since it still has vnics attached to it</code>；2026-10-26 刪另一組實驗資源的 NSG 時也是同一個回應。</li>
<li>刪 NSG：<code>oci network nsg delete --nsg-id &lt;NSG 的 OCID&gt; --force --wait-for-state TERMINATED</code>。</li>
<li>路由表改回只有 service gateway 那一條：<code>oci network route-table update --rt-id &lt;路由表的 OCID&gt; --force --wait-for-state AVAILABLE --route-rules '[{&quot;destination&quot;:&quot;&lt;Oracle 服務網路的目的地名稱&gt;&quot;,&quot;destinationType&quot;:&quot;SERVICE_CIDR_BLOCK&quot;,&quot;networkEntityId&quot;:&quot;&lt;service gateway 的 OCID&gt;&quot;}]'</code>。路由表還指著 NAT gateway 時，NAT gateway 刪不掉：2026-10-27 在改路由表之前先試刪 <code>verify-nat</code>，<code>nat-gateway delete</code> 回 <code>400 InvalidParameter</code>，訊息裡包著內層的 <code>409, Conflict</code>：<code>The Route Table references the NAT_GATEWAY &lt;NAT gateway 的 OCID&gt;. You must remove the reference to proceed with this operation.</code>。外層的回應碼是 400 而不是 409，寫腳本或排查的人只看回應碼時，會把它讀成參數錯誤，要讀到 <code>message</code> 裡的那一句才知道是路由表的引用。<code>--route-rules</code> 是整份替換，路由表裡還有 service gateway 與 0.0.0.0/0 以外的規則時，要把它們一起寫進清單，否則會跟著被刪掉；先用〈加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG〉的 <code>route-table get</code> 列出現有的規則。</li>
<li>刪 NAT gateway：<code>oci network nat-gateway delete --nat-gateway-id &lt;NAT gateway 的 OCID&gt; --force --wait-for-state TERMINATED</code>。</li>
<li>刪序列主控台連線：<code>oci compute instance-console-connection delete --instance-console-connection-id &lt;連線的 OCID&gt; --force --wait-for-state DELETED</code>。連線平時不留（〈序列主控台：RSA 金鑰、建立連線、ProxyJump 設定與看到的開機輸出〉），所以只有排查到一半、連線還在的時候才需要這一步。這一條的 <code>--wait-for-state</code> 收的是連線本身的狀態（2026-10-27 實跑，10 秒後 <code>DELETED</code>），跟終止主機那一步收 work request 狀態的 <code>terminate</code> 不同。主機終止時連線也會跟著變成 <code>DELETED</code>（終止主機那一步實跑時看到的）。</li>
<li>終止主機並刪掉開機磁碟：<code>oci compute instance terminate --instance-id &lt;主機的 OCID&gt; --preserve-boot-volume false --force --wait-for-state SUCCEEDED --wait-for-state FAILED</code>。<code>terminate --help</code> 列出 <code>--wait-for-state</code> 只接受 work request 的四種狀態（<code>ACCEPTED</code>、<code>IN_PROGRESS</code>、<code>FAILED</code>、<code>SUCCEEDED</code>），所以寫成主機的 <code>TERMINATED</code> 時，CLI 會以參數錯誤結束、請求不會送出（2026-10-27 實跑：CLI 印 <code>Invalid value for '--wait-for-state': invalid choice: TERMINATED.</code>、以 2 結束）。</li>
</ol>
<p>2026-10-27 對重建腳本建出的 <code>verify-ubuntu-rebuild</code> 實跑了終止主機那一步：work request 66 秒後 <code>SUCCEEDED</code>，主機變成 <code>TERMINATED</code>，那台的序列主控台連線變成 <code>DELETED</code>；用 <code>oci bv boot-volume list</code> 加上 <code>lifecycle-state != TERMINATED</code> 的篩選列出開機磁碟時，清單裡已經沒有那顆磁碟。<a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入</a>（建主機那篇）〈刪除這組資源的順序〉寫的是開機磁碟的狀態變成 <code>TERMINATED</code>，篩掉 <code>TERMINATED</code> 之後就不在清單裡，兩者不衝突。</p>
<p>同一天留著 <code>verify-ubuntu</code>、照從 VNIC 拿掉 NSG 到刪 NAT gateway 那四步（第 1 到第 4 步）收掉它的出站設定（實跑）：四條各在 1 到 2 秒內結束，結束碼都是 0；之後 <code>nat-gateway get</code> 與 <code>nsg get</code> 都回 <code>404 NotAuthorizedOrNotFound</code>，兩者的 <code>list</code> 也已經列不出來，路由表剩 service gateway 那一條。主機上的 <code>apt-get update</code> 回到〈只接 service gateway 的私有子網路：apt 連到套件來源時的錯誤原文與成因〉那一節的錯誤：IPv6 位址印 <code>Network is unreachable</code>，IPv4 位址印 <code>connection timed out</code>。刪掉之後，<code>ids.env</code> 的 <code>NAT</code> 與 <code>NSGU</code> 指向已經不存在的資源；重建腳本依名稱查 NAT gateway 與 NSG，查不到 <code>AVAILABLE</code> 的同名資源就重建並記下新的 OCID，所以要恢復出站時重跑重建腳本：同一天上午重跑過一次，32 秒重建了 NAT gateway、NSG 與路由，出站恢復（〈被回收之後的重建：怎麼發現、留下與消失的資源、重建腳本〉「NAT gateway 與 NSG 都不存在」那一項）。新的 OCID 以新的一行接在 <code>ids.env</code> 檔尾，舊的那一行還在；<code>source</code> 時後面那一行蓋過前面的，手動抄值時取最後一行。</p>
<p><strong>只暫停出站、不刪資源的做法</strong>（三種的暫停動作都沒有實跑；前兩種恢復時重建腳本走的「改寫」分支，在 NAT gateway 與 NSG 都不存在的那一次實跑過，第三種依 <code>--help</code> 與重建腳本的邏輯推論）：NAT gateway 本身看來沒有收費項目（〈費用與額度：A1、開機磁碟與區塊儲存額度、出站流量與 NAT gateway〉），刪掉它換到的主要是帳號裡不留下沒在用的資源，代價是重建之後 NAT gateway 與 NSG 的 OCID 都換了，NAT gateway 的出站 IP 也會換：在第三方服務用這個 IP 做白名單的話，白名單要跟著改（<code>nat-gateway create --public-ip-id &lt;保留公開 IP 的 OCID&gt;</code> 可以讓重建的 NAT gateway 沿用一個保留的公開 IP，沒有實跑，保留 IP 是否收費沒有查）。只想暫停時有三種做法：</p>
<ul>
<li><strong>只拿掉路由表的 0.0.0.0/0 那一條</strong>：做第 3 步就好。要恢復時重跑重建腳本，路由那一步查到少了 0.0.0.0/0，會印「改寫」補回來。</li>
<li><strong>只把 NSG 從 VNIC 拿掉</strong>：做第 1 步就好。security list 不放行網際網路出站（〈加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG〉），所以出站跟著斷掉。要恢復時重跑重建腳本，掛 NSG 那一步查到 VNIC 沒有掛它，會印「改寫」重新掛上。</li>
<li><strong><code>oci network nat-gateway update --nat-gateway-id &lt;NAT gateway 的 OCID&gt; --block-traffic true</code></strong>：NAT gateway 停止轉送流量，路由與 NSG 都不動。要恢復時要另外跑同一條、帶 <code>--block-traffic false</code>：重建腳本只依名稱與 <code>AVAILABLE</code> 找 NAT gateway，被設成擋流量的 NAT gateway 仍然是 <code>AVAILABLE</code>，腳本每一步都印「已存在」、以 0 結束，出站卻還是不通，<code>apt-get update</code> 回到逾時，沒有任何訊息指出原因。</li>
</ul>
<p><strong>整組收掉</strong>（2026-10-27 15:05 實跑）：照「主機也要一起刪時」的順序，先終止 <code>verify-ubuntu</code>（work request 65 秒 <code>SUCCEEDED</code>，開機磁碟一起刪），再刪 NSG（3 秒）、路由表改回只有 service gateway 那一條（2 秒）、刪 NAT gateway（2 秒）：終止回 <code>SUCCEEDED</code>，刪 NSG 與 NAT gateway 的結束碼是 0，路由表更新之後剩 1 條規則；之後 <code>nat-gateway get</code> 與 <code>nsg get</code> 都回 <code>404 NotAuthorizedOrNotFound</code>。同一個私有子網路裡還有另一台主機（建主機那篇的 Oracle Linux 主機），它的網卡沒有掛這個 NSG（<code>vnic get</code> 的 <code>nsg-ids</code> 是 <code>[]</code>，實跑），套件來源是 Oracle 服務網路裡的鏡像站、經 service gateway 連得到，所以拿掉 NAT gateway 不影響它（套件來源經 service gateway 連得到這一點是推論，收掉之後沒有在它上面跑 <code>dnf</code>）；VCN、私有子網路、service gateway 與 Bastion 留給它用。費用一節建的 50 GB 資料磁碟用 <code>oci bv volume delete --volume-id &lt;磁碟的 OCID&gt; --force --wait-for-state TERMINATED</code> 刪掉（5 秒）。</p>
<p>各項刪不掉時，看錯誤訊息指出的那一項依賴、先刪它。網路、Bastion 與 compartment 的刪除順序在建主機那篇〈刪除這組資源的順序〉。</p>
<h2 id="延伸閱讀建主機的前置網路閒置回收的規則序列主控台的比較主機上的更新與-nat-閘道的計費">延伸閱讀：建主機的前置網路、閒置回收的規則、序列主控台的比較、主機上的更新與 NAT 閘道的計費</h2>
<ul>
<li><a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入</a>：本篇沿用的 compartment、VCN、私有子網路、service gateway、Bastion 與重送腳本</li>
<li><a href="/blog/devops/infra/cloud-providers/oracle-cloud/account-and-home-region/" data-link-title="Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位" data-link-desc="OCI 的 tenancy、home region、compartment 與 availability domain 各自管什麼，home region 為什麼在申請當下就要決定，Always Free 額度的計算與回收規則，以及主控台登入時租用戶與使用者名稱兩個欄位的分工">Oracle Cloud 的帳號結構與 home region：tenancy、Always Free 的範圍與主控台登入欄位</a>：Always Free 的額度、閒置回收與試用結束時超額的規則</li>
<li><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><a href="/blog/devops/infra/cloud-providers/pricing-comparison/" data-link-title="雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目" data-link-desc="Oracle Cloud、Google Cloud 與 AWS 的免費方案分別由哪幾種額度組成，比較時要換算的計費單位，免費主機能放在哪些區域，試用期結束後帳號與資源會怎樣，以及付費主機的價格該去哪裡查">雲端免費額度與小型主機的收費比較：試用金與固定額度、計費單位、區域限制、試用結束後的處置、出站流量與查價項目</a>：AWS 與 Google Cloud 的 NAT 閘道計費、三家的出站流量額度</li>
</ul>
]]></content:encoded></item><item><title>代理程式、中繼資料服務、初始化腳本、維護行為與標籤</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/management/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/management/</guid><description>&lt;p>建立頁的管理區塊不影響主機的規格，影響的是主機跟業者的服務怎麼互動：業者的程式在主機裡做什麼、主機怎麼取得自己的設定、業者維修硬體時主機會怎樣。這些欄位多半維持預設就好，例外是要用到某個 plugin 背後的服務、或對中繼資料服務的安全性有要求的時候。例子以 Oracle Cloud 的建立頁為主，Google Cloud 與 AWS 的對應名稱列在文末的表。文中的版本條件、大小上限與生效時間取自各家官方文件，查證日期 2026-10-24。&lt;/p>
&lt;h2 id="代理程式與-plugin業者裝在主機裡的程式">代理程式與 plugin：業者裝在主機裡的程式&lt;/h2>
&lt;p>業者的平台映像預先裝了業者自己的代理程式（agent）：Oracle Cloud 的叫 Oracle Cloud Agent，Google Cloud 的平台映像預先裝了 guest environment（裡面的 guest agent 負責同步中繼資料、建使用者），AWS 的多數 AMI 預先裝了 SSM Agent。另外收集指標與日誌的代理程式（Google Cloud 的 Ops Agent、AWS 的 CloudWatch agent）要另外安裝。代理程式在主機裡執行，替業者的雲端服務做需要在作業系統裡完成的事：回報 CPU 與記憶體用量、收集日誌、執行遠端指令、替區塊磁碟建立連線、替管理通道服務把臨時的登入金鑰放進主機。&lt;/p>
&lt;p>Oracle Cloud 把每一項功能做成一個 plugin，在建立頁上個別開關。判斷要開哪些，看會不會用到它背後的那個服務：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>要用 Oracle Cloud 的 Bastion 服務、以 managed SSH session 登入&lt;/strong>：一定要開 Bastion plugin，主機有沒有放自己的公鑰都一樣；沒開時 managed SSH session 建不起來，主機放了自己的公鑰的話，還能走不經代理程式的 port forwarding session。開了是必要條件、不是充分條件：映像的作業系統版本不提供 Bastion plugin 時，開著也沒有這個 plugin（2026-10-25 的 Oracle Linux 10.2 就是這樣），建立之前用 &lt;code>oci instance-agent available-plugins get&lt;/code> 依版本查，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄&lt;/a> 的〈建立之前查映像檔版本與它可用的 plugin〉。&lt;/li>
&lt;li>&lt;strong>要在主控台看主機的 CPU、記憶體圖表&lt;/strong>：維持 Compute Instance Monitoring 開啟。&lt;/li>
&lt;li>&lt;strong>要掛區塊磁碟、想讓代理程式替主機設定連線&lt;/strong>：開 Block Volume Management。&lt;/li>
&lt;li>&lt;strong>其餘&lt;/strong>：維持建立頁的預設。官方說明一台主機支援哪些 plugin 依 Oracle Cloud Agent 的版本與映像而定，所以預設清單也隨映像不同。預設關閉的 plugin 背後的服務（作業系統修補、弱點掃描這類）都要在各自的服務裡另外啟用，用到時再回來打開。&lt;/li>
&lt;/ul>
&lt;p>Plugin 建立之後可以在主機頁面的 Oracle Cloud Agent 分頁開關，官方說明變更最多要 10 分鐘生效，所以建立時漏開的 plugin 不必重建主機。Plugin 本身是代理程式的一部分，官方的 plugin 說明頁沒有寫它的費用；費用來自它背後的服務，例如 Logging 服務依收進來的日誌量計費。Oracle Cloud 全部 plugin 的用途與一台 A1 Ubuntu 主機上的實際預設值，整理在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄&lt;/a> 的 Management 段。&lt;/p></description><content:encoded><![CDATA[<p>建立頁的管理區塊不影響主機的規格，影響的是主機跟業者的服務怎麼互動：業者的程式在主機裡做什麼、主機怎麼取得自己的設定、業者維修硬體時主機會怎樣。這些欄位多半維持預設就好，例外是要用到某個 plugin 背後的服務、或對中繼資料服務的安全性有要求的時候。例子以 Oracle Cloud 的建立頁為主，Google Cloud 與 AWS 的對應名稱列在文末的表。文中的版本條件、大小上限與生效時間取自各家官方文件，查證日期 2026-10-24。</p>
<h2 id="代理程式與-plugin業者裝在主機裡的程式">代理程式與 plugin：業者裝在主機裡的程式</h2>
<p>業者的平台映像預先裝了業者自己的代理程式（agent）：Oracle Cloud 的叫 Oracle Cloud Agent，Google Cloud 的平台映像預先裝了 guest environment（裡面的 guest agent 負責同步中繼資料、建使用者），AWS 的多數 AMI 預先裝了 SSM Agent。另外收集指標與日誌的代理程式（Google Cloud 的 Ops Agent、AWS 的 CloudWatch agent）要另外安裝。代理程式在主機裡執行，替業者的雲端服務做需要在作業系統裡完成的事：回報 CPU 與記憶體用量、收集日誌、執行遠端指令、替區塊磁碟建立連線、替管理通道服務把臨時的登入金鑰放進主機。</p>
<p>Oracle Cloud 把每一項功能做成一個 plugin，在建立頁上個別開關。判斷要開哪些，看會不會用到它背後的那個服務：</p>
<ul>
<li><strong>要用 Oracle Cloud 的 Bastion 服務、以 managed SSH session 登入</strong>：一定要開 Bastion plugin，主機有沒有放自己的公鑰都一樣；沒開時 managed SSH session 建不起來，主機放了自己的公鑰的話，還能走不經代理程式的 port forwarding session。開了是必要條件、不是充分條件：映像的作業系統版本不提供 Bastion plugin 時，開著也沒有這個 plugin（2026-10-25 的 Oracle Linux 10.2 就是這樣），建立之前用 <code>oci instance-agent available-plugins get</code> 依版本查，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈建立之前查映像檔版本與它可用的 plugin〉。</li>
<li><strong>要在主控台看主機的 CPU、記憶體圖表</strong>：維持 Compute Instance Monitoring 開啟。</li>
<li><strong>要掛區塊磁碟、想讓代理程式替主機設定連線</strong>：開 Block Volume Management。</li>
<li><strong>其餘</strong>：維持建立頁的預設。官方說明一台主機支援哪些 plugin 依 Oracle Cloud Agent 的版本與映像而定，所以預設清單也隨映像不同。預設關閉的 plugin 背後的服務（作業系統修補、弱點掃描這類）都要在各自的服務裡另外啟用，用到時再回來打開。</li>
</ul>
<p>Plugin 建立之後可以在主機頁面的 Oracle Cloud Agent 分頁開關，官方說明變更最多要 10 分鐘生效，所以建立時漏開的 plugin 不必重建主機。Plugin 本身是代理程式的一部分，官方的 plugin 說明頁沒有寫它的費用；費用來自它背後的服務，例如 Logging 服務依收進來的日誌量計費。Oracle Cloud 全部 plugin 的用途與一台 A1 Ubuntu 主機上的實際預設值，整理在 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的 Management 段。</p>
<h2 id="中繼資料服務與-authorization-header">中繼資料服務與 Authorization header</h2>
<p>每台雲端主機都能從一個只有主機自己連得到的位址（<code>169.254.169.254</code>）查詢自己的資訊：主機 ID、所在區域、建立時放進去的公鑰與初始化腳本。這個服務叫實例中繼資料服務（instance metadata service，IMDS），cloud-init（多數 Linux 雲端映像預先裝好、開機時讀中繼資料做初始設定的程式）開機時就是從這裡讀出 SSH 公鑰。知識卡見 <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>。</p>
<p>Oracle Cloud 的中繼資料服務有兩個版本。第一版的路徑是 <code>/opc/v1</code>，請求不必帶任何標頭；第二版的路徑是 <code>/opc/v2</code>，每個請求都要帶 <code>Authorization: Bearer Oracle</code> 這個標頭。建立頁上的「Require an authorization header」打開之後，主機只接受第二版的請求，送到第一版路徑的請求回 404、沒帶標頭的請求被拒絕。這個限制防的是 <a href="/blog/backend/knowledge-cards/ssrf/" data-link-title="SSRF" data-link-desc="說明伺服器端請求被濫用時如何存取內部網路或 metadata 服務">SSRF</a>（伺服器端請求偽造）：主機上的網頁應用程式有漏洞、被誘導去讀某個網址時，攻擊者可以讓它讀 <code>169.254.169.254</code> 而取得主機的資訊；多數這類漏洞只能控制網址、加不了自訂的標頭，所以要求標頭能擋掉這條路。</p>
<p>這個選項成立的前提是主機上讀中繼資料的程式都會用第二版，在不支援第二版的映像上只允許第二版時，cloud-init 讀不到公鑰，主機會連不上。官方〈Getting Instance Metadata〉把映像分成三類：支援第二版的平台映像是 Oracle Linux 與 Oracle Autonomous Linux（Oracle Linux 7.x 與 8.x 要 2020 年 7 月（含）以後發佈的映像，Oracle Autonomous Linux 7.x 要 2020 年 6 月（含）以後，8.x 全部支援）；自訂的 Linux 映像在 cloud-init 更新到 20.3 以上、Oracle Cloud Agent 更新到 0.0.19 以上時可能支援；其他平台映像、多數自訂映像與多數市集映像不支援。Ubuntu 的平台映像因此落在不支援的那一類，雖然 Ubuntu 24.04 內附的 cloud-init 版本高於 20.3、符合官方給自訂映像的版本條件；兩種說法對不上。2026-10-26 在一台建立時放了公鑰、只允許第二版的 Ubuntu 24.04 A1 主機上實際驗過：cloud-init 讀到了公鑰，首次開機之後就用那把金鑰登得進去（<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>〈建 Ubuntu 24.04 主機與經由 port forwarding 登入〉）。這是一個映像檔版本（<code>Canonical-Ubuntu-24.04-aarch64-2026.09.18-0</code>）上的一次結果，官方的分類沒有因此改變，換了映像檔版本時仍以建好之後實際登入為準。Oracle Cloud 的建立頁在 Ubuntu 24.04 的 A1 主機上把這個選項預設開著（<a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">實測紀錄</a>的〈Management：Oracle Cloud Agent 與其他進階選項〉，2026-10-24）。實測紀錄裡的那台主機在 2026-10-25 建成，開著這個選項時代理程式照常回報各 plugin 的狀態；Bastion 的 managed SSH session 建立請求約 15 分鐘後逾時、工作階段轉成 DELETED，暫時改成允許第一版也一樣逾時，所以那次失敗跟這個選項無關，成因是官方列出的 A1 加 Ubuntu 不支援 managed SSH session（排查經過在同一篇的〈建立之後：Bastion managed SSH session 逾時的排查〉）。它選的是 No SSH keys、主機裡沒有放公鑰，所以開著這個選項時 cloud-init 是否把公鑰寫進主機，是由後來另一台放了公鑰的 Ubuntu 24.04 主機驗的，結果在上一段。</p>
<p>在 Oracle Cloud 上的做法：</p>
<ul>
<li><strong>用 Oracle Linux 或 Oracle Autonomous Linux 平台映像</strong>：維持開啟。</li>
<li><strong>其他平台映像（包括 Ubuntu）</strong>：官方列為不支援。建立頁預設開著時，要嘛先關掉、建好之後確認登得進去再開，要嘛開著建立，登不進去時照「開著建立之後懷疑出了問題」那一條改回來。Ubuntu 24.04 的一個映像檔版本開著建立時登得進去（上一段的實測）。</li>
<li><strong>自訂或市集映像</strong>：先不開，建好之後在主機上用 <code>cloud-init --version</code> 與代理程式的版本確認符合官方條件，再開。</li>
<li><strong>開著建立之後懷疑出了問題</strong>：公鑰沒寫進 <code>authorized_keys</code>、初始化腳本沒執行、代理程式的 plugin 狀態異常，這三件都是讀中繼資料才做得到的事。這個設定建立之後可以改：在主機頁面的 Instance metadata service 旁按 Edit，改回允許第一版與第二版，再重開主機（官方〈Getting Instance Metadata〉的步驟）；公鑰沒寫進去而登不進主機時，要從序列主控台或其他救援入口進去，見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈建立之後加金鑰與金鑰遺失時的入口〉。</li>
</ul>
<p>AWS 的 IMDSv2 同樣可以在建立後切換，多數近年的 AMI 都支援；Google Cloud 的中繼資料伺服器固定要求 <code>Metadata-Flavor: Google</code> 標頭，沒有這個選項。</p>
<h2 id="初始化腳本">初始化腳本</h2>
<p>建立頁的進階選項裡可以放一段初始化腳本（Oracle Cloud 的主控台欄位叫 Initialization script，經由中繼資料的 user data 傳進主機），常用來安裝套件、建立使用者、寫入設定檔。Oracle Cloud 的限制是使用者資料與其他中繼資料合計不超過 32,000 bytes；官方提醒腳本裡不要放會觸發重開機的指令，否則可能讓主機建立失敗。</p>
<p>腳本什麼時候執行，看主機裡由哪個程式執行它。Oracle Cloud 與 AWS 的 Linux 主機由 cloud-init 執行初始化腳本，預設只在第一次開機時執行一次，之後修改中繼資料不會重新執行；Google Cloud 的 startup script 由 guest agent 從中繼資料讀出來執行，每次開機都執行，腳本要寫成重複執行也不出錯。要讓同一份設定能在新主機上重現，腳本本身要放在版本控制裡；主機與網路這些資源本身改用 IaC 管理，見 <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 工具選型與 state 地基</a>。</p>
<h2 id="硬體維護時的行為">硬體維護時的行為</h2>
<p>業者要維修主機底下的實體硬體時，上面的虛擬機只有兩種處理方式：搬到另一台實體主機上（業者叫遷移，不中斷的叫即時遷移，要重開一次的叫重開機遷移），或是原地停機、等硬體修好再開。Oracle Cloud 與 Google Cloud 的建立頁上有維護欄位，決定業者優先用哪一種、以及硬體恢復之後主機要不要自動啟動。Oracle Cloud 有兩個欄位：</p>
<ul>
<li><strong>Live migration</strong>：預設是讓 Oracle 選擇遷移方式。選「盡量用即時遷移」時，Oracle Cloud 先嘗試即時遷移，失敗時改用重開機遷移；選「送出維護通知」時，Oracle Cloud 在維護前通知，主機在期限前沒有自己重開的話，就由 Oracle Cloud 做即時遷移。部分機型不支援即時遷移，開了安全開機（shielded instance）的主機也不支援即時遷移與重開機遷移，見 <a href="/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機</a>。</li>
<li><strong>Restart after infrastructure maintenance</strong>（2026-10-24 主控台上的名稱，官方文件寫作 Restore instance lifecycle state after infrastructure maintenance）：預設勾選，維護事件影響到主機時，主機在硬體恢復之後自動重開。不勾的話，主機恢復後維持停止狀態，要手動啟動。</li>
</ul>
<p>Google Cloud 對應的欄位是 On host maintenance（遷移或終止）與 Automatic restart，AWS 由 scheduled events 通知維護時間。一般用途維持預設。在 Oracle Cloud 上，服務不能接受沒有預警的重開時，選送出維護通知，收到通知後在期限前、挑服務離峰的時間自己重開主機，或先把流量切到另一台。</p>
<h2 id="標籤">標籤</h2>
<p>標籤（tag）是附在資源上的鍵值對，用來分類資源、分攤費用、寫自動化腳本時篩選資源。Oracle Cloud 有兩種：自由格式標籤（有建立資源權限的人都能加）與預先定義的標籤（要有那個標籤命名空間的權限）。標籤可以在建立之後再加。成本分攤與標籤規範見 <a href="/blog/devops/infra/08-governance-habits/tagging-secrets/" data-link-title="Tagging 規範與 Secrets 不進 code" data-link-desc="tag 讓資源可盤點、可清理、可歸屬；密鑰存在專用服務裡而非 code 或 state，兩者都屬於 day-1 就該立的治理地基">Tagging 規範與 Secrets 不進 code</a>。</p>
<h2 id="各家的欄位名稱代理程式中繼資料初始化腳本與維護行為">各家的欄位名稱：代理程式、中繼資料、初始化腳本與維護行為</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>業者的代理程式</td>
          <td>Oracle Cloud Agent</td>
          <td>Guest agent、Ops Agent</td>
          <td>SSM Agent、CloudWatch agent</td>
      </tr>
      <tr>
          <td>中繼資料服務的存取驗證</td>
          <td>Require an authorization header（IMDSv2）</td>
          <td>固定要求 <code>Metadata-Flavor: Google</code> 標頭</td>
          <td>IMDSv2（token required）</td>
      </tr>
      <tr>
          <td>初始化腳本</td>
          <td>Initialization script（cloud-init）</td>
          <td>Startup script（每次開機執行）</td>
          <td>User data（cloud-init）</td>
      </tr>
      <tr>
          <td>維護時的行為</td>
          <td>Live migration、Restart after maintenance</td>
          <td>On host maintenance、Automatic restart</td>
          <td>Scheduled events、Auto-recovery</td>
      </tr>
  </tbody>
</table>
<p>不放 SSH 金鑰、改用 Bastion 登入時 plugin 要怎麼配合，見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉。</p>
]]></content:encoded></item><item><title>Oracle Cloud 主機的暫停與恢復上線實測紀錄：暫停前的基準、收窄入口、停止主機、停止期間的計費與 Cloudflare 的回應、恢復上線的每一步與耗時，以及收尾時 DNS 紀錄與主機的刪除順序</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/suspend-resume-drill/</link><pubDate>Tue, 27 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/suspend-resume-drill/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>事件說明&lt;/strong>：2026-10-27（本篇的時刻都是 UTC），在 Oracle Cloud 的 &lt;code>ap-batam-1&lt;/code> 區域，從一台 macOS 開發機用 OCI CLI 操作。tenancy（Oracle Cloud 的帳號層級）是付費帳戶。主機是 A1 機型（&lt;code>VM.Standard.A1.Flex&lt;/code>，1 OCPU、6 GB），Oracle Linux 10.2，放在公開子網路、有臨時公開 IP（ephemeral public IP）。網卡掛著一個 NSG（network security group：套在個別網卡上的一組防火牆規則，跟 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> 是同一類），只放行 Cloudflare IPv4 位址段連 80 與 443；子網路的 security list（套在整個子網路上的防火牆規則）只放行管理者的 IP 連 22。主機上跑 nginx（80 與 443，443 用自簽憑證）與一個接收監控事件的 systemd 服務。網域的根網域與 &lt;code>www&lt;/code> 兩筆 A 紀錄經過 Cloudflare 代理（Proxied：訪客連到 Cloudflare，再由 Cloudflare 回源到這台主機，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cdn/" data-link-title="CDN（內容遞送網路）" data-link-desc="網站要放到 Cloudflare 這類服務後面、看到回應標頭帶著 CDN 的標記、或要判斷源站看到的來源位址是誰時，查 CDN 代替源站接收連線的方式、回源與它對源站防火牆的影響">CDN&lt;/a>）指到這台主機，SSL/TLS 模式實際套用的是完整（Full）。這台主機與它的網路，是在 &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>（下文稱「回源那篇」）的實測收掉之後，照那篇的步驟另建的，DNS 紀錄改成根網域與 &lt;code>www&lt;/code>。當時在做的操作是：這台實驗主機要收掉之前，先照 &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;/p>&lt;/blockquote>
&lt;p>這篇記錄在 Oracle Cloud 上把一台放在 Cloudflare 後面的主機停機、再恢復上線的每一步，以及演練完把它收掉的順序。各節依序是：&lt;/p>
&lt;ul>
&lt;li>暫停前記下的基準：接聽的程式、服務的狀態、待裝的安全更新、憑證效期、公開 IP 與 A1 的用量，以及記這些值的指令。&lt;/li>
&lt;li>收窄入口：把 NSG 從主機的網卡拿掉，以及 Cloudflare 在這之後回的錯誤與等待時間。&lt;/li>
&lt;li>停止主機：&lt;code>SOFTSTOP&lt;/code> 與 &lt;code>STOP&lt;/code> 的差別、停止花的時間、停止之後公開 IP 的去留，以及停止中的主機是否仍佔 A1 與區塊儲存的用量。&lt;/li>
&lt;li>停止期間的計費：用 usage API 逐小時讀帳務明細，停止的那一小時 A1 與開機磁碟各記了什麼。&lt;/li>
&lt;li>恢復上線：啟動與 SSH 連得上的時間、跟基準比對的漂移（暫停期間改變了的狀態）、裝安全更新與重開機的判斷、只對 loopback 的冒煙測試（&lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/smoke-test/" data-link-title="Smoke Test（冒煙測試）" data-link-desc="部署或重建之後要決定先跑哪一組檢查、冒煙測試全部通過而使用者仍回報錯誤，或要判斷一組部署後檢查涵蓋到哪裡、沒有涵蓋到哪裡時">smoke test&lt;/a>：只確認服務起得來、首頁有回應的最小檢查）、重新掛回 NSG 之後從外面的驗證。&lt;/li>
&lt;li>收尾：服務要收掉時，DNS 紀錄、主機與網路資源的刪除順序、這個順序的理由與實際耗時。&lt;/li>
&lt;/ul>
&lt;p>每一步為什麼排在這個位置（入口要在停止之前收窄、更新要在開放入口之前裝），推導在暫停那篇；這裡寫的是在 Oracle Cloud 與 Cloudflare 上跑出來的結果。本篇的 OCID、IP 與網域一律是佔位值，各自的查法寫在第一次用到它的那條指令旁邊。&lt;/p>
&lt;h2 id="暫停前記下的基準接聽的程式服務待裝的更新憑證效期與公開-ip">暫停前記下的基準：接聽的程式、服務、待裝的更新、憑證效期與公開 IP&lt;/h2>
&lt;p>恢復的時候要拿一份暫停當下的值來比，才知道暫停期間變了什麼（暫停那篇〈恢復上線前要重新確認的漂移〉），所以基準在入口收窄之前、主機還在跑的時候記。下面這一條從開發機經 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"># &amp;lt;登入設定&amp;gt; 是連到這台主機的 ssh 參數（-i 私鑰、使用者@公開 IP），這次是 security list 只放行管理者 IP 的直連&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">ssh &amp;lt;登入設定&amp;gt; &lt;span class="s1">&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s1"> sudo ss -lntup # 接聽的程式與連接埠
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s1"> systemctl is-enabled nginx monitor-collector # 開機時會不會自己起來
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s1"> systemctl is-active nginx monitor-collector
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s1"> curl -s -o /dev/null -w &amp;#34;loopback http %{http_code}\n&amp;#34; http://127.0.0.1/
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s1"> curl -sk -o /dev/null -w &amp;#34;loopback https %{http_code}\n&amp;#34; https://127.0.0.1/
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s1"> echo sec=$(sudo dnf -q check-update --security | grep -c .) # 待裝的安全更新（行數）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s1"> sudo needs-restarting -r # 現在需不需要重開機
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s1"> uname -r
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s1">&amp;#39;&lt;/span> &amp;gt; pre-suspend.log&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>monitor-collector&lt;/code> 是這台主機上那個接收監控事件的服務名稱，換成自己主機上要確認的服務。&lt;code>dnf check-update --security&lt;/code> 每個待裝的套件印一行，&lt;code>grep -c .&lt;/code> 數的是非空的行數；這次是 18 行，恢復時實際裝了 18 個套件，兩個數字相同。&lt;code>needs-restarting&lt;/code> 這個指令在 Oracle Linux 10.2 由 &lt;code>yum-utils&lt;/code> 套件提供（&lt;code>oraclelinux:10&lt;/code> 容器裡 &lt;code>rpm -qf $(command -v needs-restarting)&lt;/code> 實跑），Debian 與 Ubuntu 上對應的是 &lt;code>needrestart -b&lt;/code> 與 &lt;code>/var/run/reboot-required&lt;/code>（&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;/p></description><content:encoded><![CDATA[<blockquote>
<p><strong>事件說明</strong>：2026-10-27（本篇的時刻都是 UTC），在 Oracle Cloud 的 <code>ap-batam-1</code> 區域，從一台 macOS 開發機用 OCI CLI 操作。tenancy（Oracle Cloud 的帳號層級）是付費帳戶。主機是 A1 機型（<code>VM.Standard.A1.Flex</code>，1 OCPU、6 GB），Oracle Linux 10.2，放在公開子網路、有臨時公開 IP（ephemeral public IP）。網卡掛著一個 NSG（network security group：套在個別網卡上的一組防火牆規則，跟 AWS 的 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> 是同一類），只放行 Cloudflare IPv4 位址段連 80 與 443；子網路的 security list（套在整個子網路上的防火牆規則）只放行管理者的 IP 連 22。主機上跑 nginx（80 與 443，443 用自簽憑證）與一個接收監控事件的 systemd 服務。網域的根網域與 <code>www</code> 兩筆 A 紀錄經過 Cloudflare 代理（Proxied：訪客連到 Cloudflare，再由 Cloudflare 回源到這台主機，見 <a href="/blog/devops/infra/knowledge-cards/cdn/" data-link-title="CDN（內容遞送網路）" data-link-desc="網站要放到 Cloudflare 這類服務後面、看到回應標頭帶著 CDN 的標記、或要判斷源站看到的來源位址是誰時，查 CDN 代替源站接收連線的方式、回源與它對源站防火牆的影響">CDN</a>）指到這台主機，SSL/TLS 模式實際套用的是完整（Full）。這台主機與它的網路，是在 <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>（下文稱「回源那篇」）的實測收掉之後，照那篇的步驟另建的，DNS 紀錄改成根網域與 <code>www</code>。當時在做的操作是：這台實驗主機要收掉之前，先照 <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>（下文稱「暫停那篇」）的「停止主機」那一種範圍演練一次停機與恢復上線。</p></blockquote>
<p>這篇記錄在 Oracle Cloud 上把一台放在 Cloudflare 後面的主機停機、再恢復上線的每一步，以及演練完把它收掉的順序。各節依序是：</p>
<ul>
<li>暫停前記下的基準：接聽的程式、服務的狀態、待裝的安全更新、憑證效期、公開 IP 與 A1 的用量，以及記這些值的指令。</li>
<li>收窄入口：把 NSG 從主機的網卡拿掉，以及 Cloudflare 在這之後回的錯誤與等待時間。</li>
<li>停止主機：<code>SOFTSTOP</code> 與 <code>STOP</code> 的差別、停止花的時間、停止之後公開 IP 的去留，以及停止中的主機是否仍佔 A1 與區塊儲存的用量。</li>
<li>停止期間的計費：用 usage API 逐小時讀帳務明細，停止的那一小時 A1 與開機磁碟各記了什麼。</li>
<li>恢復上線：啟動與 SSH 連得上的時間、跟基準比對的漂移（暫停期間改變了的狀態）、裝安全更新與重開機的判斷、只對 loopback 的冒煙測試（<a href="/blog/testing/knowledge-cards/smoke-test/" data-link-title="Smoke Test（冒煙測試）" data-link-desc="部署或重建之後要決定先跑哪一組檢查、冒煙測試全部通過而使用者仍回報錯誤，或要判斷一組部署後檢查涵蓋到哪裡、沒有涵蓋到哪裡時">smoke test</a>：只確認服務起得來、首頁有回應的最小檢查）、重新掛回 NSG 之後從外面的驗證。</li>
<li>收尾：服務要收掉時，DNS 紀錄、主機與網路資源的刪除順序、這個順序的理由與實際耗時。</li>
</ul>
<p>每一步為什麼排在這個位置（入口要在停止之前收窄、更新要在開放入口之前裝），推導在暫停那篇；這裡寫的是在 Oracle Cloud 與 Cloudflare 上跑出來的結果。本篇的 OCID、IP 與網域一律是佔位值，各自的查法寫在第一次用到它的那條指令旁邊。</p>
<h2 id="暫停前記下的基準接聽的程式服務待裝的更新憑證效期與公開-ip">暫停前記下的基準：接聽的程式、服務、待裝的更新、憑證效期與公開 IP</h2>
<p>恢復的時候要拿一份暫停當下的值來比，才知道暫停期間變了什麼（暫停那篇〈恢復上線前要重新確認的漂移〉），所以基準在入口收窄之前、主機還在跑的時候記。下面這一條從開發機經 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"># &lt;登入設定&gt; 是連到這台主機的 ssh 參數（-i 私鑰、使用者@公開 IP），這次是 security list 只放行管理者 IP 的直連</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">ssh &lt;登入設定&gt; <span class="s1">&#39;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s1">  sudo ss -lntup                                  # 接聽的程式與連接埠
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s1">  systemctl is-enabled nginx monitor-collector     # 開機時會不會自己起來
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s1">  systemctl is-active nginx monitor-collector
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s1">  curl -s -o /dev/null -w &#34;loopback http %{http_code}\n&#34; http://127.0.0.1/
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s1">  curl -sk -o /dev/null -w &#34;loopback https %{http_code}\n&#34; https://127.0.0.1/
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s1">  echo sec=$(sudo dnf -q check-update --security | grep -c .)   # 待裝的安全更新（行數）
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s1">  sudo needs-restarting -r                         # 現在需不需要重開機
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s1">  uname -r
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s1">&#39;</span> &gt; pre-suspend.log</span></span></code></pre></div><p><code>monitor-collector</code> 是這台主機上那個接收監控事件的服務名稱，換成自己主機上要確認的服務。<code>dnf check-update --security</code> 每個待裝的套件印一行，<code>grep -c .</code> 數的是非空的行數；這次是 18 行，恢復時實際裝了 18 個套件，兩個數字相同。<code>needs-restarting</code> 這個指令在 Oracle Linux 10.2 由 <code>yum-utils</code> 套件提供（<code>oraclelinux:10</code> 容器裡 <code>rpm -qf $(command -v needs-restarting)</code> 實跑），Debian 與 Ubuntu 上對應的是 <code>needrestart -b</code> 與 <code>/var/run/reboot-required</code>（<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>〈會老化的軟體：各條更新管道與更新後的重開機〉）。</p>
<p>這次記到的值：nginx 聽 80 與 443、sshd 聽 22、監控事件的服務聽 8765，兩個服務都是 <code>enabled</code> 與 <code>active</code>；loopback 的 HTTP 與 HTTPS 都回 200；待裝的安全更新 18 行；<code>needs-restarting -r</code> 印 <code>Reboot should not be necessary.</code>；核心是 <code>6.12.0-207.111.5.6.el10uek.aarch64</code>。</p>
<p>憑證效期照 <code>nginx -T</code> 列出的 <code>ssl_certificate</code> 路徑讀。這台主機在建好之後換過網站，建立腳本裡寫的憑證路徑已經不在，照那個路徑讀會回 <code>No such file or directory</code>；<code>nginx -T</code> 印的是 nginx 實際載入的設定，所以讀到的是現在用的那一張：</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"># 列出 nginx 實際載入的憑證路徑；開頭是 # 的是註解掉的範例，這條 grep 只取沒有註解的那一行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh &lt;登入設定&gt; <span class="s1">&#39;sudo nginx -T 2&gt;/dev/null | grep -E &#34;^\s*ssl_certificate\s&#34;&#39;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">ssh &lt;登入設定&gt; <span class="s1">&#39;sudo openssl x509 -in &lt;上一條印出的路徑&gt; -noout -enddate&#39;</span></span></span></code></pre></div><p>這次讀到 <code>notAfter</code> 在 2036 年，是建網站時簽的十年自簽憑證；暫停只有 2 小時，恢復時沒有再讀。暫停期長到可能跨過效期的，恢復時要再讀一次。</p>
<p>公開 IP、私有 IP 與 A1 的用量在開發機上用 CLI 讀。<code>&lt;tenancy 的 OCID&gt;</code> 是 <code>~/.oci/config</code> 裡 <code>tenancy=</code> 那一行的值；<code>&lt;compartment 的 OCID&gt;</code> 是主機所在的 compartment（Oracle Cloud 裡把資源分組的單位），用 <code>oci iam compartment list -c &lt;tenancy 的 OCID&gt; --compartment-id-in-subtree true --all --query 'data[].{name:name,id:id}' --output table</code> 列出名稱與 OCID，主機建在最上層時就是 tenancy 的 OCID；<code>&lt;主機的 OCID&gt;</code> 用 <code>oci compute instance list -c &lt;compartment 的 OCID&gt; --query 'data[].{name:&quot;display-name&quot;,id:id}' --output table</code> 列出每台主機的名稱與 OCID 再挑；<code>&lt;可用網域&gt;</code>（availability domain，區域裡的一個資料中心，跨業者的概念見 <a href="/blog/devops/infra/knowledge-cards/availability-zone/" data-link-title="Availability Zone（可用區）" data-link-desc="建立雲端主機或磁碟時要選可用區、遇到某個可用區容量不足、規劃兩台主機要不要放在不同可用區，或對照各家把這一層叫 zone、AZ 還是 availability domain 時，查可用區的範圍、它跟區域與故障域的關係與它限制了哪些資源的搭配">Availability Zone</a>）用 <code>oci compute instance get --instance-id &lt;主機的 OCID&gt; --query 'data.&quot;availability-domain&quot;' --raw-output</code> 查：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">oci compute instance list-vnics --instance-id &lt;主機的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data[0].{pub:&#34;public-ip&#34;,priv:&#34;private-ip&#34;,vnic:id}&#39;</span> --output table
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># A1 的核心數用量；記憶體把 limit 名稱換成 standard-a1-memory-count</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">oci limits resource-availability get -c &lt;tenancy 的 OCID&gt; --service-name compute <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --limit-name standard-a1-core-count --availability-domain &lt;可用網域&gt; --query <span class="s1">&#39;data.used&#39;</span> --raw-output</span></span></code></pre></div><p>這次 tenancy 裡有三台各 1 OCPU 的 A1，核心數的 <code>used</code> 回 3；記憶體與免費區塊儲存的用量這次在暫停前沒有讀（下面〈停止主機：SOFTSTOP、耗時、公開 IP 的去留與停止中的用量〉在停止中讀了）。暫停前沒有跑 <a href="/blog/devops/operations/09-host-lifecycle/hands-on/host-check-script-source/" data-link-title="只讀檢查腳本 host-check.py 的原始碼與逐段說明：選項的切分與各工具的白名單規則、清單的載入與驗證、指令的執行、各項檢查、路徑與時間的量測、結果檔與命令列，以及這一版的已知限制" data-link-desc="要讀主機維護清單的只讀檢查腳本實際怎麼切分各工具的選項、怎麼驗證清單、怎麼判定 PASS、WARN 與 MANUAL、怎麼寫結果檔，或要照它的做法寫自己的檢查腳本時，查完整的 Python 原始碼與每一段這樣寫的理由">只讀檢查腳本 host-check.py 的原始碼與逐段說明：選項的切分與各工具的白名單規則、清單的載入與驗證、指令的執行、各項檢查、路徑與時間的量測、結果檔與命令列，以及這一版的已知限制</a> 的檢查：這台主機沒有寫過主機清單，上面那幾條指令是手動挑的，涵蓋不到清單會檢查的累積資料與到期設定。</p>
<h2 id="收窄入口把-nsg-從網卡拿掉以及-cloudflare-回的-522">收窄入口：把 NSG 從網卡拿掉，以及 Cloudflare 回的 522</h2>
<p>收窄入口是暫停那篇〈暫停時先收窄的入口〉的用詞，指收回對外的放行、只留管理者的連線。主機停止期間不回應任何連線，入口開不開沒有差別；入口要收窄，是為了下一次啟動的那一刻：主機一開機，enabled 的服務自己起來、防火牆照舊放行，入口還開著的話，主機在裝好更新之前就在接受外部連線。所以收窄只要在下一次啟動之前做完；暫停那篇把它排在暫停的當下，是因為恢復的人可能隔了很久、或換了一個人，到時不一定記得先收窄。Oracle Cloud 對一張網卡的放行是它的 NSG 與子網路 security list 兩處規則的聯集（<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>〈防火牆規則的位置與合併方式〉），所以先確認 80 與 443 在哪一處放行：這台主機的 security list 只放行 22，對外的 80 與 443 只靠那個 NSG，收窄入口的動作就是把 NSG 從網卡拿掉，規則本身留在 NSG 裡，恢復時再掛回去。security list 也放行 80 與 443 的主機，拿掉 NSG 之後入口照樣開著，下面那條 <code>curl</code> 會回 200 而不是 522，要另外收回 security list 那幾條（它管的是整個子網路，同一個子網路的其他主機會一起受影響）。拿掉之前先把網卡現在掛著的 NSG 清單存下來，恢復時要用：</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"># &lt;VNIC 的 OCID&gt; 是〈暫停前記下的基準：接聽的程式、服務、待裝的更新、憑證效期與公開 IP〉那條 list-vnics 印的 vnic 欄</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">oci network vnic get --vnic-id &lt;VNIC 的 OCID&gt; --query <span class="s1">&#39;data.&#34;nsg-ids&#34;&#39;</span> &gt; nsg-ids.json
</span></span><span class="line"><span class="ln">3</span><span class="cl">oci network vnic update --vnic-id &lt;VNIC 的 OCID&gt; --nsg-ids <span class="s1">&#39;[]&#39;</span> --force</span></span></code></pre></div><p><code>--nsg-ids</code> 是整份替換（<a href="/blog/devops/infra/knowledge-cards/vnic/" data-link-title="VNIC（虛擬網路卡）" data-link-desc="雲端主機的私有 IP、公開 IP 或網卡層的防火牆規則要查、要改，或主機重建之後私有 IP 與防火牆規則跟著不見時，查 VNIC 掛著哪些設定、它跟主機一起建立與刪除的關係，以及各家的名稱">VNIC</a> 卡寫了這個欄位的整份替換）。網卡上掛著好幾個 NSG、只要拿掉其中一個時，<code>'[]'</code> 要換成去掉那一個之後的清單，否則別的 NSG 會一起被拿掉；這次 <code>nsg-ids.json</code> 裡只有一個。沒有存下來的話，恢復時用 <code>oci network nsg list -c &lt;compartment 的 OCID&gt; --query 'data[].{name:&quot;display-name&quot;,id:id}' --output table</code> 列出名稱與 OCID 查回來。網卡上另有一個放行監控探測的 NSG 時，探測在暫停期間不論拿不拿掉它都會失敗，探測要在監控那一端先靜音或停用，恢復時等外部驗證通過之後再啟用（暫停那篇〈恢復的順序：依元件之間的依賴排列〉的流量導回那一段）。04:59:11 送出 <code>update</code>，1 秒後回應，<code>nsg-ids</code> 變成 <code>[]</code>。之後每 40 秒從開發機經 Cloudflare 請求一次首頁：</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"># &lt;網域&gt; 換成經過 Cloudflare 代理的網域；印回應碼與總耗時</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">curl -s -o /dev/null -w <span class="s1">&#39;%{http_code} %{time_total}\n&#39;</span> --max-time <span class="m">40</span> https://&lt;網域&gt;/</span></span></code></pre></div><p>三次都回 <code>522</code>，耗時 20.3 到 20.7 秒。522 是 Cloudflare 連到源站逾時的錯誤碼：NSG 拿掉之後，security list 只放行管理者 IP 的 22，Cloudflare 送到 443 的封包沒有規則放行，被丟掉而沒有回應，所以 Cloudflare 等到逾時（源站拒絕連線時的 521 與逾時的 522 怎麼分，見暫停那篇〈暫停期間仍指向這台主機的外部設定〉）。約 20 秒是 Cloudflare 那一端等 TCP 連線的時間，源站這一端決定不了；方案或設定會不會改變它，這次沒有查。訪客在這段時間看到的是 Cloudflare 的 522 錯誤頁，每次要等約 20 秒才出現；DNS 紀錄不經 CDN、直接指公開 IP 時，訪客看到的是瀏覽器自己的連線逾時（從機制推論，這次沒有這種紀錄）。放行 22 的規則在 security list，不在這個 NSG，所以收窄之後管理者照樣連得進主機。</p>
<p>這一步的其他做法與換到的結果：</p>
<ul>
<li><strong>刪掉 NSG 裡 80 與 443 的規則</strong>：效果相同，恢復時要把整份 Cloudflare 位址段重新加回去。這個 NSG 有 30 條規則，回源那篇量到每次 API 呼叫最多加 25 條，所以要分兩次加；拿掉整個 NSG 時規則原封不動，恢復只要一條指令。</li>
<li><strong>改 security list</strong>：這台主機的 80 與 443 本來就不在 security list 裡，改它收窄不了這兩個連接埠。</li>
<li><strong>網卡一定要掛至少一個防火牆群組的業者</strong>：AWS 的網卡至少要有一個 security group，拿成零個的做法換不過去，改成掛一個沒有放行規則的空群組（依業者的規則推論，沒有實跑）。</li>
<li><strong>只在 Cloudflare 那一層擋</strong>：zone 上加一條 Worker route 回維護頁，或一條 WAF 自訂規則直接擋，訪客馬上拿到回應，不必等 20 秒的 522，DNS 也不必改。它只在 Cloudflare 這一層生效：NSG 放行的是整份 Cloudflare 位址段，別人在自己的 Cloudflare 帳號裡把一個網域指到這個 IP，回源照樣進得來，所以只能跟拿掉 NSG 並用（從回源那篇量到的 Worker 穿過 NSG 推論，這次沒有做）。</li>
<li><strong>改用 Cloudflare Tunnel</strong>：源站完全不開入站，暫停時停掉 <code>cloudflared</code> 就收窄了，恢復時也不必比對位址段（回源那篇〈Cloudflare Tunnel：源站不開入站，以及出站要放行的連接埠〉；這台主機沒有用 Tunnel）。</li>
<li><strong>DNS 紀錄改指到維護頁</strong>：訪客看到的是說明頁而不是 522，代價是要另外準備一個維護頁（Cloudflare Workers 或另一台主機）、恢復時再改回來（暫停那篇〈暫停期間仍指向這台主機的外部設定〉）。這次沒有做，訪客看到的是 522。</li>
<li><strong>不收窄</strong>：停止期間沒有差別，差別在恢復時開機的那一刻，見下面〈恢復上線：啟動、漂移比對、安全更新、冒煙測試與重新開放入口〉的對外入口開放那一段。</li>
</ul>
<h2 id="停止主機softstop耗時公開-ip-的去留與停止中的用量">停止主機：SOFTSTOP、耗時、公開 IP 的去留與停止中的用量</h2>
<p>停止之前先確認業者停止主機時收不收回公開 IP：Oracle Cloud 的臨時公開 IP 停止時留著（下面實測），DNS 紀錄可以不動；停止時就收回臨時公開 IP 的業者（暫停那篇寫的 AWS 沒有 Elastic IP 的主機），DNS 紀錄要在送出停止之前改指維護頁或刪除，否則停止那一刻舊 IP 就還給業者，之後被分給別人時就是懸空 DNS（dangling DNS：DNS 紀錄指著已經還給業者、可能分給別人的 IP）。經過 Cloudflare 代理、SSL/TLS 模式是完整（Full，不驗源站憑證）時，Cloudflare 會把訪客的請求送到那個 IP 的下一任主人。</p>
<p>主機上有寫入資料的服務時，先經 SSH 用 <code>systemctl stop</code> 停掉它、確認它已經結束，再停止主機；下面的 <code>SOFTSTOP</code> 最多等作業系統關機 15 分鐘，服務停不下來時照樣被切斷電源（這次沒有先停服務，69 秒就停好了）。從 API 停止主機。暫停那篇〈暫停期間仍在計費的資源〉引的 Oracle Cloud 官方文件寫明，在作業系統裡下 <code>shutdown</code> 關機不會停止計費，要從主控台或 API 停止；這樣關機之後主控台上顯示的狀態，這次沒有查。</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">oci compute instance action --instance-id &lt;主機的 OCID&gt; --action SOFTSTOP <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --wait-for-state STOPPED --max-wait-seconds <span class="m">1200</span> --query <span class="s1">&#39;data.&#34;lifecycle-state&#34;&#39;</span> --raw-output</span></span></code></pre></div><p><code>--action</code> 收 <code>STOP</code> 與 <code>SOFTSTOP</code> 兩種停止。依 <code>oci compute instance action --help</code>，<code>STOP</code> 直接切斷電源（Powers off the instance）；<code>SOFTSTOP</code> 先送關機指令給作業系統，等作業系統關機最多 15 分鐘，之後才切斷電源，所以作業系統上的程式有機會正常結束。這台主機上有寫入 SQLite 的服務，選 <code>SOFTSTOP</code>；<code>STOP</code> 這次沒有實跑，依說明，它跳過作業系統的關機，正在寫入的資料可能沒有寫完。<code>--wait-for-state STOPPED</code> 讓 CLI 等到主機的狀態變成 <code>STOPPED</code> 才回傳，<code>instance action</code> 收的是主機本身的狀態，跟 <code>instance terminate</code> 收 work request（Oracle Cloud 記錄一次非同步操作進度的物件）的狀態不同，下面〈收尾：DNS 紀錄、主機與網路資源的刪除順序〉會用到這個差別。上面那條的 <code>--max-wait-seconds</code> 寫 1200，比這次實跑時寫的 900 大：<code>SOFTSTOP</code> 最多等作業系統關機 15 分鐘（900 秒），作業系統拖滿 15 分鐘才關機時，等待上限也是 900 秒的話，CLI 會先以逾時結束，主機卻還在停（從這兩個 900 推論）。這次 69 秒就停好了，沒有碰到這個上限。</p>
<p>05:01:31 送出，05:02:40 回 <code>STOPPED</code>，共 69 秒。這個時間取決於主機上的服務停下來要多久，上限是上面那 15 分鐘。停止之後讀到的狀態：</p>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>讀法</th>
          <th>結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>公開 IP</td>
          <td><code>list-vnics</code> 的 <code>public-ip</code>，與 <code>oci network vnic get</code> 的 <code>public-ip</code></td>
          <td>跟停止前相同，VNIC 的狀態是 <code>AVAILABLE</code></td>
      </tr>
      <tr>
          <td>經 Cloudflare 請求首頁</td>
          <td>〈收窄入口：把 NSG 從網卡拿掉，以及 Cloudflare 回的 522〉的 <code>curl</code></td>
          <td><code>522</code>，20.4 秒</td>
      </tr>
      <tr>
          <td>SSH</td>
          <td>同一組登入設定</td>
          <td><code>Operation timed out</code></td>
      </tr>
      <tr>
          <td>A1 的核心數與記憶體用量</td>
          <td>〈暫停前記下的基準：接聽的程式、服務、待裝的更新、憑證效期與公開 IP〉的 <code>limits resource-availability get</code>，記憶體把 limit 名稱換成 <code>standard-a1-memory-count</code></td>
          <td>3 與 18；核心數跟停止前相同，記憶體 18 等於三台各 6 GB：停止中的主機仍算在用量裡</td>
      </tr>
      <tr>
          <td>免費區塊儲存的用量</td>
          <td><code>oci limits resource-availability get -c &lt;tenancy 的 OCID&gt; --service-name block-storage --limit-name total-free-storage-gb-regional --query 'data.used' --raw-output</code>（區域層的這一條不帶 <code>--availability-domain</code>）</td>
          <td>141，等於三台的開機磁碟各 47</td>
      </tr>
      <tr>
          <td>開機磁碟</td>
          <td><code>oci bv boot-volume get --boot-volume-id &lt;開機磁碟的 OCID&gt;</code>；OCID 用 <code>oci compute boot-volume-attachment list -c &lt;compartment 的 OCID&gt; --availability-domain &lt;可用網域&gt; --instance-id &lt;主機的 OCID&gt; --query 'data[0].&quot;boot-volume-id&quot;' --raw-output</code> 查</td>
          <td>47 GB、<code>AVAILABLE</code></td>
      </tr>
  </tbody>
</table>
<p>公開 IP 留在主機上，跟暫停那篇引的 Oracle Cloud 文件一致：停止時臨時公開 IP 還在，終止時才刪除。所以這台主機停止期間，DNS 紀錄指著的仍是自己的 IP，訪客看到的是 522，不會連到別人的主機；本節開頭那段寫的前提（業者停止時不收回 IP）在這裡成立。</p>
<p>停止中的主機仍算在 A1 的 limits 用量裡，也就是決定還能再建多少 A1 的那個用量，跟計費是兩回事（計費見下面〈停止期間的計費：usage API 逐小時的明細〉）。用量已經到上限時，停機期間這份 A1 的額度不能拿去建另一台主機（上限與剩餘量用同一條指令把 <code>--query</code> 換成 <code>'data.{used:used,available:available}'</code> 讀），要讓出額度得終止主機，也就是暫停那篇表裡「終止主機、保留磁碟與網路設定」那一種範圍：主機刪掉，磁碟與網路設定留著。limits 用量也不等於實體的主機容量：停止中的 A1 再啟動時會不會遇到容量不足（A1 建立時常見的 <code>Out of host capacity</code>），Oracle 停止與啟動主機的<a href="https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/restartinginstance.htm">說明頁</a>（2026-10-27 讀）沒有寫，這次 2 小時後的啟動沒有遇到。暫停期長、而那一區的 A1 常常容量不足時，停止之前先備份開機磁碟，啟動失敗時才有從備份換到別的可用網域或機型重建的路（沒有實跑）。</p>
<h2 id="停止期間的計費usage-api-逐小時的明細">停止期間的計費：usage API 逐小時的明細</h2>
<p>停止期間 A1 是否計費，用 usage API 依 SKU（帳務明細上的計費品項）與資源逐小時讀。逐小時的明細大約落後 2 小時，所以這一段在恢復之後才查得到；<code>usage-api</code> 各參數的讀法寫在 <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〉：</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">oci usage-api usage-summary request-summarized-usages --tenant-id &lt;tenancy 的 OCID&gt; <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --time-usage-started 2026-10-27T04:00:00Z --time-usage-ended 2026-10-27T09:00:00Z <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --granularity HOURLY --query-type USAGE --group-by <span class="s1">&#39;[&#34;skuName&#34;,&#34;skuPartNumber&#34;,&#34;resourceId&#34;]&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data.items[].{hour:&#34;time-usage-started&#34;,sku:&#34;sku-name&#34;,res:&#34;resource-id&#34;,qty:&#34;computed-quantity&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --output table</span></span></code></pre></div><p>起訖換成要看的時段（UTC 的整點）。這台主機與它的開機磁碟在各小時記到的項目（<code>res</code> 欄對回主機與開機磁碟的 OCID）：</p>
<table>
  <thead>
      <tr>
          <th>小時（UTC）</th>
          <th>主機的狀態</th>
          <th><code>Standard - A1</code>（OCPU 小時）</th>
          <th><code>Standard - A1 - Memory</code>（GB 小時）</th>
          <th>開機磁碟的 <code>Block Volume - Free</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>04 時</td>
          <td>整小時 <code>RUNNING</code></td>
          <td>1.0</td>
          <td>6.0</td>
          <td>0.0632</td>
      </tr>
      <tr>
          <td>05 時</td>
          <td>05:01:31 開始停止，05:02:40 停止</td>
          <td>0.0258</td>
          <td>0.1558</td>
          <td>0.0632</td>
      </tr>
      <tr>
          <td>06 時</td>
          <td>整小時 <code>STOPPED</code></td>
          <td>沒有這一項</td>
          <td>沒有這一項</td>
          <td>0.0632</td>
      </tr>
  </tbody>
</table>
<p>05 時的 0.0258 OCPU 小時約 93 秒，換算回去是 05:01:33，跟送出 <code>SOFTSTOP</code> 的 05:01:31 差 2 秒，所以這一次 A1 的計費大約停在送出停止動作的那一刻，比主機變成 <code>STOPPED</code> 的 05:02:40 早約 1 分鐘（從這一小時的數字推算，只量了這一次）。06 時整小時停止，明細裡沒有這台主機的 A1 項目。開機磁碟每小時照樣記 0.0632，等於 47 GB 除以 744 小時（31 天的小時數；10 月正好 31 天，Oracle 換算時是否固定用 744 沒有查），記在 <code>Block Volume - Free</code>，金額 0：磁碟在主機停止時繼續存在、繼續佔免費額度。這台主機的 A1 在付費 tenancy 每月的免費量裡，金額是 0，停止省下的是那幾個小時從每月免費量裡扣掉的 OCPU 與記憶體小時數；A1 用量超過免費量的帳戶，停止那幾個小時的費用就省下來了（從這三個小時的明細推論，沒有在超過免費量的帳戶上量）。這是 A1 這類標準機型的結果；Dense I/O、HPC 與多數 GPU 機型停止期間照樣計費，見 <a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統</a>〈停止主機時的計費〉。</p>
<h2 id="恢復上線啟動漂移比對安全更新冒煙測試與重新開放入口">恢復上線：啟動、漂移比對、安全更新、冒煙測試與重新開放入口</h2>
<p>主機停了 1 小時 59 分鐘之後恢復。恢復分成主機與網路就緒、主機內部就緒、對外入口開放三段，每一段的產出是下一段的前提（暫停那篇〈恢復的順序：依元件之間的依賴排列〉）；DNS 紀錄這次一直指著同一個 IP（公開 IP 停止前後沒有變），暫停那篇「流量導回」那一段（把 DNS 紀錄改回這台主機、經 CDN 從外面驗證）只剩經 CDN 的外部驗證，併在對外入口開放之後做。</p>
<p><strong>主機與網路就緒</strong>：</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">oci compute instance action --instance-id &lt;主機的 OCID&gt; --action START <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --wait-for-state RUNNING --max-wait-seconds <span class="m">900</span> --query <span class="s1">&#39;data.&#34;lifecycle-state&#34;&#39;</span> --raw-output
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 每 5 秒試一次 SSH，連得上就停</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">for</span> i in <span class="k">$(</span>seq <span class="m">1</span> 30<span class="k">)</span><span class="p">;</span> <span class="k">do</span> ssh &lt;登入設定&gt; <span class="nb">true</span> <span class="o">&amp;&amp;</span> break<span class="p">;</span> sleep 5<span class="p">;</span> <span class="k">done</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">oci network vnic get --vnic-id &lt;VNIC 的 OCID&gt; --query <span class="s1">&#39;data.{pub:&#34;public-ip&#34;,nsg:&#34;nsg-ids&#34;}&#39;</span></span></span></code></pre></div><p>07:01:28 送出 <code>START</code>，07:02:03 回 <code>RUNNING</code>（35 秒），SSH 在 07:02:21 第 4 次嘗試時連上（<code>RUNNING</code> 之後 18 秒），從送出到連得上共 53 秒。這幾個時間依機型、映像檔與開機時起來的服務而變（這次是 A1、Oracle Linux 10.2、兩個 enabled 的服務），上面的 <code>--wait-for-state</code> 與 SSH 迴圈都是依回應自己等，不依賴這個值。SSH 迴圈這次沒有帶 <code>-o ConnectTimeout</code>，主機還沒起來時一次嘗試可能等到連線逾時才換下一次；加上 <code>-o ConnectTimeout=5</code>，每次嘗試最多等 5 秒（沒有實跑）。迴圈跑完 30 次都沒連上時，它不會報錯，後面的指令照樣接著跑；這時除了主機還沒起來，也要比對 security list 放行的來源跟自己現在的對外 IP，暫停期間管理者的 IP 換了也是同樣的逾時。<code>vnic get</code> 讀到的公開 IP 跟停止前相同，<code>nsg-ids</code> 還是 <code>[]</code>；經 Cloudflare 請求首頁照樣回 522（20.2 秒），入口還收窄著。</p>
<p><strong>主機內部就緒</strong>：先跟基準比，再裝更新。</p>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>暫停前</th>
          <th>恢復後（裝更新之前）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>nginx 與監控事件的服務</td>
          <td><code>enabled</code>、<code>active</code></td>
          <td><code>active</code>：enabled 的服務開機時自己起來了</td>
      </tr>
      <tr>
          <td>接聽的 TCP 連接埠</td>
          <td>22、80、443、8765，共 7 行（22、80、443 的 IPv4 與 IPv6 各一行，8765 一行）</td>
          <td>只數了這四個連接埠的接聽行數，回 7，行數相同；沒有逐一列出連接埠</td>
      </tr>
      <tr>
          <td>待裝的安全更新</td>
          <td>18 行</td>
          <td>18 行</td>
      </tr>
      <tr>
          <td>核心</td>
          <td><code>6.12.0-207.111.5.6.el10uek</code></td>
          <td>相同</td>
      </tr>
      <tr>
          <td>公開 IP</td>
          <td>停止前的值</td>
          <td>相同</td>
      </tr>
  </tbody>
</table>
<p>這張表是手動逐項比的，接聽的連接埠只數了行數，行數相同而連接埠換了的情形看不出來。比較完整的做法是恢復時重跑暫停前那一整段指令、輸出存成 <code>post-resume.log</code>，再用 <code>diff pre-suspend.log post-resume.log</code> 看差異（這次沒有這樣做）。暫停期間只有 2 小時，待裝的安全更新數量沒有變。暫停幾週時這個數量會變多，裝更新的時間跟著變長（推論，這次沒有量長的暫停）。接著裝安全更新、判斷要不要重開機：</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">ssh &lt;登入設定&gt; <span class="s1">&#39;sudo dnf -y -q upgrade --security; sudo needs-restarting -r&#39;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh &lt;登入設定&gt; <span class="s1">&#39;echo sec=$(sudo dnf -q check-update --security | grep -c .)&#39;</span></span></span></code></pre></div><p>07:03:12 開始、07:03:40 結束（28 秒），裝了 18 個套件：<code>bind</code> 的三個套件、<code>gcc-toolset-15</code> 的四個、<code>kernel-headers</code>、<code>kernel-tools</code>、<code>kernel-tools-libs</code>、<code>libbrotli</code>、<code>nodejs</code> 的五個、<code>perf</code> 與 <code>python3-perf</code>。<code>kernel-tools</code> 這幾個是核心相關的工具，核心本身（<code>kernel-uek-core</code>）不在這一批裡，<code>needs-restarting -r</code> 印 <code>No core libraries or services have been updated since boot-up.</code> 與 <code>Reboot should not be necessary.</code>，所以沒有重開機。裝完之後待裝的安全更新是 0。核心在一批更新裡的時候，<code>needs-restarting -r</code> 會列出要重開機的原因、以 1 結束：2026-10-26 在另一台 Oracle Linux 10.2 主機上裝了 <code>kernel-uek-core</code> 的更新之後，它印 <code>Core libraries or services have been updated since boot-up:</code>、列出 <code>kernel-uek-core</code>，以 1 結束（實跑）。遇到這種情形，重開機（<code>sudo systemctl reboot</code>）排在開放入口之前，重開之後重跑上面的 SSH 迴圈與下面的 loopback 冒煙測試。</p>
<p><code>-r</code> 只判斷要不要重開機；更新了服務已經載入的共用函式庫時（常見的是 <code>openssl-libs</code>；這一批裡的共用函式庫是 <code>libbrotli</code>，有沒有被 nginx 或那個服務載入這次沒有查），已經在跑的程序在重新啟動之前用的仍是記憶體裡的舊版。<code>needs-restarting -s</code> 列出受影響的 systemd 服務，重啟它們之後再開放入口（<code>-s</code> 在 <code>oraclelinux:10</code> 容器的 <code>--help</code> 列得出來，這次沒有在主機上跑）。</p>
<p><code>--security</code> 只認套件來源裡帶 updateinfo（安全公告）中繼資料的更新，nginx 官方套件來源這類第三方來源的安全修補不會算進去（依 dnf 的說明推論，這台主機只用 Oracle Linux 的套件來源）。</p>
<p>這一步的其他做法：<code>dnf upgrade</code>（不加 <code>--security</code>）把非安全性的更新一起裝上，裝的套件更多、時間更長，換到的是恢復之後不必另外安排一般更新；<code>dnf-automatic</code> 這類自動更新在主機停止期間不會執行，恢復時照樣要補裝。另一種做法是用最新的映像檔重建主機：省下裝更新與可能的重開機，代價是網路、nginx 與服務都要照回源那篇重建，臨時公開 IP 會換、DNS 紀錄要改（暫停那篇〈恢復的順序：依元件之間的依賴排列〉）。這次只比較了要裝的範圍，三種另外的做法都沒有實跑。</p>
<p>只對 loopback 的冒煙測試：<code>curl http://127.0.0.1/</code> 與 <code>curl -k https://127.0.0.1/</code> 都回 200。監控事件的服務對 <code>/</code> 回 404，它的根路徑本來就沒有內容；要驗證它，要打它實際接收事件的路徑，這次沒有做。</p>
<p><strong>對外入口開放</strong>：NSG 裡的規則是暫停前 Cloudflare 公布的 IPv4 位址段。暫停期間 Cloudflare 新增了位址段的話，從新位址段回源的請求沒有規則放行、會回 522，而且只有部分請求失敗。反方向也要看：Cloudflare 撤回的位址段，舊規則會繼續放行，而那些位址已經不屬於 Cloudflare。所以掛回之前，先把 Cloudflare 公布的 IPv4 清單與 NSG 規則的來源位址比對，兩個方向的差異都處理，有新增的照回源那篇的寫法補進規則（每次 API 呼叫最多加 25 條）。這次暫停 2 小時，沒有做這一步比對；下面兩條印出兩邊的清單（沒有實跑）：</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">curl -s https://www.cloudflare.com/ips-v4 <span class="p">|</span> sort &gt; cf-v4.txt
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># &lt;放行 Cloudflare 的 NSG 的 OCID&gt; 是 nsg-ids.json 裡那一個；80 與 443 各一條，所以每個來源出現兩次，sort -u 去重</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">oci network nsg rules list --nsg-id &lt;放行 Cloudflare 的 NSG 的 OCID&gt; --all <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;data[?direction==`INGRESS`].source&#39;</span> --raw-output <span class="p">|</span> tr -d <span class="s1">&#39;[]&#34;, &#39;</span> <span class="p">|</span> grep . <span class="p">|</span> sort -u &gt; nsg-v4.txt
</span></span><span class="line"><span class="ln">5</span><span class="cl">diff cf-v4.txt nsg-v4.txt    <span class="c1"># 「&lt;」是 Cloudflare 新增、NSG 還沒有的；「&gt;」是 NSG 有、Cloudflare 已經撤回的</span></span></span></code></pre></div><p>接著把存下來的 NSG 清單掛回網卡，再從外面驗證：</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"># &lt;NSG 的 OCID&gt; 是 nsg-ids.json 裡的值；原本掛著好幾個時，清單要寫回 nsg-ids.json 裡的全部，否則其餘的不會掛回去</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># （OCI CLI 的清單參數也收 --nsg-ids file://nsg-ids.json，直接讀存下來的檔，這種寫法沒有實跑）</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">oci network vnic update --vnic-id &lt;VNIC 的 OCID&gt; --nsg-ids <span class="s1">&#39;[&#34;&lt;NSG 的 OCID&gt;&#34;]&#39;</span> --force
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># &lt;公開 IP&gt; 是〈暫停前記下的基準：接聽的程式、服務、待裝的更新、憑證效期與公開 IP〉那條 list-vnics 印的 pub 欄</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">curl -s -o /dev/null -w <span class="s1">&#39;direct %{http_code}\n&#39;</span> --max-time <span class="m">8</span> http://&lt;公開 IP&gt;/      <span class="c1"># 繞過 Cloudflare 直連</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">curl -s -o /dev/null -w <span class="s1">&#39;cf %{http_code} %{time_total}\n&#39;</span> --max-time <span class="m">40</span> https://&lt;網域&gt;/</span></span></code></pre></div><p>07:04:05 掛回，07:04:15 直連回 <code>000</code>（8 秒逾時）：NSG 只放行 Cloudflare 的位址段，開發機的 IP 進不來。07:04:16 經 Cloudflare 回 <code>200</code>（1.19 秒），<code>www</code> 的根路徑 07:04:24 也回 200（07:04:18 對 <code>www</code> 打的是網站改版前的舊路徑，回 404，跟入口無關）。第一次經 Cloudflare 的請求是在掛回之後約 10 秒才送出的（前面先跑了逾時 8 秒的直連），所以量到的只是 NSG 在 10 秒之內生效，生效最快要多久沒有量到。</p>
<p>入口要等更新裝完才開，是因為這台主機的服務在開機的那一刻就起來了（上表的 <code>active</code>）。恢復時沒有收窄入口的話，從 07:02:03 主機 <code>RUNNING</code> 到 07:03:40 更新裝完的 97 秒裡，主機上還有 18 個安全更新沒裝，nginx 與監控事件的服務已經在接受 Cloudflare 送來的連線。這一批的 18 個套件裡沒有 nginx 與那個服務本身的套件；它們載入的共用函式庫有沒有在這一批裡、要不要重啟，這次沒有用 <code>needs-restarting -s</code> 查。換一批更新、裡面有接聽的程式或它用的函式庫時，那段時間裡對外接聽的就是缺了修補的程式。暫停期越長、要裝的更新越多、要重開機時，這段時間越長。</p>
<p>從送出 <code>START</code> 到經 Cloudflare 回 200，整個恢復花了 2 分 48 秒；其中裝更新 28 秒，其餘是等主機啟動與手動逐步確認的時間。</p>
<h2 id="收尾dns-紀錄主機與網路資源的刪除順序">收尾：DNS 紀錄、主機與網路資源的刪除順序</h2>
<p>演練完，這台主機與它的網路要收掉。順序是先刪 DNS 紀錄、再終止主機、最後刪網路資源。先刪 DNS 紀錄，是因為終止主機時臨時公開 IP 會還給 Oracle Cloud，DNS 紀錄還指著它就是上面〈停止主機：SOFTSTOP、耗時、公開 IP 的去留與停止中的用量〉寫的懸空 DNS（完整的後果見暫停那篇〈暫停期間仍指向這台主機的外部設定〉）。網路資源排在主機之後，是因為它們被主機的網卡引用著，主機還在時刪不掉。指著這個 IP 的不只 DNS 紀錄：外部監控的探測、別處的 IP 白名單、CDN 的其他回源設定也要在終止之前一起移除（暫停那篇〈暫停期間仍指向這台主機的外部設定〉）；這台主機只有 DNS 紀錄指著它。要讓終止或重建前後 IP 不變，另一種做法是一開始就用保留的公開 IP（reserved public IP），它存在到被刪除為止，DNS 紀錄不必改，懸空 DNS 也不會發生（<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>〈公開 IP：網際網路上的入口〉；保留 IP 的計費這次沒有查）。</p>
<ol>
<li>
<p><strong>刪 DNS 紀錄</strong>：在 Cloudflare 主控台的 DNS → Records 頁，刪掉根網域與 <code>www</code> 兩筆指到這台主機的 A 紀錄（每一筆從右側的選單選 Delete record，再按確認），郵件的 MX、SPF、DKIM 紀錄不動。刪完之後直接問網域的權威 nameserver，避開遞迴解析器的快取，刪除當下就讀得到結果；<code>&lt;權威 nameserver&gt;</code> 用 <code>dig NS &lt;網域&gt; +short</code> 查。經過 Cloudflare 代理的紀錄，訪客解析到的是 Cloudflare 的位址，紀錄一刪，Cloudflare 就不再回源到這台主機，所以這次確認刪除之後 24 秒就終止主機。不經代理、直接指公開 IP 的紀錄，訪客的遞迴解析器在舊紀錄的 TTL（DNS 回應可以被快取多久，見 <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> 卡）到期之前還會回這個 IP，要從刪除那一刻起等過舊紀錄的 TTL 再終止主機；TTL 要在刪除之前讀，刪掉之後就讀不到了，<code>dig A &lt;網域&gt; @&lt;權威 nameserver&gt;</code> 的 ANSWER 段第二欄就是它（從 DNS 快取的機制推論，這次的紀錄都經過代理）：</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">dig A &lt;網域&gt; @&lt;權威 nameserver&gt;        <span class="c1"># 看 status 與有沒有 ANSWER</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">dig A www.&lt;網域&gt; @&lt;權威 nameserver&gt;
</span></span><span class="line"><span class="ln">3</span><span class="cl">dig MX &lt;網域&gt; @&lt;權威 nameserver&gt; +short  <span class="c1"># 郵件的紀錄還在</span></span></span></code></pre></div><p>07:19:54 查：根網域回 <code>NOERROR</code> 而沒有 A 紀錄（根網域上還有 MX 這些紀錄，所以名稱存在），<code>www</code> 回 <code>NXDOMAIN</code>；<code>dig MX</code> 照樣列出三筆。</p>
</li>
<li>
<p><strong>終止主機並刪掉開機磁碟</strong>：</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">oci compute instance terminate --instance-id &lt;主機的 OCID&gt; --preserve-boot-volume <span class="nb">false</span> --force <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --wait-for-state SUCCEEDED --wait-for-state FAILED --max-wait-seconds <span class="m">900</span></span></span></code></pre></div><p>07:20:18 送出，07:21:24 work request <code>SUCCEEDED</code>（66 秒）。<code>--preserve-boot-volume false</code> 讓開機磁碟一起刪掉，免費區塊儲存的 47 GB 才會釋放；不帶時開機磁碟留下來、繼續佔額度（回源那篇的清理寫了這一點；不帶的情形這次沒有實跑）。<code>terminate</code> 的 <code>--wait-for-state</code> 只收 work request 的四種狀態（<code>ACCEPTED</code>、<code>IN_PROGRESS</code>、<code>FAILED</code>、<code>SUCCEEDED</code>）。寫成主機的 <code>TERMINATED</code> 時 CLI 以 2 結束、請求不會送出（同一天對另一台主機實跑；後續網路資源跟著刪不掉的經過在回源那篇〈清理與 Cloudflare 設定的還原〉）。帶 <code>SUCCEEDED</code> 與 <code>FAILED</code> 兩個值時，CLI 等到其中一個就回傳，回傳的 work request 的 <code>status</code> 欄才分得出成功或失敗。</p>
</li>
<li>
<p><strong>刪網路資源</strong>：照回源那篇〈清理與 Cloudflare 設定的還原〉的順序刪 NSG、公開子網路、security list、route table、internet gateway，每一條都帶 <code>--wait-for-state TERMINATED</code>（這幾種資源的刪除收的是資源本身的狀態）。五條在 16 秒內跑完，之後每一個 <code>get</code> 都回 <code>NotAuthorizedOrNotFound</code>。</p>
</li>
</ol>
<p>終止之後，免費區塊儲存的用量少了這顆開機磁碟的 47（實跑讀到）；A1 的用量照理少 1 OCPU、6 GB，終止之後沒有讀。</p>
<h2 id="延伸閱讀暫停的範圍與恢復的順序回源的網路帳務明細的查法">延伸閱讀：暫停的範圍與恢復的順序、回源的網路、帳務明細的查法</h2>
<ul>
<li><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>：本篇演練的「停止主機」以外的暫停範圍（只停服務、終止主機但保留磁碟與網路設定、全部刪除再從 repo 重建）、怎麼依保留的月費與重建的成本選擇、恢復時憑證續期的時機</li>
<li><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>：這台主機的公開子網路、NSG 與 nginx 怎麼建的，以及網路資源的刪除順序與各自的錯誤訊息</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>：usage API 查帳務明細的每個參數、開機磁碟與免費區塊儲存額度的查法、超過額度的磁碟整顆計費與自動轉成免費</li>
<li><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>
</ul>
]]></content:encoded></item><item><title>安全開機：Secure Boot、Measured Boot 與 TPM</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shielded-boot/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shielded-boot/</guid><description>&lt;p>在開機階段植入的惡意程式（bootkit、rootkit）在作業系統啟動之前就載入，作業系統裡的防毒與稽核工具看不到它。建立頁上保護開機過程的選項就是針對這一類程式：Oracle Cloud 把它們歸在 Security 區塊下的 Shielded instance，官方說明是用 Secure Boot、Measured Boot 與 TPM 三者的組合強化韌體層的安全。&lt;/p>
&lt;h2 id="secure-bootmeasured-boot-與-tpm-各自的作用">Secure Boot、Measured Boot 與 TPM 各自的作用&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Secure Boot&lt;/strong>：開機時韌體逐一驗證開機載入程式與核心的數位簽章，簽章不屬於受信任的發行者就拒絕載入。效果是只有作業系統廠商簽過的開機程式跑得起來。&lt;/li>
&lt;li>&lt;strong>Measured Boot&lt;/strong>：開機過程中，每一個階段的程式（韌體、開機載入程式）在載入下一個元件之前先量測那個元件，量測值寫進 TPM 的平台設定暫存器（PCR）。它不阻擋任何元件載入，只留下紀錄。Oracle Cloud 把第一次開機的量測值存成基準（golden measurements），之後每次開機時，新的量測值跟基準比對（官方〈Shielded Instances〉沒有寫比對在主機裡還是在業者那一端執行），結果顯示在主控台主機詳細資料頁的盾牌圖示上：開機順序跟基準不符時是紅色，多出新的 PCR 值時是黃色，兩種情形主機都照樣開機。官方提醒更新作業系統之後可能要重設基準，重設的步驟在同一頁。Google Cloud 對應的比對功能叫 integrity monitoring。&lt;/li>
&lt;li>&lt;strong>TPM（Trusted Platform Module）&lt;/strong>：存放開機量測值與金鑰的安全晶片，雲端主機上是虛擬的。Measured Boot 的紀錄寫在這裡，作業系統也可以用它保存磁碟加密金鑰這類秘密。&lt;/li>
&lt;/ul>
&lt;p>Secure Boot 是事前攔截，Measured Boot 是事後偵測。TPM 是 Measured Boot 存放量測值的地方；官方說明裡 Secure Boot 的簽章驗證由 UEFI 韌體完成，沒有寫它用到 TPM。Oracle Cloud 上哪幾項要一起開依作業系統而定：Windows 虛擬機三項必須一起開，勾選其中一項時主控台會自動勾上另外兩項；Linux 虛擬機可以只開 Secure Boot、只開 TPM 加 Measured Boot，或三項全開。&lt;/p>
&lt;h2 id="oracle-cloud-上-shielded-instance-的限制">Oracle Cloud 上 shielded instance 的限制&lt;/h2>
&lt;p>Oracle Cloud 的官方〈Shielded Instances〉寫明，shielded instance 建好之後編輯主機時只能改名稱：機型不能改、遷移的設定也不能改，三項安全開機的設定同樣不能事後開關。所以開了安全開機的主機，核心數與記憶體要在建立時就定好，要改就重建。同一頁另外列了下面這些限制：&lt;/p>
&lt;ul>
&lt;li>不支援自訂映像，只能用業者的平台映像建立。&lt;/li>
&lt;li>不支援即時遷移與重開機遷移：業者維護底層硬體時，不會替這台主機做自動遷移，官方寫的是要遷移就由使用者手動遷移，搬到新硬體之後 Secure Boot 的金鑰與開機量測值會在新主機上重新取得。官方〈Infrastructure Maintenance〉的手動遷移做法是終止主機、再用保留下來的開機磁碟建一台新的，開了 TPM 的主機另有額外步驟；維護事件發生時 shielded 虛擬機要停機多久、業者是否事先通知，兩頁都沒有單獨寫。其他主機的維護行為見 &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;li>跟機密運算（confidential computing，CPU 在運算時也把記憶體裡的資料加密）互斥，一台主機只能選其中一種。&lt;/li>
&lt;/ul>
&lt;p>這幾條是 Oracle Cloud 的規則。Google Cloud 的 Confidential VM 本身就是 Shielded VM，兩者不互斥。&lt;/p>
&lt;h2 id="實驗主機開啟安全開機的取捨">實驗主機開啟安全開機的取捨&lt;/h2>
&lt;p>官方〈Shielded Instances〉沒有提到開啟這三項會另外收費；要確認自己的帳號，建好之後到帳單的成本分析頁看有沒有出現新的計費項目。開啟的代價在相容性：Secure Boot 會拒絕沒有簽章的核心模組與自己編譯的核心，要裝第三方驅動程式（部分 GPU、虛擬化或網路驅動）或自訂核心時會開不了機或載入失敗。業者的平台映像本身是簽過的，只跑一般套件時開著不會有感。在 Oracle Cloud 上另有一個代價在彈性：開了之後機型不能改，實驗機「先開小、不夠再加」的做法就走不通。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>跑一般服務、使用平台映像&lt;/strong>：開或不開都可以；有合規要求（要求開機鏈可驗證）時開。&lt;/li>
&lt;li>&lt;strong>要裝自訂核心或沒有簽章的驅動程式&lt;/strong>：不開，或先確認那個驅動程式支援 Secure Boot 的簽章流程。在 Linux 上，這個流程是用自己的金鑰簽驅動程式、再把那把金鑰登錄進開機鏈信任的清單（Machine Owner Key，MOK）；合規要求開機鏈可驗證、又要裝這類驅動程式時，就要走這條路，做法以那個驅動程式與發行版的文件為準。這一組條件最容易在主機建好幾個月之後才遇到：用 DKMS（套件更新時自動重新編譯核心模組的機制）安裝的驅動程式，編譯與套件安裝都會成功，到 &lt;code>modprobe&lt;/code> 載入模組時才被拒絕，錯誤訊息是 &lt;code>Key was rejected by service&lt;/code>；而 Oracle Cloud 上安全開機的設定建立後不能關，只能登錄 MOK 或重建主機。官方另外寫明，主機終止時登錄過的 MOK 會一起刪除，重建的主機要先用標準簽章的核心開機、再重新登錄 MOK。&lt;/li>
&lt;li>&lt;strong>實驗與學習用的主機&lt;/strong>：維持建立頁的預設即可，要測試這個功能時另外建一台開啟的。Oracle Cloud 建立 A1 Ubuntu 主機時建立頁上三項預設都關閉（&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">實測紀錄&lt;/a>的 Security 段）；Google Cloud 的 Shielded VM 預設開啟 vTPM 與 integrity monitoring。&lt;/li>
&lt;/ul>
&lt;h2 id="各家的欄位名稱開機保護的總稱secure-boot開機量測與-tpm">各家的欄位名稱：開機保護的總稱、Secure Boot、開機量測與 TPM&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>概念&lt;/th>
 &lt;th>Oracle Cloud&lt;/th>
 &lt;th>Google Cloud&lt;/th>
 &lt;th>AWS&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>開機保護的總稱&lt;/td>
 &lt;td>Shielded instance&lt;/td>
 &lt;td>Shielded VM&lt;/td>
 &lt;td>無總稱，個別設定&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Secure Boot&lt;/td>
 &lt;td>Secure Boot&lt;/td>
 &lt;td>Secure Boot&lt;/td>
 &lt;td>UEFI Secure Boot（AMI 的屬性）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>開機量測&lt;/td>
 &lt;td>Measured Boot&lt;/td>
 &lt;td>Integrity monitoring&lt;/td>
 &lt;td>NitroTPM 搭配作業系統的量測工具&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>TPM&lt;/td>
 &lt;td>Trusted Platform Module&lt;/td>
 &lt;td>vTPM&lt;/td>
 &lt;td>NitroTPM&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>映像檔的三種來源與架構相容的規則見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔&lt;/a>；Oracle Cloud 的 shielded instance 只能用其中的平台映像。&lt;/p></description><content:encoded><![CDATA[<p>在開機階段植入的惡意程式（bootkit、rootkit）在作業系統啟動之前就載入，作業系統裡的防毒與稽核工具看不到它。建立頁上保護開機過程的選項就是針對這一類程式：Oracle Cloud 把它們歸在 Security 區塊下的 Shielded instance，官方說明是用 Secure Boot、Measured Boot 與 TPM 三者的組合強化韌體層的安全。</p>
<h2 id="secure-bootmeasured-boot-與-tpm-各自的作用">Secure Boot、Measured Boot 與 TPM 各自的作用</h2>
<ul>
<li><strong>Secure Boot</strong>：開機時韌體逐一驗證開機載入程式與核心的數位簽章，簽章不屬於受信任的發行者就拒絕載入。效果是只有作業系統廠商簽過的開機程式跑得起來。</li>
<li><strong>Measured Boot</strong>：開機過程中，每一個階段的程式（韌體、開機載入程式）在載入下一個元件之前先量測那個元件，量測值寫進 TPM 的平台設定暫存器（PCR）。它不阻擋任何元件載入，只留下紀錄。Oracle Cloud 把第一次開機的量測值存成基準（golden measurements），之後每次開機時，新的量測值跟基準比對（官方〈Shielded Instances〉沒有寫比對在主機裡還是在業者那一端執行），結果顯示在主控台主機詳細資料頁的盾牌圖示上：開機順序跟基準不符時是紅色，多出新的 PCR 值時是黃色，兩種情形主機都照樣開機。官方提醒更新作業系統之後可能要重設基準，重設的步驟在同一頁。Google Cloud 對應的比對功能叫 integrity monitoring。</li>
<li><strong>TPM（Trusted Platform Module）</strong>：存放開機量測值與金鑰的安全晶片，雲端主機上是虛擬的。Measured Boot 的紀錄寫在這裡，作業系統也可以用它保存磁碟加密金鑰這類秘密。</li>
</ul>
<p>Secure Boot 是事前攔截，Measured Boot 是事後偵測。TPM 是 Measured Boot 存放量測值的地方；官方說明裡 Secure Boot 的簽章驗證由 UEFI 韌體完成，沒有寫它用到 TPM。Oracle Cloud 上哪幾項要一起開依作業系統而定：Windows 虛擬機三項必須一起開，勾選其中一項時主控台會自動勾上另外兩項；Linux 虛擬機可以只開 Secure Boot、只開 TPM 加 Measured Boot，或三項全開。</p>
<h2 id="oracle-cloud-上-shielded-instance-的限制">Oracle Cloud 上 shielded instance 的限制</h2>
<p>Oracle Cloud 的官方〈Shielded Instances〉寫明，shielded instance 建好之後編輯主機時只能改名稱：機型不能改、遷移的設定也不能改，三項安全開機的設定同樣不能事後開關。所以開了安全開機的主機，核心數與記憶體要在建立時就定好，要改就重建。同一頁另外列了下面這些限制：</p>
<ul>
<li>不支援自訂映像，只能用業者的平台映像建立。</li>
<li>不支援即時遷移與重開機遷移：業者維護底層硬體時，不會替這台主機做自動遷移，官方寫的是要遷移就由使用者手動遷移，搬到新硬體之後 Secure Boot 的金鑰與開機量測值會在新主機上重新取得。官方〈Infrastructure Maintenance〉的手動遷移做法是終止主機、再用保留下來的開機磁碟建一台新的，開了 TPM 的主機另有額外步驟；維護事件發生時 shielded 虛擬機要停機多久、業者是否事先通知，兩頁都沒有單獨寫。其他主機的維護行為見 <a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a> 的〈硬體維護時的行為〉。</li>
<li>跟機密運算（confidential computing，CPU 在運算時也把記憶體裡的資料加密）互斥，一台主機只能選其中一種。</li>
</ul>
<p>這幾條是 Oracle Cloud 的規則。Google Cloud 的 Confidential VM 本身就是 Shielded VM，兩者不互斥。</p>
<h2 id="實驗主機開啟安全開機的取捨">實驗主機開啟安全開機的取捨</h2>
<p>官方〈Shielded Instances〉沒有提到開啟這三項會另外收費；要確認自己的帳號，建好之後到帳單的成本分析頁看有沒有出現新的計費項目。開啟的代價在相容性：Secure Boot 會拒絕沒有簽章的核心模組與自己編譯的核心，要裝第三方驅動程式（部分 GPU、虛擬化或網路驅動）或自訂核心時會開不了機或載入失敗。業者的平台映像本身是簽過的，只跑一般套件時開著不會有感。在 Oracle Cloud 上另有一個代價在彈性：開了之後機型不能改，實驗機「先開小、不夠再加」的做法就走不通。</p>
<ul>
<li><strong>跑一般服務、使用平台映像</strong>：開或不開都可以；有合規要求（要求開機鏈可驗證）時開。</li>
<li><strong>要裝自訂核心或沒有簽章的驅動程式</strong>：不開，或先確認那個驅動程式支援 Secure Boot 的簽章流程。在 Linux 上，這個流程是用自己的金鑰簽驅動程式、再把那把金鑰登錄進開機鏈信任的清單（Machine Owner Key，MOK）；合規要求開機鏈可驗證、又要裝這類驅動程式時，就要走這條路，做法以那個驅動程式與發行版的文件為準。這一組條件最容易在主機建好幾個月之後才遇到：用 DKMS（套件更新時自動重新編譯核心模組的機制）安裝的驅動程式，編譯與套件安裝都會成功，到 <code>modprobe</code> 載入模組時才被拒絕，錯誤訊息是 <code>Key was rejected by service</code>；而 Oracle Cloud 上安全開機的設定建立後不能關，只能登錄 MOK 或重建主機。官方另外寫明，主機終止時登錄過的 MOK 會一起刪除，重建的主機要先用標準簽章的核心開機、再重新登錄 MOK。</li>
<li><strong>實驗與學習用的主機</strong>：維持建立頁的預設即可，要測試這個功能時另外建一台開啟的。Oracle Cloud 建立 A1 Ubuntu 主機時建立頁上三項預設都關閉（<a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">實測紀錄</a>的 Security 段）；Google Cloud 的 Shielded VM 預設開啟 vTPM 與 integrity monitoring。</li>
</ul>
<h2 id="各家的欄位名稱開機保護的總稱secure-boot開機量測與-tpm">各家的欄位名稱：開機保護的總稱、Secure Boot、開機量測與 TPM</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>開機保護的總稱</td>
          <td>Shielded instance</td>
          <td>Shielded VM</td>
          <td>無總稱，個別設定</td>
      </tr>
      <tr>
          <td>Secure Boot</td>
          <td>Secure Boot</td>
          <td>Secure Boot</td>
          <td>UEFI Secure Boot（AMI 的屬性）</td>
      </tr>
      <tr>
          <td>開機量測</td>
          <td>Measured Boot</td>
          <td>Integrity monitoring</td>
          <td>NitroTPM 搭配作業系統的量測工具</td>
      </tr>
      <tr>
          <td>TPM</td>
          <td>Trusted Platform Module</td>
          <td>vTPM</td>
          <td>NitroTPM</td>
      </tr>
  </tbody>
</table>
<p>映像檔的三種來源與架構相容的規則見 <a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔</a>；Oracle Cloud 的 shielded instance 只能用其中的平台映像。</p>
]]></content:encoded></item><item><title>建立主機之前的檢查：建好就改不了的設定、登入路徑上各服務的支援組合、兩條登入路徑各自依賴的元件，以及容量稀缺時的取捨</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/pre-launch-checks/</link><pubDate>Sun, 25 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/pre-launch-checks/</guid><description>&lt;p>雲端主機建立頁上的設定，有一部分建立之後只能重建才改得了，而登入要經過的業者服務各自只在一部分機型與作業系統上成立。這一篇把這兩類條件整理成按下建立之前要做的檢查：建好就改不了的設定，以及怎麼從業者的文件與工具查出來；登入路徑上每一個業者服務支援的機型與作業系統，以及元件狀態顯示正常為什麼不能當成支援的證據；兩條登入路徑各自依賴哪些元件、哪些元件仍然共用、不經過 sshd 的救援入口，以及停用其他入口之前要先確認什麼；機型容量難取得時，設定為什麼要選補救代價低的那一種；以及 Oracle Cloud、Google Cloud 與 AWS 查這些事的位置。各項設定本身的機制在 &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;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統&lt;/a>、&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;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;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機：Secure Boot、Measured Boot 與 TPM&lt;/a>；一次沒做這些檢查的實測經過（2026-10-25 在 Oracle Cloud 的 &lt;code>ap-batam-1&lt;/code> 建了一台 A1 Ubuntu 主機，建好之後登不進去；之後改用 Oracle Linux 並放公鑰重建一台，經由 port forwarding session 登入）在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄&lt;/a>。&lt;/p>
&lt;h2 id="建好就改不了的設定補救的代價與查法">建好就改不了的設定：補救的代價與查法&lt;/h2>
&lt;p>建立頁上的設定依建立之後的處置分成三種：可以直接改（Oracle Cloud 的 plugin 開關、磁碟的效能等級）、改的時候要停機或重開（核心數與記憶體）、只能重建（CPU 架構與作業系統）。只能重建的設定，代價出現在發現選錯的那一刻：重建主機會換掉臨時公開 IP（私有 IP 沒有在建立時指定也會換）、開機磁碟上的資料要另外搬，機型容量難取得時還要再搶一次容量。所以建立之前要逐項找出只能重建的設定，替每一項寫出選錯時的補救路徑與代價。&lt;/p>
&lt;p>同一項設定在三家的處置不同，所以這份清單要對著業者查，不能從另一家的經驗推過來。以 SSH 公鑰為例：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Oracle Cloud&lt;/strong>：公鑰與初始化腳本存在主機的中繼資料裡，&lt;code>oci compute instance update --help&lt;/code> 對 &lt;code>--metadata&lt;/code> 的說明寫明 &lt;code>ssh_authorized_keys&lt;/code> 與 &lt;code>user_data&lt;/code> 在主機建立之後不能改，送出修改的請求會被拒絕。cloud-init 只在第一次開機時把公鑰寫進 &lt;code>authorized_keys&lt;/code>，所以建立時選了不放金鑰，之後就沒有經由中繼資料補公鑰的方式；剩下的入口（代理程式臨時放公鑰、Run Command、把開機磁碟掛到另一台主機）各有前提，見 &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>Google Cloud&lt;/strong>：公鑰存在專案或主機的中繼資料裡，建立之後可以再加。Google 的映像預先裝了 guest environment（業者裝在主機裡的一組程式），其中的 guest agent 持續依中繼資料同步金鑰，加進去的公鑰會寫進主機。&lt;/li>
&lt;li>&lt;strong>AWS&lt;/strong>：EC2 Instance Connect 每次連線前把一把公鑰暫時推送到主機的中繼資料，不依賴建立時選的 key pair。條件是主機上裝了 EC2 Instance Connect 的套件、登入者有對應的 IAM 權限，以及連線到得了主機：官方〈Prerequisites for EC2 Instance Connect〉寫明主機要有公開 IP，或經由 Direct Connect、Site-to-Site VPN、VPC peering 這類私有連線，沒有公開 IP 時另一個選擇是 EC2 Instance Connect Endpoint。&lt;/li>
&lt;/ul>
&lt;p>其他類設定的處置寫在各自的那一篇：架構與映像檔見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統&lt;/a>（換作業系統要用新的映像檔重建；Oracle Cloud 可以替換開機磁碟，官方〈Replacing a Boot Volume〉限定只能換成同一個 Linux 發行版，Ubuntu 換不成 Oracle Linux），主要網卡所在的 subnet 與公開 IP 的種類見 &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 的安全開機見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機：Secure Boot、Measured Boot 與 TPM&lt;/a>，區域與可用區見 &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;/p></description><content:encoded><![CDATA[<p>雲端主機建立頁上的設定，有一部分建立之後只能重建才改得了，而登入要經過的業者服務各自只在一部分機型與作業系統上成立。這一篇把這兩類條件整理成按下建立之前要做的檢查：建好就改不了的設定，以及怎麼從業者的文件與工具查出來；登入路徑上每一個業者服務支援的機型與作業系統，以及元件狀態顯示正常為什麼不能當成支援的證據；兩條登入路徑各自依賴哪些元件、哪些元件仍然共用、不經過 sshd 的救援入口，以及停用其他入口之前要先確認什麼；機型容量難取得時，設定為什麼要選補救代價低的那一種；以及 Oracle Cloud、Google Cloud 與 AWS 查這些事的位置。各項設定本身的機制在 <a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a>、<a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統</a>、<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>、<a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 與 <a href="/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機：Secure Boot、Measured Boot 與 TPM</a>；一次沒做這些檢查的實測經過（2026-10-25 在 Oracle Cloud 的 <code>ap-batam-1</code> 建了一台 A1 Ubuntu 主機，建好之後登不進去；之後改用 Oracle Linux 並放公鑰重建一台，經由 port forwarding session 登入）在 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a>。</p>
<h2 id="建好就改不了的設定補救的代價與查法">建好就改不了的設定：補救的代價與查法</h2>
<p>建立頁上的設定依建立之後的處置分成三種：可以直接改（Oracle Cloud 的 plugin 開關、磁碟的效能等級）、改的時候要停機或重開（核心數與記憶體）、只能重建（CPU 架構與作業系統）。只能重建的設定，代價出現在發現選錯的那一刻：重建主機會換掉臨時公開 IP（私有 IP 沒有在建立時指定也會換）、開機磁碟上的資料要另外搬，機型容量難取得時還要再搶一次容量。所以建立之前要逐項找出只能重建的設定，替每一項寫出選錯時的補救路徑與代價。</p>
<p>同一項設定在三家的處置不同，所以這份清單要對著業者查，不能從另一家的經驗推過來。以 SSH 公鑰為例：</p>
<ul>
<li><strong>Oracle Cloud</strong>：公鑰與初始化腳本存在主機的中繼資料裡，<code>oci compute instance update --help</code> 對 <code>--metadata</code> 的說明寫明 <code>ssh_authorized_keys</code> 與 <code>user_data</code> 在主機建立之後不能改，送出修改的請求會被拒絕。cloud-init 只在第一次開機時把公鑰寫進 <code>authorized_keys</code>，所以建立時選了不放金鑰，之後就沒有經由中繼資料補公鑰的方式；剩下的入口（代理程式臨時放公鑰、Run Command、把開機磁碟掛到另一台主機）各有前提，見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈建立之後加金鑰與金鑰遺失時的入口〉。</li>
<li><strong>Google Cloud</strong>：公鑰存在專案或主機的中繼資料裡，建立之後可以再加。Google 的映像預先裝了 guest environment（業者裝在主機裡的一組程式），其中的 guest agent 持續依中繼資料同步金鑰，加進去的公鑰會寫進主機。</li>
<li><strong>AWS</strong>：EC2 Instance Connect 每次連線前把一把公鑰暫時推送到主機的中繼資料，不依賴建立時選的 key pair。條件是主機上裝了 EC2 Instance Connect 的套件、登入者有對應的 IAM 權限，以及連線到得了主機：官方〈Prerequisites for EC2 Instance Connect〉寫明主機要有公開 IP，或經由 Direct Connect、Site-to-Site VPN、VPC peering 這類私有連線，沒有公開 IP 時另一個選擇是 EC2 Instance Connect Endpoint。</li>
</ul>
<p>其他類設定的處置寫在各自的那一篇：架構與映像檔見 <a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統</a>（換作業系統要用新的映像檔重建；Oracle Cloud 可以替換開機磁碟，官方〈Replacing a Boot Volume〉限定只能換成同一個 Linux 發行版，Ubuntu 換不成 Oracle Linux），主要網卡所在的 subnet 與公開 IP 的種類見 <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 的安全開機見 <a href="/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機：Secure Boot、Measured Boot 與 TPM</a>，區域與可用區見 <a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a>。</p>
<p>哪些設定只能重建，業者自己的工具也查得到。修改主機的 API 與 CLI 說明會寫哪些欄位不能改（上面 Oracle Cloud 那一條就是從 <code>--help</code> 讀到的）；用 Terraform 管理主機時，<code>terraform plan</code> 把只能重建的屬性標成 forces replacement，在 plan 上改一次那個屬性，就看得到它屬於哪一種（見 <a href="/blog/devops/infra/knowledge-cards/terraform-plan-apply/" data-link-title="terraform plan / apply" data-link-desc="IaC 的兩個核心操作：plan 只看不動（產出差異報告）、apply 真的動（執行差異）">terraform plan / apply</a>）。</p>
<h2 id="登入路徑上各服務的支援組合支援清單known-issues-與元件狀態">登入路徑上各服務的支援組合：支援清單、Known Issues 與元件狀態</h2>
<p>登入一台沒有公開 IP 的主機，連線要經過幾個業者服務：哪個服務負責網路可達性、通道授權與主機登入驗證，見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉。每一個服務都只在一部分機型與作業系統上成立，而這份範圍寫在服務自己的文件裡，不寫在建立頁上：建立頁讓任何映像檔配任何相容架構的機型，建立會成功，登入在之後才失敗。</p>
<p>所以建立之前，對路徑上每一個服務讀下面三種文件，業者另外提供查詢工具時再查一次：</p>
<ul>
<li><strong>支援的作業系統與映像檔清單</strong>：例如 Oracle Cloud 的 Run Command 列出的是 Oracle Autonomous Linux、Oracle Linux、CentOS、Windows Server 與以它們為基礎的自訂映像，沒有 Ubuntu。不在清單上代表官方不保證，不代表一定不能執行：2026-10-25 在一台 A1 Ubuntu 主機上實測，Run Command 的 plugin 確實執行了指令，而能做到哪些事還要看下面的前置條件。AWS 的 EC2 Instance Connect 要主機用預先裝好它的 AMI，或在官方列為支援的 AMI 上自己安裝。</li>
<li><strong>Known Issues</strong>：支援清單寫的是作業系統，機型與作業系統的組合不支援的情形常常只寫在這一頁。Oracle Cloud〈Known Issues for Bastion〉的第一條是 Arm 架構的 A1 機型加非 Oracle Linux 的主機不支援 managed SSH session；這個組合在建立頁上建立會成功，A1 是 Always Free 的機型、Ubuntu 是建立頁列出的平台映像，兩者放在一起時 managed SSH session 才不支援。這一條只寫到作業系統，沒有寫版本。</li>
<li><strong>可以查詢的支援清單</strong>：文件寫到作業系統為止時，作業系統的版本之間仍然可能不同，而有些業者提供依版本查詢的工具。Oracle Cloud 的 <code>oci instance-agent available-plugins get</code> 依作業系統名稱與版本列出代理程式可用的 plugin：2026-10-25 查到 Oracle Linux 10 確定沒有 Bastion plugin，所以同樣是 A1 加 Oracle Linux，選 10 的主機少了 managed SSH session 需要的 plugin（從 plugin 清單推論，沒有在那台主機上實際建工作階段）。這類工具查不到對應時可能回傳不分版本的全部項目而不是報錯：這支工具填不存在的版本或作業系統名稱，回傳的都是同一份全部清單，裡面有 Bastion。所以版本要照抄映像檔的作業系統版本欄位，先拿一個不存在的值查一次當對照；查到的清單跟對照不同才算查到這個版本的資料，跟對照相同時分不出來，這次的 Oracle Linux 9 與 8 就是這樣，要建起來看主機上的 plugin 清單，或另找逐版本列出的文件。查法與重建的經過在 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈建立之前查映像檔版本與它可用的 plugin〉。</li>
<li><strong>前置條件</strong>：服務在主機上用哪個使用者執行、要什麼權限。Oracle Cloud 的 Run Command 以 <code>ocarun</code> 這個使用者執行，要寫其他使用者的檔案得先替它設 sudo；官方列的做法有兩種，建立時用 cloud-init 放 sudoers 檔，或建好之後登入主機設定。主機登不進去時才想用 Run Command 救援，只剩前一種，而初始化腳本是建立後不能改的欄位，所以這件事要在建立之前決定。AWS 的 Session Manager 要主機上有 SSM Agent，並讓 Systems Manager 取得操作這台主機的權限。</li>
</ul>
<p>元件的狀態欄顯示的是元件啟動了，這個組合能不能完成登入，寫在支援清單與 Known Issues 裡，兩者描述的是不同的事。Oracle Cloud 的主機頁面上，A1 Ubuntu 主機的 Bastion plugin 顯示 <code>RUNNING</code>，Bastion 的工作階段照樣在約 15 分鐘後逾時。缺了前置條件時，狀態欄也一樣顯示正常：同一台主機的 Run Command plugin 也是 <code>RUNNING</code>，指令確實執行了，而 <code>ocarun</code> 沒有 sudo，寫不了 <code>authorized_keys</code>。</p>
<p>演練能驗到的，只有演練主機與正式主機相同的那些條件。演練主機與正式主機在機型、映像檔、服務三者都相同時，演練才驗得到組合不支援的情形；上面那一條 Known Issue 只針對 A1，在 x86 機型上用同一版 Ubuntu 的 x86 映像演練，照 Known Issues 的寫法不在不支援的範圍裡（這個推論沒有實跑），演練成功只代表 x86 加 Ubuntu 這個組合支援。機型本身就是要驗的條件時，演練換不了機型，只剩讀文件這一條路。</p>
<h2 id="兩條登入路徑各自依賴的元件與仍然共用的部分">兩條登入路徑：各自依賴的元件與仍然共用的部分</h2>
<p>登入路徑上的每一個元件都可能失效：代理程式沒有啟動、服務不支援這個組合、權限漏給、出站路徑被收緊、sshd 的設定改壞。只有一條路徑時，那條路徑上任何一個元件失效，主機就沒有入口；這個代價在重建昂貴時才成立（主機上有狀態、機型容量難取得、第一次用這個機型加作業系統的組合）。主機不存狀態、重建便宜時，重建本身就是那一條備用路徑，見 <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> 的〈不可變基礎設施下的臨時授權：通道平時關閉〉。備用路徑能不能當退路，看它跟原本那條路徑共用哪些元件：共用的元件失效時兩條一起斷，例如 Oracle Cloud 的 managed SSH session 與 Run Command 都經由主機上的 Oracle Cloud Agent，代理程式停掉時兩者一起失效。所以要把兩條各自依賴的元件逐項列出來，確認建立之前最沒有把握的那一項（這次的機型加作業系統是否在支援範圍、代理程式會不會正常運作）只出現在其中一條上。</p>
<p>列的時候要把整條路徑走完，不只看名字裡的服務。Oracle Cloud 的 managed SSH session 名字裡只有 Bastion，而 plugin 做的是把公鑰臨時放進 <code>authorized_keys</code>，登入照樣經過主機的 sshd；官方〈Managed SSH Session Creation Failed〉把 sshd 設定與 <code>authorized_keys</code> 列在成因裡。三家常見的組法與它們實際依賴的元件：</p>
<ul>
<li><strong>Oracle Cloud</strong>：managed SSH session 依賴 Bastion plugin、機型與作業系統在支援範圍、代理程式連到服務端點的出站路徑；SSH port forwarding session 不經過 plugin，依賴建立時放進中繼資料、由 cloud-init 寫進 <code>authorized_keys</code> 的公鑰。兩條仍然共用 Bastion 服務與它允許連入的來源位址、建工作階段的權限、security list 放行 22 port 的規則、主機的 sshd。所以這一組擋得住 plugin 與支援範圍出錯，擋不住 sshd 改壞或 Bastion 允許的來源位址過期。</li>
<li><strong>Google Cloud</strong>：IAP TCP forwarding 加 OS Login 依賴 IAM 角色、防火牆放行 IAP 的來源位址 <code>35.235.240.0/20</code>、guest environment 處理的 OS Login，以及主機的 sshd。OS Login 啟用之後主機忽略中繼資料裡的公鑰，OS Login 開著時同一台主機上沒有另一條不經過 OS Login 的 SSH 路徑；對單台主機把中繼資料設成 <code>enable-oslogin=FALSE</code> 會恢復 guest agent 同步公鑰的路徑（主機層級的設定蓋過專案層級），組織政策強制 OS Login 時這條用不了，而且它仍然共用 IAP、防火牆、guest agent 與 sshd，算不上獨立的備用路徑。所以常備的備用路徑是序列主控台（serial console）加事先建好的救援帳號：它經由 Google 的序列埠閘道連線，用中繼資料裡的 SSH 公鑰驗證連線者，不經過主機自己的網路設定與 sshd，在登入提示用救援帳號的密碼登入。</li>
<li><strong>AWS</strong>：Systems Manager Session Manager 依賴 SSM Agent、Systems Manager 操作這台主機的權限與出站路徑，不經過 sshd；EC2 Instance Connect 或建立時選的 key pair 依賴 sshd、security group 放行 22 port，以及連得到主機的網路（公開 IP、私有連線或 EC2 Instance Connect Endpoint），EC2 Instance Connect 另外要登入者的 IAM 權限。這兩條共用的元件最少。官方另有 EC2 Serial Console，支援 Nitro 系統上的主機，要先完成存取設定、主機裡要有設了密碼的帳號。</li>
</ul>
<p>三家都有一條不經過 sshd 的救援入口：序列主控台加設了密碼的救援帳號。這個帳號要在主機還登得進去時建，或建立時用初始化腳本建；Oracle Cloud 與 AWS 的平台映像，預設使用者只用金鑰登入、沒有密碼。救援帳號怎麼建、序列主控台平時為什麼關著，見 <a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）</a> 卡。</p>
<p>兩條路徑都要在停用公開 IP 的 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>
<p>不放公鑰減少了主機裡長期有效的金鑰，代價是在 Oracle Cloud 上拿掉了不經過代理程式的那一條路徑。放在私有 subnet、沒有公開 IP 的主機，網路可達性這一層已經擋住外部的連線，用得了這把公鑰的只剩持有私鑰、而且有建工作階段的權限或已經在同一個 subnet 裡的人，增加的暴露很小，換到的是代理程式失效時的入口；代價是這把金鑰要管理、人員離開時要收回，而在 Oracle Cloud 上，中繼資料裡的公鑰建立後不能換。同時滿足「主機裡沒有長期有效的使用者公鑰」與「不依賴代理程式」的做法是 SSH 憑證：建立時用初始化腳本放 CA 的公鑰，登入時用 CA 簽的短效憑證，代價是要維運一個 CA；在 Oracle Cloud 上 CA 的公鑰同樣經由建立後不能改的初始化腳本放入，換 CA 要登入主機改或重建，做法見 <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 金鑰怎麼發、怎麼收回〉。所以第一次在某個機型加作業系統的組合上建主機時，放一把自己的公鑰。這把公鑰之後要不要從 <code>authorized_keys</code> 拿掉，取決於另一條不共用代理程式的入口在不在：拿掉之後只剩代理程式那一條，回到單一路徑；先建好序列主控台的救援帳號並登入過，拿掉公鑰時才仍然有兩條，而救援帳號的密碼同樣是長期有效的憑證，要逐台輪替（見 Serial Console 卡的〈設計責任〉）（Oracle Cloud 上的做法與前提見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉最後一段）。</p>
<h2 id="容量稀缺時設定的取捨補救代價與最嚴格設定的驗證位置">容量稀缺時設定的取捨：補救代價與最嚴格設定的驗證位置</h2>
<p>機型的容量難取得時（免費額度裡的熱門機型、GPU 機型），建好就改不了的設定選錯的代價，除了重建，還多了一次重新取得容量。Oracle Cloud 的 Always Free A1 在 <code>ap-batam-1</code> 實測時，同一個帳號 2026-10-24 整天的建立請求都回容量不足，2026-10-25 用重送腳本送了 16 次才建成（重送的做法與限流的量測見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈重送建立請求：CLI 自己的重送、限流門檻與重送腳本〉）。</p>
<p>這時的取捨是選補救代價低的設定：作業系統與版本選登入服務完整支援的那一個、建立時放一把之後可以移除的公鑰、兩條登入路徑都備齊。最嚴格的設定（不放任何金鑰、只靠代理程式）要拆成兩部分驗：跟機型無關的那一部分（代理程式的設定、登入權限、防火牆規則）可以先在容易重建的機型上測；跟機型有關的支援範圍，照〈登入路徑上各服務的支援組合：支援清單、Known Issues 與元件狀態〉讀文件對這次的機型確認，演練驗不到這一部分。必須在某個時間點拿到容量的情境，可以改用預留容量（capacity reservation），見 <a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統</a> 的〈計費模式：隨用隨付、可被收回、預留容量與專屬主機〉。</p>
<p>搶容量的重送腳本把建立參數寫死在腳本裡，所以腳本送出的設定，是寫腳本那一刻的判斷。從寫好腳本到建成主機可能隔了好幾個小時，這段時間裡查到的新資訊（Known Issues 的一條、某個服務的前置條件）不會自己進到腳本裡；腳本裡的參數要在兩個時間點再對過這一篇的檢查（建好就改不了的設定、各服務的支援組合、兩條登入路徑各自依賴的元件）：重新啟動腳本的那一刻，以及腳本還在跑時查到會改變建立參數的新資訊的那一刻（這時先停掉腳本，改完參數再啟動）。一開始就選補救代價低的參數時，判斷過期的代價也跟著變小。</p>
<h2 id="各家查建立後不能改的欄位登入服務支援組合與代理程式狀態的位置">各家查建立後不能改的欄位、登入服務支援組合與代理程式狀態的位置</h2>
<table>
  <thead>
      <tr>
          <th>要查的事</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>哪些欄位建立後不能改</td>
          <td><code>oci compute instance update --help</code> 各欄位的說明；〈Replacing a Boot Volume〉的限制</td>
          <td>Compute Engine 文件修改各項屬性的頁面</td>
          <td>EC2 文件修改主機屬性的頁面與 <code>aws ec2 modify-instance-attribute help</code></td>
      </tr>
      <tr>
          <td>登入服務支援的組合</td>
          <td>Bastion 的〈Known Issues for Bastion〉；Run Command 說明頁的支援映像清單；依作業系統版本查可用 plugin 的 <code>oci instance-agent available-plugins get</code></td>
          <td>OS Login 與 IAP TCP forwarding 的說明頁</td>
          <td>EC2 Instance Connect 的〈Prerequisites〉；Session Manager 的前置條件</td>
      </tr>
      <tr>
          <td>代理程式與 plugin 的執行狀態</td>
          <td>主機頁面的 Oracle Cloud Agent 分頁，或 <code>oci instance-agent plugin list</code></td>
          <td><code>get-serial-port-output</code> 裡 guest agent 的啟動</td>
          <td>Systems Manager 的 Fleet Manager 列出的受管主機</td>
      </tr>
  </tbody>
</table>
<p>「代理程式與 plugin 的執行狀態」那一列查到的是元件有沒有啟動；〈登入路徑上各服務的支援組合：支援清單、Known Issues 與元件狀態〉那一節的支援清單與 Known Issues，仍然要另外讀。設定也不等於實際：Oracle Cloud 主機的 <code>agent-config</code> 寫著 Bastion 是 <code>ENABLED</code> 只是期望的狀態，<code>plugin list</code> 裡根本沒有 Bastion 這一項時，代表這個映像版本不提供這個 plugin（2026-10-25 的 Oracle Linux 10.2 主機就是這樣）。</p>
]]></content:encoded></item><item><title>Google Cloud 的實作紀錄</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/hands-on/</link><pubDate>Sun, 25 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/hands-on/</guid><description>&lt;p>這個資料夾收在 Google Cloud 上實際操作的紀錄：前置條件、每一步的指令與輸出、遇到的錯誤、量到的值，以及每一步沒做或換一種做法時的結果。機制與判斷依據寫在上一層的概念文章，這裡的文章連回去，不重寫。&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>&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;/td>
 &lt;td>&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>&lt;/td>
 &lt;td>盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、刪除與收斂舊規則、OS Login 啟用、Cloud NAT 與移除外部 IP&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&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;/td>
 &lt;td>&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>&lt;/td>
 &lt;td>新 template 與 canary、重建與主機金鑰、退回 template、除錯 namespace、node pool 設定、控制平面端點切換&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/hands-on/budget-killswitch-walkthrough/" data-link-title="GCP 預算警示與停損開關的實測紀錄：建預算、Pub/Sub 主題、服務帳號、函式與觸發器、兩段驗收、心跳監看與停用後的恢復" data-link-desc="要在 GCP 專案上建預算警示，或建一個花費超過預算就解除帳單連結的停損開關，想照一次實跑過的順序做、看每一步漏掉或換一種做法的結果時讀；也用在函式部署成功、狀態 ACTIVE 卻從沒解除過帳單連結，或專案因超支被停用之後要恢復的時候">GCP 預算警示與停損開關的實測紀錄&lt;/a>&lt;/td>
 &lt;td>&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 上為費用失控設防的機制：預算警示寄出通知的時機、範圍、抵免與通知對象、強制執行支出上限涵蓋的服務與暫停期間，以預算通知觸發函式停止用量的兩種處置（解除帳單連結、只停止花錢的資源）該選哪一種，帳單資料延遲對這些機制反應時間的影響，停損開關放在另一個專案的理由，以及驗收為什麼分成真實通知與測試訊息兩段、停用後的恢復為什麼要先調預算">GCP 上的雲端費用失控防護&lt;/a>&lt;/td>
 &lt;td>預算、主題、服務帳號、函式與觸發器、分段驗收、心跳監看、停用後的恢復&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>這個資料夾收在 Google Cloud 上實際操作的紀錄：前置條件、每一步的指令與輸出、遇到的錯誤、量到的值，以及每一步沒做或換一種做法時的結果。機制與判斷依據寫在上一層的概念文章，這裡的文章連回去，不重寫。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>對應的概念文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><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></td>
          <td><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></td>
          <td>盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、刪除與收斂舊規則、OS Login 啟用、Cloud NAT 與移除外部 IP</td>
      </tr>
      <tr>
          <td><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></td>
          <td><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></td>
          <td>新 template 與 canary、重建與主機金鑰、退回 template、除錯 namespace、node pool 設定、控制平面端點切換</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/gcp/hands-on/budget-killswitch-walkthrough/" data-link-title="GCP 預算警示與停損開關的實測紀錄：建預算、Pub/Sub 主題、服務帳號、函式與觸發器、兩段驗收、心跳監看與停用後的恢復" data-link-desc="要在 GCP 專案上建預算警示，或建一個花費超過預算就解除帳單連結的停損開關，想照一次實跑過的順序做、看每一步漏掉或換一種做法的結果時讀；也用在函式部署成功、狀態 ACTIVE 卻從沒解除過帳單連結，或專案因超支被停用之後要恢復的時候">GCP 預算警示與停損開關的實測紀錄</a></td>
          <td><a href="/blog/devops/infra/cloud-providers/gcp/spend-protection-gcp-budget-killswitch/" data-link-title="GCP 上的雲端費用失控防護：預算警示、強制執行支出上限與自動停用帳單的作用範圍、反應時間與判斷" data-link-desc="GCP 上為費用失控設防的機制：預算警示寄出通知的時機、範圍、抵免與通知對象、強制執行支出上限涵蓋的服務與暫停期間，以預算通知觸發函式停止用量的兩種處置（解除帳單連結、只停止花錢的資源）該選哪一種，帳單資料延遲對這些機制反應時間的影響，停損開關放在另一個專案的理由，以及驗收為什麼分成真實通知與測試訊息兩段、停用後的恢復為什麼要先調預算">GCP 上的雲端費用失控防護</a></td>
          <td>預算、主題、服務帳號、函式與觸發器、分段驗收、心跳監看、停用後的恢復</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>Oracle Cloud 的實作紀錄</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/</link><pubDate>Sun, 25 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/</guid><description>&lt;p>這個資料夾收在 Oracle Cloud 上實際操作的紀錄：前置條件、每一步的指令與輸出、遇到的錯誤與除錯過程、量到的值。觀念與判斷依據寫在上一層的概念文章與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/" data-link-title="建立雲端虛擬機的選項" data-link-desc="站在雲端主控台的建立虛擬機頁面、不確定某一個欄位會影響什麼時，依功能分類查位置、機型與映像檔、網路、登入、磁碟、代理程式與安全開機各自的選項、建立後能不能改與對費用的影響">建立雲端虛擬機的選項&lt;/a>，這裡的文章連回去，不重寫。&lt;/p>
&lt;h2 id="章節文章">章節文章&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;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄&lt;/a>&lt;/td>
 &lt;td>建立前的檢查、前置網路、主控台各分頁的取值與替代做法、等效 CLI 指令、容量不足與重送腳本、Bastion 工作階段逾時的排查、Run Command 補公鑰的嘗試、改用 Oracle Linux 並放公鑰重建後經由 port forwarding session 登入、刪除資源的順序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&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;/td>
 &lt;td>私有子網路的 Ubuntu 24.04 主機連不到 Canonical 鏡像站的錯誤與成因、NAT gateway 加路由加只掛在這台的 NSG、序列主控台的 RSA 金鑰與 ProxyJump 寫法、閒置回收之後的發現方式、留下與消失的資源、先查後建的重建腳本與它在主機停止、停止中、啟動中、建立中與 NAT gateway 不存在時的實跑、費用與額度（開機磁碟實際扣 47 GB 的查法、合計超過 200 GB 時放不進剩餘額度的那一顆整顆不算免費額度、compartment quota 擋下超額的建立時回的 400 與 &lt;code>LimitExceeded&lt;/code>、線上加大開機磁碟與另建資料磁碟超過額度時整顆在免費額度外、付費磁碟在空出額度 26 到 31 分鐘後自動轉成免費、主控台 Limits, quotas and usage 頁要切到可用網域才看得到總量、NAT gateway 在價目表與帳務明細裡的收費項目）、刪除順序與刪除時的 412 和 400／409、整組收掉的實跑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/suspend-resume-drill/" data-link-title="Oracle Cloud 主機的暫停與恢復上線實測紀錄：暫停前的基準、收窄入口、停止主機、停止期間的計費與 Cloudflare 的回應、恢復上線的每一步與耗時，以及收尾時 DNS 紀錄與主機的刪除順序" data-link-desc="要讓 Oracle Cloud 上一台放在 Cloudflare 後面的主機停機一段時間再恢復上線、想知道停止之後公開 IP 留不留、停止期間 A1 與開機磁碟怎麼計費、Cloudflare 回什麼錯誤、恢復時每一步要多久，或服務要收掉時 DNS 紀錄與主機的刪除順序時，查實際跑過的指令、輸出與量到的時間">Oracle Cloud 主機的暫停與恢復上線實測紀錄：暫停前的基準、收窄入口、停止主機、停止期間的計費與 Cloudflare 的回應、恢復上線的每一步與耗時，以及收尾時 DNS 紀錄與主機的刪除順序&lt;/a>&lt;/td>
 &lt;td>放在 Cloudflare 後面的 A1 主機：暫停前記下的基準、把 NSG 從網卡拿掉之後 Cloudflare 回 522、&lt;code>SOFTSTOP&lt;/code> 的耗時與停止後公開 IP 留著、停止中的主機仍佔 A1 用量、停止那一小時的帳務明細、啟動到 SSH 連得上的時間、裝安全更新與重開機的判斷、掛回 NSG 之後的驗證，以及先刪 DNS 紀錄再終止主機的順序與 &lt;code>terminate --wait-for-state&lt;/code> 只收 work request 狀態&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>這個資料夾收在 Oracle Cloud 上實際操作的紀錄：前置條件、每一步的指令與輸出、遇到的錯誤與除錯過程、量到的值。觀念與判斷依據寫在上一層的概念文章與 <a href="/blog/devops/infra/cloud-providers/vm-options/" data-link-title="建立雲端虛擬機的選項" data-link-desc="站在雲端主控台的建立虛擬機頁面、不確定某一個欄位會影響什麼時，依功能分類查位置、機型與映像檔、網路、登入、磁碟、代理程式與安全開機各自的選項、建立後能不能改與對費用的影響">建立雲端虛擬機的選項</a>，這裡的文章連回去，不重寫。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a></td>
          <td>建立前的檢查、前置網路、主控台各分頁的取值與替代做法、等效 CLI 指令、容量不足與重送腳本、Bastion 工作階段逾時的排查、Run Command 補公鑰的嘗試、改用 Oracle Linux 並放公鑰重建後經由 port forwarding session 登入、刪除資源的順序</td>
      </tr>
      <tr>
          <td><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></td>
          <td>私有子網路的 Ubuntu 24.04 主機連不到 Canonical 鏡像站的錯誤與成因、NAT gateway 加路由加只掛在這台的 NSG、序列主控台的 RSA 金鑰與 ProxyJump 寫法、閒置回收之後的發現方式、留下與消失的資源、先查後建的重建腳本與它在主機停止、停止中、啟動中、建立中與 NAT gateway 不存在時的實跑、費用與額度（開機磁碟實際扣 47 GB 的查法、合計超過 200 GB 時放不進剩餘額度的那一顆整顆不算免費額度、compartment quota 擋下超額的建立時回的 400 與 <code>LimitExceeded</code>、線上加大開機磁碟與另建資料磁碟超過額度時整顆在免費額度外、付費磁碟在空出額度 26 到 31 分鐘後自動轉成免費、主控台 Limits, quotas and usage 頁要切到可用網域才看得到總量、NAT gateway 在價目表與帳務明細裡的收費項目）、刪除順序與刪除時的 412 和 400／409、整組收掉的實跑</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/suspend-resume-drill/" data-link-title="Oracle Cloud 主機的暫停與恢復上線實測紀錄：暫停前的基準、收窄入口、停止主機、停止期間的計費與 Cloudflare 的回應、恢復上線的每一步與耗時，以及收尾時 DNS 紀錄與主機的刪除順序" data-link-desc="要讓 Oracle Cloud 上一台放在 Cloudflare 後面的主機停機一段時間再恢復上線、想知道停止之後公開 IP 留不留、停止期間 A1 與開機磁碟怎麼計費、Cloudflare 回什麼錯誤、恢復時每一步要多久，或服務要收掉時 DNS 紀錄與主機的刪除順序時，查實際跑過的指令、輸出與量到的時間">Oracle Cloud 主機的暫停與恢復上線實測紀錄：暫停前的基準、收窄入口、停止主機、停止期間的計費與 Cloudflare 的回應、恢復上線的每一步與耗時，以及收尾時 DNS 紀錄與主機的刪除順序</a></td>
          <td>放在 Cloudflare 後面的 A1 主機：暫停前記下的基準、把 NSG 從網卡拿掉之後 Cloudflare 回 522、<code>SOFTSTOP</code> 的耗時與停止後公開 IP 留著、停止中的主機仍佔 A1 用量、停止那一小時的帳務明細、啟動到 SSH 連得上的時間、裝安全更新與重開機的判斷、掛回 NSG 之後的驗證，以及先刪 DNS 紀錄再終止主機的順序與 <code>terminate --wait-for-state</code> 只收 work request 狀態</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item></channel></rss>