From 92c9ccdb3c4db68e99d2b9350efeeaf6ba4f3758 Mon Sep 17 00:00:00 2001 From: Matthew McPherrin Date: Wed, 15 Nov 2023 21:22:29 -0500 Subject: [PATCH 1/6] Add Dockerfile, compose.yaml, and an unbound example config 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. --- Dockerfile | 20 +++++++++++ docker-compose.yml | 21 ++++++++++++ unbound-example.conf | 80 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 unbound-example.conf diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0f693bd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM 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 go build -v -o /go/bin/unbound_exporter ./... + +FROM gcr.io/distroless/static-debian12 + +COPY --from=build /go/bin/unbound_exporter / + +ENTRYPOINT ["/unbound_exporter"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9a22a2f --- /dev/null +++ b/docker-compose.yml @@ -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: diff --git a/unbound-example.conf b/unbound-example.conf new file mode 100644 index 0000000..f78c87b --- /dev/null +++ b/unbound-example.conf @@ -0,0 +1,80 @@ +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 + +remote-control: + control-enable: yes + control-interface: /var/run/socket/unbound.ctl From 2374e56bbc38b03d5b70391a04b43c46c3417d64 Mon Sep 17 00:00:00 2001 From: Matthew McPherrin Date: Thu, 16 Nov 2023 00:57:06 -0500 Subject: [PATCH 2/6] Multi-platform dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0f693bd..3977962 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/library/golang:1.21.4-bookworm AS build +FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21.4-bookworm AS build WORKDIR /go/src/app @@ -11,7 +11,7 @@ COPY *.go . ENV CGO_ENABLED=0 -RUN go build -v -o /go/bin/unbound_exporter ./... +RUN GOOS=$TARGETOS GOARCH=$TARGETPLATFORM go build -v -o /go/bin/unbound_exporter ./... FROM gcr.io/distroless/static-debian12 From f3e0c900003ddcc15371fb80f98acc45cab8b30f Mon Sep 17 00:00:00 2001 From: Matthew McPherrin Date: Thu, 16 Nov 2023 00:57:33 -0500 Subject: [PATCH 3/6] Integration test --- .github/workflows/integration.yaml | 27 +++++++++++++++++++++++++++ integration_test.go | 20 ++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 .github/workflows/integration.yaml create mode 100644 integration_test.go diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml new file mode 100644 index 0000000..0e3782a --- /dev/null +++ b/.github/workflows/integration.yaml @@ -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 --tags=integration + - name: Stop containers + if: always() + run: docker compose down diff --git a/integration_test.go b/integration_test.go new file mode 100644 index 0000000..d9e43bc --- /dev/null +++ b/integration_test.go @@ -0,0 +1,20 @@ +//go:build integration + +package main + +import "testing" + +// 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) { + // TODO +} From 52740496955c9fadd242d2331a99ac43f63f584c Mon Sep 17 00:00:00 2001 From: Matthew McPherrin Date: Thu, 16 Nov 2023 22:54:08 -0500 Subject: [PATCH 4/6] Fill out a simple integration test This is a simple smoketest to ensure the unbound_exporter is successfully serving metrics, and that it thinks it can scrape from Unbound. --- integration_test.go | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/integration_test.go b/integration_test.go index d9e43bc..f0530ef 100644 --- a/integration_test.go +++ b/integration_test.go @@ -2,7 +2,12 @@ package main -import "testing" +import ( + "net/http" + "testing" + + "github.com/prometheus/common/expfmt" +) // TestIntegration checks that unbound_exporter is running, successfully // scraping and exporting metrics. @@ -16,5 +21,32 @@ import "testing" // go test --tags=integration // docker compose down func TestIntegration(t *testing.T) { - // TODO + 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) + } + } } From 6adecd295d12eaff8765a6a92ee3167babd22c49 Mon Sep 17 00:00:00 2001 From: Matthew McPherrin Date: Thu, 16 Nov 2023 22:55:33 -0500 Subject: [PATCH 5/6] Add some commentary to unbound-example.conf --- unbound-example.conf | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/unbound-example.conf b/unbound-example.conf index f78c87b..991826e 100644 --- a/unbound-example.conf +++ b/unbound-example.conf @@ -1,3 +1,11 @@ +## 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 @@ -74,7 +82,3 @@ server: val-clean-additional: yes include: /opt/unbound/etc/unbound/a-records.conf include: /opt/unbound/etc/unbound/srv-records.conf - -remote-control: - control-enable: yes - control-interface: /var/run/socket/unbound.ctl From 7d4057680673d36c3ac0c007e5d5a0996c3427a5 Mon Sep 17 00:00:00 2001 From: Matthew McPherrin Date: Fri, 17 Nov 2023 13:10:55 -0500 Subject: [PATCH 6/6] Add -v flag to go test Verbose is good in CI --- .github/workflows/integration.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 0e3782a..912f078 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -21,7 +21,7 @@ jobs: - name: Start containers run: docker compose up --build --detach - name: run integration test - run: go test --tags=integration + run: go test -v --tags=integration - name: Stop containers if: always() run: docker compose down