Dashboards & Alerts
Grafana Operator
observability/base/grafana-operator/ installs grafana-operator (5.24.0),
but it does not deploy its own Grafana. Its Grafana custom resource
(grafana-victoriametrics.yaml) is configured as external, pointing at
the Grafana subchart already bundled inside victoria-metrics-k8s-stack:
apiVersion: grafana.integreatly.org/v1beta1
kind: Grafana
metadata:
name: grafana-victoriametrics
labels:
dashboards: "grafana"
spec:
external:
url: http://victoria-metrics-k8s-stack-grafana
adminPassword:
name: victoria-metrics-k8s-stack-grafana-envvars
key: GF_SECURITY_ADMIN_PASSWORDgrafana-operator’s actual job here is reconciling GrafanaFolder,
GrafanaDashboard, and GrafanaDatasource CRs against that one external
instance. Every such CR in this repo carries the same two fields:
instanceSelector.matchLabels.dashboards: "grafana" (matches the Grafana
CR’s own label above) and allowCrossNamespaceImport: true (folders and
dashboards are defined across apps, infrastructure, and observability
namespaces, all targeting the one Grafana in observability).
Folder registry
A repo-wide grep for kind: GrafanaFolder finds nine CRs. Eight reconcile onto
the cluster by default, each defined in the namespace that owns its
dashboards; the ninth (llm, apps/base/ai/llm/grafana-folder.yaml) only
applies once the opt-in LLM platform’s suspended umbrella Kustomization is
resumed (see CLAUDE.md’s Self-Hosted LLM Platform section) and is out of
scope for this always-on page:
| Folder | Defined in | Holds |
|---|---|---|
apps | apps | Demo “all-in-one” RED + trace/log correlation dashboard |
cilium | kube-system | Cilium agent/operator and Hubble dashboards |
databases | infrastructure | CloudNativePG query-performance and query-plan-correlation |
flux | flux-system | Flux cluster and control-plane dashboards |
kubernetes | infrastructure | Kubernetes views, node-exporter, Karpenter |
logs | observability | VictoriaLogs explorer, single/cluster overview |
runlore | observability | RunLore’s own dashboard |
traces | observability | VictoriaTraces overview |
Dashboard inventory
| Dashboard | Folder | Source |
|---|---|---|
kubernetes-views-{global,namespaces,nodes,pods} | kubernetes | Authored in-repo |
kubernetes-node-exporter-full | kubernetes | Authored in-repo |
kubernetes-karpenter | kubernetes | Imported, grafana.com dashboard 20398 |
app-all-in-one | apps | Authored in-repo |
runlore | runlore | Authored in-repo |
observability-victoria-logs-explorer | logs | Authored in-repo |
observability-victoria-logs-single | logs | Imported, dashboard 22084 |
observability-victoria-traces-single | traces | Imported, dashboard 24136 |
databases-cnpg-query-performance / -query-plan-correlation | databases | Authored in-repo — see PostgreSQL |
cilium-cilium, cilium-operator, cilium-hubble{,-dns-namespace,-l7-http-metrics,-network-overview-namespace} (6 dashboards) | cilium | Imported, from the cilium/cilium upstream dashboard JSON |
flux-cluster, flux-control-plane | flux | Imported, from fluxcd/flux2-monitoring-example |
victoria-logs and victoria-traces also self-ship a dashboard via their
own chart’s dashboards.enabled: true, grafanaOperator.enabled: true values,
targeting the same logs/traces folders as the manually-authored ones
above — two sourcing paths land in the same folder, which is redundant but
not a conflict (different dashboard names).
The victoria-metrics-k8s-stack chart’s own defaultDashboards ships into an
"observability" folder (not one of the GrafanaFolder CRs above — it’s
created implicitly by the chart’s sidecar mechanism, not grafana-operator).
Its Kubernetes-views duplicates of the kubernetes folder’s dashboards are
explicitly disabled in vm-common-helm-values-configmap.yaml to avoid
showing the same dashboard twice.
VictoriaTraces
observability/base/victoria-traces/ runs victoria-traces-single (0.1.11),
3-day retention, wired into Grafana as a Jaeger-protocol datasource:
# observability/base/victoria-traces/grafana-datasource.yaml
datasource:
type: jaeger
url: http://victoria-traces-vt-single-server.observability:10428/select/jaeger
jsonData:
tracesToLogs:
datasourceName: VictoriaLogs
tags: ['trace_id', 'traceId', 'traceID']
tracesToMetrics:
datasourceName: VictoriaMetrics
nodeGraph:
enabled: truetracesToLogs/tracesToMetrics are what let a Grafana user pivot from a
trace span straight to the matching log lines (via log.trace_id, per the
LogsQL rules)
or request-rate/latency panels, without re-typing a query by hand. The
manifest doesn’t configure an explicit receiver protocol (no OTLP toggles) —
VictoriaTraces’ chart defaults apply unmodified, so which ingest protocols
are actually enabled isn’t determinable from this repo alone.
Alerting
Two VMRule flavors coexist under the same operator.victoriametrics.com/v1beta1
API: implicit PromQL rules (the default) and explicit type: vlogs rules
evaluated as LogsQL — see loggen’s alert
for the LogsQL form. PromQL rules live mostly in
victoria-metrics-k8s-stack/vmrules/:
karpenter.yaml— 3 alerts (node-registration failures, nodepool near capacity, cloud-provider errors) for a component this stack doesn’t own but scrapes.openbao.yaml— 7 alerts (down, sealed, no active node, lost/at-risk Raft voter, snapshot job failed/stale), extensively commented with the incident history that motivated each one (an expired cert going unnoticed for 9 months; a stale Raft voter silently halving failure tolerance).runlore.yaml— 12 alerts covering the agent’s own health (down, no leader, split-brain), pipeline behavior (dropped/stalled/erroring investigations), and cost (token spend, model latency). Each carries arunbook_urlpointing at RunLore’s own docs. A byte-identicalPrometheusRuletwin ships alongside it so either metrics backend works; the file comments call out that the two must never drift independently.
# observability/base/victoria-metrics-k8s-stack/vmrules/runlore.yaml — trimmed
- alert: RunloreAgentDown
expr: absent(runlore_build_info)
for: 5m
labels:
severity: critical
annotations:
runbook_url: "https://github.com/Smana/runlore/blob/main/docs/observability.md#runloreagentdown"Alertmanager routing
Alertmanager’s routing tree (vm-common-helm-values-configmap.yaml, applied
to the victoria-metrics-k8s-stack alertmanager.spec.config) fans every
non-blackholed alert to both RunLore and Slack:
route:
receiver: "slack-monitoring"
routes:
- matchers:
- alertname =~ "InfoInhibitor|Watchdog|KubeCPUOvercommit"
receiver: "blackhole"
- receiver: "runlore"
continue: true # falls through to the next route instead of stopping
- receiver: "slack-monitoring"
receivers:
- name: "runlore"
webhook_configs:
- url: "http://runlore.runlore.svc:8080/webhook/alertmanager"
http_config:
authorization:
credentials_file: /etc/vm/secrets/runlore-webhook-token/tokenRunLore’s webhook requires a bearer token (its v0.2.0+ fail-closed
behavior — alert labels/annotations flow into an LLM prompt, so an
unauthenticated trigger path was judged unacceptable) mirrored between its
own runlore-webhook ExternalSecret and this stack’s
runlore-webhook-token ExternalSecret. The Slack receiver’s actions:
list defines five button blocks: Runbook, Query (the alert’s
GeneratorURL), Dashboard, and Silence — each sourced from the firing
alert’s own annotations — plus a fifth built from the Monzo template’s
link_button_text/link_url helpers. Whether that fifth button actually
renders on every message or only conditionally (e.g. when link_url is set)
can’t be determined from this repo — the Monzo template it calls into isn’t
vendored here, only referenced by name.
Grafana OnCall: built but not deployed
grafana-oncall is not running on this cluster. No Kustomization
anywhere in clusters/ or flux/ references
observability/base/grafana-oncall — confirmed by grepping both trees.
Everything below describes what the manifests configure, not observed
cluster behavior.The directory is a complete, “external-everything” OnCall install per
Grafana’s scalable Helm guide:
the oncall chart (1.16.5) for the engine + Celery workers, a companion
rabbitmq HelmRelease for the message broker, an SQLInstance claim for a
CloudNativePG-backed Postgres database, and a KVStore claim
(xplane-oncall, SPEC-012) for Valkey. Both oncall and oncall-rabbitmq
HTTPRoutes parent to platform-tailscale-admin, unlike the general-gateway
components covered elsewhere on this page.
Despite being undeployed, the active Grafana already carries a live
dependency on it: victoria-metrics-k8s-stack’s common values enable the
grafana-oncall-app plugin and provision it to call
http://oncall-engine:8080/ — a Service that does not exist on this
cluster. The plugin will show as installed but non-functional in Grafana
until grafana-oncall is wired into a Kustomization.
Neither the oncall nor the rabbitmq HelmRelease defines a
CiliumNetworkPolicy in this directory — oncall’s own networkPolicy.enabled
value is false, and rabbitmq’s is a plain Kubernetes NetworkPolicy
(chart comment: “To be replaced with cilium network policy”), not the
constitution-mandated Cilium one. If this component is ever wired in, that
gap needs closing first.
CiliumNetworkPolicy coverage
Of the nine component directories under observability/base/, only
runlore defines a CiliumNetworkPolicy:
# observability/base/runlore/ciliumnetworkpolicy-ingress.yaml
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: runlore
ingress:
- fromEntities: [ingress]
toPorts:
- ports: [{port: "8080", protocol: TCP}]It exists because runlore’s HTTPRoute parents to platform-public
(described in Private Access),
and this cluster’s shared cilium-envoy DaemonSet runs hostNetwork: true —
Gateway-originated traffic lands as Cilium’s reserved ingress entity, which
a namespaced Kubernetes NetworkPolicy can never match. grafana-operator,
grafana-oncall, kubernetes-event-exporter, loggen, metrics-server,
victoria-logs, victoria-metrics-k8s-stack, and victoria-traces have no
CiliumNetworkPolicy in these directories, and no cluster-wide default-deny
CiliumClusterwideNetworkPolicy covers the observability namespace either
— the one such policy in this repo
(infrastructure/base/gapi/allow-gateway-l7-proxy.yaml) only allows the
Gateway API L7 proxy, it does not deny anything by default.