feat(wireguard): add prometheus monitoring, alerts, and grafana dashboard

This commit is contained in:
Moritz Graf 2026-07-06 22:09:37 +02:00
parent db9ce2b538
commit ecdc27b155
5 changed files with 318 additions and 0 deletions

View File

@ -0,0 +1,224 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard-wireguard
namespace: monitoring
labels:
grafana_dashboard: '1'
annotations:
grafana_dashboard_folder: 🏠 Home
data:
wireguard.json: |
{
"uid": "wireguard",
"title": "🛡️ WireGuard VPN",
"tags": ["network", "vpn", "infrastructure", "prometheus"],
"timezone": "browser",
"schemaVersion": 39,
"version": 2,
"refresh": "10s",
"editable": false,
"graphTooltip": 2,
"time": {
"from": "now-24h",
"to": "now"
},
"panels": [
{
"title": "⚡ VPN Status",
"type": "row",
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 0},
"collapsed": false
},
{
"title": "Exporter Status",
"type": "stat",
"datasource": {"type": "prometheus", "uid": "prometheus"},
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 1},
"fieldConfig": {
"defaults": {
"mappings": [
{"type": "value", "options": {"0": {"text": "OFFLINE", "color": "red"}, "1": {"text": "ONLINE", "color": "green"}}}
],
"color": {"mode": "thresholds"}
}
},
"options": {
"graphMode": "none",
"colorMode": "background",
"textMode": "auto",
"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}
},
"targets": [
{"expr": "up{job=\"wireguard-exporter\"}", "legendFormat": "Exporter Connectivity", "refId": "A"}
]
},
{
"title": "Interface wg0",
"type": "stat",
"datasource": {"type": "prometheus", "uid": "prometheus"},
"gridPos": {"h": 4, "w": 6, "x": 6, "y": 1},
"fieldConfig": {
"defaults": {
"mappings": [
{"type": "value", "options": {"0": {"text": "DOWN", "color": "red"}, "1": {"text": "UP", "color": "green"}}}
],
"color": {"mode": "thresholds"}
}
},
"options": {
"graphMode": "none",
"colorMode": "background",
"textMode": "auto",
"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}
},
"targets": [
{"expr": "count(wireguard_sent_bytes_total{interface=\"wg0\"}) or on() vector(0)", "legendFormat": "wg0 Status", "refId": "A"}
]
},
{
"title": "FritzBox Peer Status",
"type": "stat",
"datasource": {"type": "prometheus", "uid": "prometheus"},
"gridPos": {"h": 4, "w": 6, "x": 12, "y": 1},
"fieldConfig": {
"defaults": {
"mappings": [
{"type": "value", "options": {"0": {"text": "DISCONNECTED", "color": "red"}, "1": {"text": "CONNECTED", "color": "green"}}}
],
"color": {"mode": "thresholds"}
}
},
"options": {
"graphMode": "none",
"colorMode": "background",
"textMode": "auto",
"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}
},
"targets": [
{"expr": "(time() - wireguard_latest_handshake_seconds{interface=\"wg0\"} < 300) or on() vector(0)", "legendFormat": "Peer Connectivity", "refId": "A"}
]
},
{
"title": "FritzBox Last Handshake",
"type": "stat",
"datasource": {"type": "prometheus", "uid": "prometheus"},
"gridPos": {"h": 4, "w": 6, "x": 18, "y": 1},
"fieldConfig": {
"defaults": {
"unit": "s",
"color": {"mode": "thresholds"},
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "orange", "value": 300},
{"color": "red", "value": 600}
]
}
}
},
"options": {
"graphMode": "none",
"colorMode": "value",
"textMode": "auto",
"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}
},
"targets": [
{"expr": "time() - wireguard_latest_handshake_seconds{interface=\"wg0\"}", "legendFormat": "Handshake Delay", "refId": "A"}
]
},
{
"title": "📊 Traffic & Performance",
"type": "row",
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 5},
"collapsed": false
},
{
"title": "FritzBox Traffic Rate",
"type": "timeseries",
"datasource": {"type": "prometheus", "uid": "prometheus"},
"gridPos": {"h": 10, "w": 24, "x": 0, "y": 6},
"fieldConfig": {
"defaults": {
"unit": "Bps",
"custom": {
"lineWidth": 2,
"fillOpacity": 10,
"spanNulls": true,
"showPoints": "never"
}
},
"overrides": [
{
"matcher": {"id": "byName", "options": "Received (Rx)"},
"properties": [
{"id": "color", "value": {"fixedColor": "#4CAF50", "mode": "fixed"}}
]
},
{
"matcher": {"id": "byName", "options": "Sent (Tx)"},
"properties": [
{"id": "color", "value": {"fixedColor": "#2196F3", "mode": "fixed"}}
]
}
]
},
"options": {
"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "mean", "max"]},
"tooltip": {"mode": "multi", "sort": "desc"}
},
"targets": [
{"expr": "rate(wireguard_received_bytes_total{interface=\"wg0\"}[5m])", "legendFormat": "Received (Rx)", "refId": "A"},
{"expr": "rate(wireguard_sent_bytes_total{interface=\"wg0\"}[5m])", "legendFormat": "Sent (Tx)", "refId": "B"}
]
},
{
"title": "Total Data Transferred",
"type": "stat",
"datasource": {"type": "prometheus", "uid": "prometheus"},
"gridPos": {"h": 5, "w": 12, "x": 0, "y": 16},
"fieldConfig": {
"defaults": {
"unit": "bytes",
"color": {"mode": "palette-classic"}
}
},
"options": {
"graphMode": "none",
"colorMode": "value",
"textMode": "auto",
"reduceOptions": {"values": true, "calcs": ["lastNotNull"]}
},
"targets": [
{"expr": "wireguard_received_bytes_total{interface=\"wg0\"}", "legendFormat": "Total Received (Rx)", "refId": "A"},
{"expr": "wireguard_sent_bytes_total{interface=\"wg0\"}", "legendFormat": "Total Sent (Tx)", "refId": "B"}
]
},
{
"title": "Handshake Delay History",
"type": "timeseries",
"datasource": {"type": "prometheus", "uid": "prometheus"},
"gridPos": {"h": 5, "w": 12, "x": 12, "y": 16},
"fieldConfig": {
"defaults": {
"unit": "s",
"custom": {
"lineWidth": 2,
"fillOpacity": 5,
"spanNulls": true,
"showPoints": "never"
},
"color": {"mode": "fixed", "fixedColor": "#FF9800"}
}
},
"options": {
"legend": {"displayMode": "list", "placement": "bottom", "calcs": ["lastNotNull", "max"]},
"tooltip": {"mode": "single"}
},
"targets": [
{"expr": "time() - wireguard_latest_handshake_seconds{interface=\"wg0\"}", "legendFormat": "Handshake Delay", "refId": "A"}
]
}
]
}

View File

@ -7,6 +7,8 @@ metadata:
app: wireguard
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: wireguard
@ -25,6 +27,7 @@ spec:
apk add --no-cache wireguard-tools iptables
cp /config/wg0.conf /etc/wireguard/wg0.conf
chmod 600 /etc/wireguard/wg0.conf
wg-quick down wg0 || true
wg-quick up wg0
echo "Wireguard is up"
trap "wg-quick down wg0" SIGINT SIGTERM
@ -44,6 +47,25 @@ spec:
- name: lib-modules
mountPath: /lib/modules
readOnly: true
- name: exporter
image: mindflavor/prometheus-wireguard-exporter:latest
args:
- "-n"
- "/config/wg0.conf"
ports:
- name: http-metrics
containerPort: 9586
protocol: TCP
securityContext:
runAsUser: 0
capabilities:
add:
- NET_ADMIN
volumeMounts:
- name: wg-config
mountPath: /config/wg0.conf
subPath: wg0.conf
readOnly: true
volumes:
- name: wg-config
secret:

View File

@ -7,3 +7,4 @@ resources:
- namespace.yaml
- secret.secret.yaml
- deployment.yaml
- monitoring.yaml

View File

@ -0,0 +1,71 @@
apiVersion: v1
kind: Service
metadata:
name: wireguard-exporter
namespace: wireguard
labels:
app: wireguard
spec:
ports:
- name: http-metrics
port: 9586
targetPort: 9586
protocol: TCP
selector:
app: wireguard
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: wireguard
namespace: wireguard
labels:
release: prometheus-operator
spec:
selector:
matchLabels:
app: wireguard
namespaceSelector:
matchNames:
- wireguard
endpoints:
- port: http-metrics
path: /metrics
interval: 30s
scrapeTimeout: 10s
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: wireguard-alerts
namespace: wireguard
labels:
release: prometheus-operator
spec:
groups:
- name: wireguard.rules
rules:
- alert: WireguardExporterDown
expr: up{job="wireguard-exporter"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "WireGuard exporter is down"
description: "The WireGuard Prometheus exporter is unreachable. The pod may have crashed or is unresponsive."
- alert: WireguardInterfaceDown
expr: absent(wireguard_sent_bytes_total{interface="wg0"}) == 1
for: 5m
labels:
severity: critical
annotations:
summary: "WireGuard interface wg0 is down"
description: "The WireGuard interface wg0 is not reporting any statistics. The VPN tunnel might be down or inactive."
- alert: WireguardPeerOffline
expr: (time() - wireguard_latest_handshake_seconds{interface="wg0"}) > 300
for: 5m
labels:
severity: critical
annotations:
summary: "WireGuard peer {{ $labels.friendly_name }} offline"
description: "No handshake received for WireGuard peer {{ $labels.friendly_name }} ({{ $labels.public_key }}) on device {{ $labels.interface }} for more than 5 minutes."

Binary file not shown.