如果你曾在两三年前搭建过 Kubernetes 上的 OpenTelemetry(OTel)可观测性体系,你的记忆大概率是这样的:
从官方仓库拉下来一个 Helm Chart,然后对着一份长达 300 到 500 行的 values.yaml 苦苦挣扎——你需要手写 hostmetrics 接收器去抓节点负载,手动声明各种宿主机卷挂载(/proc、/sys),小心翼翼地配置访问 Kubelet 10250 端口的 TLS 与 Token,再写上一整页 Kubernetes RBAC ClusterRole 规则去抓集群资源,最后还要把所有微服务的 OTLP 链路追踪(Traces)塞进同一个 Collector 进程里。
然而,当你最近重新翻开 OpenTelemetry 官方的 opentelemetry-helm-charts 时,你会发现整个部署哲学发生了翻天覆地的变化:
不仅那些冗长晦涩的底层配置被精炼成了几行声明式的 Presets 开关,更关键的是,官方与社区生产最佳实践强烈推荐将 OpenTelemetry Collector 拆分为三个独立的 Helm Release(或精简为两个) 进行部署:
| Release 名称 | 部署模式 (Mode) | 副本数 (Replicas) | 核心职责与 Preset |
|---|---|---|---|
otel-agent | daemonset | 每节点 1 份 | hostMetrics + kubeletMetrics + k8sattributes,贴身采集节点基础设施 |
otel-cluster | deployment | 严格 1 副本(单例) | clusterMetrics + events,专门与 K8s API Server 交互采集集群全局对象 |
otel-gateway | deployment | 2+(支持 HPA 扩缩) | 接收业务 OTLP 流量 → 尾部采样、批量缓冲、脱敏 → 导出 Jaeger / Tempo / Prometheus |
为什么好端端的一个 Helm Chart,上游要推荐我们“分三次装”?直接装一个单体应用难道不更省事吗?为什么 otel-cluster 必须是单例?在什么场景下又能退化为“两个 Release”?
本文将为你全面还原这次架构演进背后的工程逻辑与生产考量。
1. 机制跃迁:Presets 如何终结“手写配置地狱”?
要理解架构拆分,首先要看懂 OpenTelemetry Helm Chart 近期最重要的配置特性:Presets(预设配置)。
在早期的部署模式中,配置一个能够采集主机与容器指标的 Collector 极其痛苦:
- 接收器与流水线拼装:你需要手动定义
hostmetrics的数十个 scraper(cpu、memory、disk、filesystem、load 等),还要为kubeletstats配置鉴权服务账号与非安全跳过参数; - 宿主卷挂载易错漏:容器默认无法读取物理节点的硬件指标,必须在 Pod Spec 中手动声明
hostPath挂载宿主机的/proc、/sys、/var/run/docker.sock等目录。一旦漏挂或路径配错,指标就会无声丢失; - RBAC 权限膨胀与漂移:采集 Pod 状态需要
pods权限,采集节点需要nodes/proxy权限,采集事件需要events权限。手动手写ClusterRole不仅繁琐,而且在升级 OTel Collector 版本时极易出现权限缺失。
为了解决这一痛点,官方 Helm Chart 引入了开箱即用的 Presets 引擎:
通过 Presets,以往数百行的底层资源装配被抽象成了极简的布尔声明:
presets:
# 1. 自动挂载 /proc 和 /sys,装配 hostmetrics receiver
hostMetrics:
enabled: true
# 2. 自动赋予 nodes/proxy 权限,装配 kubeletstats receiver
kubeletMetrics:
enabled: true
# 3. 自动注入 k8sattributes processor,提取 Pod 元数据标签
kubernetesAttributes:
enabled: true
# 4. 自动赋予集群资源只读权限,装配 k8s_cluster receiver
clusterMetrics:
enabled: true
当你将某个 Preset 设为 enabled: true 时,Helm 模板引擎会在幕后自动完成三位一体的注入:
- 自动配置组件与 Pipeline:将对应的 Receiver 或 Processor 注入到 Collector 运行时的
service.pipelines中; - 自动挂载所需的 Volumes:按需向 Pod 模板中注入
/proc、/sys或/var/log/pods挂载项,未启用的 Preset 绝不挂载额外宿主目录; - 自动生成最小权限 RBAC:在生成的
ClusterRole中精准赋予当前功能所必需的 API 组和资源动词,完全符合最小权限安全基线。
然而,Preset 解决了“怎么配置”,却引发了一个更严峻的架构命题——“在哪里运行”?
如果你在同一个 Chart Release 中把上面 4 个 Preset 全部设为 true,无论你选择以 DaemonSet 还是 Deployment 运行,都会直接引发生产灾难。
2. 架构拆解:三大 Release 的职责与生命周期隔离
这就是为什么社区和上游必须将部署拆分为三个 Release。它们看似使用的是同一个 Helm Chart(open-telemetry/opentelemetry-collector),但背后承载的计算模式、安全权限、爆炸半径与扩缩容维度完全不同。
Release 1:otel-agent(节点贴身守护进程)
- 部署模式:
mode: daemonset - 副本数:与 Kubernetes 物理 Worker 节点数严格 1:1
- 开启 Presets:
hostMetrics、kubeletMetrics、kubernetesAttributes(按需开启logsCollection)
核心职责与设计约束
otel-agent 是驻留在每个计算节点上的基础设施探针。它的核心任务是抓取宿主机本身的硬件使用率(CPU、内存、网络、IO)以及当前节点上运行的 Kubelet 容器指标。
- 强宿主机绑定:由于开启了
hostMetrics,Agent 容器必须挂载宿主机的/proc和/sys,并通过环境变量KUBE_NODE_NAME获取自身所在的节点名称,直接通过localhost或节点内部网络(10250端口)与本地 Kubelet 通信; - 故障爆炸半径限制:如果某台机器上的 Agent 发生内存泄漏、被 OOM Killer 杀掉,或者宿主机物理故障,损失的仅仅是这单单一台机器的基础监控,全集群其他节点的业务追踪与指标数据完全不受影响;
- 生命周期固定:它的伸缩仅随 Worker 节点的增减而变动,绝对不能也不需要根据业务 HTTP/gRPC 请求流量进行动态 HPA 伸缩。
Release 2:otel-cluster(控制面全局单例)
- 部署模式:
mode: deployment - 副本数:严格为 1(单例 Singleton)
- 开启 Presets:
clusterMetrics(基于k8s_clusterreceiver),可选开启kubernetesEvents(基于k8sobjectsreceiver)
为什么必须严格单例(replicaCount: 1)?
这是初接触该拓扑时最容易困惑的地方:云原生架构讲究高可用,为什么 otel-cluster 只能配 1 个副本?
k8s_cluster 接收器的原理,是通过调用 Kubernetes API Server,拉取全集群范围的对象元数据与调度状态(例如:各 Namespace 下 Deployment 的期望副本与就绪副本数、Node 状态条件、Pod 处于 Pending/CrashLoopBackOff 的分布等)。
- 杜绝数据重复污染:Kubernetes API Server 返回的是全局状态视图。如果你随手给
otel-cluster设置了 3 个副本,3 个 Pod 会各自独立轮询 API Server,并向监控后端推送 3 份一模一样的时序数据!在 Grafana 面板上,你的集群 Pod 数量和资源消耗会被瞬间放大 3 倍; - 保护 Kube-APIServer 免受雪崩冲击:大规模集群中包含数十万个 K8s 资源对象。1 个单例 Pod 定时轮询足以完成度量,多副本只会让 API Server 承受数倍的额外 List/Watch 压力;
- 伸缩维度解耦:
otel-cluster的资源开销只与集群中 Kubernetes 资源对象的多少呈线性关系,与用户的业务请求并发量(QPS)没有任何直接关系。将其独立拆解出来,可以给它分配非常小且固定的 CPU/内存 Limit(例如 0.2 核、256MB),永远安稳运行。
Release 3:otel-gateway(数据面弹性中枢网关)
- 部署模式:
mode: deployment - 副本数:默认 2+,配置 HPA(Horizontal Pod Autoscaler)弹性伸缩
- 开启 Presets:全部关闭(不需要任何基础设施 Presets)
核心职责与治理能力
otel-gateway 是整个可观测性流水线的数据中枢。所有业务微服务应用(通过 OpenTelemetry SDK)打出的 Traces、Metrics 和 Logs,都会直接发送到 Gateway 暴露的统一 Service 端点(gRPC: 4317,HTTP: 4318)。
-
无特权与零宿主依赖:Gateway 是纯粹的网络代理型无状态服务。它不需要挂载
/proc,不需要访问 Kubelet,更不需要任何特殊的宿主特权,安全性极高; -
重型计算与智能治理:
- 内存熔断防爆(
memory_limiter):在突发流量压垮 Collector 之前主动丢弃低优先级遥测或触发背压,防止 Pod OOM; - 尾部智能采样(
tail_sampling):对于微服务全链路追踪,99% 的 200 成功请求并不需要全量入库,Gateway 可以根据状态码和耗时进行判断,保留 100% 的异常报错链路与慢查询,大幅削减后端存储成本; - 多后端路由与脱敏(Routing & Redaction):清洗 Trace 中的身份证号、密码等敏感属性,将 Traces 导出到 Jaeger / Tempo,将业务指标推送到 Prometheus / VictoriaMetrics;
- 内存熔断防爆(
-
纯业务流量驱动的 HPA:由于不绑定宿主机也不绑定 API Server,Gateway 可以根据自身的 CPU 消耗或接收到的 Span 速率,在秒级内从 2 个副本水平扩容至 20 个甚至 50 个副本,完美扛住双十一或大促流量洪峰。
3. 架构矛盾:为什么不能合并为一个单体?
理解了三大组件的分工后,我们再来推演:如果强行把它们合为一个 Release,会产生哪些致命冲突?
冲突 1:DaemonSet 无法充当弹性数据网关
- 如果让每台机器上的
otel-agent兼任 Gateway 接收业务全量 Trace 并做尾部采样:- 内存暴炸:尾部采样需要在内存中开辟滑动时间窗口暂存同一个 TraceId 的所有 Span。如果这部分内存开销放在 DaemonSet 中,在流量高峰期会导致 Worker 节点上的 Agent 大量 OOM,甚至触发节点维度的资源驱逐;
- 跨节点链路割裂:微服务请求是跨机器流转的,同一个 TraceId 下的 Span 会落在不同的机器上。分散在各节点的 DaemonSet 无法直接感知全局完整的链路,难以进行跨微服务的全局采样决策。
冲突 2:Gateway 绝不能混入 clusterMetrics
- 如果为了图省事,把
clusterMetrics: enabled: true塞进otel-gateway:- 当业务低峰期时,Gateway 有 2 个副本;当大促流量来临,HPA 将 Gateway 扩容到 30 个副本。此时会有 30 个 Collector 实例同时高频轮询 Kube-APIServer!集群控制面可能瞬间被自己的监控组件打瘫。
正是这种**硬件绑定(Agent)、控制面全局(Cluster)与数据面高并发(Gateway)**三者在生命周期和伸缩维度上的根本矛盾,驱动了上游推荐拓扑走向解耦。
4. 选型权衡:什么时候可以收敛为“两个 Release”?
虽然官方黄金标准是拆为 3 个 Release,但用户提问中也准确地提到了“或两个”。在真实生产环境中,确实存在收敛为两个 Release 的合理场景。
方案 A:收敛为 otel-agent + otel-gateway(合并 Cluster)
这是目前最常见的双 Release 变形:
- 做法:在
otel-gateway的 Deployment 中启用presets.clusterMetrics.enabled: true,但必须显式启用 Leader Election(领导者选举机制)。 - 机制:Gateway 虽然运行多个副本,但只有抢到 Lease 锁的 Leader Pod 才会去轮询 API Server,其他 Standby Pod 仅作为普通的 OTLP 流量网关。
- 适用边界:集群规模较小(Worker 节点小于 50 个)、网关副本数较少(2 到 4 个),且 HPA 变动不剧烈的场景。
- 潜在风险:当网关因为业务流量高峰触发滚动更新或 Pod 扩缩时,Leader 发生切换可能导致短暂的控制面指标断流。
方案 B:收敛为 otel-agent + otel-cluster(无网关直连模式)
- 做法:业务 Pod 通过 Kubernetes Downward API 获取当前节点的
status.hostIP,直接将 OTLP 遥测推送到本节点的otel-agent;Agent 完成批处理后,直接将数据推送至外部托管的监控平台(如云厂商托管的 Prometheus / Grafana / Datadog)。 - 适用边界:团队不需要自建复杂的尾部采样管道,可观测性后端完全外包给云厂商,追求极简的组件维护量。
5. 生产落地:一份清晰的 Helm values 清单
要在生产集群中落地这套标准架构,只需维护三份职责清晰的 values 文件,并基于同一个官方 Chart 执行三次部署:
# 添加官方 Helm 仓库
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update
1. values-gateway.yaml(首先部署网关)
mode: deployment
replicaCount: 2
# 生产必须配置资源约束与内存限制
resources:
limits:
cpu: "2"
memory: 2Gi
requests:
cpu: 500m
memory: 512Mi
# 开启网关自身 HPA
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# 纯网关模式,关闭所有底层 Presets
presets:
hostMetrics:
enabled: false
kubeletMetrics:
enabled: false
clusterMetrics:
enabled: false
kubernetesAttributes:
enabled: false
# 核心流水线:OTLP 接收 → 内存熔断与批处理 → 导出存储
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 75
spike_limit_percentage: 20
batch:
send_batch_size: 1024
timeout: 1s
exporters:
# 示例:链路导出到 Jaeger,指标导出到 Prometheus
otlp/jaeger:
endpoint: jaeger-collector.observability.svc:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp/jaeger]
2. values-agent.yaml(部署节点代理)
mode: daemonset
presets:
hostMetrics:
enabled: true
kubeletMetrics:
enabled: true
kubernetesAttributes:
enabled: true
logsCollection:
enabled: false # 按需开启节点容器日志采集
# 将节点指标通过内部网络汇总推向 Gateway
config:
exporters:
otlp/gateway:
endpoint: otel-gateway.observability.svc:4317
tls:
insecure: true
service:
pipelines:
metrics:
# hostmetrics 与 kubeletstats 会由 Preset 自动合成并加入 receivers
processors: [k8sattributes, batch]
exporters: [otlp/gateway]
3. values-cluster.yaml(部署控制面单例)
mode: deployment
# 铁律:严格单例
replicaCount: 1
presets:
clusterMetrics:
enabled: true
kubernetesEvents:
enabled: true
config:
exporters:
otlp/gateway:
endpoint: otel-gateway.observability.svc:4317
tls:
insecure: true
service:
pipelines:
metrics:
# k8s_cluster 会由 Preset 自动注入
processors: [batch]
exporters: [otlp/gateway]
依次安装命令
# 1. 安装网关数据中枢
helm upgrade --install otel-gateway open-telemetry/opentelemetry-collector \
-n observability --create-namespace -f values-gateway.yaml
# 2. 安装控制面单例采集器
helm upgrade --install otel-cluster open-telemetry/opentelemetry-collector \
-n observability -f values-cluster.yaml
# 3. 安装节点守护进程
helm upgrade --install otel-agent open-telemetry/opentelemetry-collector \
-n observability -f values-agent.yaml
总结:从“单体玩具”到“工业分治”
回头审视 OpenTelemetry Helm Chart 的这一演进过程,本质上是云原生监控体系从“实验室原型”走向“超大规模生产环境”的必然结果:
- 配置层面:通过 Presets 将底层的复杂拼装(Receivers + VolumeMounts + RBAC)封装为高内聚的业务意图声明,终结了数百行的配置地狱;
- 拓扑层面:通过将 Chart 拆解为
otel-agent、otel-cluster、otel-gateway三个独立的 Release,彻底实现了宿主特权、控制面状态与数据面并发的关注点分离。
对于中大型集群而言,三 Release 拓扑能够让你的集群在经历极端流量突发、节点下线与版本滚动更新时,始终保持可观测性管线的沉着与稳定。下次在 Kubernetes 中规划可观测性时,不妨告别那个笨重的巨石 YAML,拥抱这套层次分明的工业级拓扑。