Skip to content

Layer 7 Load Balancing

A layer 7 load balancer consists of a listener that accepts requests on behalf of a number of back-end pools. The requests are distributed to pools based on policies that use application data to make load balancing decisions. Please consult the Upstream documentation to learn more about the L7 Load Balancing. This documentation is inspired from Octavia L7 Cookbook.

Concepts

Before diving into the Layer 7 load balancing concept, we suggest that users should first consult the documentation for traditional OpenStack load balancers.

Logical Diagram

     +-------+     +---------------+        +------+
     |Pool 1 +-----+               +--------+Pool 2|
     +-------+     | Load Balancer |        +------+
                   | 137.138.6.18  |
                   |               |
                   +-------+-------+
                           |
             +-------------+--------------+
             |                            |
      +------v-------+           +--------v-------+
      |   Listener   |           |    Listener    |
      | Port 80/HTTP |           | Port 443/HTTPS |
      |              |           |                |
      |Default Pool1 |           |Default Pool2   |
      +------ -------+           +--+-------------+
      |              |              |
+-----+-----+  +-----+-----+    +---+-------+
| L7 Policy |  | L7 Policy |    | L7 Policy |
+-----+-----+  +-----+-----+    +----+------+
      |              |               |
   +--+---+       +--+---+        +--+---+
   |Rule 1|       |Rule 1|        |Rule 1|
   +------+       +------+        +------+
   |Rule 2|       |Rule 2|        |Rule N|
   +------+       +------+        +------+
   |Rule N|       |Rule N|        
   +------+       +------+        

Shared pools

To use the L7 policies, we have to attach pools to a load balancer instead of associating them with listeners. This feature brings a lot of flexibility because pools can now be shared among different listeners.

Default Pool

If a request doesn't match any L7 policy then it is routed to the listener’s default pool. If no default pool is set for a listener and the request doesn't match any L7 policy, then an error is returned.

Supported Listener Protocols

The diagram shows that L7 policies are applied to the listeners. L7 policies work only for listeners with protocol type HTTP and TLS_TERMINATED because L7 policies make load balancing decisions based on layer 7 (application) data.

L7 Policy

An L7 Policy contains one or more L7 rules. We can specify what action to take if all the rules for an L7 policy are satisfied. Possible actions for an L7 policy are the following:

  • REJECT: The request is denied and not forwarded to any back-end pool.
  • REDIRECT_TO_URL: The request is sent an HTTP redirect to the URL defined in the redirect-url parameter.
  • REDIRECT_TO_POOL: The request is forwarded to the back-end pool defined in the redirect-pool parameter.

When multiple L7 policies are attached to listeners, we can control the order of policy processing by specifying the policy position parameter. Since our load balancing solution is based on HaProxy, the following policy precedence rules are applied despite of the policy positioning:

  1. REJECT policies take precedence over all other policies.
  2. REDIRECT_TO_URL policies take precedence over REDIRECT_TO_POOL policies.
  3. REDIRECT_TO_POOL policies are only evaluated after all of the above, and in the order specified by the position of the policy.

L7 Rule

An L7 rule specifies a condition that returns either True or False. Multiple L7 rules can be attached to a single L7 policy. Results from all the rules within an L7 Policy are logically ANDed together.

Types of L7 Rules

  • HOST_NAME: The rule does a comparison between the HTTP/1.1 hostname in the request against the value parameter in the rule.
  • PATH: The rule compares the path portion of the HTTP URI against the value parameter in the rule.
  • FILE_TYPE: The rule compares the last portion of the URI against the value parameter in the rule. (eg. “txt”, “jpg”, etc.)
  • HEADER: The rule looks for a header defined in the key parameter and compares it against the value parameter in the rule.
  • COOKIE: The rule looks for a cookie named by the key parameter and compares it against the value parameter in the rule.

Comparison types

L7 rules perform a comparison based on the comparison type specified in the rule. Supported comparison types are shown below:

  • REGEX: regular expression matching
  • STARTS_WITH: String starts with
  • ENDS_WITH: String ends with
  • CONTAINS: String contains
  • EQUAL_TO: String is equal to

Invert

It is possible to invert the result of an L7 rule by setting the invert parameter to True. For example when the inverted flag is set, the EQUAL_TO comparison becomes "not equals to", STARTS_WITH becomes "doesn't starts with" etc.

Scenarios

This section presents several scenarios for L7 policies usage.

1. Redirect requests from HTTP listener to HTTPS listener

Logical Diagram
        +---------------+      +------------------+
        | Loadbalancer  |      |      mypool      |
        |               +------> members:         |
        | 137.138.6.28  |      | 137.138.53.95:80 |
        | demolb.cern.ch|      | 188.185.80.141:80|
        +-------+-------+      +------------------+
                |
        +-------v--------------------+
        |                            |
 +------v-------+           +--------v-----+
 |HTTP Listener |           |HTTPS Listener|
 | Port 80/HTTP |           |Port 443/HTTPS|
 |              |           |              |
 |default pool: |           |default pool: |
 |   mypool     |           |   mypool     |
 +------+-------+           +--------------+
        |
+-------v--------------+
|Redirect Https Policy |
|                      |
|    redirect url:     |
|https://demolb.cern.ch|
+-------+--------------+
        |
+-------v------------------+
|          L7 rule         |
|type: PATH                |
|compare-type: STARTS_WITH |
|value: /                  |
+--------------------------+
Setup
$ export OS_REGION_NAME=sdn1
$ openstack loadbalancer create --name mylb --vip-network-id public

# create a pool attached to loadbalancer
$ openstack loadbalancer pool create --name mypool --lb-algorithm ROUND_ROBIN --loadbalancer mylb --protocol HTTP
$ openstack loadbalancer member create --name server-1 --address 137.138.53.95 --protocol-port 80 mypool
$ openstack loadbalancer member create --name server-2 --address 188.185.80.141 --protocol-port 80 mypool

# create a listener for port 80
openstack loadbalancer listener create --name http_listener --protocol HTTP --protocol-port 80 mylb

# create a TLS_TERMINATED listener for port 443
# follow https://clouddocs.web.cern.ch/networking/load_balancing.html#tls-termination to create a secret container in Barbican
$ openstack loadbalancer listener create --name https_listener --protocol TERMINATED_HTTPS --default-tls-container-ref https://openstack.cern.ch:9311/v1/containers/5a2eef97-d1b0-487b-bae6-54a1432a79d8 --protocol-port 443 mylb

# set mypool as a default pool for http_listener and https_listener
$ openstack loadbalancer listener set --default-pool mypool http_listener
$ openstack loadbalancer listener set --default-pool mypool https_listener
L7 Policies
# create an L7 policy
$ openstack loadbalancer l7policy create --action REDIRECT_TO_URL --redirect-url https://demolb.cern.ch --name redirect_to_https http_listener

# attach an L7 rule to policy
$ openstack loadbalancer l7rule create --compare-type STARTS_WITH --type PATH --value / redirect_to_https

Validation

Requests hitting the HTTP listener trigger an HTTP redirect for the HTTPS listener.

 curl http://137.138.6.28 -vv
* About to connect() to 137.138.6.28 port 80 (#0)
*   Trying 137.138.6.28...
* Connected to 137.138.6.28 (137.138.6.28) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 137.138.6.28
> Accept: */*
>
< HTTP/1.1 302 Found
< Cache-Control: no-cache
< Content-length: 0
< Location: https://demolb.cern.ch
< Connection: close
<
* Closing connection 0

2. Redirect requests starting with /js to a static_pool

Logical Diagram
+------------------+    +---------------+    +------------------+
|   static-pool    |    | Loadbalancer  |    |     app-pool     |
|                  <----+               +---->                  |
| members:         |    | 137.138.6.69  |    | members:         |
| 137.138.53.95:80 |    |               |    | 188.185.80.141:80|
+------------------+    +-------+-------+    +------------------+
                                |
                         +------v-------+
                         |HTTP Listener |
                         | Port 80/HTTP |
                         |              |
                         |default pool: |
                         |  app_pool    |
                         +------+-------+
                                |
                      +---------v------------+
                      |  Static Pool Policy  |
                      |                      |
                      |    redirect pool:    |
                      |     static-pool      |
                      +---------+------------+
                                |
                    +-----------v--------------+
                    |          L7 rule         |
                    |type: PATH                |
                    |compare-type: STARTS_WITH |
                    |value: /js                |
                    +--------------------------+
Setup
$ export OS_REGION_NAME=sdn1
$ openstack loadbalancer create --name mylb1 --vip-network-id public

# create an app-pool attached to loadbalancer
$ openstack loadbalancer pool create --name app-pool --lb-algorithm ROUND_ROBIN --loadbalancer mylb1 --protocol HTTP
$ openstack loadbalancer member create --name app-pool-server-1 --address 188.185.80.141 --protocol-port 80 app-pool

# create a static-pool attached to loadbalancer
$ openstack loadbalancer pool create --name static-pool --lb-algorithm ROUND_ROBIN --loadbalancer mylb1 --protocol HTTP
$ openstack loadbalancer member create --name static-pool-server-1 --address 137.138.53.95 --protocol-port 80 static-pool

# create a listener for port 80
openstack loadbalancer listener create --name http_listener-1 --protocol HTTP --protocol-port 80 mylb1

# set app-pool as a default pool for http listener
$ openstack loadbalancer listener set --default-pool app-pool http_listener-1
L7 Policies
# create an L7 policy
$ openstack loadbalancer l7policy create --action REDIRECT_TO_POOL --redirect-pool static-pool --name redirect-to-static-pool http_listener-1

# attach an L7 rule to policy
$ openstack loadbalancer l7rule create --compare-type STARTS_WITH --type PATH --value /js redirect-to-static-pool

Validation

Requests with URL that starts with /js path are redirected to the static-pool.

$ curl 137.138.6.69/js
some static content from server 2

3. Load balance based on the HTTP hostname

HTTP 1.1 requests often include a Host header, which contains the hostname in the client request. This is because a server may use a single IP address or an interface to accept requests for multiple DNS hostnames. Host header helps to decide which web application should process an incoming HTTP request. In this example we will assign two dns aliases to a load balancer and create L7 policies to load balance based on hostnames.

Logical Diagram
+------------------+    +-----------------+    +------------------+
|   app-pool-2     |    | Loadbalancer    |    |   app-pool-1     |
|                  <----+ 137.138.6.79    +---->                  |
| members:         |    |mydemolb1.cern.ch|    | members:         |
| 137.138.53.95:80 |    |mydemolb2.cern.ch|    | 188.185.80.141:80|
+------------------+    +-------+---------+    +------------------+
                                |
                         +------v-------+
                         |HTTP Listener |
                         | Port 80/HTTP |
                         +------+-------+
                                |
             +------------------+-----------------+
             |                                    |
   +----------------------+             +----------------------+
   | redirect-app-pool-2  |             | redirect-app-pool-1  |
   |                      |             |                      |
   |    redirect pool:    |             |    redirect pool:    |
   |     app-pool-2       |             |     app-pool-1       |
   +---------+------------+             +---------+------------+
             |                                    |
 +-----------v--------------+         +-----------v--------------+
 |          L7 rule         |         |          L7 rule         |
 |type: HOST_NAME           |         |type: HOST_NAME           |
 |compare-type: EQUAL_TO    |         |compare-type: EQUAL_TO    |
 |value: mydemolb2.cern.ch  |         |value: mydemolb1.cern.ch  |
 +--------------------------+         +--------------------------+
Setup
$ export OS_REGION_NAME=sdn1
$ openstack loadbalancer create --name mylb2 --vip-network-id public

# set two dns aliases
$ openstack loadbalancer set --description mydemolb1,mydemolb2 mylb2

# create an app-pool-1 attached to the loadbalancer
$ openstack loadbalancer pool create --name app-pool-1 --lb-algorithm ROUND_ROBIN --loadbalancer mylb2 --protocol HTTP
$ openstack loadbalancer member create --name app-pool-1-server-1 --address 188.185.80.141 --protocol-port 80 app-pool-1

# create an app-pool-2 attached to the loadbalancer
$ openstack loadbalancer pool create --name app-pool-2 --lb-algorithm ROUND_ROBIN --loadbalancer mylb2 --protocol HTTP
$ openstack loadbalancer member create --name app-pool-2-server-1 --address 137.138.53.95 --protocol-port 80 app-pool-2

# create a listener for port 80
openstack loadbalancer listener create --name http-listener-mylb2 --protocol HTTP --protocol-port 80 mylb2
L7 Policies

Create an L7 policy to redirect requests for mydemolb1.cern.ch to app-pool-1:

$ openstack loadbalancer l7policy create --action REDIRECT_TO_POOL --redirect-pool app-pool-1 --name redirect-app-pool-1 http-listener-mylb2
$ openstack loadbalancer l7rule create --compare-type EQUAL_TO --type HOST_NAME --value mydemolb1.cern.ch redirect-app-pool-1

Create an L7 policy to redirect requests for mydemolb2.cern.ch to app-pool-2:

$ openstack loadbalancer l7policy create --action REDIRECT_TO_POOL --redirect-pool app-pool-2 --name redirect-app-pool-2 http-listener-mylb2
$ openstack loadbalancer l7rule create --compare-type EQUAL_TO --type HOST_NAME --value mydemolb2.cern.ch redirect-app-pool-2

Validation

Requests for mydemolb1.cern.ch are served by app-pool-1.

$ curl mydemolb1.cern.ch
Hi from Server 1

Requests for mydemolb2.cern.ch are served by app-pool-2.

$ curl mydemolb2.cern.ch
Hi from Server 2

4. Redirect unauthenticated requests to an authentication pool

If the request doesn't contain an auth_token cookie then the request should be forwarded to an auth-pool. As this is just an example, the server in the auth-pool will set the auth-token cookie without any challenge/response. The requests containing auth-token cookie will be forwarded to the application pool. The application pool should be able to check the validity of auth-token, if the auth-token is not valid then the cookie value should be set to INVALID to enforce redirection to the authentication pool.

Disclaimer: This example is not a guide about setting up an authentication system. The purpose of this example is to demonstrate how to make load balancing decisions based on a browser cookie.

Logical Diagram
+------------------+    +-----------------+    +------------------+
|   auth-pool      |    | Loadbalancer    |    |   app-pool       |
|                  <----+                 +---->                  |
| members:         |    | 137.138.6.80    |    | members:         |
| 188.185.86.31:80 |    |                 |    | 137.138.53.95:80 |
+------------------+    +-------+---------+    +------------------+
                                |
                         +------v-------+
                         |HTTP Listener |
                         | Port 80/HTTP |
                         +------+-------+
                                |
             +------------------+-----------------+
             |                                    |
   +----------------------+             +----------------------+
   | redirect-auth-pool-1 |             | redirect-auth-pool-2 |
   |                      |             |                      |
   |    redirect pool:    |             |    redirect pool:    |
   |     auth-pool        |             |     auth-pool        |
   +----------------------+             +----------------------+
             |                                    |
   +---------+------------+           +-----------+--------------+
   |          L7 rule     |           |          L7 rule         |
   | type: COOKIE         |           | type: COOKIE             |
   | compare-type: REGEX  |           | compare-type: EQUAL_TO   |
   | value: .*            |           | value: INVALID           |
   | invert: True         |           |                          |
   +----------------------+           +--------------------------+
Setup
$ export OS_REGION_NAME=sdn1
$ openstack loadbalancer create --name mylb3 --vip-network-id public

# create application pool attached to the loadbalancer
$ openstack loadbalancer pool create --name my-app-pool --lb-algorithm ROUND_ROBIN --loadbalancer mylb3 --protocol HTTP
$ openstack loadbalancer member create --name my-app-pool-server-1 --address 137.138.53.95 --protocol-port 80 my-app-pool

# create an authentication pool attached to the loadbalancer
$ openstack loadbalancer pool create --name my-auth-pool --lb-algorithm ROUND_ROBIN --loadbalancer mylb3 --protocol HTTP
$ openstack loadbalancer member create --name my-auth-pool-server-1 --address 188.185.86.31 --protocol-port 80 my-auth-pool

# create a listener for port 80
openstack loadbalancer listener create --name http-listener-mylb3 --protocol HTTP --protocol-port 80 mylb3

# set application pool as a default pool for http listener
$ openstack loadbalancer listener set --default-pool my-app-pool http-listener-mylb3
L7 Policies
# redirect requests missing auth_token cookie to auth pool
$ openstack loadbalancer l7policy create --action REDIRECT_TO_POOL --redirect-pool my-auth-pool --name redirect-to-auth-pool http-listener-mylb3
$ openstack loadbalancer l7rule create --compare-type REGEX --key auth_token --type COOKIE --value '.*' --invert redirect-to-auth-pool

# redirect requests which have auth_token cookie value set to INVALID to auth pool
$ openstack loadbalancer l7policy create --action REDIRECT_TO_POOL --redirect-pool my-auth-pool --name redirect-to-auth-pool1 http-listener-mylb3
$ openstack loadbalancer l7rule create --compare-type EQUAL_TO --key auth_token --type COOKIE --value INVALID redirect-to-auth-pool1

Validation

Requests without an auth-token cookie are forwarded to an auth-pool.

$ curl 137.138.6.80
AUTHENTICATION SERVER

Requests with an empty auth-token cookie are forwarded to an auth-pool.

curl 137.138.6.80 --cookie "auth_token="
AUTHENTICATION SERVER

Requests with some value in the auth-token cookie are forwarded to an application pool.

$ curl 137.138.6.80 --cookie "auth_token=kmdsfgksdfmgv"
Hi from Server 2

Requests with value INVALID set in the auth-token cookie are forwarded to an authentication pool.

$ curl 137.138.6.80 --cookie "auth_token=INVALID"
AUTHENTICATION SERVER

5. L7 policy positioning

In this example we will demonstrate how to change the processing order of L7 policies by setting the position parameter. We will create two identical policies for redirecting requests to some URL.

Logical Diagram

               +---------------+
               | Loadbalancer  |
               +-------+-------+
                       |
                +------+-------+
                |HTTP Listener |
                | Port 80/HTTP |
                +-+--------+---+
                  |        |
+-----------------v----+ +-v--------------------+
|redirect-cern-policy  | |redirect-google-policy|
|                      | |                      |
|    redirect url:     | |    redirect url:     |
|https://www.cern.ch   | |https://www.google.com|
+-----------------+----+ +--+-------------------+
                  |         |
                  |         |
+-----------------v-------+ +--v----------------------+
|       L7 rule           | |       L7 rule           |
|type: PATH               | |type: PATH               |
|compare-type: STARTS_WITH| |compare-type: STARTS_WITH|
|value: /                 | |value: /                 |
+-------------------------+ +-------------------------+

Setup
$ export OS_REGION_NAME=sdn1
$ openstack loadbalancer create --name ll --vip-network-id public
$ openstack loadbalancer listener create --name ll-listener --protocol HTTP --protocol-port 80 ll
L7 Policies
# redirect-cern-policy has position 1
$ openstack loadbalancer l7policy create --position 1 --action REDIRECT_TO_URL --redirect-url https://www.cern.ch --name redirect-cern-policy  ll-listener
$ openstack loadbalancer l7rule create --compare-type STARTS_WITH --type PATH --value / redirect-cern-policy

# redirect-google-policy has position 2
$ openstack loadbalancer l7policy create --position 2 --action REDIRECT_TO_URL --redirect-url https://www.google.com --name redirect-google-policy  ll-listener
$ openstack loadbalancer l7rule create --compare-type STARTS_WITH --type PATH --value / redirect-google-policy

Validation

redirect-cern-policy has position 1 and redirect-google-policy has position 2. Both policies have an identical rule (match all HTTP requests). It can be seen from the output below that redirect-cern-policy takes precedence over redirect-google-policy because it has a lower position value.

$ curl 137.138.6.94 -vv
* About to connect() to 137.138.6.94 port 80 (#0)
*   Trying 137.138.6.94...
* Connected to 137.138.6.94 (137.138.6.94) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 137.138.6.94
> Accept: */*
> 
< HTTP/1.1 302 Found
< Cache-Control: no-cache
< Content-length: 0
< Location: https://www.cern.ch
< Connection: close
< 
* Closing connection 0

Now let's give redirect-google-policy a lower position value:

$ openstack loadbalancer l7policy set --position 1 redirect-google-policy
$ openstack loadbalancer l7policy set --position 2 redirect-cern-policy

$ curl 137.138.6.94 -vv
* About to connect() to 137.138.6.94 port 80 (#0)
*   Trying 137.138.6.94...
* Connected to 137.138.6.94 (137.138.6.94) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 137.138.6.94
> Accept: */*
> 
< HTTP/1.1 302 Found
< Cache-Control: no-cache
< Content-length: 0
< Location: https://www.google.com
< Connection: close
< 
* Closing connection 0

Now redirect-google-policy is applied because it has a lower position value.

Deletion of a Load Balancer with L7 policies

Load balancer resources should be deleted in the following order:

  • Health monitor: $ openstack loadbalancer healthmonitor delete <healthmonitor-id>
  • Pool: $ openstack loadbalancer pool delete <pool-id>
  • l7policy: $ openstack loadbalancer l7policy delete <l7-policy-id>
  • Listener: $ openstack loadbalancer listener delete <listener-id>
  • Loadbalancer: $ openstack loadbalancer delete <loadbalancer-id>

Please note that you have to delete the pools which are directly attached to your loadbalancer. You can find the pool IDs by executing openstack loadbalancer show <lb-id> command.

When you delete a pool, cascade deletion will be performed on all members of that pool. Similarly, when you delete an L7 policy, cascade deletion will be performed on all L7 rules of that policy.