Contents

How to setup PowerDNS with replication

In this post I’d like to give you a simple step-by-step guide on how to setup a replicated PDNS server that can be used, for example, as on-premise Kubernetes DNS server with external-dns.

But first…

What is PowerDNS

PowerDNS is an authoritative/recursive DNS, server developed under GPL, that can a wide range of backends to store DNS zones:

  • Relational databases
  • BIND zone files
  • LDAP

One useful functionality of PDNS is the control and monitoring web API, that is quite well documented, and that enables full control over zones manipulation with external tools and automations, kubernetes external-dns, etc.

Environment

The environment is quite simple as is only composed by two freshly installed Debian 11 virtual machines.

schematic-768x561.png

Setup

The installation here described is completely manual. I don’t recommend this way of installing and configuring PDNS unless you are practicing with a test environment or are trying to have a first contact with this kind of setups. For real applications the process should be automated and hardening measures should be applied to enforce security.

Warning
This article can be followed using any Linux distribution. At first glance there shouldn’t be any issues by using a Debian based distribution that uses apt-packages, despite of the fact that repository URLs change, but keep in mind that if you use a RHEL based distribution more adjustments could be needed.

Pre-requirements

All the steps of this section should be applied to both nodes, the master and the slave.

To simplify the setup we will install the DB alongside PDNS, but, keep in mind that the is recommended to have both services on separate machines.

1
2
3
4
5
6
7
8
sudo apt-get install software-properties-common dirmngr apt-transport-https -y
sudo apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc'
sudo add-apt-repository 'deb [arch=amd64,arm64,ppc64el] http://ams2.mirrors.digitalocean.com/mariadb/repo/10.5/debian bullseye main'
sudo apt update
sudo apt install mariadb-server -y

# Just secure the root user and remove samnple data
sudo mysql_secure_installation

We will need to modify MariaDB’s configuration to change how it generate replication events to the one that only takes into account a ROW change. You can checkout the official documentation on how binlog works here.

1
2
3
4
5
6
7
8
9
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
	#Add this line under the [mysqld] tag
	binlog-format=ROW

# Restart the DB to apply changes
sudo systemctl restart mariadb

# Enable on-boot start of the DB
sudo systemctl enable mariadb

Create the PDNS database, user, privileges and load the schema.

1
2
3
4
5
6
7
mysql -u root -p
MariaDB [(none)]> show variables like 'binlog_format'; # Asegurate de que haya cambiado a row
MariaDB [(none)]> create database powerdns;
MariaDB [(none)]> GRANT ALL PRIVILEGES ON powerdns.* TO 'powerdns'@'localhost' IDENTIFIED BY 'yourdatabasepassword';
MariaDB [(none)]> exit;

curl https://raw.githubusercontent.com/PowerDNS/pdns/rel/auth-4.5.x/modules/gmysqlbackend/schema.mysql.sql | mysql --user=root --password=<ROOT-PASSWORD from mysql_secure-installation> --database=powerdns

We will install PDNS from its repos instead of the Debian ones to be able to install a specific version, 4.5 in this example, instead of the distribution selected one.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Add PDNS PGP keys and packet repsitories
sudo nano /etc/apt/sources.list.d/pdns.list
deb [arch=amd64] http://repo.powerdns.com/ubuntu focal-auth-45 main
deb [arch=amd64] http://repo.powerdns.com/ubuntu focal-rec-45 main

sudo nano /etc/apt/preferences.d/pdns
Package: pdns-*
Pin: origin repo.powerdns.com
Pin-Priority: 600

curl https://repo.powerdns.com/FD380FBB-pub.asc | sudo apt-key add -

# Install PDNS Authoritative Server, Recursor and MySQL/MariaDB connector
sudo apt-get update && sudo apt-get install pdns-server pdns-recursor pdns-backend-mysql -y

Master node preparation

Now that both server already have the common configuration and packages installed it’s time to start configuring the master node. To do so, we start configuring replication at MariaDB master by modifying its configuration file with the following changes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
# Add/modify the following lines
bind-address = 0.0.0.0
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
expire_logs_days = 10
max_binlog_size = 100M
binlog_do_db = powerdns

# Restart MariaDB to reload config
sudo systemctl restart mariadb

Next, we create the user used by the database for replication and,, at the same time, we will write down the position of the binlog that we will use later to configure replication in the slave node, as that value will tell the DB in which position of the binlog it should start to replicate.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
mysql -u root -p

# Create replication user and give permissions
MariaDB [(none)]> GRANT REPLICATION SLAVE ON *.* TO 'pdns-secondary'@'<SECONDARY_SERVER_IP>' IDENTIFIED BY '<SECRET_PASSWORD>';
MariaDB [(none)]> FLUSH PRIVILEGES;
MariaDB [(none)]> show master status;

## Demo output of the status command. Just write down the position value
	+------------------+----------+--------------+------------------+
	| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
	+------------------+----------+--------------+------------------+
	| mysql-bin.000001 |      634 | powerdns     |                  |
	+------------------+----------+--------------+------------------+

Finally, we only need to configure PDNS Authoritative and Recursor with the following changes in their configuration files. There’s no need to remove the entire file, just ensure that the parameters proposed here contain the expected values.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
sudo nano /etc/powerdns/pdns.conf

# If you plan to use the API then enable it and webserver too
api=yes
api-key=<SECRET-API-KEY>
webserver=yes

# The IP of the interface where PDNS web server will listen
webserver-address=192.168.1.6   # Master IP address
# Explicit allow request to API/web server from this IPs
webserver-allow-from=192.168.1.0/24

# In which which port will we listen to DNS requests
local-port=5300

# This is the master node
master=yes
slave=no

# Database parameters
launch=gmysql
gmysql-host=127.0.0.1
gmysql-user=powerdns
gmysql-password=<POWERDNS-MARIDB-PASSWORD>
gmysql-dbname=powerdns

# Reload PDNS configuration
sudo systemctl restart pdns.service

There are two points in the previous configuration that are worth mentioning:

  • local-port=5300: DNS request that arrive to servers (both, master and slave) are handled by the recursor, not the authoritative server, so that’s why recursors are the ones that listen on the standard DNS port 53. Authoritative servers will be requested only from the recursors on the same instance if a DNS request for their owned zone arrives. Saying that, any free port is valid to be assigned to the authoritative server, 5300 in this case.
  • api=yes and webserver=yes: This post is the product of the need of owning a DNS server that can be easily integrated with kubernetes external-dns. With PDNS API the process is straightforward and only the IP of the server and the API key are needed. If you are not planning to use the API or the monitoring portal feel free to comment out those options.

Recursor configuration is far easier and can be summarized as the following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
sudo nano /etc/powerdns/recursor.conf

# IP of the interface used to listen DNS requests
local-address=192.168.1.6

# Forward requests of *.subdomain.example.com to the Authoritative server
forward-zones=subdomain.example.com=127.0.0.1:5300

# Rest of queries goes to google DNSs
forward-zones-recurse=.=8.8.8.8
local-port=53
dnssec=off

# Reload PDNS recursor configuration
sudo systemctl restart pdns-recursor.service

Slave preparation

In the slave node the first step is to change MariaDB config to increment the server identifier and the binglog and database used for replication.

1
2
3
4
5
6
7
8
server-id=2
bind-address = 0.0.0.0
relay-log=slave-relay-bin
relay-log-index=slave-relay-bin.index
replicate-do-db=powerdns

# Restart MariaDB to reload config
sudo systemctl restart mariadb

To start the replication process we need to command an explicit start in MariaDB shell. At this point we should tell MariaDB the binlog position that we wrote down previously when we configured the master node.

1
2
3
4
5
6
7
mysql -u root -p

MariaDB [(none)]> change master to master_host='<PRIMARY_SERVER_IP>', master_user='pdns-secondary', master_password='<SECRET_PASSWORD>', master_log_file='mysql-bin.000001', master_log_pos=<POSITION>;
MariaDB [(none)]> start slave;

# If all is OK this will show "Waiting for master to send event"
MariaDB [(none)]> show slave status

Previous step gave us a fully configured database that replicates. All that remains is to configure the slave PDNS with the following file for the Authoritative server.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
sudo nano /etc/powerdns/pdns.conf

# If you plan to use the API then enable it and webserver too
api=yes
api-key=<SECRET-API-KEY>
webserver=yes

# The IP of the interface where PDNS web server will listen
webserver-address=192.168.1.6    # Slave IP address
# Explicit allow request to API/web server from this IPs
webserver-allow-from=192.168.1.0/24

# In which which port will we listen to DNS requests
local-port=5300

# This is the master node
master=no   # This change is important is the slave
slave=yes   # This change is important is the slave

# Database parameters
launch=gmysql
gmysql-host=127.0.0.1
gmysql-user=powerdns
gmysql-password=<POWERDNS-MARIDB-PASSWORD>
gmysql-dbname=powerdns

# Reload PDNS configuration
sudo systemctl restart pdns.service

To wind up the configuration process we can configure the slave recursor with the following configuration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
sudo nano /etc/powerdns/recursor.conf

# IP of the interface used to listen DNS requests
local-address=192.168.1.6    # Slave IP address

# Forward requests of *.subdomain.example.com to the Authoritative server
forward-zones=subdomain.example.com=127.0.0.1:5300

# Rest of queries goes to google DNSs
forward-zones-recurse=.=8.8.8.8
local-port=53
dnssec=off

# Reload PDNS recursor configuration
sudo systemctl restart pdns-recursor.service

Testing

At this point we have both PDNS server up and running so it is time to check that all is working as expected with the help of the pdnsutil tool to create and edit our DNS zone in the authoritative server. With nslookup we will verify that the DNS entries that we will create are properly resolving. IN the master node:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Create our sample zone (this zone is the one that we delegate to out authoritative server)
sudo pdnsutil create-zone subdomain.example.com

# Add the NSs server entries
sudo pdnsutil add-record subdomain.example.com @ NS ns1.subdomain.example.com
sudo pdnsutil add-record subdomain.example.com @ NS ns2.subdomain.example.com
# Provide de A records for the NSs
sudo pdnsutil add-record subdomain.example.com ns1 A 192.168.1.6
sudo pdnsutil add-record subdomain.example.com ns2 A 192.168.1.7

# Verify that the zone seems to be as expected
sudo pdnsutil list-zone subdomain.example.com

# Verify no errors in the zone
sudo pdnsutil check-all-zones

# The nslookup to the slave should answer with the A record of ourselfs if replication is OK
nslookup ns1.subdomain.example.com 192.168.1.7
Server:		192.168.1.7
Address:	192.168.1.7#53

Non-authoritative answer:
Name:	ns1.subdomain.example.com
Address: 192.168.1.6