Rate Limit Your Requests Per Consumer Groups
Shlomi Tubul

By on July 5, 2022

How to rate limit your requests per consumer groups

One of the most common use cases our customers are using Kong for is rate limiting. There are a few common reasons for doing this:

  1. Performance – How can you make sure your service will respond with the required service level agreement (SLA)?
  2. Security – How can you block attempts to take down your application, such as a distributed denial of service (DDoS)?
  3. Business – What if you want to give a paying customer better/upgraded service than one that doesn’t? 

For all those and more, we’re able to easily add this functionality with Kong’s rate limit advanced plugin

The plugin can be applied at different levels such as service, route, specific consumer, or even in a global scope. By having this flexibility, one can set a generic global limit but then still overrule the global limit to allow for a more specific rate limit at a lower level.

However, after working with many of Kong customers, one use case couldn’t be met with the specificity highlighted above. This use case is how can Kong help when customers want to have different rate limits based on an organization, partner, or tenant? The answer is Kong’s feature of “Consumer Groups,” which we’ll expand on below (and is documented here)

Released on 2.7, the Kong API gateway allows you to define limits per consumer groups. This means that one can still use the general RL functionality as mentioned above, but also add specific limits to certain groups. Let’s see how we can make it work.

Add a service

➜  demo-environment git:(main) ✗ http post localhost:8001/services name=prod-backend url=http://httpbin.org/anything
 
 
HTTP/1.1 201 Created
 
{
    "ca_certificates": null,
    "client_certificate": null,
    "connect_timeout": 60000,
    "created_at": 1655216213,
    "enabled": true,
    "host": "httpbin.org",
    "id": "9c77f727-42d2-4f75-b194-9b6dd9fcfe5b",
    "name": "prod-backend",
    "path": "/anything",
    "port": 80,
    "protocol": "http",
    "read_timeout": 60000,
    "retries": 5,
    "tags": null,
    "tls_verify": null,
    "tls_verify_depth": null,
    "updated_at": 1655216213,
    "write_timeout": 60000
}

Add a route

➜  demo-environment git:(main) ✗ http post localhost:8001/services/prod-backend/routes name=prod-backend-route paths:='["/requests"]'
 
 
HTTP/1.1 201 Created
 
{
    "created_at": 1655216505,
    "destinations": null,
    "headers": null,
    "hosts": null,
    "https_redirect_status_code": 426,
    "id": "8d1c42ed-0fe6-4774-a4df-36a790568796",
    "methods": null,
    "name": "prod-backend-route",
    "path_handling": "v0",
    "paths": [
        "/requests"
    ],
    "preserve_host": false,
    "protocols": [
        "http",
        "https"
    ],
    "regex_priority": 0,
    "request_buffering": true,
    "response_buffering": true,
    "service": {
        "id": "9c77f727-42d2-4f75-b194-9b6dd9fcfe5b"
    },
    "snis": null,
    "sources": null,
    "strip_path": true,
    "tags": null,
    "updated_at": 1655216505
}

Add Advanced Rate Limiting plugin on the service level, and enforce it to work with 2 consumer groups.

➜  demo-environment git:(main) ✗ http post localhost:8001/services/prod-backend/plugins  name=rate-limiting-advanced config:='{"sync_rate": 0, "window_size": [60], "limit": [10], "enforce_consumer_groups":true, "consumer_groups": ["hr","marketing"]}'
 
 
 
HTTP/1.1 201 Created
{
    "config": {
        "consumer_groups": [
            "hr",
            "marketing"
        ],
        "dictionary_name": "kong_rate_limiting_counters",
        "enforce_consumer_groups": true,
        "header_name": null,
        "hide_client_headers": false,
        "identifier": "consumer",
        "limit": [
            10
        ],
        "namespace": "vCcg7OpfB1rGxRrCaJ3vEytelRyqUfyZ",
        "path": null,
        "redis": {
            "cluster_addresses": null,
            "connect_timeout": null,
            "database": 0,
            "host": null,
            "keepalive_backlog": null,
            "keepalive_pool_size": 30,
            "password": null,
            "port": null,
            "read_timeout": null,
            "send_timeout": null,
            "sentinel_addresses": null,
            "sentinel_master": null,
            "sentinel_password": null,
            "sentinel_role": null,
            "sentinel_username": null,
            "server_name": null,
            "ssl": false,
            "ssl_verify": false,
            "timeout": 2000,
            "username": null
        },
        "retry_after_jitter_max": 0,
        "strategy": "cluster",
        "sync_rate": 0,
        "window_size": [
            60
        ],
        "window_type": "sliding"
    },
    "consumer": null,
    "created_at": 1655216600,
    "enabled": true,
    "id": "cf9ade32-7be9-49c4-9414-1e2e25a4c74e",
    "name": "rate-limiting-advanced",
    "protocols": [
        "grpc",
        "grpcs",
        "http",
        "https"
    ],
    "route": null,
    "service": {
        "id": "9c77f727-42d2-4f75-b194-9b6dd9fcfe5b"
    },
    "tags": null
}
 
 

Add key authentication plugin for our consumers

➜  demo-environment git:(main) ✗ http post localhost:8001/routes/prod-backend-route/plugins name=key-auth config.key_names=apikey config.key_in_body=false config.key_in_header=true config.key_in_query=true config.hide_credentials=false config.run_on_preflight=true -f
 
HTTP/1.1 201 Created
 
{
    "config": {
        "anonymous": null,
        "hide_credentials": false,
        "key_in_body": false,
        "key_in_header": true,
        "key_in_query": true,
        "key_names": [
            "apikey"
        ],
        "run_on_preflight": true
    },
    "consumer": null,
    "created_at": 1655216690,
    "enabled": true,
    "id": "6faf92ba-3d1e-4333-b603-b1dcd09a7767",
    "name": "key-auth",
    "protocols": [
        "grpc",
        "grpcs",
        "http",
        "https"
    ],
    "route": {
        "id": "8d1c42ed-0fe6-4774-a4df-36a790568796"
    },
    "service": null,
    "tags": null
}
 

Add 3 consumers

➜  demo-environment git:(main) ✗ http post localhost:8001/consumers username=ann custom_id=ann
 
HTTP/1.1 201 Created
 
{
    "created_at": 1655216804,
    "custom_id": "ann",
    "id": "62a744e3-eee6-48c9-942c-6660fe5c3414",
    "tags": null,
    "type": 0,
    "username": "ann",
    "username_lower": "ann"
}
 
 
 
➜  demo-environment git:(main) ✗ http post localhost:8001/consumers username=james custom_id=james
 
HTTP/1.1 201 Created
{
    "created_at": 1655216859,
    "custom_id": "james",
    "id": "1cb68a87-841d-4412-b902-41f74539544d",
    "tags": null,
    "type": 0,
    "username": "james",
    "username_lower": "james"
}
 
➜  demo-environment git:(main) ✗ http post localhost:8001/consumers username=sarah custom_id=sarah
 
HTTP/1.1 201 Created
 
{
    "created_at": 1655216941,
    "custom_id": "sarah",
    "id": "ca40abfd-de21-412b-94a0-14ccced9640d",
    "tags": null,
    "type": 0,
    "username": "sarah",
    "username_lower": "sarah"
}

Create Keys for the users for authentication

➜  demo-environment git:(main) ✗ http post localhost:8001/consumers/ann/key-auth
   
HTTP/1.1 201 Created
{
    "consumer": {
        "id": "62a744e3-eee6-48c9-942c-6660fe5c3414"
    },
    "created_at": 1655216979,
    "id": "e1935b1e-6c9f-4f2d-af5e-25494f26854c",
    "key": "1kGHqaKRlFPuTh5T1GOyyNo4iG3Fsvfj",
    "tags": null,
    "ttl": null
}
 
➜  demo-environment git:(main) ✗  http post localhost:8001/consumers/james/key-auth
 
HTTP/1.1 201 Created
{
    "consumer": {
        "id": "1cb68a87-841d-4412-b902-41f74539544d"
    },
    "created_at": 1655217028,
    "id": "cf7ccc1d-d851-4327-adf0-bac04c724b4a",
    "key": "OFeajtvle099Dn1chzGLbeyDW8BlFHA0",
    "tags": null,
    "ttl": null
}
 
➜  demo-environment git:(main) ✗ http post localhost:8001/consumers/sarah/key-auth
              
HTTP/1.1 201 Created
{
    "consumer": {
        "id": "ca40abfd-de21-412b-94a0-14ccced9640d"
    },
    "created_at": 1655217068,
    "id": "40118577-c706-4ec9-85da-2f5f7bb84ea5",
    "key": "skIZ9Imx0fpJsAH36tP9PDpAELotbuuM",
    "tags": null,
    "ttl": null
}
 

Add 2 consumer groups — we will assign different users to different groups later on to test our functionality

➜  demo-environment git:(main) ✗ http post localhost:8001/consumer_groups name=hr 
 
HTTP/1.1 201 Created
{
    "created_at": 1655217132,
    "id": "e61cc03c-faf3-4898-a4b5-863790c4eea0",
    "name": "hr"
}
 
 
 
➜  demo-environment git:(main) ✗ http post localhost:8001/consumer_groups name=marketing
 
HTTP/1.1 201 Created
{
    "created_at": 1655217158,
    "id": "b78ae896-7afa-4fd4-9b5e-f1486345e803",
    "name": "marketing"
}
 

Let’s see that we are able to access our service and actually get limited requests regardless of the user. We will call our service with Ann and Sarah:

➜  demo-environment git:(main) ✗ http :8000/requests apikey:1kGHqaKRlFPuTh5T1GOyyNo4iG3Fsvfj
 
HTTP/1.1 200 OK
……
RateLimit-Limit: 10
RateLimit-Remaining: 9
…….
 
 
➜  demo-environment git:(main) ✗ http :8000/requests apikey:skIZ9Imx0fpJsAH36tP9PDpAELotbuuM
 
HTTP/1.1 200 OK
…….
RateLimit-Limit: 10
RateLimit-Remaining: 9
……

As we can see, both users are limited to 10 calls per minute.

Next phase is to Link between consumer groups and consumers:

➜  demo-environment git:(main) ✗ http POST :8001/consumers/ann/consumer_groups group=hr
HTTP/1.1 201 Created
{
    "consumer": {
        "created_at": 1655216804,
        "custom_id": "ann",
        "id": "62a744e3-eee6-48c9-942c-6660fe5c3414",
        "tags": null,
        "type": 0,
        "username": "ann",
        "username_lower": "ann"
    },
    "consumer_groups": [
        {
            "created_at": 1655217132,
            "id": "e61cc03c-faf3-4898-a4b5-863790c4eea0",
            "name": "hr"
        }
    ]
}
 
➜  demo-environment git:(main) ✗ http POST :8001/consumers/james/consumer_groups group=marketing
 
HTTP/1.1 201 Created
{
    "consumer": {
        "created_at": 1655216859,
        "custom_id": "james",
        "id": "1cb68a87-841d-4412-b902-41f74539544d",
        "tags": null,
        "type": 0,
        "username": "james",
        "username_lower": "james"
    },
    "consumer_groups": [
        {
            "created_at": 1655217158,
            "id": "b78ae896-7afa-4fd4-9b5e-f1486345e803",
            "name": "marketing"
        }
    ]
}
 
 
 
➜  demo-environment git:(main) ✗ http POST :8001/consumers/sarah/consumer_groups group=marketing
 
HTTP/1.1 201 Created
 
{
    "consumer": {
        "created_at": 1655216941,
        "custom_id": "sarah",
        "id": "ca40abfd-de21-412b-94a0-14ccced9640d",
        "tags": null,
        "type": 0,
        "username": "sarah",
        "username_lower": "sarah"
    },
    "consumer_groups": [
        {
            "created_at": 1655217158,
            "id": "b78ae896-7afa-4fd4-9b5e-f1486345e803",
            "name": "marketing"
        }
    ]
}
 

Now, let’s change the consumer group functionality and assign different rates to different groups. We will set up the limit of 2000 requests per minute for “hr” group and 1000 requests per minute for “marketing”:

➜  demo-environment git:(main) ✗ http put :8001/consumer_groups/hr/overrides/plugins/rate-limiting-advanced config:='{"limit": [2000], "window_size": [60], "window_type": "sliding"}'
 
HTTP/1.1 201 Created
{
    "config": {
        "limit": [
            2000
        ],
        "window_size": [
            60
        ],
        "window_type": "sliding"
    },
    "consumer_group": "hr",
    "plugin": "rate-limiting-advanced"
}
 
➜  demo-environment git:(main) ✗ http put :8001/consumer_groups/marketing/overrides/plugins/rate-limiting-advanced config:='{"limit": [1000], "window_size": [60], "window_type": "sliding"}'
 
HTTP/1.1 201 Created
{
    "config": {
        "limit": [
            1000
        ],
        "window_size": [
            60
        ],
        "window_type": "sliding"
    },
    "consumer_group": "marketing",
    "plugin": "rate-limiting-advanced"
}

Now, lets see what we get when we test with Ann:

➜  demo-environment git:(main) ✗ http :8000/requests apikey:1kGHqaKRlFPuTh5T1GOyyNo4iG3Fsvfj
 
 
HTTP/1.1 200 OK
….
RateLimit-Limit: 2000
RateLimit-Remaining: 1999
……

As you can see, Ann now has a limit of 2000 RPM, while the plugin general config is 10. One can also test this with John and Sarah and confirm that the grouping is indeed working:

➜  demo-environment git:(main) ✗ http :8000/requests apikey:OFeajtvle099Dn1chzGLbeyDW8BlFHA0
 
 
HTTP/1.1 200 OK
….
RateLimit-Limit: 1000
RateLimit-Remaining: 999
….
 
 
➜  demo-environment git:(main) ✗http :8000/requests apikey:skIZ9Imx0fpJsAH36tP9PDpAELotbuuM
 
 
HTTP/1.1 200 OK
…..
RateLimit-Limit: 1000
RateLimit-Remaining: 999
……

As seen above, both John and Sarah have a limit of 1000 RPM as we wanted.

Summary

As we can see, it is very easy to configure Kong to rate limit your traffic with the relevant requirement for your use case — be it security, performance, or business use case. 

Share Post

Tags: