关于 synthetic-monitoring-checks
面向 Grafana Synthetic Monitoring 的 k6 检查编写技能,用探测点验证服务可用性和用户旅程。它区分可靠性监控与负载测试,并指导选择合适的检查类型。
主要功能
- 01选择 HTTP、协议、MultiHTTP、脚本或浏览器检查
- 02遵守 Synthetic Monitoring 执行与超时限制
- 03编写确定性检查并添加断言
- 04部署前在本地验证脚本
- 05将 API 描述转换为用户旅程检查
适用场景
- 01从选定探测点监控 API 用户旅程
- 02无需维护脚本即可检查静态端点
- 03为 JavaScript 渲染的用户旅程创建浏览器检查
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ grafana/ skills --skill synthetic-monitoring-checks安装后在 Claude Code 中输入/synthetic-monitoring-checks确认已加载。
附带文件references/api-and-terraform.md
详细介绍
合成监控检查编写
文档:https://grafana.com/docs/grafana-cloud/testing/synthetic-monitoring/ Grafana Cloud Testing 的综合入口(SM + k6 Cloud + Faro):
testing技能。
可靠性监控,而非负载测试
合成监控(SM)将 k6 用作可靠性/可用性引擎:每次检查执行都会从每个选定的探测点位置,按固定计划运行一次迭代、一个 VU。成功意味着“此刻从该区域出发,用户旅程能够正常运行”——在客户发现故障之前将其检测出来。
不要套用负载测试的惯用做法。这里没有要逐步增加的 VU,没有 stages、负载配置文件、持续/压力/峰值测试阶段,也没有针对汇总流量设置的 thresholds。术语应使用:检查、探测点、执行、正常运行时间、可达性、用户旅程验证——绝不要使用“负载测试”“逐步增加负载”或“VU”。
如果用户实际需要的是负载或性能测试(吞吐量、负载下的延迟、性能拐点),请停止:这属于 Grafana Cloud k6/grafana-k6 插件的 k6 技能,而非合成监控。脚本可以在两个产品之间共用,但目标、选项和定价都不同。
执行模型和限制(编写前请先核对)
| 限制 | 值 |
|---|---|
| 工作负载 | 每次探测点执行运行一次迭代。脚本和 MultiHTTP 检查会强制使用 --vus 1 --iterations 1;浏览器检查则依赖脚本中必需的单个场景。无论哪种情况,都会忽略 vus、duration、stages、iterations——绝不要编写负载模型 |
thresholds | 不支持 |
| 频率 | k6 类检查(脚本、MultiHTTP、浏览器):60–3600 秒。协议检查(HTTP/ping/DNS/TCP/gRPC):1–3600 秒。Traceroute:120–3600 秒 |
| 超时 | 必须 ≤ 频率。k6 类检查:1–180 秒。协议检查:1–60 秒。Traceroute:固定为 30 秒 |
| k6 版本 | 检查在 k6 版本通道上运行(新检查默认使用最新稳定通道;v1.x 自 2026 年 7 月起已弃用)。可通过 UI 下拉菜单或 API/Terraform 中的 channels 为每项检查固定版本 |
| 本地文件 | 不支持 open()、fs、grpc.load()。请将本地模块打包到脚本中;远程 https://jslib.k6.io/... 导入可用 |
| HTTP 请求错误 | SM 使用 --throw 运行 k6:网络级请求失败会抛出异常,并导致执行失败 |
| SM 会遵循的脚本选项 | SM 会设置自己的 CLI 标志,这些标志优先于脚本的 options 对象;仍会生效的选项包括 batch、batch-per-host、discardResponseBodies、httpDebug、insecureSkipTLSVerify、maxRedirects、noConnectionReuse、setupTimeout、systemTags、tags、teardownTimeout、throw、tlsAuth、tlsCipherSuites、tlsVersion、userAgent |
| 浏览器内存 | 公共探测点上的浏览器每个有 1GB RAM——体积过大的页面会因 Target has crashed 而失败 |
| 浏览器脚本格式 | UI 会拒绝打包/压缩后的浏览器脚本(导入验证)——请通过 API 或 Terraform 部署这些脚本 |
执行如何失败(这是 Agent 容易弄错的地方)
probe_success(1/0)是运行状态信号。当脚本抛出未捕获的异常、调用 fail()、k6-testing 的 expect() 断言失败(它在底层调用 k6 的 test.abort())、HTTP 请求在网络层出错(SM 的 --throw),或达到超时时,执行会被标记为失败。
单独的失败 check() 不会导致执行失败——它只会记录 probe_checks_total / probe_check_success_rate 指标。没有阈值时,检查不会影响 k6 的退出状态,而 SM 中的阈值已禁用。
断言模式,按优先顺序排列:
import { expect } from 'https://jslib.k6.io/k6-testing/0.6.1/index.js';
import { check, fail } from 'k6';
// 1. PREFERRED — assertions module. Throws on failure => execution fails,
// with a descriptive error in the check logs.
expect(res.status, 'login should succeed').toEqual(200);
expect(res.json('token')).toBeDefined();
// 2. Soft assertions — run all of them, still fail the execution at the end.
expect.soft(res.headers['Content-Type']).toContain('application/json');
// 3. check() when you also want per-assertion metrics — but pair it with
// fail() or the failure won't affect probe_success/uptime:
check(res, { 'status 200': (r) => r.status === 200 }) ||
fail(`login failed with status ${res.status}`);
为每个断言命名(消息参数 / 检查名称):诊断凌晨 3 点发生的故障时,你会在检查日志和 probe_checks_total 的 check 标签中看到这个名称。
首先选择足够简单的检查类型
这样对客户来说成本更低,也更容易维护。按以下列表逐项选择,匹配后即停止:
- HTTP / ping / DNS / TCP / traceroute / gRPC — 单个静态端点(运行状态、状态码、正文正则表达式、TLS 证书过期时间、记录解析、端口连通性)。无需维护脚本——这些检查通过 blackbox-exporter 探测引擎运行,各类型
target格式的 Terraform 示例见references/api-and-terraform.md。 - MultiHTTP — 一系列 HTTP 请求,支持请求间传递值(
${variable}捕获),但不包含自定义逻辑。注意:MultiHTTP 不会自动验证状态码——请为每个请求定义断言,否则失败不会影响运行状态。 - k6 脚本检查 — 需要实际逻辑的 API 流程:加密 / 签名、条件分支、生成测试数据、WebSockets、由响应驱动的请求串联。
- k6 浏览器检查 — 仅在需要真实浏览器时使用:JS 渲染的用户旅程、表单 / 点击操作、Core Web Vitals。
成本模型(按执行量计费):一次执行是在一个探针上运行一次检查,按运行时长计量,每分钟向上取整。每月费用:
probes × duration_minutes × (43200 / frequency_minutes)。API 测试执行(HTTP、
ping、DNS、TCP、traceroute、MultiHTTP、脚本检查)和浏览器测试执行分别计费——浏览器检查属于高价档。每分钟在 3 个探针上运行一次浏览器检查,每月约有 129,600 次浏览器执行;同一检查每 5 分钟运行一次,则每月约有 25,920 次。选择仍能满足检测时间目标的最长频率,并在用户附近选择 2–3 个探针(多个探针可减少告警抖动;探针并非越多越好)。
编写脚本检查
生成的每个脚本(包括脚本检查和浏览器检查)都应以署名注释作为第 1 行,如下方骨架所示。这样,之后阅读检查的人就能知道它是如何编写的(以及在哪里找到此技能),固定前缀也便于查询由技能编写的检查。Generated by synthetic-monitoring-checks 必须保持原样——只更改时间戳(date -u +%Y-%m-%dT%H:%M:%SZ)。
骨架——包含机密信息和严格失败断言的登录 + API 操作旅程:
// Generated by synthetic-monitoring-checks (https://github.com/grafana/skills) on 2026-07-31T12:00:00Z
import http from 'k6/http';
import { expect } from 'https://jslib.k6.io/k6-testing/0.6.1/index.js';
import secrets from 'k6/secrets';
const BASE = 'https://api.example.com';
export default async function () {
// Secrets are managed in Synthetics > Config > Secrets — never hardcode credentials.
const password = await secrets.get('checkout-monitor-password');
// Step 1: authenticate with a dedicated monitoring account
const login = http.post(
`${BASE}/auth/login`,
JSON.stringify({ user: 'sm-checkout-monitor', password }),
{ headers: { 'Content-Type': 'application/json' } }
);
expect(login.status, 'login should return 200').toEqual(200);
const token = login.json('token');
expect(token, 'auth token should be present').toBeDefined();
// Step 2: exercise the journey and assert the OUTCOME, not just the status
const order = http.post(`${BASE}/orders`, JSON.stringify({ sku: 'TEST-SKU-1', qty: 1 }), {
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
});
expect(order.status, 'order should be created').toEqual(201);
const orderId = order.json('id');
expect(orderId, 'order id should be returned').toBeDefined();
// Step 3: clean up so the check is idempotent against production
// http.url groups metrics for URLs containing unique IDs — without it, every
// execution creates new time series (cardinality + active-series cost).
const del = http.del(http.url`${BASE}/orders/${orderId}`, null, {
headers: { Authorization: `Bearer ${token}` },
});
expect(del.status, 'test order should be cleaned up').toEqual(204);
}
让脚本检查成为优秀监控(而非优秀测试)的规则:
- 确定性:使用固定测试数据(或如上所示,生成后再删除),不依赖一天中的时间或执行顺序。每次执行都必须能在任何时间、从任何探针通过。
- 对生产环境幂等:创建后删除,或使用只读端点。检查会永久运行——泄漏的状态也会不断累积。
- 专用测试账户:绝不使用真实用户的凭据;将账户权限范围缩至最低,将密码存储为 SM 机密,并将该账户排除在分析 / 计费之外。
- 断言每一步——未断言的步骤若发生故障,会在后续步骤中表现为难以理解的失败。
- 稳定的 URL 基数:任何包含 ID 的 URL 都使用
http.url模板字面量。 - 运行时长应远低于超时时长,超时时长应低于检查频率。
根据 OpenAPI 规范(或类似规范)生成检查项
有了 API 描述——OpenAPI/Swagger 规范、GraphQL 模式或 Postman 集合——将其机械地转换为 k6 调用并不难。关键在于选择转换哪些内容:
- 关注用户流程,而非端点。 不要为每个路径都生成一个检查项,也不要生成一个遍历所有路径的检查项——那监控的是规范,而不是服务,而且每增加一个检查项都会增加执行成本。找出 1~3 个一旦失败就意味着“客户受到影响”的流程(身份验证 → 核心操作 → 结果),并为每个流程编写一个脚本化检查项。
- 筛选操作,确保安全。 只有当流程会自行清理(如上所述,创建后再删除)或针对专用测试资源时,才纳入会修改数据的操作(
POST/PUT/DELETE)。规范会把破坏性操作和健康检查端点列在一起——不要仅仅因为这些操作有文档,就在生产环境中执行它们。 - 根据响应模式断言。 规范会明确说明健康响应包含哪些内容——断言必需字段,而不只是状态码:
expect(order.json('id'), 'id required by OrdersResponse schema').toBeDefined()。 - 验证目标 URL。
servers:块(以及 Postman 环境)通常会把 localhost 或 staging 列在最前面——请与用户确认生产环境的基础 URL,并将securitySchemes凭据映射到 SM secrets,绝不要使用规范中内嵌的值。(纯 HTTP/协议检查中的 secret 是近期推出且受功能标志控制的功能——请查阅当前文档;脚本中的secrets.get()路径始终可用。) - 将
format: int64ID 视为字符串。res.json('id')会将其解析为 JS 数字,并在数值超过 2^53 时悄然造成精度损失(雪花算法风格的 ID),因此读取回查 URL 会在每次执行时返回 404,而创建操作看起来却正常。请改为从原始响应正文中提取:const id = (/"id":\s*(\d+)/.exec(res.body) || [])[1];——并且绝不要对它进行算术运算。
完全没有 API 规范?请探查前端:使用浏览器开发者工具打开 Web 应用(或用 curl 请求可能的路由),并捕获它发出的 /api/* XHR 调用——即使有文档记录的后端服务仅支持 gRPC 或仅供内部使用,这也是可监控的 HTTP 接口。
浏览器检查编写
必需的脚手架:导入 k6/browser 并声明 chromium 浏览器类型。UI 会验证这两项。
// Generated by synthetic-monitoring-checks (https://github.com/grafana/skills) on 2026-07-31T12:00:00Z
import { browser } from 'k6/browser';
import { expect } from 'https://jslib.k6.io/k6-testing/0.6.1/index.js';
import secrets from 'k6/secrets';
export const options = {
scenarios: {
ui: {
executor: 'shared-iterations',
options: { browser: { type: 'chromium' } },
},
},
};
export default async function () {
const page = await browser.newPage();
try {
await page.goto('https://shop.example.com/login');
// Prefer role/label/test-id locators over CSS chains — they survive redesigns.
const password = await secrets.get('shop-monitor-password');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: 'Sign in' }).click();
// Assert the JOURNEY OUTCOME with auto-retrying assertions — never sleep().
await expect(page.getByRole('heading', { name: 'Your account' })).toBeVisible();
await page.getByRole('link', { name: 'Orders' }).click();
await expect(page.getByTestId('order-list')).toBeVisible();
} finally {
await page.close();
}
}
浏览器专属规则:
- 定位器:
getByRole/getByLabel/getByTestId(需要时请应用团队添加data-testid)> 文本 > CSS。绝不使用 XPath 或生成的类名。getByTestId默认使用data-testid——为 Cypress 添加了埋点的应用通常使用data-cy;此时请改用page.locator('[data-cy="..."]')。 - 交互前不要手动等待:定位器操作会自动等待元素可见且处于启用状态。不要在
click()/fill()前调用waitFor(),不要使用waitForLoadState(),也不要使用sleep()。 - 自动重试的
expect()(toBeVisible、toBeEnabled等)是断言非交互状态的 等待机制。注意:尽管文本匹配器(toHaveText/toContainText)被列为会重试, 但首次读取到不匹配的内容时就会直接失败——例如,在客户端渲染的应用水合过程中 读到空字符串。断言动态文本时,应定位到该文本并断言其可见:await expect(page.getByText('Order confirmed')).toBeVisible()。 - 断言超时默认值为 5s——客户端渲染的应用首次有意义的渲染通常需要更长时间。
请一次性调高超时并使用配置好的实例:
const expectUi = expect.configure({ timeout: 20000 });。 - 断言结果(已登录标题、订单列表、确认文本)——页面可以正常加载,但流程仍可能 已损坏。
- 使用带有
page.close()的try/finally,确保即使断言抛出异常也会释放浏览器。 - 截图工件并非有文档说明的 SM 功能——不要围绕
page.screenshot()构建失败处理; 请依赖断言消息和检查日志(SM 会将每次执行的日志存储在 Loki 中)。 - Web Vitals(
probe_browser_web_vital_lcp|cls|fcp|inp|ttfb)会自动收集——无需额外代码。
先在本地验证,再部署
SM 脚本就是普通的 k6 脚本——始终先在本地运行:
k6 run script.js # scripted check
K6_BROWSER_HEADLESS=true k6 run browser-check.js # browser check
k6 run --secret-source=mock=checkout-monitor-password=example-password script.js # with secrets
# Many/large secrets: k6 run --secret-source=file=secrets.txt script.js
通过标准 = 退出代码为 0、执行一次迭代,且摘要中没有失败的断言。运行 3–5 次; 本地成功率为 90% 的脚本,每晚从 3 个探针发出的告警都会传到你这里。
然后创建检查(任选一种方式):
- UI:Testing & synthetics → Synthetics → Add new check → k6 scripted / k6 browser → 粘贴脚本 → 选择探针 + 频率 → Test(运行一次但不保存)→ Save。
- gcx:
gcx synthetic-monitoring checks test -f check.yaml是 UI 中 Test 按钮对应的命令行操作——使用真实探针运行一次但不保存。 然后运行gcx synthetic-monitoring checks create -f check.yaml进行保存。两者都接受 纯文本脚本;gcx 会为你进行 base64 编码。使用gcx synthetic-monitoring checks get <ID> -o yaml --decode-script拉取现有检查,作为 起始模板(省略该标志则会得到原始 base64)。 - API 或 Terraform:参见
references/api-and-terraform.md。 关键注意事项:API 中的frequency/timeout单位为毫秒,且settings.scripted.script/settings.browser.script为 base64 编码;Terraform 通过file()接收纯文本脚本。
根据归属方式而非个人偏好选择:如果检查已由 Terraform 管理(漂移检测、多环境、PR
审查),请通过 Terraform 创建和更新——手动编辑其管理的状态会导致下次 apply 时
发生漂移。否则,对于一次性或由 Agent 驱动的编写任务,gcx 或 UI 更快捷。无论采用哪种
方式,gcx 仍是实时检查、临时测试以及状态 / 时间线调查的工具,不受检查由哪种工具
配置的影响。
验证其是否正常工作,并回滚
等待一个频率间隔,然后在 Explore 中使用 Synthetic Monitoring 指标 (Prometheus)数据源:
# 1 from every selected probe = healthy
probe_success{job="checkout-flow"}
# Assertion pass rate per named assertion (scripted/browser)
probe_check_success_rate{job="checkout-flow"}
# Journey duration per probe — confirm it's comfortably under the timeout
probe_script_duration_seconds{job="checkout-flow"}
# Uptime over time (how the SM app computes it)
max by () (max_over_time(probe_success{job="checkout-flow"}[5m]))
首次执行正常的表现:每个探针的 probe_success == 1,所有
probe_check_success_rate 序列均为 1,各探针的持续时间保持稳定,并且检查项的
预构建仪表板(Synthetics → 检查项 → View dashboard)会显示每次执行的日志。
浏览器检查还应显示 probe_browser_web_vital_* 序列。
回滚:将检查项的 enabled: false(通过 UI 开关、gcx synthetic-monitoring checks update、API 更新或 Terraform)设为 false,即可
停止执行,同时保留历史记录;只有在不再需要其配置时才删除检查项。告警:从
alertSensitivity / 默认的 probe_success 告警规则开始 — 告警规则示例请参阅 testing 技能。
常见故障模式
| 症状 | 原因 → 修复 |
|---|---|
| 本地通过,但所有探针都失败 | 目标无法通过公共互联网访问(内部 DNS、VPN、IP 允许列表)。对于内部目标,请使用专用探针,或将探针的出站流量加入允许列表 |
| 本地通过,但部分探针失败 | 地理封锁、区域 CDN/WAF 规则,或针对数据中心 IP 的机器人防护挑战。检查失败项上的 probe 标签;在 WAF 中豁免 SM userAgent 或这些区域 |
检查项在你看来“失败”了,但 probe_success 仍为 1 | 使用了不带 fail()/expect() 的 check() — 失败只会记录为指标。改用 expect() 或 check(...) || fail(...) |
| 浏览器检查因定位器超时而间歇性失败 | 选择器不稳定或动画时序问题。改用 getByRole/getByTestId,使用会自动重试的 expect() 断言,并移除手动等待 |
toBeVisible 报告 Expected: visible / Received: hidden,但元素显然可见 | 定位器匹配了多个元素(严格模式)— 错误消息具有误导性。收紧选择器,或使用 .first() |
| 创建成功,但每次执行读取时都返回 404 | ID 超过 Number.MAX_SAFE_INTEGER(2^53),且 res.json() 将其静默舍入了 — 将原始响应正文中的 int64 ID 提取为字符串(参见 OpenAPI 部分) |
secrets.get() 失败 | 密钥名称不匹配(名称必须完全一致,≤253 个字符,只能包含字母、数字、-、_)、密钥已删除(重新创建前检查都会失败),或编辑用户缺少 Admin/Editor 角色或“Checks writer”权限 |
| 执行超时,但操作流程本身正常 | 操作流程的超时时间过短(最长 180 秒)— 延长超时时间;或者脚本在每次迭代中执行了无界操作。还需确认超时时间 < 频率 |
浏览器检查日志中出现 Target has crashed | 页面超出了探针浏览器的 1GB 内存限制 — 精简操作流程、屏蔽占用资源较多的第三方资源,或改用内存更大的专用探针 |
| UI 拒绝浏览器脚本 | 打包/压缩后的脚本未通过 UI 的导入验证 — 改用 API 或 Terraform 创建 |
| 添加检查项后指标/费用激增 | URL 中的唯一 ID 导致每次执行都生成新的时间序列 — 使用 http.url,并根据上文的成本公式检查频率 × 探针数量 |
参考资料
references/api-and-terraform.md— SM API 身份验证 + 检查项 CRUD 请求负载(脚本、浏览器、MultiHTTP),以及适用于所有检查项类型(包括协议检查:HTTP、ping、DNS、TCP、traceroute、gRPC)的 Terraform 示例