Skip to content

Commit 219f502

Browse files
committed
monitoring: add Cluster Nodes dashboard (per-node up/down via up{job=kubernetes-nodes})
node-exporter metrics vanish when a node is down (just one fewer line); this dashboard uses the kubelet-scrape up metric, which persists as 0 for an unreachable node, so a down node shows red instead of disappearing. Verified: cirrus=UP, thelio=DOWN.
1 parent d9a967d commit 219f502

3 files changed

Lines changed: 73 additions & 1 deletion

File tree

monitoring/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ the chart's insecure default — this is a public ingress). Retrieve it:
6464
**tuned to this cluster's metric labels so they show data** — the community
6565
dashboards 1860 (node) / 12239 (DCGM) assume different `job` labels and render
6666
No Data:
67+
- `grafana-dashboard-cluster.yaml`**Cluster Nodes**: per-node UP/DOWN via
68+
`up{job="kubernetes-nodes"}` (stays red when a node is unreachable, unlike
69+
node-exporter metrics that just vanish) + up/down history.
6770
- `grafana-dashboard-smart.yaml`**Drive Health (SMART)**: NVMe wear %, temp,
6871
spare, errors, SATA attributes.
6972
- `grafana-dashboard-node.yaml`**Node Host Health**: CPU, memory, load,
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Cluster Nodes dashboard (as-code) — at-a-glance node up/down. Uses
2+
# `up{job="kubernetes-nodes"}` (the kubelet scrape): it reports 1/0 PER NODE and,
3+
# unlike node-exporter metrics, the target PERSISTS as 0 when a node is
4+
# unreachable (nodes come from k8s service discovery), so a down node shows red
5+
# rather than just disappearing. Auto-loaded by the Grafana sidecar.
6+
#
7+
# Note: this reflects kubelet reachability. A node that's up but cordoned still
8+
# reads UP here (kubelet is fine) — distinguishing Ready/cordoned/pressure needs
9+
# kube-state-metrics (currently disabled in prometheus-values.yaml).
10+
apiVersion: v1
11+
kind: ConfigMap
12+
metadata:
13+
name: grafana-dashboard-cluster
14+
namespace: monitoring
15+
labels:
16+
grafana_dashboard: "1"
17+
annotations:
18+
grafana_folder: "Hosts"
19+
data:
20+
cluster.json: |
21+
{
22+
"uid": "cluster-nodes",
23+
"title": "Cluster Nodes",
24+
"tags": ["cluster", "nodes"],
25+
"timezone": "browser",
26+
"schemaVersion": 39,
27+
"refresh": "1m",
28+
"time": { "from": "now-24h", "to": "now" },
29+
"templating": { "list": [] },
30+
"annotations": { "list": [] },
31+
"panels": [
32+
{
33+
"id": 1, "type": "stat", "title": "Node status (kubelet reachable)",
34+
"datasource": { "type": "prometheus", "uid": "prometheus" },
35+
"gridPos": { "h": 8, "w": 18, "x": 0, "y": 0 },
36+
"targets": [ { "refId": "A", "expr": "up{job=\"kubernetes-nodes\"}", "legendFormat": "{{instance}}" } ],
37+
"fieldConfig": {
38+
"defaults": {
39+
"mappings": [
40+
{ "type": "value", "options": { "0": { "text": "DOWN", "color": "red" }, "1": { "text": "UP", "color": "green" } } }
41+
],
42+
"thresholds": { "mode": "absolute", "steps": [ { "color": "red", "value": null }, { "color": "green", "value": 1 } ] }
43+
},
44+
"overrides": []
45+
},
46+
"options": { "colorMode": "background", "graphMode": "none", "textMode": "value_and_name", "reduceOptions": { "calcs": ["lastNotNull"] } }
47+
},
48+
{
49+
"id": 2, "type": "stat", "title": "Nodes up / total",
50+
"datasource": { "type": "prometheus", "uid": "prometheus" },
51+
"gridPos": { "h": 8, "w": 6, "x": 18, "y": 0 },
52+
"targets": [
53+
{ "refId": "A", "expr": "sum(up{job=\"kubernetes-nodes\"})", "legendFormat": "up" },
54+
{ "refId": "B", "expr": "count(up{job=\"kubernetes-nodes\"})", "legendFormat": "total" }
55+
],
56+
"fieldConfig": { "defaults": { "unit": "short", "color": { "mode": "thresholds" }, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] } }, "overrides": [] },
57+
"options": { "colorMode": "value", "graphMode": "none", "textMode": "value_and_name", "reduceOptions": { "calcs": ["lastNotNull"] } }
58+
},
59+
{
60+
"id": 3, "type": "timeseries", "title": "Node up/down history (1 = up, 0 = down)",
61+
"datasource": { "type": "prometheus", "uid": "prometheus" },
62+
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 8 },
63+
"targets": [ { "refId": "A", "expr": "up{job=\"kubernetes-nodes\"}", "legendFormat": "{{instance}}" } ],
64+
"fieldConfig": { "defaults": { "unit": "short", "min": 0, "max": 1, "custom": { "drawStyle": "line", "lineInterpolation": "stepAfter", "fillOpacity": 15 } }, "overrides": [] },
65+
"options": { "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["min"] } }
66+
}
67+
]
68+
}

monitoring/install.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ kubectl apply -f smartctl-exporter.yaml
2525
# Dashboards (as-code; the Grafana sidecar auto-loads labelled ConfigMaps, so
2626
# applying before/after Grafana both work). Tuned to this cluster's metric
2727
# labels so they show data (community dashboards 1860/12239 do not — wrong job).
28-
kubectl apply -f grafana-dashboard-smart.yaml \
28+
kubectl apply -f grafana-dashboard-cluster.yaml \
29+
-f grafana-dashboard-smart.yaml \
2930
-f grafana-dashboard-node.yaml \
3031
-f grafana-dashboard-gpu.yaml
3132

0 commit comments

Comments
 (0)