Forum Discussion

cdsmimth's avatar
cdsmimth
Icon for Altostratus rankAltostratus
Nov 13, 2019

iRule for handling traffic to multiple pools through the same VS

I tried searching for questions asked by people trying to do substantially the same thing I am and came up close ... but empty. I'm hoping someone here can help me out a bit. Here's the set-up:

 

We have multiple tools running on the same VMs, with each listening on different ports. They're accessible via URLs that share a common domain and may or may not include a subdomain. The URLs include a path, which is the name of the tool. Here are examples:

 

test.domain.com/tool-one

domain.com/tool-two

 

The tools each have their own pool in the F5 and, of course, the members are set to listen on different ports. For example, tool-one might be listening on port 10450, while tool-two might be listening on port 10502.

 

I've written an iRule that successfully parses the HTTP host header and the HTTP path header information to determine the pool to which to send the traffic. Examples:

 

test-tool-one

production-tool-two

 

This is the iRule that I wrote:

 

when RULE_INIT {
  set ::domain ".domain.com"
}

when HTTP_REQUEST {
  # The next 5 lines of code are used to determine the environment to which the user wishes to connect.
  # If the domain matches the global domain variable,
  #   then we know the user is requesting a non-production environment
  #   and we extract the requested environment from the domain.
  if { [string tolower [HTTP::host]] contains $::domain } {
    set subdomain [getfield [HTTP::host] $::domain 1]
  } else {
    set subdomain "production"
  }

  # Remove the leading forward slash from the path.
  set path [string trimleft [HTTP::path] ?/?]


  # This enables us to connect to version.json for the tool.
  # We explode the path on the forward slash, then grab the tool name from the list's first element
  #   and we don't care what was stored in path from the previous command.
  set path_list [split $path "/"]
  set path [lindex $path_list 0]

  # This check enables us to serve either the correct tool
  #   OR a generic message informing the user we don't know which tool they want
  #   OR to simply pass the request through to the server, as appropriate.
  if { $path contains "-" } {
    # Send the user to the correct pool for the selected environment and tool
    pool "$subdomain-$path"
  } elseif { [HTTP::path] == "" || [HTTP::path] == "/" } {
    # Send the user to the tool serving the generic "You're lost" message
    # Yet to be created
  }
  # else omitted intentionally because we want to simply pass the traffic through using the existing connection
}

 

I have used logging to see the information I'm getting along the way. The tools can make several requests to the back-end VM for things like submitting a form, fetching images, fetching additional information, and so on. This all works great .... as long as you only access one tool at a time. For example, if I go to test.domain.com/tool-one in one tab, then open test.domain.com/tool-two in another tab, and then go back to tool-one to submit a form, the connection has switched to tool-two and the form submission fails. I had originally called "disable event" at the end of the rule (right after where the elseif is now), which of course prevented you from even accessing another tool while the connection to the first tool persisted. I did suspect this might be the case, but at the time I'd written the iRule we had only one tool stood up on the VMs that are behind the F5. We got a second tool stood up on the VMs behind the F5 yesterday afternoon and I got to do some more testing when i came in this morning.

 

So the question is how do I use the same domain to access multiple tools without borking up the connections for each tool when a user wants to use multiple tools? Hopefully I've phrased that question correctly and, if not, then hopefully the information provided will give enough context to sort things out.

6 Replies

  • Try applying a oneconnect profile to the virtual server.

     

    What sort of persistence profile are you using?

    • cdsmimth's avatar
      cdsmimth
      Icon for Altostratus rankAltostratus

      I was using sticky persistence, but I have since switched to no persistence. I'll look up what a oneconnect profile is and try that.

    • cdsmimth's avatar
      cdsmimth
      Icon for Altostratus rankAltostratus

      The oneconnect profile did not make a difference. I tried it with and without sticky persistence.

      • Simon_Blakely's avatar
        Simon_Blakely
        Icon for Employee rankEmployee

        Define what you mean by sticky persistence?

        That isn't an F5 persistence type ...

         

        Use Cookie Persistence. Use something based on the default cookie persistence, and do not specify the cookie name (the cookie name identifies the pool - if you use a custom name, you lose the pool name information and all your pools look alike). You will have a separate cookie for each pool.

         

        The oneconnect profile allows server-side connection reuse, which can be more efficient. However, it also detaches the server-side connection from the client-side connection after every HTTP request/respond event.

        This means that a new load-balancing decision is made per request.

         

        You may have a logical error in your irule as well - I'd ensure that any path through the HTTP_REQUEST rule assigns the correct pool for the request with a pool command.

  • We got this sorted out yesterday afternoon. Turns out there was a route in one of our two tools that was not correctly set up. Once we made that change, everything is working as we expected. Thank you to everyone who took the time to read this and extra thanks to   for taking the time to respond with a possible solution. I really appreciate it!

  • We use an iRule and a datagroup to route different host/[path] to pools in the same iRule. Here's some example code:

    set log(search) $host[string tolower [HTTP::uri]]
    set pool [class match -value $log(search) starts_with dg_shared.example.com]
    if { $pool ne ""} {
        set matched [class match -name $log(search) starts_with dg_shared.example.com]
        set log(matched) $matched
        set log(pool) $pool
        if { $pool starts_with "http" } {
            # datagroup entry is realy a redirect
            set log(reason) "redirect"
            call hsllog log
            HTTP::respond 307 Connection Close Location $pool Virtual-Name [virtual name]
            TCP::close
            event disable
            return
        } elseif { [catch { pool $pool } ] } {
            set log(reason) "Failed to Connect to Pool"
            call hsllog log
            call errorpage 404 $log(reason) "https://[HTTP::host][HTTP::uri]" log
        }
    } else {
        call errorpage 404 "No Pool Found" "https://[HTTP::host][HTTP::uri]" log
    }

    this uses starts_with so datagroup entries would look like:

    test.example.com/ := https://test.example.com/tool-two
    test.example.com/tool-one := pool_one
    test.example.com/tool-two := pool_two