Add Dockerfile, compose.yaml, and an unbound example config (#66)
This adds a Dockerfile for unbound exporter, which we can publish in the future. An example docker compose.yml is included to demonstrate and test using it with unbound, along with a sample configuration file for unbound showing how to set up the remote-control. The unbound example config file is based on the one inside the mvance/docker image that's used here. A small integration test runs against the docker-compose setup to smoke-test unbound_exporter.
This commit is contained in:
parent
cbed00787a
commit
e54ee016b6
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
name: integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
integration:
|
||||||
|
runs-on: [ubuntu-latest]
|
||||||
|
steps:
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: "1.21.x"
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Start containers
|
||||||
|
run: docker compose up --build --detach
|
||||||
|
- name: run integration test
|
||||||
|
run: go test -v --tags=integration
|
||||||
|
- name: Stop containers
|
||||||
|
if: always()
|
||||||
|
run: docker compose down
|
|
@ -0,0 +1,20 @@
|
||||||
|
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21.4-bookworm AS build
|
||||||
|
|
||||||
|
WORKDIR /go/src/app
|
||||||
|
|
||||||
|
COPY go.mod .
|
||||||
|
COPY go.sum .
|
||||||
|
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY *.go .
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
|
||||||
|
RUN GOOS=$TARGETOS GOARCH=$TARGETPLATFORM go build -v -o /go/bin/unbound_exporter ./...
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/static-debian12
|
||||||
|
|
||||||
|
COPY --from=build /go/bin/unbound_exporter /
|
||||||
|
|
||||||
|
ENTRYPOINT ["/unbound_exporter"]
|
|
@ -0,0 +1,21 @@
|
||||||
|
services:
|
||||||
|
unbound_exporter:
|
||||||
|
build: .
|
||||||
|
command: [ "-unbound.host=unix:///var/run/socket/unbound.ctl" ]
|
||||||
|
volumes:
|
||||||
|
- socket:/var/run/socket:ro
|
||||||
|
ports:
|
||||||
|
- "9167:9167"
|
||||||
|
depends_on:
|
||||||
|
unbound:
|
||||||
|
condition: service_started
|
||||||
|
unbound:
|
||||||
|
image: "mvance/unbound:1.18.0"
|
||||||
|
volumes:
|
||||||
|
- socket:/var/run/socket:rw
|
||||||
|
- ./unbound-example.conf:/opt/unbound/etc/unbound/unbound.conf
|
||||||
|
ports:
|
||||||
|
- "1053:1053/udp"
|
||||||
|
- "1053:1053/tcp"
|
||||||
|
volumes:
|
||||||
|
socket:
|
|
@ -0,0 +1,52 @@
|
||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prometheus/common/expfmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestIntegration checks that unbound_exporter is running, successfully
|
||||||
|
// scraping and exporting metrics.
|
||||||
|
//
|
||||||
|
// It assumes unbound_exporter is available on localhost:9167, and Unbound on
|
||||||
|
// localhost:1053, as is set up in the docker-compose.yml file.
|
||||||
|
//
|
||||||
|
// A typical invocation of this test would look like
|
||||||
|
//
|
||||||
|
// docker compose up --build -d
|
||||||
|
// go test --tags=integration
|
||||||
|
// docker compose down
|
||||||
|
func TestIntegration(t *testing.T) {
|
||||||
|
resp, err := http.Get("http://localhost:9167/metrics")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to fetch metrics from unbound_exporter: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("Expected a 200 OK from unbound_exporter, got: %v", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := expfmt.TextParser{}
|
||||||
|
metrics, err := parser.TextToMetricFamilies(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse metrics from unbound_exporter: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unbound_up is 1 if we've successfully scraped metrics from it
|
||||||
|
unbound_up := metrics["unbound_up"].Metric[0].Gauge.GetValue()
|
||||||
|
if unbound_up != 1 {
|
||||||
|
t.Errorf("Expected unbound_up to be 1, not: %v", unbound_up)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check some expected metrics are present
|
||||||
|
for _, metric := range []string{"go_info", "unbound_queries_total", "unbound_response_time_seconds", "unbound_cache_hits_total"} {
|
||||||
|
if _, ok := metrics[metric]; !ok {
|
||||||
|
t.Errorf("Expected metric is missing: %s", metric)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
## This is an example Unbound configuration file
|
||||||
|
## This is needed to use unbound_exporter
|
||||||
|
remote-control:
|
||||||
|
control-enable: yes
|
||||||
|
control-interface: /var/run/socket/unbound.ctl
|
||||||
|
|
||||||
|
# The rest of this file is standard Unbound configuration
|
||||||
|
# There's nothing special here.
|
||||||
|
server:
|
||||||
|
cache-max-ttl: 86400
|
||||||
|
cache-min-ttl: 300
|
||||||
|
directory: "/opt/unbound/etc/unbound"
|
||||||
|
do-ip4: yes
|
||||||
|
do-ip6: no
|
||||||
|
do-tcp: yes
|
||||||
|
do-udp: yes
|
||||||
|
edns-buffer-size: 1232
|
||||||
|
interface: 0.0.0.0
|
||||||
|
port: 1053
|
||||||
|
prefer-ip6: no
|
||||||
|
rrset-roundrobin: yes
|
||||||
|
username: "_unbound"
|
||||||
|
log-local-actions: no
|
||||||
|
log-queries: no
|
||||||
|
log-replies: no
|
||||||
|
log-servfail: yes
|
||||||
|
logfile: /opt/unbound/etc/unbound/unbound.log
|
||||||
|
verbosity: 2
|
||||||
|
infra-cache-slabs: 4
|
||||||
|
incoming-num-tcp: 10
|
||||||
|
key-cache-slabs: 4
|
||||||
|
msg-cache-size: 142768128
|
||||||
|
msg-cache-slabs: 4
|
||||||
|
num-queries-per-thread: 4096
|
||||||
|
num-threads: 3
|
||||||
|
outgoing-range: 8192
|
||||||
|
rrset-cache-size: 285536256
|
||||||
|
rrset-cache-slabs: 4
|
||||||
|
minimal-responses: yes
|
||||||
|
prefetch: yes
|
||||||
|
prefetch-key: yes
|
||||||
|
serve-expired: yes
|
||||||
|
so-reuseport: yes
|
||||||
|
aggressive-nsec: yes
|
||||||
|
delay-close: 10000
|
||||||
|
do-daemonize: no
|
||||||
|
do-not-query-localhost: no
|
||||||
|
neg-cache-size: 4M
|
||||||
|
qname-minimisation: yes
|
||||||
|
access-control: 127.0.0.1/32 allow
|
||||||
|
access-control: 192.168.0.0/16 allow
|
||||||
|
access-control: 172.16.0.0/12 allow
|
||||||
|
access-control: 10.0.0.0/8 allow
|
||||||
|
access-control: fc00::/7 allow
|
||||||
|
access-control: ::1/128 allow
|
||||||
|
auto-trust-anchor-file: "var/root.key"
|
||||||
|
chroot: ""
|
||||||
|
deny-any: yes
|
||||||
|
harden-algo-downgrade: yes
|
||||||
|
harden-below-nxdomain: yes
|
||||||
|
harden-dnssec-stripped: yes
|
||||||
|
harden-glue: yes
|
||||||
|
harden-large-queries: yes
|
||||||
|
harden-referral-path: no
|
||||||
|
harden-short-bufsize: yes
|
||||||
|
hide-http-user-agent: no
|
||||||
|
hide-identity: yes
|
||||||
|
hide-version: yes
|
||||||
|
http-user-agent: "DNS"
|
||||||
|
identity: "DNS"
|
||||||
|
private-address: 10.0.0.0/8
|
||||||
|
private-address: 172.16.0.0/12
|
||||||
|
private-address: 192.168.0.0/16
|
||||||
|
private-address: 169.254.0.0/16
|
||||||
|
private-address: fd00::/8
|
||||||
|
private-address: fe80::/10
|
||||||
|
private-address: ::ffff:0:0/96
|
||||||
|
ratelimit: 1000
|
||||||
|
tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
|
||||||
|
unwanted-reply-threshold: 10000
|
||||||
|
use-caps-for-id: yes
|
||||||
|
val-clean-additional: yes
|
||||||
|
include: /opt/unbound/etc/unbound/a-records.conf
|
||||||
|
include: /opt/unbound/etc/unbound/srv-records.conf
|
Loading…
Reference in New Issue