Selenium Grid 监控实战指南:基于 Prometheus Exporter 与 Grafana 仪表盘的完整观测方案

原创2026-10-01 22:02:241,326 阅读
文章标签:测试后端云原生容器编排可观测性

Selenium Grid 监控实战指南:基于 Prometheus Exporter 与 Grafana 仪表盘的完整观测方案

导读

本文以 docker-selenium 仓库的 .monitoring 模块为骨架,系统讲解如何为 Selenium Grid 搭建完整的可观测体系:通过一个自定义 Prometheus Exporter 以单次 GraphQL 请求采集网格的容量、节点、会话与队列指标,再用 5 张预置 Grafana 仪表盘做可视化。读完本文,你将掌握 exporter 的构建、运行参数、指标语义与 PromQL 查询技巧,以及 Docker Compose 和 Kubernetes/Helm 两种部署形态下的完整配置方案,可直接照搬到自己的 Selenium Grid 环境中。

监控模块总览

仓库根目录下的 .monitoring/README.md 将该模块划分为两个核心部分:

目录 说明
exporter/ 自定义 Prometheus Exporter —— 构建、运行参数、指标参考、PromQL 示例
dashboards/ Grafana 仪表盘 JSON 文件 —— 面向 Docker Compose 与 Kubernetes/Helm 的配置供给

模块实际目录结构如下:

.monitoring/
├── README.md                          # 模块总览
├── exporter/                          # Go 实现的 Prometheus Exporter
│   ├── main.go                        # 入口:flag 解析、HTTP server 装配
│   ├── graphql.go                     # GraphQL 查询构建、响应解码、时间解析
│   ├── collector.go                   # Prometheus collector 指标采集逻辑
│   └── *_test.go                      # 单元测试
├── dashboards/                        # 5 张 Grafana 仪表盘 JSON
│   ├── selenium-grid-overview.json
│   ├── selenium-node-health.json
│   ├── selenium-sessions.json
│   ├── selenium-queue-capacity.json
│   └── selenium-cross-browser.json
└── config/                            # 开箱即用的监控配置
    ├── prometheus.yml                 # Prometheus 抓取配置
    └── grafana/provisioning/          # Grafana 数据源与仪表盘供给配置

其工作原理一句话概括:Exporter 周期性查询 Selenium Grid 的 GraphQL 端点,把返回的 JSON 转换成 Prometheus 指标格式,供 Prometheus 抓取;Grafana 仪表盘再消费这些指标完成可视化与告警。

Exporter 核心原理:一次抓取 = 一次 GraphQL 请求

与许多需要多次调用 REST API 的采集器不同,该 exporter 在 graphql.go 中定义了一个覆盖全部指标的复合查询,每次抓取仅发起一次 GraphQL POST 请求,同时拿到四类数据:

{
  grid {
    uri
    totalSlots
    nodeCount
    maxSession
    sessionCount
    sessionQueueSize
    version
  }
  nodesInfo {
    nodes {
      id
      uri
      status
      maxSession
      slotCount
      sessionCount
      stereotypes
      version
      osInfo { arch name version }
    }
  }
  sessionsInfo {
    sessionQueueRequests
    sessions {
      id
      capabilities
      startTime
      sessionDurationMillis
      nodeId
      nodeUri
    }
  }
}

一次往返即可覆盖网格摘要(grid)、节点及其槽位(nodesInfo)、活动会话与队列(sessionsInfo)全部维度。对 GraphQL 响应的错误处理也很严谨:HTTP 请求失败、响应体解码失败、GraphQL 层返回 errors、或 data 为空,都会被识别并反映到 selenium_grid_scrape_success 指标上(见 graphql.go)。

值得注意的一个实现细节:Grid 返回的 startTime 是 DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss").withZone(ZoneId.systemDefault()) 格式的本地时间字符串,不带时区偏移。因此 exporter 需要用户通过 -grid-timezone 显式告知 Grid JVM 的时区,才能用 ParseInLocation 正确解析为 Unix 时间戳。仓库测试 graphql_test.go 覆盖了这类时间解析逻辑,保证跨时区部署下会话起止时间依然准确。

构建 Exporter

通过 Makefile 构建

从仓库根目录执行:

make build_exporter

产物位于 bin/selenium-grid-exporter。该目标定义在 Makefile:它会先将模块的 go 指令与本地 Go 工具链对齐(go mod edit -go=...),执行 go mod tidy,再以 -ldflags="-s -w" 去掉调试符号、压缩二进制体积。

直接使用 Go 构建

要求 Go 1.26+ 以及一个可达的 Selenium Grid 4+ 实例(GraphQL 端点需可访问):

cd .monitoring/exporter
go build -ldflags="-s -w" -o ../../bin/selenium-grid-exporter .

运行单元测试

每次升级 Go 工具链或依赖后,可用仓库提供的测试目标验证回归:

make test_exporter

该目标执行 go vet 与带 -race、-covermode=atomic 的全量单元测试(见 Makefile),与 CI 中的 go-test 工作流保持一致。

运行与命令行参数

构建完成后即可启动 exporter:

bin/selenium-grid-exporter \
  -grid-url        http://selenium-hub:4444/graphql \
  -listen-address  :9615 \
  -scrape-timeout  10s \
  -metrics-path    /metrics \
  -grid-timezone   Asia/Ho_Chi_Minh \
  -retain-stopped  5m

Flags 完整参考

Flag 默认值 说明
-grid-url $SE_SERVER_PROTOCOL://localhost:$SE_ROUTER_PORT/$SE_SUB_PATH/graphql Selenium Grid GraphQL 端点;自动从 Hub/Router 的环境变量拼装
-listen-address :9615 Exporter 监听地址
-scrape-timeout 10s 单次 GraphQL 抓取的 HTTP 超时
-metrics-path /metrics 暴露指标的 URL 路径
-grid-timezone $TZ 或 UTC Grid JVM 的时区(用于解析 startTime),如 Asia/Ho_Chi_Minh
-retain-stopped 5m 会话结束后,start/stop 指标保留多长时间
-username $SE_ROUTER_USERNAME 访问 Grid 端点的 Basic Auth 用户名
-password $SE_ROUTER_PASSWORD 访问 Grid 端点的 Basic Auth 密码

这些 flag 的解析逻辑位于 main.go:所有默认值都优先从 Hub/Router 容器环境变量推导。其中 -grid-url 的默认值由 defaultEndpoint() 函数动态拼装——读取 SE_SERVER_PROTOCOL(缺省 http)、SE_ROUTER_PORT(缺省 4444)和 SE_SUB_PATH,并对子路径做了归一化处理(空串、/、/selenium、/selenium/ 都会被规整为不带尾部斜杠的干净前缀),保证在自定义子路径部署时依然能正确命中 GraphQL 端点。

-grid-timezone 在启动时通过 time.LoadLocation 校验,非法时区会导致进程直接报错退出(newServer 返回错误),避免带病运行产生错误的会话时间指标。

部署形态

Docker Compose:独立容器

在 Compose 编排中,exporter 作为独立容器运行,通过网络访问 Hub 或 Router:

selenium-exporter:
  image: selenium/selenium-grid-exporter:latest
  environment:
    SE_ROUTER_USERNAME: admin
    SE_ROUTER_PASSWORD: secret
  command:
    - -grid-url=http://selenium-hub:4444/graphql
    - -grid-timezone=UTC
  ports:
    - "9615:9615"

仓库还提供了开箱即用的抓取配置 .monitoring/config/prometheus.yml,默认 15 秒抓取一次,同时抓取 selenium-hub:9615 与 selenium-router:9615 两个目标;若 Grid 启用了 Basic Auth,只需取消注释 basic_auth 段:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: selenium_grid
    static_configs:
      - targets:
          - "selenium-hub:9615"
          - "selenium-router:9615"
    # Uncomment if the Grid is protected by Basic Auth:
    # basic_auth:
    #   username: admin
    #   password: secret

Kubernetes:内置到 Hub/Router 的边车模式

当 Helm 值 monitoring.enabled: true 时,exporter 二进制会被嵌入 Hub/Router 镜像,随 Selenium server 进程一同在 9615 端口自动启动,并自动创建 ServiceMonitor 让 Prometheus Operator 发现与抓取。相关默认值见 charts/selenium-grid/values.yaml,核心配置项如下:

monitoring:
  enabled: false
  enabledWithExistingAgent: false
  exporter:
    port: 9615          # Hub/Router 容器内 exporter 监听端口
  grafana:
    dashboards:
      enabled: true     # 为 Grafana 供给仪表盘 ConfigMap(依赖 sidecar)
  serviceMonitor:
    enabled: true       # 生成 ServiceMonitor 资源
    path: /metrics
    interval: 30s
    scrapeTimeout: 10s
  podMonitor:
    enabled: true       # 也支持 PodMonitor 作为替代方案
    path: /metrics
    interval: 30s

Prometheus Operator 抓取

在 Helm values 中显式启用 ServiceMonitor 即可:

monitoring:
  enabled: true
  serviceMonitor:
    enabled: true

Metrics 完整参考

Exporter 暴露的指标分为 6 大类,以下全部来自 collector.go 的实际定义。

Exporter 健康

Metric 类型 说明
selenium_grid_scrape_success Gauge 最近一次 GraphQL 抓取成功为 1,失败(网络错误、鉴权错误等)为 0
selenium_grid_scrape_duration_seconds Gauge 最近一次 GraphQL 抓取耗时(秒)

这两个指标无论抓取成败都会输出,是告警 exporter 与 Grid 连通性的基础(见 collector.go 的 Collect 方法)。

网格摘要

Metric 类型 Labels 说明
selenium_grid_info Gauge version 恒为 1;Grid 版本字符串以 label 形式暴露
selenium_grid_total_slots Gauge — 所有节点的槽位总数
selenium_grid_node_count Gauge — 已注册节点数
selenium_grid_max_sessions Gauge — 全网格最大并发会话数
selenium_grid_session_count Gauge — 当前活动会话数
selenium_grid_session_queue_size Gauge — 队列中等待的会话请求数

节点

Metric 类型 Labels 说明
selenium_grid_node_status Gauge node_id, uri, version, os_name, os_arch, os_version 节点可用性:1=UP,0.5=DRAINING,0=DOWN
selenium_grid_node_status_duration_seconds Gauge node_id, status 节点持续处于当前状态的秒数
selenium_grid_node_max_sessions Gauge node_id 节点最大并发会话数
selenium_grid_node_slot_count Gauge node_id 节点槽位总数
selenium_grid_node_session_count Gauge node_id 节点活动会话数

状态映射由 nodeStatusScore 完成(UP:1、DRAINING:0.5、DOWN:0)。collector 会为每个节点维护状态变更时间戳:只有状态真正变化时才重置计时,从而让 status_duration_seconds 准确反映节点在某一状态下的连续驻留时长;当节点从 Grid 注销后,其记录也会被清理(见 collector.go 的 collectNodes)。

节点 stereotype(槽位能力组合)

按节点每个槽位配置的能力组合拆解可用槽位数:

Metric 类型 Labels 说明
selenium_grid_node_stereotype_slots_total Gauge node_id, browser_name, browser_version, platform_name 每类 stereotype 能力的槽位数

示例 —— 某节点注册了 2 个 Chrome 槽位与 1 个 Firefox 槽位:

selenium_grid_node_stereotype_slots_total{node_id="abc",browser_name="chrome",browser_version="124",platform_name="linux"} 2
selenium_grid_node_stereotype_slots_total{node_id="abc",browser_name="firefox",browser_version="125",platform_name="linux"} 1

注意 GraphQL 返回的 stereotypes 字段是 JSON 编码字符串,exporter 会通过 parseStereotypes 解码后逐条展开为独立序列(见 collector.go 与 graphql.go)。

活动会话

Metric 类型 Labels 说明
selenium_grid_sessions_active Gauge browser_name, browser_version, platform_name 按能力聚合的活动会话数
selenium_grid_session_duration_seconds Gauge session_id, node_id, browser_name, browser_version, platform_name, test_name, container_name 每个活动会话的持续时长(秒)

时长优先使用 Grid 上报的 sessionDurationMillis;当 Grid 尚未上报该值时,会回退为「当前时间 − 会话启动时间」的墙钟计算,保证指标始终有值(见 collector.go 的 collectSessions)。

会话生命周期(start / stop 时间)

Metric 类型 Labels 说明
selenium_grid_session_start_seconds Gauge session_id, node_id, browser_name, browser_version, platform_name, test_name, container_name 会话启动的 Unix 时间戳(解析自 Grid 的 startTime 字段)
selenium_grid_session_stop_seconds Gauge session_id, node_id, browser_name, browser_version, platform_name, test_name, container_name 会话结束的 Unix 时间戳(会话终止后的首次抓取时设置)
selenium_grid_sessions_completed_total Counter — 自 exporter 启动以来结束的会话总数(exporter 重启后归零)

工作机制:

  • session_start_seconds 在每次抓取时对所有活动会话输出;
  • session_stop_seconds 在 exporter 检测到某会话从 Grid 响应中消失时设置,并在 -retain-stopped(默认 5m)窗口内持续保留,确保 Prometheus 至少抓取到一次;
  • sessions_completed_total 在每次会话消失时递增,可配合 rate() / increase() 计算吞吐量;
  • -grid-timezone 必须与 Grid 服务器的 JVM 时区一致,因为 Grid 将 startTime 格式化为不带偏移的本地时间字符串。

会话队列

Metric 类型 Labels 说明
selenium_grid_session_queue_requests Gauge browser_name, browser_version, platform_name 按期望能力聚合的排队请求数

输出示例

一个运行中网格的典型 /metrics 输出:

# HELP selenium_grid_total_slots Total number of slots across all nodes.
# TYPE selenium_grid_total_slots gauge
selenium_grid_total_slots 8

# HELP selenium_grid_node_count Number of registered nodes.
# TYPE selenium_grid_node_count gauge
selenium_grid_node_count 2

# HELP selenium_grid_session_count Number of active sessions grid-wide.
# TYPE selenium_grid_session_count gauge
selenium_grid_session_count 3

# HELP selenium_grid_session_queue_size Number of session requests waiting in the queue.
# TYPE selenium_grid_session_queue_size gauge
selenium_grid_session_queue_size 1

# HELP selenium_grid_node_status Node availability: 1=UP, 0.5=DRAINING, 0=DOWN.
# TYPE selenium_grid_node_status gauge
selenium_grid_node_status{node_id="abc",uri="http://node1:5555",version="4.20.0",os_name="Linux",os_arch="amd64",os_version="5.15"} 1
selenium_grid_node_status{node_id="def",uri="http://node2:5555",version="4.20.0",os_name="Linux",os_arch="amd64",os_version="5.15"} 0.5

# HELP selenium_grid_node_stereotype_slots_total Slots available per node stereotype (browser/version/platform combination).
# TYPE selenium_grid_node_stereotype_slots_total gauge
selenium_grid_node_stereotype_slots_total{node_id="abc",browser_name="chrome",browser_version="124",platform_name="linux"} 4
selenium_grid_node_stereotype_slots_total{node_id="def",browser_name="firefox",browser_version="125",platform_name="linux"} 4

# HELP selenium_grid_sessions_active Number of active sessions by capability.
# TYPE selenium_grid_sessions_active gauge
selenium_grid_sessions_active{browser_name="chrome",browser_version="124",platform_name="linux"} 2
selenium_grid_sessions_active{browser_name="firefox",browser_version="125",platform_name="linux"} 1

# HELP selenium_grid_session_duration_seconds Duration of an active session in seconds.
# TYPE selenium_grid_session_duration_seconds gauge
selenium_grid_session_duration_seconds{session_id="s1",node_id="abc",browser_name="chrome",browser_version="124",platform_name="linux"} 42.3

# HELP selenium_grid_session_queue_requests Number of queued session requests by desired capability.
# TYPE selenium_grid_session_queue_requests gauge
selenium_grid_session_queue_requests{browser_name="chrome",browser_version="124",platform_name="linux"} 1

实用 PromQL 查询

容量与负载

排队会话 vs 总容量:

selenium_grid_session_queue_size / selenium_grid_total_slots

按浏览器统计槽位利用率:

selenium_grid_sessions_active
  / on(browser_name, browser_version, platform_name)
  sum by(browser_name, browser_version, platform_name) (selenium_grid_node_stereotype_slots_total)

节点不在 UP 状态:

selenium_grid_node_status < 1

DRAINING 超过 2 分钟的节点:

selenium_grid_node_status_duration_seconds{status="DRAINING"} > 120

每个节点在当前状态的持续时长:

selenium_grid_node_status_duration_seconds

会话追踪

所有活动会话的当前时长:

selenium_grid_session_duration_seconds

某个测试的全部会话:

selenium_grid_session_duration_seconds{test_name="my-login-test"}

某个容器中的所有会话:

selenium_grid_session_duration_seconds{container_name="node-chrome-1"}

指定会话的启动时间:

selenium_grid_session_start_seconds{session_id="<id>"}

指定会话的结束时间(结束后的 -retain-stopped 窗口内可用):

selenium_grid_session_stop_seconds{session_id="<id>"}

已完成会话的总墙钟时长(start → stop):

selenium_grid_session_stop_seconds{session_id="<id>"}
  - selenium_grid_session_start_seconds{session_id="<id>"}

运行超过 10 分钟的所有会话:

(selenium_grid_session_stop_seconds - selenium_grid_session_start_seconds) > 600

连通性与吞吐

告警:exporter 无法到达 Grid:

selenium_grid_scrape_success == 0

会话吞吐(5 分钟窗口内的每秒完成数):

rate(selenium_grid_sessions_completed_total[5m])

最近一小时内完成的会话数:

increase(selenium_grid_sessions_completed_total[1h])

Grid 版本:

selenium_grid_info

Grafana 仪表盘

.monitoring/dashboards/README.md 提供了 5 张预置仪表盘,均消费上述 exporter 指标:

文件 UID 用途
selenium-grid-overview.json sel-grid-overview 网格健康一览:槽位、会话、队列、节点汇总表
selenium-node-health.json sel-node-health 每节点状态、容量、stereotype 分解、状态时长趋势
selenium-sessions.json sel-sessions 按浏览器/平台的会话分布、带测试名与容器名的会话明细表、已完成会话历史
selenium-queue-capacity.json sel-queue-cap 队列深度、槽位利用压力、按期望能力拆分的队列
selenium-cross-browser.json sel-cross-browser 跨浏览器测试覆盖:各浏览器活动数、槽位利用率、会话趋势、队列压力与版本分布

所有仪表盘默认每 30 秒自动刷新(Queue & Capacity 为 15 秒)。

数据源要求

仪表盘按 UID prometheus 引用 Prometheus 数据源。这是 kube-prometheus-stack 与标准 Grafana Docker Compose 环境中的默认 UID;若你的数据源 UID 不同,需在 Grafana 的 Connections → Data sources 中更新。

仓库在 .monitoring/config/grafana/provisioning/datasources/prometheus.yml 提供了配套的数据源供给配置,其 UID 为 selenium-prometheus、指向 http://prometheus:9090,若直接使用该配置则无需手动改 UID。

Docker Compose 供给

将仪表盘目录挂载到 Grafana 的 provisioning 路径,并添加供给配置:

grafana:
  volumes:
    - ./.monitoring/dashboards:/var/lib/grafana/dashboards/selenium
    - ./.monitoring/config/grafana/provisioning:/etc/grafana/provisioning

provisioning/dashboards/selenium.yaml(仓库对应文件见 .monitoring/config/grafana/provisioning/dashboards/selenium.yml,每 30 秒刷新一次供给):

apiVersion: 1
providers:
  - name: Selenium Grid
    folder: Selenium Grid
    type: file
    options:
      path: /var/lib/grafana/dashboards/selenium

Kubernetes(Helm chart)供给

当 monitoring.enabled: true 时,Helm chart 会为每张仪表盘自动创建带 grafana_dashboard: "1" 标签的 ConfigMap。Grafana 的 sidecar 容器检测到这些标签后,会将仪表盘供给到 Selenium Grid 文件夹:

monitoring:
  enabled: true
  grafana:
    dashboards:
      enabled: true  # monitoring.enabled=true 时的默认值

Grafana sidecar 必须配置为监控所有命名空间(kube-prometheus-stack 默认行为):

kube-prometheus-stack:
  grafana:
    sidecar:
      dashboards:
        searchNamespace: ALL

仪表盘更新流程

若需在 Grafana UI 中调整仪表盘后同步回仓库,官方推荐流程:

  1. 在 Grafana UI 中编辑仪表盘;
  2. 通过 Share → Export → Save to file 导出(勾选 Export for sharing externally 以包含 __inputs);
  3. 用导出的文件替换本目录对应 JSON;
  4. 若数据源输入被导出为 ${DS_PROMETHEUS},需将每次出现替换为字面量 prometheus,并删除 __inputs 与 __requires 块 —— provisioning 不处理 __inputs,只有 UI 导入器才处理;
  5. 执行 make copy_dashboards 将更新后的文件同步进 Helm chart(该目标被 Makefile 中的 chart_build / chart_build_nightly 前置依赖,确保镜像构建前仪表盘始终同步)。

小结

至此,一条完整的 Selenium Grid 观测链路已经清晰:Go 编写的 exporter 用单次 GraphQL 请求抓取网格全貌 → 输出 6 大类 20 余个语义明确的 Prometheus 指标 → Prometheus(或 Prometheus Operator)按配置周期抓取 → 5 张预置 Grafana 仪表盘实时可视化。无论是 Docker Compose 的独立容器形态,还是 Kubernetes 下嵌入 Hub/Router 的边车形态,仓库都给出了可直接复用的配置与源码实现;配合文中整理的全量指标表与 PromQL 模板,你可以快速定位节点排空、槽位耗尽、会话积压与跨浏览器覆盖不足等典型问题。

登录后查看全文
docker-selenium