PowerDNS Authoritative Server with dnsdist - 夜莺博客

PowerDNS Authoritative Server with dnsdist

PowerDNS splits the two halves of DNS in a way that makes both easier to run: an authoritative server that reads zones from a database (MySQL, PostgreSQL or a flat SQLite file) and dnsdist, a high-performance front end that caches, load-balances, rate-limits and inspects queries before they reach any backend. That combination handles the two problems that hurt internal DNS most — a record change that requires no reload, and a recursive resolver that folds under a query flood. This guide walks from schema creation to a dnsdist front end with per-client rate limiting, and finishes with the verification commands that prove answers are correct and signed.

Install and create the database schema

# Debian/Ubuntu
sudo apt install pdns-server pdns-backend-pgsql dnsdist

# PostgreSQL schema
sudo -u postgres createdb pdns
sudo -u postgres psql pdns < /usr/share/pdns-backend-pgsql/schema/schema.pgsql.sql

# confirm the tables
sudo -u postgres psql -d pdns -c "\dt"
# domains, records, comments, domainmetadata, cryptokeys, tsigkeys

The schema is the API: a zone is a row in domains, and every record is a row in records. That means a configuration management tool or a Python script can create records transactionally, with no zone-file reload and no risk of a partially applied edit.

# /etc/powerdns/pdns.d/pdns.local.gpgsql.conf
launch=gpgsql
gpgsql-host=127.0.0.1
gpgsql-dbname=pdns
gpgsql-user=pdns
gpgsql-password=<db-password>

# /etc/powerdns/pdns.conf (key settings)
local-address=127.0.0.1
local-port=5300
api=yes
api-key=<api-key>
webserver=yes
webserver-address=127.0.0.1
dnssec=yes

Create a zone and manage records

# via pdnsutil
sudo pdnsutil create-zone example.internal ns1.example.internal
sudo pdnsutil add-record example.internal www A 60 10.20.0.15
sudo pdnsutil add-record example.internal @ MX "10 mail.example.internal"
sudo pdnsutil add-record example.internal db CNAME db-primary.example.internal
sudo pdnsutil list-zone example.internal
sudo pdnsutil check-zone example.internal

# via the HTTP API
curl -s -H "X-API-Key: <api-key>" http://127.0.0.1:8081/api/v1/servers/localhost/zones | jq '.[].name'
curl -s -X PATCH -H "X-API-Key: <api-key>" -H "Content-Type: application/json" \
  http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.internal. \
  -d '{"rrsets":[{"name":"www.example.internal.","type":"A","ttl":60,
       "changetype":"REPLACE","records":[{"content":"10.20.0.16","disabled":false}]}]}'

pdnsutil check-zone before and after every change is the habit that prevents the classic mistake: an NS record pointing at a name the zone does not contain, which makes the whole zone look broken to some resolvers and fine to others.

DNSSEC in three commands

sudo pdnsutil secure-zone example.internal
sudo pdnsutil show-zone example.internal
sudo pdnsutil export-zone-ds example.internal        # give this to the parent
dig +dnssec @127.0.0.1 -p 5300 example.internal DNSKEY +short
dig +dnssec @127.0.0.1 -p 5300 www.example.internal A +short

Publish the DS record at the parent and remember the parent is usually a different system — an internal AD domain, a registrar, or another PowerDNS instance. Until the DS is published, validating resolvers will treat the zone as unsigned rather than broken, which is a comfortable place to be while you verify. Keep the zone's NSEC mode in mind for large zones: NSEC3 with a low iteration count avoids the "zone too expensive to serve" problem that NSEC3 with high iterations creates.

dnsdist in front: caching, balancing and rate limits

-- /etc/dnsdist/dnsdist.conf
setLocal('0.0.0.0:53')

-- upstreams: two PowerDNS authoritative nodes
newServer({address='10.20.0.11:5300', name='auth1', checkName='example.internal.',
           checkType='SOA'})
newServer({address='10.20.0.12:5300', name='auth2', checkName='example.internal.',
           checkType='SOA'})

-- packet cache: absorb repeat queries
pc = newPacketCache(100000, {maxTTL=86400, minTTL=1, temporaryFailureTTL=60,
                             staleTTL=60, dontAge=false})
getPool(''):setCache(pc)

-- rate limit abusive clients but not your own resolvers
addAction(MaxQPSIPRule(50, 4, 32), DelayAction(30))
-- allow the internal resolver through without limits
addAction(NetmaskGroupRule({newNMG({'10.10.0.0/16'})}), SkipCacheAction)

Two design notes. First, keep the authoritative servers bound to localhost or a private interface and let dnsdist own port 53 — anything that can reach the backend directly bypasses every rate limit you wrote. Second, protect against DNS amplification: an ANY or DNSKEY query answered over UDP is a large response to a small request, so apply rate limits per netmask before adding any response-size-based rules.

# health and traffic visibility
dnsdist -c -e 'showServers()'
dnsdist -c -e 'dumpStats()' | head -30
dnsdist -c -e 'showCache()'
dig @127.0.0.1 example.internal SOA +short
dig @127.0.0.1 www.example.internal A +short

Verification checklist

  1. pdnsutil check-zone clean for every zone after each change.
  2. dig +dnssec returns the AD flag when queried from a validating resolver.
  3. Both authoritative nodes answer directly on port 5300 (proving the backend, not just the front end, is healthy).
  4. dnsdist reports both servers up in showServers(), and dumpStats() shows cache hits growing.
  5. A query flood from a single source is delayed or dropped, while normal traffic keeps resolving — test with dnsperf from a lab host, never from production.
  6. TXT and MX records for a new domain resolve from an external resolver, not only from the local host.

Operations and integration

  • Back up the database, not just the zone exports; the schema holds DNSSEC key material in cryptokeys.
  • Automate record changes through the API so every edit is logged in one place; the raw SQL INSERT that looks convenient is the one that skips the metadata another team depends on.
  • Monitor query rates per client, cache hit ratio and SERVFAIL rates — a rising SERVFAIL usually means the backend database connection pool, not DNS.
  • Keep the recursion and authoritative roles separate. If you also run a resolver, the Unbound DNSSEC validation setup covers a validating resolver in front of your authoritative data.
  • If a Windows DNS zone still owns part of the namespace, document the conditional forwarders explicitly — the split is described in the Windows DNS zones and conditional forwarder guide.
  • Familiarise the team with dig and nslookup diagnostics, including how to force TCP and disable recursion when testing an authoritative server.

原文链接:https://doc.powerdns.com/authoritative/