Files
fancy-dns/deploy/k8s/00-config.yaml
T
matsandClaude Sonnet 5.5 c6c5ca4ff7 Make the resolver pure DNS: JSON access log on stdout instead of a log store
The resolver kept its own query-log store (a bbolt ring, a live ring with SSE
fanout, rolling per-domain counters and dashboard time-series) for the console.
In a multi-node deployment each pod held a different slice of that, so every
view disagreed with itself, and the cost sat on the query path. Charts belong
in Grafana and log search in the log collector, so the store is gone.

internal/accesslog writes one JSON line per notable query to stdout. The default
is what was blocked and what failed (blocked, redirected, SERVFAIL, dropped,
refused); log_cached and sample_rate add ordinary answers for a debugging
session. Lines go through a bounded buffer to a single writer goroutine and are
dropped, and counted in fancydns_access_log_dropped_total, when the stream
cannot keep up, so a stalled log pipe can never stall a query. The config key is
access_log (the old qlog block is ignored, like any unknown key).

The metrics gain what the dashboard used to compute: fancydns_group_queries_total
by ACL group and outcome and fancydns_responses_total by RCODE. Removed with the
store: /api/dashboard, /api/logs, /api/logs/stream, /api/logs/clear,
/api/cache/top, the series rings, and the cache's per-name hit table, which took
a mutex and built a string on every cache hit.

Tests that read the old log now read the access-log lines; the contract test
drops the removed endpoints. The web console still calls them and is changed in
the next commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 09:53:04 +02:00

145 lines
4.2 KiB
YAML

apiVersion: v1
kind: Namespace
metadata:
name: dns
---
apiVersion: v1
kind: ConfigMap
metadata:
name: fancy-dns-config
namespace: dns
labels:
app.kubernetes.io/name: fancy-dns
data:
config.yaml: |
# Rendered by the deployment below; kept in a ConfigMap so a change is a
# `kubectl apply` rather than a rebuild.
server:
max_concurrent: 2048
timeouts:
upstream: 2s
list_fetch: 60s
dns:
# Use high ports in the pod so the container needs no privileged-port
# capability. Services expose the standard DNS ports to clients.
udp: ["0.0.0.0:1053", "[::]:1053"]
tcp: ["0.0.0.0:1053", "[::]:1053"]
max_udp_size: 1232
dot:
enabled: true
listen: ["0.0.0.0:1853"]
cert_file: /etc/fancy-dns/tls/tls.crt
key_file: /etc/fancy-dns/tls/tls.key
doh:
enabled: true
listen: ["0.0.0.0:8443"]
cert_file: /etc/fancy-dns/tls/tls.crt
key_file: /etc/fancy-dns/tls/tls.key
base_path: /dns-query
upstream:
forward_zones:
- zone: local
servers:
- addr: 10.10.0.1:53
proto: udp
policy: parallel
max_attempts: 3
healthcheck_interval: 30s
servers:
- addr: 9.9.9.9:53
proto: udp
- addr: 1.1.1.1:53
proto: tls
server_name: cloudflare-dns.com
- addr: 9.9.9.9:853
proto: tls
server_name: dns.quad9.net
# Memory envelope. The container limit (1Gi, in the StatefulSet) is the
# hard ceiling; this tells the process about it so the collector works to
# a target instead of being OOM-killed. `limit` is the soft GOMEMLIMIT and
# must be at or below the container limit; `reserve` covers what the Go
# heap does not account for — goroutine stacks, runtime metadata, and the
# mmapped cache snapshot and query log.
#
# The cache byte budget is derived (60% of limit - reserve) unless it is
# set explicitly. cache.size below is an *entry count*, so this is what
# actually bounds the cache's memory: 500k entries of a few hundred bytes
# each is well past 384MiB, and the byte budget is what will bite first.
memory:
limit: 1GiB
reserve: 256MiB
cache_max_bytes: 384MiB
cache:
enabled: true
size: 500000
max_ttl: 1h
serve_stale: 30m
persist:
enabled: true
path: /var/lib/fancy-dns/cache.db
interval: 30s
blocklist:
enabled: true
default_action: nxdomain
managed_zones: /var/lib/fancy-dns/managed-zones
local_zones: [/etc/fancy-dns/zones/*.zone]
lists:
- name: edge-router-x-address-overrides
format: rpz
path: /etc/fancy-dns/rpz/address-overrides.rpz
enabled: true
refresh: 0s
- name: haas
format: plain
url: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
path: /var/lib/fancy-dns/lists/hosts
enabled: true
refresh: 24h
categories: [ads, tracking]
acl:
default_policy: allow
groups: []
# JSON lines on stdout for blocked and failed queries; the metrics cover
# the rest. See deploy/config.example.yaml.
access_log:
enabled: true
log_blocked: true
log_errors: true
sample_rate: 0
api:
enabled: true
listen: ["0.0.0.0:8080"]
base_path: /api
ui: true
allow_config_write: false
# The key comes from the environment (a Secret mounted into the pod), not
# from this ConfigMap, so `kubectl get cm` never prints it. The loader
# substitutes ${VAR} before parsing and *fails to start* if the variable
# is unset, so a missing secret is a crash loop rather than an
# unauthenticated console.
auth:
mode: oidc
api_keys: ["${FANCYDNS_API_KEY}"]
oidc_issuer: https://users.k6n.net
oidc_audience: https://dns.k6n.net
oidc_client_id: fancy-dns
oidc_write_roles: [admin]
trust_loopback: false
metrics:
enabled: true
path: /metrics
protect: true
log:
level: info
format: json