diff --git a/doc/source/guides/l7-cookbook.rst b/doc/source/guides/l7-cookbook.rst new file mode 100644 index 0000000000..625010e3b3 --- /dev/null +++ b/doc/source/guides/l7-cookbook.rst @@ -0,0 +1,353 @@ +.. + Copyright (c) 2016 IBM + + Licensed under the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain + a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations + under the License. + +================ +Layer 7 Cookbook +================ + +Introduction +============ +This document gives several examples of common L7 load balancer usage. For a +description of L7 load balancing see: :doc:`l7` + +For the puposes of this guide we assume that the neutron command-line interface +is going to be used to configure all features of Neutron LBaaS with an Octavia +back-end. Also, in order to keep these examples short, we assume that many +non-L7 configuration tasks (such as deploying loadbalancers, listeners, pools, +members, healthmonitors, etc.) have already been accomplished. A description +of the starting conditions is given in each example below. + + +Examples +======== + +Redirect http://www.example.com/ to https://www.example.com/ +------------------------------------------------------------ +**Scenario description**: + +* Load balancer *lb1* has been set up with ``TERMINATED_HTTPS`` listener + *tls_listener* on TCP port 443. +* *tls_listener* has been populated with a default pool, members, etc. +* *tls_listener* is available under the DNS name *https://www.example.com/* +* We want any regular HTTP requests to TCP port 80 on *lb1* to be redirected + to *tls_listener* on TCP port 443. + +**Solution**: + +1. Create listener *http_listener* as an HTTP listener on *lb1* port 80. +2. Set up an L7 Policy *policy1* on *http_listener* with action + ``REDIRECT_TO_URL`` pointed at the URL *https://www.example.com/* +3. Add an L7 Rule to *policy1* which matches all requests. + + +**CLI commands**: + +:: + + neutron lbaas-listener-create --name http_listener --loadbalancer lb1 --protocol HTTP --protocol-port 80 + neutron lbaas-l7policy-create --action REDIRECT_TO_URL --redirect-url https://www.example.com/ --listener http_listener --name policy1 + neutron lbaas-l7rule-create --type PATH --compare-type STARTS_WITH --value / policy1 + + +.. _send-requests-to-static-pool: + +Send requests starting with /js or /images to *static_pool* +----------------------------------------------------------- +**Scenario description**: + +* Listener *listener1* on load balancer *lb1* is set up to send all requests to + its default_pool *pool1*. +* We are introducing static content servers 10.0.0.10 and 10.0.0.11 on subnet + *private-subnet*, and want any HTTP requests with a URL that starts with + either "/js" or "/images" to be sent to those two servers instead of *pool1*. + +**Solution**: + +1. Create pool *static_pool* on *lb1*. +2. Populate *static_pool* with the new back-end members. +3. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at + *static_pool*. +4. Create an L7 Rule on *policy1* which looks for "/js" at the start of + the request path. +5. Create L7 Policy *policy2* with action ``REDIRECT_TO_POOL`` pointed at + *static_pool*. +6. Create an L7 Rule on *policy2* which looks for "/images" at the start + of the request path. + +**CLI commands**: + +:: + + neutron lbaas-pool-create --name static_pool --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP + neutron lbaas-member-create --subnet private-subnet --address 10.0.0.10 --protocol-port 80 static_pool + neutron lbaas-member-create --subnet private-subnet --address 10.0.0.11 --protocol-port 80 static_pool + neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool static_pool --listener listener1 --name policy1 + neutron lbaas-l7rule-create --type PATH --compare-type STARTS_WITH --value /js policy1 + neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool static_pool --listener listener1 --name policy2 + neutron lbaas-l7rule-create --type PATH --compare-type STARTS_WITH --value /images policy2 + +**Alternate solution** (using regular expressions): + +1. Create pool *static_pool* on *lb1*. +2. Populate *static_pool* with the new back-end members. +3. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at + *static_pool*. +4. Create an L7 Rule on *policy1* which uses a regular expression to match + either "/js" or "/images" at the start of the request path. + +**CLI commands**: + +:: + + neutron lbaas-pool-create --name static_pool --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP + neutron lbaas-member-create --subnet private-subnet --address 10.0.0.10 --protocol-port 80 static_pool + neutron lbaas-member-create --subnet private-subnet --address 10.0.0.11 --protocol-port 80 static_pool + neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool static_pool --listener listener1 --name policy1 + neutron lbaas-l7rule-create --type PATH --compare-type REGEX --value '^/(js|images)' policy1 + + +Send requests for http://www2.example.com/ to *pool2* +----------------------------------------------------- +**Scenario description**: + +* Listener *listener1* on load balancer *lb1* is set up to send all requests to + its default_pool *pool1*. +* We have set up a new pool *pool2* on *lb1* and want any requests using the + HTTP/1.1 hostname *www2.example.com* to be sent to *pool2* instead. + +**Solution**: + +1. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at + *pool2*. +2. Create an L7 Rule on *policy1* which matches the hostname + *www2.example.com*. + +**CLI commands**: + +:: + + neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool pool2 --listener listener1 --name policy1 + neutron lbaas-l7rule-create --type HOST_NAME --compare-type EQUAL_TO --value www2.example.com policy1 + + +Send requests for *\*.example.com* to *pool2* +--------------------------------------------- +**Scenario description**: + +* Listener *listener1* on load balancer *lb1* is set up to send all requests to + its default_pool *pool1*. +* We have set up a new pool *pool2* on *lb1* and want any requests using any + HTTP/1.1 hostname like *\*.example.com* to be sent to *pool2* instead. + +**Solution**: + +1. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at + *pool2*. +2. Create an L7 Rule on *policy1* which matches any hostname that ends with + *example.com*. + +**CLI commands**: + +:: + + neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool pool2 --listener listener1 --name policy1 + neutron lbaas-l7rule-create --type HOST_NAME --compare-type ENDS_WITH --value example.com policy1 + + +Send unauthenticated users to *login_pool* (scenario 1) +------------------------------------------------------- +**Scenario description**: + +* ``TERMINATED_HTTPS`` listener *listener1* on load balancer *lb1* is set up + to send all requests to its default_pool *pool1*. +* The site behind *listener1* requires all web users to authenticate, after + which a browser cookie *auth_token* will be set. +* When web users log out, or if the *auth_token* is invalid, the application + servers in *pool1* clear the *auth_token*. +* We want to introduce new secure authentication server 10.0.1.10 on Neutron + subnet *secure_subnet* (a different Neutron subnet from the default + application servers) which handles authenticating web users and sets the + *auth_token*. + +*Note:* Obviously, to have a more secure authentication system that is less +vulnerable to attacks like XSS, the new secure authentication server will need +to set session variables to which the default_pool servers will have access +outside the data path with the web client. There may be other security concerns +as well. This example is not meant to address how these are to be +accomplished--it's mainly meant to show how L7 application routing can be done +based on a browser cookie. + +**Solution**: + +1. Create pool *login_pool* on *lb1*. +2. Add member 10.0.1.10 on *secure_subnet* to *login_pool*. +3. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at + *login_pool*. +4. Create an L7 Rule on *policy1* which looks for browser cookie *auth_token* + (with any value) and matches if it is *NOT* present. + +**CLI commands**: + +:: + + neutron lbaas-pool-create --name login_pool --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP + neutron lbaas-member-create --subnet secure_subnet --address 10.0.1.10 --protocol-port 80 login_pool + neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool login_pool --listener listener1 --name policy1 + neutron lbaas-l7rule-create --type COOKIE --key auth_token --compare-type REGEX --value '.*' --invert policy1 + + +Send unauthenticated users to *login_pool* (scenario 2) +-------------------------------------------------------- +**Scenario description**: + +* ``TERMINATED_HTTPS`` listener *listener1* on load balancer *lb1* is set up + to send all requests to its default_pool *pool1*. +* The site behind *listener1* requires all web users to authenticate, after + which a browser cookie *auth_token* will be set. +* When web users log out, or if the *auth_token* is invalid, the application + servers in *pool1* set *auth_token* to the literal string "INVALID". +* We want to introduce new secure authentication server 10.0.1.10 on Neutron + subnet *secure_subnet* (a different Neutron subnet from the default + application servers) which handles authenticating web users and sets the + *auth_token*. + +*Note:* Obviously, to have a more secure authentication system that is less +vulnerable to attacks like XSS, the new secure authentication server will need +to set session variables to which the default_pool servers will have access +outside the data path with the web client. There may be other security concerns +as well. This example is not meant to address how these are to be +accomplished-- it's mainly meant to show how L7 application routing can be done +based on a browser cookie. + +**Solution**: + +1. Create pool *login_pool* on *lb1*. +2. Add member 10.0.1.10 on *secure_subnet* to *login_pool*. +3. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at + *login_pool*. +4. Create an L7 Rule on *policy1* which looks for browser cookie *auth_token* + (with any value) and matches if it is *NOT* present. +5. Create L7 Policy *policy2* with action ``REDIRECT_TO_POOL`` pointed at + *login_pool*. +6. Create an L7 Rule on *policy2* which looks for browser cookie *auth_token* + and matches if it is equal to the literal string "INVALID". + +**CLI commands**: + +:: + + neutron lbaas-pool-create --name login_pool --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP + neutron lbaas-member-create --subnet secure_subnet --address 10.0.1.10 --protocol-port 80 login_pool + neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool login_pool --listener listener1 --name policy1 + neutron lbaas-l7rule-create --type COOKIE --key auth_token --compare-type REGEX --value '.*' --invert policy1 + neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool login_pool --listener listener1 --name policy2 + neutron lbaas-l7rule-create --type COOKIE --key auth_token --compare-type EQUAL_TO --value INVALID policy2 + + +Send requests for *http://api.example.com/api* to *api_pool* +------------------------------------------------------------ +**Scenario description**: + +* Listener *listener1* on load balancer *lb1* is set up to send all requests + to its default_pool *pool1*. +* We have created pool *api_pool* on *lb1*, however, for legacy business logic + reasons, we only want requests sent to this pool if they match the hostname + *api.example.com* AND the request path starts with */api*. + +**Solution**: + +1. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at + *api_pool*. +2. Create an L7 Rule on *policy1* which matches the hostname *api.example.com*. +3. Create an L7 Rule on *policy1* which matches */api* at the start of the + request path. (This rule will be logically ANDed with the previous rule.) + +**CLI commands**: + +:: + + neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool api_pool --listener listener1 --name policy1 + neutron lbaas-l7rule-create --type HOST_NAME --compare-type EQUAL_TO --value api.example.com policy1 + neutron lbaas-l7rule-create --type PATH --compare-type STARTS_WITH --value /api policy1 + + +Set up A/B testing on an existing production site using a cookie +---------------------------------------------------------------- +**Scenario description**: + +* Listener *listener1* on load balancer *lb1* is a production site set up as + described under :ref:`send-requests-to-static-pool` (alternate solution) + above. Specifically: + + * HTTP requests with a URL that starts with either "/js" or "/images" are + sent to pool *static_pool*. + * All other requests are sent to *listener1's* default_pool *pool1*. + +* We are introducing a "B" version of the production site, complete with its + own default_pool and static_pool. We will call these *pool_B* and + *static_pool_B* respectively. +* The *pool_B* members should be 10.0.0.50 and 10.0.0.51, and the + *static_pool_B* members should be 10.0.0.100 and 10.0.0.101 on subnet + *private-subnet*. +* Web clients which should be routed to the "B" version of the site get a + cookie set by the member servers in *pool1*. This cookie is called + "site_version" and should have the value "B". + +**Solution**: + +1. Create pool *pool_B* on *lb1*. +2. Populate *pool_B* with its new back-end members. +3. Create pool *static_pool_B* on *lb1*. +4. Populate *static_pool_B* with its new back-end members. +5. Create L7 Policy *policy2* with action ``REDIRECT_TO_POOL`` pointed at + *static_pool_B*. This should be inserted at position 1. +6. Create an L7 Rule on *policy2* which uses a regular expression to match + either "/js" or "/images" at the start of the request path. +7. Create an L7 Rule on *policy2* which matches the cookie "site_version" to + the exact string "B". +8. Create L7 Policy *policy3* with action ``REDIRECT_TO_POOL`` pointed at + *pool_B*. This should be inserted at position 2. +9. Create an L7 Rule on *policy3* which matches the cookie "site_version" to + the exact string "B". + +*A word about L7 Policy position*: Since L7 Policies are evaluated in order +according to their position parameter, and since the first L7 Policy whose L7 +Rules all evaluate to True is the one whose action is followed, it is important +that L7 Policies with the most specific rules get evaluated first. + +For example, in this solution, if *policy3* were to appear in the listener's L7 +Policy list before *policy2* (that is, if *policy3* were to have a lower +position number than *policy2*), then if a web client were to request the URL +http://www.example.com/images/a.jpg with the cookie "site_version:B", then +*policy3* would match, and the load balancer would send the request to +*pool_B*. From the scenario description, this request clearly was meant to be +sent to *static_pool_B*, which is why *policy2* needs to be evaluated before +*policy3*. + +**CLI commands**: + +:: + + neutron lbaas-pool-create --name pool_B --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP + neutron lbaas-member-create --subnet private-subnet --address 10.0.0.50 --protocol-port 80 pool_B + neutron lbaas-member-create --subnet private-subnet --address 10.0.0.51 --protocol-port 80 pool_B + neutron lbaas-pool-create --name static_pool_B --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP + neutron lbaas-member-create --subnet private-subnet --address 10.0.0.100 --protocol-port 80 static_pool_B + neutron lbaas-member-create --subnet private-subnet --address 10.0.0.101 --protocol-port 80 static_pool_B + neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool static_pool_B --listener listener1 --name policy2 --position 1 + neutron lbaas-l7rule-create --type PATH --compare-type REGEX --value '^/(js|images)' policy2 + neutron lbaas-l7rule-create --type COOKIE --key site_version --compare-type EQUAL_TO --value B policy2 + neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool pool_B --listener listener1 --name policy3 --position 2 + neutron lbaas-l7rule-create --type COOKIE --key site_version --compare-type EQUAL_TO --value B policy3 diff --git a/doc/source/guides/l7.rst b/doc/source/guides/l7.rst new file mode 100644 index 0000000000..d4e73cf63d --- /dev/null +++ b/doc/source/guides/l7.rst @@ -0,0 +1,185 @@ +.. + Copyright (c) 2016 IBM + + Licensed under the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain + a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations + under the License. + +====================== +Layer 7 Load Balancing +====================== + +What is L7 load balancing? +========================== +Layer 7 load balancing takes its name from the OSI model, indicating that the +load balancer distributes requests to back-end pools based on layer 7 +(application) data. Layer 7 load balancing is also known as "request +switching," "application load balancing," "content based routing," "content +based switching," and "content based balancing." + +A layer 7 load balancer consists of a listener that accepts requests on behalf +of a number of back-end pools and distributes those requests based on policies +that use application data to determine which pools should service any given +request. This allows for the application infrastructure to be specifically +tuned/optimized to serve specific types of content. For example, one group of +back-end servers (pool) can be tuned to serve only images, another for +execution of server-side scripting languages like PHP and ASP, and another for +static content such as HTML, CSS, and JavaScript. + +Unlike lower-level load balancing, layer 7 load balancing does not require that +all pools behind the load balancing service have the same content. In fact, it +is generally expected that a layer 7 load balancer expects the back-end servers +from different pools will have different content. Layer 7 load balancers are +capable of directing requests based on URI, host, HTTP headers, and other data +in the application message. + + +L7 load balancing in Octavia +---------------------------- +The layer 7 load balancing capabilities described in this document were added +to Neutron LBaaS and Octavia in the Mitaka release cycle (Octavia 0.8). + +While layer 7 load balancing in general can theoretically be done for any +well-defined layer 7 application interface, for the purposes of Octavia, L7 +functionality refers only to the HTTP protocol and its semantics. + + +How does it work? +================= +Neutron LBaaS and Octavia accomplish the logic of layer 7 load balancing +through the use of L7 Rules and L7 Policies. An L7 Rule is a single, simple +logical test which evaluates to true or false. An L7 Policy is a collection of +L7 rules, as well as a defined action that should be taken if all the rules +associated with the policy match. + +These concepts and their specific details are expanded upon below. + + +L7 Rules +-------- +An L7 Rule is a single, simple logical test which returns either true or false. +It consists of a rule type, a comparison type, a value, and an optional key +that gets used depending on the rule type. An L7 rule must always be associated +with an L7 policy. + +See also: :doc:`../api/octaviaapi` + +Rule types +__________ +L7 rules have the following types: + +* ``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 of a given type always do comparisons. The types of comparisons we +support are listed below. Note that not all rule types support all comparison +types: + +* ``REGEX``: Perl type regular expression matching +* ``STARTS_WITH``: String starts with +* ``ENDS_WITH``: String ends with +* ``CONTAINS``: String contains +* ``EQUAL_TO``: String is equal to + +Invert +______ +In order to more fully express the logic required by some policies, rules may +have their result inverted. That is to say, if the invert parameter of a given +rule is true, the result of its comparison will be inverted. (For example, an +inverted "equal to" rule effectively becomes a "not equal to", and an inverted +"regex" rule returns true only if the given regex does not match.) + + +L7 Policies +----------- +An L7 Policy is a collection of L7 rules associated with a Listener, and which +may also have an association to a back-end pool. Policies describe actions that +should be taken by the load balancing software if all of the rules in the +policy return true. + +See also: :doc:`../api/octaviaapi` + +Policy Logic +____________ +Policy logic is very simple: All the rules associated with a given policy are +logically ANDed together. A request must match all the policy's rules to match +the policy. + +If you need to express a logical OR operation between rules, then do this by +creating multiple policies with the same action (or, possibly, by making a more +elaborate regular expression). + +Policy Actions +______________ +If an L7 policy matches a given request, then that policy's action is executed. +The following are the actions an L7 Policy may take: + +* ``REJECT``: The request is denied with an appropriate response code, and not + forwarded on 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 + associated with the L7 policy. + +Policy Position +_______________ +When multiple L7 Policies are associated with a listener, then the policies' +``position`` parameter becomes important. The ``position`` parameter is used +when determining the order in which L7 policies are evaluated. Here are a few +notes about how policy position affects listener behavior: + +* In the reference implementation (haproxy amphorae) of Octavia, haproxy + enforces the following ordering regarding policy actions: + + * ``REJECT`` policies take precedence over all other policies. + * ``REDIRECT_TO_URL`` policies take precedence over ``REDIRECT_TO_POOL`` + policies. + * ``REDIRECT_TO_POOL`` policies are only evaluated after all of the above, + and in the order specified by the ``position`` of the policy. + +* L7 Policies are evaluated in a specific order (as defined by the ``position`` + attribute), and the first policy that matches a given request will be the one + whose action is followed. +* If no policy matches a given request, then the request is routed to the + listener's default pool ,if it exists. If the listener has no default pool, + then an error 503 is returned. +* Policy position numbering starts with 1. +* If a new policy is created with a position that matches that of an existing + policy, then the new policy is inserted at the given position. +* If a new policy is created without specifying a position, or specifying a + position that is greater than the number of policies already in the list, the + new policy will just be appended to the list. +* When policies are inserted, deleted, or appended to the list, the policy + position values are re-ordered from 1 without skipping numbers. For example, + if policy A, B, and C have position values of 1, 2 and 3 respectively, if you + delete policy B from the list, policy C's position becomes 2. + + +L7 usage examples +================= +For a cookbook of common L7 usage examples, please see the :doc:`l7-cookbook` + + +Useful links +============ +* :doc:`../api/octaviaapi` +* https://github.com/openstack/neutron-specs/blob/master/specs/mitaka/lbaas-l7-rules.rst +* http://cbonte.github.io/haproxy-dconv/1.6/configuration.html#7 diff --git a/doc/source/index.rst b/doc/source/index.rst index c7509ba8d0..4157efefa0 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -12,6 +12,15 @@ Getting started main/glossary.rst +For users +--------- + +.. toctree:: + :maxdepth: 1 + + guides/l7.rst + guides/l7-cookbook.rst + For developers --------------