Add UnPoller for UniFi network metrics (#298)
## Summary - Deploy UnPoller as a k8s service on indri to export UniFi controller metrics to Prometheus - Custom-built container from forge mirror (`containers/unpoller/Dockerfile`) - Credentials pulled from 1Password via external-secrets - Prometheus scrape job added, docs and service-versions updated ## Test plan - [ ] Build container: `mise run container-release unpoller v2.34.0` - [ ] Update kustomization tag with built image tag - [ ] Deploy from branch: `argocd app set unpoller --revision feature/unpoller && argocd app sync unpoller` - [ ] Verify pod connects to UX7 controller (check logs) - [ ] Confirm `unpoller` target appears in Prometheus - [ ] Query `unifi_` metrics in Grafana 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #298
This commit is contained in:
parent
a29ced71b5
commit
4dc3e5cae2
12 changed files with 225 additions and 1 deletions
17
argocd/apps/unpoller.yaml
Normal file
17
argocd/apps/unpoller.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: unpoller
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
|
||||||
|
targetRevision: main
|
||||||
|
path: argocd/manifests/unpoller
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: monitoring
|
||||||
|
syncPolicy:
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
|
@ -90,6 +90,42 @@ spec:
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: sc-dashboard-volume
|
- name: sc-dashboard-volume
|
||||||
mountPath: /tmp/dashboards
|
mountPath: /tmp/dashboards
|
||||||
|
# Fetch UnPoller (UniFi) dashboards from forge mirror.
|
||||||
|
# Source: github.com/unpoller/dashboards (v2.0.0 Prometheus set)
|
||||||
|
- name: init-unpoller-dashboards
|
||||||
|
image: docker.io/library/alpine:kustomized
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["sh", "-c"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
BASE_URL="https://forge.ops.eblu.me/mirrors/unpoller-dashboards/raw/branch/master/v2.0.0"
|
||||||
|
DEST="/tmp/dashboards/UniFi"
|
||||||
|
mkdir -p "$DEST"
|
||||||
|
for f in \
|
||||||
|
# DPI dashboard requires DPI enabled on both UX7 and UnPoller
|
||||||
|
# "UniFi-Poller_ Client DPI - Prometheus.json" \
|
||||||
|
"UniFi-Poller_ Client Insights - Prometheus.json" \
|
||||||
|
"UniFi-Poller_ Network Sites - Prometheus.json" \
|
||||||
|
"UniFi-Poller_ UAP Insights - Prometheus.json" \
|
||||||
|
"UniFi-Poller_ USG Insights - Prometheus.json" \
|
||||||
|
"UniFi-Poller_ USW Insights - Prometheus.json" \
|
||||||
|
; do
|
||||||
|
wget -q -O "$DEST/$f" "$BASE_URL/$(echo "$f" | sed 's/ /%20/g')"
|
||||||
|
done
|
||||||
|
# Fix datasource UIDs to match our Prometheus instance
|
||||||
|
sed -i 's/"uid": *"bdkj55oguty4gd"/"uid": "prometheus"/g' "$DEST"/*.json
|
||||||
|
sed -i 's/"uid": *"\${DS_PROMETHEUS}"/"uid": "prometheus"/g' "$DEST"/*.json
|
||||||
|
echo "Fetched $(ls "$DEST" | wc -l) UnPoller dashboards"
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
volumeMounts:
|
||||||
|
- name: sc-dashboard-volume
|
||||||
|
mountPath: /tmp/dashboards
|
||||||
containers:
|
containers:
|
||||||
# Dashboard sidecar - watches ConfigMaps with grafana_dashboard=1
|
# Dashboard sidecar - watches ConfigMaps with grafana_dashboard=1
|
||||||
- name: grafana-sc-dashboard
|
- name: grafana-sc-dashboard
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,14 @@ scrape_configs:
|
||||||
- target_label: cluster
|
- target_label: cluster
|
||||||
replacement: indri
|
replacement: indri
|
||||||
|
|
||||||
|
# UniFi network metrics (via UnPoller exporter)
|
||||||
|
- job_name: "unpoller"
|
||||||
|
static_configs:
|
||||||
|
- targets: ["unpoller.monitoring.svc.cluster.local:9130"]
|
||||||
|
metric_relabel_configs:
|
||||||
|
- target_label: cluster
|
||||||
|
replacement: indri
|
||||||
|
|
||||||
# Frigate NVR metrics (via Caddy on indri — Frigate runs on ringtail)
|
# Frigate NVR metrics (via Caddy on indri — Frigate runs on ringtail)
|
||||||
- job_name: "frigate"
|
- job_name: "frigate"
|
||||||
scheme: https
|
scheme: https
|
||||||
|
|
|
||||||
42
argocd/manifests/unpoller/deployment.yaml
Normal file
42
argocd/manifests/unpoller/deployment.yaml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: unpoller
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
app: unpoller
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: unpoller
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: unpoller
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: unpoller
|
||||||
|
image: registry.ops.eblu.me/blumeops/unpoller:kustomized
|
||||||
|
ports:
|
||||||
|
- containerPort: 9130
|
||||||
|
name: metrics
|
||||||
|
env:
|
||||||
|
- name: UP_UNIFI_DEFAULT_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: unpoller-unifi
|
||||||
|
key: api-key
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/unpoller
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 32Mi
|
||||||
|
limits:
|
||||||
|
memory: 128Mi
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: unpoller-config
|
||||||
18
argocd/manifests/unpoller/external-secret.yaml
Normal file
18
argocd/manifests/unpoller/external-secret.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: unpoller-unifi
|
||||||
|
namespace: monitoring
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
name: onepassword-blumeops
|
||||||
|
target:
|
||||||
|
name: unpoller-unifi
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: api-key
|
||||||
|
remoteRef:
|
||||||
|
key: unpoller
|
||||||
|
property: credential
|
||||||
18
argocd/manifests/unpoller/kustomization.yaml
Normal file
18
argocd/manifests/unpoller/kustomization.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
namespace: monitoring
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- external-secret.yaml
|
||||||
|
|
||||||
|
images:
|
||||||
|
- name: registry.ops.eblu.me/blumeops/unpoller
|
||||||
|
newTag: v2.34.0-6b0005d
|
||||||
|
|
||||||
|
configMapGenerator:
|
||||||
|
- name: unpoller-config
|
||||||
|
files:
|
||||||
|
- up.conf
|
||||||
13
argocd/manifests/unpoller/service.yaml
Normal file
13
argocd/manifests/unpoller/service.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: unpoller
|
||||||
|
namespace: monitoring
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: unpoller
|
||||||
|
ports:
|
||||||
|
- port: 9130
|
||||||
|
targetPort: metrics
|
||||||
|
protocol: TCP
|
||||||
|
name: metrics
|
||||||
16
argocd/manifests/unpoller/up.conf
Normal file
16
argocd/manifests/unpoller/up.conf
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[prometheus]
|
||||||
|
http_listen = "0.0.0.0:9130"
|
||||||
|
report_errors = true
|
||||||
|
|
||||||
|
[influxdb]
|
||||||
|
disable = true
|
||||||
|
|
||||||
|
[unifi]
|
||||||
|
dynamic = false
|
||||||
|
|
||||||
|
[unifi.defaults]
|
||||||
|
# API key comes from environment variable: UP_UNIFI_DEFAULT_API_KEY
|
||||||
|
url = "https://192.168.1.1"
|
||||||
|
verify_ssl = false
|
||||||
|
save_sites = true
|
||||||
|
save_dpi = false
|
||||||
40
containers/unpoller/Dockerfile
Normal file
40
containers/unpoller/Dockerfile
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# UnPoller — UniFi metrics exporter for Prometheus
|
||||||
|
# Two-stage build: Go compilation, then minimal Alpine runtime
|
||||||
|
|
||||||
|
ARG CONTAINER_APP_VERSION=v2.34.0
|
||||||
|
|
||||||
|
FROM golang:alpine3.22 AS build
|
||||||
|
|
||||||
|
ARG CONTAINER_APP_VERSION
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
RUN git clone --depth 1 --branch ${CONTAINER_APP_VERSION} \
|
||||||
|
https://forge.ops.eblu.me/mirrors/unpoller.git /app
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
|
||||||
|
RUN go build -ldflags="-s -w \
|
||||||
|
-X main.version=${CONTAINER_APP_VERSION} \
|
||||||
|
-X main.builtBy=blumeops \
|
||||||
|
-X golift.io/version.Version=${CONTAINER_APP_VERSION} \
|
||||||
|
-X golift.io/version.Branch=HEAD \
|
||||||
|
-X golift.io/version.BuildUser=blumeops \
|
||||||
|
-X golift.io/version.Revision=blumeops-build" \
|
||||||
|
-o /bin/unpoller .
|
||||||
|
|
||||||
|
FROM alpine:3.22
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="UnPoller"
|
||||||
|
LABEL org.opencontainers.image.description="UniFi metrics exporter for Prometheus"
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/unpoller/unpoller"
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
|
||||||
|
COPY --from=build /bin/unpoller /usr/bin/unpoller
|
||||||
|
|
||||||
|
EXPOSE 9130
|
||||||
|
USER 65534:65534
|
||||||
|
ENTRYPOINT ["/usr/bin/unpoller"]
|
||||||
|
CMD ["--config", "/etc/unpoller/up.conf"]
|
||||||
1
docs/changelog.d/feature-unpoller.feature.md
Normal file
1
docs/changelog.d/feature-unpoller.feature.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Add UnPoller deployment to monitor UniFi network metrics via Prometheus
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: UniFi
|
title: UniFi
|
||||||
modified: 2026-02-24
|
modified: 2026-03-16
|
||||||
tags:
|
tags:
|
||||||
- infrastructure
|
- infrastructure
|
||||||
- networking
|
- networking
|
||||||
|
|
@ -69,6 +69,14 @@ Local admin account on the UX7. Credentials stored in 1Password (vault `blumeops
|
||||||
|
|
||||||
Attempted Feb 2026 with the `ubiquiti-community/unifi` Terraform provider via Pulumi. A "no-op" update on the default LAN network reset undeclared properties, bricking the network and requiring a factory reset. The provider ecosystem is too immature for single-device infrastructure.
|
Attempted Feb 2026 with the `ubiquiti-community/unifi` Terraform provider via Pulumi. A "no-op" update on the default LAN network reset undeclared properties, bricking the network and requiring a factory reset. The provider ecosystem is too immature for single-device infrastructure.
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
UniFi metrics are exported to Prometheus via [UnPoller](https://github.com/unpoller/unpoller), running as a k8s deployment in the `monitoring` namespace on indri. UnPoller polls the UX7 controller API using an API key and exposes metrics on port 9130.
|
||||||
|
|
||||||
|
- **Prometheus job:** `unpoller`
|
||||||
|
- **Metrics prefix:** `unifi_`
|
||||||
|
- **Credentials:** 1Password item `unpoller` (vault `blumeops`, API key)
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
- [[hosts]] — Device inventory
|
- [[hosts]] — Device inventory
|
||||||
|
|
|
||||||
|
|
@ -253,6 +253,13 @@ services:
|
||||||
upstream-source: https://code.forgejo.org/forgejo/runner/releases
|
upstream-source: https://code.forgejo.org/forgejo/runner/releases
|
||||||
notes: Forgejo runner on ringtail via nixpkgs; version tracks flake.lock
|
notes: Forgejo runner on ringtail via nixpkgs; version tracks flake.lock
|
||||||
|
|
||||||
|
- name: unpoller
|
||||||
|
type: argocd
|
||||||
|
last-reviewed: 2026-03-16
|
||||||
|
current-version: "v2.34.0"
|
||||||
|
upstream-source: https://github.com/unpoller/unpoller/releases
|
||||||
|
notes: UniFi metrics exporter for Prometheus
|
||||||
|
|
||||||
- name: forgejo
|
- name: forgejo
|
||||||
type: ansible
|
type: ansible
|
||||||
last-reviewed: 2026-02-22
|
last-reviewed: 2026-02-22
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue