作者:阳其凯 (逸陵)
01 背景
在云原生可观测蓬勃发展的当下,想必大家对 OpenTelemetry & Prometheus 并不是太陌生。OpenTelemetry 是 CNCF(Cloud Native Computing Foundation)旗下的开源项目,它的目标是在云原生时代成为应用性能监控领域的事实标准,它提供了一套统一的 API 和 SDK,用于生成、收集和处理分布式系统的遥测数据。总而言之,OpenTelemetry 是一套观察性的标准,具有语言无关性,支持各种编程语言和框架,并可与多种观察平台集成。
而 Prometheus 作为目前最受欢迎的开源监控系统,并且已经被 Kubernetes、Envoy 等广泛使用的云原生项目所采用。虽然 Prometheus 的查询语言十分强大,但是其数据格式还是 Prometheus 自己定义的。因此,它只能与 Prometheus 服务器集成,不能与其他系统集成。
而 OpenTelemetry 承诺在 Trace、Log 和 Metric 之间创建一个统一标准,所以它的数据格式是统一的,并具有足够的灵活性与交互性,同时完全兼容 Prometheus,所以吸引越来越多的开发运维人员使用 OpenTelemetry 在承载 Trace & Log 同时将其用于 Metric 的统计,与 Prometheus 生态进行结合,实现更好的观测监控。
本文以构建系统可观测(重点为指标监控体系)为切入点,对比 OpenTelemetry 与 Prometheus 的相同与差异,粗浅的谈下个人选择的一些观点与看法;后重点介绍如何将应用的 OpenTelemetry 指标接入 Prometheus 及背后原理,最后介绍阿里云可观测监控 Prometheus 版拥抱 OpenTelemetry 及相关落地实践案例,希望能更好的帮助读者更好的理解 OpenTelemetry 及与 Prometheus 的生态融合。
02 站在 OpenTelemetry 与 Prometheus 的十字路口
如果你作为研发运维人员在进行系统观测时,特别是构建指标监控体系时,会有很多困惑,到底选择 OpenTelemetry 还是 Prometheus。在回答这个问题之前我们首先需要明确的了解 OpenTelemetry 与 Prometheus 的同异。
而在对比之前,我们还是有必要对 OpenTelemetry 的指标模型进行深入的介绍。
2.1 OpenTelemetry 指标模型介绍
OpenTelemetry 将指标分解为三个交互模型,事件模型、Timeseries 模型以及指标(Metric)流模型。其中事件模型表示仪器如何报告指标数据;Timeseries 模型表示后端如何存储指标数据;指标(Metric)流模型定义 OpenTelemetry 协议 (OTLP),表示如何在事件模型和时间序列存储之间操作和传输指标数据流。
2.1.1 事件模型
事件模型是数据记录发生的地方,它的基础由 Instruments[1]组成,Instruments 用于通过事件记录可观测数据。然后,这些原始事件在发送到其他系统之前以某种方式进行转换。OpenTelemetry 指标的设计使得使用相同的 Instruments 和事件使用不同的方式来生成 metric 指标流。
尽管观测事件可以直接报告给后端,但实际上这是不可行的,因为可观测系统中使用的数据量巨大,并且可用于遥测收集目的的网络/CPU 资源有限,最好的例子是直方图指标,其中原始事件以压缩格式而不是单个时间序列记录。
注意:上图显示了一个 Instrument 如何将事件转换为多种类型的指标流。
2.1.2 时间序列模型
在 low-level 指标数据模型中,时间序列是由多个元数据属性组成的实体定义:
- 指标名称
- 属性(维度)
- 点的值类型(整数、浮点数等)
- 测量单位
每个时间序列的原始数据都是有序的(时间戳,值)点,其值类型如下:
- Counter
- Gauge
- Histogram
- Exponential Histogram
2.1.3 OpenTelemetry 协议数据模型
OpenTelemetry 协议(OTLP)数据模型由 Metric 数据流组成,这些流又由度量数据点组成,指标数据流可以直接转换为时间序列。指标流被分组为单独的指标对象,通过以下方式标识:
- 原始 Resource 属性
- Instrumentation Scope(例如 instrumentation lib 名称、版本)
- 指标流的 name
包括 name 在内,Metric 对象由以下属性定义:
- 数据点类型(例如 Sum、Gauge、Histogram、ExponentialHistogram、Summary)
- 指标流的单位(unit)
- 指标流的描述(description)
- 内部数据点属性(如果适用):AggregationTemporality、Monotonic
数据点类型、单位和内在属性被认为是识别性的,而描述字段本质上不具备识别性。特定点的外在属性不被视为识别;这些包括但不限于:
- 直方图(Histogram)数据点的桶边界
- 指数直方图(ExponentialHistogram)数据点的大小或桶数量
Metric 对象包含不同流,由 Attributes 标识。在各个流中,点由一个或者两个时间戳标识,详细信息因数据点类型而异。
2.1.4 点类型介绍
2.1.4.1 Sum
OTLP 中的 Sums[2]由以下部分组成:
1. 增量(delta)或累积(cumulative)的 Aggregation Temporality。2. 一个表示 Sum 是否单调(monotonic[3])的标志。
- 对于增量单调(delta monotonic) Sums,这意味着读者应该期望非负值
- 对于累积单调(cumulative monotonic) Sums,这意味着读者应该期望不小于先前值的值
一组数据点,每个数据点包含:
- 一组独立的属性名称-值对
- 计算 Sum 的时间窗口
- (可选)一组 examplars[4]
当聚合时间性为"delta"时,我们期望度量流的时间窗口没有重叠,如下图所示:
而当与聚合时间性为"cumulative"的时候,我们期望报告自“开始”以来的全部总和(其中通常开始意味着进程/应用程序启动)。
在各种用例中,使用 Delta 与累积聚合之间存在各种权衡,例如:
- 检测进程重新启动
- 计算 rate
- 基于推与拉的指标报告
OTLP 支持这两种模型,并允许 API、SDK 和用户在其各自的场景中确定最佳的折衷方案。
2.1.4.2 Gauge
OTLP 中的 Gauge[5]表示给定时间的采样值。Gauge 流包括一组数据点,每个数据点包含:
- 一组独立的属性名称-值对
- 采样值(例如当前 CPU 温度)
- 对值进行采样时的时间戳(time_unix_nano)
- (可选)时间戳(start_time_unix_nano),它最能代表可以记录测量的第一个可能时刻,这通常设置为指标收集系统启动时的时间戳
- (可选)一组 examplars
在 OTLP 中,Gauge 流中的点表示给定时间窗口的最后采样事件。
在此示例中,我们可以看到我们使用 Gauge 采样的基础时间序列,虽然事件模型可以在给定的指标报告间隔内多次采样,但通过 OTLP 在指标流中仅报告最后一个值。
Gauge 不提供聚合语义,而是在执行时间对齐或调整分辨率等操作时使用“最后一个样本值”。可以通过转换为直方图或其他度量类型来聚合 Gauge。这些操作默认情况下不执行,需要用户直接配置。
2.1.4.3 Histogram
Histogram[6]度量数据点以压缩格式传达大量记录的测量结果。直方图将一组事件分为为多个群体,并提供总体事件计数和所有事件的总和。
直方图 Histogram 由以下部分组成:
1. 增量(delta)或累积(cumulative)的 Aggregation Temporality2. 一组数据点,每个数据点包含:
- 一组独立的属性名称-值对
- 捆绑直方图的时间窗口((start, end])
- 直方图中点总数的计数(count)
- 直方图中所有值的总和(sum)
- (可选)直方图中所有值的最小值(min)
- (可选)直方图中所有值的最大值(max)
- (可选)一系列桶:明确的边界值,这些值表示存储桶的下限和上限,以及是否将给定的观察结果记录在该存储桶中;属于该存储桶的观测值数量的计数。
- (可选)一组 examplars
与 Sums 一样,直方图也定义聚合时间性。上图表示 Delta 时间性,其中累积的事件计数在报告后重置为零,并发生新的聚合。另一方面,累积继续聚合事件,并使用新的开始时间进行重置。聚合时间性 Aggregation Temporality 也对最小和最大字段有影响,最小值和最大值对于增量时间性(Delta)更有用,因为随着记录更多事件,累积最小值和最大值表示的值将稳定。此外,可以将最小值和最大值从 Delta 转换为 Cumulative,但不能从 Cumulative 转换为 Delta。从累积转换为增量时,可以删除最小值和最大值,或者以替代表示形式(例如仪表)捕获最小值和最大值。
桶数是可选的。没有桶的直方图仅根据总和和计数来传达总体,并且可以解释为具有单桶覆盖的直方图(-Inf,+Inf),如下为 Cumulative 型 Histogram 样例数据。
2023-11-29T13:43:45.238Z info ResourceMetrics #0 Resource SchemaURL: Resource attributes: -> service.name: Str(opentelemetry-metric-demo-delta-or-cumulative) -> service.version: Str(0.0.1) -> telemetry.sdk.language: Str(java) -> telemetry.sdk.name: Str(opentelemetry) -> telemetry.sdk.version: Str(1.29.0) ScopeMetrics #0 ScopeMetrics SchemaURL: InstrumentationScope aliyun Metric #0 Descriptor: -> Name: cumulative_long_histogram -> Description: ot cumulative demo cumulative_long_histogram -> Unit: ms -> DataType: Histogram -> AggregationTemporality: Cumulative HistogramDataPoints #0 StartTimestamp: 2023-11-29 13:43:15.181 +0000 UTC Timestamp: 2023-11-29 13:43:45.182 +0000 UTC Count: 2 Sum: 141.000000 Min: 62.000000 Max: 79.000000 ExplicitBounds #0: 0.000000 ExplicitBounds #1: 5.000000 ExplicitBounds #2: 10.000000 ExplicitBounds #3: 25.000000 ExplicitBounds #4: 50.000000 ExplicitBounds #5: 75.000000 ExplicitBounds #6: 100.000000 ExplicitBounds #7: 250.000000 ExplicitBounds #8: 500.000000 ExplicitBounds #9: 750.000000 ExplicitBounds #10: 1000.000000 ExplicitBounds #11: 2500.000000 ExplicitBounds #12: 5000.000000 ExplicitBounds #13: 7500.000000 ExplicitBounds #14: 10000.000000 Buckets #0, Count: 0 Buckets #1, Count: 0 Buckets #2, Count: 0 Buckets #3, Count: 0 Buckets #4, Count: 0 Buckets #5, Count: 1 Buckets #6, Count: 1 Buckets #7, Count: 0 Buckets #8, Count: 0 Buckets #9, Count: 0 Buckets #10, Count: 0 Buckets #11, Count: 0 Buckets #12, Count: 0 Buckets #13, Count: 0 Buckets #14, Count: 0 Buckets #15, Count: 0
2.1.4.4 ExponentialHistogram
指数直方图数据点是直方图数据点的替代表示形式,用于以压缩格式传达一组记录的测量值。ExponentialHistogram 使用指数公式压缩桶边界,与类似大小的替代表示相比,使其适合以较小的相对误差传输高动态范围数据。
直方图 Histogram 涉及聚合时间性、属性和时间戳以及 sum、count、min、max 和 exemplars 字段,ExponentialHistogram 与 Histogram 类似,这些字段都与直方图 Histogram 具有相同的解释,只是这两种类型之间的存储 bucket 结构不同。如下为一个 delta 形式的 ExponentialHistogram 数据格式:
2023-11-29T13:13:09.866Z info ResourceMetrics #0 Resource SchemaURL: Resource attributes: -> service.name: Str(opentelemetry-metric-demo-delta-or-cumulative) -> service.version: Str(0.0.1) -> telemetry.sdk.language: Str(java) -> telemetry.sdk.name: Str(opentelemetry) -> telemetry.sdk.version: Str(1.29.0) ScopeMetrics #0 ScopeMetrics SchemaURL: InstrumentationScope aliyun Metric #0 Descriptor: -> Name: cumulative_long_e_histogram -> Description: ot cumulative demo cumulative_long_e_histogram -> Unit: ms -> DataType: ExponentialHistogram -> AggregationTemporality: Cumulative ExponentialHistogramDataPoints #0 StartTimestamp: 2023-11-29 13:11:54.858 +0000 UTC Timestamp: 2023-11-29 13:13:09.86 +0000 UTC Count: 3 Sum: 191.000000 Min: 15.000000 Max: 89.000000 Bucket (14.993341, 15.321652], Count: 1 Bucket (15.321652, 15.657153], Count: 0 Bucket (15.657153, 16.000000], Count: 0 Bucket (16.000000, 16.350354], Count: 0 Bucket (16.350354, 16.708381], Count: 0 Bucket (16.708381, 17.074246], Count: 0 Bucket (17.074246, 17.448124], Count: 0 Bucket (17.448124, 17.830188], Count: 0 Bucket (17.830188, 18.220618], Count: 0 Bucket (18.220618, 18.619598], Count: 0 Bucket (18.619598, 19.027314], Count: 0 Bucket (19.027314, 19.443958], Count: 0 Bucket (19.443958, 19.869725], Count: 0 Bucket (19.869725, 20.304815], Count: 0 Bucket (20.304815, 20.749433], Count: 0 Bucket (20.749433, 21.203786], Count: 0 Bucket (21.203786, 21.668089], Count: 0 Bucket (21.668089, 22.142558], Count: 0 Bucket (22.142558, 22.627417], Count: 0 Bucket (22.627417, 23.122893], Count: 0 Bucket (23.122893, 23.629218], Count: 0 Bucket (23.629218, 24.146631], Count: 0 Bucket (24.146631, 24.675373], Count: 0 Bucket (24.675373, 25.215694], Count: 0 Bucket (25.215694, 25.767845], Count: 0 Bucket (25.767845, 26.332088], Count: 0 Bucket (26.332088, 26.908685], Count: 0 Bucket (26.908685, 27.497909], Count: 0 Bucket (27.497909, 28.100035], Count: 0 Bucket (28.100035, 28.715345], Count: 0 Bucket (28.715345, 29.344129], Count: 0 Bucket (29.344129, 29.986682], Count: 0 Bucket (29.986682, 30.643305], Count: 0 Bucket (30.643305, 31.314306], Count: 0 Bucket (31.314306, 32.000000], Count: 0 Bucket (32.000000, 32.700709], Count: 0 Bucket (32.700709, 33.416761], Count: 0 Bucket (33.416761, 34.148493], Count: 0 Bucket (34.148493, 34.896247], Count: 0 Bucket (34.896247, 35.660376], Count: 0 Bucket (35.660376, 36.441236], Count: 0 Bucket (36.441236, 37.239195], Count: 0 Bucket (37.239195, 38.054628], Count: 0 Bucket (38.054628, 38.887916], Count: 0 Bucket (38.887916, 39.739450], Count: 0 Bucket (39.739450, 40.609631], Count: 0 Bucket (40.609631, 41.498866], Count: 0 Bucket (41.498866, 42.407573], Count: 0 Bucket (42.407573, 43.336178], Count: 0 Bucket (43.336178, 44.285116], Count: 0 Bucket (44.285116, 45.254834], Count: 0 Bucket (45.254834, 46.245786], Count: 0 Bucket (46.245786, 47.258437], Count: 0 Bucket (47.258437, 48.293262], Count: 0 Bucket (48.293262, 49.350746], Count: 0 Bucket (49.350746, 50.431387], Count: 0 Bucket (50.431387, 51.535691], Count: 0 Bucket (51.535691, 52.664175], Count: 0 Bucket (52.664175, 53.817371], Count: 0 Bucket (53.817371, 54.995818], Count: 0 Bucket (54.995818, 56.200069], Count: 0 Bucket (56.200069, 57.430690], Count: 0 Bucket (57.430690, 58.688259], Count: 0 Bucket (58.688259, 59.973364], Count: 0 Bucket (59.973364, 61.286610], Count: 0 Bucket (61.286610, 62.628612], Count: 0 Bucket (62.628612, 64.000000], Count: 0 Bucket (64.000000, 65.401418], Count: 0 Bucket (65.401418, 66.833522], Count: 0 Bucket (66.833522, 68.296986], Count: 0 Bucket (68.296986, 69.792495], Count: 0 Bucket (69.792495, 71.320752], Count: 0 Bucket (71.320752, 72.882473], Count: 0 Bucket (72.882473, 74.478391], Count: 0 Bucket (74.478391, 76.109255], Count: 0 Bucket (76.109255, 77.775831], Count: 0 Bucket (77.775831, 79.478900], Count: 0 Bucket (79.478900, 81.219261], Count: 0 Bucket (81.219261, 82.997731], Count: 0 Bucket (82.997731, 84.815145], Count: 0 Bucket (84.815145, 86.672355], Count: 0 Bucket (86.672355, 88.570232], Count: 1 Bucket (88.570232, 90.509668], Count: 1
2.1.4.4.1 Exponential Scale
指数直方图的分辨率由 scale 参数来表征,scale 值越大,精度越高。指数直方图的 bucket 边界位于 base 的整数次幂,也称为“增长因子”,其中:
base = 2**(2**(-scale))
这些公式中的符号 ** 表示求幂,因此 2**x 读作“2 的 x 次方”,通常由 math.Pow(2.0, x) 等表达式计算,如下为 OpenTelemetry 官方中的样例,所选 scale 的计算 base 如下所示:
该设计的一个重要属性被描述为“完美子集”,具有给定比例的指数直方图的桶精确地映射到具有较小比例的指数直方图的桶中,这允许消费者降低直方图的分辨率(即缩小比例)而不会引入误差。上述 delta ExponentialHistogram 数据使用默认的 Base2ExponentialHistogramAggregation,其中 max scale 为 20。
private static final int DEFAULT_MAX_BUCKETS = 160; private static final int DEFAULT_MAX_SCALE = 20; private static final Aggregation DEFAULT = new Base2ExponentialHistogramAggregation(160, 20); private final int maxBuckets; private final int maxScale; private Base2ExponentialHistogramAggregation(int maxBuckets, int maxScale) { this.maxBuckets = maxBuckets; this.maxScale = maxScale; } public static Aggregation getDefault() { return DEFAULT; } public static Aggregation create(int maxBuckets, int maxScale) { Utils.checkArgument(maxBuckets >= 1, "maxBuckets must be > 0"); Utils.checkArgument(maxScale <= 20 && maxScale >= -10, "maxScale must be -10 <= x <= 20"); return new Base2ExponentialHistogramAggregation(maxBuckets, maxScale); }
2.1.4.4.2 Exponential Buckets
由索引(有符号整数)标识的指数直方图桶表示总体中大于基数**索引且小于或等于基数**(索引+1)的值,直方图的正负范围分别表示。使用与正值范围相同的比例,将负值按其绝对值映射到负值范围。请注意,因此,在负范围内,直方图桶使用下限边界。ExponentialHistogram 数据点的每个范围都使用桶的密集表示,其中桶的范围表示为单个偏移值、有符号整数和计数值数组,其中数组元素 i 表示桶索引的桶计数偏移 +i。
对于给定范围(正数或负数):
- 存储 bucket 索引 0 对范围 (1, base] 内的测量进行计数
- 正索引对应于大于 base 的绝对值
- 负索引对应于绝对值小于或等于 1
- 连续的 2 次幂之间有 2** scale 个 bucket
例如,当 scale=3 时,1和2之间有 2**3 个桶,请注意,scale=3 直方图中存储桶索引 4 的下边界映射到 scale=2 直方图中存储桶索引 2 的下边界,并映射到 scale=3 直方图中存储桶索引 1(即基数)的下边界。1 个直方图——这些是完美子集的示例。
Ps:由于最新版本中 Summay 已经不推荐,所以这里不做详细的介绍。
2.2 OpenTelemetry VS Prometheus
在本文中我们就不详细的介绍 Prometheus 的指标类型了,详细介绍参考 Prometheus 官网介绍[7]。我们通过对比 OpenTelemetry 指标类型以及 Prometheus 指标类型,我们会发现:
- OpenTelemetry 可以将 Metric 表示为增量,而不是累计,存储每个数据点之间的差异,而不是累计总和。然而Prometheus 在设计上不允许这样做。
- OpenTelemetry 允许 Metric 值为整数,而 Prometheus 无法表达浮点数。
- OpenTelemetry 可以将一些额外的元数据附加到直方图中,允许您跟踪最大值和最小值,这一点在有些场景是非常有用的。
- OpenTelemetry 具有指数直方图聚合类型(使用公式和刻度来计算 bucket 大小),Prometheus 不支持,但是可以实现兼容。
从上面的模型对比总结来说,OpenTelemetry 允许表示所有 Prometheus 度量类型(计数器、仪表、摘要和直方图),尽管如此 Prometheus 无法表示 OpenTelemetry 度量的某些配置,包括 delta 表示和指数直方图(尽管这些将很快添加到 Prometheus 中)以及整数值。所以换句话说,Prometheus 度量是 OpenTelemetry 度量的严格子集。
尽管这样,但是两者还是存在一些差异的,这里需要强调一下。
- Prometheus 提供 Metric 收集、存储和查询,它通过通过抓取目标来采集集指标,当然也可以通过 pushgateway形式,相关 Metric 数据最后存储在 Prometheus 本地数据库或者其他远端时许数据库中,最后通过 Prometheus 查询语言 PromQL 进行数据的读取,所以 Prometheus 是一个完整的可观测解决方法,集数据的采集、计算、存储、报警、查询等于一体。
- OpenTelemetry 作用域要小很多。它通过推送或拉取使用整合的 API 收集指标(以及跟踪和日志),并将其发送到其他系统进行存储或查询。所以 OpenTelemetry 只关注应用程序交互的可观测性部分,从而将信号的创建与存储和查询信号的操作问题脱钩。所以在指标的存储上 OpenTelemetry Metric 通常回到 Prometheus 或者 Prometheus 兼容的系统中。
2.3 如何选择
不可否认 OpenTelemetry 与 Prometheus 都是非常优秀的,有时候选择上确实会比较纠结,所以总的原则还是结合实际的业务场景出发,因地制宜,以发展的眼光看待问题。如下的浅谈如有不妥欢迎交流、批评与指正。
- 如果你尝试构建统一的可观测体系包含 Trace、Log 和 Metric,那么 OpenTelemetry 将允许使用相同的库来对所有三种信号类型进行检测,减少包依赖,方便运维管理。这是一个显著的好处,可以将三种相同的信号发送到同一一个后端也可以发送到不同的后端,OpenTelemetry 都支持。
- 如果你希望你的应用/系统/组件在云原生、容器化的场景下监控指标能更好的被观测,特别是开源、被第三方使用的场景,这种场景一般建议使用 Prometheus,比如常见的中间 Redis、MySQL、Zookeeper 等都支持 Prometheus Exporter 形式暴露指标。
- 比如特殊的网络环境或者其他限制条件下,不能采用 Prometheus pull 形式,但在 Prometheus pushgateway 形式又存在多种弊端的情况下,可以尝试使用 OpenTelemetry push 形式。
但无论如何选择,有点尴尬的就是即使客户端选择了 OpenTelemetry,实际情况 OpenTelemetry Metric 存储往往回到了 Prometheus 上。这也是当前的现状,大多数组织可能会混合使用这两种标准:用于基础设施监控的 Prometheus,利用更成熟的集成生态系统从数百个组件中提取指标,以及用于已开发服务的 OpenTelemetry。
下一章节,我们重点介绍如何将 OpenTelemetry 指标接入到 Prometheus。
03 如何将 OpenTelemetryMetric 接入 Prometheus
3.1 原理介绍
将 OpenTelemetry 指标接入 Prometheus 的核心要素是需要将 OpenTelemetry 指标转化为 Prometheus 指标格式,所以无论是 OpenTelemetry Collector 形式,还是直接在客户端采用 Prometheus exporter bridge 形式,本质上就是进行数据格式转化。
1)OpenTelemetry 官网
下图为 OpenTelemetry 官网文档建议的 OpenTelemetry 指标到 Prometheus 指标的转化路径。
详细参考文档:otlp-metric-points-to-prometheus[8]
2)OpenTelemetry Collector的实现
而 OpenTelemetry Collector 具体实现与官网的建议实现略有不同,详细实现参考 prometheus-client-bridge[9]。具体的转化关系如下图所示,与 OpenTelemetry 官方文档的核心差异在于如下几点。
- OpenTelemetry 官方文档建议将 monotonic & delta Sum 转化为 Prometheus cumulative 的 Counter;而 OpenTelemetry Collector 的 Prometheus bridge 中将该中类型直接转化为 Prometheus 的 gauge。
- OpenTelemetry 官方文档建议 Histogram delta 转化为 cumulative,从而转化为 Prometheus Histogram;虽然 OpenTelemetry Collector 代码中没有区分 Histogram 的 cumulative 及 delta,实际仅支持 cumulative 型 Histogram 转化为 Prometheus Histogram。
- OpenTelemetry 官方文档建议 Exponential Histogram cumulative 转化为 Prometheus 的 Histograms,而在 OpenTelemetry Collector 中 Exponential Histogram cumulative & Exponential Histogram delta 都做丢弃处理。
3.2 传统接入方式
以 Java 应用为例,当前 Java 应用(其他多语言应用类似) 将 OpenTelemetry 指标接入到 Prometheus 有如下几种常见的方式,当然还有其他解决方案如使用 micrometer,这里就不赘述。
方式 1:应用暴露 OpenTelemetry 指标,通过 gRpc/HTTP 上报到 OpenTelemetry Collector,OpenTelemetry Collector 中以 Prometheus exporter 形式暴露 Prometheus 指标,Prometheus 可通过静态 Job 形式进行采集(或者其他服务发现的形式)。
详细的代码参考 Demo:https://github.com/OuyangKevin/opentelemetry-metric-java-demo/blob/main/metric-http/README.md
方式 2:应用暴露 OpenTelemetry 指标通过,gRpc/HTTP 上报到 OpenTelemetry Collector,OpenTelemetry Collector 中直接将指标转化为 Prometheus 指标格式,然后通过 remote write 形式写入远端 Prometheus。
详细的代码参考 Demo:https://github.com/OuyangKevin/opentelemetry-metric-java-demo/blob/main/metric-grpc/README.md
Ps:老版本 OpenTelemetry Collector 不支持 remote write 形式,如需验证请使用最新版本。
方式3:Java 应用引入 OpenTelemetry-exporter-prometheus lib,将 OpenTelemetry 指标直接以 Prometheus Exporter 形式进行暴露,Prometheus 直接以拉的形式进行采集。
如下为初始化的核心代码:
详细代码参考 Demo:https://github.com/OuyangKevin/opentelemetry-metric-java-demo/blob/main/metric-prometheus/README.md
PrometheusHttpServer prometheusHttpServer = PrometheusHttpServer.builder().setPort(1000).build(); Resource resource = Resource.getDefault().toBuilder().put(SERVICE_NAME, "opentelemetry-metric-demo-http").put(SERVICE_VERSION, "0.0.1").build(); SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder() .registerMetricReader(prometheusHttpServer) .setResource(resource) .build(); LongCounter longCounter = sdkMeterProvider .get("aliyun") .counterBuilder("ping_long_counter") .setDescription("ot http demo long_counter") .setUnit("ms") .build();
04 可观测监控 Prometheus 版零距离拥抱 OpenTelemetry
目前,阿里云可观测监控 Prometheus 版已全面支持 OpenTelemetry 指标接入,用户仅需修改相关的上报地址以及鉴权配置即可无缝接入可观测监控 Prometheus 版,欢迎前往 Prometheus 控制台[10]进行体验。
详细的使用限制说明参考 OpenTelemetry 指标上报地址使用说明:https://help.aliyun.com/zh/prometheus/user-guide/instructions-for-using-the-reporting-address-of-opentelemetry-indicator/
4.1 准备工作
在接入可观测监控 Prometheus 版之前,请您确保已经创建如下类型的 Prometheus 实例,具体操作请参考:
- Prometheus 实例 for 容器服务[11]
- Prometheus 实例 for 通用[12]
- Prometheus 实例 for ECS[13]
- Prometheus 实例 for 云服务[14]
4.2 获取 OpenTelemetry 指标上报地址
1. 登录 Prometheus 控制台。
2. 在左侧导航栏单击监控列表,进入可观测监控 Prometheus 版的实例列表页面。
3. 在页面顶部选择 Prometheus 实例所在的地域,并在目标 Prometheus 实例右侧的操作列单击设置。
4. 在设置页签上,根据需求复制公网或内网的 OpenTelemetry 指标上报地址(当前仅支持 HTTP 形式,暂不支持使用 gRPC)。
4.3 应用程序改造
如下以 Java 语言为例,在 SpringBoot 项目中进行 OpenTelemetry 指标的埋点。
添加依赖
<properties> <java.version>1.8</java.version> <org.springframework.boot.version>2.7.17</org.springframework.boot.version> <org.projectlombok.version>1.18.0</org.projectlombok.version> <opentelemetry.version>1.31.0</opentelemetry.version> </properties> <dependencies> <!-- springboot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${org.springframework.boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>${org.springframework.boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>${org.springframework.boot.version}</version> </dependency> <!-- opentelemetry --> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-api</artifactId> <version>${opentelemetry.version}</version> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk</artifactId> <version>${opentelemetry.version}</version> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-otlp</artifactId> <version>${opentelemetry.version}</version> </dependency> </dependencies>
初始化 OpenTelemetry Bean
修改 OtlpHttpMetricExporterBuilder 中的 Endpoint 参数,将其替换为上文获取的 OpenTelemetry 指标上报地址,即可将应用的 OpenTelemetry 指标接入可观测监控 Prometheus 版。
详细请参考示例 Demo:https://github.com/OuyangKevin/opentelemetry-metric-java-demo/blob/main/metric-http/README.md
@Bean public OpenTelemetry openTelemetry() { String endpoint = httpMetricConfig.getOetlExporterOtlpEndpoint();//自己修改 Resource resource = Resource.getDefault().toBuilder() .put("service.name", "opentelemetry-metric-demo-http") .put("service.version", "0.0.1").build(); SdkMeterProvider defaultSdkMeterProvider = SdkMeterProvider.builder() .registerMetricReader(PeriodicMetricReader.builder( OtlpHttpMetricExporter.builder() .setEndpoint(endpoint) .build()) .setInterval(15, TimeUnit.SECONDS).build()) .setResource(resource) .build(); OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() .setMeterProvider(defaultSdkMeterProvider) .buildAndRegisterGlobal(); return openTelemetry; }
在初始化 OpenTelemetry bean 时,可以对上报的客户端进行详细地参数设置,具体配置说明如下:
1)OpenTelemetry 相关客户端默认没有开启压缩,建议设置 Compression 参数为 gzip,减少网络传输消耗。
OtlpHttpMetricExporter.builder() .setEndpoint("******") .setCompression("gzip") .build()
2)OpenTelemetry 指标上报可观测监控 Prometheus 版后,若需要针对所有的指标加上前缀,可以设置 Header "metricNamespace",如下所示设置 metricNamespace 为 ot,所有的上报的指标将会加上"ot_"前缀。
OtlpHttpMetricExporter.builder() .setEndpoint("******") .setCompression("gzip") .addHeader("metricNamespace","ot") .build()
3)OpenTelemetry 指标上报到可观测监控 Prometheus 版后,所有的指标默认会带上 OpenTelemetry Scope Lables,如果不希望添加默认的 Scope Lables,可以设置 Header skipGlobalLabel=true,如下所示。
OtlpHttpMetricExporter.builder() .setEndpoint("******") .setCompression("gzip") .addHeader("skipGlobalLabel","true") .build()
定义业务指标,如下样例设置了一个 LongCounter 及 LongHistogram。
@RestController public class MetricController { @Autowired private OpenTelemetry openTelemetry; @GetMapping("/ping") public String ping(){ long startTime = System.currentTimeMillis(); Meter meter = openTelemetry.getMeter("io_opentelemetry_metric_ping"); LongCounter longCounter = meter.counterBuilder("ping_long_counter") .setDescription("ot http demo long_counter") .setUnit("ms") .build(); LongHistogram longHistogram= meter.histogramBuilder("ping_long_histogram") .ofLongs() .setDescription("ot http demo histogram") .setUnit("ms") .build(); try{ longCounter.add(1,Attributes.of(AttributeKey.stringKey("regionId"),"cn-hangzhou")); TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10)); }catch (Throwable e){ }finally { longHistogram.record(System.currentTimeMillis() - startTime); } return "ping success!"; } }
关于 OpenTelemetry 指标模型与阿里云可观测监控 Prometheus 版指标模型转化的映射关系如下。由于 Prometheus 对于 delta Histogram 支持需要转化为 cumulative Histogram,所以可观测监控 Prometheus 版暂时不支持 OpenTelemetry delta Histogram 转化,建议在该场景下使用 OpenTelemetry cumulative Histogram。
4.4 Grafana 中查看监控数据
- 登录可观测可视化 Grafana 版控制台[15]。
- 在工作区管理页面单击 Grafana 共享版,然后选择对应的公网地址单击登录。
- 在左侧导航栏单击图标,然后 Explore 右侧选择对应的 Datasource。
- 配置自己的大盘与告警,如下为客户利用 OpenTelemetry 指标配置的大盘。
05 阿里云可观测监控 Prometheus 版 VS 开源 Prometheus
阿里云可观测监控 Prometheus 版全面对接开源 Prometheus 生态,支持类型丰富的组件监控,覆盖绝大部分开源基础设施软件指标采集能力。提供多种开箱即用的预置监控大盘,并集成丰富的 Kubernetes 基础监控以及常用服务预设看板,且提供全面托管的 Prometheus 服务。阿里云可观测监控 Prometheus 版的优势可以归纳为“开箱即用”、“低成本”、“开源兼容”、“数据规模无上限”、“高性能”、“高可用性”。
06 产品计费
目前,可观测监控 Prometheus 版已开启全新按数据写入量计费模式,并每月提供 50GB 免费额度。每日上报 1000 万指标,指标数据存储 90 天,仅需 37 元。点击阅读原文,了解更多产品详情。
参考文档:
https://www.timescale.com/blog/prometheus-vs-opentelemetry-metrics-a-complete-guide/
https://opentelemetry.io/docs/specs/otel/metrics/data-model/
https://opentelemetry.io/docs/specs/otel/compatibility/prometheus_and_openmetrics/
https://github.com/open-telemetry/opentelemetry-collector-contrib
相关链接:
[1] Instruments
https://opentelemetry.io/docs/specs/otel/metrics/api/#instrument
[2] Sums
[3] monotonic
https://en.wikipedia.org/wiki/Monotonic_function
[4] examplars
https://opentelemetry.io/docs/specs/otel/metrics/data-model/#exemplars
[5] Gauge
[6] Histogram
[7] 介绍
https://prometheus.io/docs/concepts/metric_types/
[8] otlp-metric-points-to-prometheus
[9] prometheus-client-bridge
[10] Prometheus 控制台
[11] Prometheus 实例 for 容器服务
[12] Prometheus 实例 for 通用
https://help.aliyun.com/zh/prometheus/user-guide/create-a-prometheus-instance-for-remote-storage
[13] Prometheus 实例 for ECS
[14] Prometheus 实例 for 云服务
[15] 可观测可视化 Grafana 版控制台
https://account.aliyun.com/login/login.htm?oauth_callback=/#/grafana/workspace/