Skip to content

Commit b59f269

Browse files
authored
Metrics whitelist for alloy (#418)
## Summary Comprehensive metrics cost control for the monitoring module — reduces cardinality, halves DPM billing, fixes broken scraping, and adds bad-node detection capability. ### Scrape interval standardization (halves DPM) - **All monitors now use 60s scrape interval** (Grafana recommendation) — directly halves data points per minute (DPM), reducing Grafana Cloud billing (billed at 95th percentile) - Changed: kubelet, cadvisor, KSM, DCGM, ARC controller, Karpenter, BuildKit, BuildKit HAProxy, Harbor, git-cache-central, git-cache DaemonSet, node-compactor - Exceptions: arc-listeners (3m), node-exporter/apiserver/coredns (already 60s) ### Metrics cardinality reduction - **Disable kubelet `/metrics/probes`** (`probes: false`) — drops ~1800 `prober_probe_*` series with no alerting value - **Karpenter**: replace broad regex with precise keep list (7 metrics) — saves ~400-500 series by dropping all histogram buckets and low-value gauges - **API server**: switch from drop-list to keep-whitelist (`apiserver_request_total`, `apiserver_request_terminations_total`) - **ARC controller**: add keep-whitelist (`gha_controller_.*`, `controller_runtime_reconcile_errors_total`) - **BuildKit**: drop `go_.*`, `process_.*`, `promhttp_.*`, and all `_bucket` histograms - **BuildKit HAProxy**: switch to keep-list (4 metrics: `haproxy_server_status`, `haproxy_server_current_sessions`, `haproxy_server_connection_errors_total`, `haproxy_backend_current_sessions`) - **Harbor**: drop `go_.*`, `process_.*`, `promhttp_.*` - **KSM**: explicit `keep` allowlist (RE2-compatible, no lookahead): - daemonset: 4 health status metrics only - deployment: 5 core metrics (replica status + conditions + desired count) - namespace/node/statefulset/pv/hpa/job: all (low cardinality) - pod: `kube_pod_info` + error indicators + non-routine status reasons - Drop successful terminations (exit code 0, reason Completed) - Drop routine status reasons (Shutdown, NodeAffinity) - **Node-exporter**: keep-whitelist for CPU (idle/iowait only) and memory metrics - **Kubelet**: keep-whitelist for `kubelet_running_pods/containers` and `kubelet_node_name` - **Alloy cost_control**: drop `go_.*|process_.*|promhttp_.*|prometheus_operator_.*` globally, drop `kubernetes_feature_enabled`, drop ARC histogram buckets ### Fix KSM regex (RE2 compatibility) - Replace `(?!...)` negative lookahead with explicit `keep` allowlist — RE2 (used by Prometheus/Alloy relabeling) does not support lookahead syntax, causing all `kube_*` metrics to be silently dropped ### Bad node detection - **Keep `kube_pod_info`** in KSM metrics — provides `node` label to correlate pod failures with specific nodes: ```promql count( kube_pod_container_status_last_terminated_exitcode{container_exit_code!="0"} * on(namespace, pod) group_left(node) kube_pod_info ) by (node) ``` ### Fix arc-listeners PodMonitor (was returning no data) - **Namespace**: `arc-runners` → `arc-systems` (listener pods run in arc-systems) - **Label selector**: → `app.kubernetes.io/component: runner-scale-set-listener` - **Add metricRelabelings**: keep only important listener metrics, drop `instance` label - **Scrape interval**: 30s → 3m (reduce load, listener metrics change slowly) ### Documentation - Add comprehensive "What we collect" section to CLAUDE.md per source - Document 60s scrape interval standard, KSM keep-list, HAProxy keep-list, status_reason filtering
1 parent f63f995 commit b59f269

15 files changed

Lines changed: 158 additions & 37 deletions

osdc/modules/monitoring/CLAUDE.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# modules/monitoring/
2+
3+
## Key rules
4+
5+
- **All scrape intervals must be 60s** (Grafana recommendation — halves DPM billing). Only exception: arc-listeners at 3m. Do not add monitors below 60s without justification.
6+
- **KSM metricRelabelings use RE2** — no `(?!...)` lookahead support. Use explicit `keep` allowlists instead.
7+
- **Alloy is the only metrics pipeline** — no local Prometheus. PrometheusRule CRDs are synced to Grafana Cloud via `mimir.rules.kubernetes`.
8+
9+
## Metrics filtering layers
10+
11+
1. **`--metric-allowlist`** (KSM server-side) — controls which resource groups KSM generates
12+
2. **`metricRelabelings`** (ServiceMonitor/PodMonitor) — `keep` whitelists per source
13+
3. **Alloy `cost_control`** (before remote_write) — safety net for anything not filtered at source
14+
15+
## Bad node detection
16+
17+
`kube_pod_info` provides pod-to-node mapping. Join with error metrics to find bad nodes:
18+
19+
```promql
20+
count(
21+
kube_pod_container_status_last_terminated_exitcode{container_exit_code!="0"}
22+
* on(namespace, pod) group_left(node) kube_pod_info
23+
) by (node)
24+
```

osdc/modules/monitoring/deploy.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ EOF
111111
grafana/alloy
112112

113113
rm -f "$ALLOY_OVERRIDE"
114+
kubectl rollout restart deployment/alloy -n "$NAMESPACE"
115+
kubectl rollout status deployment/alloy -n "$NAMESPACE" --timeout=3m
114116
echo "Alloy installed — pushing metrics to Grafana Cloud."
115117
else
116118
echo "No grafana-cloud-credentials secret found, skipping Alloy (no remote metrics push)."

osdc/modules/monitoring/helm/alloy-values.yaml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,35 +47,37 @@ alloy:
4747
}
4848
4949
// Drop high-cardinality and low-value metrics before remote_write.
50+
// Most filtering is done at ServiceMonitor level (keep whitelists).
51+
// These rules catch anything that slips through from sources without per-monitor filtering.
5052
prometheus.relabel "cost_control" {
5153
forward_to = [prometheus.remote_write.grafana_cloud.receiver]
5254
53-
// Drop noisy cadvisor metrics (network, tasks, load avg, blkio, spec)
55+
// --- KSM: drop low-value metrics ---
5456
rule {
5557
action = "drop"
5658
source_labels = ["__name__"]
57-
regex = "container_network_(tcp|udp)_usage_total|container_tasks_state|container_cpu_load_average_10s|container_memory_failures_total"
59+
regex = "kube_.*_created|kube_.*_metadata_resource_version|kube_secret_.*|kube_configmap_.*|kube_endpoint_.*|kube_lease_.*"
5860
}
5961
60-
// Drop cadvisor lifecycle and spec metrics
62+
// --- drop misc high-cardinality metrics ---
6163
rule {
6264
action = "drop"
6365
source_labels = ["__name__"]
64-
regex = "container_blkio_device_usage_total|container_last_seen|container_start_time_seconds|container_spec_.*"
66+
regex = "kubernetes_feature_enabled"
6567
}
6668
67-
// Drop low-value KSM metrics
69+
// --- ARC histogram buckets (keep sum/count) ---
6870
rule {
6971
action = "drop"
7072
source_labels = ["__name__"]
71-
regex = "kube_.*_created|kube_.*_metadata_resource_version|kube_secret_.*|kube_configmap_.*|kube_endpoint_.*|kube_lease_.*"
73+
regex = "gha_job_(execution|startup)_duration_seconds_bucket"
7274
}
7375
74-
// Drop ARC histogram buckets (keep sum/count)
76+
// --- go/process/promhttp/prometheus_operator internals from any source ---
7577
rule {
7678
action = "drop"
7779
source_labels = ["__name__"]
78-
regex = "gha_job_(execution|startup)_duration_seconds_bucket"
80+
regex = "go_.*|process_.*|promhttp_.*|prometheus_operator_.*"
7981
}
8082
}
8183

osdc/modules/monitoring/helm/values.yaml

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,28 @@ kubeScheduler:
3030
kubeEtcd:
3131
enabled: false
3232

33+
# --- kubelet + cadvisor ServiceMonitor ---
34+
kubelet:
35+
enabled: true
36+
serviceMonitor:
37+
interval: 60s
38+
# Disable /metrics/probes scraping — generates ~1440 high-cardinality
39+
# prober_probe_duration_seconds_bucket series with no alerting value
40+
probes: false
41+
# cadvisor: keep only pod-level memory metrics (per pod/container series)
42+
# - container_memory_working_set_bytes: active memory used by pod; K8s uses this for OOM kill decisions
43+
# - container_memory_rss: physical memory (no cache); useful for spotting memory leaks
44+
# NOTE: these generate one series per container, so high pod churn (e.g. runners) increases cardinality
45+
cAdvisorMetricRelabelings:
46+
- action: keep
47+
sourceLabels: [__name__]
48+
regex: "container_memory_working_set_bytes|container_memory_rss"
49+
# kubelet: keep only running pod/container counts
50+
metricRelabelings:
51+
- action: keep
52+
sourceLabels: [__name__]
53+
regex: "kubelet_running_(pods|containers)|kubelet_node_name"
54+
3355
# --- Grafana (DISABLED — use Grafana Cloud) ---
3456
grafana:
3557
enabled: false
@@ -66,11 +88,48 @@ prometheus-node-exporter:
6688
prometheus:
6789
monitor:
6890
interval: 60s
91+
metricRelabelings:
92+
# Keep only CPU (idle/iowait) and memory — drop everything else
93+
- action: keep
94+
sourceLabels: [__name__]
95+
regex: "node_cpu_seconds_total|node_memory_MemAvailable_bytes|node_memory_MemTotal_bytes"
96+
- action: drop
97+
sourceLabels: [__name__, mode]
98+
regex: "node_cpu_seconds_total;(user|system|nice|irq|softirq|steal)"
6999

70100
# --- kube-state-metrics ---
71101
kube-state-metrics:
72102
extraArgs:
73-
- --metric-allowlist=kube_(daemonset|deployment|pod|namespace|node|statefulset|persistentvolume|horizontalpodautoscaler|replicaset|job)_.+
103+
- --metric-allowlist=kube_(daemonset|deployment|pod|namespace|node|statefulset|persistentvolume|horizontalpodautoscaler|job)_.+
104+
prometheus:
105+
monitor:
106+
interval: 60s
107+
metricRelabelings:
108+
# Keep only the KSM metrics we actually use (single rule — multiple keeps are ANDed).
109+
# RE2 engine (no lookahead), so we use an explicit allowlist.
110+
# daemonset: health status (desired/ready/available/unavailable)
111+
# deployment: replica status + conditions + desired count
112+
# namespace/node/statefulset/pv/hpa/job: all metrics (low cardinality)
113+
# pod: info (node mapping), error indicators, status reasons
114+
- action: keep
115+
sourceLabels: [__name__]
116+
regex: "kube_daemonset_status_(desired_number_scheduled|number_ready|number_available|number_unavailable)|kube_deployment_status_(replicas_ready|replicas_available|replicas_unavailable|condition)|kube_deployment_spec_replicas|kube_(namespace|node|statefulset|persistentvolume|horizontalpodautoscaler|job)_.*|kube_pod_info|kube_pod_container_status_last_terminated_reason|kube_pod_container_status_last_terminated_exitcode|kube_pod_status_reason"
117+
# Drop low-value node metrics
118+
- action: drop
119+
sourceLabels: [__name__]
120+
regex: "kube_node_status_addresses"
121+
# Drop successful terminations (only keep actual errors)
122+
- action: drop
123+
sourceLabels: [__name__, reason]
124+
regex: "kube_pod_container_status_last_terminated_reason;Completed"
125+
- action: drop
126+
sourceLabels: [__name__, container_exit_code]
127+
regex: "kube_pod_container_status_last_terminated_exitcode;0"
128+
# Drop routine pod status reasons (spot reclaim, scheduling changes)
129+
# Keep only non-routine errors: Evicted, NodeLost, UnexpectedAdmissionError
130+
- action: drop
131+
sourceLabels: [__name__, reason]
132+
regex: "kube_pod_status_reason;(Shutdown|NodeAffinity)"
74133
tolerations:
75134
- key: CriticalAddonsOnly
76135
operator: Equal

osdc/modules/monitoring/kubernetes/monitors/dcgm-servicemonitor.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ spec:
1414
app.kubernetes.io/name: dcgm-exporter
1515
endpoints:
1616
- port: metrics
17-
interval: 30s
17+
interval: 60s
1818
metricRelabelings:
1919
- action: labeldrop
2020
regex: UUID

osdc/modules/monitoring/kubernetes/monitors/podmonitors/arc-listeners.yaml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,20 @@ metadata:
88
spec:
99
namespaceSelector:
1010
matchNames:
11-
- arc-runners
11+
- arc-systems
1212
selector:
1313
matchLabels:
14-
actions.github.com/scale-set-namespace: arc-runners
14+
app.kubernetes.io/component: runner-scale-set-listener
1515
podMetricsEndpoints:
1616
- port: metrics
17-
interval: 30s
17+
interval: 3m
1818
scrapeTimeout: 10s
19+
metricRelabelings:
20+
# Keep only operationally important listener metrics
21+
# Drops low-value gauges (idle/busy/registered/min/max runners)
22+
- action: keep
23+
sourceLabels: [__name__]
24+
regex: "gha_assigned_jobs|gha_completed_jobs_total|gha_started_jobs_total|gha_running_jobs|gha_job_execution_duration_seconds_(sum|count)|gha_job_startup_duration_seconds_(sum|count)"
25+
# Drop instance label — pod IP changes on restart, adds no value
26+
- action: labeldrop
27+
regex: "instance"

osdc/modules/monitoring/kubernetes/monitors/podmonitors/git-cache-daemonset.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ spec:
1414
app: git-cache-warmer
1515
podMetricsEndpoints:
1616
- port: metrics
17-
interval: 30s
17+
interval: 60s

osdc/modules/monitoring/kubernetes/monitors/servicemonitors/apiserver.yaml

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,7 @@ spec:
2424
serverName: kubernetes
2525
insecureSkipVerify: false
2626
metricRelabelings:
27-
# Drop Go runtime metrics (high cardinality, low operational value)
28-
- action: drop
27+
# Keep only essential API server health metrics, drop everything else
28+
- action: keep
2929
sourceLabels: [__name__]
30-
regex: "go_.*"
31-
# Drop process metrics (CPU/memory of the API server process itself)
32-
- action: drop
33-
sourceLabels: [__name__]
34-
regex: "process_.*"
35-
# Drop internal workqueue metrics (very noisy, many queues)
36-
- action: drop
37-
sourceLabels: [__name__]
38-
regex: "workqueue_.*"
39-
# Drop high-cardinality histogram buckets (keep _sum/_count for averages)
40-
- action: drop
41-
sourceLabels: [__name__]
42-
regex: >-
43-
apiserver_request_duration_seconds_bucket|apiserver_request_sli_duration_seconds_bucket|apiserver_response_sizes_bucket|apiserver_watch_events_sizes_bucket|apiserver_admission_controller_admission_duration_seconds_bucket
30+
regex: "apiserver_request_total|apiserver_request_terminations_total"

osdc/modules/monitoring/kubernetes/monitors/servicemonitors/arc-controller.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,9 @@ spec:
1414
app.kubernetes.io/name: gha-rs-controller
1515
endpoints:
1616
- port: metrics
17-
interval: 30s
17+
interval: 60s
18+
metricRelabelings:
19+
# Keep only ARC operational metrics and reconcile errors — drop everything else
20+
- action: keep
21+
sourceLabels: [__name__]
22+
regex: "gha_controller_.*|controller_runtime_reconcile_errors_total"

osdc/modules/monitoring/kubernetes/monitors/servicemonitors/buildkit-haproxy.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,9 @@ spec:
1515
app.kubernetes.io/component: load-balancer
1616
endpoints:
1717
- port: metrics
18-
interval: 30s
18+
interval: 60s
19+
metricRelabelings:
20+
# Keep only operationally important HAProxy metrics
21+
- action: keep
22+
sourceLabels: [__name__]
23+
regex: "haproxy_server_status|haproxy_server_current_sessions|haproxy_server_connection_errors_total|haproxy_backend_current_sessions"

0 commit comments

Comments
 (0)