Stacking iRules: A Modular Approach

Many administrators ask about splitting up the functionality of a single large iRule into a set of "iRule-lets" which each perform a specific function, then selectively applying them to a number of virtual servers, each of which may need only a subset of the resulting iRule-lets.

While you can't (yet) call an iRule from within another iRule, you can apply a set of different iRules to a single virtual server, and the same iRule to many virtual servers.  Last time I explained Disabling iRule Events when multiple iRules with the same event were applied to the same virtual server.  In this article, I will explain further the tools available and the condsiderations you'll need to make to build and apply a set of "modular" iRules.

Simple Stacking

The very simplest stacking scenarios include multiple iRules in which there is no overlap in event handling:  In other words, there are no duplicate events between iRules.

The order in which events are executed is the same for every iRule.  iRules are executed in the order they are applied to the virtual server (the order in which they appear in the GUI, the configuration file or in  'bigpipe virtual show' commands.) iRules will enumerate in forward order on both request and response traffic (more on that later, it's important.)

For an HTTP connection, here are some of the events an iRule can be constructed to trigger:

And here is an example of a very simple set of 3 iRules that can be applied to the same virtual server without adding anything to enforce execution order:

Because no events are duplicated between the 3 iRules, there is no question that the logic will fire in the intended order:  The absence of any collisions in the natural flow of events will take care of that for you.

Duplicate Events

Let's say you have a more complex set of functions you'd like to perform against various virtual servers.  If you'd like to control the execution order of various events among multiple iRules, you have a couple of options. 

Preventing Events from Executing

There are 2 basic approaches you can use to selectively execute the code within an event.  You can either use conditions within each event to determine if its code should be executed, or in some cases you can disable the event for subsequent iRules running on the same connection.

For more detail on the disabling events option, see my previous article, iRules: Disabling Event Processing.

If your intended logic is not that linear,  you can instead use conditional logic in each iRule to control the execution of any code block.  To prevent the execution of the entire event if a specific condition is seen, you can wrap the entire event codeblock in the conditional test. This approach is frequently combined with the use of control or flag variables that are set in one iRule if a condition is seen that would indicate some follow on logic should or should not execute subsequently. Since  local variables are scoped to the connection, they are accessible from all iRules running against a connection, not just the one that created them.

Here's an example of the logical flag approach in a monolithic iRule:

when HTTP_REQUEST {
# set logic control flag variable (0 means not yet re-written)
set uri_rewritten 0
# This logic is generic and needed on all virtuals log local0.alert "Insert Client IP" HTTP::header insert "X-Forwarded-For" [IP::client_addr] # Extract the file extension set extension [string range [HTTP::path] [string last "." [HTTP::path]] [string length [HTTP::path]]] # If the extension matches against the class then re-write with the appropriate directory if { [matchclass $extension equals $::static_content] } { set new_path [format "%s%s" "/common" [HTTP::path]] HTTP::path $new_path pool common_pool # set logic control flag variable (1 means it has been re-written)
set uri_rewritten 1 } # This logic is only required on some virtuals # If the request hasn’t already been re-written by a previous operation, then rewrite now # condition these actions on logic control flag variable
if { $uri_rewritten equals 0 } {
set new_path [format "%s%s" "/proxy" [HTTP::path]] HTTP::path $new_path pool proxy_pool } }

 and the same logic in a set of 2 iRules:

when HTTP_REQUEST {
# set logic control flag variable (0 means not yet re-written)
set uri_rewritten 0
# This logic is generic and needed on all virtuals log local0.alert "Insert Client IP" HTTP::header insert "X-Forwarded-For" [IP::client_addr] # Extract the file extension set extension [string range [HTTP::path] [string last "." [HTTP::path]] [string length [HTTP::path]]] # If the extension matches against the class then re-write with the appropriate directory if { [matchclass $extension equals $::static_content] } { set new_path [format "%s%s" "/common" [HTTP::path]] HTTP::path $new_path pool common_pool # set logic control flag variable (1 means it has been re-written)
set uri_rewritten 1 } }
when HTTP_REQUEST {
# This logic is only required on some virtuals
# If the request hasn’t already been re-written by a previous operation, then rewrite now
# condition next actions on logic control flag variable
if { $uri_rewritten equals 0 } {
set new_path [format "%s%s" "/proxy" [HTTP::path]] HTTP::path $new_path pool proxy_pool } }

Setting Execution Order Explicity

The iRules priority command allows you to specify the order of execution for duplicate iRule events. The name of the setting is somewhat misleading:  A lower "priority" value actually means the event will execute sooner than the same event with a higher "priority".  By default, all iRule events have a priority of 500.  If you are adding iRules in the GUI, LTM will accept duplicate event priorities and run them in the order the iRules were applied.  On the other hand, if you are using the iRule Editor, it will NOT allow you to assign iRules with duplicate event priorities to the same virtual server.  The best way to ensure that duplicate events are executed in the intended order is to explicitly assign priority to each event that is duplicated. Here is an example of the priority assignment approach further splitting the iRule we looked at above:

when HTTP_REQUEST priority 10 {
# This rule is generic and needed on all virtuals
log local0.alert "Insert Client IP"
HTTP::header insert "X-Forwarded-For" [IP::client_addr]
}
when HTTP_REQUEST priority 500 {
# Extract the file extension
set extension [string range [HTTP::path] [string last "." [HTTP::path]] [string length [HTTP::path]]]
# If the extension matches against the class then re-write with the appropriate directory
if { [matchclass $extension equals $::static_content]   } {
set new_path [format "%s%s" "/common" [HTTP::path]]
HTTP::path $new_path
pool common_pool
set uri_rewritten 1
}
}
when HTTP_REQUEST priority 1000 {
# If the request hasn’t already been re-written by a previous rule then rewrite it with this default rule
if { $::uri_rewritten equals 0 } {
set new_path [format "%s%s" "/proxy" [HTTP::path]]
HTTP::path $new_path
pool test_http_pool
}
}

(Note:  Setting event priority will only control the order in which duplicate events execute.  Other events with higher "priority" values may execute first if that event is triggered first in the connection stream.)

Managing Bi-Directional Traffic

If you are using multiple iRules with overlapping events for bidirectional traffic, you will need to ensure the response traffic traverses the overlapping events in the expected order.  By default, the iRules applied to a virtual server will run in the order in which they are applied to the virtual server, regardless of the direction the traffic is flowing.

For example, if you have multiple iRules applied to the same virtual server and the iRules contain both request and response logic, the default execution order is NOT like this:

Rather the logic executes by default in the same order as request traffic did, like this:

The best way to enforce the exectuaion order for response traffic is to use the event priority command as we did above, paying special attention to both request and response flows.

Best Practices for Modular iRules

  • Don't unless you have to.  A single iRule is usually simpler to deploy, manage and troubleshoot.
  • Use event priority if you must create modular iRules to explicitly manage the order of processing for duplicate events.  Although LTM will apply the iRule logic in a deterministic order regardless of the approach you use to enforce it, we recommend the use of explicit priority declarations to make iRule maintencance and support more straighforward, and, well... more logical.
  • Try to split on directional/contextual boundaries in preference to or along with functional ones.  For instance, handling all serverside processing or all response traffic in one rule rather than splitting up the logic may make things simpler to maintain & support.

Although we definitley recommend a monolithic iRule in most cases, you can build some relatively sophisticated modular iRules using variable flags and event priority even without the use of proc() for inline functions.

Published Jun 19, 2008
Version 1.0

Was this article helpful?

4 Comments

  • I really like the idea of a rule of low priority executed on all virts. I understand monolithic irules might be easier, but it there any performance difference with the modular approach. I would rather not duplicate that code in each iRule. Thoughts?
  • In the example "if { $::uri_rewritten equals 0 } {" uses a global variable which would be in effect across all TCP connections. I'm assuming this should be a local variable so it is specific to the single TCP connection that the iRule is executing on.

     

     

    Aaron
  • Deb_Allen_18's avatar
    Deb_Allen_18
    Historic F5 Account
    yes sir, that is correct. Good catch.

     

     

    The variable in all cases should be local:

     

    "uri_rewritten" or "$uri_rewritten"

     

     

    I no longer have edit capability as I'm no longer a member of the formal DC team, but will forward your comment to them for correction.