This exporter is primarily intended to run as a sidecar container for AdGuard
Home in Kubernetes. It enables metrics visibility across multiple replica
instances by mounting AdGuard Home's work directory and reading lines from
querylog.json.
The exporter is written in Go and uses a distroless image running as nonroot.
Images are published to Docker Hub and GHCR:
sholdee/adguardexporter
ghcr.io/sholdee/adguard-exporter
Published images support linux/amd64 and linux/arm64. Releases publish
these tags:
vX.Y.Z
X.Y.Z
X.Y
latest
The latest tag points to the latest release, not the latest commit on
master.
agh_dns_queries_total: Total number of DNS queries
agh_blocked_dns_queries_total: Total number of AdGuard blocked queries
agh_dns_query_types_total: DNS query types and respective counts
agh_dns_query_hosts_total: Top 100 DNS query hosts
agh_blocked_dns_query_hosts_total: Top 100 blocked DNS query hosts
agh_safe_search_enforced_hosts_total: Safe search enforced hosts
agh_dns_filtering_reason_total: DNS query filtering reasons
agh_dns_filtering_reason_hosts_total: Top 100 filtered hosts by reason
agh_querylog_entries_skipped_total: Query log entries skipped by reason
agh_dns_average_response_time: Average response time of all queries in ms
agh_dns_average_upstream_response_time: Query processing time by upstream in ms
The upstream timing metric keeps its historical name, but it is based on
querylog Elapsed grouped by Upstream; AdGuard Home querylog records do not
include pure upstream round-trip duration.
The dashboard JSON is versioned in dashboards/adguard-exporter.v2.json. It uses Grafana's V2 JSON model, range-based counter queries for dashboard totals, filtering reason and skipped querylog record metrics, and upstream timing labeled as query processing time rather than pure upstream round-trip duration.
Configure AdGuard Home to dump query logs to disk at a regular interval by
using a low size_memory setting. This example causes queries to be logged
every five lines.
apiVersion: v1
kind: Secret
metadata:
name: adguard-secret
namespace: adguard
type: Opaque
stringData:
AdGuardHome.yaml: |
# ... [earlier configuration omitted]
querylog:
dir_path: ""
ignored:
- localhost
interval: 24h
size_memory: 5
enabled: true
file_enabled: true
# ... [remaining configuration omitted]Add the sholdee/adguardexporter sidecar container to your existing AdGuard
deployment manifest.
apiVersion: apps/v1
kind: Deployment
metadata:
name: &app adguard
namespace: *app
spec:
replicas: 3
strategy:
type: RollingUpdate
selector:
matchLabels:
app: *app
template:
metadata:
labels:
app: *app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: *app
initContainers:
- name: adguard-init
image: busybox:1.36.1
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
allowPrivilegeEscalation: false
imagePullPolicy: IfNotPresent
command:
- sh
- -c
- |
cp /home/AdGuardHome.yaml /config/AdGuardHome.yaml
chmod 644 /config/AdGuardHome.yaml
volumeMounts:
- mountPath: /home
name: adguard-secret
- mountPath: /config
name: adguard-conf
containers:
- name: adguard-home
image: adguard/adguardhome:v0.107.52
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
allowPrivilegeEscalation: false
imagePullPolicy: IfNotPresent
ports:
- containerPort: 53
name: dns
protocol: UDP
- containerPort: 53
name: dnstcp
protocol: TCP
- containerPort: 3000
name: http-initial
protocol: TCP
- containerPort: 80
name: http
protocol: TCP
volumeMounts:
- name: adguard-data
mountPath: /opt/adguardhome/work
- name: adguard-conf
mountPath: /opt/adguardhome/conf
resources:
requests:
memory: 150Mi
cpu: "15m"
limits:
memory: 400Mi
livenessProbe: &probe
exec:
command:
- /bin/sh
- -c
- nslookup localhost 127.0.0.1
readinessProbe: *probe
- name: adguard-exporter
image: ghcr.io/sholdee/adguard-exporter:v2.0.2
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
allowPrivilegeEscalation: false
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8000
name: metrics
protocol: TCP
volumeMounts:
- name: adguard-data
mountPath: /opt/adguardhome/work
livenessProbe:
httpGet:
path: /livez
port: metrics
readinessProbe:
httpGet:
path: /readyz
port: metrics
volumes:
- emptyDir: {}
name: adguard-data
- emptyDir: {}
name: adguard-conf
- name: adguard-secret
secret:
secretName: adguard-secretAdd the metrics port to the Service definition.
apiVersion: v1
kind: Service
metadata:
name: adguard-http
namespace: adguard
labels:
app: adguard
spec:
selector:
app: adguard
ports:
- protocol: TCP
port: 80
targetPort: 80
name: http
- port: 8000
protocol: TCP
targetPort: 8000
name: metrics
type: ClusterIPCreate a ServiceMonitor for Prometheus to start scraping metrics.
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: adguard-metrics
namespace: adguard
labels:
app: adguard
spec:
selector:
matchLabels:
app: adguard
namespaceSelector:
matchNames:
- adguard
endpoints:
- port: metrics
interval: 30s
path: /metrics