Rule Priority (Enterprise)

Overview

In this guide we explore some more advanced configuration options added to Gloo’s implementation of Envoy’s rate limit API. If you are not already familiar with Gloo’s Envoy rate limit API support and examples, you should read our Envoy rate limit API intro first.

Motivation

Remember that rate limit configuration is defined in two places in the Envoy API:

Sample Envoy client config:

rateLimits:
  # generate descriptors for Rule 1 and 2
  - actions:
      - requestHeaders:
          descriptorKey: type
          headerName: x-type
  # generate descriptors for Rule 3
  - actions:
      - requestHeaders:
          descriptorKey: type
          headerName: x-type
      - requestHeaders:
          descriptorKey: number
          headerName: x-number

Each RateLimitAction (the top-level list) generates a descriptor tuple to be sent to the rate limit server to be checked for rate limiting. Each Action within a RateLimitAction (i.e., the inner list) gets appended in order to generate the final rate-limiting descriptor.

For a single request with the x-type and x-number headers, the generated descriptor tuples for the above Envoy client config would look like:

Descriptor tuples that cannot be built aren’t sent to the rate limit server in their incomplete form, rather they are ignored. For the above example, a request with only the x-type header generates only the first descriptor tuple.

Sample rate limit server config:

descriptors:
  # Rule 1, limit all Messenger requests to 2/min
  - key: type
    value: Messenger
    rateLimit:
      requestsPerUnit: 2
      unit: MINUTE
  # Rule 2, limit all Whatsapp requests to 1/min
  - key: type
    value: Whatsapp
    rateLimit:
      requestsPerUnit: 1
      unit: MINUTE
    descriptors:
      # Rule 3, limit all Whatsapp requests to number '411' to 100/min
      - key: number
        rateLimit:
          requestsPerUnit: 100
          unit: MINUTE
        value: "411"
        weight: 1 # Rule 3 takes priority over other rules

Each descriptor with a rate limit defines a rule to be considered during rate-limiting. When a request comes into Envoy, rate limit actions are applied to the request to generate a list of descriptor tuples that are sent to the rate limit server. The rate limit server evaluates each descriptor tuple to its full depth, ignoring any tuples that don’t have matching rules.

If any descriptor tuple matches a rule that requires rate limiting then the entire request returns HTTP 429 Too Many Requests. This is a strict requirement that rule priority lets us get around.

In Gloo Enterprise 1.x, Gloo added the weight (default 0) and alwaysApply (default false) configuration options to the Descriptor definition:

Test the Example

Install the petclinic application and create a virtual service that routes to it:

kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo/v1.2.9/example/petclinic/petclinic.yaml

glooctl add route --name default --namespace gloo-system \
  --path-prefix / \
  --dest-name default-petclinic-8080 \
  --dest-namespace gloo-system

Open an editor to modify your rate limit client config:

glooctl edit virtualservice --namespace gloo-system --name default ratelimit client-config

And paste the example config:

rateLimits:
  # generate descriptors for Rule 1 and 2
  - actions:
      - requestHeaders:
          descriptorKey: type
          headerName: x-type
  # generate descriptors for Rule 3
  - actions:
      - requestHeaders:
          descriptorKey: type
          headerName: x-type
      - requestHeaders:
          descriptorKey: number
          headerName: x-number

Now open an editor to modify your rate limit server config:

glooctl edit settings --namespace gloo-system --name default ratelimit server-config

And paste the example config:

descriptors:
  # Rule 1, limit all Messenger requests to 2/min
  - key: type
    value: Messenger
    rateLimit:
      requestsPerUnit: 2
      unit: MINUTE
  # Rule 2, limit all Whatsapp requests to 1/min
  - key: type
    value: Whatsapp
    rateLimit:
      requestsPerUnit: 1
      unit: MINUTE
    descriptors:
      # Rule 3, limit all Whatsapp requests to number '411' to 100/min
      - key: number
        rateLimit:
          requestsPerUnit: 100
          unit: MINUTE
        value: "411"
        weight: 1 # Rule 3 takes priority over other rules

Run the following three times; you should get HTTP 429 Too Many Requests on the third request.

curl -H "x-type: Messenger" -H "x-number: 311" --head $(glooctl proxy url)

Run the following two times; you should get HTTP 429 Too Many Requests on the second request.

curl -H "x-type: Whatsapp" -H "x-number: 311" --head $(glooctl proxy url)

By changing the number we can match the more specific rule that has a higher priority.

Run the following a couple times; you shouldn’t get rate-limited:

curl -H "x-type: Whatsapp" -H "x-number: 411" --head $(glooctl proxy url)

Requests to number 411 have a much higher rate limit, and will not return HTTP 429 unless more than 100 requests are sent within a minute. Note that this wouldn’t work without rule priority (play around with the server settings to test this!) because our requests would match the inner Whatsapp rule (Rule 2) that limits all requests to 1/min, regardless of the provided number.

Cleanup

kubectl delete -f https://raw.githubusercontent.com/solo-io/gloo/v1.2.9/example/petclinic/petclinic.yaml
kubectl delete vs default --namespace gloo-system