关于部署检测,以及你的 span 必须携带的信息
sigiro 只读取一个属性来判断部署——span 资源上的 service.version。本页说明一个版本号如何变成部署事件、sigiro 如何挑选可疑部署,以及当某个服务完全不发送版本号时你会得到什么。
诊断结果可以点出一个可疑部署:incident ~14:30 on service ‘checkout’: … ; suspect deploy: version 1.4.2 first seen at 14:29。这是一个很强的论断, 读者有理由追问它的依据从何而来。
它来自一个属性、一个位置。本页点明这个属性,说明 sigiro 如何把部署与稳定状态区分开, 解释一次部署如何成为那个可疑部署,并说明该属性缺失时你会得到什么。最后一部分最为重要, 因为这种缺失是无声的。
一个属性,一个位置
sigiro 从 span 的资源属性 中读取 service.version。
这就是全部来源。sigiro 不会为部署读取任何其他字段:
| 不用于部署判断的字段 | 为什么不用 |
|---|---|
deployment.environment、deployment.* |
没有任何查询读取它们 |
git.commit.sha、vcs.* |
没有任何查询读取它们 |
日志、指标或 profile 上的 service.version |
sigiro 只扫描 sigiro_spans |
| CI webhook、部署 API、发布信息流 | 除 OTLP 之外,sigiro 没有任何接入通道 |
| Kubernetes API server | sigiro 从不连接它 |
资源属性通过 OTLP 到达,并原样落入 sigiro_spans 表的 resource_attributes 列,
存为一个 JSON 字符串。有三个资源属性在写入时拥有各自独立的列——service.name、
service.namespace 和 service.instance.id。service.version 不在其中,
因此 sigiro 会在每次查询时从 JSON 中把它读出来。
“只看 span”带来两个后果。一个导出日志但不导出 span 的服务不会产生任何部署事件, 无论它的日志有多好。一个只在部分进程中导出 span 的服务,只有当被埋点的那个进程 以新版本重启时,才会报告一次部署。
版本值本身对 sigiro 来说是不透明的。semver 字符串、git SHA、构建号和日期的作用完全相同, 因为 sigiro 只比较两个字符串是否相等,从不解析其中任何一个。sigiro 不会从版本号推导出任何顺序, 所以它无法区分升级和回滚。
部署就是基线期没有出现过的版本
sigiro 没有可读取的部署时间戳,所以它自己推导一个。部署事件是这样一个 service.version 值:
它出现在诊断时间窗内的某个 span 上,且在基线时间窗内的 任何 span 上都没有出现过。
基线时间窗的长度是诊断时间窗的四倍,并且它的结束点就是诊断时间窗的起点。 默认的诊断时间窗是最近 15 分钟,因此默认基线是它之前的那一小时。
每个部署事件携带:
event_type—— 字面字符串deploydetected_at—— 时间窗内携带该版本的最早 span 时间戳,也就是 sigiro 第一次 看到 该版本的时刻,而不是你的部署开始的时刻description——version 1.4.2 first seen
与基线的比较是其中重要的那一半,它修正了一个真实的失效。如果没有它,“首次看到”就意味着 时间窗的第一行数据,于是每一次对每一个健康服务的诊断都会在窗口起点报告一次部署。 那个事件永远存在,永远在时间上紧邻故障,而读到它的 agent 会把它当成原因。 一条永久的错误线索比没有线索更糟,所以在时间窗开始之前就已在运行的版本算作稳定状态。
由此还直接引出五个后果,每一个都会让某些人意外:
- 恒定的版本号什么也产生不了。 如果每个构建都发布
service.version=1.0.0, sigiro 只会报告一次部署事件,之后再也不会。要让部署可见,就给每次发布一个不同的值。 - 多个新版本产生多个事件。 查询按版本分组,所以一个包含三个新版本的时间窗会返回三个部署事件。
- 回滚会被读作部署。 旧版本在基线时间窗中不存在,因此按此定义它是新的。
- 遥测数据的空档会被读作部署。 如果某个服务在基线时间窗内没有发送任何 span, 基线集合就是空的,于是窗口内的每一个版本都是新的。当服务是新上线的或曾经很安静时, 要谨慎解读窗口起点处的部署。
- 更宽的时间窗会看得更远。 sigiro 从你请求的时间窗推导基线,所以一次 6 小时的诊断 会与之前的 24 小时作比较。
Kubernetes 事件来自日志文本
同一个 events 数组还承载第二种事件类型 k8s_event,它有不同的来源。
sigiro 在时间窗内扫描该服务日志的 body 列,查找五个固定子串:OOMKilled、
Unhealthy、BackOff、FailedScheduling 和 ScalingReplicaSet。匹配区分大小写,
并且是纯粹的子串匹配。description 就是整条日志正文,且 sigiro 每次诊断最多返回 20 条这类事件。
所以这并不是 Kubernetes 集成。sigiro 从不与 API server 通信。
只有当某个组件把集群事件转发进你的 OTLP 日志、并使用与你所诊断服务相同的 service.name 时,
你才会看到这些事件。通常的做法是用一个 collector 监听事件并将其作为日志导出。
当没有任何组件转发它们时,这个数组里只有部署事件。
k8s_event 永远不会成为可疑部署。只有类型为 deploy 的事件才有资格。
一次部署如何成为可疑部署
由两条规则决定,两者都源自检测器自身的 5 分钟分桶。
第一,只有 incident 才会有可疑部署。一个与其他任何偏移都不相关的单一偏移, 仍然只是单信号条目,完全不携带部署字段。位于同一个桶或相邻桶中的两个或更多偏移会合并成一个 incident, 只有这样的条目才会去寻找部署。关于用证据取代仪表盘 解释了偏移为什么会那样关联。
第二,当一次部署所在的桶落在该 incident 自身的桶区间内,或落在第一个偏移之前紧邻的那一个桶中时, 它就符合条件。多出的那个桶是算术结果,而不是容差:一次发生在 14:29 的部署导致了在 14:30 桶中 检测到的偏移,按构造它就是早一个桶。当有多次部署符合条件时,时间上最接近 incident 起点的那一次胜出。
胜出者会出现两次:作为 incident 上的 deploy 对象,以及作为条目摘要末尾的一个子句。
其他所有部署事件都留在该服务的 events 数组中,sigiro 会按时间对其排序,且从不过滤。
所以在比较中落选的部署仍然在响应里,供你查看。
sigiro 只主张时间上的相关性,仅此而已。用 suspect(可疑)这个词是有意的。
服务不设置版本号时会发生什么
直白地说:你得不到任何部署事件,而 sigiro 对此什么也不会告诉你。
查询会丢弃 service.version 为 null 的行,所以一个从不设置该属性的服务不会贡献任何部署事件。
此时 events 数组里只有 Kubernetes 事件,或者干脆是空的。每个 incident 都携带 deploy: null,
摘要结尾也没有可疑部署子句。
没有任何条目会提醒你。sigiro 会针对缺失的 k8s.namespace.name 和 k8s.deployment.name
产生一个 missing_resource_attributes 类型的条目,而那个条目对 service.version 一言不发。
版本号没有对应的机制。这种降级是无声的。
这是响应中唯一一处你不能照字面理解的部分。一个没有部署事件的时间窗,在两种截然不同的情形下
看起来完全一样:没有发生过部署,以及没有任何进程设置 service.version。响应无法区分二者。
有一个查询可以区分它们:
SELECT DISTINCT resource_attributes::JSON->>'service.version' AS version
FROM sigiro_spans
WHERE service_name = 'checkout'
AND timestamp > now() - INTERVAL '24 hours';
按下面的方式解读结果:
- 只有一行,且为
NULL—— 没有任何东西设置该属性。sigiro 永远无法为这个服务报告部署。 - 只有一行且带有值,并且多天不变 —— 有东西设置了该属性,但值从不改变。sigiro 在第一次看到 这个值时报告过一次部署,此后不再报告。
- 有多行 —— 部署检测对这个服务是有效的。
在你服务的 OpenTelemetry 资源上设置该属性,并为每次发布赋予一个不同的值。 用 agent 为你的代码埋点 涵盖资源本身的配置。
继续阅读
- 关于用证据取代仪表盘 —— 一个偏移如何成为 incident, 以及为什么每一行都携带一条查询
- 关于这些表,以及它们各自描述哪台机器 ——
resource_attributes存放在哪里,以及哪张表回答哪个问题 - API 参考 ——
DerivedEvent与Incident字段的完整说明