The following guide outlines the deployment methodology and installation instructions for DNS based Global Load Balancing service. The solution includes PowerDNS Authoritative application deployed on Centos 7.9 servers. The application supports static as well as dynamic DNS records. The dynamic records contain small snippets which can perform different checks based on End User IP address, Geo Location, Application health and return appropriate DNS records.
PowerDNS Auth Server Installation
Install the EPEL and the PowerDNS Auth repositories. Install the PowerDNS server with GeoIP and SQLite backends.
$ sudo yum install epel-release yum-plugin-priorities $ $ sudo curl -o /etc/yum.repos.d/powerdns-auth-45.repo \ https://repo.powerdns.com/repo-files/centos-auth-45.repo $ $ sudo yum update $ sudo yum install pdns pdns-backend-geoip pdns-backend-sqlite
Create the Database directory for the PowerDNS Sqlite backend. Import the DB schema and set proper File/Directory permissions.
$ sudo mkdir /var/lib/powerdns $ $ sudo sqlite3 /var/lib/powerdns/pdns.sqlite3 < \ /usr/share/doc/pdns-backend-sqlite-4.5.1/schema.sqlite3.sql $ $ sudo chown -R pdns:pdns /var/lib/powerdns $ $ sudo chown -R pdns /etc/pdns/
Download Maxmind GeoIP2 City database and move the file into the /etc/pdns directory.
Configuration of PowerDNS Application
The configurations need to be done in the configuration file /etc/pdns/pdns.conf. The Primary Authoritative server needs to be defined as Primary in the configuration file. The IPs to which zone transfer is allowed and which IP subnets to send notifications for zone updates needs to be configured. The Secondary servers need to be configured as secondary and the IP subnets from which to receive zone updates should be configured.
Primary Auth Server Configuration
################################# # primary Act as a primary # primary=yes ################################# # slave Act as a secondary secondary=no ################################# # allow-axfr-ips Allow zonetransfers only to these subnets # allow-axfr-ips=192.168.10.0/24,192.168.20.0/24 ################################# # daemon Operate as a daemon # daemon=no ################################# # edns-subnet-processing If we should act on EDNS Subnet options # edns-subnet-processing=yes ################################# # guardian Run within a guardian process # guardian=no ################################# # launch Which backends to launch and order to query them in # launch=gsqlite3,geoip gsqlite3-database=/var/lib/powerdns/pdns.sqlite3 ################################# # local-address Local IP addresses to which we bind # local-address=0.0.0.0, :: ################################# # only-notify Only send AXFR NOTIFY to these IP addresses or netmasks # only-notify=192.168.10.0/24, 192.168.20.0/24 ################################# # loglevel Amount of logging. Higher is more. Do not set below 3 # loglevel=6 ################################# # lua-health-checks-interval LUA records health checks monitoring interval in seconds # lua-health-checks-interval=20 ################################# # prevent-self-notification Don't send notifications to what we think is ourself # prevent-self-notification=yes ################################# # allow-notify-from Allow AXFR NOTIFY from these IP ranges. If empty, drop all incoming notifies. # allow-notify-from= ################################# # receiver-threads Default number of receiver threads to start # receiver-threads=2 ################################# # reuseport Enable higher performance on compliant kernels by using SO_REUSEPORT allowing each receiver thread to open its own socket # reuseport=yes ################################# # setgid If set, change group id to this gid for more security # setgid=pdns ################################# # setuid If set, change user id to this uid for more security # setuid=pdns ################################# # webserver Start a webserver for monitoring (api=yes also enables the HTTP listener) # webserver=yes webserver-address=0.0.0.0 webserver-allow-from=127.0.0.1,192.168.20.0/27 webserver-password=secure-pass webserver-port=8081 ################################# # consistent-backends Assume individual zones are not divided over backends. Send only ANY lookup operations to the backend to reduce the number of lookups # consistent-backends=yes ################################# # enable-lua-records Process LUA records for all zones (metadata overrides this) # enable-lua-records=yes geoip-database-files=/etc/pdns/GeoLite2-City.mmdb ################################# # cache-ttl Seconds to store packets in the PacketCache # cache-ttl=0 ################################# # query-cache-ttl Seconds to store query results in the QueryCache # query-cache-ttl= 0
Secondary Auth Server Configuration
################################# # primary Act as a primary # primary=no ################################# # slave Act as a secondary secondary=yes ################################# # allow-axfr-ips Allow zonetransfers only to these subnets # allow-axfr-ips= ################################# # daemon Operate as a daemon # daemon=no ################################# # edns-subnet-processing If we should act on EDNS Subnet options # edns-subnet-processing=yes ################################# # guardian Run within a guardian process # guardian=no ################################# # launch Which backends to launch and order to query them in # launch=gsqlite3,geoip gsqlite3-database=/var/lib/powerdns/pdns.sqlite3 ################################# # local-address Local IP addresses to which we bind # local-address=0.0.0.0, :: ################################# # only-notify Only send AXFR NOTIFY to these IP addresses or netmasks # only-notify=192.168.10.0/24, 192.168.20.0/24 ################################# # loglevel Amount of logging. Higher is more. Do not set below 3 # loglevel=5 ################################# # lua-health-checks-interval LUA records health checks monitoring interval in seconds # lua-health-checks-interval=20 ################################# # prevent-self-notification Don't send notifications to what we think is ourself # prevent-self-notification=yes ################################# # allow-notify-from Allow AXFR NOTIFY from these IP ranges. If empty, drop all incoming notifies. # allow-notify-from=192.168.10.0/24,192.168.20.0/24 ################################# # receiver-threads Default number of receiver threads to start # receiver-threads=2 ################################# # reuseport Enable higher performance on compliant kernels by using SO_REUSEPORT allowing each receiver thread to open its own socket # reuseport=yes ################################# # setgid If set, change group id to this gid for more security # setgid=pdns ################################# # setuid If set, change user id to this uid for more security # setuid=pdns ################################# # webserver Start a webserver for monitoring (api=yes also enables the HTTP listener) # webserver=yes webserver-address=0.0.0.0 webserver-allow-from=127.0.0.1,192.168.20.0/27 webserver-password=secure-pass webserver-port=8081 ################################# # consistent-backends Assume individual zones are not divided over backends. Send only ANY lookup operations to the backend to reduce the number of lookups # consistent-backends=yes ################################# # enable-lua-records Process LUA records for all zones (metadata overrides this) # enable-lua-records=yes geoip-database-files=/etc/pdns/GeoLite2-City.mmdb ################################# # cache-ttl Seconds to store packets in the PacketCache # cache-ttl=0 ################################# # query-cache-ttl Seconds to store query results in the QueryCache # query-cache-ttl= 0
Creation of GLB Zone
A subdomain will be used as the GLB zone. The subdomain will be delegated to the PowerDNS Auth servers in the master domain.com zone.
$ORIGIN domain.com. . . . . glbns1.domain.com. IN A 192.168.10.101 glbns2.domain.com. IN A 192.168.20.101 $ORIGIN glb.domain.com. $TTL 1D @ IN NS glbns1.domain.com. @ IN NS glbns2.domain.com.
The glb.domain.com zone will be created in the Primary/Secondary GLB servers. On the Primary server create the zone and set it as primary
$ sudo pdnsutil create-zone glb.domain.com $ sudo pdnsutil set-kind glb.domain.com primary
Define the variable EDITOR in /etc/environment file. The “pdnsutil edit-zone” command uses this variable to open file editor for configuring the zone files.
# /etc/environment EDITOR=/usr/bin/vi
Edit the zone glb.domain.com on the Primary and configure the SOA and NS record.
$ sudo pdnsutil edit-zone glb.domain.com
Enter the following in the opened editor and save the file.
$ORIGIN . glb.domain.com 3600 IN SOA glbns1.domain.com hostmaster.glb.domain.com 2021102801 10800 3600 604800 3600 glb.domain.com 3600 IN NS glbns1.domain.com. glb.domain.com 3600 IN NS glbns2.domain.com.
On the Secondary server create the slave zone and define the IP of primary to replicate the zone.
$ sudo pdnsutil create-slave-zone glb.domain.com 192.168.10.101
Verify from log files that the transfer of zone was done from Primary to Secondary.
$ sudo journalctl -u pdns -f . . 19:33:54 glbns1.domain.com pdns_server[15611]: 1 domain for which we are master needs notifications 19:33:54 glbns1.domain.com pdns_server[15611]: Skipped notification of domain 'glb.domain.com' to glbns1.domain.com. because it does not match only-notify. 19:33:54 glbns1.domain.com pdns_server[15611]: Queued notification of domain 'glb.domain.com' to 192.168.20.101:53 19:33:54 glbns1.domain.com pdns_server[15611]: AXFR-out zone 'glb.domain.com', client '192.168.20.101:38721', transfer initiated ا19:33:54 glbns1.domain.com pdns_server[15611]: AXFR-out zone 'glb.domain.com', client '192.168.20.101:38721', allowed: client IP is in allow-axfr-ips 19:33:54 glbns1.domain.com pdns_server[15611]: gsqlite3: connection to '/var/lib/powerdns/pdns.sqlite3' successful 19:33:54 glbns1.domain.com pdns_server[15611]: AXFR-out zone 'glb.domain.com', client '192.168.20.101:38721', AXFR finished 19:33:55 glbns1.domain.com pdns_server[15611]: Removed from notification list: 'glb.domain.com' to 192.168.20.101:53 (was acknowledged) 19:33:57 glbns1.domain.com pdns_server[15611]: No master domains need notifications
Verify on the Secondary that the zone transfer was done from Primary.
$ sudo journalctl -u pdns -f . . 19:33:54 glbns2.domain.com pdns_server[887]: Received NOTIFY for glb.domain.com from 192.168.10.101:12791 - queueing check 19:33:54 glbns2.domain.com pdns_server[887]: Domain 'glb.domain.com' is stale, master 192.168.10.101 serial 2021102803, our serial 2021102802 19:33:54 glbns2.domain.com pdns_server[887]: XFR-in zone: 'glb.domain.com', primary: '192.168.10.101', initiating transfer 19:33:54 glbns2.domain.com pdns_server[887]: XFR-in zone: 'glb.domain.com', primary: '192.168.10.101', starting AXFR 19:33:54 glbns2.domain.com pdns_server[887]: AXFR-in zone 'glb.domain.com', primary '192.168.10.101', retrieval started 19:33:54 glbns2.domain.com pdns_server[887]: AXFR-in zone: 'glb.domain.com', primary: '192.168.10.101', retrieval finished 19:33:54 glbns2.domain.com pdns_server[887]: AXFR-in zone: 'glb.domain.com', primary: '192.168.10.101', zone committed with serial 2021102803
Configuration of GLB Zone Records
The zone files can be edited using the “pdnsutil edit-zone” command.
Create A records
internal.glb.domain.com 1800 IN A 192.168.10.10 external.glb.domain.com 1800 IN A 192.168.20.20
Configure record to return the Latitude/Longitude of the client IP.
latlon.glb.domain.com 60 IN LUA LOC "latlonloc()"
Configure record to return the client IP.
whoami.glb.domain.com 60 IN LUA TXT "who:toString()"
Configure DNS views based on client IP. The following will return 192.168.1.1 for client source network 192.168.30.0/24 and 192.168.2.2 for everyone else.
view.glb.domain.com 60 IN LUA A "view({ " "{ {'192.168.30.0/24'}, {'192.168.1.1'}}," "{ {'0.0.0.0/0'}, {'192.168.2.2'}} " " }) "
Configure DNS CNAME records to return records based on client source network/IP.
www.glb.domain.com 60 IN LUA CNAME ";if(netmask({'192.168.30.0/24','172.16.0.0/16'})) then return 'internal.glb.domain.com' " "else return 'external.glb.domain.com' end"
Configure HTTP(s) health check for specific URL backend IPs and return records which return 200 OK.
web.glb.domain.com 60 IN LUA A "ifurlup('https://domain.com/', {'172.16.43.219', '172.16.188.234'}, {selector='all', timeout=4} )"
Configure TCP port status and return the IP which is closest to the Geo Location of the requester client.
app.glb.domain.com IN LUA A ( "ifportup(995, {'192.168.100.10', '192.168.200.10'}, " "{selector='pickclosest'}) ")
List the domain zone records
$ sudo pdnsutil list-zone glb.domain.com $ORIGIN . careers.glb.domain.com 60 IN LUA A "ifurlup('https://careers.domain.com/', {'124.109.43.219', '203.82.54.18'}, {selector='all', timeout=4})" glb.domain.com 3600 IN NS glbns1.domain.com. glb.domain.com 3600 IN NS glbns2.domain.com. glb.domain.com 3600 IN SOA glbns1.domain.com hostmaster.domain.com 2021102803 10800 3600 604800 3600 latlong.glb.domain.com 60 IN LUA LOC "latlonloc()" web.glb.domain.com 60 IN LUA A "ifurlup('https://domain.com/', {'172.16.43.219', '172.16.188.234'}, {selector='all', timeout=4} )" whoami.glb.domain.com 60 IN LUA TXT "who:toString()"
Check zone for errors
$ sudo pdnsutil check-zone glb.domain.com Checked 7 records of 'glb.domain.com', 0 errors, 0 warnings.
GLB Domain Resolution Flow
References
https://docs.varnish-software.com/tutorials/dns-based-gslb/
https://doc.powerdns.com/authoritative/lua-records/functions.html
https://blog.powerdns.com/2017/12/15/powerdns-authoritative-lua-records/