OneUptime 与 PagerDuty 集成实战:基于 Workflow 实现事件触发、去重与自动解决
【免费下载链接】oneuptimeComplete open-source monitoring and observability platform.项目地址: https://gitcode.com/GitHub_Trending/on/oneuptime
本篇技术指南以 OneUptime 的 PagerDuty 集成为主线,讲解如何借助 OneUptime 内置的Workflow(工作流)引擎,在 OneUptime 事件(Incident)创建时自动向 PagerDuty 的 Events API v2 投递trigger事件,并在事件解决时投递resolve事件实现全自动联动。读完本文,你将掌握 PagerDuty Integration Key(routing key)的保存方式、dedup_key去重与回关原理、严重级别映射方案,以及出站/入站双向集成与常见故障排查方法。
集成概览:出站方向与 Workflow 机制
这条集成是**出站(outbound)**方向的:OneUptime 作为事件的产生方,调用 PagerDuty 的 Events API v2(端点https://events.pagerduty.com/v2/enqueue),把 OneUptime 内部的事件状态同步到 PagerDuty。它并不依赖任何内置连接器,而是完全由 OneUptime 的Workflow引擎拼装而成:一个Hændelse → On Create(事件创建)触发器,加上一个API 组件。
整体数据流可以概括为:
OneUptime Incident → On Create ──► API component (POST /v2/enqueue) ──► PagerDuty incident从源码角度看,Workflow 的组件注册表(Common/Server/Types/Workflow/Components/Index.ts)动态注册了incident-on-create、incident-on-update等数据库触发器组件,同时注册了ApiPost、IfElse等普通组件。这意味着“事件创建时触发 → 条件判断 → HTTP POST”这套链路全部由图上的组件拼接实现,而不是硬编码的 PagerDuty 专用代码。
注意:OneUptime 自身内置了值班计划(On-Call)与升级策略(Escalation),详见 On Call 文档。只有当你的组织确实希望事件同时进入 PagerDuty 由其接管调度时,才需要这条集成。
前置条件
在开始配置前,需要准备两样东西:
- 一个 PagerDuty 服务,且其集成类型必须是 Events API v2。在 PagerDuty 控制台按
Service → Integrations → Add integration → Events API v2添加,然后复制Integration Key(在 PagerDuty 中也称为routing key)。注意区分 v2 与已废弃的 v1:后者会导致后续请求返回invalid routing key。 - 一个可以创建 Workflow 的 OneUptime 项目。Workflow 功能位于侧边栏「Arbejdsgange / Workflows」。
先理解 Workflow 的变量与模板语法
由于整个集成的请求体全部由模板引用({{...}})拼装而成,先弄清变量体系能让后续步骤少踩坑。依据 Workflow 变量文档 与模板语法解析实现(Common/Types/Workflow/TemplateSyntax.ts),存在三类可引用对象:
| 类型 | 语法 | 说明 |
|---|---|---|
| 全局变量(项目级) | {{global.variables.NAME}} | 项目内所有 Workflow 共用,适合存放 API Key |
| 本地变量(工作流级) | {{local.variables.NAME}} | 仅对当前 Workflow 有效 |
| 组件输出 | {{local.components.COMPONENT_ID.returnValues.FIELD_ID}} | 上游触发器/组件在运行期产生的数据 |
例如事件触发器(incident-on-create-1)会把事件记录以model返回值暴露出来,于是事件的标题就是{{local.components.incident-on-create-1.returnValues.model.title}}。变量名是大小写敏感的,且{{ }}内部不允许出现多余空格——{{ global.variables.X }}与{{global.variables.X}}是两个不同的查找,前者不会被解析。
值得特别强调的一点(也是 Workflow 变量文档中明确的陷阱):无法解析的引用不会被清空,而是原样发送。如果{{...}}中的路径拼错(比如把incident-on-create-1写成incident-on-create),运行时不会报错,占位符会被当作字面文本 POST 到 PagerDuty,而工作流日志仍会显示成功。这正是下文每一步都要求“在日志中确认 202 响应”的原因。
步骤一:把 routing key 保存为全局变量
- 打开Arbejdsgange(Workflows)→ Globale variabler(全局变量)→ Opret(创建)。
- 命名为
PAGERDUTY_ROUTING_KEY,粘贴第 1 步复制的 Integration Key,并开启Is Secret(保密)开关。
根据 Workflow 变量文档,全局变量拥有四个属性:Navn(名称)、Beskrivelse(描述)、Hemmelighed(保密)、Indhold(内容)。其中名称至少要两个字符、不含空格,且仅允许字母、数字、连字符与下划线(对应模板代码中的命名约束/^[A-Za-z0-9_-]+$/,见 Common/Types/Workflow/Templates.ts)。PAGERDUTY_ROUTING_KEY这种UPPER_SNAKE_CASE命名方式正是推荐习惯。
开启Is Secret后,该值会被从运行日志与步骤追踪(Step Trace)中清除,防止密钥泄露给项目内其他成员(注意:它并不会在数据库中以加密形式存储,保密仅作用于日志脱敏)。之后在 API 组件的 Headers 或 Body 中引用时,就写成:
{{global.variables.PAGERDUTY_ROUTING_KEY}}步骤二:构建"触发"工作流(Incident → PagerDuty)
打开Arbejdsgange → Opret arbejdsgang(创建工作流),命名为
Incidents → PagerDuty,进入**Bygger(构建器)**画布。添加一个Hændelse(事件)触发器,模式设为On Create,并将其重命名为
Incident(该名称将出现在local.components引用路径中)。添加一个API 组件,与触发器连线。按源码中
ApiPost组件的元数据定义(Common/Types/Workflow/Components/API.ts),该组件要求以下参数:Method(方法):
POSTURL:
https://events.pagerduty.com/v2/enqueueHeaders(请求头):
Content-Type: application/json源码将该字段标记为
isSensitive: true(高级选项)。原因在于:请求头通常是填写Authorization之类凭证的地方,如果直接用字面量填写,解析后的值会原样写入 WorkflowLog。只有来自标记为 secret 的变量的值才会被自动脱敏——所以把密钥放进全局变量PAGERDUTY_ROUTING_KEY是必须的,而不是直接写在 Header 或 Body 里。Body(请求体),完整 JSON 如下:
{ "routing_key": "{{global.variables.PAGERDUTY_ROUTING_KEY}}", "event_action": "trigger", "dedup_key": "oneuptime-{{local.components.incident-on-create-1.returnValues.model._id}}", "payload": { "summary": "{{local.components.incident-on-create-1.returnValues.model.title}}", "source": "OneUptime", "severity": "critical", "custom_details": { "description": "{{local.components.incident-on-create-1.returnValues.model.description}}" } } }这里的引用写法是完整形式(由构建器的值选择器自动插入)。事件触发器返回的唯一返回值是
model,可继续向下钻取_id、title、description、currentIncidentState.name、incidentSeverity.name等字段——这些字段与内置模板INCIDENT_SELECT的投影一致(见 Common/Types/Workflow/Templates.ts),保证运行期一定能取到。
保存并激活,然后创建一条测试事件进行验证。若在工作流日志中看到 HTTP
202响应,说明 PagerDuty 已接受该事件。
关于dedup_key:这是整个集成的关键
dedup_key把这条 PagerDuty 事件与 OneUptime 事件绑定起来,是后续resolve调用能够“对上号”的依据。使用 OneUptime 事件自身的_id作为后缀(oneuptime-<id>),可以保证 key 唯一且可预测:同一事件无论触发多少次,都对应 PagerDuty 中同一条 incident,不会重复建单。
关于 API 组件的输出
ApiPost组件在运行后会暴露以下返回值(定义于 Common/Types/Workflow/Components/API.ts),这些值可以在后续步骤中继续引用,也用于故障排查:
error:请求失败时的错误信息;response-status:响应状态码(如 202、400);response-headers:响应头;response-body:响应体。
组件还提供Success与Error两个输出端口:请求成功走 Success 分支,失败走 Error 分支。你可以在 Error 分支上再接一个 Log 或 Email 组件,把失败信息({{local.components.api-post-1.returnValues.error}})记录下来,避免静默丢失。
步骤三:在 OneUptime 解决事件时自动解决 PagerDuty 事件(推荐)
在同一个工作流里再加一个事件触发器?不行——一个工作流只能有一个触发器。正确做法是新建第二个工作流Resolve PagerDuty:
使用Hændelse → On Update触发器。
添加Betingelser(条件)组件(即 If/Else),判断事件是否已经解决:将
{{local.components.incident-on-update-1.returnValues.model.currentIncidentState.name}}与你的“已解决”状态名称做比较。根据源码定义(Common/Types/Workflow/Components/Condition.ts),If/Else 支持==、!=、>、>=、<、<=、contains、does not contain、starts with、ends with等操作符,并输出Yes/No两个分支端口。从Yes分支连接一个新的API 组件,向 PagerDuty 发送相同的
dedup_key,且event_action设为resolve:{ "routing_key": "{{global.variables.PAGERDUTY_ROUTING_KEY}}", "event_action": "resolve", "dedup_key": "oneuptime-{{local.components.incident-on-update-1.returnValues.model._id}}" }
PagerDuty 收到后会用dedup_key匹配原始事件并自动关闭它。dedup_key必须与触发调用完全一致(包括大小写与前后缀),这是 resolve 生效的唯一匹配依据。
需要说明的是,On Update触发器还支持listen-on字段来监听特定字段的变化(内置模板incident-state-changed-slack就是监听currentIncidentStateId的示例,见 Common/Types/Workflow/Templates.ts)。若希望只在状态字段变化时才触发,可在触发器中把监听范围收窄到该字段,减少无关运行。
严重级别映射(可选)
PagerDuty 的severity字段只接受四档取值:critical、error、warning、info。如果你的 OneUptime 事件严重级别(incidentSeverity.name)与 PagerDuty 的档位不一致,可以在API 组件之前插入多个Betingelser(If/Else)分支,对{{Incident.incidentSeverity.name}}进行逐一判断,每个分支发送不同的请求体:
- 若 OneUptime 严重级别为
Critical→ PagerDutyseverity: "critical"; - 若为
High→"error"; - 若为
Medium→"warning"; - 若为
Low→"info"。
将各分支分别连到对应的 API 组件(或先在一个 JavaScript 组件里做映射,再统一发送),即可实现自定义映射规则。完整引用形式可参考变量文档中的写法{{local.components.incident-on-create-1.returnValues.model.incidentSeverity.name}}(Workflow 变量文档)。
入站集成(可选):PagerDuty → OneUptime
上面的步骤都是“OneUptime 出站调用 PagerDuty”。如果你想走反方向——由 PagerDuty 的事件触发创建一条 OneUptime 事件——可以使用 OneUptime 的Webhook 触发器工作流:
- 新建一个使用Webhook 触发器的工作流,复制其 Webhook URL;
- 在 PagerDuty 侧配置一个 V3 webhook(或 Events Orchestration),把事件投递到该 URL;
- 在工作流中使用Opret hændelse(创建事件)组件,从 webhook 的
request-body返回值中提取字段,构造 OneUptime 事件。
这种"外部工具把数据送入 OneUptime"的入站模式,在 集成总览文档 中有更完整的说明。Webhook 组件的返回值为request-body,引用方式形如{{local.components.webhook-1.returnValues.request-body}},可用 If/Else 与创建事件组件组合成完整的入站链路。
故障排查
| 现象 | 原因与对策 |
|---|---|
400且响应为"invalid routing key" | 集成类型不是Events API v2(可能是已废弃的 v1 或其他类型)。回到 PagerDuty 的 Service → Integrations,重新添加 Events API v2 集成并重新复制 key。 |
| resolve 调用没有关闭任何 PagerDuty 事件 | dedup_key与触发调用不一致(拼写、大小写或前后缀有差异)。PagerDuty 完全依赖该 key 匹配,务必逐字符核对。 |
| 工作流日志中没有任何记录 | 确认工作流已激活(Enabled),且触发器模式确实为On Create(或 On Update)。未激活的工作流不会响应事件。 |
| 日志显示成功但 PagerDuty 无事件 | 检查请求体中的{{...}}引用是否全部可解析——无法解析的引用会作为字面文本发送而不报错。打开运行日志的警告行,它会列出所有未被解析的引用。 |
延伸阅读
- Workflow 集成总览 —— 各类集成的模式与认证方式汇总
- Workflow 变量文档 —— 全局/本地变量、组件输出与常见陷阱
- Workflow 组件文档 —— 每个组件可用的输出字段
- Workflow 运行与日志文档 —— 如何查看每次运行中各变量的实际取值
- On Call 文档 —— OneUptime 内置的值班与升级能力
- Opsgenie 集成 —— 同样的思路用于 Opsgenie
【免费下载链接】oneuptimeComplete open-source monitoring and observability platform.项目地址: https://gitcode.com/GitHub_Trending/on/oneuptime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考