Wire kube-apiserver and kubelet tracing to a Jaeger collector on docker-29, deploy Grafana Alloy in-cluster to ship logs/metrics, and stand up Mimir + Loki on docker-30 as their backing stores. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.0 KiB
Alloy → Mimir: scrape Kubernetes metrics + Prometheus Operator monitors
On execution, also copy this file into the repo per CLAUDE.md convention:
docs/plans/2026-07-08-HHMM-alloy-metrics-to-mimir.md(getHHMMviadate "+%H%M").
Context
Mimir is now up on docker-30 (http://192.168.0.30:9009, single-tenant, plain
HTTP over LAN) but nothing in the cluster pushes metrics to it. Grafana Alloy
already exists in the repo but is logs-only (ships pod/journal logs + k8s
events to Loki) and — importantly — the entire gitops/home-kubernetes/alloy/
directory is not wired into Flux yet.
kube-prometheus-stack is deployed and active: it provides the Prometheus Operator CRDs (ServiceMonitor / PodMonitor), kube-state-metrics, node-exporter, and ServiceMonitors for all standard targets (kubelet, cAdvisor, apiserver, coredns, etc.). Its bundled Prometheus scrapes everything into a local 60d TSDB but does not remote_write anywhere.
Goal: add a dedicated alloy-metrics instance that scrapes standard
Kubernetes metrics and reads ServiceMonitors/PodMonitors from the Prometheus
Operator, remote-writing to Mimir — additive alongside the existing Prometheus
(no changes to kube-prometheus-stack). Because kube-prometheus-stack already
ships ServiceMonitors for every standard target, a single
prometheus.operator.servicemonitors + prometheus.operator.podmonitors pair
satisfies both requirements at once. As part of wiring the alloy/ dir into
Flux, the existing alloy-logs and alloy-events releases also come under
GitOps management (confirmed desired).
Decisions confirmed: additive/coexist with bundled Prometheus; activate all three Alloy instances (logs, events, metrics).
Design
- Metrics scraping runs as a single Deployment (
controller.type: deployment, 1 replica) — never a DaemonSet, or every cluster-wide target would be scraped once per node. This is why it must be a separate release fromalloy-logs(which is a DaemonSet). - Mirror the conventions already in
helmrelease_alloy-logs.yaml: chartalloy>=1.8.0 <2.0.0, imagetag: v1.17.1,rbac.create: false,alloy.enableReporting: false,driftDetection: enabled, install/upgraderetries: 3,interval: 30m, namespacemonitoring. - River config = three components:
prometheus.operator.servicemonitors "sm"— discovers/scrapes all ServiceMonitors cluster-wide (covers kubelet, cAdvisor, kube-state-metrics, node-exporter, apiserver, coredns, controller-manager, scheduler — i.e. the "standard kubernetes metrics").prometheus.operator.podmonitors "pm"— discovers/scrapes all PodMonitors.prometheus.remote_write "mimir"→http://192.168.0.30:9009/api/v1/push,external_labels = { cluster = "homelab" }(matches the logs config). Both operator componentsforward_to = [prometheus.remote_write.mimir.receiver].
- Reuse
cluster = "homelab"external label for consistency with the Loki side.
Files
New — gitops/home-kubernetes/alloy/helmrelease_alloy-metrics.yaml
HelmRelease alloy-metrics (ns monitoring), Deployment, serviceAccount
alloy-metrics, inline alloy.configMap.content with the three components
above. Modest resources (e.g. requests cpu 100m / mem 256Mi, limit mem 768Mi —
operator discovery + scrape buffers use more than the log shipper).
New — gitops/home-kubernetes/alloy/rbac_metrics-collector.yaml
The prometheus.operator.* components need read access beyond the existing
alloy-log-reader role. ClusterRole alloy-metrics-reader + ClusterRoleBinding
to SA alloy-metrics (monitoring), granting:
- core:
namespaces, nodes, nodes/metrics, nodes/proxy, services, endpoints, pods— get/list/watch discovery.k8s.io:endpointslices— get/list/watchnetworking.k8s.io:ingresses— get/list/watchmonitoring.coreos.com:servicemonitors, podmonitors, probes, scrapeconfigs— get/list/watch- nonResourceURLs
/metrics,/metrics/cadvisor— get (kubelet scraping)
(Kept as a separate file so the metrics RBAC is self-contained; the existing
rbac_log-collector.yaml stays untouched.)
New — gitops/home-kubernetes/alloy/kustomization.yaml
kind: Kustomization listing all manifests in the dir: helmrepository_grafana.yaml,
helmrelease_alloy-logs.yaml, helmrelease_allow-events.yaml,
helmrelease_alloy-metrics.yaml, rbac_log-collector.yaml,
rbac_metrics-collector.yaml. Makes the Flux build explicit/deterministic.
Modified — gitops/home-kubernetes/flux-system/extra-kustomizations.yaml
Append an alloy Flux Kustomization:
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: alloy
namespace: flux-system
spec:
interval: 10m0s
path: ./gitops/home-kubernetes/alloy
prune: true
sourceRef:
kind: GitRepository
name: flux-system
dependsOn:
- name: kube-prometheus # needs the monitoring ns + operator CRDs/ServiceMonitors
Verification
- Commit + push; force reconcile:
flux reconcile kustomization flux-system --with-source. kubectl get kustomizations -A—alloyReady=True.kubectl get helmreleases -n monitoring—alloy-metrics(+ logs/events) Ready.kubectl -n monitoring get pods -l app.kubernetes.io/instance=alloy-metricsRunning;kubectl -n monitoring logs deploy/alloy-metricsshows no RBACforbiddenerrors and targets discovered.- Alloy UI:
kubectl -n monitoring port-forward deploy/alloy-metrics 12345:12345→ openhttp://localhost:12345; confirm the two operator components list healthy targets andprometheus.remote_write.mimirshows successful sends. - Confirm Mimir is receiving: query it directly, e.g.
curl -s 'http://192.168.0.30:9009/prometheus/api/v1/query?query=up' | jq '.data.result | length'returns > 0 (or check the Grafana Mimir datasource forup,kube_pod_info,node_cpu_seconds_total,container_cpu_usage_seconds_total). - Sanity: a
cluster="homelab"label is present on the ingested series.