Prometheus 远程写入规范

  • 版本:1.0
  • 状态:已发布
  • 日期:2023 年 4 月

本文档旨在定义和标准化现有已广泛且自然采用的协议的 API、线格式、协议和语义,而非提出任何新内容。

远程写入规范旨在记录 Prometheus 以及与 Prometheus 远程写入兼容的 Agent 如何向 Prometheus 或与 Prometheus 远程写入兼容的 Receiver 发送数据的标准。

本文档中的关键词 "MUST"(必须)、"MUST NOT"(禁止)、"REQUIRED"(需要)、"SHALL"(应)、"SHALL NOT"(不应)、"SHOULD"(应该)、"SHOULD NOT"(不应该)、"RECOMMENDED"(推荐)、"MAY"(可以)和 "OPTIONAL"(可选)应按照 RFC 2119 中的描述进行解释。

注意:本规范的 2.0 版本已可用,请参阅此处

介绍

背景

远程写入协议旨在实现将样本从发送方实时可靠地传播到接收方,且无数据丢失。

远程写入协议被设计为无状态的;严格来说没有消息间的通信。因此,该协议不被认为是“流式”的。要实现流式效果,应通过同一个连接(例如使用 HTTP/1.1 或 HTTP/2)发送多条消息。曾考虑过 gRPC 等“高级”技术,但当时 gRPC 尚未被广泛采用,并且将 gRPC 服务暴露在负载均衡器(例如 AWS EC2 ELB)后的互联网上具有挑战性。

远程写入协议提供了批量处理的机会,例如在单个请求中发送不同时序的多个样本。尽管协议支持在同一请求中发送同一时序的多个样本,但这预计并非常见用法。

远程写入协议不旨在供应用程序使用来将指标推送到与 Prometheus 远程写入兼容的接收方。它的目的是让与 Prometheus 远程写入兼容的发送方抓取(scrape)经过埋点(instrumented)的应用程序或 exporter,并将远程写入消息发送到服务器。

测试套件位于 https://github.com/prometheus/compliance/tree/main/remotewrite/sender

词汇表

为了本文档的目的,必须遵循以下定义:

  • “发送方”(Sender)是指发送 Prometheus 远程写入数据的事物。
  • “接收方”(Receiver)是指接收 Prometheus 远程写入数据的事物。
  • “样本”(Sample)是 (时间戳, 值) 对。
  • “标签”(Label)是 (键, 值) 对。
  • “时序”(Series)是由唯一标签集标识的样本列表。

定义

协议

远程写入协议必须包含具有以下签名的 RPC:

func Send(WriteRequest)

message WriteRequest {
  repeated TimeSeries timeseries = 1;
  // Cortex uses this field to determine the source of the write request.
  // We reserve it to avoid any compatibility issues.
  reserved  2;

  // Prometheus uses this field to send metadata, but this is
  // omitted from v1 of the spec as it is experimental.
  reserved  3;
}

message TimeSeries {
  repeated Label labels   = 1;
  repeated Sample samples = 2;
}

message Label {
  string name  = 1;
  string value = 2;
}

message Sample {
  double value    = 1;
  int64 timestamp = 2;
}

远程写入发送方必须将写入请求编码在 HTTP POST 请求体中,并通过 HTTP 发送到接收方提供的 URL 路径。接收方可以指定任何 HTTP URL 路径来接收指标。

时间戳必须是自 Unix 纪元以来的毫秒数,类型为 int64。值必须是 float64 类型。

HTTP 请求必须发送以下头部:

  • Content-Encoding: snappy
  • Content-Type: application/x-protobuf
  • User-Agent: <发送方名称与版本>
  • X-Prometheus-Remote-Write-Version: 0.1.0

客户端可以允许用户发送自定义 HTTP 头部;它们禁止允许用户配置发送保留头部。更多信息请参阅 https://github.com/prometheus/prometheus/pull/8416

HTTP POST 请求体中的远程写入请求必须使用 Google 的 Snappy 进行压缩。必须使用块格式(block format),禁止使用帧格式(framed format)。

远程写入请求必须使用 Google Protobuf 3 进行编码,并且必须使用上面定义的模式。请注意 Prometheus 的实现使用了 gogoproto 优化 - 对于非 Golang 语言编写的接收方,gogoproto 类型可以替换为行级等效类型。

远程写入接收方返回的响应体应该为空;客户端必须忽略响应体。响应体保留供将来使用。

向后与向前兼容性

协议遵循 语义化版本控制 2.0:任何 1.x 兼容的接收方必须能够读取任何 1.x 兼容的发送方,依此类推。不兼容的变更将导致规范版本升级到 2.x。

proto 格式本身在某些方面具有向前/向后兼容性

  • 从 proto 中移除字段意味着主版本号升级。
  • 添加(可选)字段将是次版本号升级。

协商

  • 发送方必须在头部中发送版本号。
  • 接收方可以在响应头部 ("X-Prometheus-Remote-Write-Version") 中返回它们支持的最高版本号。
  • 希望使用 >1.x 格式发送的发送方必须首先发送一个空的 1.x 请求,并查看响应是否表明接收方支持其他版本。发送方可以使用任何支持的版本。如果响应中没有版本头部,发送方必须假定仅支持 1.x 版本。

标签

每个样本都必须发送完整的标签集。此外,与样本关联的标签集

  • 应该包含一个 __name__ 标签。
  • 禁止包含重复的标签名称。
  • 标签名称必须按字典顺序排序。
  • 禁止包含空的标签名称或值。

发送方必须只发送有效的指标名称、标签名称和标签值。

  • 指标名称必须符合正则表达式 [a-zA-Z_:]([a-zA-Z0-9_:])*
  • 标签名称必须符合正则表达式 [a-zA-Z_]([a-zA-Z0-9_])*
  • 标签值可以是任何 UTF-8 字符序列。

接收方可以对标签的数量和长度施加限制,但这将是接收方特定的,并且不在本文档的范围之内。

以 "__" 开头的标签名称保留供系统使用,不应该被使用,请参阅 Prometheus 数据模型

远程写入接收方可以接收写入请求中包含的有效样本,即使该请求也包含无效样本。对于包含任何无效样本的写入请求,接收方必须返回 HTTP 400 状态码("Bad Request")。接收方应该在响应体中提供人类可读的错误消息。发送方禁止尝试解释错误消息,并且应该原样记录它。

顺序

与 Prometheus 远程写入兼容的发送方必须按时间戳顺序发送任何给定序列的样本。与 Prometheus 远程写入兼容的发送方可以并行发送不同序列的多个请求。

重试与退避

与 Prometheus 远程写入兼容的发送方在收到 HTTP 5xx 响应时必须重试写入请求,并且必须使用退避算法来防止压垮服务器。对于 HTTP 2xx 和除 429 之外的 4xx 响应,禁止重试写入请求。对于 HTTP 429 响应,可以重试,这可能导致发送方在服务器无法跟上时“落后”。这样做是为了确保在发生服务器端错误时数据不丢失,并在发生客户端错误时能够取得进展。

与 Prometheus 远程写入兼容的接收方在写入成功时必须响应 HTTP 2xx 状态码。当写入失败且应该重试时,必须响应 HTTP 5xx 状态码。当请求无效、永远不会成功且不应重试时,必须响应 HTTP 4xx 状态码。

陈旧标记

与 Prometheus 远程写入兼容的发送方在不再向某个时序附加数据时,必须发送陈旧标记。

陈旧标记必须通过特殊的 NaN 值 0x7ff0000000000002 来表示。禁止在其他情况下使用此值。

通常,发送方可以使用以下技术检测何时不再向某个时序附加数据:

  1. 使用服务发现检测暴露该时序的目标已消失。
  2. 注意到目标在连续抓取之间不再暴露该时序。
  3. 未能抓取最初暴露该时序的目标。
  4. 跟踪记录规则和告警规则的配置和评估。

不在范围之内

本文档不打算解释构建一个完全兼容 Prometheus 的监控系统所需的所有功能。特别是,以下方面不在本规范的第一个版本范围之内:

"up" 指标 "up" 指标的定义和语义超出了远程写入协议的范围,应单独文档化。

HTTP 路径 HTTP 处理程序的路径可以是任何内容 - 并且必须由发送方提供。通常我们期望在配置中指定完整的 URL。

持久性 建议与 Prometheus 远程写入兼容的发送方在接收方发生中断时,应持久化缓冲样本数据。

认证与加密 由于远程写入使用 HTTP,我们将认证与加密视为传输层问题。发送方和接收方应支持所有常见的(Basic auth、TLS 等),并可自由添加潜在的自定义认证选项。不应假定 Prometheus 远程写入发送方和未来的 agent 支持自定义认证,但我们将努力在可行的情况下支持常见和广泛使用的认证协议。

远程读取 这是一个单独的接口,已经经历了一些迭代,且使用不太广泛。

分片 Prometheus 当前用于远程写入并行化的分片方案非常偏向实现细节,不属于规范的一部分。当发送方实现并行化时,必须保持每个时序内的样本顺序。

回填 规范并未限制可以推送多久以前的时序数据,但服务器/实现可能存在特定限制。

限制 对标签数量和长度、批量大小等的限制超出了本文档的范围,但预计实现会施加合理的限制。

基于推送的 Prometheus 应用程序将指标推送到与 Prometheus 远程写入兼容的接收方并非本系统的设计目标,应在单独的文档中探讨。

标签 每个时序可以包含 "job" 和/或 "instance" 标签,因为这些通常由发送方的服务发现添加。这些不是强制性的。

未来计划

本节包含一些推测性计划,这些计划不被视为协议规范的一部分,但在此提及以供参考。

事务性 Prometheus 的目标是具有“事务性”——即永远不会向查询暴露部分抓取的目标。我们打算对远程写入做同样的事情——例如,将来我们希望将远程写入与抓取“对齐”,也许将单个抓取的所有样本、元数据和样本示例在一个远程写入请求中发送。这尚待设计。

元数据和样本示例 如上所述,我们还随抓取的样本一起发送元数据(类型信息、帮助文本)和样本示例。我们计划将其打包在单个远程写入请求中——未来版本的规范可能会对此作出要求。Prometheus 目前对发送元数据和样本示例具有实验性支持。

优化 我们希望研究各种优化方法,通过消除标签名称和值的重复来减小消息大小。

兼容的发送方和接收方

本规范旨在描述以下组件如何交互(截至 2023 年 4 月):

常见问题

你们为什么没有使用 gRPC? 有趣的是,我们最初使用了 gRPC,但在 2016 年改用基于 HTTP 的 Protos,因为当时很难让它们通过 ELB:https://github.com/prometheus/prometheus/issues/1982

为什么不使用流式 Protobuf 消息? 如果使用持久的 HTTP/1.1 连接,它们与流式传输非常接近……当然头部必须重新发送,但这确实比建立新的 TCP 连接成本更低。

为什么我们按顺序发送样本? 有序约束源于我们在 Prometheus 中用于时序数据编码的方式,其实现是只追加。可以通过缓冲样本并在编码前重新排序来移除此约束。我们可以在未来的协议版本中对此进行研究。

在有序约束下如何并行化请求? 样本必须是针对给定序列有序的。远程写入请求可以并行发送,只要它们是针对不同序列的。在 Prometheus 中,我们按标签将样本分片到不同的队列中,然后在每个队列中按顺序进行写入。这保证了同一序列的样本按顺序发送,但不同序列的样本可以并行发送——并且在不同序列之间可能“乱序”。

我们认为这是必要的,因为即使接收方可以支持乱序样本,我们也不能让 agent 乱序发送,因为它们将永远无法发送到 Prometheus、Cortex 和 Thanos。我们这样做是为了确保生态系统的完整性,并防止社区混淆或分裂成“可以写入 Prometheus 的 prometheus-agent”和“不能写入的 agent”。

本文档是开源的。请通过提交 issue 或 pull request 帮助改进它。