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.17 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).
- DanSkow
Cirrus
Old topic, but... If the goal is just to have the F5 route to an FQDN node based on a route specified in another Route Domain, then one option is to attach this simple iRule to any VIPs using FQDN nodes defining the Route Domain Gateway the pool members should use:
when CLIENT_ACCEPTED { nexthop 10.10.10.1%1 }Many other options here are using some variation of "set node" in HTTP_REQUEST to modify the node selection to have the Route Domain appended to it. The main issue I've observed with these methods is that using "set node" prevents any load balancing and persistence used by the VIP/pool from happening, since the load balancing decision and persistence takes place in LB_SELECTED, which is after HTTP_REQUEST. They also don't take healthchecks into account, and would send traffic to a downed node.
This iRule still uses whatever load balancing method is assigned to the pool, and whatever persistence is assigned to the VIP, and it doesn't interfere with any other HTTP_REQUEST iRules attached to the VIP since it uses CLIENT_ACCEPTED.
The only remaining downside of this iRule is that the healthcheck traffic will still use the default Route Domain routing table. Unfortunately this issue can't be bypassed by using an inband monitor, because inband monitors aren't supported on FQDN nodes.