Set-Style API (Enterprise)

Set-style rate limiting was introduced with Gloo Gateway Enterprise, release v1.6.0-beta9. If you are using an earlier version, this feature will not be available.

As we saw in the Envoy API guide, Gloo Gateway Enterprise exposes a fine-grained API that allows you to configure a vast number of rate limiting use cases by defining actions that specify an ordered tuple of descriptor keys to attach to a request and descriptors that match an ordered tuple of descriptor keys and apply an associated rate limit.

Although powerful, this API has some drawbacks. We only limit requests whose ordered descriptors match a rule exactly. If, for example, we want to limit requests with an x-type header but limit requests differently that have an x-type header as well as an x-number header equal to 5, we need two sets of actions on each request- one that gets only the value of x-type and another that gets the value of both x-type and x-number. While this is certainly doable, it can quickly become verbose with enough descriptor keys. We might need to enumerate all the combinations of descriptors when we want to rate limit based on several different subsets.

To address these shortcomings, we introduced a new API. Starting with Gloo Gateway Enterprise v1.6.0-beta9 you can define rate limits using set-style descriptors. These are treated as an unordered set such that a given rule will apply if all the specified descriptors match, regardless of the presence and value of the other descriptors and regardless of descriptor order. For example, a rule may match type: a and number: one but the color descriptor can have any value. This can also be understood as color: * where * is a wildcard.

Set-style rate-limiting can be used alongside the prior implementation and is supported by both global rate limiting, described in the Envoy API guide, and RateLimitConfig resources, described in the RateLimitConfigs guide.

SetActions

setActions have the same structure as the actions already used for rate limiting but must be listed under setActions to indicate to the rate limit server that they should be treated as a set and not an ordered tuple.

SetDescriptors

setDescriptors specify a rate limit along with any number of simpleDescriptors which, like descriptors, must include a key and can optionally include a value.

Simple Example

Let’s run through a simple example that uses set-style rate limiting.

Initial setup

First, we need to install Gloo Gateway Enterprise (minimum version 1.6.0-beta9). Please refer to the corresponding installation guide for details.

Let’s also deploy a simple application called petstore:

kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo/v1.14.x/example/petstore/petstore.yaml

Now let’s create a simple Virtual Service routing to this application. (It may take a few seconds to be Accepted.)

glooctl add route --name petstore --path-prefix / --dest-name default-petstore-8080

To verify that the Virtual Service works, let’s send a request:

curl $(glooctl proxy url)/api/pets

It should return the expected response:

[{"id":1,"name":"Dog","status":"available"},{"id":2,"name":"Cat","status":"pending"}]

Add rate limit configuration

Now, let’s edit the Settings resource to include a setDescriptor rate limiting rule:

kubectl edit settings -n gloo-system

and add a ratelimit section that limits requests with type: a and number: one to 1 per minute:

apiVersion: gloo.solo.io/v1
kind: Settings
metadata:
  name: default
  namespace: gloo-system
  # etc...
spec:
  ratelimit:
    setDescriptors:
    - simpleDescriptors:
      - key: type
        value: a
      - key: number
        value: one
      rateLimit:
        requestsPerUnit: 1
        unit: MINUTE
  # etc...

Now edit the Virtual Service to include setActions:

kubectl edit vs petstore -n gloo-system

and add setActions capturing the x-number and x-type headers on the virtualHost:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: petstore
  namespace: gloo-system
  # etc...
spec:
  virtualHost:
    options:
      ratelimit:
        rateLimits:
        - setActions:
          - requestHeaders:
              descriptorKey: number
              headerName: x-number
          - requestHeaders:
              descriptorKey: type
              headerName: x-type
    domains:
    - '*'
    # etc...

Note that the descriptor order doesn’t match, but this is irrelevant for set-style rate limiting.

Test our configuration

Let’s verify that our rate limit policy is correctly enforced.

Let’s try sending some requests to the petstore Virtual Service. Submit the following command twice:

curl $(glooctl proxy url)/api/pets -v -H "x-type: a" -H "x-number: one"

On the second attempt you should receive the following response:

< HTTP/1.1 429 Too Many Requests
< x-envoy-ratelimited: true
< date: Tue, 14 Jul 2020 23:13:18 GMT
< server: envoy
< content-length: 0

This demonstrates that the rate limit is enforced.

Understanding set-style rate limiting functionality

Now modify the Virtual Service setActions to add another descriptor. For example:

          - requestHeaders:
              descriptorKey: color
              headerName: x-color

Send the following curl request a few times.

curl $(glooctl proxy url)/api/pets -v -H "x-type: a" -H "x-number: one"  -H "x-color: blue"

You should see that the request is still rate limited. Since the setDescriptor rule only looks for two descriptors, it still matches whether more descriptors are present or not.

However, if you modify the Virtual Service setActions to remove the type or number descriptor, the request will no longer be rate limited.

Rule Priority

By default, setDescriptor rules are evaluated in the order they are listed. If a rule matches, later rules are ignored.

For example, consider the following rules:

spec:
  ratelimit:
    setDescriptors:
    - simpleDescriptors:
      - key: type
      - key: number
      rateLimit:
        requestsPerUnit: 10
        unit: MINUTE
    - simpleDescriptors:
      - key: type
      rateLimit:
        requestsPerUnit: 5
        unit: MINUTE

If the type and number are both present on a request, we want the limit to be 10 per minute. However, if only the type is present on a request, we want the limit to be 5 per minute.

You can also specify the alwaysApply flag. This tells the server to consider a rule even if an earlier rule has already matched.

For example, if we have the same configuration as above but with the alwaysApply flag set to true, a request with both type and number present will be limited after just 5 requests per minute, as both rules below are now considered.

spec:
  ratelimit:
    setDescriptors:
    - simpleDescriptors:
      - key: type
      - key: number
      rateLimit:
        requestsPerUnit: 10
        unit: MINUTE
    - simpleDescriptors:
      - key: type
      rateLimit:
        requestsPerUnit: 5
        unit: MINUTE
      alwaysApply: true

All-Encompassing Rules

We can also create rules that match all requests by omitting simpleDescriptors altogether. Any setDescriptor rule should match requests whose descriptors contain the rule’s simpleDescriptors as a subset. If simpleDescriptors is omitted from the rule, requests whose descriptors contain the empty set as a subset should match, i.e., all requests.

These rules should be listed after all other rules without alwaysApply set to true, or later rules will not be considered due to rule priority, as explained above.

An all-encompassing rule without simpleDescriptors would look like this:

spec:
  ratelimit:
    setDescriptors:
    - rateLimit:
        requestsPerUnit: 10
        unit: MINUTE

This rule will limit all requests to at most 10 per minute.