Master-Slave PowerDNS Configuration and Domain Migration from BIND Using API and AXFR
One of my latest projects was a fully dockerized name server infrastructure based on PowerDNS: one master and two slaves — one in the same subnet and the second running in a cloud on a basic virtual machine.
Why PowerDNS? Because I needed an API, a proper admin-friendly web interface, user management, and LDAP integration. PowerDNS fully matched my requirements.
Why Docker? Because I wanted some level of automation and IaC — Docker fits nicely in the middle, where you have several configs and docker-compose files fully describing the container configuration.
Here are my domain import command-line tools, written in Bash: powerdns-api
This script performs the main actions needed to migrate from almost any name server (by reading zones via AXFR) into PowerDNS. You need both pdns-master and pdns-admin, because the PowerDNS API alone does not provide all the required functionality.
And then you just use the command line to migrate domains:
Example of master name server:
Example of slave name server:
Why PowerDNS? Because I needed an API, a proper admin-friendly web interface, user management, and LDAP integration. PowerDNS fully matched my requirements.
Why Docker? Because I wanted some level of automation and IaC — Docker fits nicely in the middle, where you have several configs and docker-compose files fully describing the container configuration.
Here are my domain import command-line tools, written in Bash: powerdns-api
This script performs the main actions needed to migrate from almost any name server (by reading zones via AXFR) into PowerDNS. You need both pdns-master and pdns-admin, because the PowerDNS API alone does not provide all the required functionality.
And then you just use the command line to migrate domains:
cat domains.txt | awk '{print "./pdns-migrator.sh --create-slave-zone="$1}'
cat domains.txt | awk '{print "./pdns-migrator.sh --axfr-retrieve="$1}'
cat domains.txt | awk '{print "./pdns-migrator.sh --convert-to-master="$1}'
cat domains.txt | awk '{print "./pdns-migrator.sh --replace-ns-soa="$1}'
I really like using a startup service for Docker that removes all data on stop/start — just to make sure I don’t forget to move changes out of containers 😉
# cat docker-compose.service # # /etc/systemd/system/docker-compose.service # [Unit] Description=Docker compose start service Requires=docker.service network-online.target After=docker.service network-online.target [Service] PIDFile=/run/docker-compose.pid WorkingDirectory=/srv/docker-compose ExecStartPre=/usr/bin/docker compose -f docker-compose.yaml down -v ExecStartPre=/usr/bin/docker compose -f docker-compose.yaml rm -fv ExecStart=/usr/bin/docker compose -f docker-compose.yaml up ExecStop=/usr/bin/docker compose down -v StandardOutput=null StandardError=null [Install] WantedBy=multi-user.target
Example of master name server:
# cat master-docker-compose.yaml
x-all-defaults: &all-defaults
logging:
driver: fluentd
services:
fluentd:
image: fluent/fluentd:v1.18-debian-1
hostname: fluentd
container_name: fluentd
networks:
- pdns
ports:
- '127.0.0.1:24224:24224'
- '127.0.0.1:24224:24224/udp'
volumes:
- 'logs:/fluentd/log'
db:
<< : *all-defaults
image: mariadb:11.7.2-ubi
hostname: db
container_name: db
networks:
pdns:
ipv4_address: 172.6.0.20
volumes:
- /etc/localtime:/etc/localtime:ro
- db:/var/lib/mysql:Z
environment:
- MARIADB_ROOT_PASSWORD=PASSWORD
depends_on:
- fluentd
healthcheck:
test: ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized']
timeout: 10s
retries: 5
pdns-master:
<< : *all-defaults
image: pschiffe/pdns-mysql:4.9
hostname: ns1.domain.com
container_name: pdns-master
networks:
pdns:
ipv4_address: 172.6.0.30
ports:
- '53:53'
- '53:53/udp'
- '8987:8987'
extra_hosts:
- 'ns1.domain.com:111.111.111.111'
- 'ns2.domain.com:10.0.0.10'
- 'ns3.domain.com:10.0.100.55'
volumes:
- /etc/localtime:/etc/localtime:ro
environment:
- PDNS_gmysql_host=172.6.0.20
- PDNS_gmysql_password=PASSWORD
- PDNS_primary=yes
- PDNS_api=yes
- PDNS_api_key=PASSWORD
- PDNS_webserver=yes
- PDNS_webserver_password=PASSWORD
- PDNS_webserver_address=0.0.0.0
- PDNS_webserver_port=8987
- PDNS_webserver_allow_from=1.2.3.4/32
- PDNS_version_string=anonymous
- PDNS_default_ttl=1500
- PDNS_allow_axfr_ips=10.0.0.10/32,111.111.111.111/32
- PDNS_allow_notify_from=10.0.0.10/32,111.111.111.111/32
- PDNS_only_notify=10.0.0.10/32,111.111.111.111/32
- PDNS_also_notify=10.0.0.10/32,111.111.111.111/32
- PDNS_default_soa_content=ns1.domain.com. hostmaster.@ 0 3600 3600 1209600 86400
- PDNS_log_dns_details=yes
- PDNS_loglevel=4
depends_on:
- fluentd
- db
pdns-admin:
<< : *all-defaults
image: pschiffe/pdns-admin:0.4.1
hostname: pdns-admin
container_name: pdns-admin
networks:
pdns:
ipv4_address: 172.6.0.35
ports:
- '8989:8080'
volumes:
- /etc/localtime:/etc/localtime:ro
environment:
- PDNS_ADMIN_SQLA_DB_HOST=172.6.0.20
- PDNS_ADMIN_SQLA_DB_PASSWORD=PASSWORD
- PDNS_ADMIN_SALT=$$11$$11$$SALT
- PDNS_API_KEY=PASSWORD
- PDNS_API_URL=http://172.6.0.30:8987
- PDNS_VERSION=4.9.0
depends_on:
- fluentd
- db
- pdns-master
phpmyadmin:
<< : *all-defaults
image: phpmyadmin:5
hostname: phpmyadmin
container_name: phpmyadmin
networks:
pdns:
ipv4_address: 172.6.0.40
ports:
- '8988:80'
volumes:
- /etc/localtime:/etc/localtime:ro
depends_on:
- fluentd
- db
healthcheck:
test: ['CMD', 'curl', '-fsSL', 'http://127.0.0.1:80']
timeout: 10s
retries: 5
networks:
pdns:
ipam:
config:
- subnet: 172.6.0.0/16
gateway: 172.6.0.1
volumes:
db:
driver: local
driver_opts:
type: none
o: bind
device: /data/db_data
logs:
driver: local
driver_opts:
type: none
o: bind
device: /data/logs_data
Example of slave name server:
# cat slave-docker-compose.yaml
x-all-defaults: &all-defaults
logging:
driver: fluentd
services:
fluentd:
image: fluent/fluentd:v1.18-debian-1
hostname: fluentd
container_name: fluentd
networks:
- pdns
ports:
- '127.0.0.1:24224:24224'
- '127.0.0.1:24224:24224/udp'
volumes:
- 'logs:/fluentd/log'
db:
<< : *all-defaults
image: mariadb:11.7.2-ubi
hostname: db
container_name: db
networks:
pdns:
ipv4_address: 172.6.0.20
volumes:
- /etc/localtime:/etc/localtime:ro
- db:/var/lib/mysql:Z
environment:
- MARIADB_ROOT_PASSWORD=PASSWORD
depends_on:
- fluentd
healthcheck:
test: ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized']
timeout: 10s
retries: 5
pdns-slave:
<< : *all-defaults
image: pschiffe/pdns-mysql:4.9
hostname: ns2.domain.com
container_name: pdns-slave
networks:
pdns:
ipv4_address: 172.6.0.30
ports:
- '53:53'
- '53:53/udp'
- '8987:8987'
extra_hosts:
- 'ns1.domain.com:111.111.111.111'
- 'ns2.domain.com:10.0.0.10'
- 'ns3.domain.com:10.0.100.55'
volumes:
- /etc/localtime:/etc/localtime:ro
environment:
- PDNS_gmysql_host=172.6.0.20
- PDNS_gmysql_dbname=powerdnsslave
- PDNS_gmysql_password=PASSWORD
- PDNS_secondary=yes
- PDNS_autosecondary=yes
- PDNS_webserver=yes
- PDNS_webserver_password=PASSWORD
- PDNS_webserver_address=0.0.0.0
- PDNS_webserver_port=8987
- PDNS_webserver_allow_from=1.2.3.4/32
- PDNS_version_string=anonymous
- PDNS_disable_axfr=yes
- PDNS_allow_notify_from=10.0.0.10/32,111.111.111.111/32
- PDNS_log_dns_details=yes
- PDNS_loglevel=4
- SUPERMASTER_IPS=10.0.0.123
- SUPERMASTER_HOSTS=ns1.domain.com
depends_on:
- fluentd
- db
phpmyadmin:
<< : *all-defaults
image: phpmyadmin:5
hostname: phpmyadmin
container_name: phpmyadmin
networks:
pdns:
ipv4_address: 172.6.0.40
ports:
- '8988:80'
volumes:
- /etc/localtime:/etc/localtime:ro
depends_on:
- fluentd
- db
healthcheck:
test: ['CMD', 'curl', '-fsSL', 'http://127.0.0.1:80']
timeout: 10s
retries: 5
networks:
pdns:
ipam:
config:
- subnet: 172.6.0.0/16
gateway: 172.6.0.1
volumes:
db:
driver: local
driver_opts:
type: none
o: bind
device: /data/db_data
logs:
driver: local
driver_opts:
type: none
o: bind
device: /data/logs_data
Easy as 1-2-3: Build → Migrate → Use 😉
Comments
Post a Comment