Forum Discussion

youe_100642's avatar
youe_100642
Icon for Nimbostratus rankNimbostratus
Nov 30, 2011

Host header based web farm iRule & Class

We run multiple name based clusters of web servers (some with thousands of name based virtual hosts). For various reasons, we need to be able to divide the web server configuration of these clusters into multiple clusters without changing the IP used to access the clusters. Also, we needed to be able to easily manage these from a Class (Data Group) instead of modifying the iRule. Also, we run 2 pools for HTTP and HTTPS traffic. The BIG-IP handles the SSL decryption and passes on plain HTTP, but we still need to differentiate in the web servers whether the request was HTTP or HTTPS.

 

 

I looked around and found a few people mentioning this functionality for using Classes in this way in an iRule, but didn't actually see it in use. So I created and tested a new iRule to provide this functionality.

 

 

The iRule:

 

rule RULE_POOLMAPPING_V01 {
   when RULE_INIT {
      set static::CLASS "CLASS_POOLMAPPING_V01"
      set static::DEBUG 0
      set static::HTTPS_PORT 443
   }
   when HTTP_REQUEST {
      set zone_pool [class match -element -- [string tolower [HTTP::host]] equals $static::CLASS]
      if { [llength $zone_pool] == 2 } {
         set pool [lindex $zone_pool 1]
         if { [TCP::local_port clientside] equals $static::HTTPS_PORT } {
            append pool "S"
         }
         if { $static::DEBUG } {
            log local0. "WEBCLUSTER DEBUG: [HTTP::host]->$pool"
         }
         if { [catch { pool $pool }] } {
            log local0. "WEBCLUSTER ERROR: Non-existant pool: $pool"
         }
      } elseif { $static::DEBUG } {
         log local0. "WEBCLUSTER DEBUG: No match for host: [HTTP::host]"
      }
   }
}

 

The Class with key:value pair pool mapping examples:

 

class CLASS_POOLMAPPING_V01 {
   {
      "foo.com" { "POOL_V01_C01_HTTP" }
      "foo" { "POOL_V01_C01_HTTP" }
      "bar.com" { "POOL_V01_C02_HTTP" }
   }
}

 

How it works:
  1. `class match` searches for the lowercase form of the HTTP Host header in the specified Class. If a match is found, the entire element (key:value pair) is returned as a list with 2 items and assigned to local variable "zone_pool". If no match is found, zone_pool is an empty list.
  2. If the client side port is on HTTPS, then the letter "S" is appended to the local variable "pool" to use the HTTPS Pool instead. (Our pool names are identical except for this)

     

  3. The Pool is then selected inside a `catch` statement to catch if the Pool doesn't exist (misconfiguration or a bad request). An error will be logged if the pool doesn't exist.
  4. If no Pool can be successfully selected from the iRule, then the default Pool that is assigned to the Virtual Server will automatically be used as a fall-back.

 

The default Pool acts as a catch-all for bad pool mappings or attempts to access generic host names or the IP directly (no host header).

 

 

The performance of this iRule is pretty reasonable and the size of the Class doesn't seem to affect the cost of using `class match` (hash based key searching). I tested variations between 800-3200 entries in the Class and hundreds of simultaneous requests. All the tests ended up with similar results in cycle usage:

 

 

Cycles (min, avg, max) = (0, 85007, 206665)

 

 

I want to provide this to the community in case others have a similar need and can use this. Also I would like to know if anyone can see any flaws in this design or a better way to do it. Thanks!

 

 

I tried to attach my proof of concept Java code for managing Class key:value pair objects from command line. However, I was unable to attach it to the thread. If anyone is interested in it, I can paste it in a reply (~240 lines).

 

  • Hi Youe,

    Great work. That's a nice, simple iRule for the scenario.

    The only improvement I see is to change class match -element to class match -value to retrieve just the pool name from the datagroup. You can then check for $zone_pool ne "" before trying to assign the pool.

    Another slight change you could make is to check that the host isn't null or an IP before doing the class match:

    
     Check if the host header is not null and not an IP address
    if {[string match {*[a-zA-Z]*} [HTTP::host]]}{
    

    Aaron
  • I actually did try `class match -value` first but kept running into problems when specifying the required parameters to it. It was a while ago when I first did this though, so maybe I had some mistake and I don't remember off hand. But at the time it just didn't work right and switching to -element made it flawless. I might go back and try that again soon.

     

     

    Good point about the host header being null. I'll play around with requests like that.

     

     

    Thanks for the response!
  • I updated the rule to not error if the class did not exist:

    rule RULE_POOLMAPPING_V01 {
        when RULE_INIT {
            set static::CLASS "CLASS_POOLMAPPING_V01"
            set static::DEBUG 0
            set static::HTTPS_PORT 443
        }
        when HTTP_REQUEST {
            if { [class exists $static::CLASS] } {
                set zone_pool [class match -element -- [string tolower [HTTP::host]] equals $static::CLASS]
            }
            if { [info exists zone_pool] && [llength $zone_pool] == 2 } {
                set pool [lindex $zone_pool 1]
                if { [TCP::local_port clientside] equals $static::HTTPS_PORT } {
                    append pool "S"
                }
                if { $static::DEBUG } {
                    log local0. "WEBCLUSTER DEBUG: [HTTP::host]->$pool"
                }
                if { [catch { pool $pool }] } {
                    log local0. "WEBCLUSTER ERROR: Non-existant pool: $pool"
                }
            } elseif { $static::DEBUG } {
                log local0. "WEBCLUSTER DEBUG: No match for host: [HTTP::host]"
            }
        }
    }