Rate limit in specific APIs via Nginx Ingress Controller.

Rate limit in specific APIs via Nginx Ingress Controller.

In this documentation, we'll play around with NGINX config to limit requests coming to our web app deployed in the cluster. It can be done with 2 parts.

  1. Rate limit based on IP address

  2. Rate limit regardless of IP address

Rate Limit based on IP address

Step 1: Define the limit_req_zone

When you set a zone, you define a rate, like 300r/m to allow 300 requests per minute, or 5r/s to allow 5 requests each second.

For instance:

  • limit_req_zone $binary_remote_addr zone=zone1:10m rate=300r/m;

  • limit_req_zone $binary_remote_addr zone=zone2:10m rate=5r/s;

Here, the states are kept in a 10 megabyte zone “zone1”, and an average request processing rate for this zone cannot exceed 5 requests per second or 300 requests per minute

A client IP address serves as a key. Note that instead of $remote_addr, the $binary_remote_addr variable is used here. The $binary_remote_addr variable’s size is always 4 bytes for IPv4 addresses or 16 bytes for IPv6 addresses. The stored state always occupies 64 bytes on 32-bit platforms and 128 bytes on 64-bit platforms. One megabyte zone can keep about 16 thousand 64-byte states or about 8 thousand 128-byte states.

To generalise,

Syntax:

limit_req_zone key zone=name:size rate=rate [sync];

Default:

Context:

http

Sets parameters for a shared memory zone that will keep states for different keys. In particular, the state stores the current number of excessive requests. The key can contain text, variables, and their combination. Requests with an empty key value are not accounted for.

In order to use rate limit in specific APIs. We are going to use map function in nginx.

Here's how this map works:

  1. If the $request_uri matches the regular expression ~^/web-api/auths/v1/auths (i.e., it starts with /web-api/auths/v1/auths), then the value in the map will be the binary representation of the client's remote IP address ($binary_remote_addr).

  2. If the $request_uri does not match the above pattern, the value in the map will be an empty string (specified by default "").

Here's configmap file we will apply to define limit_req_zone

....
data:
  allow-snippet-annotations: 'true'
  http-snippet: |
    limit_req_zone $binary_remote_addr_map zone=web_api:10m rate=300r/m;
    map $request_uri $binary_remote_addr_map {
      default "";
      ~^/web-api/auths/v1/auths $binary_remote_addr;
    }
....

In a nut shell, Here's a breakdown of the snippet:

  1. map $request_uri $binary_remote_addr_map: This creates a map named $binary_remote_addr_map based on the value of the $request_uri variable. The map is defined with a regular expression match. If the $request_uri matches the pattern ~^/web-api/auths/v1/auths (i.e., it starts with /web-api/auths/v1/auths), the value in the map will be the binary representation of the client's remote IP address ($binary_remote_addr). Otherwise, the map will have a default value of an empty string.

  2. limit_req_zone $binary_remote_addr_map zone=web_api:10m rate=300r/m;: This sets up a shared memory zone called web_api with a size of 10 megabytes (10m). The zone is used to store information about client IP addresses and their request rates. The rate=300r/m parameter specifies that the zone will allow a maximum of 300 requests per minute from each unique IP address.

By combining these two directives, the server effectively limits the rate of incoming requests to the /web-api/auths/v1/auths endpoint to 300 requests per minute per unique IP address.

Step 2: Apply limit_req using Ingress file

Next on we will be applying the directive defined to our server's endpoints using ingress files.

....
annotation:
  kubernetes.io/ingress.class: nginx
  ......
  nginx.ingress.kubernetes.io/configuration-snippet: |
    limit_req zone=web_api;
    limit_req_status 429;
  ......
....

Using the configuration snippet the zone would be placed under each of the location blocks in our config file, hence limiting requests for all the endpoints specified.

Here is the break down of the annotations:

  • limit_req zone=web_api;: This limit_req directive sets up rate limiting for the zone named web_api. The web_api zone is defined in the Nginx ingress configmap.

  • limit_req_status 429;: This limit_req_status directive sets the HTTP status code that will be returned to the client when the rate limit specified in the limit_req directive is exceeded. In this case, it's set to 429 Too Many Requests, which is a standard status code to indicate that the client has sent too many requests in a given amount of time.

Rate Limit regardless of IP address

Step 1: Define the limit_req_zone

As mentioned earlier, when setting up limit_req_zone, using a variable as the key ( i.e. $binary_remote_addr_map ) will result in a separate key being created for each IP address. This allows for rate limiting based on individual IP addresses, which works effectively.

However, if the goal is to limit requests regardless of the IP address, the key should be set to a static value. By doing this, all incoming requests will be grouped under a single key, and rate limiting will be applied universally without considering the IP addresses.

Here's configmap file we will apply to define limit_req_zone

....
data:
  allow-snippet-annotations: 'true'
  http-snippet: |
    limit_req_zone $addr_map zone=web_api:1k rate=300r/m;
    map $request_uri $addr_map {
      default "";
      ~^/web-api/auths/v1/auths global;
    }
....

Here, global has no semantic meaning. It's just a random bit of text. It could be anything. If the value matches then the static key will be passed in $addr_map. Hence making it a static key.

Step 2: Apply limit_req using Ingress file

Next on we will be applying the directive defined to our server's endpoints using ingress files.

....
annotation:
  kubernetes.io/ingress.class: nginx
  ......
  nginx.ingress.kubernetes.io/configuration-snippet: |
    limit_req zone=web_api;
    limit_req_status 429;
  ......
....

Conclusion

By using above process, we can rate limit on specific APIs which is based on or not on IP.