mcpField
G
groundcover-cli

groundcover-cli

github.com/paymog云与 DevOps19.4k 次安装10 周前更新
代码仓库

关于 groundcover-cli

用于通过命令行操作 Groundcover 可观测性平台的技能,适合管理遥测数据、仪表板、监控器和平台设置。它结合类型化 SDK 命令与原始端点访问,覆盖 SDK 尚未支持的操作。

主要功能

  1. 01类型化平台资源增删改查
  2. 02搜索日志、追踪和指标
  3. 03访问更多原始端点
  4. 04管理认证配置和凭据
  5. 05管理 Grafana 仪表板与设置

适用场景

  1. 01查询遥测数据并检查服务状态
  2. 02创建或更新监控器和仪表板
  3. 03访问 SDK 尚未提供的端点明细

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/paymog/groundcover-cli --skill groundcover-cli

安装后在 Claude Code 中输入/groundcover-cli确认已加载。

详细介绍

Groundcover CLI

调用 groundcover 二进制文件(通过 brew install paymog/tap/groundcover 安装)。权威来源是 paymog/groundcover-cli。

身份验证(任何命令执行前都必须完成)

export GROUNDCOVER_API_KEY=gcsa_...   # must be a service-account key

密钥必须是服务账号密钥:前缀为 gcsa_,长度恰好为 40 个字符。 其他格式均会被拒绝,并显示明确消息——Invalid token prefix. Expected 'gcsa_' 或 Invalid token length (N, expected 40)。请勿混淆密钥类型:

  • gcsa_… — 服务账号密钥。查询和 CRUD 命令需要使用此密钥。
  • gcik_… — 数据摄取密钥(用于推送数据)。无法用于 API 身份验证;类型错误。

如果使用格式正确的 gcsa_ 密钥却收到 401 错误,说明此密钥属于与你正在查询的租户不同的租户。

如果手头没有 gcsa_ 密钥,可以使用已有的有效密钥运行 groundcover service-accounts create 来创建一个,然后在当前会话中执行 export GROUNDCOVER_API_KEY=gcsa_…。

内置默认值:

  • --base-url https://api.groundcover.com
  • --backend-id groundcover

没有默认租户 UUID — 设置 --tenant-uuid / GROUNDCOVER_TENANT_UUID,即可在非 Grafana 的 raw … 直通调用中发送 X-Tenant-UUID 标头(例如跨租户访问)。内嵌的 Grafana raw grafana … 端点会忽略它(参见下文的 Grafana 部分)。

raw grafana … 命令完全不使用 gcsa_ 密钥 — 它们需要 Grafana 服务账号令牌(glsa_…)。请设置 GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN / --grafana-token。

覆盖环境变量:GROUNDCOVER_BACKEND_ID、GROUNDCOVER_TENANT_UUID、GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN、GROUNDCOVER_BASE_URL(或对应的 GC_* 变量)。相同名称也可用作 --api-key、--backend-id、--tenant-uuid、--grafana-token、--base-url 标志。

已保存的配置(可替代环境变量)

无需每次会话都导出 GROUNDCOVER_API_KEY,也可以将凭据保存在命名配置中。API 密钥存储在操作系统密钥环中;元数据(后端 ID、基础 URL、租户 UUID)存储在 ~/.config/groundcover/profiles.yaml 中。

groundcover auth login [name] --backend-id <id>   # prompts for key (or --key); validates before saving
groundcover auth list                              # * marks default
groundcover auth default <name>                    # change default
groundcover --profile <name> <command>             # use a profile for one command
groundcover auth status                            # show resolved source
groundcover auth token                             # print resolved key
groundcover auth logout <name> [-f]

优先级:--api-key/GROUNDCOVER_API_KEY 环境变量 → --profile <name> → 默认配置。显式指定密钥的同时使用 --profile 会被视为存在歧义并遭到拒绝。GROUNDCOVER_PROFILE 可通过环境变量设置配置。

其他全局标志:--timeout(默认值为 30s)、--raw(不重新格式化 JSON 响应)。

两种调用方式

方式适用场景
基于 SDK(groundcover <resource> <verb>)首选。契约稳定,请求正文类型明确。
基于 HAR 的原始调用(groundcover raw …)SDK 中缺少相应端点,或需要 SDK 不具备的运行器功能(见下文)。

始终优先尝试 SDK 方式。

原始调用提供但 SDK 不支持的功能
  • --set dotted.path=value — 在正文基础上进行深度合并覆盖
  • --query key=value(可重复)— 任意查询字符串覆盖
  • 内置默认正文,采集自 Web 应用 HAR(SDK 要求显式指定 --body-file/--body-json)
  • 在设置 --tenant-uuid / GROUNDCOVER_TENANT_UUID 时,非 Grafana 原始调用会发送 X-Tenant-UUID(用于跨租户访问;否则 SDK 传输会从 API 密钥推导租户)。Grafana raw grafana … 命令不会使用它 — 它们改用 glsa_ 令牌进行身份验证。
  • 使用 groundcover raw list 发现所有已采集的端点

两种调用方式都支持 --body-file(json/yaml)、--body-json 和 --raw 输出。

目前只能使用 raw 的端点(尚无 SDK 命令)

对于以下端点,请使用 groundcover raw …;SDK 提供了父级资源,但没有下钻端点:

  • 日志:filters、velocity(SDK 仅提供 search)
  • 追踪:attributes、details、errors、filters、insights、latencies、requests、values-distribution(SDK 仅提供 search)
  • 指标:cardinality、cardinality-graph、discovery、labels-cardinality、query-range、resources errors|latencies|list|requests(SDK 提供 query、names、keys、values)
  • prometheus:prometheus api query(原始 Prom 透传——可通过 --query query='up' 方便地运行临时 PromQL 查询)
  • Grafana 仪表板:grafana search、grafana dashboards get|save|delete、grafana dashboards permissions get|update、grafana dashboards versions|get|restore、grafana folders list|get|create|update|delete、grafana folders permissions get|update、grafana annotations list|create|update|delete、grafana prometheus rules、grafana datasources label-values、grafana ds query
  • 监控器下钻:instances filters|query|timeline、labels keys、silences、summary filters|query、timeline(SDK 仅提供 CRUD)
  • k8s 下钻:configmaps|cronjob|daemonsets|deployments|jobs|pods|pvcs|replicasets|statefulsets list、container info、context events、namespaces info|list、nodes info-with-resources|list|resources|usage top10、pod container usage、pods status-over-time、workloads availability|events|usage top10、network connections|cross-az|cross-az-regions|partners|throughput|top-connections、events search-time-series(SDK 仅提供 clusters、workloads、events-search、events-over-time)
  • 基础设施:infra hosts info-with-resources
  • 资源 / RUM:resources apis errors|filters|latencies|list|requests、rum sessions filters|query、sources list
  • 流水线统计:pipelines logs current-stats、pipelines traces current-stats(SDK 仅支持配置 CRUD)
  • 租户 / 计费 / RBAC 读取:rbac seatsUsage、rbac tenant ai-settings、rbac tenant settings、backend settings、billing method、agent token-budgets|token-usage|token-usage history|token-usage tenant
  • 存储管理:storage-management get|update --data-type <logs|traces|events|measurements|monitor_instance>,用于设置保留策略、gcQL 异常规则和索引层级
  • 其他:graph、graph filters、views member、views member defaults、migrations、connectors list org|personal、synthetics rules、aggregations metrics config|config default、integrations data config

运行 groundcover raw list / groundcover raw list <group>,确认准确名称后再调用。

基于 SDK 的 CRUD 模式

groundcover <resource> list   [--query '<filter>']
groundcover <resource> get    <id>
groundcover <resource> create --body-file <file>   # or --body-json '<json>'
groundcover <resource> update <id> --body-file <file>
groundcover <resource> delete <id>

请求体文件:.json 或 .yaml。

资源(均遵循上述模式)
  • 仪表板——另有 archive <id>、restore <id>
  • 监控器——--query 'monitor_name = "cpu"'
  • 静默规则——list --active
  • 定期静默规则
  • 已连接的应用——--query 'type:slack-webhook'
  • 通知路由——--query 'prod'
  • 合成监控
  • 密钥——另有 hash <id>
  • 工作流
身份验证 / RBAC
groundcover api-keys list
groundcover api-keys create --body-file key.json
groundcover service-accounts list
groundcover service-accounts create --body-file sa.json
groundcover ingestion-keys list
groundcover policies list
groundcover policies apply --body-file policy.json
groundcover policies audit-trail <id>
流水线单例(get / create / update / delete,无 id)
groundcover logs-pipeline get
groundcover metrics-pipeline get
groundcover traces-pipeline get
groundcover metrics-aggregator get
集成(类型化)
groundcover integrations list
groundcover integrations describe <type>
groundcover integrations create <type>      --body-file config.json
groundcover integrations update <type> <id> --body-file config.json
可观测性 / 读取命令(由请求体文件驱动)
groundcover logs search          --body-file body.json
groundcover traces search        --body-file body.json
groundcover metrics query        --body-file body.json   # PromQL range/instant
groundcover metrics names        --body-file body.json   # list metric names
groundcover metrics keys         --body-file body.json   # label keys for a metric
groundcover metrics values       --body-file body.json   # label values for a key
groundcover search discovery     --body-file body.json   # facet / discovery
groundcover search keys          --body-file body.json
groundcover search values        --body-file body.json
groundcover k8s clusters         --body-file body.json
groundcover k8s workloads        --body-file body.json
groundcover k8s events-search    --body-file body.json
groundcover k8s events-over-time --body-file body.json

使用管道传递给 jq 提取字段,或传入 --raw 输出原始响应。

可观测性请求体模板

时间使用 RFC 3339 UTC 格式(2026-05-28T00:00:00Z)。不内置当前时间获取功能——请使用 date -u 计算。所有搜索采用相同结构(logs search、traces search、k8s events-search):

{
  "start": "2026-05-28T00:00:00Z",
  "end":   "2026-05-28T01:00:00Z",
  "query": "<GCQL>",
  "filters": "",
  "sources": []
}

metrics query(注意首字母大写的字段名——这是 API 的要求):

{
  "Promql":    "rate(http_requests_total[5m])",
  "Start":     "2026-05-28T00:00:00Z",
  "End":       "2026-05-28T01:00:00Z",
  "Step":      "60",
  "QueryType": "range",
  "Conditions": []
}

进行单点查询时,使用 "QueryType": "instant" 并省略 Step。

k8s workloads / k8s clusters:

{
  "conditions":  [],
  "sources":     [],
  "gcqlFilter":  "namespace = 'prod'",
  "limit":       100
}

Raw 命令

groundcover raw list                          # list groups
groundcover raw list <group>                  # list commands in group
groundcover raw k8s clusters list
groundcover raw dashboards get --dashboard-id <id>
groundcover raw metrics query-range --body-file body.json
groundcover raw prometheus api query --query query='up'
groundcover raw grafana dashboards get --dashboard-uid <uid>
groundcover raw grafana dashboards save --body-file dashboard.json
groundcover raw grafana folders list
groundcover raw grafana ds query --body-file query.json
存储管理

管理员可以读取和更新 logs、traces、events、measurements(APM)和 monitor_instance(监控问题)的生命周期设置:

groundcover raw storage-management get --data-type logs --raw \
  | jq '{retention,version,cold_move_duration,cold_volume,custom_rules:(.custom_rules // [])}' \
  > storage.json
# Edit storage.json, preserving every writable field and the complete rule list.
groundcover raw storage-management update --data-type logs --body-file storage.json

更新采用乐观并发控制,并替换可写设置文档。始终从 get 开始,沿用当前的 version,并保留 retention、cold_move_duration、cold_volume 和完整的 custom_rules 列表;省略 custom_rules 会移除现有规则。成功更新后,version 会递增。每条已观测到的自定义规则都包含 name、retention 和 gcQL filters 表达式。

Grafana 原生仪表板

Groundcover 还在 /grafana 嵌入了 Grafana。这些仪表板与 Groundcover 一等的 dashboards SDK 资源并不相同,因此,如果需要原生 Grafana JSON 仪表板、文件夹、权限、由数据源支持的变量值或面板查询执行,请使用 raw grafana …。

**身份验证:**嵌入式 Grafana 位于受会话控制的代理之后,该代理会忽略 gcsa_ API 密钥、后端 ID 和租户 UUID。向 /grafana/api/* 发送 bearer/gcsa_ 请求只会返回约 980KB 的 Grafana SPA index.html(HTTP 200,text/html),而不会返回 JSON。这些命令需要 Grafana 服务账号令牌(glsa_…):请设置 GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN(或使用 --grafana-token)。运行任何不带令牌的 raw grafana … 命令时,CLI 都会打印完整的设置指南。

生成令牌(需要 groundcover 租户管理员权限)。令牌由 groundcover 的官方 CLI(github.com/groundcover-com/cli)提供,但遗憾的是它也会安装一个名为 groundcover 的二进制文件。安装程序会将其放在 ~/.groundcover/bin/groundcover,并将该目录添加到 PATH 的开头;此后,groundcover 会指向官方 CLI,并覆盖此 CLI。请使用官方二进制文件的完整路径来避免冲突:

sh -c "$(curl -fsSL https://groundcover.com/install.sh)"      # installs to ~/.groundcover/bin
~/.groundcover/bin/groundcover auth login                     # auth flow
~/.groundcover/bin/groundcover auth generate-service-account-token   # prints glsa_… once
export GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN=glsa_...     # hand it to this CLI

若要继续将此 CLI 用作 groundcover,请从 shell rc 中移除安装程序添加的 ~/.groundcover/bin PATH 行。

常用命令:

groundcover raw grafana search --query query='service slo' --query folderUIDs=general
groundcover raw grafana dashboards get --dashboard-uid streamling-pipeline-slo
groundcover raw grafana dashboards save --body-file dashboard.json
groundcover raw grafana folders list
groundcover raw grafana datasources label-values --datasource-uid <uid> --label project_id --query start=<unix> --query end=<unix>
groundcover raw grafana ds query --query ds_type=prometheus --body-file query.json

Grafana raw 命令默认使用 https://app.groundcover.com(Web 应用主机),而常规 API/SDK 命令默认使用 https://api.groundcover.com。仅在非标准部署中传递 --base-url。

Raw 标志:--body-json、--body-file、--set dotted.path=value、--query key=value(可重复)、生成的路径标志(例如 --dashboard-id)、--raw。许多 raw 命令附带从 Web 应用 HAR 中捕获的默认请求体,因此无需传入任何 --body-* 即可运行,也可以使用 --set 覆盖特定字段。

操作示例

查询某个服务最近一小时的错误日志
END=$(date -u +%FT%TZ); START=$(date -u -v-1H +%FT%TZ)   # GNU: date -u -d '1 hour ago' +%FT%TZ
jq -n --arg s "$START" --arg e "$END" '{
  start:$s, end:$e,
  query: "service_name = \"api\" AND level = \"error\"",
  filters: "", sources: []
}' | groundcover logs search --body-file /dev/stdin | jq '.[] | {ts:.timestamp, pod:.pod_name, body:(.body|fromjson?)}'

logs search 在根路径返回一个原始 JSON 数组——请使用 jq '.[]',而不是 .logs[]。人类可读的消息位于 body 字段中,且为 JSON 字符串(使用 fromjson 解析)。有用的顶层字段包括:pod_name、container_name、namespace、workload、level(已标准化为小写——error/warning/debug,而不是 body 中显示的 WARN/DEBG)、cluster、timestamp、trace_id。

查询某个工作负载的 p99 延迟(最近 30 分钟,PromQL)
END=$(date -u +%FT%TZ); START=$(date -u -v-30M +%FT%TZ)
jq -n --arg s "$START" --arg e "$END" '{
  Promql: "histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{service=\"api\"}[5m])))",
  Start: $s, End: $e, Step: "60", QueryType: "range", Conditions: []
}' | groundcover metrics query --body-file /dev/stdin
查询发生崩溃循环或重启的 Pod
jq -n '{ conditions: [], sources: [], gcqlFilter: "status != \"Running\"", limit: 200 }' \
  | groundcover k8s workloads --body-file /dev/stdin \
  | jq '.workloads[] | select(.restarts > 0) | {name, namespace, status, restarts}'
查询某个命名空间最近的 k8s 事件
END=$(date -u +%FT%TZ); START=$(date -u -v-1H +%FT%TZ)
jq -n --arg s "$START" --arg e "$END" '{
  start:$s, end:$e,
  query: "namespace = \"prod\" AND type = \"Warning\"",
  filters: "", sources: []
}' | groundcover k8s events-search --body-file /dev/stdin
查找慢速跟踪,然后转向查看其日志
END=$(date -u +%FT%TZ); START=$(date -u -v-30M +%FT%TZ)
jq -n --arg s "$START" --arg e "$END" '{
  start:$s, end:$e,
  query: "service_name = \"api\" AND duration_ms > 1000",
  filters: "", sources: []
}' | groundcover traces search --body-file /dev/stdin \
  | jq -r '.[] | .trace_id' | head -5 \
  | while read tid; do
      jq -n --arg s "$START" --arg e "$END" --arg t "$tid" '{
        start:$s, end:$e, query: ("trace_id = \"" + $t + "\""), filters: "", sources: []
      }' | groundcover logs search --body-file /dev/stdin
    done
查找某个流中有哪些标签/字段
# What metric names match a prefix?
jq -n '{ prefix: "http_" }' | groundcover metrics names --body-file /dev/stdin

# What labels does a specific metric have?
jq -n '{ metric: "http_request_duration_seconds" }' | groundcover metrics keys --body-file /dev/stdin

# What values does a label take?
jq -n '{ metric: "http_request_duration_seconds", key: "service" }' \
  | groundcover metrics values --body-file /dev/stdin
GCQL 快速参考(日志/追踪/事件查询)
  • 相等:service_name = "api"(JSON 中的双引号 → \")
  • 比较:duration_ms > 1000、status_code >= 500
  • 布尔运算:AND、OR、NOT
  • 子字符串:message ~ "timeout",正则表达式:message ~* "^panic:"
  • 成员关系:level IN ("error", "fatal") — 仅适用于命名字段
  • **全文短语:**不带字段名的带引号字符串,例如 container = "my-service" AND "database unavailable"。IN(...) 不能用作全文搜索(会报错 freetext 'in(...)' is not supported)——请展开为 level = "error" OR level = "fatal"。
  • 切勿发送空的 query — query: "" → 500 pipeline cannot be nil。请始终至少提供一个条件。
  • 空值检查:trace_id IS NOT NULL

枚举字段:search discovery/search keys 比较棘手(discovery 需要 Limit + Type,且仍可能返回 400)。可靠的做法是获取一条示例日志,并检查 keys 和解析后的 body。

查找并静默一个监控项
groundcover monitors list --query 'monitor_name ~ "ingest"'
groundcover silences create --body-json '{"monitor_id":"<id>","duration_seconds":3600,"reason":"deploy"}'
导出所有仪表板

列表字段是 uuid,不是 id(.id 为 null)。每个条目还包含 archivedTimestamp — 跳过已归档的条目(活动状态 = 0001-01-01T00:00:00.000Z)。

groundcover dashboards list \
  | jq -r '.[] | select(.archivedTimestamp == "0001-01-01T00:00:00.000Z") | .uuid' \
  | while read id; do groundcover dashboards get "$id" > "dashboards/$id.json"; done
构建预填模板变量的仪表板深层链接

groundcover dashboards get <uuid> 会在 .preset 下返回变量定义(一个JSON 字符串——用 fromjson 解析)。查找仪表板并检查其变量:

groundcover dashboards list | jq -r '.[] | select(.name|test("prod";"i")) | "\(.uuid)\t\(.name)"'
groundcover dashboards get <uuid> | jq -r '.preset | fromjson | .variables'
# each var: { kind:"list", spec:{ values:{default:["*"]}, datasource:{key,kind,metric}, variableName } }

Web 应用会将选定的变量值编码到 variables URL 查询参数中——这是一个以 $<variableName> 为键的 URL 编码 JSON 对象。每个条目都需要 name、key、values(数组;支持 myapp* 这样的通配符)、datasource(通常为 "metrics")和 refiner.metric(变量 datasource.metric 中的源指标)。

**请固定所有你关心的变量——包括你希望不受限制的变量。**任何省略的变量都会回退到仪表板的已保存默认值,而它通常不是 *(例如,硬编码的 deployment 哈希值或 shard: primary)。要获取真正宽泛的视图,请明确将这些变量设为 ["*"]。使用前务必确认标签值确实存在——例如,app_kubernetes_io_instance 可能没有单独的 myapp,只有 myapp-web-0 等值,因此请使用 myapp* 通配符。

使用 jq 构建,确保编码正确:

VARS=$(jq -cn '{
  "$cluster":{name:"cluster",key:"cluster",values:["example-cluster"],datasource:"metrics",refiner:{metric:"my_metric_total"}},
  "$deployment":{name:"deployment",key:"deployment",values:["*"],datasource:"metrics",refiner:{metric:"my_metric_total"}},
  "$app_kubernetes_io_instance":{name:"app_kubernetes_io_instance",key:"app_kubernetes_io_instance",values:["myapp*"],datasource:"metrics",refiner:{metric:"my_metric_total"}}
}')
ENC=$(jq -rn --arg v "$VARS" '$v|@uri')
echo "https://app.groundcover.com/dashboards?backendId=groundcover&tenantUUID=<your-tenant-uuid>&duration=Last+1+hour&viewId=<uuid>&qb-disable-auto-focus=true&variables=${ENC}"

注意:仪表板通过 viewId=<uuid> 打开(而不是 /dashboards/<uuid> 路径);duration 接受 Last+1+hour / Last+5+minutes 这样的 UI 字符串。

通过 raw 发起临时 Prom 查询
groundcover raw prometheus api query --query query='up{namespace="prod"}'

构建和编辑仪表板

仪表板的关键不在于 CRUD 操作(dashboards get/list/create/update/archive/restore),而在于 preset,整个面板布局都存放在一个字段中。

Preset 结构

dashboards get <uuid> 会返回仪表板,其中 .preset 是一个JSON 字符串(用 fromjson / json.loads 解析;写回前重新序列化为字符串)。结构:

{
  "spec": { "layoutType": "ordered", "crosshairSyncEnabled": true },
  "layout": [ /* one entry per TOP-LEVEL item, placed on a 24-col grid */ ],
  "widgets": [ /* the actual panels, referenced by id from layout */ ],
  "duration": "Last 6 hours",   // UI duration string
  "variables": [ /* template vars, same shape as the deep-link recipe */ ],
  "schemaVersion": 7
}
  • layout — 顶层网格布局。每个条目的格式为 { "id":"A", "h":12, "w":24, "x":0, "y":0, "minH":8 }。网格宽 24 列(w:8 = 三分之一,w:12 = 一半);id 必须与某个组件的 id 匹配。需要手动计算 y 来堆叠行——没有自动流式布局。
  • widgets — { id, name, type, queries:[{id, expr, dataType:"metrics", editorMode:"editor"}], visualizationConfig:{ type, selectedUnit, ... } }。expr 是带有 $var 替换的 PromQL。
  • 组件类型:time-series、stat、text(content 中为 Markdown,无查询——可用于标题/分隔线)、section。
分区(可折叠组)

分区是一个组件 { id, name, type:"section", color, isCollapsed:false }。它的 layout 条目包含一个 children 数组,其中是采用相对位置的面板条目:{ id, h, w:24, x:0, y:0, children:[ {h,w,x,y,id,minH}, ... ] }。标题占据顶部约 2 行,因此子项从 y:2 开始。

更新契约

dashboards update <uuid> 请求体必须包含 currentRevision(当前的 .revisionNumber)——这是乐观并发控制。省略它会导致 Field validation for 'CurrentRevision' failed on the 'required_if' tag。每次成功更新后都要重新执行 get,读取递增后的版本号,再进行下一次写入。

groundcover dashboards get <uuid> > d.json
# NEW_PRESET must be the preset object re-serialized to a STRING
jq -n --slurpfile d d.json --arg preset "$NEW_PRESET" '{
  name: $d[0].name, viewType: ($d[0].viewType // "explore"),
  status: "active", currentRevision: $d[0].revisionNumber, preset: $preset
}' | groundcover dashboards update <uuid> --body-file /dev/stdin
验证规则(API 只会返回 Dashboard validation failed)

更新端点会返回不透明的 400 {"message":"Dashboard validation failed"}——没有指出字段,也没有指出哪个组件有问题。遇到这种情况时,使用二分法排查:部署最小预设(一个分区 / 一个面板),然后逐步添加内容,直到再次出错。以下两条不太明显的规则,排查起来都很费时间:

  1. 统计组件(visualizationConfig.type:"stat")只有作为顶层扁平 layout 条目时才能通过验证——绝不能放在分区的 children[] 中。 时序组件则在这两种位置都可用。要构建概览统计行,请将统计面板作为顶层扁平条目放置(并在其上方放置一个 text 标题组件),将时序面板保留在分区中。
  2. 分区的 color 仅接受固定调色板中的颜色。 已验证有效:gray、purple、teal。已验证无效:green、blue、red、yellow、orange。
滞后指标的统计面板显示“No Results” → 用 last_over_time 包装

统计面板会将“现在”作为时间点执行即时查询。实时数据有延迟的指标会导致该即时窗口为空 → 面板显示 “No Results”,尽管它在时序面板中能正常绘图(时序面板会对整个窗口执行范围查询)。从轮询式集成(例如 CloudWatch 等云服务提供商集成)拉取的指标通常会滞后实时数据数分钟,因此直接基于这些指标构建的统计面板会显示空白。

解决方法:在聚合表达式内部用 last_over_time(<selector>[30m]) 包装选择器,让即时查询向前回溯,越过数据陈旧时段:

max(last_over_time(my_metric{...}[30m]))   # NOT  max(my_metric{...})

*_over_time 包装函数在统计面板中可以正常渲染(例如,100 * avg_over_time((...)[7d:5m]) 可以正常显示运行时间统计)。实时抓取的集群内指标(约 1 秒内更新)不需要这样处理。

不通过 UI 验证仪表板

如果无头浏览器遇到 Groundcover 的 SSO 登录墙(没有会话),就无法通过截图验证。可以改用 API 验证:

  • 每条查询都返回数据: 拉取已部署的预设,在每个 expr 中填入具体值(将 $var 替换为实际标签值;将通配符变量替换为 =~".+" 正则表达式),然后通过 metrics query 执行范围查询。查询窗口应不短于指标的生成间隔(稀疏的集成指标可能每隔几分钟才生成一次)。
  • 几何布局: 确认没有两个布局矩形相互重叠,并且每个 x+w ≤ 24(顶层条目和每个分区的 children 均需检查)。
  • 注意: CLI 无法可靠地执行即时查询(参见“常见问题”),因此无法通过 CLI 复现统计面板的即时查询路径——应根据数据陈旧情况和范围查询数据进行判断。
轮询式集成指标(例如 CloudWatch):标签方案注意事项

从轮询式云集成接入的指标,在构建仪表板时与集群内导出器指标有所不同,容易造成问题:

  • 它们会滞后实时数据(参见上面的统计面板说明)——延迟以分钟计,而不是秒。
  • 它们通常会按 stat 维度成倍出现(stat=average/maximum/minimum)——每个序列都会出现 3 次。务必筛选到所需的一个值(例如 stat="average"),否则聚合结果会被重复计算两到三次。
  • 环境 / 范围标签采用集成命名方式,而不是统一的 env。如果同一资源也由集群内导出器采集,那么这两组指标会用不同的标签键表示同一概念(例如 instance_id 与 instanceid)。请为每种标签方案分别使用模板变量,不要强行让一个变量同时适配两者。

生产环境可观测性排查流程

当有人问“为什么 X 在生产环境中出故障了”或“服务 Y 怎么了”时:

  1. 确定时间范围。 默认使用最近 1h。用 date -u 计算 START/END(参见操作指南)。
  2. 先查日志。 使用 groundcover logs search,通过 service_name 和 level = "error" 设置 GCQL 过滤条件。成本低,通常就能找到答案。
  3. 延迟高或运行缓慢时查追踪。 使用 groundcover traces search,按 duration_ms、status_code 或 service_name 过滤。获取几个 trace_id,再通过 trace_id = "..." 回查日志。
  4. 查看指标随时间的变化。 使用 groundcover metrics query 和 PromQL 查询错误率、p99、饱和度等。绘图时使用 range,检查单个时间点时使用 instant。
  5. 确认“是否仍在运行”时查 K8s。 使用 groundcover k8s workloads 查看状态和重启情况;使用 groundcover k8s events-search 查找 Warning 事件(OOMKilled、FailedScheduling、BackOff)。
  6. 不知道字段名? 使用 groundcover search discovery、metrics names、metrics keys、metrics values 列举字段。
  7. 需要在事件期间静默嘈杂的告警? 执行 groundcover monitors list --query … → groundcover silences create。

不确定有哪些命令时

  1. 查看上方的资源列表。
  2. 执行 groundcover --help 和 groundcover <resource> --help。
  3. 对于原始资源:执行 groundcover raw list,然后执行 groundcover raw list <group>。

常见问题

401 Unauthorized、令牌前缀/长度错误或结果为空
  • Invalid token prefix / Invalid token length → 密钥不是 gcsa_ 服务账号密钥(参见 身份验证)。常见错误是使用了 gcik_ 摄取密钥。
  • 使用看似有效的 gcsa_ 密钥却收到 401 → 该密钥属于其他租户。对于其他租户,还要设置 GROUNDCOVER_TENANT_UUID,并在适用时设置 GROUNDCOVER_BASE_URL。
  • 否则,请确认当前 shell 中确实已导出该环境变量。
SDK 命令拒绝请求体

请求体结构与 SDK 契约不一致。使用 get <id> 获取现有资源,并将其结构用作模板。

logs search / traces search / events-search 返回空结果

几乎总是以下原因之一:

  • 时间范围不正确或过窄。start 必须早于 end,且两者都必须是 RFC 3339 UTC 时间。
  • GCQL 字段名在此租户中不存在。使用 search discovery / search keys 列举字段。
  • 查询字符串未转义——在 JSON 中,service_name = "api" 应写成 "service_name = \"api\""。
metrics query 未返回数据,但指标确实存在
  • 字段名区分大小写(Promql、Start、End、Step、QueryType)——JSON 会静默接受小写字段名,但会忽略它们。
  • Step 是字符串("60"),不是数字。
  • 对于即时查询,设置 "QueryType": "instant" 并省略 Step。已观察到的问题:测试中,CLI 的 metrics query 对即时查询请求体返回过 400 metricsQueryBadRequest(尝试过的所有结构均如此——Start==End、Time、设置或不设置 QueryType),即使指标是新创建的也是如此。需要单个当前值时,可改用短时间范围的范围查询(QueryType:"range" + Step),并取最后一个数据点。(真正的即时 PromQL 也可通过 raw prometheus api query 访问,但该直通接口可能查询的是与 metrics query 不同的数据存储。)
  • 解析响应:结果位于 .data.result // .result;每个 values 条目都是 [unix_ts, "stringvalue"]。使用 [.values[][1]] | map(tonumber) | add 对范围内的值求和;使用 strftime 格式化时间戳。
logs search 超时(context deadline exceeded)

高日志量的工作负载会产生大量日志,因此即使查询范围仅为约 60–90s,宽泛查询或单 Pod 查询也可能超出时限。解决方法:

  • 始终加入具有区分度的词项(一个短语 + container/pod),不要只查询 Pod 本身。
  • 缩短时间范围并提高 --timeout(例如 --timeout 120s)。
  • 避免在一个查询中使用较长的 OR 链——分别运行这些查询。
  • 对于“频率如何/随时间如何变化”这类问题,优先使用 metrics query,而不是抓取日志。
monitors 编辑与搜索
  • monitors get / update 使用 YAML(即使带有 --raw 也是如此)。工作流:monitors get <uuid> > mon.yaml,编辑后执行 monitors update <uuid> --body-file mon.yaml。成功更新会返回 {"status":202}(异步应用——重新执行 get 以确认)。
  • Slack 应用目标需要同时设置 connectedApps: [<app-id>] 和 connectedAppParams.<app-id>.channels: [{id, name}]。Web 应用 PUT 使用具有该结构的 YAML text/plain。SDK ≥ v1.376 会编码 connectedAppParams;较旧的 SDK 会将其丢弃,API 则返回 400 connectedApps[0]: channels is required for slack-app route params。
  • 监控器模型位于 model.queries[].sqlPipeline.filters.conditions[] 下——每个条件都是 {key, origin: root, type, filters:[{op, value}]};自由文本使用 type: freetext + op: phrase_search。其他参数:instantRollup(例如 1 minute)、thresholds[]、evaluationInterval.{interval, pendingFor}。
  • monitors list --query 'monitor_name ~ "..."' → 400 regexp requires a string column type。请使用 monitor_name = "exact",或列出全部结果后用 jq 过滤。
分组内原始命令“未找到”

该端点未包含在捕获的 HAR 中。如果存在对应形式,请使用 SDK;否则重新生成——重新生成脚本位于 groundcover-cli 仓库中(go run ./scripts/generate-commands.go <har>)。

评论

支持 Markdown