k8s: add Starlink dish prometheus exporter + dashboard

joshuasing/starlink_exporter 0.9.0 (Docker Hub; ghcr.io lags behind
upstream releases). Targets the dish's gRPC management interface at
192.168.100.1:9200, reachable from cluster pods via 10.0.15.254 while
the Starlink router is in bypass mode.

Grafana dashboard "Starlink" covers throughput, PoP ping latency/drop,
obstruction state + 24h total, GPS lock, and dish position/info — synced
to /var/lib/grafana/dashboards/ by the existing grafana role.
This commit is contained in:
Graham McIntire 2026-05-10 18:11:21 -05:00
parent 8466e4590f
commit 04b3855874
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
5 changed files with 284 additions and 0 deletions

View file

@ -0,0 +1,183 @@
{
"uid": "starlink",
"title": "Starlink",
"tags": ["network", "starlink"],
"schemaVersion": 38,
"version": 1,
"timezone": "",
"refresh": "30s",
"time": {"from": "now-6h", "to": "now"},
"editable": true,
"graphTooltip": 1,
"templating": {
"list": [
{
"name": "datasource",
"label": "Datasource",
"type": "datasource",
"query": "prometheus",
"current": {"text": "Prometheus", "value": "Prometheus", "selected": false},
"hide": 0,
"includeAll": false,
"multi": false,
"refresh": 1,
"skipUrlSync": false
}
]
},
"panels": [
{"type": "row", "id": 100, "title": "Overview", "collapsed": false, "gridPos": {"h": 1, "w": 24, "x": 0, "y": 0}},
{"type": "stat", "id": 1, "title": "Dish up", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 4, "x": 0, "y": 1},
"fieldConfig": {"defaults": {
"mappings": [{"type": "value", "options": {"0": {"text": "DOWN", "color": "red"}, "1": {"text": "UP", "color": "green"}}}],
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute", "steps": [{"color":"red","value":null},{"color":"green","value":1}]}
}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]}, "graphMode":"none","colorMode":"background","textMode":"value"},
"targets": [{"expr": "starlink_dish_up", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 2, "title": "Uptime", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 4, "x": 4, "y": 1},
"fieldConfig": {"defaults": {"unit":"s","color":{"mode":"thresholds"},
"thresholds":{"mode":"absolute","steps":[{"color":"red","value":null},{"color":"green","value":300}]}}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]}, "graphMode":"area","colorMode":"value"},
"targets": [{"expr": "starlink_dish_uptime_seconds", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 3, "title": "Downlink", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 4, "x": 8, "y": 1},
"fieldConfig": {"defaults": {"unit":"bps","color":{"mode":"thresholds"},
"thresholds":{"mode":"absolute","steps":[{"color":"red","value":null},{"color":"green","value":1000000}]}}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]}, "graphMode":"area","colorMode":"value"},
"targets": [{"expr": "starlink_dish_downlink_throughput_bps", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 4, "title": "Uplink", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 4, "x": 12, "y": 1},
"fieldConfig": {"defaults": {"unit":"bps","color":{"mode":"thresholds"},
"thresholds":{"mode":"absolute","steps":[{"color":"red","value":null},{"color":"green","value":100000}]}}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]}, "graphMode":"area","colorMode":"value"},
"targets": [{"expr": "starlink_dish_uplink_throughput_bps", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 5, "title": "PoP ping", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 4, "x": 16, "y": 1},
"fieldConfig": {"defaults": {"unit":"s","color":{"mode":"thresholds"},
"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"yellow","value":0.05},{"color":"orange","value":0.1},{"color":"red","value":0.2}]}}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]}, "graphMode":"area","colorMode":"value"},
"targets": [{"expr": "starlink_dish_pop_ping_latency_seconds", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 6, "title": "Power", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 4, "x": 20, "y": 1},
"fieldConfig": {"defaults": {"unit":"watt","color":{"mode":"thresholds"},
"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"yellow","value":80},{"color":"red","value":120}]}}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]}, "graphMode":"area","colorMode":"value"},
"targets": [{"expr": "starlink_dish_power_input_watts", "refId":"A", "datasource":"$datasource"}]},
{"type": "row", "id": 101, "title": "Throughput", "collapsed": false, "gridPos": {"h": 1, "w": 24, "x": 0, "y": 5}},
{"type": "timeseries", "id": 10, "title": "Downlink (bps)", "datasource": "$datasource",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 6},
"fieldConfig": {"defaults": {"unit":"bps","custom":{"lineWidth":1,"fillOpacity":15,"showPoints":"never"}}},
"options": {"legend":{"showLegend":true,"placement":"bottom"},"tooltip":{"mode":"multi"}},
"targets": [{"expr": "starlink_dish_downlink_throughput_bps", "legendFormat":"downlink", "refId":"A", "datasource":"$datasource"}]},
{"type": "timeseries", "id": 11, "title": "Uplink (bps)", "datasource": "$datasource",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 6},
"fieldConfig": {"defaults": {"unit":"bps","custom":{"lineWidth":1,"fillOpacity":15,"showPoints":"never"}}},
"options": {"legend":{"showLegend":true,"placement":"bottom"},"tooltip":{"mode":"multi"}},
"targets": [{"expr": "starlink_dish_uplink_throughput_bps", "legendFormat":"uplink", "refId":"A", "datasource":"$datasource"}]},
{"type": "row", "id": 102, "title": "Latency & reliability", "collapsed": false, "gridPos": {"h": 1, "w": 24, "x": 0, "y": 14}},
{"type": "timeseries", "id": 20, "title": "PoP ping latency (s)", "datasource": "$datasource",
"description": "Round-trip latency to the Starlink point-of-presence. Anything under 50ms is healthy; sustained >100ms suggests congestion or weather.",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 15},
"fieldConfig": {"defaults": {"unit":"s","custom":{"lineWidth":1,"fillOpacity":10,"showPoints":"never"},
"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"yellow","value":0.05},{"color":"red","value":0.1}]}}},
"options": {"legend":{"showLegend":true,"placement":"bottom"},"tooltip":{"mode":"multi"}},
"targets": [{"expr": "starlink_dish_pop_ping_latency_seconds", "legendFormat":"pop", "refId":"A", "datasource":"$datasource"}]},
{"type": "timeseries", "id": 21, "title": "PoP ping drop ratio", "datasource": "$datasource",
"description": "Fraction of pings dropped to the Starlink PoP — 0 is perfect, anything above 0 means packets are being lost upstream of the dish.",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 15},
"fieldConfig": {"defaults": {"unit":"percentunit","min":0,"max":1,"custom":{"lineWidth":1,"fillOpacity":30,"showPoints":"never"},
"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"yellow","value":0.01},{"color":"red","value":0.05}]}}},
"options": {"legend":{"showLegend":true,"placement":"bottom"},"tooltip":{"mode":"multi"}},
"targets": [{"expr": "starlink_dish_pop_ping_drop_ratio", "legendFormat":"drop", "refId":"A", "datasource":"$datasource"}]},
{"type": "row", "id": 103, "title": "Obstruction & signal", "collapsed": false, "gridPos": {"h": 1, "w": 24, "x": 0, "y": 23}},
{"type": "stat", "id": 30, "title": "Currently obstructed", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 4, "x": 0, "y": 24},
"fieldConfig": {"defaults": {
"mappings":[{"type":"value","options":{"0":{"text":"CLEAR","color":"green"},"1":{"text":"OBSTRUCTED","color":"red"}}}],
"color":{"mode":"thresholds"},
"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":1}]}}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]},"graphMode":"none","colorMode":"background","textMode":"value"},
"targets": [{"expr": "starlink_dish_currently_obstructed", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 31, "title": "Obstruction fraction", "datasource": "$datasource",
"description": "Fraction of the sky the dish believes is obstructed.",
"gridPos": {"h": 4, "w": 4, "x": 4, "y": 24},
"fieldConfig": {"defaults": {"unit":"percentunit","min":0,"max":1,"color":{"mode":"thresholds"},
"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"yellow","value":0.05},{"color":"red","value":0.15}]}}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]},"graphMode":"area","colorMode":"value"},
"targets": [{"expr": "starlink_dish_fraction_obstruction_ratio", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 32, "title": "Obstructed (last 24h)", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 4, "x": 8, "y": 24},
"fieldConfig": {"defaults": {"unit":"s","color":{"mode":"thresholds"},
"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"yellow","value":60},{"color":"red","value":600}]}}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]},"graphMode":"area","colorMode":"value"},
"targets": [{"expr": "starlink_dish_last_24h_obstructed_seconds", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 33, "title": "GPS valid", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 4, "x": 12, "y": 24},
"fieldConfig": {"defaults": {
"mappings":[{"type":"value","options":{"0":{"text":"NO FIX","color":"red"},"1":{"text":"LOCKED","color":"green"}}}],
"color":{"mode":"thresholds"},
"thresholds":{"mode":"absolute","steps":[{"color":"red","value":null},{"color":"green","value":1}]}}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]},"graphMode":"none","colorMode":"background","textMode":"value"},
"targets": [{"expr": "starlink_dish_gps_valid", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 34, "title": "GPS satellites", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 4, "x": 16, "y": 24},
"fieldConfig": {"defaults": {"color":{"mode":"thresholds"},
"thresholds":{"mode":"absolute","steps":[{"color":"red","value":null},{"color":"yellow","value":4},{"color":"green","value":8}]}}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]},"graphMode":"area","colorMode":"value"},
"targets": [{"expr": "starlink_dish_gps_satellites", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 35, "title": "SNR above noise floor", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 4, "x": 20, "y": 24},
"fieldConfig": {"defaults": {
"mappings":[{"type":"value","options":{"0":{"text":"LOW","color":"red"},"1":{"text":"OK","color":"green"}}}],
"color":{"mode":"thresholds"},
"thresholds":{"mode":"absolute","steps":[{"color":"red","value":null},{"color":"green","value":1}]}}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]},"graphMode":"none","colorMode":"background","textMode":"value"},
"targets": [{"expr": "starlink_dish_snr_above_noise_floor", "refId":"A", "datasource":"$datasource"}]},
{"type": "row", "id": 104, "title": "Dish position & info", "collapsed": true, "gridPos": {"h": 1, "w": 24, "x": 0, "y": 28},
"panels": [
{"type": "stat", "id": 40, "title": "Tilt", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 29},
"fieldConfig": {"defaults": {"unit":"degree"}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]},"graphMode":"none"},
"targets": [{"expr": "starlink_dish_tilt_angle_deg", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 41, "title": "Boresight azimuth", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 6, "x": 6, "y": 29},
"fieldConfig": {"defaults": {"unit":"degree"}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]},"graphMode":"none"},
"targets": [{"expr": "starlink_dish_boresight_azimuth_deg", "refId":"A", "datasource":"$datasource"}]},
{"type": "stat", "id": 42, "title": "Boresight elevation", "datasource": "$datasource",
"gridPos": {"h": 4, "w": 6, "x": 12, "y": 29},
"fieldConfig": {"defaults": {"unit":"degree"}},
"options": {"reduceOptions":{"calcs":["lastNotNull"]},"graphMode":"none"},
"targets": [{"expr": "starlink_dish_boresight_elevation_deg", "refId":"A", "datasource":"$datasource"}]},
{"type": "table", "id": 43, "title": "Dish info", "datasource": "$datasource",
"gridPos": {"h": 6, "w": 24, "x": 0, "y": 33},
"options": {"showHeader": true},
"targets": [{"expr": "starlink_dish_info", "refId":"A", "instant": true, "format":"table", "datasource":"$datasource"}],
"transformations": [{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true,"__name__":true,"app_kubernetes_io_instance":true,"app_kubernetes_io_name":true,"instance":true,"job":true,"namespace":true,"service":true}}}]}
]}
]
}

View file

@ -0,0 +1,23 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: starlink-exporter
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://codeberg.org/gmcintire/infra.git
targetRevision: main
path: home/cluster/starlink-exporter
destination:
server: https://kubernetes.default.svc
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true

View file

@ -0,0 +1,55 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: starlink-exporter
namespace: monitoring
labels:
app.kubernetes.io/name: starlink-exporter
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: starlink-exporter
template:
metadata:
labels:
app.kubernetes.io/name: starlink-exporter
spec:
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: exporter
image: docker.io/joshuasing/starlink_exporter:0.9.0
# Defaults already match this network: -dish 192.168.100.1:9200,
# -listen :9451. Dish is reachable via 10.0.15.254 (home router)
# while the Starlink router is in bypass mode.
ports:
- name: metrics
containerPort: 9451
protocol: TCP
resources:
requests:
cpu: 10m
memory: 24Mi
limits:
memory: 96Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
readinessProbe:
httpGet:
path: /metrics
port: metrics
periodSeconds: 15
livenessProbe:
httpGet:
path: /metrics
port: metrics
periodSeconds: 30
failureThreshold: 5

View file

@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml

View file

@ -0,0 +1,18 @@
apiVersion: v1
kind: Service
metadata:
name: starlink-exporter
namespace: monitoring
labels:
app.kubernetes.io/name: starlink-exporter
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9451"
spec:
selector:
app.kubernetes.io/name: starlink-exporter
ports:
- name: metrics
port: 9451
targetPort: metrics
protocol: TCP