Dynamic FQDN Node DNS Resolution based on URI with Route Domains and Caching iRule
Problem this snippet solves:
Following on from the code share Ephemeral Node FQDN Resolution with Route Domains - DNS Caching iRule that I posted, I have made a few modifications based on further development and comments/questions in the original share.
On an incoming HTTP request, this iRule will dynamically query a DNS server (out of the appropriate route domain) defined in a pool to determine the IP address of an FQDN ephemeral node depending on the requesting URI. The IP address will be cached in a subtable so to prevent querying on every HTTP request
It will also append the route domain to the node and replace the HTTP Host header.
Features of this iRule * Dynamically resolves FQDN node from requesting URI * Iterates through other DNS servers in a pool if response is invalid or no response * Caches response in the session table using custom TTL * Uses cached result if present (prevents DNS lookup on each HTTP_REQUEST event) * Appends route domain to the resolved node * Replaces host header for outgoing request
How to use this snippet:
Add a DNS pool with appropriate health monitors, in this example the pool is called
dns_pool
Add the lookup datagroup,
dns_lookup_dg. This defines the parameters of the DNS query. The values in the datagroup will built up an array using the key in capitals to define the array object e.g. $myArray(FQDN)
Modify the values as required:
FQDN: the FQDN of the node to load balance to. DNS-RD: the outbound route domain to reach the DNS servers NODE-RD: the outbound route domain to reach the node TTL: TTL value for the DNS cache in seconds
ltm data-group internal dns_lookup_dg { records { /app1 { data "FQDN app1.my-domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 8443" } /app2 { data "FQDN app2.my-other-domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 8080" } default { data "FQDN default.domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 443" } } type string }
Code :
ltm data-group internal dns_lookup_dg {
records {
/app1 {
data "FQDN app1.my-domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 8443"
}
/app2 {
data "FQDN app2.my-other-domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 8080"
}
default {
data "FQDN default.domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 443"
}
}
type string
}
when CLIENT_ACCEPTED {
set dnsPool "dns_pool"
set dnsCache "dns_cache"
set dnsDG "dns_lookup_dg"
}
when HTTP_REQUEST {
# set datagroup values in an array
if {[class match -value [HTTP::uri] "starts_with" $dnsDG]} {
set dnsValues [split [string trim [class match -value [HTTP::uri] "starts_with" $dnsDG]] "| "]
} else {
# set to default if URI is not defined in DG
set dnsValues [split [string trim [class match -value "default" "equals" $dnsDG]] "| "]
}
if {([info exists dnsValues]) && ($dnsValues ne "")} {
if {[catch {
array set dnsArray $dnsValues
set fqdn $dnsArray(FQDN)
set dnsRd $dnsArray(DNS-RD)
set nodeRd $dnsArray(NODE-RD)
set ttl $dnsArray(TTL)
set port $dnsArray(PORT)
} catchErr ]} {
log local0. "failed to set DNS variables - error: $catchErr"
event disable
return
}
}
# check if fqdn has been previously resolved and cached in the session table
if {[table lookup -notouch -subtable $dnsCache $fqdn] eq ""} {
log local0. "IP is not cached in the subtable - attempting to resolve IP using DNS"
# initialise loop vars
set flgResolvError 0
if {[active_members $dnsPool] > 0} {
foreach dnsServer [active_members -list $dnsPool] {
set dnsResolvIp [lindex [RESOLV::lookup @[lindex $dnsServer 0]$dnsRd -a $fqdn] 0]
# verify result of dns lookup is a valid IP address
if {[scan $dnsResolvIp {%d.%d.%d.%d} a b c d] == 4} {
table set -subtable $dnsCache $fqdn $dnsResolvIp $ttl
# clear any error flag and break out of loop
set flgResolvError 0
set nodeIpRd $dnsResolvIp$nodeRd
break
} else {
call ${partition}log local0."$dnsResolvIp is not a valid IP address, attempting to query other DNS server"
set flgResolvError 1
}
}
} else {
call ${partition}log local0."ERROR no DNS servers are availible"
event disable
return
}
#log error if query answers are invalid
if {$flgResolvError} {
call ${partition}log local0."ERROR unable to resolve $fqdn"
unset -nocomplain dnsServer dnsResolvIp dnsResolvIp
event disable
return
}
} else {
# retrieve cached IP from subtable (verification of resolved IP compelted before being commited to table)
set dnsResolvIp [table lookup -notouch -subtable $dnsCache $fqdn]
set nodeIpRd $dnsResolvIp$nodeRd
log local0. "IP found in subtable: $dnsResolvIp with TTL: [table timeout -subtable $dnsCache -remaining $fqdn]"
}
# rewrite outbound host header and set node IP with RD and port
HTTP::header replace Host $fqdn
node $nodeIpRd $port
log local0. "setting node to $nodeIpRd $port"
}Tested this on version:
12.16 Comments
- Stanislas_Piro2
Cumulonimbus
Hi Lee,
I think the line 16 must be removed.
if you want to keep it, you must add a closing curly bracket before (line 15) and add a priority less than 500 on the HTTP_REQUEST event on line 7.
when HTTP_REQUEST priority 400 { - Lee_Sutcliffe
Nacreous
Thanks Stan.. it's a copy and paste error from another version when I put it together for DC
- thundat00th_206
Nimbostratus
I think that there is an extra space on line 63, should be:
set nodeIpRd $dnsResolvIp$nodeRdInstead of
set nodeIpRd $dnsResolvIp $nodeRd - wlopez
Cirrocumulus
Did you ever get this iRule to work?
I'm trying it on versions 12.1.2 and 14.1.2 but am getting TCL errors:
TCL error: /Web/rule_FQDN_pool_member <HTTP_REQUEST> - expected boolean value but got "FQDN app1.my-domain.com
%0|NODE-RD %2|TTL 300|" while executing "if {[class match -value [HTTP::uri] starts_with $dnsDG]} { set dnsValues [split [string trim [class match -value [HTTP::uri] starts_with $..."
- Vladimir_Shishk
Altocumulus
This iRule doesn't work in 15.1.1
node $nodeIpRd $port
This node statement doesn't direct traffic to specified node.
Don't waste you time. - Vladimir_Shishk
Altocumulus
Remark:
node $nodeIpRd $port
In 15.1.1 this statement works only for plain, non-ecnrypted HTTP traffic (not SSL).