配置与部署形态 · confmap 与 agent/gateway
Collector 的配置能从文件、环境变量、HTTP、甚至内联 YAML 来——这一章读 confmap 的 provider 机制,以及那个真正影响架构的决策:Collector 该部署成 agent(每节点一个)还是 gateway(集中一层),还是两者都要。
配置与部署形态 · confmap 与 agent/gateway
组件都认识了(五类、数据模型、怎么传)。这一章讲怎么把它们配起来,以及一个真正影响架构的决策: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:{...} (内联)配置源被抽象成 provider,用 URI scheme 区分。这带来两个实用能力:
- 可以从远程拉配置 ——
httpprovider让你把配置放在配置中心。改配置不用重新打镜像。 - 可以组合多个源 —— 这是最有用的:
多个配置会被合并**(后面的覆盖前面的)。** 于是你可以做“基础配置 + 环境覆盖”的分层——和 Kustomize / Helm values 一个思路。otelcol --config=file:/etc/otel/base.yaml \ --config=file:/etc/otel/override.yaml \ --config="yaml:exporters::otlp::endpoint: ${BACKEND}"
${VAR} 语法可以在配置里直接引用环境变量,这是最常用的:把 endpoint、token 这类环境相关的东西外置。注意 token 别写进配置文件——用 ${ENV} 或专门的 extension 处理认证。
一份完整配置长什么样
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]两个新手必踩的坑:
① service.pipelines 才是真正生效的部分。 上面 receivers:/processors: 那些段只是定义组件;没有被 service.pipelines 引用的组件,压根不会被实例化。这是“我配了但没生效”的头号原因。
② processor 的顺序 = 数据流经的顺序。 [memory_limiter, resource, batch] 意味着数据依次经过这三个。顺序错了后果很实际:
memory_limiter必须第一 —— 要在数据进管道前就拦住。batch通常最后 —— 攒批应该在所有加工之后,否则你在对着一批一批的数据反复做单条加工。- 过滤类 processor 要早 —— 越早丢掉不要的数据,后面的处理就越省。
otlp/tempo 这种 type/name 语法是给“同一类组件的多个实例”命名的(比如两个 otlp exporter 指向不同后端)。
部署形态:agent 还是 gateway
这才是真正影响架构的决策,而且它常被忽略。
两种形态,各有各的活:
| Agent(每节点一个) | Gateway(集中一层) | |
|---|---|---|
| 应用到它的延迟 | 极低(本地) | 有网络跳 |
| 能加节点级元数据 | ✅(k8s pod/node 信息) | ❌(离得远,不知道) |
| 能做全局的事 | ❌ | ✅(尾采样、跨服务聚合) |
| 资源开销 | × 节点数(每个都占内存) | 集中,可控 |
| 后端连接数 | 多(每个 agent 一份) | 少(集中收敛) |
关键洞见:两者解决的问题不同,所以生产环境常常两层都要**:**
- Agent(本地) —— 应用极快地把数据吐给本地进程就返回(不用等远程后端),agent 负责加节点/pod 元数据(它知道自己在哪台机器、哪个 pod),然后转发。
- Gateway(集中) —— 做需要全局视野的事:尾采样(必须看到一条 trace 的所有 span,而它们可能来自不同节点!)、收敛到后端的连接数、统一的重试和缓冲。
尾采样必须在 gateway 这条尤其重要——它是“为什么需要两层”最硬的技术理由:agent 只看得到本节点的 span,而一条 trace 横跨多个节点。下一部分讲采样时会展开。
发行版:core / contrib / k8s / otlp
看 distributions.yaml(真实文件):
- name: core # 最小集,只有核心组件
- name: contrib # 全家桶,几百个社区组件
- name: k8s # 为 k8s 场景裁剪的
- name: otlp # 只有 OTLP 收发,极小为什么要分发行版?因为 contrib 里有几百个组件(每个云厂商、每个中间件都有自己的 receiver/exporter),全打进一个二进制会非常臃肿——镜像几百 MB,而你可能只用其中三个。
实战建议:生产环境用 OCB(OpenTelemetry Collector Builder)构建自己的发行版,只打包你真正用的组件。好处:
- 镜像小 —— 从几百 MB 降到几十 MB。
- 攻击面小 —— 不用的组件不会有 CVE 影响你。
- 启动快、内存省。
这个“按需组装”的模式,正是五类组件 + Factory 注册那套设计带来的红利——因为核心只认接口,组件可以自由增删。
小结与下一章
confmap 把配置源抽象成 provider(file/env/http/https/yaml),能从远程拉配置、且能组合多个源做分层覆盖(基础 + 环境覆盖,和 Kustomize 一个思路),配合 ${VAR} 外置环境相关的东西。两个必踩的坑:①只有 service.pipelines 引用的组件才会被实例化(“配了没生效”的头号原因);②processor 顺序 = 数据流经顺序(memory_limiter 必须第一、batch 通常最后、过滤要早)。部署形态是真正影响架构的决策:Agent(本地、极低延迟、能加节点元数据)vs Gateway(集中、能做全局的事、收敛后端连接);生产常常两层都要,而“尾采样必须在 gateway(agent 只看得到本节点的 span,而一条 trace 横跨多节点)”是需要两层最硬的技术理由。发行版分 core/contrib/k8s/otlp——生产该用 OCB 组装自己的(镜像小、攻击面小),这是Factory 注册那套设计的红利。
最后一章:怎么扩展它。👉 自定义组件。
参考资料
- [1] OTel Collector 源码
confmap/provider/(file/env/http/https/yaml)、distributions.yaml。 - [2] OTel Collector 官方文档:Configuration、Deployment patterns(agent/gateway)、OCB(ocb)。
- [3] 关联Collector 架构、背压与 memory_limiter。