Add UnPoller deployment for UniFi network metrics

Deploy UnPoller as a k8s service on indri to export UniFi controller
metrics to Prometheus. Custom-built container from forge mirror, with
credentials pulled from 1Password via external-secrets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-03-16 13:56:24 -07:00
commit 6b0005df1e
11 changed files with 199 additions and 1 deletions

17
argocd/apps/unpoller.yaml Normal file
View 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

View file

@ -72,6 +72,14 @@ scrape_configs:
- target_label: cluster
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)
- job_name: "frigate"
scheme: https

View file

@ -0,0 +1,47 @@
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_USER
valueFrom:
secretKeyRef:
name: unpoller-unifi
key: username
- name: UP_UNIFI_DEFAULT_PASS
valueFrom:
secretKeyRef:
name: unpoller-unifi
key: password
volumeMounts:
- name: config
mountPath: /etc/unpoller
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
volumes:
- name: config
configMap:
name: unpoller-config

View file

@ -0,0 +1,22 @@
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: username
remoteRef:
key: unifi
property: username
- secretKey: password
remoteRef:
key: unifi
property: password

View 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
configMapGenerator:
- name: unpoller-config
files:
- up.conf

View 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

View file

@ -0,0 +1,17 @@
[prometheus]
http_listen = "0.0.0.0:9130"
report_errors = true
[influxdb]
disable = true
[unifi]
dynamic = false
[unifi.defaults]
# Credentials come from environment variables:
# UP_UNIFI_DEFAULT_USER and UP_UNIFI_DEFAULT_PASS
url = "https://192.168.1.1"
verify_ssl = false
save_sites = true
save_dpi = false

View 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"]

View file

@ -0,0 +1 @@
Add UnPoller deployment to monitor UniFi network metrics via Prometheus

View file

@ -1,6 +1,6 @@
---
title: UniFi
modified: 2026-02-24
modified: 2026-03-16
tags:
- infrastructure
- 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.
## 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 a read-only local account (`blumeops`) and exposes metrics on port 9130.
- **Prometheus job:** `unpoller`
- **Metrics prefix:** `unifi_`
- **Credentials:** 1Password item `unifi` (vault `blumeops`)
## Related
- [[hosts]] — Device inventory

View file

@ -253,6 +253,13 @@ services:
upstream-source: https://code.forgejo.org/forgejo/runner/releases
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
type: ansible
last-reviewed: 2026-02-22