Forum Discussion
Code review: Http conditionally reselect for active connections based on persistence when member down or disabled
I am looking for a concept/code review or alternative suggestions if I'm going about this all wrong.
What we are trying to do is be able to maintain servers / release web application updates, killing as few users as possible.
We use source address persistence on the http vs. Pool is selected based on request uri using an existing rule. Each pool has a monitor that hits a keep alive page with just a receive string defined - generally 'OK3' or some sort. We do not use oneconnect. Big ip is 10.2.4 HF7.
Trouble is even after we take out a keep alive page and the member is marked down, requests on already-active connections are still going to the down server. Some of these requests are from external browsers that are using http keep alive to maintain connection. As long as they are not idle, their connections persist. Other requests are from other web applications which also pool their connections and keep them open. New connections are re-balanced, as expected, but this means any requests that rely on persistence for web server state - e.g. images stored locally - break.
What I would like to happen is that when we are going to take a member out for maintenance, to be able to drain the requests by setting the Disable string on the monitor. Then at the request-level when disabled - i.e. even for active connections - to reselect and update persistence, but only if I know the particular request does not require persistence, based on uri. Otherwise, keep the connection on the same member. We'll monitor the connections at this point while it's draining. When everyone is off, or we've waited long enough, bring it all the way down and do maintenance.
I wrote the following to do this.
when HTTP_REQUEST {
set lb_server_addr [LB::server addr]
if { $lb_server_addr != "" } {
set lb_server_pool [LB::server pool]
set lb_server_status [LB::status pool $lb_server_pool member $lb_server_addr [LB::server port]]
if { $lb_server_status == "down" || $lb_server_status == "session_disabled" } {
Reselect active connections when pool member is out
Only do this if there are other active members available..
if { [active_members $lb_server_pool] > 0 } {
Only if the client is NOT requesting stateful things
Switch on path after partner, e.g. for /tenant/foo/bar.aspx, this switches on /foo/bar.aspx
switch -glob [URI::path [HTTP::path] 2] {
"/ThisPathNeedsPersistence/*" -
"/This/Path/Needs/Persistence" {
Do nothing - allow request through
}
default {
LB::detach
Delete any persistence record so we get a new one. The record is automatically
invalidated when server is DOWN, but not when DISABLED
if { [persist lookup source_addr [IP::client_addr] node] == $lb_server_addr } {
persist delete source_addr [IP::client_addr]
}
pool $lb_server_pool
}
}
}
}
}
}It seems to work in testing, but is it ok? Are there any weird conditions you can think of that would break this that I should account for? Or alternatives? Or is it right in general but I'm using something stupidly?
I did try Action on Service Down Reselect, but it never seems to do anything when the node is marked down - or at least not consistently. If it did work though, one problem is I assume in-flight requests may be lost. Also it would give us no opportunity to drain the connections with persistence requirements, breaking those, too.
Let me know if I can clarify anything.
10 Replies
- Mario_Baron_147
Nimbostratus
Hi,
Please have a look at tis solution to check if this is the behaviour you would need. http://support.f5.com/kb/en-us/solutions/public/13000/300/sol13310.html Best regards, Mario
- Jeremy_Rosenber
Nimbostratus
Disabled does not affect persistence clients (all of ours). Forced offline does not migrate existing connections it only prevents new connections. Our problem is with long running active connections. Also our production big ip is managed I don't have access to the options you linked in prod unless I schedule it w provider.
Hi!
What you might want is to set the action on service down to reject instead. First you drain the connections with the disable string and then it will force the client to restart the session and thus get a new persistence record when the object is marked as down.
/Patrik
Hi!
Making this a reply as the syntax for comments is horrible.
I tried the rule and it seems to do what you want it to do at first glance.
I am curious though about what would happen to the persistent connections if you'd implement this rule. The detach command would close the connection with the server, but the client would be unknowing.
Unless you're using SNAT today letting the load balancer handle the server connection for the client, I imagine this would result in the client sending a request over an open TCP connection ending up at a new server on which the connection is not established. My guess is that this would result in an TCP connection reset forcing the client to open a new session. Could be worth investigating.
A humble suggestion as to the rule syntax would be to replace the switch -glob with a data group list instead:
when HTTP_REQUEST { set lb_server_addr [LB::server addr] if { $lb_server_addr != "" } { set lb_server_pool [LB::server pool] set lb_server_status [LB::status pool $lb_server_pool member $lb_server_addr [LB::server port]] if { $lb_server_status == "down" || $lb_server_status == "session_disabled" } { Reselect active connections when pool member is out Only do this if there are other active members available.. if { [active_members $lb_server_pool] > 0 } { Only if the client is NOT requesting stateful things Switch on path after partner, e.g. for /tenant/foo/bar.aspx, this switches on /foo/bar.aspx if { ![class search persistencepaths starts_with [string tolower [HTTP::uri]]] } { LB::detach Delete any persistence record so we get a new one. The record is automatically invalidated when server is DOWN, but not when DISABLED if { [persist lookup source_addr [IP::client_addr] node] == $lb_server_addr } { persist delete source_addr [IP::client_addr] } pool $lb_server_pool } } } } }/Patrik
- Jeremy_Rosenber
Nimbostratus
Here's an updated rule which seems better than the original. This doesn't require the 'disable' state. It is better at dealing with new and existing connections, consistently forcing requests where persistence is known to be needed to the down server while redirecting to an active member for other requests. It also doesn't muck with the persistence record, just disables on the connection with 'persist none'. Finally, it restores the original persistence node when the down server comes back up.
Just posting this FYI. I'll be testing it more, but seems much better so far. Not sure the performance impact.
when HTTP_REQUEST { set lb_server_pool [LB::server pool] set lb_server_addr [LB::server addr] if { $lb_server_pool != "" && $lb_server_addr != "" } { if { !([info exists drain_pool]) } { set lb_server_port [LB::server port] set lb_server_status [LB::status pool $lb_server_pool member $lb_server_addr $lb_server_port] if { $lb_server_status != "up" } { set drain_pool $lb_server_pool set drain_addr $lb_server_addr set drain_port $lb_server_port } } if { [info exists drain_pool] } { LB::detach persist none set drain_status [LB::status pool $drain_pool member $drain_addr $drain_port] if { $drain_status == "up" } { if { $drain_pool == $lb_server_pool } { pool $drain_pool member $drain_addr $drain_port persist source_addr } unset drain_pool unset drain_addr unset drain_port } elseif { $drain_pool == $lb_server_pool } { switch -glob "[URI::path [HTTP::path] 2][URI::basename [HTTP::uri]]" { "/ThisPathNeedsPersistence/*" - "/This/Path/Needs/Persistence" { pool $drain_pool member $drain_addr $drain_port } default { pool $drain_pool } } } } } }- Kevin_Davies_40
Nacreous
Can you add comments as it is difficult to follow in which scenarios parts of the iRule are used. - Jeremy_Rosenber
Nimbostratus
If anyone knows how to delete this reply... I added comments in another one.
- Jeremy_Rosenber
Nimbostratus
Here's an updated rule which seems better than the original. This doesn't require the 'disable' state. It is better at dealing with new and existing connections, consistently forcing requests where persistence is known to be needed to the down server while redirecting to an active member for other requests. It also doesn't muck with the persistence record, just disables on the connection with 'persist none'. Finally, it restores the original persistence node when the down server comes back up.
Just posting this FYI. I'll be testing it more, but seems much better so far. Not sure the performance impact.
when HTTP_REQUEST { set lb_server_pool [LB::server pool] set lb_server_addr [LB::server addr] if { $lb_server_pool != "" && $lb_server_addr != "" } { If not yet draining, check if we should start if { !([info exists drain_pool]) } { set lb_server_port [LB::server port] set lb_server_status [LB::status pool $lb_server_pool member $lb_server_addr $lb_server_port] Start draining member if it is not up if { $lb_server_status != "up" } { set drain_pool $lb_server_pool set drain_addr $lb_server_addr set drain_port $lb_server_port } } If draining, we'll make a LB decision each request if { [info exists drain_pool] } { LB::detach Ignore persistence as we're basically managing our own. We'll restore it when we're back up. If we didn't ignore it here, then our commands to select an active member will be ignored in favor of the persisted member. persist none set drain_status [LB::status pool $drain_pool member $drain_addr $drain_port] When draining member is back up, stop tracking it if { $drain_status == "up" } { If we're going to that pool, go back to the node we were draining and reset persistence to it if { $drain_pool == $lb_server_pool } { pool $drain_pool member $drain_addr $drain_port persist source_addr } unset drain_pool unset drain_addr unset drain_port } elseif { $drain_pool == $lb_server_pool } { At this point we're draining a member, it's still down, and we are indeed going to that pool. Go to the draining member only for requests we know persistence is necessity. Otherwise, choose an active member instead. switch -glob "[URI::path [HTTP::path] 2][URI::basename [HTTP::uri]]" { "/ThisPathNeedsPersistence/*" - "/This/Path/Needs/Persistence" { pool $drain_pool member $drain_addr $drain_port } default { pool $drain_pool } } } } } }- Jeremy_Rosenber
Nimbostratus
Added commented version at request of Kevin Davies. Sorry about that. Had to add as separate reply it wouldn't let me edit. (Save button no worky) - I'd change to case insensitive comparison by using string tolower, and change the "-glob" part. ;) Otherwise it looks ok to me. /Patrik
Help guide the future of your DevCentral Community!
What tools do you use to collaborate? (1min - anonymous)Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com