Learn OpenStack · core/dns
Designate Internals
Backend Driver Framework
Designate uses a pluggable backend driver model to support multiple DNS server implementations. The driver interface (designate.backend.base.Backend) defines the operations that each driver must implement:
class Backend:
def create_zone(self, context, zone): ...
def update_zone(self, context, zone): ...
def delete_zone(self, context, zone): ...
The driver is invoked by designate-worker when it receives a task from designate-central via RabbitMQ.
Backend Types
| Backend | Driver Module | Mechanism |
|---|---|---|
| BIND9 (agent) | designate.backend.agent_backend.impl_bind9 |
Connects to a designate-agent sidecar on the BIND9 host via RPC; the agent runs rndc commands locally |
| BIND9 (direct) | designate.backend.impl_bind9 |
designate-worker sends RNDC commands directly to BIND9 (requires network access to RNDC port 953) |
| PowerDNS | designate.backend.impl_pdns4 |
Calls the PowerDNS authoritative API (HTTP) to create/delete zones and records |
| Infoblox | designate.backend.impl_infoblox |
Calls the Infoblox NIOS REST API (WAPI) |
| Akamai | designate.backend.impl_akamai |
Calls the Akamai Fast DNS REST API |
| NOOP | designate.backend.impl_noop |
Used for testing; does nothing |
BIND9 Agent Backend (Most Common)
In most deployments, Designate does not manage BIND9 directly. Instead, a designate-agent process runs on each BIND9 nameserver host. The flow is:
designate-worker
│ RPC (oslo.messaging)
▼
designate-agent (on BIND9 host)
│ subprocess
▼
rndc addzone / modzone / delzone → BIND9
The designate-agent translates Designate's zone creation/deletion tasks into rndc commands:
rndc addzone example.com. '{type slave; masters { <mdns-ip> port 5354; }; };'rndc delzone example.com.rndc modzone example.com. ...
When BIND9 receives the addzone command, it contacts designate-mdns to pull the zone data via AXFR.
BIND9 Direct Backend (Simpler, Less Scalable)
For single-node setups, designate-worker can manage BIND9 directly:
# /etc/designate/designate.conf
[service:worker]
enabled_backends = bind9
[backend:bind9]
type = bind9
[backend:bind9:bind9]
rndc_host = 127.0.0.1
rndc_port = 953
rndc_config_file = /etc/bind/rndc.conf
rndc_key_file = /etc/bind/rndc.key
PowerDNS Backend
PowerDNS is managed via its authoritative API. Designate calls:
POST /api/v1/servers/localhost/zonesto create a zone (as a slave that transfers from mdns)DELETE /api/v1/servers/localhost/zones/<zone_id>to delete a zone- PowerDNS then fetches zone data from
designate-mdnsvia AXFR
[backend:pdns4]
type = pdns4
[backend:pdns4:pdns4]
host = 127.0.0.1
port = 8081
api_endpoint = http://127.0.0.1:8081
api_token = <pdns_api_key>
Infoblox Backend
Infoblox integration uses the NIOS WAPI. Designate creates a delegated zone object in Infoblox that points to Designate's mdns as the authoritative source:
[backend:infoblox]
type = infoblox
[backend:infoblox:infoblox]
wapi_url = https://infoblox.example.com/wapi/v2.1
username = admin
password = <password>
ns_group = designate-ns-group
Pool Configuration
Pools are defined in pools.yaml and loaded into the database with designate-manage pool update. A complete pool configuration:
---
- name: default
description: Default BIND9 pool
attributes: {}
ns_records:
- hostname: ns1.example.com.
priority: 1
- hostname: ns2.example.com.
priority: 2
nameservers:
- host: 198.51.100.1
port: 53
- host: 198.51.100.2
port: 53
targets:
- type: bind9
description: Primary BIND9 server
masters:
- host: 192.168.1.10 # designate-mdns IP
port: 5354
options:
host: 198.51.100.1
port: 953
rndc_key_file: /etc/designate/rndc.key
rndc_config_file: /etc/designate/rndc.conf
clean_zonefile: True
- type: bind9
description: Secondary BIND9 server
masters:
- host: 192.168.1.10
port: 5354
options:
host: 198.51.100.2
port: 953
rndc_key_file: /etc/designate/rndc.key
rndc_config_file: /etc/designate/rndc.conf
also_notifies:
- host: 198.51.100.50
port: 53
Load after editing:
designate-manage pool update --file /etc/designate/pools.yaml
designate-manage pool list
Multi-Pool Setup
Operators can define multiple pools for different purposes:
- name: internal
description: Internal zones served by local BIND9
...
- name: external
description: External zones served by PowerDNS + Akamai
...
Pool selection for a zone is controlled by a scheduler configured in designate.conf:
[scheduler:pool_manager_scheduler]
filters = random, in_doubt_default_pool
Zones can also be explicitly pinned to a pool at creation time:
openstack zone create \
--email admin@example.com \
--attributes pool_id:<external-pool-uuid> \
external.example.com.
Serial Number Management
The SOA serial number is a 32-bit unsigned integer that must increase with every zone change. Designate manages this automatically using the configured serial_generator.
UNIX Timestamp Strategy (Default)
[service:central]
serial_generator = timestamp
- Serial = current UNIX timestamp at time of change
- Always increases
- Gap between serials reflects the time between changes
- Can produce large jumps if the zone is infrequently modified
Datestamp Strategy
[service:central]
serial_generator = datestamp
- Serial format:
YYYYMMDDnn(date + 2-digit sequence) - Familiar to DNS operators used to manual BIND9 serial management
- Supports up to 99 changes per day
Increment Strategy
[service:central]
serial_generator = increment
- Serial starts at 1 and increments by 1 with each change
- Smallest gaps but doesn't encode timestamp information
SOA Record Auto-Generation
Designate auto-generates the SOA record for every primary zone and updates it whenever zone configuration changes. The SOA record is constructed from:
example.com. 300 IN SOA ns1.example.com. admin.example.com. (
2024030101 ; serial
3600 ; refresh (seconds before secondary checks for updates)
600 ; retry (seconds before secondary retries after failed refresh)
86400 ; expire (seconds secondary may continue serving if primary unreachable)
300 ; minimum TTL (negative caching TTL)
)
These values come from pool configuration (ns_records for the primary NS) and designate.conf:
[service:central]
default_ttl = 300
default_soa_refresh = 3600
default_soa_retry = 600
default_soa_expire = 86400
default_soa_minimum = 300
The SOA record is read-only via the Designate API — it cannot be created or deleted by API users. Attempting to create a recordset with type SOA returns a 400 error.
Mini-DNS (designate-mdns) — AXFR Zone Transfer
designate-mdns is a purpose-built DNS server that serves zone data directly from the Designate database. It is not a general-purpose DNS server — it only handles:
- NOTIFY (outbound): sends DNS NOTIFY messages to backend nameservers when a zone changes
- AXFR/IXFR (inbound requests): responds to zone transfer requests from backend nameservers
AXFR Flow
1. designate-central receives API call to update record set
2. designate-central increments serial, writes new recordset to DB
3. designate-central dispatches UPDATE_ZONE task to designate-worker
4. designate-worker calls backend driver (e.g., BIND9 agent)
5. Backend driver sends RNDC or API call to nameserver
6. Nameserver receives NOTIFY from designate-mdns (step 4a, parallel)
7. Nameserver sends AXFR request to designate-mdns
8. designate-mdns reads zone from DB, generates RFC 1035 wire-format AXFR response
9. Nameserver stores the zone and begins serving it
designate-mdns config:
[service:mdns]
host = 192.168.1.10 # Management network IP
port = 5354
tcp_backlog = 100
tcp_recv_timeout = 0.5
TSIG key configuration for authenticating AXFR:
[backend:bind9:tsig_key:default]
name = designate-tsig
algorithm = hmac-sha256
secret = <base64-encoded-key>
NOTIFY Flow
When a zone is updated, designate-mdns sends a DNS NOTIFY to each nameserver in the pool's nameservers list and also_notifies list. The NOTIFY carries the new serial number. Standard-compliant nameservers respond with NOTIMPL (they will pull via AXFR) or NOERROR (they checked the serial and decided whether to pull).
designate-sink — Notification Handling
designate-sink subscribes to oslo.messaging notification events published by other OpenStack services. This enables automatic DNS record lifecycle management.
Notification Flow
Nova/Neutron ──► oslo.messaging (notifications.info topic)
│
▼
designate-sink (consumes)
│
▼
evaluate handler rules
│
┌────────────┴────────────┐
▼ ▼
create recordset delete recordset
(via designate-central) (via designate-central)
Neutron Floating IP Handler
[handler:neutron_floatingip]
event_types = floatingip.create.end, floatingip.update.end, floatingip.delete.end
control_exchange = neutron
zone_id = <reverse-zone-uuid>
format_pattern = %(octet0)s-%(octet1)s-%(octet2)s-%(octet3)s.cloud.example.com.
When floatingip.create.end fires with IP 203.0.113.42:
- Sink handler parses
octet0=203,octet1=0,octet2=113,octet3=42 - Generates PTR name
42.113.0.203.in-addr.arpa. - Generates PTR target
203-0-113-42.cloud.example.com. - Creates PTR record in the reverse zone via designate-central
When floatingip.delete.end fires:
- Sink finds and deletes the corresponding PTR record
Nova Instance Handler
[handler:nova_fixed]
event_types = compute.instance.create.end, compute.instance.delete.end
control_exchange = nova
zone_id = <forward-zone-uuid>
format_pattern = %(hostname)s.nova.cloud.example.com.
When an instance is created with hostname web-01, the sink creates:
- web-01.nova.cloud.example.com. A <instance-fixed-ip>
Database Schema Overview
Key Designate tables:
| Table | Contents |
|---|---|
zones |
Zone name, email, TTL, serial, status, pool_id, project_id |
recordsets |
Zone ID, name, type, TTL, status |
records |
RecordSet ID, data (the actual record value), hash |
pool_attributes |
Key/value attributes for pool scheduler |
pool_nameservers |
Publicly advertised NS records per pool |
pool_targets |
Backend target configs (type, host, options) |
tsigkeys |
TSIG key name, algorithm, secret, scope |
zone_transfer_requests |
Pending zone transfers (source zone, key, target project) |
zone_transfer_accepts |
Completed zone transfer records |
Periodic Tasks (designate-producer)
designate-producer runs the following periodic tasks:
| Task | Interval | Purpose |
|---|---|---|
zone_purge |
Configurable (default 3600 s) | Hard-delete zones in DELETED state older than zone_purge_after |
secondary_zone_refresh |
Configurable (default 300 s) | Check serial of secondary zones against upstream primaries; trigger AXFR if stale |
delayed_notify |
5 s | Re-send NOTIFY to backends that haven't confirmed receipt |
worker_recovery |
120 s | Re-queue tasks stuck in PENDING state longer than worker_recovery_timeout |
[service:producer]
enabled_tasks = zone_purge, secondary_zone_refresh, delayed_notify, worker_recovery
threads = 10
Configuration Reference
Key designate.conf sections:
[service:api]
listen = 0.0.0.0:9001
api_base_uri = http://designate-api:9001/
auth_strategy = keystone
enable_api_v2 = True
[service:central]
backend_driver = agent # or pdns4, infoblox, etc.
scheduler_filters = random, in_doubt_default_pool
serial_generator = timestamp
default_ttl = 300
[service:worker]
enabled_backends = bind9
notify = True
workers = 10
[service:mdns]
host = 192.168.1.10
port = 5354
[service:sink]
enabled_notification_handlers = neutron_floatingip
[handler:neutron_floatingip]
zone_id = <uuid>
format_pattern = %(octet0)s-%(octet1)s-%(octet2)s-%(octet3)s.example.com.