# 配置与部署形态 · confmap 与 agent/gateway

> Collector 的配置能从文件、环境变量、HTTP、甚至内联 YAML 来——这一章读 confmap 的 provider 机制，以及那个真正影响架构的决策：Collector 该部署成 agent（每节点一个）还是 gateway（集中一层），还是两者都要。

- 作者：David（道雾轩）
- 专栏：指标采集与观测（https://daiw.org/manual/observability.md）
- 最后更新：2026-07-15
- 原文：https://daiw.org/manual/observability/collector-config
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 配置与部署形态 · confmap 与 agent/gateway

组件都认识了（[五类](https://daiw.org/manual/observability/collector-architecture)、[数据模型](https://daiw.org/manual/observability/pdata)、[怎么传](https://daiw.org/manual/observability/consumer-backpressure)）。这一章讲**怎么把它们配起来**，以及一个真正影响架构的决策：**Collector 该部署在哪**。

## confmap：配置从哪来

看 `confmap/provider/` 目录：

```
confmap/provider/
├── fileprovider/     file:/etc/otel/config.yaml
├── envprovider/      env:MY_CONFIG
├── httpprovider/     http://config-server/otel.yaml
├── httpsprovider/    https://...
└── yamlprovider/     yaml:{...}  (内联)
```

<Callout type="info">
  **配置源被抽象成 **provider**，用 URI scheme 区分。这带来两个实用能力：**

  - **可以从远程拉配置** —— `httpprovider` 让你把配置放在配置中心。**改配置不用重新打镜像。**
  - **可以**组合**多个源** —— 这是最有用的：
    ```bash
    otelcol --config=file:/etc/otel/base.yaml \
            --config=file:/etc/otel/override.yaml \
            --config="yaml:exporters::otlp::endpoint: ${BACKEND}"
    ```
    **多个配置会被**合并**（后面的覆盖前面的）。** 于是你可以做“基础配置 + 环境覆盖”的分层——**和 Kustomize / Helm values 一个思路**。

  **`${VAR}` 语法可以在配置里直接引用环境变量**，这是最常用的：把 endpoint、token 这类环境相关的东西外置。**注意 token 别写进配置文件**——用 `${ENV}` 或专门的 [extension](https://daiw.org/manual/observability/collector-architecture) 处理认证。
</Callout>

## 一份完整配置长什么样

```yaml
receivers:
  otlp:
    protocols:
      grpc: { endpoint: 0.0.0.0:4317 }
      http: { endpoint: 0.0.0.0:4318 }
  prometheus:                    # 它能像 Prometheus 一样去抓!
    config:
      scrape_configs:
        - job_name: 'app'
          static_configs: [{ targets: ['localhost:8080'] }]

processors:
  memory_limiter:                # ← 必须放最前面(上一章讲过)
    check_interval: 1s
    limit_mib: 512
  batch:                         # ← 削峰,几乎必配
    timeout: 10s
    send_batch_size: 8192
  resource:                      # 给所有数据加统一标签
    attributes:
      - key: env
        value: prod
        action: upsert

exporters:
  prometheusremotewrite:
    endpoint: "http://mimir:9009/api/v1/push"
  otlp/tempo:
    endpoint: "tempo:4317"

extensions:
  health_check: {}               # k8s 探针用
  pprof: {}

service:
  extensions: [health_check, pprof]
  pipelines:
    metrics:
      receivers: [otlp, prometheus]
      processors: [memory_limiter, resource, batch]    # ← 顺序有意义!
      exporters: [prometheusremotewrite]
    traces:
      receivers: [otlp]
      processors: [memory_limiter, resource, batch]
      exporters: [otlp/tempo]
```

<Callout type="warn">
  **两个新手必踩的坑：**

  **① `service.pipelines` 才是真正生效的部分。** 上面 `receivers:`/`processors:` 那些段只是**定义**组件；**没有被 `service.pipelines` 引用的组件，压根不会被实例化**。这是“我配了但没生效”的头号原因。

  **② processor 的顺序 = 数据流经的顺序。** `[memory_limiter, resource, batch]` 意味着数据**依次**经过这三个。顺序错了后果很实际：
  - **`memory_limiter` 必须第一** —— [要在数据进管道前就拦住](https://daiw.org/manual/observability/consumer-backpressure)。
  - **`batch` 通常最后** —— 攒批应该在所有加工之后，否则你在对着一批一批的数据反复做单条加工。
  - **过滤类 processor 要早** —— 越早丢掉不要的数据，后面的处理就越省。

  **`otlp/tempo` 这种 `type/name` 语法**是给“同一类组件的多个实例”命名的（比如两个 otlp exporter 指向不同后端）。
</Callout>

## 部署形态：agent 还是 gateway

**这才是真正影响架构的决策**，而且它常被忽略。

<Callout type="info">
  **两种形态，各有各的活：**

  ```mermaid
  flowchart TD
      subgraph A["Agent 模式(每个节点/Pod 一个)"]
          A1["应用"] --> A2["本地 Collector<br/>(DaemonSet/sidecar)"]
          A2 --> A3["后端"]
      end
      subgraph B["Gateway 模式(集中一层)"]
          B1["应用 ×N"] --> B2["Collector 集群<br/>(Deployment + LB)"]
          B2 --> B3["后端"]
      end
      subgraph C["两层(生产常见)"]
          C1["应用"] --> C2["Agent<br/>(本地,快速接收)"]
          C2 --> C3["Gateway<br/>(集中,重加工)"]
          C3 --> C4["后端"]
      end
      style C fill:#22c55e,color:#fff
  ```

  | | **Agent**（每节点一个） | **Gateway**（集中一层） |
  | --- | --- | --- |
  | **应用到它的延迟** | **极低**（本地） | 有网络跳 |
  | **能加节点级元数据** | **✅**（k8s pod/node 信息） | ❌（离得远，不知道） |
  | **能做全局的事** | ❌ | **✅**（[尾采样](https://daiw.org/manual/observability/sampling)、跨服务聚合） |
  | **资源开销** | × 节点数（每个都占内存） | 集中，可控 |
  | **后端连接数** | **多**（每个 agent 一份） | **少**（集中收敛） |

  **关键洞见：两者解决的问题不同，所以生产环境常常**两层都要**：**

  - **Agent（本地）** —— 应用**极快地**把数据吐给本地进程就返回（[不用等远程后端](https://daiw.org/manual/observability/consumer-backpressure)），agent 负责**加节点/pod 元数据**（它知道自己在哪台机器、哪个 pod），然后转发。
  - **Gateway（集中）** —— 做**需要全局视野**的事：[尾采样](https://daiw.org/manual/observability/sampling)（必须看到一条 trace 的所有 span，而它们可能来自不同节点！）、收敛到后端的连接数、统一的重试和缓冲。

  **[尾采样必须在 gateway](https://daiw.org/manual/observability/sampling) 这条尤其重要**——它是“为什么需要两层”最硬的技术理由：**agent 只看得到本节点的 span，而一条 trace 横跨多个节点**。[下一部分讲采样时会展开](https://daiw.org/manual/observability/sampling)。
</Callout>

## 发行版：core / contrib / k8s / otlp

看 `distributions.yaml`（真实文件）：

```yaml
- name: core        # 最小集,只有核心组件
- name: contrib     # 全家桶,几百个社区组件
- name: k8s         # 为 k8s 场景裁剪的
- name: otlp        # 只有 OTLP 收发,极小
```

<Callout type="info">
  **为什么要分发行版？因为 [contrib 里有几百个组件](https://daiw.org/manual/observability/custom-components)**（每个云厂商、每个中间件都有自己的 receiver/exporter），**全打进一个二进制会非常臃肿**——镜像几百 MB，而你可能只用其中三个。

  **实战建议：生产环境用 [OCB（OpenTelemetry Collector Builder）构建自己的发行版](https://daiw.org/manual/observability/custom-components)**，只打包你真正用的组件。好处：
  - **镜像小** —— 从几百 MB 降到几十 MB。
  - **攻击面小** —— 不用的组件不会有 CVE 影响你。
  - **启动快、内存省。**

  **这个“按需组装”的模式，正是[五类组件 + Factory 注册](https://daiw.org/manual/observability/collector-architecture)那套设计带来的红利**——因为核心只认接口，组件可以自由增删。
</Callout>

## 小结与下一章

**confmap 把配置源抽象成 provider**（[file/env/http/https/yaml](https://daiw.org/manual/observability/collector-config)），能[从远程拉配置、且能**组合多个源做分层覆盖**](https://daiw.org/manual/observability/collector-config)（基础 + 环境覆盖，和 Kustomize 一个思路），配合 `${VAR}` 外置环境相关的东西。**两个必踩的坑**：①[**只有 `service.pipelines` 引用的组件才会被实例化**](https://daiw.org/manual/observability/collector-architecture)（“配了没生效”的头号原因）；②[**processor 顺序 = 数据流经顺序**](https://daiw.org/manual/observability/consumer-backpressure)（`memory_limiter` 必须第一、`batch` 通常最后、过滤要早）。**部署形态是真正影响架构的决策**：**Agent**（[本地、极低延迟、能加节点元数据](https://daiw.org/manual/observability/collector-config)）vs **Gateway**（[集中、能做全局的事、收敛后端连接](https://daiw.org/manual/observability/sampling)）；**生产常常两层都要**，而“[**尾采样必须在 gateway**（agent 只看得到本节点的 span，而一条 trace 横跨多节点）](https://daiw.org/manual/observability/sampling)”是需要两层最硬的技术理由。**发行版分 core/contrib/k8s/otlp**——生产该[用 OCB 组装自己的](https://daiw.org/manual/observability/custom-components)（镜像小、攻击面小），这是[Factory 注册那套设计的红利](https://daiw.org/manual/observability/collector-architecture)。

最后一章：怎么扩展它。👉 [自定义组件](https://daiw.org/manual/observability/custom-components)。

## 参考资料

- [1] OTel Collector 源码 `confmap/provider/`（file/env/http/https/yaml）、`distributions.yaml`。
- [2] OTel Collector 官方文档：Configuration、Deployment patterns（agent/gateway）、OCB（ocb）。
- [3] 关联[Collector 架构](https://daiw.org/manual/observability/collector-architecture)、[背压与 memory_limiter](https://daiw.org/manual/observability/consumer-backpressure)。
