on
15-Nov-2016
08:00
- edited on
05-Jun-2023
22:45
by
JimmyPackets
You wish to provide a static maintenance splash page when all members of a pool serving a Virtual Server are currently down or disabled.
# cat /var/tmp/splash-page.html <!DOCTYPE html> <html lang="en"> <head><title>Site Maintenance</title></head> <body> <!-- YOUR GREAT CONTENT HERE --> </body> </html> # tmsh create sys file ifile maintenance-splash-page.html source-path file:/var/tmp/splash-page.html # tmsh create ltm ifile maintenance-splash-page.html file-name maintenance-splash-page.html
when LB_FAILED { if { [active_members [LB::server pool]] == 0 } { HTTP::respond 503 content [ifile get maintenance-splash-page.html] } }
LB_FAILED fires when the Virtual Server load-balancing decision fails. There are a number of reasons why this may occur, but we're only interested in the case where there are no active pool members available, either because all available members have been disabled, or because they are all marked down by the monitors. The
HTTP::respond
sends an HTTP Response message with a 503 status code, then the contents of the so-called ifile. This is a file that has been uploaded into the BIG-IP configuration filestore. The first tmsh
command loads a file from the BIG-IP local filesystem into the configuration filestore. We could have imported a file via http using the appropriate source-path
url. The second tmsh
command makes the file visible to the LTM module.
In order to use the
HTTP::respond
, an http profile must be applied to the Virtual Server.
The content could also be directly embedded in the
HTTP::respond
command by doing this:
when LB_FAILED { if { [active_members [LB::server pool]] == 0 } { HTTP::respond 503 content { <!DOCTYPE html> <html> <head><title>Maintenance Page</title></head> <body> <!-- YOUR GREAT CONTENT HERE --> </body> </html> } } }
The iFile mechanism, however, is more flexible, because it permits changes to the file without needing to modify and reload the iRule. Perhaps more importantly, it supports a more flexible mechanism when you need to supply more than one file.
Why send a 503? Web search crawlers should not cache this response. You may also wish to add headers to this effect, as in:
HTTP::respond 503 content [ifile ...] Cache-Control "no-store, must-revalidate"
The recipes above work fine if all of the required assets for the response are in a single HTML page. But what if you need to send other things, like a separate css or one or more images? You need to solve two problems: 1. retrieve the file based on the requested object; and 2. set the Content-Type based on the actual contents. A Data Group can be used for this purpose:
# tmsh create ltm data-group internal splash-page-assets type string records add { "/css/maintenance.css" { data "splash-page-maintenace-css,text/css" } "/img/logo.png" { data "splash-page-logo-png,image/png" } }
Then:
when LB_FAILED { if { [active_members [LB::server pool]] == 0 } { set attr [class lookup [HTTP::path] splash-page-assets] if { $attr eq "" } { HTTP::respond 503 content [ifile get maintenance-splash-page.html] } else { HTTP::respond 503 content [ifile get [getfield $attr , 1]] Content-Type [getfield $attr , 2] } } }
It's worth noting that there is a slight race-condition here. Let us say that the pool member comes up while the client is pulling in the additional maintenance page assets. In that case, LB_FAILED isn't raised and the request will go through. For this reason, it is sensible to ensure these assets exist on the pool members, as well.
As you can see in the "Comments" section below, starting with 13.0, this method will not work when all members of a pool are disabled (as you might do during maintenance work). 13.0 changed the behavior of the BIG-IP platform such that it sends a RST to the initial SYN when the pool is disabled. This behavior is generally more sensible, but will require a different approach if you want to address this use-case.
Would probably be better still to just exclude the if statement and return the maintenance page on LB_FAILED. Although there could be a use case for acting differently based on why LB_FAILED was triggered.
As Lloyd Christmas would say, "I like it a lot."
There are limitations with LB_FAILED, for the ifile must be short enough to fit in one data packet (sol9456: Using the HTTP::respond iRule command in the LB_FAILED event may result in truncated responses).
Also it might be appropriate to use the "503" HTTP status code in the response; search engines will not index the maintenance message with that.
I am trying to implement this recipe but I'm having syntax issues with the code you have under the "Elaboration" section (send other things like images). Specifically, I am getting syntax error related to:
set attr [class lookup [HTTP::path]] <- error: [wrong args][class lookup [HTTP::path]]
and
HTTP::respond 200 content [ifile get [getfield $attr 1 ,]] Content-Type [getfield $attr 2,] <- error: [wrong args][getfield $attr 1,], error: [wrong args][getfield $attr 2,]
For the 'getfield' errors, I believe it should be this instead:
HTTP::respond 200 content [ifile get [getfield $attr , 1]] Content-Type [getfield $attr , 2]
Can you confirm the syntax you've provided for both items I've listed above?
Thanks.
For the first, I failed to include the data-group name. This should be:
set attr [class lookup [HTTP::path] splash-page-assets]
You are also correct on the
getfield
argument order. It turns out the error was cascading. I forgot to include the data-group name, but the parser complained instead about the getfield
order. That sometimes happens on parsing. I will attempt to fix this article with the corrected code. Thank you for pointing this out.
Thanks for the suggestions! Keep them coming. I've incorporated @Jie's response code recommendation and corrected the code based on @rlou's findings.
I have tried to implement iFile on Performance Layer4. It is not helping me.
What we have done here is to have the CSS and images embedded in a single HTML ifile, with the images encoded in the base64 format. As mentioned above, do not get carried away with the content as this is just a functional maintenance notice and need not to be fancy; limit the size of the content to as small as possible.
I've used very similar to this. Did the syntax change though? LB_FAILED always was the case for all down?
LB_FAILED does not mean "all pool members are down". Rather, it means "a load-balancing decision cannot be made, or an attempt to proxy the traffic failed". Usually, the first happens when all members are down, but the second can occur for a variety of reasons. For example, if the pool members becomes overloaded and TCP connections timeout, then LB_FAILED will be raised.
If the validation were placed in HTTP_REQUEST, it would execute on each received HTTP Request message, even though the positive condition is rare. When all pool members are down, LB_FAILED will be raised, and is the most common cause for this event.
As I understand it, LB_FAILED will not be triggered for existing/persistent user connections, in which case the user will not get the maintenance page in time. I suppose there is always some trade-off and no perfect solution.
I know im a little late to the game here, but how do the ifiles work with static content like images? If I want to upload an html file that has images in it, does the F5 support that?
Thanks.
The ifile is sourced from a regular file, so yes, you can have any arbitrary content in the file. If you use an embedded image (i.e., a base64 encoded version of the data directly in the HTML file), there is no issue at all. If you have referenced files (e.g.,
in your splash HTML page), then you must handle incoming requests for that file in HTTP_REQUEST
, as in:
when HTTP_REQUEST {
switch [HTTP::path] {
"/some/img.png" {
HTTP::respond 200 content [ifile get my-lovely-image.png]
}
}
}
Vernon,
Thanks for the reply. So any images that reside in the html file need to be base64 encoded in order for the F5 to be able to render them without having to handle them via HTTP_REQUEST? You wouldnt happen to have an example of a maintenance page html file I could use for a reference would you?
Thanks.
Thanks alot Vernon! That is definitely enough to get me going!
Update: I have uploaded an html file "maint-page.html" via the GUI File Management-->iFile List-->import and have referenced the file in the following iRule:
when HTTP_REQUEST {
HTTP::respond 503 content [ifile get maint-page.html]
}
However; when I go to apply the iRule to the VIP is states
Rule [/Common/irule_maint-page] error: Unable to find ifile (maint-page.html) referenced at line 2: [ifile get maint-page.html]
I have confirmed that the iFile is in the iFile list though. Any ideas? Thanks.
I have a very simple setup on version 13.0.0, with one virtual HTTP server with a default pool that has a few members. I want to test the irule above by disabling all pool members which should be the right way according to one sentence in the "Analysis" section (we're only interested in the case where there are no active pool members available, either because all available members have been disabled, or because they are all marked down by the monitors.).
However, if I do that a connection attempt to the virtual server will fail as the LTM resets the TCP connection after the initial SYN packet from the client. As I discovered, this behaviour is also described in K86025623, and it is the norm now.
So there is no load balancing decision, and the irule doesn't fire. Does it mean the irule above only applies to versions prior to 13.0.0? If yes, is there a different way to achieve the same result with versions 13 and 14? Or would I have to resort to priority groups, fallback hosts and the like?
Thanks in advance!
Unfortunately, you will need to work around this change. You may do so in a few different ways. One way is to use priority group activation, just as you say. The fallback pool would have a monitor that always marks it up, but would otherwise drop traffic (either an invalid address in your space, or something like an address in the 100::/64 space, which is the IPv6 discard block. Alternatively, when you wish to disable a pool, you could swap to a dummy pool with this same configuration set up, which will have more-or-less the same effect.
If you wish for F5 to add a feature that allows changing the behavior on a disabled pool, I recommend contacting your F5 Field Sales team, and ask them to file the request.
Thank you for the confirmation. I will have to ponder the options. May i suggest you change the original instructions to include the versions it applies to so that others wishing to implement it in version 13 do not have to spend time on troubleshooting?
Hi GScholz,
add this line somewhere to your iRule...
if 0 { pool AllMYHighAvailableDefaultGWs }
The
if 0 {pool selection}
will never become executed. Its basically a multi-line TCL comment which gets ignored by the TCL compiler. But the syntax is sufficient enough to mark your VS as green as long as the default pool OR
the dummy pool is active...
https://wiki.tcl-lang.org/page/comment
Cheers, Kai