<?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>OpenTelemetry on Tarragon</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/opentelemetry/</link><description>Recent content in OpenTelemetry on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 01 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/backend/04-observability/vendors/opentelemetry/index.xml" rel="self" type="application/rss+xml"/><item><title>OTel Collector 部署模式：agent / gateway / sidecar 與 pipeline 設計</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/opentelemetry/collector-deployment-patterns/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/vendors/opentelemetry/collector-deployment-patterns/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/opentelemetry/" data-link-title="OpenTelemetry" data-link-desc="可觀測性開放標準、SDK 與 Collector">OpenTelemetry&lt;/a> 的 vendor deep article，深化 overview「Collector 部署模式」段。初次接觸 OpenTelemetry 的讀者建議先讀 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/opentelemetry/" data-link-title="OpenTelemetry" data-link-desc="可觀測性開放標準、SDK 與 Collector">OpenTelemetry 服務頁&lt;/a>，再回到本文。指令於 2026-06-16 用 &lt;code>otel/opentelemetry-collector-contrib:0.154.0&lt;/code> 在 docker 實機驗證。&lt;/p>&lt;/blockquote>
&lt;p>應用程式產生的 telemetry 跟最終存放的 backend 之間需要一個中介層 — OTel Collector 就是這個中介。應用只負責用 OTLP 把資料吐給 collector，collector 負責接收、處理、轉發，兩邊解耦。部署這個 collector 的第一個決策是它擺在哪裡（同 host、集中 gateway、還是 pod sidecar），而非配置細節。位置決定了 buffer 能力、enrichment 時機與失效影響面。&lt;/p>
&lt;h2 id="問題情境telemetry-直送-backend-的三個代價">問題情境：telemetry 直送 backend 的三個代價&lt;/h2>
&lt;p>應用程式直接用 vendor SDK 把 telemetry 送到後端，會在規模變大時撞到三個問題。第一是耦合：每個服務都寫死了某個 backend 的 endpoint 與認證，換 backend 要改所有服務重新部署。第二是缺乏 buffer：backend 短暫不可用時，telemetry 直接丟失，因為應用程式不會為了觀測資料保留重試佇列。第三是 enrichment 分散：每個服務各自加 resource attribute、各自做 sampling，標準難統一。&lt;/p>
&lt;p>Collector 把這三件事收斂到一個中介層。應用只認 collector 的 OTLP endpoint，換 backend 只改 collector 配置；collector 有 queue 與重試；enrichment 與 sampling 在 collector 統一做。但這個中介層擺在哪裡，決定了它各自解掉多少。&lt;/p>
&lt;p>服務數少、backend 單一且穩定時，應用直送 backend 是合理起點 — 上述三個代價在小規模下可控。Collector 是規模化後的升級：當 backend 要換、服務數成長到 enrichment 要統一、或 sampling 需求出現時，再引入 collector 補這一層。&lt;/p>
&lt;h2 id="核心概念三種部署位置的責任分工">核心概念：三種部署位置的責任分工&lt;/h2>
&lt;p>Collector 的部署位置分三種，差別在「離應用多近」與「聚合多少來源」。&lt;/p>
&lt;p>Agent 模式把 collector 跟應用程式放在同一個 host 或同一個 K8s node（DaemonSet）。它的責任是做 local buffer 與 host 層 enrichment：應用透過 localhost 把 telemetry 吐給同機的 collector，延遲極低、不跨網路；collector 補上 host name、container id 這類只有在本機才知道的 resource attribute。agent 的價值是「離應用最近」，應用送出 telemetry 後就不必管後續，buffer 與重試由同機 collector 承擔。&lt;/p>
&lt;p>Agent 解了「離應用近、不丟資料」的問題，但它只看得到本機 — 需要全域視野的處理放不進去。Gateway 模式補這一塊：把 collector 集中部署成一個獨立的服務叢集，跨多個 agent 或多個應用接收 telemetry，負責需要全域視野的處理：tail-based sampling（要看完整 trace 才決定採不採）、跨來源的 routing（不同 telemetry 送不同 backend）、集中的 rate limit 與成本控制。gateway 的價值是「集中決策」，把只有匯流後才做得到的處理放在這一層。&lt;/p>
&lt;p>Sidecar 模式在 K8s 把 collector 當成跟應用 pod 同生命週期的 sidecar container。它的責任跟 agent 相似（local buffer、pod 層 enrichment），差別在隔離粒度是 pod 而非 node：比 DaemonSet agent 更貼近單一 pod（共享 pod 網路、隨 pod 起停），適合需要 pod 級獨立配置或強隔離的場景，代價是每個 pod 都多一份 collector 的資源開銷。&lt;/p>
&lt;p>常見部署是兩層組合：agent（DaemonSet）做 local buffer + host enrichment，再把資料送到 gateway 叢集做 tail sampling 與 routing。agent 解掉「離應用近、不丟資料」，gateway 解掉「需要全域視野的處理」，兩層各司其職。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/04-observability/vendors/opentelemetry/" data-link-title="OpenTelemetry" data-link-desc="可觀測性開放標準、SDK 與 Collector">OpenTelemetry</a> 的 vendor deep article，深化 overview「Collector 部署模式」段。初次接觸 OpenTelemetry 的讀者建議先讀 <a href="/blog/backend/04-observability/vendors/opentelemetry/" data-link-title="OpenTelemetry" data-link-desc="可觀測性開放標準、SDK 與 Collector">OpenTelemetry 服務頁</a>，再回到本文。指令於 2026-06-16 用 <code>otel/opentelemetry-collector-contrib:0.154.0</code> 在 docker 實機驗證。</p></blockquote>
<p>應用程式產生的 telemetry 跟最終存放的 backend 之間需要一個中介層 — OTel Collector 就是這個中介。應用只負責用 OTLP 把資料吐給 collector，collector 負責接收、處理、轉發，兩邊解耦。部署這個 collector 的第一個決策是它擺在哪裡（同 host、集中 gateway、還是 pod sidecar），而非配置細節。位置決定了 buffer 能力、enrichment 時機與失效影響面。</p>
<h2 id="問題情境telemetry-直送-backend-的三個代價">問題情境：telemetry 直送 backend 的三個代價</h2>
<p>應用程式直接用 vendor SDK 把 telemetry 送到後端，會在規模變大時撞到三個問題。第一是耦合：每個服務都寫死了某個 backend 的 endpoint 與認證，換 backend 要改所有服務重新部署。第二是缺乏 buffer：backend 短暫不可用時，telemetry 直接丟失，因為應用程式不會為了觀測資料保留重試佇列。第三是 enrichment 分散：每個服務各自加 resource attribute、各自做 sampling，標準難統一。</p>
<p>Collector 把這三件事收斂到一個中介層。應用只認 collector 的 OTLP endpoint，換 backend 只改 collector 配置；collector 有 queue 與重試；enrichment 與 sampling 在 collector 統一做。但這個中介層擺在哪裡，決定了它各自解掉多少。</p>
<p>服務數少、backend 單一且穩定時，應用直送 backend 是合理起點 — 上述三個代價在小規模下可控。Collector 是規模化後的升級：當 backend 要換、服務數成長到 enrichment 要統一、或 sampling 需求出現時，再引入 collector 補這一層。</p>
<h2 id="核心概念三種部署位置的責任分工">核心概念：三種部署位置的責任分工</h2>
<p>Collector 的部署位置分三種，差別在「離應用多近」與「聚合多少來源」。</p>
<p>Agent 模式把 collector 跟應用程式放在同一個 host 或同一個 K8s node（DaemonSet）。它的責任是做 local buffer 與 host 層 enrichment：應用透過 localhost 把 telemetry 吐給同機的 collector，延遲極低、不跨網路；collector 補上 host name、container id 這類只有在本機才知道的 resource attribute。agent 的價值是「離應用最近」，應用送出 telemetry 後就不必管後續，buffer 與重試由同機 collector 承擔。</p>
<p>Agent 解了「離應用近、不丟資料」的問題，但它只看得到本機 — 需要全域視野的處理放不進去。Gateway 模式補這一塊：把 collector 集中部署成一個獨立的服務叢集，跨多個 agent 或多個應用接收 telemetry，負責需要全域視野的處理：tail-based sampling（要看完整 trace 才決定採不採）、跨來源的 routing（不同 telemetry 送不同 backend）、集中的 rate limit 與成本控制。gateway 的價值是「集中決策」，把只有匯流後才做得到的處理放在這一層。</p>
<p>Sidecar 模式在 K8s 把 collector 當成跟應用 pod 同生命週期的 sidecar container。它的責任跟 agent 相似（local buffer、pod 層 enrichment），差別在隔離粒度是 pod 而非 node：比 DaemonSet agent 更貼近單一 pod（共享 pod 網路、隨 pod 起停），適合需要 pod 級獨立配置或強隔離的場景，代價是每個 pod 都多一份 collector 的資源開銷。</p>
<p>常見部署是兩層組合：agent（DaemonSet）做 local buffer + host enrichment，再把資料送到 gateway 叢集做 tail sampling 與 routing。agent 解掉「離應用近、不丟資料」，gateway 解掉「需要全域視野的處理」，兩層各司其職。</p>
<h2 id="pipeline-模型receivers--processors--exporters">pipeline 模型：receivers / processors / exporters</h2>
<p>不論擺在哪個位置，collector 的內部都是同一個 pipeline 模型：telemetry 從 receivers 進來、經過 processors 加工、由 exporters 送出。三者用 <code>service.pipelines</code> 依訊號類型（traces / metrics / logs）串接。以下是最小可驗證配置，三個區塊（receivers / processors / exporters）對應 pipeline 的三個階段，各自職責在後面逐段說明。這份配置在 docker 驗證過可正常啟動並端到端流通（<code>validate --config</code> 回傳 0、送 5 條 trace 後 debug exporter 完整輸出 spans）：</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">receivers</span><span class="p">:</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">otlp</span><span class="p">:</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">protocols</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">grpc</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">endpoint</span><span class="p">:</span><span class="w"> </span><span class="m">0.0.0.0</span><span class="p">:</span><span class="m">4317</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">processors</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">memory_limiter</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">check_interval</span><span class="p">:</span><span class="w"> </span><span class="l">1s</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">limit_mib</span><span class="p">:</span><span class="w"> </span><span class="m">256</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">spike_limit_mib</span><span class="p">:</span><span class="w"> </span><span class="m">64</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">batch</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">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">5s</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">send_batch_size</span><span class="p">:</span><span class="w"> </span><span class="m">1024</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">exporters</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">debug</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">verbosity</span><span class="p">:</span><span class="w"> </span><span class="l">detailed</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">service</span><span class="p">:</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">pipelines</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">traces</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">receivers</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">otlp]</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">processors</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">memory_limiter, batch]</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">exporters</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">debug]</span></span></span></code></pre></div><p>receivers 定義「資料怎麼進來」，OTLP（gRPC 4317 / HTTP 4318）是標準入口。processors 定義「資料怎麼加工」，順序有意義：<code>memory_limiter</code> 放最前面，先擋住記憶體爆掉；<code>batch</code> 放後面，把零散 span 攢成批次再送，降低下游請求數。此處 256 / 64 MiB 是 demo 用量，production 應依 container memory limit 按比例設定（常見做法是 limit_mib 設為 container memory 的 80%、spike 設為 limit 的 20-25%）。exporters 定義「資料送到哪」，正式環境會是 OTLP 到 backend 或某 vendor exporter，這裡用 <code>debug</code> 驗證流通。service.pipelines 才是真正生效的接線：只有被掛進某個 pipeline 的元件才會運作，定義了卻沒掛進 pipeline 的元件不生效。</p>
<p>processor 順序是常見踩雷點。<code>memory_limiter</code> 要排在第一個，讓它在資料進入後續 processor 前就有機會審查與拒收；<code>batch</code> 排在它之後，因為如果 batch 先跑，telemetry 會先在 batch processor 累積成大批，等觸發記憶體限制時壓力已經更高、拒收效果下降。需要 sampling 時，head sampling 可以放 agent 層的 pipeline，tail sampling 必須放 gateway 層（它要匯流完整 trace），且同一 trace 的所有 span 要路由到同一個 gateway 實例（用 trace-id 維度的 load balancing exporter），否則各 gateway 節點各看片段、tail 決策仍不完整。</p>
<h2 id="production-故障演練">Production 故障演練</h2>
<p>Collector 失效的影響面取決於部署模式，這是選位置時要先想清楚的。agent 模式下，單一 node 的 collector 掛掉只影響該 node 的應用，且應用送往 localhost 失敗可以 fail-fast；gateway 模式下，gateway 叢集掛掉會影響所有上游 agent，因此 gateway 必須多副本 + 負載均衡，不能單點；sidecar 模式下，失效影響面比 agent 更窄（只影響同 pod 的應用），但每個 pod 各自是獨立失效點，pod 數多時同時出狀況的機率也高。演練時要分別注入「單 agent 掛」與「gateway 叢集不可用」，確認前者影響被局限、後者有 agent 層 buffer 兜著。</p>
<p>記憶體壓力是 collector 最常見的故障。telemetry 流入速度超過 exporter 送出速度時，資料在 collector 內累積、記憶體上升，沒有保護會 OOM 被 kill、整段 telemetry 全丟。<code>memory_limiter</code> processor 是這道防線，它定期（<code>check_interval</code>）檢查記憶體並用兩個閾值分級反應：記憶體超過軟上限（<code>limit_mib</code> 減去 <code>spike_limit_mib</code>）時強制觸發 GC 並開始拒收，給回收一個緩衝區間；超過硬上限（<code>limit_mib</code>）時全面拒收新資料。只設 <code>limit_mib</code>、不設 <code>spike_limit_mib</code> 是不完整的配置，等於沒有軟性緩衝、直接撞硬牆。演練時用高於 exporter 吞吐的速率灌資料，確認 memory_limiter 在軟上限就介入、collector 存活，而不是 OOM。</p>
<p>Backpressure 的傳遞要驗證到底。當 backend 變慢、exporter queue 滿，collector 的 OTLP receiver 會回壓給上游（gRPC 層用 resource-exhausted 拒收）。在 agent 模式這個回壓會傳到應用的 OTLP exporter，應用 SDK 的 queue 也會滿——此時 SDK 的反應取決於 exporter 配置，要確認 queue-full 策略設為 drop 而非 block，讓 telemetry 被丟棄而非阻塞業務執行緒（各語言 SDK 預設不同，不能假設一定是 drop）。演練要確認「backend 慢 → collector 回壓 → 應用丟 telemetry 但業務不受影響」這條鏈成立，避免觀測系統的壓力反噬主流程。</p>
<table>
  <thead>
      <tr>
          <th>觀察訊號</th>
          <th>判讀</th>
          <th>對應動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>collector 容器頻繁 OOM restart</td>
          <td>memory_limiter 閾值過高或未啟用</td>
          <td>調低 limit_mib、確認 spike_limit_mib 有設</td>
      </tr>
      <tr>
          <td>exporter queue depth 持續飽和</td>
          <td>下游 backend 回應慢或不可用</td>
          <td>查 backend 狀態、確認 exporter retry 與 timeout 設定</td>
      </tr>
      <tr>
          <td>receiver refused spans 計數上升</td>
          <td>memory_limiter 啟動拒收、collector 處於壓力狀態</td>
          <td>查上游流量是否異常、考慮擴容 gateway 或調降 sampling</td>
      </tr>
      <tr>
          <td>gateway 全部不可用、agent buffer 開始丟棄</td>
          <td>全域 telemetry 中斷</td>
          <td>確認 gateway 多副本與負載均衡、agent 的 queue 與 drop 策略</td>
      </tr>
      <tr>
          <td>telemetry 到 backend 有延遲但不丟失</td>
          <td>batch processor 正常攢批</td>
          <td>正常行為、確認 batch timeout 符合預期</td>
      </tr>
  </tbody>
</table>
<h2 id="capacity--cost-邊界">Capacity / cost 邊界</h2>
<p>agent 與 gateway 的成本曲線不同，選型要對著規模看。agent（DaemonSet）的成本是「每個 node 一份 collector」的固定開銷：node 多時總開銷隨 node 數線性成長，但每份 collector 只處理本機流量、單份負載可控。gateway 的成本是「集中叢集」：份數少但每份要扛匯流後的總流量，要按總 telemetry 吞吐量做容量規劃與水平擴展。</p>
<p>兩層架構的成本判讀是：agent 層用最小配置（夠做 buffer + enrichment 即可，<code>limit_mib</code> 設小），把重處理（tail sampling、大量 routing）集中到 gateway，讓 gateway 的擴展跟總流量綁定、agent 的開銷跟 node 數綁定。把 tail sampling 誤放在 agent 層是常見的成本錯誤——agent 看不到完整 trace、做不了正確的 tail sampling，還白白吃掉每個 node 的記憶體。</p>
<p>gateway 層的 processor 是攔截高 cardinality attribute 的有效位置：在 telemetry 流入 backend 前用 <code>attributes</code> / <code>transform</code> processor 把高 cardinality label（user id、request id 當 metric label）移除或降維，比讓它流到 backend 後才治理便宜。高 cardinality 的 attribute 會在下游 backend 炸開成本，是另一條要在 collector 攔截的成本線。這條跟 <a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 Cardinality 治理與成本邊界</a> 對齊。</p>
<h2 id="整合--下一步">整合 / 下一步</h2>
<p>Collector 部署模式是 OTel 落地的第一個決策，它的下游是 sampling 策略與 backend 選型。決定了 agent + gateway 兩層後，tail sampling 的設計接到 gateway 層的 pipeline；exporter 指向哪個 backend 則回到 <a href="/blog/backend/04-observability/vendors/opentelemetry/#%e4%bd%95%e6%99%82%e6%94%b9%e8%b5%b0%e5%85%b6%e4%bb%96%e6%9c%8d%e5%8b%99" data-link-title="OpenTelemetry" data-link-desc="可觀測性開放標準、SDK 與 Collector">何時改走其他服務</a> 的 vendor portability 判讀。</p>
<p>pipeline 的訊號治理與資料品質回到 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline 架構</a> 與 <a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality</a>；cardinality 攔截回到 <a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 Cardinality 治理與成本邊界</a>。</p>
<h2 id="相關連結">相關連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/vendors/opentelemetry/" data-link-title="OpenTelemetry" data-link-desc="可觀測性開放標準、SDK 與 Collector">OpenTelemetry 服務頁</a></li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline 架構</a></li>
<li><a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 Cardinality 治理與成本邊界</a></li>
<li><a href="/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.3 tracing 與 context link</a></li>
</ul>
]]></content:encoded></item></channel></rss>