This is an automated email from the ASF dual-hosted git repository.
github-actions[bot] pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git
The following commit(s) were added to refs/heads/master by this push:
new 7d5675a1 Remove redundant design logic (#979)
7d5675a1 is described below
commit 7d5675a141516bc8f96f3f2c49ab309ad140ca0a
Author: mfordjody <[email protected]>
AuthorDate: Fri Jul 24 11:16:16 2026 +0800
Remove redundant design logic (#979)
---
dubbod/gui/DESIGN.md | 31 +-
dubbod/gui/resources/data/app.js | 42 +--
dubbod/gui/resources/data/styles.css | 79 -----
dubbod/gui/resources/data/topology.js | 364 ---------------------
.../charts/addons/grafana/files/manifests.yaml | 301 -----------------
.../charts/addons/prometheus/files/manifests.yaml | 63 ----
samples/addons/grafana.yaml | 301 -----------------
samples/addons/prometheus.yaml | 63 ----
8 files changed, 11 insertions(+), 1233 deletions(-)
diff --git a/dubbod/gui/DESIGN.md b/dubbod/gui/DESIGN.md
index 30ced4cd..4bab84d3 100644
--- a/dubbod/gui/DESIGN.md
+++ b/dubbod/gui/DESIGN.md
@@ -20,7 +20,6 @@ Log fetches are on demand with `tail` capped at 2000 by the
backend.
```
OBSERVE Overview real api/overview
- Topology real derived from api/overview (config-plane graph)
Metrics real api/metrics (control-plane Prometheus registry)
Logs real api/logs
MESH Services real overview.services
@@ -29,7 +28,6 @@ MESH Services real overview.services
Config real overview.configKinds (counts only)
PLANNED Traffic gated — no request-level telemetry feed exists
Events gated — no Kubernetes events feed exists
- Alerts gated — no alerting rule engine exists
SYSTEM Runtime real overview.server / mesh / version / instance
```
@@ -49,20 +47,6 @@ renders a capability gate stating exactly which backend feed
is missing (see §6
| Registry table | `registries[].{provider,cluster,synced}` |
| Header cluster / ns / updated | `clusterId`, `namespace`, `updatedAt` |
-### Topology page (config-plane graph — honest by construction)
-Nodes and edges are all derivable facts; nothing is invented:
-| Graph element | Source |
-|---|---|
-| Registry nodes | `registries[]` (status = `synced`) |
-| Control-plane node | `instances[]` ready ratio, `counts.xdsConnections`,
`version` |
-| Gateway nodes | `gatewayInstances[]` ready ratio |
-| Namespace groups + service nodes | `services[]` grouped by `namespace`;
per-node `registry`, `exposure`, `ports`, `meshExternal` |
-| registry → dubbod edge | sync relationship, styled by `synced` |
-| dubbod → namespace edge | xDS distribution (service count per namespace) |
-| dubbod → gateway edge | provisioning relationship (`managed-by=dubbod`),
styled by `isReady` |
-| Edge RPS / error rate / latency | **missing capability** — labeled
"telemetry not connected", never faked |
-| gateway → service routing edges | **missing capability** — needs HTTPRoute
parentRef/backendRef detail API |
-
### Metrics page (all series from `api/metrics`)
| UI element | Metric family |
|---|---|
@@ -86,10 +70,10 @@ from `PodLogOptions.Timestamps: true`).
## 4. Drill-down model
Global → module → entity drawer. One right-hand drawer component serves every
-entity type (service, gateway, registry, control plane, config kind, topology
-node) so drill-down behaves identically everywhere. Drawer actions only expose
-operations the backend can execute today: fetch logs (dubbod/gateway), open
-metrics, locate in topology, copy hostname/address.
+entity type (service, gateway, registry, control plane, config kind) so
+drill-down behaves identically everywhere. Drawer actions only expose
operations
+the backend can execute today: fetch logs (dubbod/gateway), open metrics, copy
+hostname/address.
## 5. Mock mode
@@ -103,9 +87,8 @@ marks mock mode; it is never on by default.
| Capability | Blocked UI | Required feed |
|---|---|---|
-| Request-level telemetry (RPS, error rate, P95/P99 per service/edge) |
Traffic page; edge metrics in Topology; service KPI columns | metrics ingestion
from proxyless workloads (e.g. scrape/OTLP) aggregated by service pair |
+| Request-level telemetry (RPS, error rate, P95/P99 per service/edge) |
Traffic page; service KPI columns | metrics ingestion from proxyless workloads
(e.g. scrape/OTLP) aggregated by service pair |
| Kubernetes events | Events page; event timeline in drawers | `api/events`
backed by an events informer |
-| Alerting | Alerts page; alert badges | rule engine + alert store
(ack/silence lifecycle) |
| Historical time series | Metrics page beyond session buffer; time-range
pickers | TSDB-backed query API (or Prometheus proxy) |
| Per-resource config listing/spec | Config page rows → resource list & YAML |
`api/configs?kind=` listing endpoint |
| Service instance/endpoint listing | Service drawer endpoints tab |
`api/services/{host}/endpoints` |
@@ -130,5 +113,5 @@ Each gate page states the required feed in these exact
terms.
One y-axis per chart; legend + direct labels for multi-series.
- States implemented everywhere: skeleton (first load), error banner with
retry,
empty, capability gate, mock banner. Reduced motion respected.
-- Responsive: nav rail collapses to icons < 1200 px; grids reflow; topology and
- tables stay usable at 1280×800.
+- Responsive: nav rail collapses to icons < 1200 px; grids and tables stay
+ usable at 1280×800.
diff --git a/dubbod/gui/resources/data/app.js b/dubbod/gui/resources/data/app.js
index 6b494a8e..b3046547 100644
--- a/dubbod/gui/resources/data/app.js
+++ b/dubbod/gui/resources/data/app.js
@@ -24,7 +24,6 @@
*/
import { mockOverview, mockLogs, mockMetrics } from "./mock.js";
-import { Topology } from "./topology.js";
import {
Sparkline, TrendChart, BucketBars, BreakdownBars,
bucketQuantile, fmtNumber, fmtDuration,
@@ -96,9 +95,9 @@ const applyTheme = (mode) => {
const LEGACY_ROUTES = { home: "overview", mesh: "services", meshgateway:
"gateways", configuration: "config" };
const ROUTES = [
- "overview", "topology", "metrics", "logs",
+ "overview", "metrics", "logs",
"services", "gateways", "registries", "config",
- "traffic", "events", "alerts", "runtime",
+ "traffic", "events", "runtime",
];
const parseRoute = () => {
let hash = (location.hash || "").replace(/^#\/?/, "");
@@ -233,8 +232,7 @@ const Drawer = ({ item, onClose, onOpenLogs, navigate }) =>
{
<span class="chip chip-gate">request metrics n/a</span>
</div>
<div class="drawer-actions">
- <button class="btn" onClick=${() => { onClose();
navigate("topology"); }}>Locate in topology</button>
- <button class="btn btn-ghost" onClick=${() =>
copyText(data.hostname)}>Copy hostname</button>
+ <button class="btn" onClick=${() =>
copyText(data.hostname)}>Copy hostname</button>
</div>
`}
@@ -246,7 +244,6 @@ const Drawer = ({ item, onClose, onOpenLogs, navigate }) =>
{
<${Field} label="Replicas" mono>${data.readyReplicas || 0} /
${data.desiredReplicas || 0} ready</${Field}>
<div class="drawer-actions">
<button class="btn" onClick=${() => onOpenLogs({ kind:
"gateway", name: data.name, namespace: data.namespace })}>View logs</button>
- <button class="btn btn-ghost" onClick=${() => { onClose();
navigate("topology"); }}>Locate in topology</button>
</div>
`}
@@ -932,7 +929,7 @@ const GATED = {
title: "Traffic",
purpose: "Per-service and per-edge request analytics: RPS, success rate,
error breakdown, latency percentiles, protocol split and drill-down to slow
calls.",
missing: "Request-level telemetry from proxyless gRPC workloads — a
metrics ingestion path (scrape or OTLP) aggregated by service pair. The xds-api
wire protocol carries no per-request stats today.",
- wouldShow: ["RPS / error-rate / P95 per service and per edge",
"traffic-weighted topology edges", "status-code and protocol breakdowns",
"time-range comparison and anomaly flags"],
+ wouldShow: ["RPS / error-rate / P95 per service and per edge",
"status-code and protocol breakdowns", "time-range comparison and anomaly
flags"],
},
events: {
title: "Events",
@@ -940,12 +937,6 @@ const GATED = {
missing: "An api/events feed backed by a Kubernetes events informer (or an
internal event store) scoped to mesh resources.",
wouldShow: ["chronological event stream with severity", "affected-resource
links into services/gateways", "filters by kind, namespace, and reason"],
},
- alerts: {
- title: "Alerts",
- purpose: "Active and historical alerts with acknowledge / silence / close
lifecycle, wired to the metrics that triggered them.",
- missing: "An alerting rule engine and alert store. Nothing evaluates
thresholds server-side today; the GUI will not fabricate alert states.",
- wouldShow: ["severity-grouped active alerts", "ack / silence / close
actions with audit trail", "links to the triggering metric and topology node"],
- },
};
// --- shell
---------------------------------------------------------------------
@@ -953,7 +944,6 @@ const GATED = {
const NAV = [
{ group: "Observe", items: [
{ id: "overview", label: "Overview" },
- { id: "topology", label: "Topology" },
{ id: "metrics", label: "Metrics" },
{ id: "logs", label: "Logs" },
]},
@@ -966,7 +956,6 @@ const NAV = [
{ group: "Planned", items: [
{ id: "traffic", label: "Traffic", gated: true },
{ id: "events", label: "Events", gated: true },
- { id: "alerts", label: "Alerts", gated: true },
]},
{ group: "System", items: [
{ id: "runtime", label: "Runtime" },
@@ -976,7 +965,6 @@ const NAV = [
const NavIcon = ({ id }) => {
const paths = {
overview: html`<path d="M3 12h5V3H3zM10 21h5v-9h-5zM17 8h4V3h-4zM3
21h5v-6H3zM10 9h5V3h-5zM17 21h4V11h-4z"/>`,
- topology: html`<circle cx="5" cy="12" r="2.5"/><circle cx="19" cy="6"
r="2.5"/><circle cx="19" cy="18" r="2.5"/><path d="M7.4 11l9.2-4.2M7.4 13l9.2
4.2" fill="none"/>`,
metrics: html`<path d="M3 20h18M6 16l4-6 4 3 5-8" fill="none"/>`,
logs: html`<path d="M4 5h16M4 10h16M4 15h10M4 20h7" fill="none"/>`,
services: html`<circle cx="12" cy="12" r="8.5" fill="none"/><circle
cx="12" cy="12" r="3"/>`,
@@ -985,7 +973,6 @@ const NavIcon = ({ id }) => {
config: html`<circle cx="12" cy="12" r="3" fill="none"/><path d="M12
2v3M12 19v3M2 12h3M19 12h3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M19.1 4.9L17 7M7
17l-2.1 2.1" fill="none"/>`,
traffic: html`<path d="M3 17c4 0 4-10 9-10s5 10 9 10" fill="none"/>`,
events: html`<circle cx="12" cy="12" r="8.5" fill="none"/><path d="M12
7v5l3.5 2" fill="none"/>`,
- alerts: html`<path d="M12 3l9.5 17h-19zM12 10v4M12 17.3v.4" fill="none"/>`,
runtime: html`<rect x="4" y="4" width="16" height="16" rx="2"
fill="none"/><path d="M9 9h6v6H9z" fill="none"/>`,
};
return html`<svg viewBox="0 0 24 24" class="nav-icon" fill="none"
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
stroke-linejoin="round">${paths[id] || paths.overview}</svg>`;
@@ -1149,27 +1136,6 @@ const App = () => {
<div class="content" key=${route}>
${route === "overview" && html`<${OverviewPage} ...${pageProps} />`}
- ${route === "topology" && html`
- <div class="page page-flush">
- <div class="page-head">
- <div>
- <${Eyebrow}>observe / topology</${Eyebrow}>
- <h1 class="page-title">Topology</h1>
- <div class="page-sub">Configuration-plane relationships as
of ${updatedAt || "–"} — registries sync into dubbod; dubbod provisions
gateways and pushes xDS to workloads.</div>
- </div>
- </div>
- <${Topology} data=${data} selectedId=${drawer?.nodeId}
- onSelect=${(node) => {
- const map = {
- registry: () => setDrawer({ type: "registry", nodeId:
node.id, title: node.label, status: node.status, data: node.data }),
- gateway: () => setDrawer({ type: "gateway", nodeId:
node.id, title: node.label, status: node.status, data: node.data }),
- service: () => setDrawer({ type: "service", nodeId:
node.id, title: node.label, status: "unknown", data: node.data }),
- controlplane: () => setDrawer({ type: "controlplane",
nodeId: node.id, title: "dubbod", status: node.status, data: node.data }),
- };
- map[node.type]?.();
- }} />
- </div>
- `}
${route === "metrics" && html`
<${MetricsPage} history=${history} interval=${metricsInterval}
setInterval=${setMetricsInterval}
paused=${metricsPaused} setPaused=${setMetricsPaused}
error=${metricsError} retry=${pollMetrics} />
diff --git a/dubbod/gui/resources/data/styles.css
b/dubbod/gui/resources/data/styles.css
index 568d9459..4db4b5bf 100644
--- a/dubbod/gui/resources/data/styles.css
+++ b/dubbod/gui/resources/data/styles.css
@@ -607,85 +607,6 @@ mark { background: var(--warn-soft); color: inherit;
outline: 1px solid var(--wa
.hbar-fill { display: block; height: 100%; border-radius: 3px; }
.hbar-value { flex: none; width: 64px; font-family: var(--mono); font-size:
11.5px; font-weight: 600; }
-/* --- topology (signature)
------------------------------------------------------------------------ */
-
-.topo { display: flex; flex-direction: column; height: calc(100vh - 230px);
min-height: 460px; }
-.topo-fullscreen {
- position: fixed; inset: 0; z-index: 50;
- height: 100vh; min-height: 0;
- background: var(--canvas);
- padding: 12px;
-}
-.topo-toolbar { flex: none; display: flex; gap: 8px; align-items: center;
margin-bottom: 10px; flex-wrap: wrap; }
-.topo-search { width: 220px; }
-.topo-toolbar-gap { flex: 1; }
-.topo-canvas {
- flex: 1; position: relative; overflow: hidden;
- background: var(--surface);
- background-image: radial-gradient(var(--border) 1px, transparent 1px);
- background-size: 22px 22px;
- border: 1px solid var(--border);
- border-radius: var(--r-md);
- cursor: grab;
- touch-action: none;
-}
-.topo-canvas:active { cursor: grabbing; }
-.topo-canvas svg { display: block; }
-
-.topo-band {
- font-family: var(--mono); font-size: 10px; letter-spacing: 0.18em;
- fill: var(--ink-3);
-}
-.topo-group { fill: var(--accent-soft); stroke: var(--border-strong);
stroke-dasharray: 4 4; }
-.topo-group-label { font-family: var(--mono); font-size: 11px; font-weight:
600; fill: var(--ink-2); }
-.topo-group-count { font-family: var(--mono); font-size: 10px; fill:
var(--ink-3); }
-
-.topo-node { cursor: pointer; }
-.topo-node-box {
- fill: var(--surface);
- stroke: var(--border-strong);
- transition: stroke var(--speed);
-}
-.topo-node:hover .topo-node-box { stroke: var(--accent); }
-.topo-node.is-selected .topo-node-box { stroke: var(--accent); stroke-width:
2; }
-.topo-node.status-ok .topo-node-strip { fill: var(--ok); }
-.topo-node.status-warn .topo-node-strip { fill: var(--warn); }
-.topo-node.status-err .topo-node-strip { fill: var(--err); }
-.topo-node.status-unknown .topo-node-strip { fill: var(--unk); opacity: 0.45; }
-.topo-node-label { font-size: 12px; font-weight: 600; fill: var(--ink); }
-.topo-node-sub { font-family: var(--mono); font-size: 9.5px; fill:
var(--ink-3); }
-.topo-icon {
- stroke: var(--ink-3); stroke-width: 1.6; fill: none;
- stroke-linecap: round; stroke-linejoin: round;
- overflow: visible;
-}
-.topo-node.status-ok .topo-icon { stroke: var(--ok); }
-.topo-node.status-warn .topo-icon { stroke: var(--warn); }
-.topo-node.status-err .topo-icon { stroke: var(--err); }
-
-.topo-edge-line { fill: none; stroke: var(--axis); stroke-width: 1.5; }
-.topo-edge.is-degraded .topo-edge-line { stroke: var(--warn);
stroke-dasharray: 5 4; }
-.topo-edge-label { font-family: var(--mono); font-size: 9.5px; fill:
var(--ink-3); }
-.topo-edge.is-degraded .topo-edge-label { fill: var(--warn); }
-
-.topo-more-box { fill: var(--surface-2); stroke: var(--border-strong);
stroke-dasharray: 3 3; }
-.topo-more { cursor: pointer; }
-.topo-more-label { font-family: var(--mono); font-size: 10.5px; fill:
var(--ink-2); }
-
-.is-dim { opacity: 0.18; }
-.topo-node.is-hit .topo-node-box { stroke: var(--accent); stroke-width: 2; }
-
-.topo-legend {
- position: absolute; left: 12px; bottom: 10px;
- display: flex; gap: 14px; flex-wrap: wrap;
- font-size: 11px; color: var(--ink-2);
- background: color-mix(in srgb, var(--surface) 85%, transparent);
- border: 1px solid var(--border);
- border-radius: var(--r-sm);
- padding: 5px 10px;
-}
-.topo-legend > span { display: inline-flex; align-items: center; gap: 6px; }
-
/* --- responsive
-----------------------------------------------------------------------------------
*/
@media (max-width: 1360px) {
diff --git a/dubbod/gui/resources/data/topology.js
b/dubbod/gui/resources/data/topology.js
deleted file mode 100644
index 26e57b32..00000000
--- a/dubbod/gui/resources/data/topology.js
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Config-plane topology — the console's signature view.
- *
- * Every node and edge is a fact from GET api/overview: registries sync into
- * dubbod, dubbod provisions gateways and distributes xDS to namespaces of
- * mesh services. There is no request-level telemetry feed yet, so edges carry
- * relationship + state, never invented traffic numbers.
- */
-
-const html = window.html;
-const { useState, useMemo, useRef, useEffect, useCallback } = window;
-
-const NODE_W = 168;
-const NODE_H = 46;
-const CP_W = 200;
-const CP_H = 92;
-const COL_X = [24, 320, 640];
-const GROUP_PAD = 14;
-const GROUP_HEAD = 30;
-const ROW_GAP = 10;
-const MAX_VISIBLE = 8;
-
-const statusOf = {
- registry: (r) => (r.synced ? "ok" : "warn"),
- gateway: (g) => (g.isReady ? "ok" : (g.readyReplicas || 0) === 0 ? "err" :
"warn"),
- controlplane: (instances) => {
- const ready = instances.filter((i) => i.isReady).length;
- if (instances.length === 0 || ready === 0) return "err";
- return ready === instances.length ? "ok" : "warn";
- },
-};
-
-export const buildGraph = (data, { expanded, namespaceFilter }) => {
- const nodes = [];
- const edges = [];
-
- const registries = data.registries || [];
- const gateways = data.gatewayInstances || [];
- const instances = data.instances || [];
- const services = (data.services || []).filter(
- (s) => !namespaceFilter || s.namespace === namespaceFilter
- );
-
- // Column 1: registries then gateways.
- let y = 56;
- registries.forEach((r) => {
- nodes.push({
- id: `registry:${r.provider}/${r.cluster}`, type: "registry", label:
r.provider,
- sub: r.cluster, status: statusOf.registry(r), x: COL_X[0], y, w: NODE_W,
h: NODE_H, data: r,
- });
- y += NODE_H + 18;
- });
- y += 40;
- const gatewayBandY = y;
- gateways.forEach((g) => {
- nodes.push({
- id: `gateway:${g.namespace}/${g.name}`, type: "gateway", label:
g.gatewayName || g.name,
- sub: `${g.namespace} · ${g.readyReplicas || 0}/${g.desiredReplicas || 0}
ready`,
- status: statusOf.gateway(g), x: COL_X[0], y, w: NODE_W, h: NODE_H, data:
g,
- });
- y += NODE_H + 18;
- });
- const col1H = y;
-
- // Column 3: namespace groups of services.
- const byNamespace = new Map();
- services.forEach((s) => {
- if (!byNamespace.has(s.namespace)) byNamespace.set(s.namespace, []);
- byNamespace.get(s.namespace).push(s);
- });
- let gy = 56;
- const groups = [];
- [...byNamespace.entries()].sort((a, b) =>
a[0].localeCompare(b[0])).forEach(([ns, list]) => {
- const isExpanded = expanded.has(ns);
- const visible = isExpanded ? list : list.slice(0, MAX_VISIBLE);
- const hidden = list.length - visible.length;
- const rows = visible.length + (hidden > 0 || isExpanded ? 1 : 0);
- const h = GROUP_HEAD + GROUP_PAD + rows * (NODE_H + ROW_GAP);
- const group = { ns, x: COL_X[2], y: gy, w: NODE_W + GROUP_PAD * 2, h,
count: list.length };
- groups.push(group);
- visible.forEach((s, i) => {
- nodes.push({
- id: `service:${s.hostname}`, type: "service", label: s.name ||
s.hostname,
- sub: s.registry + (s.meshExternal ? " · external" : ""), status:
"unknown",
- x: group.x + GROUP_PAD, y: gy + GROUP_HEAD + GROUP_PAD + i * (NODE_H +
ROW_GAP),
- w: NODE_W, h: NODE_H, data: s, group: ns,
- });
- });
- if (hidden > 0 || isExpanded) {
- nodes.push({
- id: `more:${ns}`, type: "more", label: hidden > 0 ? `+${hidden} more`
: "collapse",
- x: group.x + GROUP_PAD, y: gy + GROUP_HEAD + GROUP_PAD +
visible.length * (NODE_H + ROW_GAP),
- w: NODE_W, h: 30, group: ns,
- });
- }
- gy += h + 26;
- });
- const col3H = gy;
-
- // Column 2: control plane vertically centered against the taller side.
- const totalH = Math.max(col1H, col3H, 320);
- const cp = {
- id: "controlplane", type: "controlplane", label: "dubbod",
- sub: `${instances.filter((i) => i.isReady).length}/${instances.length}
ready · ${data.counts?.xdsConnections ?? 0} xDS conns`,
- status: statusOf.controlplane(instances),
- x: COL_X[1], y: totalH / 2 - CP_H / 2, w: CP_W, h: CP_H,
- data: { instances, version: data.version, namespace: data.namespace },
- };
- nodes.push(cp);
-
- // Edges — all real relationships.
- nodes.filter((n) => n.type === "registry").forEach((n) => {
- edges.push({ id: `e:${n.id}`, from: n, to: cp, kind: "sync", ok:
n.data.synced, label: n.data.synced ? "synced" : "syncing" });
- });
- nodes.filter((n) => n.type === "gateway").forEach((n) => {
- edges.push({ id: `e:${n.id}`, from: cp, to: n, kind: "provision", ok:
n.data.isReady, label: "provisions" });
- });
- groups.forEach((g) => {
- edges.push({
- id: `e:ns:${g.ns}`, from: cp, kind: "xds", ok: true, label: `xDS ·
${g.count} svc`,
- toPoint: { x: g.x, y: g.y + GROUP_HEAD / 2 + 8 }, group: g.ns,
- });
- });
-
- return { nodes, edges, groups, width: COL_X[2] + NODE_W + GROUP_PAD * 2 +
40, height: totalH + 40, gatewayBandY };
-};
-
-const edgePath = (x1, y1, x2, y2) => {
- const mx = (x1 + x2) / 2;
- return `M${x1},${y1} C${mx},${y1} ${mx},${y2} ${x2},${y2}`;
-};
-
-const NodeIcon = ({ type }) => {
- switch (type) {
- case "registry":
- return html`<svg x="9" y="9" width="14" height="14" viewBox="0 0 24 24"
class="topo-icon"><ellipse cx="12" cy="6" rx="8" ry="3" fill="none"/><path
d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6" fill="none"/><path d="M4 12c0 1.7 3.6 3
8 3s8-1.3 8-3" fill="none"/></svg>`;
- case "controlplane":
- return html`<svg x="9" y="9" width="14" height="14" viewBox="0 0 24 24"
class="topo-icon"><polygon points="12 2 2 7 12 12 22 7 12 2"
fill="none"/><polyline points="2 17 12 22 22 17" fill="none"/><polyline
points="2 12 12 17 22 12" fill="none"/></svg>`;
- case "gateway":
- return html`<svg x="9" y="9" width="14" height="14" viewBox="0 0 24 24"
class="topo-icon"><path d="M4 21V8l8-5 8 5v13" fill="none"/><path d="M9
21v-6h6v6" fill="none"/></svg>`;
- default:
- return html`<svg x="9" y="9" width="14" height="14" viewBox="0 0 24 24"
class="topo-icon"><circle cx="12" cy="12" r="9" fill="none"/><circle cx="12"
cy="12" r="3.5" fill="none"/></svg>`;
- }
-};
-
-export const Topology = ({ data, onSelect, selectedId }) => {
- const [transform, setTransform] = useState({ x: 0, y: 0, k: 1 });
- const [expanded, setExpanded] = useState(new Set());
- const [namespaceFilter, setNamespaceFilter] = useState("");
- const [query, setQuery] = useState("");
- const [fullscreen, setFullscreen] = useState(false);
- const [dragOffsets, setDragOffsets] = useState(new Map());
- const containerRef = useRef(null);
- const gesture = useRef(null);
-
- const namespaces = useMemo(
- () => [...new Set((data.services || []).map((s) => s.namespace))].sort(),
- [data]
- );
-
- const graph = useMemo(
- () => buildGraph(data, { expanded, namespaceFilter }),
- [data, expanded, namespaceFilter]
- );
-
- const offset = (node) => dragOffsets.get(node.id) || { dx: 0, dy: 0 };
- const nx = (node) => node.x + offset(node).dx;
- const ny = (node) => node.y + offset(node).dy;
-
- const matches = (node) => {
- if (!query) return true;
- const q = query.toLowerCase();
- return [node.label, node.sub, node.data?.hostname, node.data?.cluster]
- .some((v) => v && String(v).toLowerCase().includes(q));
- };
-
- const fit = useCallback(() => {
- const el = containerRef.current;
- if (!el) return;
- const k = Math.min(el.clientWidth / graph.width, el.clientHeight /
graph.height, 1.4);
- setTransform({
- x: (el.clientWidth - graph.width * k) / 2,
- y: Math.max(8, (el.clientHeight - graph.height * k) / 2),
- k,
- });
- }, [graph.width, graph.height]);
-
- useEffect(() => { fit(); }, [fullscreen, namespaceFilter]);
- useEffect(() => { fit(); }, []);
-
- const onWheel = (event) => {
- event.preventDefault();
- const el = containerRef.current.getBoundingClientRect();
- const px = event.clientX - el.left;
- const py = event.clientY - el.top;
- setTransform((t) => {
- const k = Math.min(2.5, Math.max(0.3, t.k * (event.deltaY < 0 ? 1.12 :
0.89)));
- return { k, x: px - ((px - t.x) / t.k) * k, y: py - ((py - t.y) / t.k) *
k };
- });
- };
-
- const onPointerDown = (event, node) => {
- event.currentTarget.setPointerCapture?.(event.pointerId);
- gesture.current = {
- node, startX: event.clientX, startY: event.clientY,
- base: node ? { ...offset(node) } : { ...transform }, moved: false,
- };
- };
-
- const onPointerMove = (event) => {
- const g = gesture.current;
- if (!g) return;
- const dx = event.clientX - g.startX;
- const dy = event.clientY - g.startY;
- if (Math.abs(dx) + Math.abs(dy) > 3) g.moved = true;
- if (!g.moved) return;
- if (g.node) {
- setDragOffsets((prev) => {
- const next = new Map(prev);
- next.set(g.node.id, { dx: g.base.dx + dx / transform.k, dy: g.base.dy
+ dy / transform.k });
- return next;
- });
- } else {
- setTransform((t) => ({ ...t, x: g.base.x + dx, y: g.base.y + dy }));
- }
- };
-
- const onPointerUp = (event, node) => {
- const g = gesture.current;
- gesture.current = null;
- if (g && !g.moved && node) {
- if (node.type === "more") {
- setExpanded((prev) => {
- const next = new Set(prev);
- next.has(node.group) ? next.delete(node.group) :
next.add(node.group);
- return next;
- });
- } else {
- onSelect?.(node);
- }
- }
- };
-
- const cp = graph.nodes.find((n) => n.type === "controlplane");
-
- return html`
- <div class=${`topo ${fullscreen ? "topo-fullscreen" : ""}`}>
- <div class="topo-toolbar">
- <input class="input topo-search" placeholder="Locate node…"
value=${query} onInput=${(e) => setQuery(e.target.value)} />
- <select class="input topo-ns" value=${namespaceFilter} onChange=${(e)
=> setNamespaceFilter(e.target.value)}>
- <option value="">All namespaces</option>
- ${namespaces.map((ns) => html`<option key=${ns}
value=${ns}>${ns}</option>`)}
- </select>
- <span class="topo-toolbar-gap" />
- <span class="chip chip-gate" title="Request-level telemetry is not
connected; edges show configuration relationships only.">edge telemetry
n/a</span>
- <button class="btn btn-ghost" onClick=${() => setTransform((t) => ({
...t, k: Math.min(2.5, t.k * 1.2) }))} title="Zoom in">+</button>
- <button class="btn btn-ghost" onClick=${() => setTransform((t) => ({
...t, k: Math.max(0.3, t.k / 1.2) }))} title="Zoom out">−</button>
- <button class="btn btn-ghost" onClick=${fit} title="Fit to
view">Fit</button>
- <button class="btn btn-ghost" onClick=${() =>
setFullscreen(!fullscreen)}>${fullscreen ? "Exit" : "Fullscreen"}</button>
- </div>
-
- <div
- class="topo-canvas"
- ref=${containerRef}
- onWheel=${onWheel}
- onPointerDown=${(e) => { if (e.target === e.currentTarget ||
e.target.tagName === "svg") onPointerDown(e, null); }}
- onPointerMove=${onPointerMove}
- onPointerUp=${(e) => onPointerUp(e, null)}
- >
- <svg width="100%" height="100%">
- <g transform=${`translate(${transform.x},${transform.y})
scale(${transform.k})`}>
- <text x=${COL_X[0]} y="30" class="topo-band">REGISTRY PLANE</text>
- <text x=${COL_X[0]} y=${graph.gatewayBandY - 14}
class="topo-band">GATEWAY PLANE</text>
- <text x=${COL_X[1]} y="30" class="topo-band">CONTROL PLANE</text>
- <text x=${COL_X[2]} y="30" class="topo-band">WORKLOAD PLANE</text>
-
- ${graph.edges.map((e) => {
- const from = e.from;
- const x1 = nx(from) + from.w;
- const y1 = ny(from) + from.h / 2;
- const x2 = e.toPoint ? e.toPoint.x : nx(e.to);
- const y2 = e.toPoint ? e.toPoint.y : ny(e.to) + e.to.h / 2;
- // Registry edges enter dubbod's left side; others leave its
right.
- const flip = e.kind === "sync";
- const [ax1, ay1, ax2, ay2] = flip
- ? [nx(from) + from.w, y1, nx(e.to), ny(e.to) + e.to.h / 2]
- : [x1, y1, x2, y2];
- const dim = query && !(matches(from) || (e.to && matches(e.to)));
- return html`
- <g key=${e.id} class=${`topo-edge ${e.ok ? "" : "is-degraded"}
${dim ? "is-dim" : ""}`}>
- <path d=${edgePath(ax1, ay1, ax2, ay2)}
class="topo-edge-line" />
- <text x=${(ax1 + ax2) / 2} y=${(ay1 + ay2) / 2 - 6}
class="topo-edge-label" text-anchor="middle">${e.label}</text>
- </g>
- `;
- })}
-
- ${graph.groups.map((g) => html`
- <g key=${g.ns} class=${query && ![...graph.nodes].some((n) =>
n.group === g.ns && matches(n)) ? "is-dim" : ""}>
- <rect x=${g.x} y=${g.y} width=${g.w} height=${g.h} rx="10"
class="topo-group" />
- <text x=${g.x + GROUP_PAD} y=${g.y + 20}
class="topo-group-label">ns/${g.ns}</text>
- <text x=${g.x + g.w - GROUP_PAD} y=${g.y + 20}
class="topo-group-count" text-anchor="end">${g.count}</text>
- </g>
- `)}
-
- ${graph.nodes.map((node) => {
- if (node.type === "more") {
- return html`
- <g key=${node.id} class="topo-more"
transform=${`translate(${nx(node)},${ny(node)})`}
- onPointerDown=${(e) => { e.stopPropagation();
onPointerDown(e, node); }}
- onPointerMove=${onPointerMove}
- onPointerUp=${(e) => { e.stopPropagation();
onPointerUp(e, node); }}>
- <rect width=${node.w} height=${node.h} rx="6"
class="topo-more-box" />
- <text x=${node.w / 2} y="19" text-anchor="middle"
class="topo-more-label">${node.label}</text>
- </g>
- `;
- }
- const dim = query && !matches(node);
- const hit = query && matches(node);
- return html`
- <g key=${node.id}
- class=${`topo-node status-${node.status} ${selectedId ===
node.id ? "is-selected" : ""} ${dim ? "is-dim" : ""} ${hit ? "is-hit" : ""}`}
- transform=${`translate(${nx(node)},${ny(node)})`}
- onPointerDown=${(e) => { e.stopPropagation();
onPointerDown(e, node); }}
- onPointerMove=${onPointerMove}
- onPointerUp=${(e) => { e.stopPropagation(); onPointerUp(e,
node); }}>
- <rect width=${node.w} height=${node.h} rx="8"
class="topo-node-box" />
- <rect width="3" height=${node.h} rx="1.5"
class="topo-node-strip" />
- <${NodeIcon} type=${node.type} />
- <text x="30" y=${node.type === "controlplane" ? 34 : 19}
class="topo-node-label">${node.label}</text>
- ${node.sub && html`<text x="30" y=${node.type ===
"controlplane" ? 52 : 35} class="topo-node-sub">${node.sub}</text>`}
- ${node.type === "controlplane" && html`<text x="30" y="72"
class="topo-node-sub">${(node.data.version || "").split("-")[0]}</text>`}
- </g>
- `;
- })}
- </g>
- </svg>
-
- <div class="topo-legend">
- <span><span class="dot status-ok" />ready / synced</span>
- <span><span class="dot status-warn" />degraded / syncing</span>
- <span><span class="dot status-err" />down</span>
- <span><span class="dot status-unknown" />no health feed
(services)</span>
- </div>
- </div>
- </div>
- `;
-};
diff --git a/manifests/charts/addons/grafana/files/manifests.yaml
b/manifests/charts/addons/grafana/files/manifests.yaml
index b9a56ef6..98651b8c 100644
--- a/manifests/charts/addons/grafana/files/manifests.yaml
+++ b/manifests/charts/addons/grafana/files/manifests.yaml
@@ -804,307 +804,6 @@ data:
],
"title": "XDS Reject Rate",
"type": "timeseries"
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 40
- },
- "id": 19,
- "panels": [],
- "title": "Gateway Traffic",
- "type": "row"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ops"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 0,
- "y": 41
- },
- "id": 20,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "sum by (route, cluster)
(rate(dxgate_http_route_requests_total[5m]))",
- "legendFormat": "{{route}} / {{cluster}}",
- "refId": "A"
- }
- ],
- "title": "HTTP Request Rate",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ops"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 8,
- "y": 41
- },
- "id": 21,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "sum by (route, cluster)
(rate(dxgate_http_route_failures_total[5m]))",
- "legendFormat": "{{route}} / {{cluster}}",
- "refId": "A"
- }
- ],
- "title": "HTTP Failure Rate",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ms"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 16,
- "y": 41
- },
- "id": 22,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "histogram_quantile(0.95, sum by (le, route, cluster)
(rate(dxgate_http_route_latency_ms_bucket[5m])))",
- "legendFormat": "{{route}} / {{cluster}}",
- "refId": "A"
- }
- ],
- "title": "HTTP Upstream P95",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ms"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 0,
- "y": 49
- },
- "id": 23,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "histogram_quantile(0.99, sum by (le, route, cluster)
(rate(dxgate_http_route_latency_ms_bucket[5m])))",
- "legendFormat": "{{route}} / {{cluster}}",
- "refId": "A"
- }
- ],
- "title": "HTTP Upstream P99",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ops"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 8,
- "y": 49
- },
- "id": 24,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "sum by (route, cluster, status_code)
(rate(dxgate_http_route_requests_total{status_code=~\"5..\"}[5m]))",
- "legendFormat": "{{route}} / {{cluster}} / {{status_code}}",
- "refId": "A"
- }
- ],
- "title": "HTTP 5xx Rate",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ops"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 16,
- "y": 49
- },
- "id": 25,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "sum by (route, cluster, status_code)
(rate(dxgate_http_route_requests_total{status_code=~\"401|403|429\"}[5m]))",
- "legendFormat": "{{route}} / {{cluster}} / {{status_code}}",
- "refId": "A"
- }
- ],
- "title": "Policy Rejection Rate",
- "type": "timeseries"
}
],
"refresh": "15s",
diff --git a/manifests/charts/addons/prometheus/files/manifests.yaml
b/manifests/charts/addons/prometheus/files/manifests.yaml
index 4301ab58..17f1d459 100644
--- a/manifests/charts/addons/prometheus/files/manifests.yaml
+++ b/manifests/charts/addons/prometheus/files/manifests.yaml
@@ -64,9 +64,6 @@ data:
scrape_interval: 15s
evaluation_interval: 15s
- rule_files:
- - /etc/prometheus/rules/*.yaml
-
scrape_configs:
- job_name: prometheus
static_configs:
@@ -106,60 +103,6 @@ data:
target_label: app
---
apiVersion: v1
-kind: ConfigMap
-metadata:
- name: prometheus-rules
- namespace: dubbo-system
- labels:
- app.kubernetes.io/name: prometheus
- app.kubernetes.io/part-of: dubbo-observability
-data:
- dubbo-alerts.yaml: |
- groups:
- - name: dubbo-observability
- rules:
- - alert: Dxgate5xxRateHigh
- expr: sum by (namespace, gateway, route, cluster)
(rate(dxgate_http_route_requests_total{status_code=~"5.."}[5m])) > 0
- for: 5m
- labels:
- severity: warning
- annotations:
- summary: dxgate 5xx responses are increasing
- description: dxgate is returning 5xx responses for one or more
Gateway routes.
- - alert: DxgateP95LatencyHigh
- expr: histogram_quantile(0.95, sum by (le, namespace, gateway, route,
cluster) (rate(dxgate_http_route_latency_ms_bucket[5m]))) > 1000
- for: 10m
- labels:
- severity: warning
- annotations:
- summary: dxgate P95 latency is above 1s
- description: Gateway upstream latency is above the sample threshold.
- - alert: DxgateNotReady
- expr: up{app="dxgate"} == 0
- for: 2m
- labels:
- severity: critical
- annotations:
- summary: dxgate metrics target is down
- description: Prometheus cannot scrape a dxgate pod or the pod is not
ready.
- - alert: DubbodXDSRejects
- expr: sum(rate(dubbod_total_xds_rejects[5m])) > 0
- for: 5m
- labels:
- severity: warning
- annotations:
- summary: xDS rejects are increasing
- description: One or more data-plane clients rejected xDS resources
from dubbod.
- - alert: DubbodSDSError
- expr: sum(rate(dubbod_sds_certificate_errors_total[5m])) > 0
- for: 5m
- labels:
- severity: warning
- annotations:
- summary: SDS certificate errors are increasing
- description: Workload certificate fetch or delivery is failing.
----
-apiVersion: v1
kind: Service
metadata:
name: prometheus
@@ -230,17 +173,11 @@ spec:
- name: config
mountPath: /etc/prometheus
readOnly: true
- - name: rules
- mountPath: /etc/prometheus/rules
- readOnly: true
- name: storage
mountPath: /prometheus
volumes:
- name: config
configMap:
name: prometheus
- - name: rules
- configMap:
- name: prometheus-rules
- name: storage
emptyDir: {}
diff --git a/samples/addons/grafana.yaml b/samples/addons/grafana.yaml
index b9a56ef6..98651b8c 100644
--- a/samples/addons/grafana.yaml
+++ b/samples/addons/grafana.yaml
@@ -804,307 +804,6 @@ data:
],
"title": "XDS Reject Rate",
"type": "timeseries"
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 40
- },
- "id": 19,
- "panels": [],
- "title": "Gateway Traffic",
- "type": "row"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ops"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 0,
- "y": 41
- },
- "id": 20,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "sum by (route, cluster)
(rate(dxgate_http_route_requests_total[5m]))",
- "legendFormat": "{{route}} / {{cluster}}",
- "refId": "A"
- }
- ],
- "title": "HTTP Request Rate",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ops"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 8,
- "y": 41
- },
- "id": 21,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "sum by (route, cluster)
(rate(dxgate_http_route_failures_total[5m]))",
- "legendFormat": "{{route}} / {{cluster}}",
- "refId": "A"
- }
- ],
- "title": "HTTP Failure Rate",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ms"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 16,
- "y": 41
- },
- "id": 22,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "histogram_quantile(0.95, sum by (le, route, cluster)
(rate(dxgate_http_route_latency_ms_bucket[5m])))",
- "legendFormat": "{{route}} / {{cluster}}",
- "refId": "A"
- }
- ],
- "title": "HTTP Upstream P95",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ms"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 0,
- "y": 49
- },
- "id": 23,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "histogram_quantile(0.99, sum by (le, route, cluster)
(rate(dxgate_http_route_latency_ms_bucket[5m])))",
- "legendFormat": "{{route}} / {{cluster}}",
- "refId": "A"
- }
- ],
- "title": "HTTP Upstream P99",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ops"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 8,
- "y": 49
- },
- "id": 24,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "sum by (route, cluster, status_code)
(rate(dxgate_http_route_requests_total{status_code=~\"5..\"}[5m]))",
- "legendFormat": "{{route}} / {{cluster}} / {{status_code}}",
- "refId": "A"
- }
- ],
- "title": "HTTP 5xx Rate",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "custom": {
- "drawStyle": "line",
- "fillOpacity": 10,
- "lineWidth": 1,
- "showPoints": "never"
- },
- "unit": "ops"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 16,
- "y": 49
- },
- "id": 25,
- "options": {
- "legend": {
- "calcs": [
- "lastNotNull"
- ],
- "displayMode": "table",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "sum by (route, cluster, status_code)
(rate(dxgate_http_route_requests_total{status_code=~\"401|403|429\"}[5m]))",
- "legendFormat": "{{route}} / {{cluster}} / {{status_code}}",
- "refId": "A"
- }
- ],
- "title": "Policy Rejection Rate",
- "type": "timeseries"
}
],
"refresh": "15s",
diff --git a/samples/addons/prometheus.yaml b/samples/addons/prometheus.yaml
index 4301ab58..17f1d459 100644
--- a/samples/addons/prometheus.yaml
+++ b/samples/addons/prometheus.yaml
@@ -64,9 +64,6 @@ data:
scrape_interval: 15s
evaluation_interval: 15s
- rule_files:
- - /etc/prometheus/rules/*.yaml
-
scrape_configs:
- job_name: prometheus
static_configs:
@@ -106,60 +103,6 @@ data:
target_label: app
---
apiVersion: v1
-kind: ConfigMap
-metadata:
- name: prometheus-rules
- namespace: dubbo-system
- labels:
- app.kubernetes.io/name: prometheus
- app.kubernetes.io/part-of: dubbo-observability
-data:
- dubbo-alerts.yaml: |
- groups:
- - name: dubbo-observability
- rules:
- - alert: Dxgate5xxRateHigh
- expr: sum by (namespace, gateway, route, cluster)
(rate(dxgate_http_route_requests_total{status_code=~"5.."}[5m])) > 0
- for: 5m
- labels:
- severity: warning
- annotations:
- summary: dxgate 5xx responses are increasing
- description: dxgate is returning 5xx responses for one or more
Gateway routes.
- - alert: DxgateP95LatencyHigh
- expr: histogram_quantile(0.95, sum by (le, namespace, gateway, route,
cluster) (rate(dxgate_http_route_latency_ms_bucket[5m]))) > 1000
- for: 10m
- labels:
- severity: warning
- annotations:
- summary: dxgate P95 latency is above 1s
- description: Gateway upstream latency is above the sample threshold.
- - alert: DxgateNotReady
- expr: up{app="dxgate"} == 0
- for: 2m
- labels:
- severity: critical
- annotations:
- summary: dxgate metrics target is down
- description: Prometheus cannot scrape a dxgate pod or the pod is not
ready.
- - alert: DubbodXDSRejects
- expr: sum(rate(dubbod_total_xds_rejects[5m])) > 0
- for: 5m
- labels:
- severity: warning
- annotations:
- summary: xDS rejects are increasing
- description: One or more data-plane clients rejected xDS resources
from dubbod.
- - alert: DubbodSDSError
- expr: sum(rate(dubbod_sds_certificate_errors_total[5m])) > 0
- for: 5m
- labels:
- severity: warning
- annotations:
- summary: SDS certificate errors are increasing
- description: Workload certificate fetch or delivery is failing.
----
-apiVersion: v1
kind: Service
metadata:
name: prometheus
@@ -230,17 +173,11 @@ spec:
- name: config
mountPath: /etc/prometheus
readOnly: true
- - name: rules
- mountPath: /etc/prometheus/rules
- readOnly: true
- name: storage
mountPath: /prometheus
volumes:
- name: config
configMap:
name: prometheus
- - name: rules
- configMap:
- name: prometheus-rules
- name: storage
emptyDir: {}