<?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>Kubernetes on Tarragon</title><link>https://tarrragon.github.io/blog/tags/kubernetes/</link><description>Recent content in Kubernetes on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 19 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/kubernetes/index.xml" rel="self" type="application/rss+xml"/><item><title>5.2 Kubernetes 部署策略</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/kubernetes-deployment/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/kubernetes-deployment/</guid><description>&lt;p>Kubernetes 部署策略（Kubernetes deployment strategy）的核心責任是把服務版本切換做成可預測流程。Deployment 把副本數、健康訊號、流量承接、設定變更與回退條件組成同一條交付路徑。&lt;/p>
&lt;h2 id="deploymentreplica-與-rollout">deployment、replica 與 rollout&lt;/h2>
&lt;p>Deployment 的責任是宣告目標狀態：期望副本數、版本、更新策略。rollout 的責任是把現況收斂到目標狀態，並在過程中維持可服務能力。這兩者分開理解後，才能在異常時判斷是目標設定問題，還是收斂過程問題。&lt;/p>
&lt;p>rolling update 常用來降低單次切換風險。rolling update 的判讀重點是批次大小與節奏：每批新增多少新副本、每批回收多少舊副本、每批觀察多長時間。這些參數以服務容量曲線與回退時間目標校準、名稱本身只是工具標籤、不是判讀條件。&lt;/p>
&lt;h2 id="probe-對齊服務生命週期">probe 對齊服務生命週期&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/probe/" data-link-title="Probe" data-link-desc="說明平台如何透過 probe 判斷服務狀態與接流量條件">probe&lt;/a> 要對齊服務生命週期，不同 probe 有不同責任：&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/startup-probe/" data-link-title="Startup Probe" data-link-desc="保護慢啟動服務不被 liveness probe 過早重啟的探針">startup probe&lt;/a>：確認服務啟動完成，避免慢啟動服務被過早重啟。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness&lt;/a> probe：確認服務可安全接流量。&lt;/li>
&lt;li>liveness probe：確認服務仍可維持基本運作，必要時觸發重建。&lt;/li>
&lt;/ol>
&lt;p>probe 設計若只回傳固定成功，rollout 期間會出現「容器在線但服務未就緒」的流量抖動。穩定做法是讓 readiness 反映依賴就緒條件，例如資料庫連線池、必要配置、關鍵背景任務狀態。&lt;/p>
&lt;h3 id="startup-probe-設計注意事項">Startup probe 設計注意事項&lt;/h3>
&lt;p>startup probe 跟 &lt;code>initialDelaySeconds&lt;/code> 解決同一個問題（避免慢啟動服務被 liveness 殺掉），但機制不同。&lt;code>initialDelaySeconds&lt;/code> 是 liveness / readiness probe 的延遲啟動——在等待期間 probe 完全不跑，無法觀測啟動進度。startup probe 在啟動期間持續探測，一旦成功就交棒給 liveness / readiness，啟動失敗時能更快偵測到。&lt;/p>
&lt;p>startup probe 的總容忍時間 = &lt;code>failureThreshold × periodSeconds&lt;/code>。例如 &lt;code>failureThreshold: 30, periodSeconds: 10&lt;/code> 給服務 300 秒啟動窗口。設計時先量測服務在最差情境下的啟動時間（冷啟動 + image pull + 依賴連線建立），再加 20-30% headroom 作為總容忍時間。&lt;/p>
&lt;h3 id="readiness-probe-的深度選擇">Readiness probe 的深度選擇&lt;/h3>
&lt;p>readiness probe 的檢查深度決定它能攔截多少「可啟動但不可服務」的狀態。三個常見層級：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Port check&lt;/strong>（TCP probe）：確認進程在監聽。最淺，無法偵測依賴未就緒。適合依賴簡單、啟動快的服務。&lt;/li>
&lt;li>&lt;strong>Dependency check&lt;/strong>（HTTP endpoint 檢查必要依賴）：確認資料庫連線池、cache 連線可用。涵蓋多數「啟動完但依賴不通」的場景。常用做法是在 &lt;code>/ready&lt;/code> endpoint 內驗證必要依賴的連線狀態。&lt;/li>
&lt;li>&lt;strong>Deep health&lt;/strong>（業務路徑驗證）：執行一次簡化的業務查詢確認端到端通路。最深但代價最高——probe 本身消耗資源，且可能被下游延遲拖慢導致 readiness 抖動。&lt;/li>
&lt;/ol>
&lt;p>依賴分類（必要 / 可降級 / 觀測）的判讀框架見 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 Readiness 設計的核心取捨&lt;/a>。&lt;/p>
&lt;h2 id="config-rollout-與版本相容">config rollout 與版本相容&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/config-rollout/" data-link-title="Config Rollout" data-link-desc="說明設定如何安全下發到正在運作的服務實例">Config Rollout&lt;/a> 需要和應用版本一起治理。設定先行、版本後行，或版本先行、設定後行，都要保留相容窗口。相容窗口存在時，才有漸進 rollout 與快速回退空間。&lt;/p>
&lt;p>跨版本配置遷移要先定義停止條件：錯誤率上升、延遲尖峰、關鍵路徑失敗或下游壓力超標。停止條件明確後，部署決策才能一致。&lt;/p>
&lt;h3 id="n-1-相容與-feature-flag-gating">N-1 相容與 Feature Flag Gating&lt;/h3>
&lt;p>版本相容窗口的操作基線是 N-1 相容：版本 N 的程式碼可以處理版本 N-1 的設定，反之亦然。這讓 rollback 從「版本 + config 必須同時回退」降級成「版本先回退、config 稍後再處理」，回退操作的原子性要求降低。&lt;/p>
&lt;p>N-1 相容的實作通常搭配 feature flag gating：新功能在程式碼中預設關閉，先部署程式碼（版本 N 上線但新功能 off），確認版本穩定後再開啟 feature flag。這讓版本部署跟功能啟用分成兩個獨立決策，rollback 時只需關 flag 而不必回退版本。&lt;/p>
&lt;p>N-1 相容窗口的壽命要有明確終點。長期維護雙版本相容會累積技術債——舊欄位不能刪、舊路徑不能移除。穩定做法是在 rollout 完成 + 觀測確認穩定後設定移除 deadline，把 N-1 相容視為暫時性保護而非永久設計。設定注入方式與版本追蹤見 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/container-runtime/" data-link-title="5.1 container 與 runtime" data-link-desc="整理 image、resource limit 與啟動行為">5.1 配置注入方式與取捨&lt;/a>。&lt;/p>
&lt;h2 id="autoscaling-與部署策略協同">Autoscaling 與部署策略協同&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/autoscaling/" data-link-title="Autoscaling" data-link-desc="說明系統如何依負載自動調整服務實例數量">autoscaling&lt;/a> 在部署期間扮演容量緩衝角色。部署批次若超過服務可承受變動幅度，autoscaling 會被動補償並延長收斂時間。穩定做法是讓 rollout 節奏與容量策略同時設計：先保證服務穩態，再提高切換速度。&lt;/p></description><content:encoded><![CDATA[<p>Kubernetes 部署策略（Kubernetes deployment strategy）的核心責任是把服務版本切換做成可預測流程。Deployment 把副本數、健康訊號、流量承接、設定變更與回退條件組成同一條交付路徑。</p>
<h2 id="deploymentreplica-與-rollout">deployment、replica 與 rollout</h2>
<p>Deployment 的責任是宣告目標狀態：期望副本數、版本、更新策略。rollout 的責任是把現況收斂到目標狀態，並在過程中維持可服務能力。這兩者分開理解後，才能在異常時判斷是目標設定問題，還是收斂過程問題。</p>
<p>rolling update 常用來降低單次切換風險。rolling update 的判讀重點是批次大小與節奏：每批新增多少新副本、每批回收多少舊副本、每批觀察多長時間。這些參數以服務容量曲線與回退時間目標校準、名稱本身只是工具標籤、不是判讀條件。</p>
<h2 id="probe-對齊服務生命週期">probe 對齊服務生命週期</h2>
<p><a href="/blog/backend/knowledge-cards/probe/" data-link-title="Probe" data-link-desc="說明平台如何透過 probe 判斷服務狀態與接流量條件">probe</a> 要對齊服務生命週期，不同 probe 有不同責任：</p>
<ol>
<li><a href="/blog/backend/knowledge-cards/startup-probe/" data-link-title="Startup Probe" data-link-desc="保護慢啟動服務不被 liveness probe 過早重啟的探針">startup probe</a>：確認服務啟動完成，避免慢啟動服務被過早重啟。</li>
<li><a href="/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness</a> probe：確認服務可安全接流量。</li>
<li>liveness probe：確認服務仍可維持基本運作，必要時觸發重建。</li>
</ol>
<p>probe 設計若只回傳固定成功，rollout 期間會出現「容器在線但服務未就緒」的流量抖動。穩定做法是讓 readiness 反映依賴就緒條件，例如資料庫連線池、必要配置、關鍵背景任務狀態。</p>
<h3 id="startup-probe-設計注意事項">Startup probe 設計注意事項</h3>
<p>startup probe 跟 <code>initialDelaySeconds</code> 解決同一個問題（避免慢啟動服務被 liveness 殺掉），但機制不同。<code>initialDelaySeconds</code> 是 liveness / readiness probe 的延遲啟動——在等待期間 probe 完全不跑，無法觀測啟動進度。startup probe 在啟動期間持續探測，一旦成功就交棒給 liveness / readiness，啟動失敗時能更快偵測到。</p>
<p>startup probe 的總容忍時間 = <code>failureThreshold × periodSeconds</code>。例如 <code>failureThreshold: 30, periodSeconds: 10</code> 給服務 300 秒啟動窗口。設計時先量測服務在最差情境下的啟動時間（冷啟動 + image pull + 依賴連線建立），再加 20-30% headroom 作為總容忍時間。</p>
<h3 id="readiness-probe-的深度選擇">Readiness probe 的深度選擇</h3>
<p>readiness probe 的檢查深度決定它能攔截多少「可啟動但不可服務」的狀態。三個常見層級：</p>
<ol>
<li><strong>Port check</strong>（TCP probe）：確認進程在監聽。最淺，無法偵測依賴未就緒。適合依賴簡單、啟動快的服務。</li>
<li><strong>Dependency check</strong>（HTTP endpoint 檢查必要依賴）：確認資料庫連線池、cache 連線可用。涵蓋多數「啟動完但依賴不通」的場景。常用做法是在 <code>/ready</code> endpoint 內驗證必要依賴的連線狀態。</li>
<li><strong>Deep health</strong>（業務路徑驗證）：執行一次簡化的業務查詢確認端到端通路。最深但代價最高——probe 本身消耗資源，且可能被下游延遲拖慢導致 readiness 抖動。</li>
</ol>
<p>依賴分類（必要 / 可降級 / 觀測）的判讀框架見 <a href="/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 Readiness 設計的核心取捨</a>。</p>
<h2 id="config-rollout-與版本相容">config rollout 與版本相容</h2>
<p><a href="/blog/backend/knowledge-cards/config-rollout/" data-link-title="Config Rollout" data-link-desc="說明設定如何安全下發到正在運作的服務實例">Config Rollout</a> 需要和應用版本一起治理。設定先行、版本後行，或版本先行、設定後行，都要保留相容窗口。相容窗口存在時，才有漸進 rollout 與快速回退空間。</p>
<p>跨版本配置遷移要先定義停止條件：錯誤率上升、延遲尖峰、關鍵路徑失敗或下游壓力超標。停止條件明確後，部署決策才能一致。</p>
<h3 id="n-1-相容與-feature-flag-gating">N-1 相容與 Feature Flag Gating</h3>
<p>版本相容窗口的操作基線是 N-1 相容：版本 N 的程式碼可以處理版本 N-1 的設定，反之亦然。這讓 rollback 從「版本 + config 必須同時回退」降級成「版本先回退、config 稍後再處理」，回退操作的原子性要求降低。</p>
<p>N-1 相容的實作通常搭配 feature flag gating：新功能在程式碼中預設關閉，先部署程式碼（版本 N 上線但新功能 off），確認版本穩定後再開啟 feature flag。這讓版本部署跟功能啟用分成兩個獨立決策，rollback 時只需關 flag 而不必回退版本。</p>
<p>N-1 相容窗口的壽命要有明確終點。長期維護雙版本相容會累積技術債——舊欄位不能刪、舊路徑不能移除。穩定做法是在 rollout 完成 + 觀測確認穩定後設定移除 deadline，把 N-1 相容視為暫時性保護而非永久設計。設定注入方式與版本追蹤見 <a href="/blog/backend/05-deployment-platform/container-runtime/" data-link-title="5.1 container 與 runtime" data-link-desc="整理 image、resource limit 與啟動行為">5.1 配置注入方式與取捨</a>。</p>
<h2 id="autoscaling-與部署策略協同">Autoscaling 與部署策略協同</h2>
<p><a href="/blog/backend/knowledge-cards/autoscaling/" data-link-title="Autoscaling" data-link-desc="說明系統如何依負載自動調整服務實例數量">autoscaling</a> 在部署期間扮演容量緩衝角色。部署批次若超過服務可承受變動幅度，autoscaling 會被動補償並延長收斂時間。穩定做法是讓 rollout 節奏與容量策略同時設計：先保證服務穩態，再提高切換速度。</p>
<p>長連線服務或有大量背景任務的 workload，通常需要比 stateless API 更保守的 rollout 策略，並額外搭配 drain 與 reconnect 設計。</p>
<p>擴縮策略的演進需要版本化跟可回放。對應 <a href="/blog/backend/05-deployment-platform/cases/airbnb-kubernetes-cluster-scaling-evolution/" data-link-title="5.C6 Airbnb：Kubernetes 叢集擴縮演進" data-link-desc="從手動擴縮走向自動化容量治理的部署平台案例。">5.C6 Airbnb K8s 叢集擴縮演進</a>：揭露「擴縮策略版本化跟可回放」「不同 workload 區分擴縮政策」「容量治理跟事故指標綁定」三個方向。以下基於通用工程知識展開。</p>
<p>可重複套用的做法：</p>
<ol>
<li><strong>擴縮策略進 IaC</strong>：HPA / VPA / Karpenter / Cluster Autoscaler 的配置都進 git、變更走 release flow、避免手動調整在事故後被遺忘。IaC + 自動化的 ownership 邊界見 [5.7 <a href="/blog/backend/knowledge-cards/control-plane/" data-link-title="Control Plane" data-link-desc="負責下發策略、配置與路由決策的控制層">control plane</a> boundary](/backend/05-deployment-platform/traffic-config-control-plane-boundary/)。</li>
<li><strong>workload 分群擴縮</strong>：stateless API、長連線服務、batch job、background worker 對擴縮的需求不同。把不同 workload 用不同 namespace + 不同 autoscaler policy 隔離，避免一套規則套全部。</li>
<li><strong>擴縮事件接事故指標</strong>：HPA 觸發、scale-up 延遲、scale-down 過快、cluster autoscaler 加 node 失敗，都該在事故 timeline 上可見。回到 <a href="/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13 service topology</a> 的擴縮事件 vs 事故區分。</li>
</ol>
<h2 id="分階段平台遷移">分階段平台遷移</h2>
<p>平台遷移的本質是流量跟依賴的分段切換。遷移期內新舊叢集同時存在，rollout 策略要把跨叢集流量切換納入批次節奏、視為連續多批決策。本段聚焦流量 / 依賴切換時序；遷移期的團隊職責邊界重訂見 <a href="/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/#managed-%e5%b9%b3%e5%8f%b0%e8%b7%9f%e5%9c%98%e9%9a%8a%e8%81%b7%e8%b2%ac%e9%82%8a%e7%95%8c" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 Managed 平台跟團隊職責邊界</a>。</p>
<p>對應 <a href="/blog/backend/05-deployment-platform/cases/tradeshift-self-managed-k8s-to-eks/" data-link-title="5.C1 Tradeshift：self-managed Kubernetes 遷移到 EKS" data-link-desc="零停機平台遷移的分段策略案例。">5.C1 Tradeshift：self-managed K8s → EKS</a>：揭露「零停機遷移要把切換做成分段策略」「難點通常在跨叢集服務依賴跟流量切換、不在 Kubernetes API 本身」。對應 <a href="/blog/backend/05-deployment-platform/cases/mobileye-workloads-to-eks/" data-link-title="5.C4 Mobileye：Workloads 遷移到 EKS" data-link-desc="大規模工作負載遷移到 managed Kubernetes 的分段治理案例。">5.C4 Mobileye workloads 遷移</a>：揭露「分批遷移 workload、保留觀測對照」「明確切換 / 回退條件」「新平台先驗證容量跟恢復節奏」。以下基於通用工程知識展開。</p>
<p>可重複套用的分階段做法：</p>
<ol>
<li><strong>新叢集 + 共通配置基線</strong>：先在新叢集上建立跟舊叢集對等的配置基線（namespace、ResourceQuota、NetworkPolicy、Ingress class、storage class），讓 workload 可以無縫部署。</li>
<li><strong>小流量先導服務</strong>：選擇影響面小、依賴單純的服務作為先導，先在新叢集跑完整 deployment cycle（rollout、drain、rollback 驗證）、累積信心後再擴大。</li>
<li><strong>可控流量分批切換</strong>：用 DNS 加權、service mesh 流量切分或 LB 規則把流量分批從舊叢集導到新叢集。每批切換後驗證 SLI 偏差、再進下一批。</li>
<li><strong>每批保留回退路徑</strong>：舊叢集服務不立即下線，保留作為回退目標。回退條件先驗證（rollback script、流量切回 DNS / LB 規則），再開始下一批切換。</li>
</ol>
<p>延伸 5.C1 揭露的「跨叢集服務依賴是難點」、5.C10 中型組織判讀「服務本身切過去了、但資料面、認證面、觀測面還沒同步」也指向同類問題。跨叢集遷移最容易出的事故是「服務切過去了、依賴沒切過去」。Database、cache、message queue、observability pipeline、auth service 的切換時機要分別規劃，避免應用層在新叢集但仍跨網路打舊叢集的依賴，造成隱性 latency 或單點失效。規模差異下的同類問題見 <a href="/blog/backend/05-deployment-platform/cases/contrast-platform-migration-by-scale/" data-link-title="5.C10 對照：規模差異下的平台遷移" data-link-desc="平台遷移策略在小中大型組織下的差異。">5.C10 對照</a>。</p>
<h2 id="大規模-k8s-的設計取捨">大規模 K8s 的設計取捨</h2>
<p>K8s 在不同規模下的設計取捨會明顯分歧。小規模叢集追求簡單跟低運維成本，大規模叢集追求隔離跟自動化治理。同一套部署策略放到不同規模會在某個量級開始失效。</p>
<p>對應 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games：246 個 EKS cluster</a>：揭露架構決策從 multi-tenant cluster 改成 single-tenant per game、Karpenter + Terraform 的 cluster 級自動化、35ms 延遲門檻 + Local Zones / Outposts 區域部署（case 中「35ms 反推 region 部署」屬作者判讀層、本章引用此推論）。對應 <a href="/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/" data-link-title="9.C34 GCP：130,000-node GKE cluster 的工程極限" data-link-desc="Google 用單一 GKE control plane 跑 13 萬個 node、AI workload &#43; 1000 Pods/sec 創建吞吐">9.C34 GCP 130,000-node GKE cluster</a>：揭露 control plane 極限取決於 storage backend（GCP 用 Spanner 替代 etcd）、AI workload 跟 web workload 容量規劃差異。對應 <a href="/blog/backend/09-performance-capacity/cases/maersk-bosch-azure-aks/" data-link-title="9.C33 Maersk &#43; Bosch：傳統產業在 Azure AKS 上的微服務治理" data-link-desc="全球海運 Maersk 跟 Bosch 智慧建築把 AKS 當微服務治理基礎、釋放工程資源做業務功能">9.C33 Maersk + Bosch AKS</a>：揭露 Maersk 工程訴求引語「focus on things that makes the most business impact」、傳統產業上 K8s 動機是治理一致性（作者判讀）、適合 single-cluster-multi-namespace。</p>
<p>可重複套用的取捨判讀：</p>
<ol>
<li><strong>single-tenant per workload vs single-cluster multi-namespace</strong>：高隔離需求（每個 workload 失效不能影響其他）、高延遲敏感度（需 region cluster）→ 多 cluster；治理一致性訴求（統一 release flow、合規邊界）→ 單一 cluster 多 namespace。</li>
<li><strong>Cluster 容量極限取決於 control plane</strong>：data plane（worker nodes）擴容容易、control plane（API server、etcd / storage）擴容難、瓶頸通常在 control plane。etcd 撐 5K-10K node 後吃力、需要替換 storage backend（Spanner / PostgreSQL / 自家 KV）才能撐萬級節點（見 <a href="/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/" data-link-title="9.C34 GCP：130,000-node GKE cluster 的工程極限" data-link-desc="Google 用單一 GKE control plane 跑 13 萬個 node、AI workload &#43; 1000 Pods/sec 創建吞吐">9.C34</a>）。control plane 的 ownership 邊界由 <a href="/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 control plane boundary</a> 處理。</li>
<li><strong>Multi-cluster 治理需要 IaC + 自動化</strong>：Terraform / Crossplane / Cluster API + Karpenter / Cluster Autoscaler 是基本工具。手動管理超過數十個 cluster 不可行。</li>
<li><strong>AI workload 跟 web workload 容量規劃完全不同</strong>：AI workload 短時間爆量創建 Pods（萬級 / 秒）、preempt 頻繁；web workload 節點生命週期長、變動緩。把 web 經驗套到 AI workload 容量規劃會嚴重低估壓力。</li>
</ol>
<p>關鍵判讀是「先決定 cluster 是隔離單位還是治理單位」。Riot Games 把 cluster 當隔離單位（246 個獨立 cluster），Maersk / Bosch 把 cluster 當治理單位（單 cluster 多 namespace）。同一個工具兩種用法、決定整體運維模型。</p>
<p>對應 <a href="/blog/backend/05-deployment-platform/cases/conde-nast-platform-modernization-eks/" data-link-title="5.C2 Condé Nast：EKS 平台整併與標準化" data-link-desc="多地區異質 Kubernetes 平台整併為統一控制面的案例。">5.C2 Condé Nast：EKS 平台整併與標準化</a>：揭露多叢集整併到單一控制面的場景、跟 Maersk-Bosch 同屬「治理一致性」取捨方向（治理單位優先於隔離單位）。Condé Nast 的整併路徑是「盤點既有叢集差異 → 建立統一平台基線 → 藍綠或漸進切換業務流量」、對應前面「分階段平台遷移」段的批次節奏。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>rollout 卡在中段且新副本反覆重啟</td>
          <td>probe 與啟動路徑不匹配</td>
          <td>校正 startup/readiness 探針與超時參數</td>
      </tr>
      <tr>
          <td>rollout 完成後延遲與錯誤率短期上升</td>
          <td>批次切換過快或下游未對齊</td>
          <td>降低批次、延長觀察窗口、回退再重試</td>
      </tr>
      <tr>
          <td>config 變更後特定路徑失敗率飆升</td>
          <td>設定與版本相容窗口不足</td>
          <td>啟動回退配置、補雙軌相容</td>
      </tr>
      <tr>
          <td>autoscaling 在部署期間頻繁抖動</td>
          <td>容量閾值與 rollout 節奏衝突</td>
          <td>分離部署窗口與擴縮窗口、調整資源策略</td>
      </tr>
      <tr>
          <td>長連線服務切版後 reconnect storm</td>
          <td>drain 與連線生命週期控制不足</td>
          <td>拉長 drain、分批切流、校正 timeout</td>
      </tr>
      <tr>
          <td>跨叢集遷移後特定路徑 latency 升高</td>
          <td>應用切過去但依賴未切、跨網路</td>
          <td>規劃依賴切換時機、分批一致</td>
      </tr>
  </tbody>
</table>
<h2 id="常見誤區">常見誤區</h2>
<p>把 Kubernetes 部署看成 YAML 套版，會忽略服務語意差異。相同 deployment 參數在不同服務上，可能代表完全不同風險。</p>
<p>把 probe 當成健康檢查 URL，會讓服務在邊界條件下過早接流量。probe 的工程價值在於反映服務真實可用條件。</p>
<p>把 cluster scale-up 想成「加 node 就好」也是常見誤判。當 cluster 規模超過 control plane 預設邊界，etcd / API server 會先撐不住，加 node 反而加重 control plane 負擔。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>部署切換語意可用 <a href="/blog/backend/05-deployment-platform/cases/failure-platform-cutover-without-drain/" data-link-title="5.C9 反例：平台切流未先 Draining" data-link-desc="切流時忽略連線清退造成請求錯誤與重試風暴。">5.C9 反例</a> 做回寫。先看事件中的失敗是在 rollout 批次、probe 判斷、還是 drain 時序，再對照本章的 rollout 節奏與停止條件。</p>
<p>這個案例主要支撐的是「部署批次與切換時序」判讀，不直接支撐資料庫交易切分或 consumer 冪等；若問題落在提交一致性或重播補償，應轉到 1.3 或 3.4。</p>
<p>若版本已切換但錯誤率延遲上升，先回到 probe 與 config 相容窗口，再把證據欄位接到 <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package</a> 與 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a>。</p>
<h2 id="跨模組路由">跨模組路由</h2>
<p>Kubernetes 部署策略要和觀測、驗證、事故流程同時對齊。</p>
<ol>
<li>與 5.6 的交接：startup / readiness / liveness / drain 的生命週期定義回到 <a href="/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">Platform Lifecycle Contract</a>。</li>
<li>與 5.1 的交接：image、entrypoint、resource limit 的 runtime 層回到 <a href="/blog/backend/05-deployment-platform/container-runtime/" data-link-title="5.1 container 與 runtime" data-link-desc="整理 image、resource limit 與啟動行為">container 與 runtime</a>。</li>
<li>與 5.3 的交接：流量承接與退出落在 <a href="/blog/backend/05-deployment-platform/load-balancer-contract/" data-link-title="5.3 load balancer 合約" data-link-desc="整理 idle timeout、draining 與 health check">load balancer 合約</a>。</li>
<li>與 5.4 的交接：endpoint 註冊與摘除回到 <a href="/blog/backend/05-deployment-platform/service-discovery/" data-link-title="5.4 service discovery" data-link-desc="整理 endpoint discovery 與 DNS">service discovery</a>。</li>
<li>與 5.7 的交接：control plane 跟 data plane 邊界落在 <a href="/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">Traffic、Config 與 Control Plane Boundary</a>。</li>
<li>與 4.20 的交接：版本切換證據進入 <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">Observability Evidence Package</a>。</li>
<li>與 6.8 的交接：放行與停損條件進入 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">Release Gate</a>。</li>
<li>與 8.19 的交接：部署中止與回退判斷進入 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">Incident Decision Log</a>。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>要把部署與流量切換一起治理，接著讀 <a href="/blog/backend/05-deployment-platform/load-balancer-contract/" data-link-title="5.3 load balancer 合約" data-link-desc="整理 idle timeout、draining 與 health check">5.3 load balancer 合約</a>。要看切換失敗與回退判讀，接著讀 <a href="/blog/backend/05-deployment-platform/cases/failure-platform-cutover-without-drain/" data-link-title="5.C9 反例：平台切流未先 Draining" data-link-desc="切流時忽略連線清退造成請求錯誤與重試風暴。">5.C9 反例</a>。要看大規模 K8s 容量設計，接著讀 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a> 跟 <a href="/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/" data-link-title="9.C34 GCP：130,000-node GKE cluster 的工程極限" data-link-desc="Google 用單一 GKE control plane 跑 13 萬個 node、AI workload &#43; 1000 Pods/sec 創建吞吐">9.C34 GCP 130K-node</a>。</p>
]]></content:encoded></item><item><title>cert-manager</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/cert-manager/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/cert-manager/</guid><description>&lt;p>cert-manager 是 K8s 原生的 &lt;em>certificate lifecycle automation&lt;/em> — 把「拿 cert、放 cert、定期 renew」這條從以前需要 cron + certbot + 手動 reload 的鏈、轉成 &lt;em>declarative + controller pattern&lt;/em>。使用者在 cluster 內 apply 一個 &lt;code>Certificate&lt;/code> resource、cert-manager controller 自動跟 issuer 對話、把 cert 存進 Secret、在 lifetime 2/3 點觸發 renew。它把 cert 這件事接進 K8s 控制循環、跟 Pod / Service / Ingress 同等地位的 first-class resource、層級高於 certbot 的 K8s 移植。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>cert-manager 的核心責任是 &lt;em>K8s cluster 內所有 cert 的生命週期治理&lt;/em>。從 Ingress / Gateway 對外 TLS、internal service mTLS、到 workload-level 短期 cert、都用同一套 declarative model 表達。Issuer 抽象讓底層 cert 來源可換 — 公開 cert 走 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/letsencrypt/" data-link-title="Let&amp;#39;s Encrypt" data-link-desc="免費 &amp;#43; 自動化的公共 ACME CA、90 天 TTL 強制自動化、跨雲跨平台 public TLS cert 的事實基礎">Let&amp;rsquo;s Encrypt&lt;/a> ACME、內部 cert 走 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/hashicorp-vault/" data-link-title="HashiCorp Vault" data-link-desc="Self-hosted secret management 與 dynamic credential / encryption-as-a-service / PKI engine、跨雲跨環境的 secret 控制面">Vault PKI engine&lt;/a> 或 self-signed CA、企業環境走 Venafi 或 AWS PCA — 上層 &lt;code>Certificate&lt;/code> spec 不變。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/aws-acm/" data-link-title="AWS ACM" data-link-desc="AWS-managed certificate provisioning、DNS validation &amp;#43; auto-renewal、整合 ELB / CloudFront / API Gateway、Private CA 後端">AWS ACM&lt;/a> 的差異是 &lt;em>cert 的部署面&lt;/em>：ACM 是 AWS-managed cert、只能掛在 AWS service（ELB / CloudFront / API Gateway）、私鑰永不離 AWS；cert-manager 是 K8s-native client、cert 放在 cluster 內的 Secret、可以掛任何 ingress controller 或 workload mTLS。跟 Let&amp;rsquo;s Encrypt 的關係是 &lt;em>client vs issuer&lt;/em> — cert-manager 是 ACME client、Let&amp;rsquo;s Encrypt 是 ACME server、不是替代關係。跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/spire/" data-link-title="SPIRE" data-link-desc="SPIFFE Runtime Environment、attested workload identity、short-lived SVID &amp;#43; Trust Bundle、跨組織 federation">SPIRE&lt;/a> 的差異是 &lt;em>身份模型&lt;/em> — cert-manager 給 &lt;em>DNS-named cert&lt;/em>（CN / SAN 是 hostname）、SPIRE 給 &lt;em>SPIFFE ID-based workload identity&lt;/em>（&lt;code>spiffe://trust-domain/workload&lt;/code>）、兩者互補不衝突。&lt;/p></description><content:encoded><![CDATA[<p>cert-manager 是 K8s 原生的 <em>certificate lifecycle automation</em> — 把「拿 cert、放 cert、定期 renew」這條從以前需要 cron + certbot + 手動 reload 的鏈、轉成 <em>declarative + controller pattern</em>。使用者在 cluster 內 apply 一個 <code>Certificate</code> resource、cert-manager controller 自動跟 issuer 對話、把 cert 存進 Secret、在 lifetime 2/3 點觸發 renew。它把 cert 這件事接進 K8s 控制循環、跟 Pod / Service / Ingress 同等地位的 first-class resource、層級高於 certbot 的 K8s 移植。</p>
<h2 id="服務定位">服務定位</h2>
<p>cert-manager 的核心責任是 <em>K8s cluster 內所有 cert 的生命週期治理</em>。從 Ingress / Gateway 對外 TLS、internal service mTLS、到 workload-level 短期 cert、都用同一套 declarative model 表達。Issuer 抽象讓底層 cert 來源可換 — 公開 cert 走 <a href="/blog/backend/07-security-data-protection/vendors/letsencrypt/" data-link-title="Let&#39;s Encrypt" data-link-desc="免費 &#43; 自動化的公共 ACME CA、90 天 TTL 強制自動化、跨雲跨平台 public TLS cert 的事實基礎">Let&rsquo;s Encrypt</a> ACME、內部 cert 走 <a href="/blog/backend/07-security-data-protection/vendors/hashicorp-vault/" data-link-title="HashiCorp Vault" data-link-desc="Self-hosted secret management 與 dynamic credential / encryption-as-a-service / PKI engine、跨雲跨環境的 secret 控制面">Vault PKI engine</a> 或 self-signed CA、企業環境走 Venafi 或 AWS PCA — 上層 <code>Certificate</code> spec 不變。</p>
<p>跟 <a href="/blog/backend/07-security-data-protection/vendors/aws-acm/" data-link-title="AWS ACM" data-link-desc="AWS-managed certificate provisioning、DNS validation &#43; auto-renewal、整合 ELB / CloudFront / API Gateway、Private CA 後端">AWS ACM</a> 的差異是 <em>cert 的部署面</em>：ACM 是 AWS-managed cert、只能掛在 AWS service（ELB / CloudFront / API Gateway）、私鑰永不離 AWS；cert-manager 是 K8s-native client、cert 放在 cluster 內的 Secret、可以掛任何 ingress controller 或 workload mTLS。跟 Let&rsquo;s Encrypt 的關係是 <em>client vs issuer</em> — cert-manager 是 ACME client、Let&rsquo;s Encrypt 是 ACME server、不是替代關係。跟 <a href="/blog/backend/07-security-data-protection/vendors/spire/" data-link-title="SPIRE" data-link-desc="SPIFFE Runtime Environment、attested workload identity、short-lived SVID &#43; Trust Bundle、跨組織 federation">SPIRE</a> 的差異是 <em>身份模型</em> — cert-manager 給 <em>DNS-named cert</em>（CN / SAN 是 hostname）、SPIRE 給 <em>SPIFFE ID-based workload identity</em>（<code>spiffe://trust-domain/workload</code>）、兩者互補不衝突。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本頁、讀者能判斷：</p>
<ol>
<li>cert-manager 用 Issuer / ClusterIssuer 哪個、配什麼 issuer backend（Let&rsquo;s Encrypt / Vault PKI / self-signed / 公司 CA）</li>
<li>Challenge solver 選 HTTP01 還是 DNS01、為什麼 wildcard cert 必須用 DNF01</li>
<li>Auto-renewal 觸發點、renew 失敗的 alert 時機、跟 Ingress / Gateway API 整合的 annotation</li>
<li>何時用 cert-manager、何時改走 <a href="/blog/backend/07-security-data-protection/vendors/aws-acm/" data-link-title="AWS ACM" data-link-desc="AWS-managed certificate provisioning、DNS validation &#43; auto-renewal、整合 ELB / CloudFront / API Gateway、Private CA 後端">ACM</a>（雲端原生 service）或 <a href="/blog/backend/07-security-data-protection/vendors/spire/" data-link-title="SPIRE" data-link-desc="SPIFFE Runtime Environment、attested workload identity、short-lived SVID &#43; Trust Bundle、跨組織 federation">SPIRE</a>（workload identity）</li>
</ol>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 cert-manager 部署是否健康、最少看四件事：</p>
<ul>
<li><strong>Issuer 配置</strong>：是 <code>ClusterIssuer</code>（cluster-wide）還是 <code>Issuer</code>（namespace-scoped）、backend 是哪一種（acme / vault / ca / venafi）、credential（ACME private key、Vault token、CA cert）放哪、RBAC 限制誰能參考這個 issuer</li>
<li><strong>Certificate spec</strong>：<code>dnsNames</code> / <code>ipAddresses</code> 跟實際 service 一致、<code>duration</code> 跟 <code>renewBefore</code> 比例合理（renewBefore &gt;= duration / 3）、<code>secretName</code> 指向的 Secret 是不是 ingress 真的會讀的那個</li>
<li><strong>Renewal 觸發</strong>：controller log 有沒有按時觸發 renew、<code>kubectl describe certificate</code> 的 <code>Renewal Time</code> 接近沒、Challenge resource 沒有卡在 pending</li>
<li><strong>Challenge solver</strong>：HTTP01 的 ingress / Gateway 80 port 真的能被 Let&rsquo;s Encrypt 從 Internet 打到、DNS01 用的 cloud provider credential 還有效、wildcard cert 沒誤用 HTTP01</li>
</ul>
<p>四件事任一缺失、cert 就會在不知不覺中過期、production 看到 <code>x509: certificate has expired</code> 才驚覺、是 <a href="/blog/backend/07-security-data-protection/transport-trust-and-certificate-lifecycle/" data-link-title="7.5 傳輸信任與憑證生命週期" data-link-desc="以問題驅動方式整理傳輸信任鏈、會話完整性與憑證節奏">Transport Trust and Certificate Lifecycle</a> 的典型缺口。</p>
<h2 id="日常操作與決策形狀">日常操作與決策形狀</h2>
<p><strong>Issuer vs ClusterIssuer 的選擇</strong>：<code>Issuer</code> 是 namespace-scoped、只能 issue 該 namespace 的 cert、適合 <em>單 team 自管 issuer credential</em> 的場景；<code>ClusterIssuer</code> 是 cluster-wide、所有 namespace 都可以參考、適合 <em>平台 team 統一管理 issuer</em>。production 通常用 ClusterIssuer 配特定 issuer backend + RBAC 收 <code>Certificate</code> 建立權（讓 application team 只能在自己 namespace 建 Certificate、不能改 ClusterIssuer）。</p>
<p><strong>Certificate spec 設計</strong>：<code>dnsNames</code> 列出該 cert 涵蓋的 hostname（支援 wildcard <code>*.example.com</code>）、<code>ipAddresses</code> 加 IP SAN（mTLS 跨 service 常用）、<code>duration</code> 是 cert 有效期、<code>renewBefore</code> 是提前多久 renew（預設 duration 的 1/3）。短期 cert（hours-level、Vault PKI 常用）配 <code>renewBefore</code> 短、長期 cert（90 天、Let&rsquo;s Encrypt）配 <code>renewBefore</code> 30 天。<code>secretName</code> 指向 cert-manager 會寫入的 Secret、Ingress 跟 workload 從這個 Secret 讀。</p>
<p><strong>Challenge solver 的選擇</strong>：ACME issuer（Let&rsquo;s Encrypt）需要證明 <em>你控制這個 domain</em>、有兩個方法：HTTP01（在 <code>http://yourdomain/.well-known/acme-challenge/&lt;token&gt;</code> 放檔案、Let&rsquo;s Encrypt 從 Internet 來抓）跟 DNS01（在 DNS zone 加 <code>_acme-challenge.yourdomain TXT &lt;token&gt;</code> record、Let&rsquo;s Encrypt 查 DNS）。<strong>wildcard cert（<code>*.example.com</code>）必須用 DNS01</strong>、HTTP01 不支援 wildcard 因為 Let&rsquo;s Encrypt 不知道要打哪個 subdomain。HTTP01 要求 ingress controller 80 port 對 Internet 開放、DNS01 要求 cluster 有 cloud DNS API credential。</p>
<p><strong>Auto-renewal 機制</strong>：cert-manager 在 cert lifetime 達到 <code>(duration - renewBefore)</code> 時間時觸發 renew、預設約 lifetime 2/3 點。Let&rsquo;s Encrypt cert 90 天 = 60 天時開始嘗試 renew、留 30 天緩衝給 renew 失敗的重試。renew 失敗會持續重試（exponential backoff、最長 8 小時間隔）、剩下 ~7 天時 controller log 開始 ERROR 級別 alert — 監控要 hook 進這個 log 訊號、否則 cert 真的過期才知道就太晚。</p>
<p><strong>跟 Ingress 整合</strong>：Ingress resource 加 annotation <code>cert-manager.io/cluster-issuer: letsencrypt-prod</code>（或 <code>cert-manager.io/issuer:</code>）、cert-manager 看到 Ingress 的 <code>tls.hosts</code> 自動建立對應 Certificate、issue 完寫進 <code>tls.secretName</code> 指定的 Secret、ingress controller 自動 reload 用新 cert。Gateway API 的整合機制類似、用 <code>cert-manager.io/issuer</code> annotation 在 <code>Gateway</code> resource。</p>
<p><strong>CertificateRequest Approval Policy（v1.4+）</strong>：每個 Certificate 建立會產生 CertificateRequest、由 Approver 決定要不要送給 issuer。預設 cert-manager 內建 approver 自動 approve、但可以加 admission policy（Kyverno / OPA / 自寫 webhook）限制「誰能在哪個 namespace 建什麼 SAN 的 cert」— 防 internal compromise 任意 issue cert 對外冒名。production 環境通常會在 platform-level 鎖 wildcard cert、防 application team 誤建涵蓋整個 zone 的 cert。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>cert-manager</th>
          <th>AWS ACM</th>
          <th>手動 certbot / OpenSSL</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>部署模型</td>
          <td>K8s controller、declarative <code>Certificate</code> resource</td>
          <td>AWS managed、Console / API request</td>
          <td>手動跑 CLI、cron 跑 renew</td>
      </tr>
      <tr>
          <td>Cert 部署面</td>
          <td>K8s Secret、任何 ingress controller / workload</td>
          <td>只能掛 ELB / CloudFront / API Gateway</td>
          <td>任何地方、但 deploy 要自己做</td>
      </tr>
      <tr>
          <td>Issuer 彈性</td>
          <td>多 issuer（ACME / Vault / Venafi / CA / AWS PCA）</td>
          <td>只能 Amazon CA</td>
          <td>任何 ACME provider、但要手寫 hook</td>
      </tr>
      <tr>
          <td>Auto-renewal</td>
          <td>內建 controller、預設 2/3 lifetime 點 renew</td>
          <td>AWS 自動 renew（DNS-validated only）</td>
          <td>自己寫 cron + reload script</td>
      </tr>
      <tr>
          <td>Wildcard 支援</td>
          <td>走 DNS01 challenge</td>
          <td>支援、需 DNS 驗證</td>
          <td>走 DNS01 hook</td>
      </tr>
      <tr>
          <td>私鑰位置</td>
          <td>K8s Secret（cluster 內、需 RBAC + etcd encryption）</td>
          <td>AWS 內、不可 export</td>
          <td>Local filesystem、要自己管</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>K8s cluster 內所有 cert、跨 issuer、internal mTLS</td>
          <td>AWS-only serving cert（ELB / CDN）</td>
          <td>非 K8s 的 server、舊系統</td>
      </tr>
      <tr>
          <td>退場成本</td>
          <td>中 — 改其他 ACME client 或回手動</td>
          <td>高 — 私鑰拿不出來、要重新 issue</td>
          <td>低 — 完全自管</td>
      </tr>
  </tbody>
</table>
<p>選 cert-manager 的核心訴求：<em>cluster 內 cert 跨 issuer 統一管理 + 自動 renew + 跟 Ingress / Gateway declarative 整合</em>。如果 cert 完全給 AWS service 用、不進 K8s workload、ACM 更簡單（不用裝 controller、AWS 自動處理）。如果是非 K8s 環境（VM、bare-metal Nginx）、certbot + cron 仍是合理選擇、不需要為了 cert 跑 K8s controller。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>DNS01 challenge 跟 cloud DNS 整合</strong>：cert-manager 支援多家 cloud DNS provider 作為 DNS01 solver — Route53、Cloud DNS（GCP）、Azure DNS、Cloudflare、ACMEDNS（自管 DNS proxy）。每個 provider 需要 <em>DNS zone 寫入 credential</em>（IAM role、service account key、API token）— 這份 credential 等於 <em>任意改該 zone DNS record 的權力</em>、blast radius 大、要走 <a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">least privilege</a> 限定到 specific zone + 只給 TXT record write、不要全 zone 全 record type。</p>
<p><strong>跟 Vault PKI engine 整合</strong>：cert-manager 可用 <a href="/blog/backend/07-security-data-protection/vendors/hashicorp-vault/" data-link-title="HashiCorp Vault" data-link-desc="Self-hosted secret management 與 dynamic credential / encryption-as-a-service / PKI engine、跨雲跨環境的 secret 控制面">Vault PKI engine</a> 作為 issuer backend — 在 cluster 內建 <code>Issuer</code> / <code>ClusterIssuer</code> type 為 <code>vault</code>、指向 Vault address + PKI mount path + auth method（Kubernetes auth / AppRole）。每張 cert 的 issue / revoke 都進 Vault audit log、跟 secret rotation 用同一套 evidence chain（呼應 <a href="/blog/backend/07-security-data-protection/credential-rotation-scoped-evidence/" data-link-title="7.27 Credential Rotation with Scoped Evidence 實作示範" data-link-desc="以 webhook/API credential 輪替示範 scope map、證據欄位與回退窗口如何一起設計。">Credential Rotation Scoped Evidence</a>）。typical 用法：short-lived workload mTLS cert（hours-level duration、minutes-level renewBefore）、靠 Vault PKI 短期 cert + cert-manager 自動換。</p>
<p><strong>跟 SPIRE 的互補</strong>：cert-manager 自動更新 cert、但 <em>cert 是給人讀的 DNS name</em>；SPIRE 自動建立 workload identity、<em>identity 是 SPIFFE ID</em>。兩者解不同問題 — cert-manager 解「Ingress / external API 的 TLS」、SPIRE 解「service A 要怎麼證明自己是 A 給 service B 看」。production 環境常 <em>並存</em>：edge cert 跟 user-facing TLS 用 cert-manager + Let&rsquo;s Encrypt、internal service mesh 用 SPIRE + SPIFFE。</p>
<p><strong>Trust bundle 管理（trust-manager）</strong>：trust-manager 是 cert-manager 姐妹專案、解決 <em>trust anchor（root CA bundle）跨 namespace 同步</em> 問題。傳統做法是每個 pod ConfigMap 各自塞 CA bundle、更新時要逐個改；trust-manager 提供 <code>Bundle</code> resource 一處定義、自動 distribute 到指定 namespace 的 ConfigMap。對應 <em>cert rotation</em> 跟 <em>CA rotation</em> 是兩條獨立 chain、後者是 trust-manager 的領域。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Challenge 卡在 pending</strong>：HTTP01 卡 = ingress 80 port 沒對 Internet、firewall / NLB 沒開、redirect 80→443 把 challenge 也轉了；DNS01 卡 = DNS provider credential 過期、IAM 沒 zone write 權、<code>_acme-challenge</code> record 沒寫進去 — <code>kubectl describe challenge</code> 看 reason</li>
<li><strong>Wildcard cert 用 HTTP01</strong>：申請失敗 + log 寫 &ldquo;wildcard not supported with HTTP-01&rdquo; — 改 DNS01 solver</li>
<li><strong>renewBefore 太短</strong>：renew 失敗只剩幾天才 alert、實際過期前來不及處理 — <code>renewBefore</code> 至少 duration / 3、production cert 給 30 天</li>
<li><strong>Secret 沒被 ingress 讀到</strong>：Certificate 已 Ready 但 ingress 還用舊 cert — ingress <code>tls.secretName</code> 拼錯、ingress controller 沒 reload、TLS handshake 用的 SNI 沒匹配</li>
<li><strong>ACME rate limit 撞牆</strong>：<a href="/blog/backend/07-security-data-protection/vendors/letsencrypt/" data-link-title="Let&#39;s Encrypt" data-link-desc="免費 &#43; 自動化的公共 ACME CA、90 天 TTL 強制自動化、跨雲跨平台 public TLS cert 的事實基礎">Let&rsquo;s Encrypt rate limit</a> 每週同 domain 50 cert / 同 account 300 pending — 反覆建錯 Certificate 重 issue 會撞、staging environment 用 <code>letsencrypt-staging</code> issuer 測過再上 prod</li>
<li><strong>ClusterIssuer 被 application team 誤改</strong>：沒設 RBAC、任何 namespace 都能 patch ClusterIssuer — 用 admission policy 鎖 ClusterIssuer 變更權給 platform team</li>
<li><strong>Approval Policy 缺失</strong>：任何 namespace 能建 wildcard cert、internal compromise 拿到 K8s API token 就能 issue 假冒 cert — 上 CertificateRequest Approval Policy + Kyverno / OPA rule</li>
</ul>
<h2 id="何時改走其他服務">何時改走其他服務</h2>
<table>
  <thead>
      <tr>
          <th>需求形狀</th>
          <th>改走</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>AWS-only serving cert（ELB / CloudFront）</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/aws-acm/" data-link-title="AWS ACM" data-link-desc="AWS-managed certificate provisioning、DNS validation &#43; auto-renewal、整合 ELB / CloudFront / API Gateway、Private CA 後端">AWS ACM</a></td>
      </tr>
      <tr>
          <td>非 K8s 環境（VM、bare-metal）的 ACME cert</td>
          <td>certbot / acme.sh / <a href="/blog/backend/07-security-data-protection/vendors/letsencrypt/" data-link-title="Let&#39;s Encrypt" data-link-desc="免費 &#43; 自動化的公共 ACME CA、90 天 TTL 強制自動化、跨雲跨平台 public TLS cert 的事實基礎">Let&rsquo;s Encrypt</a> 直接用</td>
      </tr>
      <tr>
          <td>Workload identity（不是 DNS-named cert）</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/spire/" data-link-title="SPIRE" data-link-desc="SPIFFE Runtime Environment、attested workload identity、short-lived SVID &#43; Trust Bundle、跨組織 federation">SPIRE</a>（SPIFFE-based）</td>
      </tr>
      <tr>
          <td>大量短期 internal cert + 完整 PKI 治理</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/hashicorp-vault/" data-link-title="HashiCorp Vault" data-link-desc="Self-hosted secret management 與 dynamic credential / encryption-as-a-service / PKI engine、跨雲跨環境的 secret 控制面">Vault PKI engine</a>（可配 cert-manager 為 client）</td>
      </tr>
      <tr>
          <td>公司既有 enterprise CA（Venafi / DigiCert）</td>
          <td>cert-manager + Venafi issuer / 商用 issuer plugin</td>
      </tr>
      <tr>
          <td>全公司 cert rotation 證據鏈</td>
          <td><a href="/blog/backend/07-security-data-protection/credential-rotation-scoped-evidence/" data-link-title="7.27 Credential Rotation with Scoped Evidence 實作示範" data-link-desc="以 webhook/API credential 輪替示範 scope map、證據欄位與回退窗口如何一起設計。">7.5 Credential Rotation Scoped Evidence</a></td>
      </tr>
  </tbody>
</table>
<h2 id="不在本頁內的主題">不在本頁內的主題</h2>
<ul>
<li>cert-manager Helm chart 的所有 value 細節跟版本相容性矩陣</li>
<li>每個 issuer backend 的完整 schema（acme / vault / venafi / ca / selfSigned）</li>
<li>Gateway API 跟 Ingress API 的 cert-manager annotation 完整對照</li>
<li>ACME RFC 8555 protocol 細節（HTTP01 / DNS01 / TLS-ALPN-01 challenge mechanism）</li>
<li>trust-manager 的 Bundle source 種類（inMemory / secret / configMap / defaultPackage）</li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<p>cert-manager 在 07 案例庫沒有直接 vendor-level 事件、以下案例採對照引用：</p>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>跟 cert-manager 的關係（對照）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/transport-trust-and-certificate-lifecycle/" data-link-title="7.5 傳輸信任與憑證生命週期" data-link-desc="以問題驅動方式整理傳輸信任鏈、會話完整性與憑證節奏">Transport Trust and Certificate Lifecycle (section)</a></td>
          <td>cert-manager 是 cert lifecycle automation 的具體實作 — auto-renewal + Challenge solver + Approval Policy 是 lifecycle 治理三層機制</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/credential-rotation-scoped-evidence/" data-link-title="7.27 Credential Rotation with Scoped Evidence 實作示範" data-link-desc="以 webhook/API credential 輪替示範 scope map、證據欄位與回退窗口如何一起設計。">Credential Rotation Scoped Evidence (section)</a></td>
          <td>cert-manager 的 renewal 自動但 <em>revocation 流程不自動</em> — 舊 cert 失效後 fleet 層級 trust bundle update 是另一條 chain、走 trust-manager</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-bleed-2023-session-hijack/" data-link-title="7.R7.3.3 Citrix Bleed 2023：會話被劫持與重放風險" data-link-desc="邊界設備會話資料外洩後，如何演變成帳號與服務風險">Citrix Bleed 2023 Session Hijack</a></td>
          <td>對照啟示 — cert 更新後 session 仍可能延續、cert-manager 只管 cert lifecycle、session invalidation 是另一層責任、不要把 cert rotation 當 session 失效手段</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.6 秘密管理與機器憑證治理</a>、<a href="/blog/backend/07-security-data-protection/transport-trust-and-certificate-lifecycle/" data-link-title="7.5 傳輸信任與憑證生命週期" data-link-desc="以問題驅動方式整理傳輸信任鏈、會話完整性與憑證節奏">Transport Trust and Certificate Lifecycle</a></li>
<li>平行：<a href="/blog/backend/07-security-data-protection/vendors/letsencrypt/" data-link-title="Let&#39;s Encrypt" data-link-desc="免費 &#43; 自動化的公共 ACME CA、90 天 TTL 強制自動化、跨雲跨平台 public TLS cert 的事實基礎">Let&rsquo;s Encrypt</a>（ACME issuer）、<a href="/blog/backend/07-security-data-protection/vendors/aws-acm/" data-link-title="AWS ACM" data-link-desc="AWS-managed certificate provisioning、DNS validation &#43; auto-renewal、整合 ELB / CloudFront / API Gateway、Private CA 後端">AWS ACM</a>（AWS-managed cert）、<a href="/blog/backend/07-security-data-protection/vendors/spire/" data-link-title="SPIRE" data-link-desc="SPIFFE Runtime Environment、attested workload identity、short-lived SVID &#43; Trust Bundle、跨組織 federation">SPIRE</a>（workload identity）</li>
<li>下游：<a href="/blog/backend/07-security-data-protection/vendors/hashicorp-vault/" data-link-title="HashiCorp Vault" data-link-desc="Self-hosted secret management 與 dynamic credential / encryption-as-a-service / PKI engine、跨雲跨環境的 secret 控制面">HashiCorp Vault</a>（PKI engine 作為 issuer backend）</li>
<li>跨模組：<a href="/blog/backend/08-incident-response/vendors/" data-link-title="事故處理 Vendor 清單" data-link-desc="規劃 on-call、incident response、status page 與 postmortem 工具的服務頁撰寫順序與判準">8 事故處理 vendor 清單</a>（cert 過期 / mis-issue 事件如何 routing）</li>
<li>官方：<a href="https://cert-manager.io/docs/">cert-manager Documentation</a></li>
</ul>
]]></content:encoded></item><item><title>Kubernetes Graceful Shutdown：termination 序列跟你以為的不一樣</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/vendors/kubernetes/graceful-shutdown/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/vendors/kubernetes/graceful-shutdown/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/vendors/kubernetes/" data-link-title="Kubernetes" data-link-desc="Container orchestration 主流、GKE / EKS / AKS / 自管">Kubernetes&lt;/a> overview 的 implementation-layer deep article。Overview 已說明 K8s 在 deployment platform 譜系的定位、本文聚焦 &lt;em>pod termination&lt;/em> 這個 production 最常踩、被誤解最深的議題：序列、配置、五個 case、跟 service mesh 整合。&lt;/p>&lt;/blockquote>
&lt;h2 id="graceful-shutdown-沒做對500-期間每次-deploy-都吃-502">Graceful shutdown 沒做對、500 期間每次 deploy 都吃 502&lt;/h2>
&lt;p>最常見的觸發場景：deploy 新 image、prometheus alert 在 5 分鐘內收到一波 502 / 503、SRE 翻 application log 看到「正在處理 request」「connection closed」交替出現。Application 本身沒 bug、但 K8s 在 pod terminate 時跟 traffic 來源 &lt;em>沒對齊步調&lt;/em>、舊 pod 還在處理請求時就被 SIGKILL、新 request 還在打到準備關閉的 pod 上。&lt;/p>
&lt;p>很多團隊修法是 &lt;em>把 terminationGracePeriodSeconds 從 30 拉到 120&lt;/em>、暫時掩蓋問題；但症狀會在下次 rolling update / HPA scale-down / node drain 時換個形式回來。根因在 &lt;em>termination 序列&lt;/em> — pod 不是收到 SIGTERM 就 graceful、序列裡每一步出錯都有不同 fail mode。&lt;/p>
&lt;h2 id="termination-序列五步每步都能爆">Termination 序列：五步、每步都能爆&lt;/h2>
&lt;p>K8s 收到 delete pod 請求後、發生的事 &lt;em>按時間&lt;/em> 是：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>時序&lt;/th>
 &lt;th>事件&lt;/th>
 &lt;th>動作來源&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>t=0&lt;/td>
 &lt;td>API server 標 pod 為 Terminating&lt;/td>
 &lt;td>kubelet 收到 delete&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>t=0&lt;/td>
 &lt;td>Pod 從 Service Endpoints 移除（&lt;strong>async&lt;/strong>）&lt;/td>
 &lt;td>endpoint controller&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>t=0&lt;/td>
 &lt;td>kubelet 跑 preStop hook（若有定義）&lt;/td>
 &lt;td>container runtime&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>t=preStop 結束&lt;/td>
 &lt;td>container 收到 SIGTERM&lt;/td>
 &lt;td>container runtime&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>t=SIGTERM + terminationGracePeriodSeconds&lt;/td>
 &lt;td>container 收到 SIGKILL&lt;/td>
 &lt;td>container runtime&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵誤解：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>「pod 從 Service 移除」跟「container 收到 SIGTERM」是 &lt;em>平行&lt;/em>、不是序列&lt;/strong>。Endpoint controller 更新 Endpoints object → kube-proxy 重新寫 iptables → 各 node 的 traffic 才真正停 — 這條鏈通常需要 &lt;em>1-5 秒&lt;/em>；同時間 SIGTERM 已經發給 application。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>preStop hook 是「container 還在跑、SIGTERM 還沒發」期間執行&lt;/strong>。pre-Stop 設 &lt;code>sleep 10&lt;/code> 是 production 標準作法 — 用 sleep 讓 endpoint controller 有時間把 pod 從 Service 移除、避免 SIGTERM 期間還有新 request 進來。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/05-deployment-platform/vendors/kubernetes/" data-link-title="Kubernetes" data-link-desc="Container orchestration 主流、GKE / EKS / AKS / 自管">Kubernetes</a> overview 的 implementation-layer deep article。Overview 已說明 K8s 在 deployment platform 譜系的定位、本文聚焦 <em>pod termination</em> 這個 production 最常踩、被誤解最深的議題：序列、配置、五個 case、跟 service mesh 整合。</p></blockquote>
<h2 id="graceful-shutdown-沒做對500-期間每次-deploy-都吃-502">Graceful shutdown 沒做對、500 期間每次 deploy 都吃 502</h2>
<p>最常見的觸發場景：deploy 新 image、prometheus alert 在 5 分鐘內收到一波 502 / 503、SRE 翻 application log 看到「正在處理 request」「connection closed」交替出現。Application 本身沒 bug、但 K8s 在 pod terminate 時跟 traffic 來源 <em>沒對齊步調</em>、舊 pod 還在處理請求時就被 SIGKILL、新 request 還在打到準備關閉的 pod 上。</p>
<p>很多團隊修法是 <em>把 terminationGracePeriodSeconds 從 30 拉到 120</em>、暫時掩蓋問題；但症狀會在下次 rolling update / HPA scale-down / node drain 時換個形式回來。根因在 <em>termination 序列</em> — pod 不是收到 SIGTERM 就 graceful、序列裡每一步出錯都有不同 fail mode。</p>
<h2 id="termination-序列五步每步都能爆">Termination 序列：五步、每步都能爆</h2>
<p>K8s 收到 delete pod 請求後、發生的事 <em>按時間</em> 是：</p>
<table>
  <thead>
      <tr>
          <th>時序</th>
          <th>事件</th>
          <th>動作來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>t=0</td>
          <td>API server 標 pod 為 Terminating</td>
          <td>kubelet 收到 delete</td>
      </tr>
      <tr>
          <td>t=0</td>
          <td>Pod 從 Service Endpoints 移除（<strong>async</strong>）</td>
          <td>endpoint controller</td>
      </tr>
      <tr>
          <td>t=0</td>
          <td>kubelet 跑 preStop hook（若有定義）</td>
          <td>container runtime</td>
      </tr>
      <tr>
          <td>t=preStop 結束</td>
          <td>container 收到 SIGTERM</td>
          <td>container runtime</td>
      </tr>
      <tr>
          <td>t=SIGTERM + terminationGracePeriodSeconds</td>
          <td>container 收到 SIGKILL</td>
          <td>container runtime</td>
      </tr>
  </tbody>
</table>
<p>關鍵誤解：</p>
<ol>
<li>
<p><strong>「pod 從 Service 移除」跟「container 收到 SIGTERM」是 <em>平行</em>、不是序列</strong>。Endpoint controller 更新 Endpoints object → kube-proxy 重新寫 iptables → 各 node 的 traffic 才真正停 — 這條鏈通常需要 <em>1-5 秒</em>；同時間 SIGTERM 已經發給 application。</p>
</li>
<li>
<p><strong>preStop hook 是「container 還在跑、SIGTERM 還沒發」期間執行</strong>。pre-Stop 設 <code>sleep 10</code> 是 production 標準作法 — 用 sleep 讓 endpoint controller 有時間把 pod 從 Service 移除、避免 SIGTERM 期間還有新 request 進來。</p>
</li>
<li>
<p><strong>terminationGracePeriodSeconds 是 <em>從 preStop 開始</em> 計時、不是從 SIGTERM</strong>。preStop sleep 10s + application 30s graceful = 至少要設 40s。</p>
</li>
<li>
<p><strong>graceful 不是 framework 自動的</strong>。Application 必須 <em>主動處理 SIGTERM</em>：拒絕新 request、等 in-flight 完成、close DB connection、flush log。沒處理 SIGTERM、container 會在 grace period 後被強殺。</p>
</li>
<li>
<p><strong>readiness probe 在 Terminating 期間 <em>仍會被執行</em>、但結果不影響 traffic</strong>（已經從 Endpoints 移除）。但若 application 沒主動讓 readiness fail、service mesh / external LB 可能仍在送 request（依不同 mesh 行為）。</p>
</li>
</ol>
<h2 id="配置全圖">配置全圖</h2>
<h3 id="deployment-spec">Deployment spec</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">      </span><span class="nt">terminationGracePeriodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">60</span><span class="w">          </span><span class="c"># SIGTERM 後 60s 才 SIGKILL</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">app</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">          </span><span class="nt">lifecycle</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">            </span><span class="nt">preStop</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">              </span><span class="nt">exec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">                </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;/bin/sh&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-c&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;sleep 10&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">          </span><span class="nt">readinessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">            </span><span class="nt">httpGet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">              </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/healthz/ready</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">              </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">            </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">            </span><span class="nt">failureThreshold</span><span class="p">:</span><span class="w"> </span><span class="m">2</span></span></span></code></pre></div><p>時序：t=0 preStop 開始 sleep 10s → t=10s container SIGTERM → t=70s SIGKILL（不是 t=60s、是 60s after SIGTERM）。</p>
<h3 id="application-處理-sigtermgo-範例">Application 處理 SIGTERM（Go 範例）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nx">sigs</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="nx">os</span><span class="p">.</span><span class="nx">Signal</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">signal</span><span class="p">.</span><span class="nf">Notify</span><span class="p">(</span><span class="nx">sigs</span><span class="p">,</span> <span class="nx">syscall</span><span class="p">.</span><span class="nx">SIGTERM</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">server</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">http</span><span class="p">.</span><span class="nx">Server</span><span class="p">{</span><span class="nx">Addr</span><span class="p">:</span> <span class="s">&#34;:8080&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">go</span> <span class="nx">server</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="o">&lt;-</span><span class="nx">sigs</span>                                              <span class="c1">// 等 SIGTERM</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;SIGTERM received, draining...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// 1. readiness fail（讓 mesh-aware 流量停）</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">ready</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">// 2. wait 5s 讓 readiness probe failureThreshold 觸發</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">5</span> <span class="o">*</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1">// 3. graceful shutdown server（拒新請求、等 in-flight）</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nx">ctx</span><span class="p">,</span> <span class="nx">cancel</span> <span class="o">:=</span> <span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span> <span class="mi">45</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">defer</span> <span class="nf">cancel</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nx">server</span><span class="p">.</span><span class="nf">Shutdown</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1">// 4. close DB / cache / message consumer</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nx">db</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nx">consumer</span><span class="p">.</span><span class="nf">Stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1">// 5. flush log + exit</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="nx">logger</span><span class="p">.</span><span class="nf">Sync</span><span class="p">()</span></span></span></code></pre></div><p>關鍵：<code>server.Shutdown(ctx)</code> 是 <em>拒新請求、等 in-flight</em>、ctx timeout 設 <em>grace period 減去 preStop sleep 跟 readiness fail 等待時間</em>（60s - 10s - 5s = 45s）。</p>
<h2 id="production-故障演練">Production 故障演練</h2>
<h3 id="case-1rolling-update-期間-502--503">Case 1：Rolling update 期間 502 / 503</h3>
<p><strong>徵兆</strong>：每次 deploy 後 5 分鐘內 LB / ingress log 一波 502 / 503、application log 顯示「context canceled」「connection closed by peer」、新 pod 已 ready 但舊 pod 在 grace period 內仍收 request。</p>
<p><strong>根因</strong>：沒設 preStop sleep、container 收到 SIGTERM 後立刻 <code>server.Shutdown()</code>、但 kube-proxy 還沒把舊 pod 從 iptables 移除、新 request 持續送到舊 pod、舊 pod 已拒收。</p>
<p><strong>修法</strong>：preStop <code>sleep 10</code>、讓 endpoint propagation 完成再進入 SIGTERM 流程。</p>
<h3 id="case-2connection-drain-racelong-running-request-被中斷">Case 2：Connection drain race，long-running request 被中斷</h3>
<p><strong>徵兆</strong>：deploy 後 application log 有大量 <code>context canceled</code> 對應到 long-running endpoint（例：報表生成、檔案上傳）、user 端看到 transaction 失敗、但短 request 沒事。</p>
<p><strong>根因</strong>：long-running endpoint 處理時間 &gt; terminationGracePeriodSeconds、<code>server.Shutdown(ctx)</code> ctx timeout 設太短、in-flight 強制中斷。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>把 long-running endpoint 改 async（背景 job + status endpoint）、HTTP request 立刻 return job ID</li>
<li>短期：terminationGracePeriodSeconds 拉到 long-running 99 percentile + buffer</li>
<li>application 側 ctx timeout = grace period - preStop - readiness fail wait</li>
</ol>
<h3 id="case-3init-container-在-grace-period-期間重啟sigterm-沒到-main">Case 3：Init container 在 grace period 期間重啟、SIGTERM 沒到 main</h3>
<p><strong>徵兆</strong>：pod 顯示 Terminating 但 phase 一直在 Running、main container restart count + 1、application log 沒看到「SIGTERM received」。</p>
<p><strong>根因</strong>：init container 用 <code>restartPolicy: Always</code>（K8s 1.28+ sidecar 模式）、或 main container 在 SIGTERM 前先 crash 觸發 restart、kubelet 在 restart 後 <em>不重發 SIGTERM</em>、main container 跑到 grace period 結束直接 SIGKILL。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>Sidecar container（restartPolicy: Always）的 preStop 也要設 <code>sleep</code>、跟 main 同 lifecycle</li>
<li>main container readinessProbe 失敗時 <em>別自動 restart</em>（restartPolicy: OnFailure + crashLoopBackOff 觀察）</li>
<li>觀察 <code>kubectl describe pod</code> 的 events、SIGTERM 沒發出來會有 <code>Killing container</code> event 缺失</li>
</ol>
<h3 id="case-4statefulset-串行終止總時間--pod-數--grace-period">Case 4：StatefulSet 串行終止、總時間 = pod 數 × grace period</h3>
<p><strong>徵兆</strong>：StatefulSet rolling update / scale-down 比 Deployment 慢 N 倍（N = replica 數）、deploy 一個 5 replica 的 statefulset 要 5 分鐘以上。</p>
<p><strong>根因</strong>：StatefulSet 預設 <code>podManagementPolicy: OrderedReady</code> — pod 串行終止 + 串行創建、每個 pod 至少要 grace period 完成才動下一個。Deployment 用 <code>RollingUpdate</code> 預設 maxUnavailable=25% 平行終止。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>StatefulSet 改 <code>podManagementPolicy: Parallel</code>（若 application 不要求嚴格順序）</li>
<li>嚴格順序情境（Cassandra / Kafka / etcd）保留 OrderedReady、但 grace period 設 <em>單 pod 必要時間</em>、不要設 <em>總時間能承受</em></li>
<li>接受序列化代價、把 deploy 排在低流量時段</li>
</ol>
<h3 id="case-5job--cronjob-不-gracefulsigterm-直接-sigkill">Case 5：Job / CronJob 不 graceful、SIGTERM 直接 SIGKILL</h3>
<p><strong>徵兆</strong>：CronJob 在 Job timeout / pod eviction 時不 graceful、寫一半的 file 留在 PVC、下次跑時 corrupt；application log 沒「SIGTERM received」、直接斷。</p>
<p><strong>根因</strong>：Job 的 <code>activeDeadlineSeconds</code> 到期 / node eviction 觸發時、K8s 對 Job pod <em>仍會發 SIGTERM</em>、但 <em>很多 batch framework（Spring Batch / Argo Workflow worker）沒處理 SIGTERM</em>、application 沒主動 checkpoint。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>Batch application 處理 SIGTERM、checkpoint 進度寫 storage、下次跑時 resume</li>
<li>不適合 checkpoint 的 batch、保證 <em>idempotent re-run</em>、SIGKILL 後重跑不會 corrupt</li>
<li>Job spec 加 <code>terminationGracePeriodSeconds</code>（預設 30、batch 通常要 60-300）</li>
</ol>
<h2 id="規模影響">規模影響</h2>
<p>Graceful shutdown 的成本主要在 <em>deploy 時間</em> 跟 <em>capacity buffer</em>：</p>
<table>
  <thead>
      <tr>
          <th>規模因素</th>
          <th>影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>terminationGracePeriod 60s</td>
          <td>單 pod deploy ~70-80s（含 preStop + grace + new pod startup）</td>
      </tr>
      <tr>
          <td>Deployment 100 replica + maxSurge 25%</td>
          <td>全 deploy ~5-10 分鐘、需要 <em>25% extra capacity</em>（25 replica buffer）</td>
      </tr>
      <tr>
          <td>StatefulSet 串行 + 60s grace</td>
          <td>10 replica 約 10-12 分鐘、deploy window 要在低流量時段</td>
      </tr>
      <tr>
          <td>HPA scale-down 跟 graceful 一起跑</td>
          <td>scale-down 觸發 → preStop + grace + new metric → 下次 scale 判斷、avg 反應週期 ≈ 3-5 分鐘</td>
      </tr>
  </tbody>
</table>
<p>實務 default：</p>
<ul>
<li>Web service：<code>terminationGracePeriodSeconds: 60</code>、preStop sleep 10、application graceful 45s</li>
<li>Backend worker（消費 queue）：<code>terminationGracePeriodSeconds: 120</code>、preStop 不 sleep（用 readiness 控）、application 處理當前 message + commit offset</li>
<li>Batch job：<code>terminationGracePeriodSeconds: 300</code>、checkpoint pattern</li>
<li>StatefulSet（DB / queue）：grace period 對齊 vendor 建議（Kafka 90s、PostgreSQL 60s）</li>
</ul>
<h2 id="跟其他元件整合">跟其他元件整合</h2>
<h3 id="service-meshistio--linkerd">Service mesh（Istio / Linkerd）</h3>
<p>Service mesh sidecar（envoy / linkerd-proxy）也有自己的 termination — 通常比 main container 晚一點關。配置原則：</p>
<ol>
<li>mesh sidecar 設 <code>terminationGracePeriodSeconds</code> 比 main 多 5-10s、main 處理完才換 sidecar</li>
<li>Istio 1.12+ 的 <code>proxy.istio.io/config.holdApplicationUntilProxyStarts</code> 控啟動順序、shutdown 也要對應</li>
<li>mTLS 環境 graceful 多一道：在 SIGTERM 後等 mesh 主動 close cert rotation、不要硬斷</li>
</ol>
<h3 id="readiness-probe-跟-mesh-aware-traffic">Readiness probe 跟 mesh-aware traffic</h3>
<p>純 K8s Service（kube-proxy iptables）：endpoint 移除後 <em>已建立 connection 仍會跑完</em>、新 connection 不來。Mesh-aware traffic（service mesh / external LB with health check）：要 readiness fail 才會停送。</p>
<p>修法：application graceful 第一步是 <code>ready.Store(false)</code> + 等 readiness probe 至少 fail 一次（5-10s）、才開始 server.Shutdown。</p>
<h3 id="跟-pod-disruption-budgetpdb的衝突">跟 Pod Disruption Budget（PDB）的衝突</h3>
<p>Node drain 時 PDB 限制可同時 unavailable 的 pod 數、graceful shutdown 拖長會讓 drain 卡住。對策：</p>
<ol>
<li>緊急 drain（node 硬體故障）：<code>kubectl drain --grace-period=30 --force</code>、接受短時間 502</li>
<li>正常 drain（升級 / 維運）：PDB 設 <code>minAvailable: &lt;replicas-1&gt;</code>、容許單 pod 慢慢 graceful</li>
<li>不要設 <code>maxUnavailable: 0</code>、會讓 drain 卡死</li>
</ol>
<h2 id="下一步">下一步</h2>
<ul>
<li><strong>Application graceful 寫法</strong>：<a href="https://12factor.net/disposability">12-factor app</a> disposability 章節給 framework-agnostic 模板、各語言 SDK 寫法見對應 framework</li>
<li><strong>Queue consumer 的 graceful</strong>：訊息 ack / offset commit 必須在 SIGTERM 內完成、否則 duplicate message — 對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 message queue</a> 模組的 consumer-design 段</li>
<li><strong>跨 region / 多 cluster 的 graceful</strong>：multi-cluster service mesh（Istio multicluster / Linkerd multicluster）的 traffic shift 期間 graceful 行為跟單 cluster 不同、需要對齊 mesh 配置</li>
</ul>
<h2 id="相關連結">相關連結</h2>
<ul>
<li>上游 vendor 頁：<a href="/blog/backend/05-deployment-platform/vendors/kubernetes/" data-link-title="Kubernetes" data-link-desc="Container orchestration 主流、GKE / EKS / AKS / 自管">Kubernetes</a></li>
<li>上游 chapter：<a href="/blog/backend/05-deployment-platform/deployment-rollout-drain-rollback/" data-link-title="5.8 Deployment Rollout with Drain and Rollback（實作示範）" data-link-desc="以 checkout service 示範部署切換如何交付 canary evidence、drain signal、release gate 與 incident decision log。">5.X deployment-rollout-drain-rollback</a></li>
<li>對照案例：rolling update 期間 502 多見於 stage-3 mesh adoption case 庫</li>
<li>平行 deep article：<a href="/blog/backend/01-database/vendors/postgresql/pgbouncer-config/" data-link-title="PostgreSQL pgBouncer 配置 &#43; 連線池治理" data-link-desc="pgBouncer transaction pooling 配置、跟 application connection pool 的分層、production 故障演練（pool exhaustion / stale connection / DNS failover）跟容量規劃">pgBouncer 配置</a> / <a href="/blog/backend/07-security-data-protection/vendors/hashicorp-vault/dynamic-credential/" data-link-title="HashiCorp Vault Dynamic Credential：lease 治理跟 application 整合的實作層" data-link-desc="Vault database secrets engine 怎麼配、application 怎麼 renew lease、production 五大踩雷（lease 過期 race、DB max_connections 撞牆、Vault sealed、token expire、scope 過寬）、容量規劃跟 vault-agent injector 整合">Vault Dynamic Credential</a></li>
<li>Methodology：<a href="/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">Vendor 深度技術文章的寫作方法論</a></li>
</ul>
]]></content:encoded></item><item><title>Docker Swarm → Kubernetes：5 個 Swarm production cluster 撞牆數據</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/vendors/kubernetes/migrate-from-docker-swarm/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/vendors/kubernetes/migrate-from-docker-swarm/</guid><description>&lt;blockquote>
&lt;p>本文是跨 vendor migration playbook、cross-link Docker Swarm 跟 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/vendors/kubernetes/" data-link-title="Kubernetes" data-link-desc="Container orchestration 主流、GKE / EKS / AKS / 自管">Kubernetes&lt;/a>。跑 &lt;a href="https://tarrragon.github.io/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">migration-playbook-methodology 6 維 audit&lt;/a> 後對映 &lt;em>Paradigm = High（Swarm 簡單 container orchestration → K8s declarative resource model）→ Type E paradigm shift&lt;/em>。&lt;/p>&lt;/blockquote>
&lt;h2 id="5-個-swarm-production-cluster-撞牆數據">5 個 Swarm production cluster 撞牆數據&lt;/h2>
&lt;p>從 2020-2024 觀察 5 個中型 organization 的 Swarm production cluster lifecycle、典型撞牆點：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Cluster&lt;/th>
 &lt;th>規模 (peak)&lt;/th>
 &lt;th>撞牆點&lt;/th>
 &lt;th>觸發遷移時間&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>A (SaaS startup)&lt;/td>
 &lt;td>80 service / 12 node&lt;/td>
 &lt;td>service discovery latency 升、無 sidecar mesh&lt;/td>
 &lt;td>2022&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>B (E-commerce)&lt;/td>
 &lt;td>150 service / 25 node&lt;/td>
 &lt;td>rolling update + canary 邏輯自寫複雜&lt;/td>
 &lt;td>2023&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>C (Fintech)&lt;/td>
 &lt;td>60 service / 15 node&lt;/td>
 &lt;td>secret rotation + RBAC 自管、合規難&lt;/td>
 &lt;td>2023&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>D (Media)&lt;/td>
 &lt;td>200 service / 40 node&lt;/td>
 &lt;td>autoscaling 自寫、預測流量失敗&lt;/td>
 &lt;td>2024&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>E (Logistics)&lt;/td>
 &lt;td>100 service / 20 node&lt;/td>
 &lt;td>multi-region 不支援&lt;/td>
 &lt;td>2024&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>5 個共同 pattern：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Swarm 簡單但 ceiling 100-200 service / 20-40 node&lt;/strong>&lt;/li>
&lt;li>&lt;strong>跨 service 治理（mesh / RBAC / secret / autoscale）需要 &lt;em>外掛&lt;/em> 工具、複雜度反超 K8s&lt;/strong>&lt;/li>
&lt;li>&lt;strong>無 multi-region native&lt;/strong>、災備受限&lt;/li>
&lt;li>&lt;strong>生態縮、社群活躍度低、新 feature 緩&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>撞牆點不是「Swarm 跑不動」、是「Swarm 不會幫你解 &lt;em>跨 service 治理&lt;/em> 問題、要自寫」。Kubernetes 不是 simpler、是 &lt;em>把治理問題納入框架&lt;/em>。&lt;/p>
&lt;h2 id="為什麼遷ceiling--ecosystem--multi-region-三條-driver">為什麼遷：ceiling / ecosystem / multi-region 三條 driver&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Driver&lt;/th>
 &lt;th>觸發&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Ceiling&lt;/td>
 &lt;td>Swarm 跑 100-200 service 後 service discovery latency / scheduling 跟不上&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ecosystem&lt;/td>
 &lt;td>K8s ecosystem (Helm / Operator / mesh / GitOps) 成熟、Swarm 對等工具缺&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Multi-region&lt;/td>
 &lt;td>Swarm 不支援、K8s 多 cluster federation 成熟&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>反向 driver（K8s → Swarm）：&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是跨 vendor migration playbook、cross-link Docker Swarm 跟 <a href="/blog/backend/05-deployment-platform/vendors/kubernetes/" data-link-title="Kubernetes" data-link-desc="Container orchestration 主流、GKE / EKS / AKS / 自管">Kubernetes</a>。跑 <a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">migration-playbook-methodology 6 維 audit</a> 後對映 <em>Paradigm = High（Swarm 簡單 container orchestration → K8s declarative resource model）→ Type E paradigm shift</em>。</p></blockquote>
<h2 id="5-個-swarm-production-cluster-撞牆數據">5 個 Swarm production cluster 撞牆數據</h2>
<p>從 2020-2024 觀察 5 個中型 organization 的 Swarm production cluster lifecycle、典型撞牆點：</p>
<table>
  <thead>
      <tr>
          <th>Cluster</th>
          <th>規模 (peak)</th>
          <th>撞牆點</th>
          <th>觸發遷移時間</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>A (SaaS startup)</td>
          <td>80 service / 12 node</td>
          <td>service discovery latency 升、無 sidecar mesh</td>
          <td>2022</td>
      </tr>
      <tr>
          <td>B (E-commerce)</td>
          <td>150 service / 25 node</td>
          <td>rolling update + canary 邏輯自寫複雜</td>
          <td>2023</td>
      </tr>
      <tr>
          <td>C (Fintech)</td>
          <td>60 service / 15 node</td>
          <td>secret rotation + RBAC 自管、合規難</td>
          <td>2023</td>
      </tr>
      <tr>
          <td>D (Media)</td>
          <td>200 service / 40 node</td>
          <td>autoscaling 自寫、預測流量失敗</td>
          <td>2024</td>
      </tr>
      <tr>
          <td>E (Logistics)</td>
          <td>100 service / 20 node</td>
          <td>multi-region 不支援</td>
          <td>2024</td>
      </tr>
  </tbody>
</table>
<p>5 個共同 pattern：</p>
<ul>
<li><strong>Swarm 簡單但 ceiling 100-200 service / 20-40 node</strong></li>
<li><strong>跨 service 治理（mesh / RBAC / secret / autoscale）需要 <em>外掛</em> 工具、複雜度反超 K8s</strong></li>
<li><strong>無 multi-region native</strong>、災備受限</li>
<li><strong>生態縮、社群活躍度低、新 feature 緩</strong></li>
</ul>
<p>撞牆點不是「Swarm 跑不動」、是「Swarm 不會幫你解 <em>跨 service 治理</em> 問題、要自寫」。Kubernetes 不是 simpler、是 <em>把治理問題納入框架</em>。</p>
<h2 id="為什麼遷ceiling--ecosystem--multi-region-三條-driver">為什麼遷：ceiling / ecosystem / multi-region 三條 driver</h2>
<table>
  <thead>
      <tr>
          <th>Driver</th>
          <th>觸發</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Ceiling</td>
          <td>Swarm 跑 100-200 service 後 service discovery latency / scheduling 跟不上</td>
      </tr>
      <tr>
          <td>Ecosystem</td>
          <td>K8s ecosystem (Helm / Operator / mesh / GitOps) 成熟、Swarm 對等工具缺</td>
      </tr>
      <tr>
          <td>Multi-region</td>
          <td>Swarm 不支援、K8s 多 cluster federation 成熟</td>
      </tr>
  </tbody>
</table>
<p>反向 driver（K8s → Swarm）：</p>
<ul>
<li>純 internal tool / 小規模（&lt; 30 service）、K8s 過度複雜</li>
<li>Edge / IoT scenario、Swarm footprint 小</li>
</ul>
<h2 id="6-維-audit">6 維 audit</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>等級</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Schema / API</td>
          <td><strong>High</strong>（docker-compose stack.yml → K8s YAML、syntax 完全不同）</td>
      </tr>
      <tr>
          <td>Operational</td>
          <td>Medium（Swarm 自管 → K8s self-host or managed）</td>
      </tr>
      <tr>
          <td>Paradigm</td>
          <td><strong>High</strong>（簡單 container orchestration → declarative resource model）</td>
      </tr>
      <tr>
          <td>Components</td>
          <td>Low（同 1 個 orchestration 系統）</td>
      </tr>
      <tr>
          <td>Application change</td>
          <td>Low（container image 不變）</td>
      </tr>
      <tr>
          <td>Data topology</td>
          <td>Low</td>
      </tr>
  </tbody>
</table>
<p>Schema + Paradigm 雙 High → <strong>Type E paradigm shift</strong> 為主、Schema 高維獨立段。</p>
<h2 id="paradigm-對位">Paradigm 對位</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>Swarm</th>
          <th>K8s</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Workload unit</td>
          <td>Service</td>
          <td>Deployment + Pod + Service</td>
      </tr>
      <tr>
          <td>Stack 定義</td>
          <td>stack.yml (docker-compose 格式)</td>
          <td>YAML manifest (multiple resources)</td>
      </tr>
      <tr>
          <td>Networking</td>
          <td>Overlay network (built-in)</td>
          <td>CNI plugin (Calico / Cilium / etc)</td>
      </tr>
      <tr>
          <td>Service discovery</td>
          <td>DNS-based built-in</td>
          <td>DNS-based (CoreDNS) + Service object</td>
      </tr>
      <tr>
          <td>Load balancing</td>
          <td>Built-in routing mesh</td>
          <td>Service + Ingress + LoadBalancer</td>
      </tr>
      <tr>
          <td>Secret management</td>
          <td>Docker secrets</td>
          <td>K8s Secret + 外部 Vault / Secrets Manager</td>
      </tr>
      <tr>
          <td>Rolling update</td>
          <td><code>docker service update --image ...</code></td>
          <td>Deployment + rolling update + readiness probe</td>
      </tr>
      <tr>
          <td>Autoscaling</td>
          <td>手動 scale</td>
          <td>HPA (Horizontal Pod Autoscaler)</td>
      </tr>
      <tr>
          <td>RBAC</td>
          <td>Limited (Swarm enterprise)</td>
          <td>First-class (Role / RoleBinding / ServiceAccount)</td>
      </tr>
      <tr>
          <td>Persistent storage</td>
          <td>Volume + driver plugin</td>
          <td>PV / PVC + CSI driver</td>
      </tr>
      <tr>
          <td>Service mesh</td>
          <td>無 (要外掛 Traefik)</td>
          <td>Istio / Linkerd / Cilium</td>
      </tr>
      <tr>
          <td>GitOps</td>
          <td>無 native</td>
          <td>Argo CD / Flux (first-class)</td>
      </tr>
  </tbody>
</table>
<h2 id="schema-gapdocker-compose-vs-k8s-yaml">Schema gap：docker-compose vs K8s YAML</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># Docker Swarm stack.yml</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3.8&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">webapp</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">myapp:1.0</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">deploy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">      </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">      </span><span class="nt">update_config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">        </span><span class="nt">parallelism</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">      </span><span class="nt">restart_policy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">        </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="kc">on</span>-<span class="l">failure</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">      </span>- <span class="l">frontend</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">      </span>- <span class="s2">&#34;8080:8080&#34;</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># K8s equivalent (Deployment + Service + Ingress)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">webapp</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">  </span><span class="nt">strategy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">RollingUpdate</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="nt">rollingUpdate</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">      </span><span class="nt">maxSurge</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">      </span><span class="nt">maxUnavailable</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">webapp }</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">webapp }</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">webapp</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">          </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">myapp:1.0</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">          </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">            </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">          </span><span class="nt">readinessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">            </span><span class="nt">httpGet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">              </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/healthz</span><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">              </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">          </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="w">            </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w">              </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="l">100m</span><span class="w">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="w">              </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="l">128Mi</span><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w">            </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="w">              </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="l">500m</span><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="w">              </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="l">512Mi</span><span class="w">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">webapp</span><span class="w">
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">webapp }</span><span class="w">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="w">    </span>- <span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="w">      </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span></span></span></code></pre></div><p>1 Swarm service → 2-3 K8s resource（Deployment + Service + 可能 Ingress / HPA）；application 不改但 <em>deployment 端工作量 5-10x</em>。</p>
<h2 id="migration-流程">Migration 流程</h2>
<h3 id="partial-migration--混合架構">Partial migration + 混合架構</h3>
<p>跟 <a href="/blog/backend/03-message-queue/vendors/kafka/migrate-from-to-nats/" data-link-title="Kafka ↔ NATS：不是 migration、是 messaging paradigm 重設計" data-link-desc="Kafka 跟 NATS 不是同類產品（log-based event streaming vs subject-based messaging）、&#39;migration&#39; 字面上不成立；本文釐清兩家 paradigm 邊界、什麼情境真的能換、application 模式重設計的 5 個踩雷（consumer offset 觀念差 / retention model / exactly-once 假設 / schema registry 缺位 / fan-out 模式差）、跟 JetStream 對位 &#43; 混合架構">Kafka ↔ NATS</a> / <a href="/blog/backend/05-deployment-platform/vendors/consul/migrate-from-etcd/" data-link-title="etcd → Consul：KV &#43; N 個 extras feature matrix" data-link-desc="etcd → Consul 是 Type E paradigm shift expansion — 從 pure KV store 升到 service mesh / discovery / health check / multi-DC；本文用對照表 &#43; paradigm expansion 路線、5 個 production 踩雷（API 對位 / lock semantics / watch event model / multi-DC topology / ACL system）">etcd → Consul</a> 同 Type E pattern：</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">1. Audit application：列所有 Swarm stack + service
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">2. 分類處理 plan:
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">   - 簡單 stateless: 先切 K8s (低風險)
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   - Stateful (DB / queue): 評估 K8s operator 或保留 Swarm
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   - Critical service: 雙跑期確認 K8s 行為對等
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">3. K8s cluster 建置:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   - Managed (EKS / GKE / AKS) vs self-host (kubeadm)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   - 配 ingress controller / cert-manager / monitoring
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">4. Application 遷移 (per stack)
</span></span><span class="line"><span class="ln">10</span><span class="cl">   - 寫 K8s YAML / Helm chart
</span></span><span class="line"><span class="ln">11</span><span class="cl">   - 配 readiness/liveness probe / resource request
</span></span><span class="line"><span class="ln">12</span><span class="cl">   - Networking + secret 對位
</span></span><span class="line"><span class="ln">13</span><span class="cl">5. Cutover + Swarm decommission
</span></span><span class="line"><span class="ln">14</span><span class="cl">   - 部分 stack 切完、評估 Swarm 是否保留 (legacy / edge)
</span></span><span class="line"><span class="ln">15</span><span class="cl">   - 多數 organization 完全 decommission Swarm</span></span></code></pre></div><p>整體 3-6 個月、依 stack 數量跟 application 複雜度。</p>
<h2 id="production-故障演練">Production 故障演練</h2>
<h3 id="case-1networking-model-差cross-service-connectivity-失效">Case 1：Networking model 差、cross-service connectivity 失效</h3>
<p><strong>徵兆</strong>：cutover 後 service A 連 service B 失敗、Swarm 端 <code>tasks.service_b</code> DNS 對位 K8s 端 <code>service-b.namespace.svc.cluster.local</code> 不通。</p>
<p><strong>根因</strong>：Swarm overlay network 內 service-to-service 用 short name (<code>service_b</code>)、K8s 用 FQDN；application 端 service URL 寫死。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>Application 端用 short name + cluster DNS search domain</li>
<li>K8s 端設 <code>dnsPolicy: ClusterFirst</code> 預設、確認 <code>kubectl get svc -A</code> 對應</li>
<li>NetworkPolicy 預設 deny-all、明示 allow rule</li>
</ol>
<h3 id="case-2secret-rotation-從-swarm-secrets-換-vault--secrets-manager">Case 2：Secret rotation 從 Swarm secrets 換 Vault / Secrets Manager</h3>
<p><strong>徵兆</strong>：原本 Swarm 用 <code>docker secret</code> 旋轉 secret、切 K8s 後 K8s Secret 是 <em>static value</em>、rotation 不自動。</p>
<p><strong>根因</strong>：K8s Secret 是 K8s-native 但 <em>not auto-rotated</em>、需要外部 Vault / Secrets Manager + agent (vault-agent-injector / external-secrets-operator)。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>K8s 端 deploy external-secrets-operator + AWS Secrets Manager / Vault integration</li>
<li>Application 端 mount file or env variable、不在 code 寫死</li>
<li>Rotation 走 vendor-side、K8s 端 sidecar 自動 reload</li>
</ol>
<h3 id="case-3readiness-probe-沒設rolling-update-期間-traffic-loss">Case 3：Readiness probe 沒設、rolling update 期間 traffic loss</h3>
<p><strong>徵兆</strong>：cutover 後 deploy 期間 application 5-10% request 失敗；發現 pod startup 完成前就接 traffic。</p>
<p><strong>根因</strong>：Swarm 簡單 restart_policy 沒對等 probe 概念；K8s 預設 deploy 後 immediate ready、若沒 readiness probe、startup 時間長的 application 會在未 ready 時接流量。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>必加 readiness probe</strong>：HTTP / TCP / exec check</li>
<li><strong>配 initial delay</strong>：JVM application 預留 30-60s</li>
<li><strong>配 <code>minReadySeconds</code></strong>：deployment 端設 30s 確保 stable</li>
</ol>
<h3 id="case-4hpa-預設不啟autoscaling-失效">Case 4：HPA 預設不啟、autoscaling 失效</h3>
<p><strong>徵兆</strong>：Swarm 端寫了 cron-based autoscale script、切 K8s 後 script 失效、流量高峰沒 scale up。</p>
<p><strong>根因</strong>：K8s HPA 不是預設啟動、需要 <em>明示配置</em> + metrics-server install。</p>
<p><strong>修法</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">autoscaling/v2</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">HorizontalPodAutoscaler</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">webapp-hpa</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">  </span><span class="nt">scaleTargetRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">webapp</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">  </span><span class="nt">minReplicas</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">  </span><span class="nt">maxReplicas</span><span class="p">:</span><span class="w"> </span><span class="m">20</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">  </span><span class="nt">metrics</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">Resource</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">      </span><span class="nt">resource</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cpu</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">        </span><span class="nt">target</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">Utilization</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">          </span><span class="nt">averageUtilization</span><span class="p">:</span><span class="w"> </span><span class="m">70</span></span></span></code></pre></div><p>裝 metrics-server / Keda（event-driven autoscaling）+ 配 HPA per Deployment。</p>
<h3 id="case-5yaml-維護地獄helm--kustomize-配置遲">Case 5：YAML 維護地獄、Helm / Kustomize 配置遲</h3>
<p><strong>徵兆</strong>：cutover 後 K8s YAML 從 5 個檔（Swarm stack）變 50+ 個 K8s manifest；每個 application 端要改一個 config 都要動 N 個 file。</p>
<p><strong>根因</strong>：K8s YAML 是 <em>very verbose</em>、不像 docker-compose 簡潔；缺 templating 跟 environment 抽象。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>Helm chart</strong>：對 application 包成 chart、用 <code>values.yaml</code> 抽象環境差異</li>
<li><strong>Kustomize</strong>：base + overlay pattern、不靠 templating</li>
<li><strong>GitOps with Argo CD / Flux</strong>：宣告式部署、降 manual kubectl 操作</li>
</ol>
<h2 id="capacity--cost">Capacity / cost</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Docker Swarm</th>
          <th>Kubernetes (managed)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cluster cost (mid-tier)</td>
          <td>$300-800 / mo</td>
          <td>$500-1500 / mo（EKS/GKE/AKS control plane + nodes）</td>
      </tr>
      <tr>
          <td>Operational FTE</td>
          <td>0.3-0.8</td>
          <td>0.5-1.5（除非 managed、降到 0.3-0.7）</td>
      </tr>
      <tr>
          <td>Ecosystem maturity</td>
          <td>低、衰退</td>
          <td>高、active growth</td>
      </tr>
      <tr>
          <td>Multi-region</td>
          <td>不支援</td>
          <td>多 cluster federation 成熟</td>
      </tr>
      <tr>
          <td>Migration cost</td>
          <td>-</td>
          <td>2-4 FTE × 3-6 個月</td>
      </tr>
      <tr>
          <td>Long-term ROI</td>
          <td>Negative（社群縮）</td>
          <td>Positive（feature growth）</td>
      </tr>
  </tbody>
</table>
<p><strong>判讀</strong>：&lt; 30 service 小 organization 可不切；50+ service 開始撞 Swarm ceiling、值得評估；100+ service / multi-region 必切。</p>
<h2 id="整合--下一步">整合 / 下一步</h2>
<h3 id="跟-service-mesh-整合">跟 Service mesh 整合</h3>
<p>Cutover 後 <em>順便</em> 評估 Istio / Linkerd / Cilium service mesh、cover mTLS / observability / traffic policy；不要在 Swarm migration 後立刻上 mesh、分階段。</p>
<h3 id="跟-gitops-整合">跟 GitOps 整合</h3>
<p>K8s + Argo CD / Flux 是 <em>natural pair</em>；migration 時直接走 GitOps、避免 manual kubectl 操作累積。</p>
<h3 id="跟-vault--aws-secrets-manager-對齊">跟 <a href="/blog/backend/07-security-data-protection/vendors/hashicorp-vault/migrate-to-aws-secrets-manager/" data-link-title="Vault → AWS Secrets Manager：「secret」不是「secret」、identity model 才是核心差異" data-link-desc="Vault → AWS Secrets Manager migration 表面是 secret store 替換、實際核心是 identity model 對位（Vault token &#43; policy vs AWS IAM &#43; resource policy）；驗證 [#128](/report/data-topology-as-audit-dimension/) self-aware limitation 提出的 identity axis 候選 — identity 是否獨立 audit 軸；5 個 production 踩雷（IAM principal 對位 / dynamic credential 對等失敗 / lease lifecycle 模型不同 / audit log 結構差 / 計費模型反轉）">Vault → AWS Secrets Manager</a> 對齊</h3>
<p>Swarm secrets → K8s Secret → external secrets management 是 <em>3-step 演進</em>、不是 1-step；migration 期間先用 K8s Secret、之後切 Vault / Secrets Manager。</p>
<h2 id="相關連結">相關連結</h2>
<ul>
<li>Target vendor：<a href="/blog/backend/05-deployment-platform/vendors/kubernetes/" data-link-title="Kubernetes" data-link-desc="Container orchestration 主流、GKE / EKS / AKS / 自管">Kubernetes</a></li>
<li>平行 migration playbook (Type E)：<a href="/blog/backend/03-message-queue/vendors/kafka/migrate-from-to-nats/" data-link-title="Kafka ↔ NATS：不是 migration、是 messaging paradigm 重設計" data-link-desc="Kafka 跟 NATS 不是同類產品（log-based event streaming vs subject-based messaging）、&#39;migration&#39; 字面上不成立；本文釐清兩家 paradigm 邊界、什麼情境真的能換、application 模式重設計的 5 個踩雷（consumer offset 觀念差 / retention model / exactly-once 假設 / schema registry 缺位 / fan-out 模式差）、跟 JetStream 對位 &#43; 混合架構">Kafka ↔ NATS</a> / <a href="/blog/backend/02-cache-redis/vendors/redis/migrate-to-memcached/" data-link-title="Redis → Memcached：Memcached 不是 simpler Redis、是 cache paradigm" data-link-desc="Redis → Memcached 是 Type E paradigm reduction migration — 從 multi-paradigm（KV &#43; 資料結構 &#43; pub/sub &#43; Lua &#43; streams）退到 pure cache；不是「remove Redis features」、是「重新分配 Redis-specific feature 到對應 specialized 服務」；5 個 production 踩雷 &#43; paradigm reduction 路線">Redis → Memcached</a> / <a href="/blog/backend/05-deployment-platform/vendors/consul/migrate-from-etcd/" data-link-title="etcd → Consul：KV &#43; N 個 extras feature matrix" data-link-desc="etcd → Consul 是 Type E paradigm shift expansion — 從 pure KV store 升到 service mesh / discovery / health check / multi-DC；本文用對照表 &#43; paradigm expansion 路線、5 個 production 踩雷（API 對位 / lock semantics / watch event model / multi-DC topology / ACL system）">etcd → Consul</a> / <a href="/blog/backend/04-observability/vendors/honeycomb/migrate-from-sentry/" data-link-title="Sentry → Honeycomb：trace 不是 error、是不同 observability paradigm" data-link-desc="Sentry → Honeycomb 是 paradigm shift — Sentry 主軸是 error tracking &#43; transaction trace、Honeycomb 主軸是 high-cardinality wide-event observability；本文釐清 paradigm 邊界、5 個 production 踩雷（event schema 對位 / sampling 行為 / error grouping 失效 / cost 模型差 / alert paradigm shift）">Sentry → Honeycomb</a></li>
<li>Methodology：<a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">Migration playbook methodology</a></li>
</ul>
]]></content:encoded></item><item><title>Kyverno</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/kyverno/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/kyverno/</guid><description>&lt;p>Kyverno 是 K8s-native 的 policy engine、CNCF Incubating（2024 升級）、設計 mindset 把 &lt;em>policy 寫成 YAML&lt;/em> 而不是引入新語言（vs &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &amp;#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA&lt;/a> 的 Rego、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/gatekeeper/" data-link-title="OPA Gatekeeper" data-link-desc="OPA 官方 K8s admission controller、ConstraintTemplate &amp;#43; Constraint 兩層、Rego policy &amp;#43; Audit &amp;#43; Mutation">Gatekeeper&lt;/a> 也用 Rego）。它的核心不是「更輕量的 OPA」、而是 &lt;em>K8s 專用 policy engine&lt;/em> — 把 Validate / Mutate / Generate / Verify Images / Cleanup 五類動作做成 first-class rule type、跟 K8s admission webhook + GitOps + cosign / Sigstore ecosystem 深度整合。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>Kyverno 的定位是 &lt;em>K8s admission controller-shaped policy engine、policy 用 YAML 表達&lt;/em>。底層是 dynamic admission webhook + background controller、頂層 CRD 包含 &lt;em>ClusterPolicy&lt;/em>（cluster 範圍）/ &lt;em>Policy&lt;/em>（namespace 範圍）/ &lt;em>PolicyException&lt;/em>（明確例外）/ &lt;em>ClusterCleanupPolicy&lt;/em>（過期 resource 清理）/ &lt;em>PolicyReport&lt;/em>（CIS / NIST 等審計輸出）。Nirmata 是 Kyverno 商業版、補 policy library / multi-cluster management / audit dashboard / 24x7 support。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &amp;#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA&lt;/a> 比、Kyverno 走 &lt;em>narrow + opinionated&lt;/em> — OPA 是 general-purpose policy engine（K8s / API gateway / Terraform / 自家服務都能用、語言是 Rego）、Kyverno &lt;em>K8s-only + YAML&lt;/em>、學習成本對 K8s admin 接近零。跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/gatekeeper/" data-link-title="OPA Gatekeeper" data-link-desc="OPA 官方 K8s admission controller、ConstraintTemplate &amp;#43; Constraint 兩層、Rego policy &amp;#43; Audit &amp;#43; Mutation">Gatekeeper&lt;/a> 比、Gatekeeper 也是 K8s admission controller 但底層用 OPA + Rego、ConstraintTemplate / Constraint 兩層 CRD；Kyverno 不用 Rego、policy 就是 YAML rule list。跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/trivy/" data-link-title="Trivy" data-link-desc="Aqua Security 開源 all-in-one scanner：Container / Filesystem / K8s / IaC &amp;#43; Secret &amp;#43; License &amp;#43; SBOM、Apache 2.0、CI 友善">Trivy&lt;/a> 的 misconfig scan 比、Trivy 是 &lt;em>scan static manifest&lt;/em>、Kyverno 是 &lt;em>admission gate + background scan&lt;/em>、定位互補不衝突。&lt;/p></description><content:encoded><![CDATA[<p>Kyverno 是 K8s-native 的 policy engine、CNCF Incubating（2024 升級）、設計 mindset 把 <em>policy 寫成 YAML</em> 而不是引入新語言（vs <a href="/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA</a> 的 Rego、<a href="/blog/backend/07-security-data-protection/vendors/gatekeeper/" data-link-title="OPA Gatekeeper" data-link-desc="OPA 官方 K8s admission controller、ConstraintTemplate &#43; Constraint 兩層、Rego policy &#43; Audit &#43; Mutation">Gatekeeper</a> 也用 Rego）。它的核心不是「更輕量的 OPA」、而是 <em>K8s 專用 policy engine</em> — 把 Validate / Mutate / Generate / Verify Images / Cleanup 五類動作做成 first-class rule type、跟 K8s admission webhook + GitOps + cosign / Sigstore ecosystem 深度整合。</p>
<h2 id="服務定位">服務定位</h2>
<p>Kyverno 的定位是 <em>K8s admission controller-shaped policy engine、policy 用 YAML 表達</em>。底層是 dynamic admission webhook + background controller、頂層 CRD 包含 <em>ClusterPolicy</em>（cluster 範圍）/ <em>Policy</em>（namespace 範圍）/ <em>PolicyException</em>（明確例外）/ <em>ClusterCleanupPolicy</em>（過期 resource 清理）/ <em>PolicyReport</em>（CIS / NIST 等審計輸出）。Nirmata 是 Kyverno 商業版、補 policy library / multi-cluster management / audit dashboard / 24x7 support。</p>
<p>跟 <a href="/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA</a> 比、Kyverno 走 <em>narrow + opinionated</em> — OPA 是 general-purpose policy engine（K8s / API gateway / Terraform / 自家服務都能用、語言是 Rego）、Kyverno <em>K8s-only + YAML</em>、學習成本對 K8s admin 接近零。跟 <a href="/blog/backend/07-security-data-protection/vendors/gatekeeper/" data-link-title="OPA Gatekeeper" data-link-desc="OPA 官方 K8s admission controller、ConstraintTemplate &#43; Constraint 兩層、Rego policy &#43; Audit &#43; Mutation">Gatekeeper</a> 比、Gatekeeper 也是 K8s admission controller 但底層用 OPA + Rego、ConstraintTemplate / Constraint 兩層 CRD；Kyverno 不用 Rego、policy 就是 YAML rule list。跟 <a href="/blog/backend/07-security-data-protection/vendors/trivy/" data-link-title="Trivy" data-link-desc="Aqua Security 開源 all-in-one scanner：Container / Filesystem / K8s / IaC &#43; Secret &#43; License &#43; SBOM、Apache 2.0、CI 友善">Trivy</a> 的 misconfig scan 比、Trivy 是 <em>scan static manifest</em>、Kyverno 是 <em>admission gate + background scan</em>、定位互補不衝突。</p>
<p>關鍵張力：<em>YAML policy 的表達力上限</em> ↔ <em>跨平台統一 policy 的訴求</em>。Kyverno YAML rule 對 90% K8s 場景夠用、但需要跨 K8s / API gateway / Terraform 統一 policy decision 時、Rego 的表達力跟可移植性勝出。要看清楚 policy <em>邊界是否就在 K8s 內</em>。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本頁、讀者能判斷：</p>
<ol>
<li>Kyverno 在 K8s 治理 stack 中承擔哪一段（admission gate / mutation / generation / image verify / cleanup）、跟 <a href="/blog/backend/07-security-data-protection/vendors/trivy/" data-link-title="Trivy" data-link-desc="Aqua Security 開源 all-in-one scanner：Container / Filesystem / K8s / IaC &#43; Secret &#43; License &#43; SBOM、Apache 2.0、CI 友善">Trivy</a> scan / <a href="/blog/backend/07-security-data-protection/vendors/syft-grype/" data-link-title="Syft &#43; Grype" data-link-desc="Anchore 開源姐妹工具：Syft 產 SBOM (CycloneDX / SPDX) &#43; Grype scan 漏洞、Unix philosophy、cosign attestation 整合">SBOM Tools</a> / Sigstore cosign 怎麼分工</li>
<li>ClusterPolicy / Policy 的 ownership 設計（platform team 還是 app team 寫、誰 review、PolicyException 怎麼治理）</li>
<li>Validate / Mutate / Generate / Verify Images / Cleanup 五類 rule 的使用邊界跟陷阱</li>
<li>何時用 Kyverno、何時走 OPA / Gatekeeper / K8s native ValidatingAdmissionPolicy 的取捨</li>
</ol>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Kyverno deployment 是否健康、最少看四件事：</p>
<ul>
<li><strong>Policy 是否走 GitOps</strong>：ClusterPolicy / Policy 是否在 Git 版控、走 ArgoCD / Flux sync、policy change 是否經 PR review、staging cluster 跑過 audit mode 才 promote 到 enforce</li>
<li><strong>Mode 配置</strong>：每條 policy 是 <em>Audit</em>（只記、不擋）還是 <em>Enforce</em>（擋 admission）、新規則是否先 audit 觀察 24-48hr 再 enforce、Background scan 是否開（補 admission 不到的 historical drift）</li>
<li><strong>Verify Images 啟用度</strong>：production cluster 是否要求 image 必須通過 cosign signature verify、SBOM attestation 是否驗、policy 是否包含 keyless verify（Fulcio + Rekor）</li>
<li><strong>PolicyException 治理</strong>：例外是否走 PR 申請 + 到期日 + owner、跟 <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">Detection Coverage and Signal Governance</a> 的 exception governance 對齊</li>
</ul>
<p>四件事任一缺失、就是 <a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.12 供應鏈完整性</a> 邊界的待補項目。</p>
<h2 id="日常操作與決策形狀">日常操作與決策形狀</h2>
<p><strong>ClusterPolicy / Policy 結構</strong>：Kyverno policy 是 K8s CRD、結構 <code>spec.rules[]</code> 一條條 rule、每條 rule 有 <code>match</code>（套用對象、kind / namespace / label / name）+ <code>exclude</code>（明確排除）+ rule body（<code>validate</code> / <code>mutate</code> / <code>generate</code> / <code>verifyImages</code> / <code>cleanup</code> 五選一）。ClusterPolicy 套整個 cluster、Policy 套單一 namespace、app team 通常只能改自家 namespace 的 Policy、平台 team 控 ClusterPolicy。</p>
<p><strong>Validate rule</strong>：admission 階段檢查 manifest 是否符合條件、不符合就拒絕。最常見場景 — 禁止 <code>latest</code> tag、要求所有 pod 有 resource limit、禁止 privileged container、要求 specific label。寫法是 <code>validate.pattern</code> 或 <code>validate.deny</code>（後者支援更複雜的 boolean expression）、output 是 admission webhook reject。Validate 是 <em>K8s policy as code</em> 的入門場景、80% 的 ClusterPolicy 都是 Validate rule。</p>
<p><strong>Mutate rule</strong>：admission 階段修改 manifest、把缺的欄位補上或改成符合的值。常見場景 — 自動注入 sidecar（service mesh proxy / log forwarder）、自動加 resource limit default、自動加 label（cost center / owner）、自動把 imagePullPolicy 改成 <code>Always</code>。Mutate 是 OPA / Gatekeeper 做不到的（兩者都偏 Validate-only）、是 Kyverno 的 <em>K8s-specific 強項</em>。陷阱是 mutate 變更後 GitOps diff 會永遠不一致、要在 ArgoCD ignoreDifferences 上對齊。</p>
<p><strong>Generate rule</strong>：cluster event（namespace 建立、resource 變動）觸發、自動建立 <em>關聯 resource</em>。最常見場景 — 新 namespace 自動建 default NetworkPolicy（deny-all egress 起手）、自動建 ResourceQuota / LimitRange、自動 copy ConfigMap / Secret 到新 namespace。Generate 是把 <em>security default</em> 從文件層落到 runtime layer、避免 app team 忘記設 NetworkPolicy 就把整個 cluster 暴露。Generate 也是 OPA / Gatekeeper 做不到、Kyverno 獨有。</p>
<p><strong>Verify Images rule</strong>：admission 階段驗證 container image 的簽章 / SBOM attestation / in-toto provenance。實作底層 <a href="https://docs.sigstore.dev/">Sigstore</a> cosign — keyless 簽章驗 Fulcio CA + Rekor transparency log、key-based 驗 public key、attestation 驗 SLSA provenance / SBOM。production 場景 — internal registry image 必須 cosign 簽 + 來自 trusted CI runner、external image 必須在 allowlist。對應 <a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/solarwinds-2020-sunburst/" data-link-title="7.R7.2.1 SolarWinds 2020：更新鏈被濫用" data-link-desc="合法更新流程遭植入後，攻擊者如何長期潛伏與橫向擴散">SolarWinds 2020 Sunburst</a> 的 supply chain attack 防禦邊界。</p>
<p><strong>Cleanup policy</strong>：ClusterCleanupPolicy / CleanupPolicy 是 K8s 1.27+ 引入、Kyverno 1.10+ 支援、按 cron 跑、清掉符合條件的 resource。常見場景 — 過 30 天的 completed Job、過 7 天的 failed Pod、ephemeral namespace（PR preview env）超過 TTL 自動刪。Cleanup 補的是 K8s 沒有 <em>resource lifecycle policy</em> 的洞、TTL controller 只覆蓋 Job / Pod 子集。</p>
<p><strong>Background scan</strong>：除了 admission 攔截 <em>新 resource</em>、Kyverno 定期掃描 <em>已存在 resource</em> 是否違反 policy、結果寫入 PolicyReport CRD。意義是補 <em>歷史 drift</em> — policy 是後來加的、已 deploy 的 resource 不會被 admission 攔到、background scan 才會找出來。production 一定要開、不開等於 policy 只防新犯不抓舊案。</p>
<p><strong>ValidatingAdmissionPolicy (VAP) 整合</strong>：K8s 1.30+ 內建 CEL-based admission policy、不需要 admission webhook（VAP 由 kube-apiserver 直接 enforce、延遲低、不會因為 Kyverno pod 掛掉就讓 admission 失敗）。Kyverno 1.11+ 可以從 ClusterPolicy <em>生成</em> VAP、把簡單 Validate rule 卸載給 K8s native engine、複雜 rule（Mutate / Generate / Verify Images）留在 Kyverno。長期趨勢 — K8s native VAP 會吃掉 Kyverno <em>Validate-only</em> 的場景、Mutate / Generate / Verify Images 仍是 Kyverno 護城河。</p>
<p><strong>GitOps 整合</strong>：ClusterPolicy / Policy 是普通 K8s CRD、走 ArgoCD / Flux sync 沒任何特殊性。staging cluster 跑 Audit mode 24-48hr 看 PolicyReport 有多少違規 → tune rule 或加 PolicyException → 確認沒誤殺再 promote 到 production cluster 的 Enforce mode。對應 <a href="/blog/backend/07-security-data-protection/blue-team/detection-engineering-lifecycle/" data-link-title="7.B5 Detection Engineering Lifecycle" data-link-desc="把偵測規則視為可維護資產，建立從來源、測試、調校到退場的完整生命週期">Detection Engineering Lifecycle</a> 的 propose → staging → promote pattern。</p>
<p><strong>Policy Reporter</strong>：OSS dashboard（不是 Kyverno 內建、是社群專案）、把 PolicyReport CRD 視覺化、給 platform team / app team 看 cluster 違規概況。Nirmata 商業版有更完整的 multi-cluster dashboard + 歷史 trend + compliance mapping（CIS / NIST / PCI）。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>Kyverno</th>
          <th>OPA + Gatekeeper</th>
          <th>OPA standalone</th>
          <th>Conftest</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Policy 語言</td>
          <td>YAML（patterns / deny / preconditions）</td>
          <td>Rego（DSL、表達力強）</td>
          <td>Rego</td>
          <td>Rego</td>
      </tr>
      <tr>
          <td>覆蓋範圍</td>
          <td>K8s only</td>
          <td>K8s only</td>
          <td>K8s / API / Terraform / 任意 JSON 輸入</td>
          <td>CI-time static file（Terraform / Docker）</td>
      </tr>
      <tr>
          <td>Rule 類型</td>
          <td>Validate / Mutate / Generate / Verify Images / Cleanup</td>
          <td>Validate-only（Mutate 是 experimental）</td>
          <td>由 host application 決定</td>
          <td>Validate（CI-time）</td>
      </tr>
      <tr>
          <td>部署形態</td>
          <td>K8s admission webhook + controller</td>
          <td>K8s admission webhook（Gatekeeper 是 OPA 包）</td>
          <td>sidecar / library / standalone server</td>
          <td>CLI（CI pipeline）</td>
      </tr>
      <tr>
          <td>學習曲線</td>
          <td>緩 — K8s admin 已熟 YAML</td>
          <td>陡 — 要學 Rego</td>
          <td>陡 — 要學 Rego + host integration</td>
          <td>中 — Rego 但範圍小</td>
      </tr>
      <tr>
          <td>Image signature</td>
          <td>內建 Verify Images（cosign + Sigstore）</td>
          <td>需自己接 cosign CLI</td>
          <td>需自己接</td>
          <td>不適用</td>
      </tr>
      <tr>
          <td>Background scan</td>
          <td>內建</td>
          <td>gator audit（弱）</td>
          <td>不適用</td>
          <td>不適用</td>
      </tr>
      <tr>
          <td>跨 platform 一致</td>
          <td>弱 — K8s only</td>
          <td>弱 — K8s only</td>
          <td>強 — 同份 Rego 跑 K8s / API / Terraform</td>
          <td>強 — CI 跑同份 Rego</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>K8s-heavy + 想用 YAML + 需 Mutate / Generate / Image</td>
          <td>K8s + 已有 Rego 投資 + Validate-only</td>
          <td>跨 K8s / API / Terraform 統一 policy</td>
          <td>CI-time pre-merge 檢查</td>
      </tr>
      <tr>
          <td>退場成本</td>
          <td>中 — YAML rule 跟 K8s CRD 綁</td>
          <td>中 — Rego 可移植到 OPA standalone</td>
          <td>低 — Rego 跨平台</td>
          <td>低</td>
      </tr>
  </tbody>
</table>
<p>選 Kyverno 的核心訴求：<em>K8s-only 場景 + 不想學 Rego + 需要 Mutate / Generate / Verify Images 的 K8s-specific 能力</em>。團隊已投資 Rego ecosystem、或 policy 邊界跨 K8s + Terraform + API gateway、走 OPA / Gatekeeper 更合適。CI-time pre-merge 檢查走 Conftest 補位。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Verify Images 進階 — cosign keyless + SBOM attestation</strong>：production-grade image trust 不只驗 signature、要驗 <em>who signed it from where with what build process</em>。keyless 模式驗 Fulcio CA-issued 短期憑證 + Rekor transparency log entry、確認簽章來自 trusted CI runner 的 OIDC identity（例如 <code>https://github.com/myorg/myrepo/.github/workflows/release.yaml@refs/tags/v*</code>）。SBOM attestation 用 <code>verifyImages.attestations</code> 驗 in-toto envelope、確認 image 帶 SLSA provenance + SBOM（<a href="/blog/backend/07-security-data-protection/vendors/syft-grype/" data-link-title="Syft &#43; Grype" data-link-desc="Anchore 開源姐妹工具：Syft 產 SBOM (CycloneDX / SPDX) &#43; Grype scan 漏洞、Unix philosophy、cosign attestation 整合">CycloneDX / SPDX</a>）。對應 <a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/xz-backdoor-2024-open-source-supply-chain/" data-link-title="7.R7.2.4 XZ Backdoor 2024：開源供應鏈長期滲透" data-link-desc="開源維護鏈遭滲透後，為何會直接影響廣泛 Linux 發行流程">XZ Backdoor 2024</a> 的 lesson：maintainer takeover 也能簽 image、要靠 build provenance attestation 看出 build process 跟過去不一致。</p>
<p><strong>Mutate policy 跟 GitOps 的張力</strong>：Mutate 自動補欄位、ArgoCD / Flux 會永遠看到 live state 跟 Git state diff。處理方式有三 — <em>ignoreDifferences</em> on specific fields（ArgoCD <code>spec.ignoreDifferences</code>、Flux <code>spec.patches</code>）、<em>把 mutate 改成 validate + 在 PR template 補預設</em>（成本高但 GitOps diff 乾淨）、<em>Mutate at create only</em>（用 <code>mutate.mutateExistingOnPolicyUpdate: false</code>、只在 admission 動、不重複 mutate existing resource）。</p>
<p><strong>Generate policy 跟 multi-tenant security default</strong>：新 namespace 一建立、Generate rule 自動建 default-deny NetworkPolicy + ResourceQuota + LimitRange + 必要 RoleBinding。意義是 <em>security default 從 README 落到 runtime</em>、app team 開新 namespace 不會忘記設安全邊界。陷阱是 generated resource 的 ownership — 預設 Kyverno owns、app team 修改會被 reconcile 回去；要讓 app team 改、用 <code>synchronize: false</code>。</p>
<p><strong>Nirmata Enterprise</strong>：商業版補三件事 — <em>Policy Library</em>（CIS / NIST / PCI / SOC 2 預製 policy pack）、<em>Multi-cluster Management</em>（中央 console 推 policy 到多 cluster + audit dashboard + drift detection）、<em>Policy Reporter Plus</em>（trend + compliance mapping + JIRA / Slack integration）。對大企業多 cluster + 合規驅動的場景值得評估、中小 deployment OSS Kyverno + 社群 Policy Reporter 夠用。</p>
<p><strong>PolicyException 治理</strong>：Kyverno 1.9+ 引入 PolicyException CRD、讓特定 resource 明確繞過特定 policy、避免「app team 為了 deploy 直接把 policy 改寬」。Exception 走 PR + 到期日 + owner、跟 <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">Detection Coverage and Signal Governance</a> 的 exception lifecycle 對齊 — 例外不是黑箱、是 <em>暫時性、有 owner、有 review 日期</em>。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Policy 改了沒生效</strong>：admission webhook 沒 ready、或 policy 寫在錯的 namespace（Policy CRD 是 namespace-scoped、放錯 namespace 不會作用）— <code>kubectl get clusterpolicies</code> 看 ready 狀態、<code>kubectl describe</code> 看 events</li>
<li><strong>Admission 卡住 / Pod 起不來</strong>：Kyverno webhook 掛掉、failurePolicy 設 <code>Fail</code> 結果整個 cluster 不能 deploy — production 對 critical workload 設 <code>failurePolicy: Ignore</code> + 監控 Kyverno controller availability、不要讓 policy engine 變成 cluster-wide SPOF</li>
<li><strong>Mutate 後 ArgoCD 永遠 OutOfSync</strong>：mutate 改的欄位沒在 ArgoCD <code>ignoreDifferences</code> 排除 — 對應加 <code>spec.ignoreDifferences[*].jsonPointers</code> 或 <code>.jqPathExpressions</code>、不然每次 sync 都跳 diff</li>
<li><strong>Verify Images 全部失敗</strong>：cluster 沒對外網路、Fulcio / Rekor 拉不到、或 image 真的沒簽 — 先 audit mode 跑 + 看 PolicyReport 統計 unsigned image 比例、確認預期路徑（內部 image 簽 / 外部 image allowlist）後才 enforce</li>
<li><strong>Background scan 跑爆 controller</strong>：cluster 太大、scan interval 太短 — 調整 <code>backgroundScan: false</code> for 高頻變動 policy、或拉長 scan interval、或 Nirmata 用分散式 scan</li>
<li><strong>PolicyException 變成漏洞</strong>：例外沒到期日、owner 離職、規則永久繞過 — Exception CRD 補 metadata（owner / expiry / ticket）+ 定期 audit 過期 Exception</li>
<li><strong>VAP migration 不一致</strong>：Kyverno 生成的 VAP 跟原 ClusterPolicy 行為有差（CEL 不支援部分 Kyverno feature）— 對 critical rule 保留 Kyverno 不 migrate、只把簡單 Validate 卸載</li>
</ul>
<h2 id="何時改走其他服務">何時改走其他服務</h2>
<table>
  <thead>
      <tr>
          <th>需求形狀</th>
          <th>改走</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>跨 K8s + API gateway + Terraform 統一 policy</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA standalone</a></td>
      </tr>
      <tr>
          <td>K8s only 但團隊已投資 Rego</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/gatekeeper/" data-link-title="OPA Gatekeeper" data-link-desc="OPA 官方 K8s admission controller、ConstraintTemplate &#43; Constraint 兩層、Rego policy &#43; Audit &#43; Mutation">Gatekeeper</a></td>
      </tr>
      <tr>
          <td>CI-time pre-merge 檢查 Terraform / Dockerfile</td>
          <td>Conftest（OPA 系列、CLI-based）</td>
      </tr>
      <tr>
          <td>Image 漏洞 / misconfig scan（scan, not gate）</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/trivy/" data-link-title="Trivy" data-link-desc="Aqua Security 開源 all-in-one scanner：Container / Filesystem / K8s / IaC &#43; Secret &#43; License &#43; SBOM、Apache 2.0、CI 友善">Trivy</a> / <a href="/blog/backend/07-security-data-protection/vendors/snyk/" data-link-title="Snyk" data-link-desc="跨 SCM 多模組 application security platform：Open Source (SCA) &#43; Code (SAST) &#43; Container &#43; IaC &#43; Cloud (CSPM)、Reachability analysis">Snyk</a></td>
      </tr>
      <tr>
          <td>SBOM 生成 / 管理</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/syft-grype/" data-link-title="Syft &#43; Grype" data-link-desc="Anchore 開源姐妹工具：Syft 產 SBOM (CycloneDX / SPDX) &#43; Grype scan 漏洞、Unix philosophy、cosign attestation 整合">SBOM Tools</a></td>
      </tr>
      <tr>
          <td>Image signing pipeline</td>
          <td>Sigstore cosign（CI 簽、Kyverno 驗）</td>
      </tr>
      <tr>
          <td>K8s 1.30+ 簡單 Validate-only 場景</td>
          <td>K8s native ValidatingAdmissionPolicy（CEL、kube-apiserver 內建）</td>
      </tr>
  </tbody>
</table>
<h2 id="不在本頁內的主題">不在本頁內的主題</h2>
<ul>
<li>Kyverno policy 完整 YAML reference、JMESPath 進階用法</li>
<li>Sigstore cosign CLI 操作、Fulcio / Rekor 部署</li>
<li>Nirmata Enterprise 詳細功能跟 pricing</li>
<li>K8s ValidatingAdmissionPolicy CEL 語法 reference</li>
<li>跟 service mesh（Istio / Linkerd）整合的 sidecar injection 細節</li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>跟 Kyverno 的關係（對照啟示）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/solarwinds-2020-sunburst/" data-link-title="7.R7.2.1 SolarWinds 2020：更新鏈被濫用" data-link-desc="合法更新流程遭植入後，攻擊者如何長期潛伏與橫向擴散">SolarWinds 2020 Sunburst</a></td>
          <td>Kyverno Verify Images policy 強制 production cluster 只 deploy 已 cosign 簽章 + Rekor transparency log entry 的 image、未簽 / 來源異常 image 在 admission 階段擋掉</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/log4shell-cve-2021-44228-component-chain/" data-link-title="7.R7.2.7 Log4Shell 2021：共用元件風險與修補鏈" data-link-desc="共用元件漏洞如何同步影響多服務，並迫使團隊建立依賴治理 workflow">Log4Shell CVE-2021-44228</a></td>
          <td>Kyverno admission policy 配 <a href="/blog/backend/07-security-data-protection/vendors/trivy/" data-link-title="Trivy" data-link-desc="Aqua Security 開源 all-in-one scanner：Container / Filesystem / K8s / IaC &#43; Secret &#43; License &#43; SBOM、Apache 2.0、CI 友善">Trivy</a> scan 結果 — image 帶 vulnerability label 超過閾值就擋 deploy、補 CI scan 沒攔到的舊 image</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/xz-backdoor-2024-open-source-supply-chain/" data-link-title="7.R7.2.4 XZ Backdoor 2024：開源供應鏈長期滲透" data-link-desc="開源維護鏈遭滲透後，為何會直接影響廣泛 Linux 發行流程">XZ Backdoor 2024</a></td>
          <td>Kyverno Verify Images + SBOM attestation 補位 — maintainer takeover 也能簽 image、但缺乏 SLSA build provenance attestation 會被 Kyverno admission 擋住</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.12 供應鏈完整性 (section)</a></td>
          <td>Kyverno 是 <em>K8s admission gate</em> 的 K8s-specific 落實工具、跟 CI-time SBOM 生成 + cosign 簽章 + Rekor transparency log 組成 supply chain trust chain 的 runtime enforcement 段</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/blue-team/detection-engineering-lifecycle/" data-link-title="7.B5 Detection Engineering Lifecycle" data-link-desc="把偵測規則視為可維護資產，建立從來源、測試、調校到退場的完整生命週期">Detection Engineering Lifecycle (section)</a></td>
          <td>ClusterPolicy / Policy 走 propose → staging audit mode → tune → promote enforce mode 的工程 lifecycle、PolicyException 是 lifecycle 一部分、不是黑箱繞過</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.12 供應鏈完整性</a>、<a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">Detection Coverage and Signal Governance</a></li>
<li>平行：<a href="/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA</a>、<a href="/blog/backend/07-security-data-protection/vendors/gatekeeper/" data-link-title="OPA Gatekeeper" data-link-desc="OPA 官方 K8s admission controller、ConstraintTemplate &#43; Constraint 兩層、Rego policy &#43; Audit &#43; Mutation">Gatekeeper</a></li>
<li>下游：<a href="/blog/backend/07-security-data-protection/vendors/trivy/" data-link-title="Trivy" data-link-desc="Aqua Security 開源 all-in-one scanner：Container / Filesystem / K8s / IaC &#43; Secret &#43; License &#43; SBOM、Apache 2.0、CI 友善">Trivy</a>（scan + label）、<a href="/blog/backend/07-security-data-protection/vendors/snyk/" data-link-title="Snyk" data-link-desc="跨 SCM 多模組 application security platform：Open Source (SCA) &#43; Code (SAST) &#43; Container &#43; IaC &#43; Cloud (CSPM)、Reachability analysis">Snyk</a>（vuln 資訊源）、<a href="/blog/backend/07-security-data-protection/vendors/syft-grype/" data-link-title="Syft &#43; Grype" data-link-desc="Anchore 開源姐妹工具：Syft 產 SBOM (CycloneDX / SPDX) &#43; Grype scan 漏洞、Unix philosophy、cosign attestation 整合">SBOM Tools</a>（attestation 來源）</li>
<li>跨類：Sigstore cosign（CI 簽、Kyverno 驗）、ArgoCD / Flux（GitOps sync policy 本身）</li>
<li>跨模組：<a href="/blog/backend/08-incident-response/vendors/" data-link-title="事故處理 Vendor 清單" data-link-desc="規劃 on-call、incident response、status page 與 postmortem 工具的服務頁撰寫順序與判準">8 事故處理 vendor 清單</a>（policy violation → IR routing）</li>
<li>官方：<a href="https://kyverno.io/docs/">Kyverno Documentation</a>、<a href="https://docs.sigstore.dev/">Sigstore Documentation</a></li>
</ul>
]]></content:encoded></item><item><title>OPA Gatekeeper</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/gatekeeper/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/gatekeeper/</guid><description>&lt;p>OPA Gatekeeper 是 OPA 官方在 Kubernetes admission 層的落實、把 OPA 的 general-purpose policy engine 適配成 K8s-native admission controller。它跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &amp;#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/kyverno/" data-link-title="Kyverno" data-link-desc="K8s-native policy engine、YAML policy（非 Rego）、五類 rule（Validate / Mutate / Generate / Verify Images / Cleanup）、CNCF Incubating">Kyverno&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/conftest/" data-link-title="Conftest" data-link-desc="OPA CLI wrapper for static config policy check、Rego policy &amp;#43; 多 parser（Terraform / K8s / Dockerfile / JSON）、CI-time gate">Conftest&lt;/a> 的差異不在「policy 能不能寫」、而在 &lt;em>對接面 + 抽象層次 + 工具鏈定位&lt;/em> — Gatekeeper 是 OPA 在 K8s admission 的 first-class 落實、ConstraintTemplate + Constraint 兩層抽象把 Rego policy 變成 K8s CRD、Audit 補位 background scan、Mutation 2024 起進 stable。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>Gatekeeper 的核心定位是 &lt;em>Rego policy 在 K8s admission 層的 K8s-native 包裝&lt;/em>、不是另一個 policy engine。底層仍是 OPA、Rego 是同一套語言；上層加了兩個 K8s-specific 抽象 — &lt;em>ConstraintTemplate&lt;/em>（Rego policy + parameter schema 的 CRD 定義）跟 &lt;em>Constraint&lt;/em>（Template 的 instance、指定 match scope 與 parameter）。意義是同一份 Rego policy 寫一次、在不同 cluster / 不同 namespace 給不同 Constraint instance、不用改 Rego 本體。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &amp;#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA&lt;/a>（純 sidecar）比、Gatekeeper 走 &lt;em>K8s-native + 兩層抽象&lt;/em>、犧牲 OPA 純 sidecar 的跨平台彈性（OPA 可同時管 K8s admission + API gateway + Terraform plan）、換來 K8s 內部 CRD + RBAC + GitOps 的一致體驗。跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/kyverno/" data-link-title="Kyverno" data-link-desc="K8s-native policy engine、YAML policy（非 Rego）、五類 rule（Validate / Mutate / Generate / Verify Images / Cleanup）、CNCF Incubating">Kyverno&lt;/a> 比、Gatekeeper 走 &lt;em>Rego DSL&lt;/em>、Kyverno 走 &lt;em>YAML pattern matching&lt;/em> — team 已投資 OPA / Rego（API gateway / Terraform 已用 Rego）就走 Gatekeeper、純 K8s shop + 沒 Rego 包袱直接用 Kyverno 較省學習成本。跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/conftest/" data-link-title="Conftest" data-link-desc="OPA CLI wrapper for static config policy check、Rego policy &amp;#43; 多 parser（Terraform / K8s / Dockerfile / JSON）、CI-time gate">Conftest&lt;/a> 比、Conftest 是 &lt;em>CI-time static config check&lt;/em>、Gatekeeper 是 &lt;em>runtime admission + audit&lt;/em>、兩者互補不互斥（CI 用 Conftest 擋 PR、admission 用 Gatekeeper 擋 deploy）。&lt;/p></description><content:encoded><![CDATA[<p>OPA Gatekeeper 是 OPA 官方在 Kubernetes admission 層的落實、把 OPA 的 general-purpose policy engine 適配成 K8s-native admission controller。它跟 <a href="/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA</a> / <a href="/blog/backend/07-security-data-protection/vendors/kyverno/" data-link-title="Kyverno" data-link-desc="K8s-native policy engine、YAML policy（非 Rego）、五類 rule（Validate / Mutate / Generate / Verify Images / Cleanup）、CNCF Incubating">Kyverno</a> / <a href="/blog/backend/07-security-data-protection/vendors/conftest/" data-link-title="Conftest" data-link-desc="OPA CLI wrapper for static config policy check、Rego policy &#43; 多 parser（Terraform / K8s / Dockerfile / JSON）、CI-time gate">Conftest</a> 的差異不在「policy 能不能寫」、而在 <em>對接面 + 抽象層次 + 工具鏈定位</em> — Gatekeeper 是 OPA 在 K8s admission 的 first-class 落實、ConstraintTemplate + Constraint 兩層抽象把 Rego policy 變成 K8s CRD、Audit 補位 background scan、Mutation 2024 起進 stable。</p>
<h2 id="服務定位">服務定位</h2>
<p>Gatekeeper 的核心定位是 <em>Rego policy 在 K8s admission 層的 K8s-native 包裝</em>、不是另一個 policy engine。底層仍是 OPA、Rego 是同一套語言；上層加了兩個 K8s-specific 抽象 — <em>ConstraintTemplate</em>（Rego policy + parameter schema 的 CRD 定義）跟 <em>Constraint</em>（Template 的 instance、指定 match scope 與 parameter）。意義是同一份 Rego policy 寫一次、在不同 cluster / 不同 namespace 給不同 Constraint instance、不用改 Rego 本體。</p>
<p>跟 <a href="/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA</a>（純 sidecar）比、Gatekeeper 走 <em>K8s-native + 兩層抽象</em>、犧牲 OPA 純 sidecar 的跨平台彈性（OPA 可同時管 K8s admission + API gateway + Terraform plan）、換來 K8s 內部 CRD + RBAC + GitOps 的一致體驗。跟 <a href="/blog/backend/07-security-data-protection/vendors/kyverno/" data-link-title="Kyverno" data-link-desc="K8s-native policy engine、YAML policy（非 Rego）、五類 rule（Validate / Mutate / Generate / Verify Images / Cleanup）、CNCF Incubating">Kyverno</a> 比、Gatekeeper 走 <em>Rego DSL</em>、Kyverno 走 <em>YAML pattern matching</em> — team 已投資 OPA / Rego（API gateway / Terraform 已用 Rego）就走 Gatekeeper、純 K8s shop + 沒 Rego 包袱直接用 Kyverno 較省學習成本。跟 <a href="/blog/backend/07-security-data-protection/vendors/conftest/" data-link-title="Conftest" data-link-desc="OPA CLI wrapper for static config policy check、Rego policy &#43; 多 parser（Terraform / K8s / Dockerfile / JSON）、CI-time gate">Conftest</a> 比、Conftest 是 <em>CI-time static config check</em>、Gatekeeper 是 <em>runtime admission + audit</em>、兩者互補不互斥（CI 用 Conftest 擋 PR、admission 用 Gatekeeper 擋 deploy）。</p>
<p>關鍵張力：<em>Rego 學習曲線</em> ↔ <em>跨平台 policy 一致性</em> 是 Gatekeeper 跟 Kyverno 最大的選擇分水嶺。純 K8s 場景 Kyverno YAML 寫起來快、但同樣的 image signature 規則若要在 Terraform plan / CI / admission 三處 enforce、Rego 寫一次跨三處比 YAML / Cue / Sentinel 多種語言混用乾淨。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本頁、讀者能判斷：</p>
<ol>
<li>Gatekeeper 在 cluster policy stack 中承擔哪一段（admission validation / audit / mutation）、哪些要外接（<a href="/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA</a> 純 sidecar 管非 K8s 對象、<a href="/blog/backend/07-security-data-protection/vendors/conftest/" data-link-title="Conftest" data-link-desc="OPA CLI wrapper for static config policy check、Rego policy &#43; 多 parser（Terraform / K8s / Dockerfile / JSON）、CI-time gate">Conftest</a> 補 CI-time）</li>
<li>ConstraintTemplate 跟 Constraint 兩層怎麼切（Template 由 platform team 維護、Constraint 給 app team 在 namespace 內 instantiate）</li>
<li>Audit / Mutation / External Data Provider 何時開、開了之後 cost 與 failure mode</li>
<li>何時用 Gatekeeper、何時改 Kyverno 或退回純 OPA 的取捨</li>
</ol>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Gatekeeper deployment 是否健康、最少看四件事：</p>
<ul>
<li><strong>ConstraintTemplate 的 ownership</strong>：誰寫 Rego、誰 review、Template 是否走 Git（PR review + Gator CLI unit test）、是否有共用 library 避免每個 Template 重寫 K8s helper</li>
<li><strong>Audit coverage</strong>：除了 admission 攔截、Audit 是否定期 scan 已存在 resource（pre-Gatekeeper 部署的 legacy resource 違規）、<code>auditFromCache</code> 是否開、audit interval 是否合理（預設 60s、production 通常拉到 5-10min 避 API server 壓力）</li>
<li><strong>Failure mode 治理</strong>：Constraint <code>enforcementAction</code> 是 <code>deny</code> / <code>warn</code> / <code>dryrun</code>、Webhook failurePolicy 是 <code>Fail</code> / <code>Ignore</code>、<code>Fail</code> + Gatekeeper pod down 會擋全 cluster deploy</li>
<li><strong>跟 GitOps 的對接</strong>：ConstraintTemplate / Constraint 是否走 ArgoCD / Flux 部署、policy change 是否經 staging cluster 驗證、emergency exception 流程是否定義</li>
</ul>
<p>四件事任一缺失、就是 <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">Detection Coverage and Signal Governance</a> 在 admission 層的待補項目。</p>
<h2 id="日常操作與決策形狀">日常操作與決策形狀</h2>
<p><strong>ConstraintTemplate（CT）— Rego policy + CRD 定義</strong>：CT 是 Gatekeeper 的核心抽象、由 Rego policy + parameter schema（OpenAPI v3）兩段組成。Template 寫好 apply 到 cluster 後、Gatekeeper 會生成同名 CRD（例 <code>K8sRequiredLabels</code>）、app team 就能用該 CRD 寫 Constraint。Template 由 platform team 維護、不該每個 app team 自己寫 Rego — 集中維護才能保證 helper / convention / unit test 一致。</p>
<p><strong>Constraint — Template 的 instance + match scope</strong>：Constraint 指定三件事 — <em>該套用哪個 Template</em>（kind）、<em>套用範圍</em>（match：kinds / namespaces / labelSelector / excludedNamespaces）、<em>parameter 值</em>（spec.parameters、對應 Template 的 schema）。同一個 Template 可以有多個 Constraint instance（production / staging 不同 threshold、不同 namespace 不同 required label set）。這層抽象的意義是 <em>policy logic 跟 environment-specific configuration 分開</em>。</p>
<p><strong>Audit — background scan 已存在 resource</strong>：除了 admission webhook 在 create / update 時攔、Audit controller 定期（預設 60s）掃整個 cluster 找違規 resource、結果寫到 Constraint status 的 <code>violations</code> 欄位。意義是 <em>legacy resource 在你 install Gatekeeper 之前就在那、admission 不會觸發、Audit 才會抓到</em>。<code>auditFromCache: true</code> 用 Gatekeeper 自己的 informer cache 不打 API server、適合大 cluster。</p>
<p><strong>Mutation — 2024+ stable</strong>：早期 Gatekeeper 只有 Validation、Mutation 在 v3.10+ 進 beta、2024 隨 v3.14+ 進 stable。Mutation 走獨立 CRD（<code>Assign</code> / <code>AssignMetadata</code> / <code>ModifySet</code>）、不走 ConstraintTemplate。常見用法：注入 <code>securityContext.runAsNonRoot: true</code>、補 default resource limit、加 organization label。Mutation 跟 Validation 都開的話、Mutation 先跑、Validation 看 mutated 後的結果。</p>
<p><strong>Sync Resources — cross-resource lookup</strong>：Rego policy 若要查 <em>別的 resource</em>（例：擋 Service 用了不存在的 Namespace）、要先 declare <code>Config</code> CRD 把該 resource type 加進 Gatekeeper 的 sync list、Gatekeeper 才會在 cache 裡有那個 resource 供 Rego 查。沒 sync 的 resource 不能跨 reference、是常見踩雷點。</p>
<p><strong>External Data Provider — query 外部 API 做 decision</strong>：Gatekeeper v3.10+ 引入 External Data Provider、Rego 可以 call 外部 HTTPS endpoint 取 runtime data 做 policy decision。典型用法：query image scan service（例 <a href="/blog/backend/07-security-data-protection/vendors/trivy/" data-link-title="Trivy" data-link-desc="Aqua Security 開源 all-in-one scanner：Container / Filesystem / K8s / IaC &#43; Secret &#43; License &#43; SBOM、Apache 2.0、CI 友善">Trivy</a> server）確認 image 沒 CVE、query SBOM attestation service 確認 supply chain 完整、query custom IAM 確認 namespace owner 有權建立該 resource。要設 timeout + cache、外部 service down 不能擋全 cluster admission。</p>
<p><strong>Gator CLI — policy unit test</strong>：Gator 是 Gatekeeper 官方 CLI、本機跑 Template + Constraint 對 mock K8s manifest、不需 cluster。CI pipeline 跑 <code>gator test</code> 對每個 Template 跑 fixture、policy change 出 PR 時自動驗證 — 避免 production deploy 才發現 Template Rego bug 擋全 cluster。</p>
<p><strong>跟 GitOps 整合</strong>：ConstraintTemplate / Constraint / Mutation / Config CRD 都是純 YAML、走 ArgoCD / Flux 部署是標準作法。實務 layout：<code>gatekeeper-system</code> namespace 裝 Gatekeeper、<code>gatekeeper-policies</code> repo 放 Template 跟 baseline Constraint（platform team owned）、各 app namespace 的 Constraint instance 可以由 app team 在自己 repo 管理（透過 ArgoCD AppProject 限制 Constraint kind）。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>OPA Gatekeeper</th>
          <th>Kyverno</th>
          <th>OPA 純 sidecar</th>
          <th>Conftest</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>對接面</td>
          <td>K8s admission + Audit（K8s-only）</td>
          <td>K8s admission + Audit（K8s-only）</td>
          <td>任意 — API gateway / Terraform / K8s</td>
          <td>CI-time（static config check）</td>
      </tr>
      <tr>
          <td>Policy 語言</td>
          <td>Rego（OPA 同一套）</td>
          <td>YAML pattern matching（K8s-native）</td>
          <td>Rego</td>
          <td>Rego（OPA 同一套）</td>
      </tr>
      <tr>
          <td>抽象層次</td>
          <td>ConstraintTemplate + Constraint 兩層</td>
          <td>ClusterPolicy / Policy（單層）</td>
          <td>OPA policy bundle（無 K8s-specific 抽象）</td>
          <td>conftest test file（無 cluster 概念）</td>
      </tr>
      <tr>
          <td>Mutation</td>
          <td>支援（v3.14+ stable）</td>
          <td>支援（first-class、Kyverno 強項）</td>
          <td>不支援（需自寫 admission webhook）</td>
          <td>不適用</td>
      </tr>
      <tr>
          <td>Cross-resource</td>
          <td>Sync Resources（要 declare）</td>
          <td>Context API（內建）</td>
          <td>看自己 sidecar 怎麼寫</td>
          <td>看 CI 怎麼 load</td>
      </tr>
      <tr>
          <td>外部 data</td>
          <td>External Data Provider（v3.10+）</td>
          <td>Context API（image registry / ConfigMap）</td>
          <td>看自己 sidecar 怎麼寫</td>
          <td>不適用（純 static）</td>
      </tr>
      <tr>
          <td>學習曲線</td>
          <td>Rego 陡 + 兩層抽象多概念</td>
          <td>YAML 直觀、K8s-native idiom</td>
          <td>Rego 陡 + 自管 deployment</td>
          <td>Rego 陡 + CI integration</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>team 已投資 Rego / OPA、跨 K8s + 其他平台一致</td>
          <td>純 K8s shop、無 Rego 包袱、Mutation 是重點</td>
          <td>跨 K8s + API + Terraform 一致 policy 管理面</td>
          <td>PR 階段擋 manifest / IaC config</td>
      </tr>
      <tr>
          <td>退場成本</td>
          <td>高 — Template / Constraint / Rego 量多</td>
          <td>中 — YAML 較可移植</td>
          <td>中 — Rego 可搬到 Gatekeeper</td>
          <td>低</td>
      </tr>
  </tbody>
</table>
<p>選 Gatekeeper 的核心訴求：<em>team 已用 Rego（API gateway / Terraform plan / CI 已 OPA）+ 想把 same policy 延伸到 K8s admission + 看重 OPA ecosystem 一致性</em>。純 K8s shop 沒 Rego 包袱、又特別需要 Mutation 場景密集（PSP 廢除後重建、跨 namespace 統一 sidecar 注入）直接走 Kyverno 更省學習成本。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Rego idioms for K8s admission</strong>：K8s admission review 物件結構是 <code>input.review.object</code>、Template 的 <code>violation</code> rule 走 <code>violation[{&quot;msg&quot;: msg}] { ... }</code> 形式。常見 idiom：<code>match.kinds</code> 跟 <code>match.namespaceSelector</code> 在 Constraint 層處理 scope、Rego 內只寫 <em>policy logic</em>；K8s helper（label 取值、container loop、init container 排除）抽到 shared library Template；錯誤訊息要帶 <code>input.review.object.metadata.name</code> 幫 app team 定位是哪個 resource 被擋。</p>
<p><strong>External Data Provider 的 production 治理</strong>：Provider 是獨立 service、Gatekeeper webhook 透過 HTTPS call、cache 在 Gatekeeper 內。要設 timeout（預設 3s、過時 ConstraintTemplate <code>failurePolicy</code> 決定 fail-open / fail-closed）、cache TTL、Provider 自身的 readiness / liveness。Provider down 不該擋全 cluster — 用 <code>failurePolicy: Ignore</code> 對 External Data Provider 例外、但記錄 metric alert。對應 <a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/xz-backdoor-2024-open-source-supply-chain/" data-link-title="7.R7.2.4 XZ Backdoor 2024：開源供應鏈長期滲透" data-link-desc="開源維護鏈遭滲透後，為何會直接影響廣泛 Linux 發行流程">XZ Backdoor 2024</a> 的 SBOM attestation 查詢場景。</p>
<p><strong>Gator CLI 在 CI 的 pipeline 設計</strong>：<code>gator test</code> 對 fixture 跑、<code>gator verify</code> 跑 Template 自帶 test suite、<code>gator expand</code> 預覽 Mutation 結果。PR 流程：Template change → <code>gator verify</code> 跑 unit test → kind cluster 起 Gatekeeper apply Template + sample violation manifest → confirm 擋下來才 merge。</p>
<p><strong>跟 Styra DAS / Nirmata 整合</strong>：Gatekeeper OSS 本身沒 central management UI、多 cluster deployment 看 violation status 要自己拼。Styra DAS 是 OPA 商業 control plane、可以 push Template / Constraint 到多 cluster Gatekeeper、彙整 audit violation、做 policy impact analysis。Nirmata 走類似路線。OSS-only deployment 通常用 ArgoCD ApplicationSet + Prometheus exporter（gatekeeper-policy-manager / Open Policy Agent metrics）拼。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Gatekeeper webhook timeout / 擋全 cluster admission</strong>：Rego policy 寫了 expensive operation（大量 cross-resource lookup、External Data Provider call without cache）— webhook timeout 預設 3s、超過就走 failurePolicy；改寫 Rego 用 indexed lookup、External Data Provider 加 cache、<code>failurePolicy: Ignore</code> for non-critical Template</li>
<li><strong>新 Template apply 後 admission 整個壞</strong>：Rego syntax / logic bug、production 才發現 — PR 必跑 <code>gator verify</code> + staging cluster 24-48hr soak、Constraint 先用 <code>enforcementAction: dryrun</code> 觀察 violation count 才切 <code>deny</code></li>
<li><strong>Audit 跑很慢 / API server 壓力大</strong>：cluster resource 量大、Audit interval 預設 60s 太頻繁 — 拉長到 5-10min、<code>auditFromCache: true</code> 用 informer 不打 API server、大 cluster 開 <code>auditChunkSize</code> 分批處理</li>
<li><strong>legacy resource 不擋</strong>：admission webhook 只攔 create / update、<code>kubectl apply</code> 沒改動 spec 不觸發 — 用 Audit 抓 violation、配合手動 migration plan、不要期待 admission 自動修</li>
<li><strong>Mutation 跟 Validation 衝突</strong>：Mutation 加了 label、Validation 又擋說 label 不該存在 — Mutation 先跑、Validation 看 mutated 結果；設計 policy 時要對齊兩端、不能各自寫</li>
<li><strong>Sync 沒 declare、cross-resource policy 看不到對象</strong>：Rego <code>data.inventory.namespace[&quot;foo&quot;].v1.Pod</code> 回 undefined — <code>Config</code> CRD 加 sync targets、確認 Gatekeeper pod restart 後 cache 載入</li>
<li><strong>External Data Provider down 擋全 cluster</strong>：Provider service 自己掛、<code>failurePolicy: Fail</code> 整個 admission 壞 — Provider 走 <code>failurePolicy: Ignore</code> + metric alert、Provider 自身 HA 部署、cache TTL 拉長</li>
</ul>
<h2 id="何時改走其他服務">何時改走其他服務</h2>
<table>
  <thead>
      <tr>
          <th>需求形狀</th>
          <th>改走</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>純 K8s + 無 Rego 包袱 + Mutation 重點</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/kyverno/" data-link-title="Kyverno" data-link-desc="K8s-native policy engine、YAML policy（非 Rego）、五類 rule（Validate / Mutate / Generate / Verify Images / Cleanup）、CNCF Incubating">Kyverno</a></td>
      </tr>
      <tr>
          <td>跨 K8s + API gateway + Terraform</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA</a>（純 sidecar）</td>
      </tr>
      <tr>
          <td>CI-time / PR 階段擋 manifest</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/conftest/" data-link-title="Conftest" data-link-desc="OPA CLI wrapper for static config policy check、Rego policy &#43; 多 parser（Terraform / K8s / Dockerfile / JSON）、CI-time gate">Conftest</a></td>
      </tr>
      <tr>
          <td>Image scan 結果作為 policy 來源</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/trivy/" data-link-title="Trivy" data-link-desc="Aqua Security 開源 all-in-one scanner：Container / Filesystem / K8s / IaC &#43; Secret &#43; License &#43; SBOM、Apache 2.0、CI 友善">Trivy</a>（feed External Data Provider）</td>
      </tr>
      <tr>
          <td>Runtime threat detection（syscall）</td>
          <td>Falco / Cilium Tetragon（屬 runtime detection、不在 admission 層）</td>
      </tr>
      <tr>
          <td>Multi-cluster policy 集中管理</td>
          <td>Styra DAS / Nirmata（OPA / Gatekeeper 商業 control plane）</td>
      </tr>
      <tr>
          <td>偵測 / SIEM</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/splunk/" data-link-title="Splunk" data-link-desc="業界 SIEM 標準、forwarder &#43; indexer &#43; search head 架構、SPL 為核心查詢語言、ingestion-based 計費跟偵測覆蓋率的 trade-off">Splunk</a> 或同類 SIEM</td>
      </tr>
  </tbody>
</table>
<h2 id="不在本頁內的主題">不在本頁內的主題</h2>
<ul>
<li>Rego 完整語法 reference（unification、comprehension、partial evaluation）</li>
<li>Gatekeeper helm chart / installation 細節（看官方 docs）</li>
<li>Open Policy Agent 在 service mesh / API gateway 的 sidecar 部署模式（看 <a href="/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA</a> 頁）</li>
<li>Pod Security Admission（K8s 內建、跟 Gatekeeper 互補但不是 Gatekeeper 一部分）</li>
<li>Multi-cluster policy bundle 的 OCI registry 分發（屬 <a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.12 供應鏈完整性</a> 邊界）</li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<p>Gatekeeper 在 07 案例庫沒有直接 vendor-level 事件、但 supply chain 跟 admission policy 相關 case 都是 Gatekeeper 落實位置的對照：</p>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>跟 Gatekeeper 的關係（對照啟示）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/solarwinds-2020-sunburst/" data-link-title="7.R7.2.1 SolarWinds 2020：更新鏈被濫用" data-link-desc="合法更新流程遭植入後，攻擊者如何長期潛伏與橫向擴散">SolarWinds 2020 Sunburst</a></td>
          <td>ConstraintTemplate 配 cosign image signature verify、擋未簽 / 簽章不符 image 進 cluster；Audit 補位掃既有 deployment 找未簽 image</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/log4shell-cve-2021-44228-component-chain/" data-link-title="7.R7.2.7 Log4Shell 2021：共用元件風險與修補鏈" data-link-desc="共用元件漏洞如何同步影響多服務，並迫使團隊建立依賴治理 workflow">Log4Shell CVE-2021-44228</a></td>
          <td>Gatekeeper External Data Provider 接 <a href="/blog/backend/07-security-data-protection/vendors/trivy/" data-link-title="Trivy" data-link-desc="Aqua Security 開源 all-in-one scanner：Container / Filesystem / K8s / IaC &#43; Secret &#43; License &#43; SBOM、Apache 2.0、CI 友善">Trivy</a> server、admission 階段查 image 是否有 critical CVE 直接擋</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/xz-backdoor-2024-open-source-supply-chain/" data-link-title="7.R7.2.4 XZ Backdoor 2024：開源供應鏈長期滲透" data-link-desc="開源維護鏈遭滲透後，為何會直接影響廣泛 Linux 發行流程">XZ Backdoor 2024</a></td>
          <td>External Data Provider 可 query SBOM attestation 服務做 policy decision、不只看 image hash 而看 component provenance 鏈</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.12 供應鏈完整性 (section)</a></td>
          <td>Gatekeeper 是 OPA ecosystem 在 K8s admission 的官方落實、artifact trust gate 從 CI（Conftest）延伸到 runtime（Gatekeeper）的閉環</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.12 供應鏈完整性與 Artifact Trust</a>、<a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.13 偵測覆蓋率與訊號治理</a></li>
<li>平行：<a href="/blog/backend/07-security-data-protection/vendors/opa/" data-link-title="Open Policy Agent (OPA)" data-link-desc="CNCF general-purpose policy engine、Rego Datalog-like 語言、decoupled decision &#43; enforcement、跨 K8s / API / Terraform / SQL 統一 policy">OPA</a>、<a href="/blog/backend/07-security-data-protection/vendors/kyverno/" data-link-title="Kyverno" data-link-desc="K8s-native policy engine、YAML policy（非 Rego）、五類 rule（Validate / Mutate / Generate / Verify Images / Cleanup）、CNCF Incubating">Kyverno</a>、<a href="/blog/backend/07-security-data-protection/vendors/conftest/" data-link-title="Conftest" data-link-desc="OPA CLI wrapper for static config policy check、Rego policy &#43; 多 parser（Terraform / K8s / Dockerfile / JSON）、CI-time gate">Conftest</a></li>
<li>下游：<a href="/blog/backend/07-security-data-protection/vendors/trivy/" data-link-title="Trivy" data-link-desc="Aqua Security 開源 all-in-one scanner：Container / Filesystem / K8s / IaC &#43; Secret &#43; License &#43; SBOM、Apache 2.0、CI 友善">Trivy</a>（image scan 結果 feed External Data Provider）、<a href="/blog/backend/07-security-data-protection/vendors/spire/" data-link-title="SPIRE" data-link-desc="SPIFFE Runtime Environment、attested workload identity、short-lived SVID &#43; Trust Bundle、跨組織 federation">SPIRE</a>（workload identity 跟 admission policy 互補）</li>
<li>跨類：<a href="/blog/backend/07-security-data-protection/vendors/splunk/" data-link-title="Splunk" data-link-desc="業界 SIEM 標準、forwarder &#43; indexer &#43; search head 架構、SPL 為核心查詢語言、ingestion-based 計費跟偵測覆蓋率的 trade-off">Splunk</a>（admission violation event 進 SIEM correlation）</li>
<li>跨模組：<a href="/blog/backend/08-incident-response/vendors/" data-link-title="事故處理 Vendor 清單" data-link-desc="規劃 on-call、incident response、status page 與 postmortem 工具的服務頁撰寫順序與判準">8 事故處理 vendor 清單</a>（policy violation → IR routing）</li>
<li>官方：<a href="https://open-policy-agent.github.io/gatekeeper/">OPA Gatekeeper Documentation</a></li>
</ul>
]]></content:encoded></item><item><title>Cilium Tetragon</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/cilium-tetragon/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/cilium-tetragon/</guid><description>&lt;p>Tetragon 是 Cilium 旗下的 &lt;em>eBPF-based runtime security + enforcement&lt;/em> 元件、Isovalent 主導、2024 年起在 CNCF 屬 Incubating 階段。跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/falco/" data-link-title="Falco" data-link-desc="CNCF Graduated runtime cloud-native threat detection、eBPF / kmod driver、Rule YAML &amp;#43; Falcosidekick &amp;#43; Talon、K8s container runtime 偵測為主">Falco&lt;/a> 的核心差異在於 &lt;em>偵測 vs 偵測 + 可 enforce&lt;/em> — Falco 預設 alert-only、Tetragon 設計支援 &lt;em>kernel-level inline enforcement&lt;/em>（直接 kill process、override syscall return value）；對 K8s heavy + 已用 Cilium CNI 的環境、Tetragon 把 &lt;em>network policy + process policy&lt;/em> 收進同一個 eBPF 生態。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>Tetragon 的核心定位是 &lt;em>eBPF 為基底的 runtime observability + enforcement&lt;/em>、TracingPolicy CRD 是 first-class concept — 一份 YAML 同時描述 &lt;em>要觀察什麼 syscall / kprobe / tracepoint&lt;/em> 跟 &lt;em>觀察到後要不要 enforce&lt;/em>。底層 hook 點包括 syscall entry/exit、kprobe（任意 kernel function）、tracepoint（穩定 kernel event）、uprobe（user-space function），enforcement action 包括 &lt;code>Sigkill&lt;/code>（kill process）、&lt;code>Override&lt;/code>（override syscall return value）、&lt;code>NotifyEnforcer&lt;/code>、&lt;code>Post&lt;/code>（送 event 出 plane）。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/falco/" data-link-title="Falco" data-link-desc="CNCF Graduated runtime cloud-native threat detection、eBPF / kmod driver、Rule YAML &amp;#43; Falcosidekick &amp;#43; Talon、K8s container runtime 偵測為主">Falco&lt;/a> 比、Falco rule 用 Sysdig filter syntax、Tetragon 用 K8s CRD + JSON schema、對 K8s native 模型更貼近；Falco 主走 &lt;em>alert&lt;/em>、Tetragon 主走 &lt;em>alert + enforce&lt;/em>；Falco 對非 K8s VM-heavy 場景更 mature。跟 &lt;em>Datadog Cloud Workload Security&lt;/em> 比、Datadog 是 SaaS-only + per-host 計費、Tetragon 是 OSS Apache 2.0 + 自管 + Isovalent Enterprise 付費版可選。跟 &lt;em>Prisma Cloud Defender&lt;/em> 比、Prisma 是 CSPM/CWPP 一體化平台、Tetragon 專注 runtime + 跟 Cilium L3-L7 network policy 同 plane。&lt;/p>
&lt;p>關鍵張力：&lt;em>eBPF inline enforcement 的爆炸半徑&lt;/em> ↔ &lt;em>偵測即時性&lt;/em>。在 kernel-level 直接 kill process 比 userspace agent 更難 bypass、但 TracingPolicy 寫錯（match 太寬）可能誤殺合法 workload、且回退路徑只能改 CRD 再 reload。要看清楚自己 &lt;em>能不能承擔 enforcement 規則錯誤的 blast radius&lt;/em>、再決定哪些 policy 進 enforce、哪些只 observe。&lt;/p></description><content:encoded><![CDATA[<p>Tetragon 是 Cilium 旗下的 <em>eBPF-based runtime security + enforcement</em> 元件、Isovalent 主導、2024 年起在 CNCF 屬 Incubating 階段。跟 <a href="/blog/backend/07-security-data-protection/vendors/falco/" data-link-title="Falco" data-link-desc="CNCF Graduated runtime cloud-native threat detection、eBPF / kmod driver、Rule YAML &#43; Falcosidekick &#43; Talon、K8s container runtime 偵測為主">Falco</a> 的核心差異在於 <em>偵測 vs 偵測 + 可 enforce</em> — Falco 預設 alert-only、Tetragon 設計支援 <em>kernel-level inline enforcement</em>（直接 kill process、override syscall return value）；對 K8s heavy + 已用 Cilium CNI 的環境、Tetragon 把 <em>network policy + process policy</em> 收進同一個 eBPF 生態。</p>
<h2 id="服務定位">服務定位</h2>
<p>Tetragon 的核心定位是 <em>eBPF 為基底的 runtime observability + enforcement</em>、TracingPolicy CRD 是 first-class concept — 一份 YAML 同時描述 <em>要觀察什麼 syscall / kprobe / tracepoint</em> 跟 <em>觀察到後要不要 enforce</em>。底層 hook 點包括 syscall entry/exit、kprobe（任意 kernel function）、tracepoint（穩定 kernel event）、uprobe（user-space function），enforcement action 包括 <code>Sigkill</code>（kill process）、<code>Override</code>（override syscall return value）、<code>NotifyEnforcer</code>、<code>Post</code>（送 event 出 plane）。</p>
<p>跟 <a href="/blog/backend/07-security-data-protection/vendors/falco/" data-link-title="Falco" data-link-desc="CNCF Graduated runtime cloud-native threat detection、eBPF / kmod driver、Rule YAML &#43; Falcosidekick &#43; Talon、K8s container runtime 偵測為主">Falco</a> 比、Falco rule 用 Sysdig filter syntax、Tetragon 用 K8s CRD + JSON schema、對 K8s native 模型更貼近；Falco 主走 <em>alert</em>、Tetragon 主走 <em>alert + enforce</em>；Falco 對非 K8s VM-heavy 場景更 mature。跟 <em>Datadog Cloud Workload Security</em> 比、Datadog 是 SaaS-only + per-host 計費、Tetragon 是 OSS Apache 2.0 + 自管 + Isovalent Enterprise 付費版可選。跟 <em>Prisma Cloud Defender</em> 比、Prisma 是 CSPM/CWPP 一體化平台、Tetragon 專注 runtime + 跟 Cilium L3-L7 network policy 同 plane。</p>
<p>關鍵張力：<em>eBPF inline enforcement 的爆炸半徑</em> ↔ <em>偵測即時性</em>。在 kernel-level 直接 kill process 比 userspace agent 更難 bypass、但 TracingPolicy 寫錯（match 太寬）可能誤殺合法 workload、且回退路徑只能改 CRD 再 reload。要看清楚自己 <em>能不能承擔 enforcement 規則錯誤的 blast radius</em>、再決定哪些 policy 進 enforce、哪些只 observe。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本頁、讀者能判斷：</p>
<ol>
<li>Tetragon 在 K8s runtime stack 中承擔哪一段（process visibility / file access / network syscall / enforcement）、哪些要外接（<a href="/blog/backend/07-security-data-protection/vendors/falco/" data-link-title="Falco" data-link-desc="CNCF Graduated runtime cloud-native threat detection、eBPF / kmod driver、Rule YAML &#43; Falcosidekick &#43; Talon、K8s container runtime 偵測為主">Falco</a> for VM-heavy、SIEM for log aggregation）</li>
<li>TracingPolicy 的 ownership 設計（誰寫 CRD、enforcement action 誰簽核、staging vs production rollout）</li>
<li><em>Observe</em> vs <em>Enforce</em> 的階段化決策、什麼樣的 policy 適合 inline kill、什麼樣的應該停在 alert</li>
<li>何時用 Tetragon、何時走 Falco / Datadog CWS / Prisma Defender 的取捨</li>
</ol>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Tetragon deployment 是否健康、最少看四件事：</p>
<ul>
<li><strong>TracingPolicy 治理</strong>：CRD 是否走 Git + PR review、enforcement action（Sigkill / Override）是否需額外簽核、staging cluster 是否先跑 24-48hr 觀察 false positive 才 promote production</li>
<li><strong>跟 Cilium 整合深度</strong>：Hubble flow + Tetragon process event 是否同 plane export、Pod identity 是否在 process event 自動 enrich、跟 Cilium NetworkPolicy 是否雙層 enforcement 設計</li>
<li><strong>Enforcement coverage 分層</strong>：哪些 policy 處於 observe-only（log JNDI lookup / setuid abuse / unexpected outbound）、哪些升到 enforce（kill known exploit pattern）、升級條件是什麼</li>
<li><strong>Event export pipeline</strong>：Tetragon event 是否進 SIEM（OpenTelemetry / JSON log → Splunk / Elastic）、是否跟 <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">Detection Coverage and Signal Governance</a> 邊界一致</li>
</ul>
<p>四件事任一缺失、就是 runtime security 邊界的待補項目。</p>
<h2 id="日常操作與決策形狀">日常操作與決策形狀</h2>
<p><strong>TracingPolicy CRD</strong>：Tetragon 的 first-class concept、一份 YAML 描述 hook 點 + match selector + enforcement action。Hook 點包含 <em>syscall</em>（最穩定但 surface 廣）、<em>kprobe</em>（任意 kernel function、版本相依）、<em>tracepoint</em>（穩定 kernel event、首選）、<em>uprobe</em>（user-space function、低層用）。Match selector 支援 K8s namespace / pod label / container image、process credentials（UID / GID / capabilities）、parent process。Production rule 用 <em>pod label selector + 具體 syscall name + 額外 process credentials 條件</em>、避免 cluster-wide 寬鬆 match 誤殺。</p>
<p><strong>kprobe / tracepoint / syscall hook 的選擇</strong>：tracepoint 是 kernel 公開穩定介面、跨版本不變、首選；kprobe 可 hook 任意 kernel function 但跟 kernel build 緊綁、kernel upgrade 後可能要重寫；raw syscall 適合 audit 整類 syscall（如全部 <code>execve</code>）但量大、需要 in-kernel filter 控成本。</p>
<p><strong>Process credentials tracking</strong>：Tetragon 從 process exec 開始 track UID / GID / capabilities / namespace、偵測 <em>privilege escalation</em>（setuid abuse、capabilities drift、container escape）是 first-class use case。跟 audit log 比、credentials drift 是 <em>狀態變遷</em>、不是單一事件、更能 surface lateral movement 早期訊號（process 開始時 UID 1000、跑到一半變 0 是異常）。</p>
<p><strong>Pod identity correlation</strong>：Tetragon 在 K8s 環境會自動把 process event enrich K8s metadata（namespace / pod name / container image / service account）、不用後處理 join；event schema 跟 Hubble flow 同根、可在 Hubble UI 看 <em>某 Pod 的 network flow + process event</em> 同 timeline。</p>
<p><strong>跟 Cilium NetworkPolicy 雙層 enforcement</strong>：Cilium 控 <em>network ingress / egress / L7 HTTP</em>、Tetragon 控 <em>process / syscall / file access</em>。雙層設計的意義是 — network layer 擋不住的（如 process 內部 lateral movement、container escape syscall）由 process layer 補上；process layer 漏的（如合法 process 突然 outbound 異常 destination）由 network layer 補上。對 supply chain 攻擊特別有效、攻擊鏈通常跨 <em>malicious process spawn + outbound C2</em>。</p>
<p><strong>Event export 跟 SIEM 整合</strong>：Tetragon event 預設走 JSON log 到 stdout、可走 OpenTelemetry exporter 進 collector pipeline、再 fanout 到 <a href="/blog/backend/07-security-data-protection/vendors/splunk/" data-link-title="Splunk" data-link-desc="業界 SIEM 標準、forwarder &#43; indexer &#43; search head 架構、SPL 為核心查詢語言、ingestion-based 計費跟偵測覆蓋率的 trade-off">Splunk</a> / <a href="/blog/backend/07-security-data-protection/vendors/elastic-security/" data-link-title="Elastic Security" data-link-desc="Elastic Stack 上的 SIEM &#43; EDR &#43; Cloud Security 套件、OSS 起源、KQL/EQL/Lucene/ES|QL 多查詢語言、resource-based pricing">Elastic Security</a> / <a href="/blog/backend/07-security-data-protection/vendors/google-security-operations/" data-link-title="Google Security Operations" data-link-desc="Google 雲原生 SIEM &#43; SOAR &#43; Mandiant threat intel 三合一（前 Chronicle）、UDM &#43; YARA-L、fixed-price by data tier、PB-scale 友善">Google Security Operations</a>。在 SIEM 端做跨來源 correlation（process event + IdP audit + cloud control plane）是 production 標配、不可只看 Tetragon 自家視圖。</p>
<p><strong>Observe → Enforce 階段化</strong>：TracingPolicy 通常 <em>先進 observe-only</em>、跑 1-2 週收 baseline、確認 false positive 可控、再加 enforcement action 進 staging cluster、staging 觀察 24-48hr 才 promote production。對應 <a href="/blog/backend/07-security-data-protection/blue-team/detection-engineering-lifecycle/" data-link-title="7.B5 Detection Engineering Lifecycle" data-link-desc="把偵測規則視為可維護資產，建立從來源、測試、調校到退場的完整生命週期">Detection Engineering Lifecycle</a> 的章節原則 — runtime enforcement 不是 console 直改、是 detection content lifecycle。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>Cilium Tetragon</th>
          <th>Falco</th>
          <th>Datadog CWS</th>
          <th>Prisma Cloud Defender</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>偵測技術</td>
          <td>eBPF（kprobe / tracepoint / syscall / uprobe）</td>
          <td>eBPF + kernel module 兩種 driver</td>
          <td>eBPF agent</td>
          <td>eBPF + kernel module</td>
      </tr>
      <tr>
          <td>Enforcement</td>
          <td>內建（Sigkill / Override syscall return）</td>
          <td>預設 alert-only（plugin 可擴 response）</td>
          <td>自動 response（kill / isolate、SaaS 控）</td>
          <td>內建（block process / file / network）</td>
      </tr>
      <tr>
          <td>規則語言</td>
          <td>K8s CRD（TracingPolicy YAML）</td>
          <td>Sysdig filter syntax（YAML rule）</td>
          <td>Datadog Security Rules（JSON / UI）</td>
          <td>Prisma Runtime Rules（UI / JSON）</td>
      </tr>
      <tr>
          <td>計費 / 授權</td>
          <td>OSS Apache 2.0、Isovalent Enterprise 付費</td>
          <td>OSS Apache 2.0、Sysdig Secure 付費</td>
          <td>SaaS per-host</td>
          <td>商業 per-defender</td>
      </tr>
      <tr>
          <td>K8s native</td>
          <td>強 — Pod identity 自動 enrich、跟 Cilium 同源</td>
          <td>中 — K8s metadata 需 audit endpoint</td>
          <td>強 — Datadog Agent 已熟</td>
          <td>強 — Prisma 平台一體</td>
      </tr>
      <tr>
          <td>Network policy</td>
          <td>跟 Cilium L3-L7 雙層（同 plane）</td>
          <td>無 — 純 process / file</td>
          <td>無 — 跟 Datadog Network 分離</td>
          <td>內建 micro-segmentation</td>
      </tr>
      <tr>
          <td>VM / 非 K8s</td>
          <td>弱 — Linux only、K8s-first</td>
          <td>強 — VM / bare metal mature</td>
          <td>中 — 跨環境同 agent</td>
          <td>強 — VM / serverless / container 全覆蓋</td>
      </tr>
      <tr>
          <td>部署模型</td>
          <td>Self-hosted DaemonSet（K8s）</td>
          <td>Self-hosted DaemonSet / VM agent</td>
          <td>SaaS</td>
          <td>商業 self-hosted + SaaS console</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>K8s heavy + 已用 Cilium + 要 inline enforce</td>
          <td>VM-heavy / K8s 混合、需要 mature alert ecosystem</td>
          <td>Datadog 已用、要 unified observability</td>
          <td>多雲 CSPM/CWPP 一體化、合規驅動</td>
      </tr>
      <tr>
          <td>退場成本</td>
          <td>中 — TracingPolicy CRD 跨 cluster 可移植</td>
          <td>中 — Falco rule 跟 Sigma 可互轉</td>
          <td>高 — SaaS lock-in</td>
          <td>高 — 商業平台 lock-in</td>
      </tr>
  </tbody>
</table>
<p>選 Tetragon 的核心訴求：<em>K8s heavy + 已用 Cilium CNI + 想要 kernel-level inline enforcement + OSS 免授權成本</em>、且有 SRE / security team 能維護 TracingPolicy CRD lifecycle。VM-heavy 或 K8s 但用其他 CNI 走 Falco 更划算。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Inline enforcement 的 blast radius 設計</strong>：<code>Sigkill</code> 直接 kill 觸發 process、<code>Override</code> 改寫 syscall return value（讓 process 以為成功但實際沒做）— 兩者都在 kernel-level、攻擊者很難 bypass、但寫錯規則的 blast radius 是 <em>整個 cluster 內 match 到的 process 全死</em>。實務治理：enforcement action 規則進 GitOps、PR 需 security + SRE 雙簽、staging cluster 跑 namespace-scoped 規則先驗證、production rollout 走 canary namespace 再擴散。</p>
<p><strong>Process credentials drift detection</strong>：track UID / GID / capabilities 變遷、偵測 setuid abuse（process 從 uid 1000 變 0）、capabilities 突然新增（特別是 CAP_SYS_ADMIN / CAP_NET_ADMIN）。對 lateral movement 早期警報是 first-class signal — 攻擊者拿到初始 access 後通常要 escalate privilege、credentials drift 是必經訊號。配對 <a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/solarwinds-2020-sunburst/" data-link-title="7.R7.2.1 SolarWinds 2020：更新鏈被濫用" data-link-desc="合法更新流程遭植入後，攻擊者如何長期潛伏與橫向擴散">SolarWinds 2020 Sunburst</a> 的 lesson：簽章驗證通過但 runtime 行為異常需 <em>runtime credentials + process behavior</em> 雙重 baseline。</p>
<p><strong>跟 Cilium L3-L7 雙層 enforcement</strong>：典型 supply chain 攻擊鏈 — <em>malicious dependency loaded → process spawn → C2 outbound</em>、network layer 擋 outbound（Cilium NetworkPolicy 限制 egress destination）、process layer 擋 process（Tetragon KillerAction kill 異常 spawn）。雙層任一通則攻擊鏈中斷。對應 <a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/3cx-2023-desktopapp-supply-chain/" data-link-title="7.R7.2.8 3CX 2023：桌面軟體更新鏈攻擊" data-link-desc="合法更新流程被植入後，桌面端供應鏈事件如何傳到企業端點">3CX 2023 Desktop App Supply Chain</a> 的 case shape。</p>
<p><strong>跟 SBOM / image signing 整合 baseline</strong>：Tetragon 偵測 runtime 行為偏離 baseline、SBOM / image signing 控 build-time 信任、合在一起是 <em>trusted artifact + verified runtime behavior</em> 雙重保障。runtime 行為 baseline 通常從 SBOM 列出的合法 process / syscall set 出發、deviation 進 alert。</p>
<p><strong>Isovalent Enterprise</strong>：商業版加值在 multi-cluster management、policy 集中下發、support SLA、跟 Isovalent Hubble Enterprise / Cilium Service Mesh Enterprise 整合。OSS 版本核心功能完整、Enterprise 主要解 <em>多 cluster 大規模管理</em> 跟 <em>企業 support</em>、不是 feature gating。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>TracingPolicy 誤殺合法 workload</strong>：match selector 太寬、cluster-wide 沒加 namespace / pod label 條件 — 改 namespace-scoped + 加 process credentials 額外條件、staging 跑 48hr 再 promote</li>
<li><strong>kprobe rule kernel upgrade 後壞</strong>：hook 的 kernel function 改名或 signature 變 — 改用 tracepoint（穩定介面）、kprobe 進 staging 版本相依測試</li>
<li><strong>Event volume 爆炸 / SIEM ingestion cost 飆</strong>：raw syscall hook 沒做 in-kernel filter、所有 <code>execve</code> 都進 event — 加 in-kernel filter（按 pod label / process name），讓 filter 在 eBPF 端做、不要事後 drop</li>
<li><strong>Inline enforcement 規則錯誤 blast radius 太大</strong>：production 直接上 <code>Sigkill</code> 沒走 staging — enforcement action 規則一律先 observe-only 1 週、staging cluster 24-48hr、canary namespace、才 production</li>
<li><strong>跟 Cilium NetworkPolicy 重疊或衝突</strong>：同一個 attack pattern 被 network + process 同時阻擋、log 重複、誤判 — 設計時雙層各管 <em>互補面</em>（network 管 destination、process 管 process spawn）、不重複管同一面</li>
<li><strong>non-K8s workload 進不來</strong>：Tetragon DaemonSet 只在 K8s 跑、VM / bare metal 不支援 — VM-heavy 環境改走 <a href="/blog/backend/07-security-data-protection/vendors/falco/" data-link-title="Falco" data-link-desc="CNCF Graduated runtime cloud-native threat detection、eBPF / kmod driver、Rule YAML &#43; Falcosidekick &#43; Talon、K8s container runtime 偵測為主">Falco</a>、K8s + VM 混合走雙 stack</li>
<li><strong>Pod identity enrich 不全</strong>：某些 process event 缺 namespace / pod name — 通常是 process 在 pod sandbox 啟動前 spawn、或 short-lived process 太快結束、調 Tetragon 的 process cache lifetime + K8s API server 連線健康</li>
</ul>
<h2 id="何時改走其他服務">何時改走其他服務</h2>
<table>
  <thead>
      <tr>
          <th>需求形狀</th>
          <th>改走</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>VM-heavy / 非 K8s 為主</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/falco/" data-link-title="Falco" data-link-desc="CNCF Graduated runtime cloud-native threat detection、eBPF / kmod driver、Rule YAML &#43; Falcosidekick &#43; Talon、K8s container runtime 偵測為主">Falco</a></td>
      </tr>
      <tr>
          <td>Datadog observability 已用</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/datadog-security/" data-link-title="Datadog Security" data-link-desc="Datadog observability platform 上的 security suite：Cloud SIEM &#43; CSPM &#43; CWS &#43; AAP &#43; Sensitive Data Scanner、跟 observability 同 plane">Datadog Security</a>（Cloud Workload Security）</td>
      </tr>
      <tr>
          <td>多雲 CSPM/CWPP 一體化、合規驅動</td>
          <td>Prisma Cloud Defender（商業）</td>
      </tr>
      <tr>
          <td>SIEM 偵測為主、不需 inline kill</td>
          <td><a href="/blog/backend/07-security-data-protection/vendors/splunk/" data-link-title="Splunk" data-link-desc="業界 SIEM 標準、forwarder &#43; indexer &#43; search head 架構、SPL 為核心查詢語言、ingestion-based 計費跟偵測覆蓋率的 trade-off">Splunk</a> / <a href="/blog/backend/07-security-data-protection/vendors/elastic-security/" data-link-title="Elastic Security" data-link-desc="Elastic Stack 上的 SIEM &#43; EDR &#43; Cloud Security 套件、OSS 起源、KQL/EQL/Lucene/ES|QL 多查詢語言、resource-based pricing">Elastic Security</a></td>
      </tr>
      <tr>
          <td>Endpoint EDR（user laptop / VDI）</td>
          <td>CrowdStrike Falcon / Microsoft Defender for Endpoint</td>
      </tr>
      <tr>
          <td>偵測覆蓋率治理</td>
          <td><a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.13 偵測覆蓋率與訊號治理</a></td>
      </tr>
      <tr>
          <td>Incident routing</td>
          <td><a href="/blog/backend/08-incident-response/vendors/" data-link-title="事故處理 Vendor 清單" data-link-desc="規劃 on-call、incident response、status page 與 postmortem 工具的服務頁撰寫順序與判準">8 事故處理 vendor 清單</a></td>
      </tr>
  </tbody>
</table>
<h2 id="不在本頁內的主題">不在本頁內的主題</h2>
<ul>
<li>TracingPolicy CRD 完整欄位 reference 跟 kprobe / tracepoint 寫法 cookbook</li>
<li>Cilium NetworkPolicy 寫法（屬 network 治理、跨章節）</li>
<li>eBPF kernel programming 內部原理跟 verifier 限制</li>
<li>Isovalent Enterprise 跟 Cilium Service Mesh 商業整合細節</li>
<li>Hubble UI 操作（屬 observability 視角、跨章節）</li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<p>Tetragon 在 07 案例庫沒有直接 vendor-level 事件、但所有 runtime detection + supply chain case 都是 eBPF inline enforcement 的對照：</p>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>跟 Tetragon 的關係（對照啟示）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/log4shell-cve-2021-44228-component-chain/" data-link-title="7.R7.2.7 Log4Shell 2021：共用元件風險與修補鏈" data-link-desc="共用元件漏洞如何同步影響多服務，並迫使團隊建立依賴治理 workflow">Log4Shell CVE-2021-44228</a></td>
          <td>TracingPolicy 可 hook JNDI lookup 相關 syscall、配 <code>Sigkill</code> 直接 kill exploit process、比 userspace WAF 更難 bypass</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/solarwinds-2020-sunburst/" data-link-title="7.R7.2.1 SolarWinds 2020：更新鏈被濫用" data-link-desc="合法更新流程遭植入後，攻擊者如何長期潛伏與橫向擴散">SolarWinds 2020 Sunburst</a></td>
          <td>process credentials drift detection 對 lateral movement 早期警報、簽章驗證通過但 runtime 行為異常需 runtime baseline 補位</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/3cx-2023-desktopapp-supply-chain/" data-link-title="7.R7.2.8 3CX 2023：桌面軟體更新鏈攻擊" data-link-desc="合法更新流程被植入後，桌面端供應鏈事件如何傳到企業端點">3CX 2023 Desktop App Supply Chain</a></td>
          <td>偵測 desktop app 異常 outbound、Tetragon 抓 process + Cilium NetworkPolicy 同層擋 destination、雙層 enforcement 中斷攻擊鏈</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/blue-team/detection-engineering-lifecycle/" data-link-title="7.B5 Detection Engineering Lifecycle" data-link-desc="把偵測規則視為可維護資產，建立從來源、測試、調校到退場的完整生命週期">Detection Engineering Lifecycle (section)</a></td>
          <td>TracingPolicy CRD 走 GitOps + PR review + staging tune + canary rollout、inline enforcement 不可 console 直改</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/blue-team/alert-fatigue-and-signal-quality/" data-link-title="7.B10 Alert Fatigue and Signal Quality" data-link-desc="建立告警疲勞治理方法，讓訊號品質、分級一致性與處置效率同步提升">Alert Fatigue and Signal Quality (section)</a></td>
          <td>observe-only 階段先收 baseline、in-kernel filter 控 event volume、enforcement 只升給高 confidence pattern、避免 alert / log 雙重 fatigue</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.13 偵測覆蓋率與訊號治理</a>、<a href="/blog/backend/07-security-data-protection/blue-team/detection-engineering-lifecycle/" data-link-title="7.B5 Detection Engineering Lifecycle" data-link-desc="把偵測規則視為可維護資產，建立從來源、測試、調校到退場的完整生命週期">Detection Engineering Lifecycle</a></li>
<li>平行：<a href="/blog/backend/07-security-data-protection/vendors/falco/" data-link-title="Falco" data-link-desc="CNCF Graduated runtime cloud-native threat detection、eBPF / kmod driver、Rule YAML &#43; Falcosidekick &#43; Talon、K8s container runtime 偵測為主">Falco</a>、<a href="/blog/backend/07-security-data-protection/vendors/datadog-security/" data-link-title="Datadog Security" data-link-desc="Datadog observability platform 上的 security suite：Cloud SIEM &#43; CSPM &#43; CWS &#43; AAP &#43; Sensitive Data Scanner、跟 observability 同 plane">Datadog Security</a></li>
<li>下游：<a href="/blog/backend/07-security-data-protection/vendors/splunk/" data-link-title="Splunk" data-link-desc="業界 SIEM 標準、forwarder &#43; indexer &#43; search head 架構、SPL 為核心查詢語言、ingestion-based 計費跟偵測覆蓋率的 trade-off">Splunk</a> / <a href="/blog/backend/07-security-data-protection/vendors/elastic-security/" data-link-title="Elastic Security" data-link-desc="Elastic Stack 上的 SIEM &#43; EDR &#43; Cloud Security 套件、OSS 起源、KQL/EQL/Lucene/ES|QL 多查詢語言、resource-based pricing">Elastic Security</a>（Tetragon event 進 SIEM 做跨來源 correlation）</li>
<li>跨類：<a href="/blog/backend/07-security-data-protection/vendors/cloudflare-waf/" data-link-title="Cloudflare WAF" data-link-desc="Edge WAF &#43; DDoS &#43; Bot management 整合套件、global anycast 網路、控制面信任邊界跟客戶側補強的對照">Cloudflare WAF</a>（network edge 擋 + process 層補位）、<a href="/blog/backend/07-security-data-protection/vendors/hashicorp-vault/" data-link-title="HashiCorp Vault" data-link-desc="Self-hosted secret management 與 dynamic credential / encryption-as-a-service / PKI engine、跨雲跨環境的 secret 控制面">HashiCorp Vault</a>（credentials drift 配 secret rotation）</li>
<li>跨模組：<a href="/blog/backend/08-incident-response/vendors/" data-link-title="事故處理 Vendor 清單" data-link-desc="規劃 on-call、incident response、status page 與 postmortem 工具的服務頁撰寫順序與判準">8 事故處理 vendor 清單</a>（runtime alert → IR routing）、<a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">4 observability</a>（Hubble + Tetragon event pipeline 共用）</li>
<li>官方：<a href="https://tetragon.io/">Tetragon Documentation</a>、<a href="https://cilium.io/">Cilium Project</a></li>
</ul>
]]></content:encoded></item></channel></rss>